13

I have built a Chrome extension that I have been installing into Chrome using Selenium.

Now I would like to build my own Chromium from source so that my extension is pre-bundled into the built distributed package so that I don't have to worry about needing Selenium to install the CRX file for my use case.

I have found several forums where people suggested they were going to try this, but none of them ended up seeming like they were successful.

I found some tips on how a system administrator can force install extensions into chromium for users in their network: https://support.google.com/chrome/a/answer/6306504?hl=en But that is for chrome enterprise, probably not going to be useful for me.

Here is another post which talks about how to offline install chrome extensions. I might be able to use some of this to make what I want happen.

Has anyone had success actually building into chromium a CRX so that the CRX is just installed automatically?

Quick update:

I just want to note: I'm installing my custom version of chrome with an InnoSetup installer. So I do have the chance to, after my chromium fork is installed, do some custom execution steps post install. And my extensions are hosted on the chrome web store and approved.

So if there is some way to programmatically install chrome extensions into a Chromium installation from the web store, I would could easily use that.

Nicholas DiPiazza
  • 10,029
  • 11
  • 83
  • 152
  • Can you add any details like: code used, error problem encountered? [How do I ask a good question?](http://stackoverflow.com/help/how-to-ask), [How to create a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) Show the community what you have tried. – ReyAnthonyRenacia May 02 '18 at 09:34
  • You are asking for a reproducer, but my question is more general like "how do I get started with this process". a process related question doesn't have a "steps to reproduce." And I did attempt to explain what I've tried. – Nicholas DiPiazza May 02 '18 at 12:58
  • Is it just an extension or component extension? Remember extensions can be removed from Chromium by users and component extensions on the other hand can't be removed – Asesh May 09 '18 at 05:05
  • @Asesh thanks so much for your reply. They are just typical extensions. That is fine if they remove these extensions. the key is that by default they need to be installed. – Nicholas DiPiazza May 09 '18 at 05:11
  • I can answer this question if it's component extension. I have worked on bundling extensions as both extensions and component extensions in our Chromium fork but don't remember much about bundling it as extension only. You will have to modify your C++ source file to do so. Let me know if you want to bundle it as component extension. I won't be able to answer your question ASAP cause am at work but will do it later – Asesh May 09 '18 at 05:18
  • @Asesh the extensions are in CRX format. As long as I can install CRX files as component extensions, that's perfect! – Nicholas DiPiazza May 09 '18 at 05:20
  • @NicholasDiPiazza To bundle your extension as component extension, you will have to unpack that crx extension and place it in ``src\chrome\browser\resources\your_extension_folder`` and make changes in C++ source code too. So in other words, you can't directly make crx file as component extension. – Asesh May 09 '18 at 11:00
  • I just went through our Chromium fork's repo history and looks like it's easy to bundle crx file as an extension. For component extension, you would have to make more changes to C++ source files. I will try to answer your question when I will be free – Asesh May 09 '18 at 11:15
  • cool! whatever is easier. I'm not opposed to making some source changes if it becomes necessary! – Nicholas DiPiazza May 09 '18 at 12:59
  • Regardless, you will have to modify C++ source code. BTW I meant relatively easy than making component extension. I will have to debug and test during my free time, so it might take a day or two. Will post as an answer when am done – Asesh May 09 '18 at 15:55
  • 1
    no worries man. a bounty awaits you. – Nicholas DiPiazza May 09 '18 at 16:16
  • FYI, you should add C++ tag too. – Asesh May 12 '18 at 10:26

1 Answers1

15

This has been tested in our Chromium fork versions 66.0.3359.139 to 7x.x.x on Windows 10. The extension bundling differs for Linux in the first step(tested in version 105.0.5137). I have also tried to make it as easy as possible to accomplish this task. There are a couple of things you will have to do to accomplish this:

  1. Add your Chromium extension (.crx) file to a list of default extensions to bundle with mini installer
  2. Find out the ID of that extension
  3. Automate extension installation process
  4. By pass Chrome Web Store check
  5. Build mini installer to install your Chromium fork

1: For Windows: To bundle your extension with the installer, you will have to modify: src\chrome\browser\extensions\default_extensions\BUILD.gn file. Suppose tab_capture.crx is your extension then it's contents should look something like this:

if (is_win) {
copy("default_extensions") {
sources = [
  "external_extensions.json",
  "tab_capture.crx"
]
outputs = [
  "$root_out_dir/extensions/{{source_file_part}}",
]

I have just appended tab_capture.crx and have not modified anything else. Your extension file should be in this location: src\chrome\browser\extensions\default_extensions\tab_capture.crx

1: For Linux (Thanks to @dmena):

if (is_win) {
  // Omitted Chromium code
} else {
  # No-op on non-Windows.
  # Added copy policy
  copy("default_extensions") {
    sources = [ "tab_capture.crx" ]
    outputs = [ "$root_out_dir/extensions/{{source_file_part}}" ]
  }
  # end of added copy policy

2: Each extension will have a unique ID assigned to it by Chromium to identify that extension. To find out the ID of your extension you should go to chrome://extensions/ page and drag and drop your crx file. A confirmation dialog box should popup. Click Add extension button and make sure Developer mode is enabled then your ID should be visible but the extension will be disabled as shown below:

enter image description here

3: Now, we will start modifying C++ source files. Let's declare our extension's name and ID. We will do so in these files: src\extensions\common\extension.h

namespace extensions {

extern const int kOurNumExtensions;
extern const char* kOurExtensionIds[];
extern const char* kOurExtensionFilenames[];

I have just declared those variables below extensions namespace. Remember, extension ID that we are assigning below must match with the extension ID assigned by Chromium.

Those variables' definition in: src\extensions\common\extension.cc

namespace extensions {

const char* kOurExtensionIds[] = {
    "aaaaaaaaaaaaaaaaaaaaaaaaaaa"}; // Assumed extension ID of tab_capture
const char* kOurExtensionFilenames[] = {
    "tab_capture.crx"};
const int kOurNumExtensions = 1;

Chromium will create a profile when it's first launched. So we assume no profile exists yet because we will install our extension at run time when it's first launched. A profile on a Windows machine should typically exists here: C:\Users\Username\AppData\Local\CompanyName\ChromiumForkName so make sure to delete CompanyName folder before launching Chromium. Of course, we can do the installation process after a profile has been created too. For that you will have to check if our extension has been installed or not to prevent multiple attempts of installation.

Chromium handles startup browser creation stuff in this file: src\chrome\browser\ui\startup\startup_browser_creator.cc so we install this extension after a profile has been initialized and browser has been launched. You will have to add some header files too. We will do so in LaunchBrowser method:

// Add these header files cause we we will be using them
#include "base/path_service.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/common/chrome_paths.h"
#include "extensions/browser/extension_system.h"

void StartupBrowserCreator::LaunchBrowser(
const base::CommandLine& command_line,
Profile* profile,
const base::FilePath& cur_dir,
chrome::startup::IsProcessStartup process_startup,
chrome::startup::IsFirstRun is_first_run,
std::unique_ptr<LaunchModeRecorder> launch_mode_recorder) {
    // Omitted Chromium code
    in_synchronous_profile_launch_ = false;

    // Install our extension
    base::FilePath extension_dir;
    if (first_run::IsChromeFirstRun() &&
        base::PathService::Get(chrome::DIR_EXTERNAL_EXTENSIONS,         
&extension_dir)) {
        for (int i = 0; i < extensions::kOurNumExtensions; ++i) {
            base::FilePath file_to_install(extension_dir.AppendASCII(
                extensions::kOurExtensionFilenames[i]));
            std::unique_ptr<ExtensionInstallPrompt> prompt(
                new 
ExtensionInstallPrompt(chrome::FindBrowserWithProfile(profile)- 
>tab_strip_model()->GetActiveWebContents()));
            scoped_refptr<extensions::CrxInstaller> crx_installer(extensions::CrxInstaller::Create(
                extensions::ExtensionSystem::Get(profile)- 
>extension_service(), std::move(prompt)));
            crx_installer->set_error_on_unsupported_requirements(true);
            crx_installer->set_off_store_install_allow_reason(   
extensions::CrxInstaller::OffStoreInstallAllowedFromSettingsPage);
            crx_installer->set_install_immediately(true);
            crx_installer->InstallCrx(file_to_install);
        }
    }
    // End of install our extension

    // Chromium code
    profile_launch_observer.Get().AddLaunched(profile);
}

That should install our extension but as we want our extension to be forcefully installed without any user interaction, let's do it here: chrome/browser/extensions/extension_install_prompt.cc

void ExtensionInstallPrompt::ShowDialog(
const DoneCallback& done_callback,
const Extension* extension,
const SkBitmap* icon,
std::unique_ptr<Prompt> prompt,
std::unique_ptr<const PermissionSet> custom_permissions,
const ShowDialogCallback& show_dialog_callback) {
// Chromium code
return;
}

// Don't show add extension prompt for our extensions
for (int i = 0; i < extensions::kOurNumExtensions; ++i) {
    if (extension->id() == extensions::kOurExtensionIds[i]) {
        
        // Note: The line below won't work in recent versions of Chromium. So if you are using a recent version then use the code just below it instead of this one
        base::ResetAndReturn(&done_callback_).Run(
           Result::ACCEPTED);

        // Note: For recent versions of Chromium. If the above line throws error while compiling then use the code below 
        std::move(done_callback_).Run(
           DoneCallbackPayload(Result::ACCEPTED));
        return;
    }
}
// End of don't show add extension prompt for our extensions

// Chromium code
LoadImageIfNeeded();

4: Even if we automate the installation process, Chromium will disable our extension cause it was not installed from Chrome Web Store. It's handled here: src\chrome\browser\extensions\install_verifier.cc in this method:

bool InstallVerifier::MustRemainDisabled(const Extension* extension,
                                     disable_reason::DisableReason* reason,
                                     base::string16* error) const {
// Omitted Chromium code

// Chromium code
if (Manifest::IsUnpackedLocation(extension->location())) {
MustRemainDisabledHistogram(UNPACKED);
return false;
}

// Always enable our tab capture extension
// Use loop if you have more than one extension
if (extension->id() == extensions::kOurExtensionIds[0]) {
    return false;
}
// End of always enable our tab capture extension

// Chromium code
if (extension->location() == Manifest::COMPONENT) {
    MustRemainDisabledHistogram(COMPONENT);
    return false;
}

This will ensure that our extension will be enabled as we are bypassing Chrome Web Store check.

If you don't want your extension to be uninstalled and remain enabled then you can do so by modifying this file: chrome/browser/extensions/standard_management_policy_provider.cc and modify these methods: MustRemainInstalled and MustRemainEnabled

5: Now you can build mini installer by executing this command

ninja -C out\BuildFolder mini_installer

The above command will build mini_installer.exe. Note If you pass --system-level argument to mini_installer.exe then it should install your Chromium fork in Program files folder. After the installation is complete your crx file should be located here: C:\Program Files (x86)\YourChromium\Application\66.0.3359.139\Extensions\tab_capture.crx.

Chromium will unpack and install this crx file to your profile: C:\Users\Username\AppData\Local\YourChromium\User Data\Default\Extensions (Assumed default profile)

Note: To improve code readability and ease of use, you could use container classes to hold those extension file names and their corresponding IDs and use it easily in a range based for loop.

Let me know if it works. Took longer than expected cause I noticed lots of changes in their code base and our old code was not working in this latest Chromium build. I am sure, I have not missed anything else :)

Asesh
  • 3,186
  • 2
  • 21
  • 31
  • Posted a question here about the mini installer portion: https://stackoverflow.com/questions/50305917/chromium-how-to-make-an-actual-installer-out-of-mini-installer-exe – Nicholas DiPiazza May 12 '18 at 11:51
  • do you know how to do it for mac? – JOhn Feb 16 '19 at 23:38
  • I tried the instructions. It compiles ok, but I does not see the extension in chrome://extensions – JOhn Feb 16 '19 at 23:45
  • @JOhn I don't do Chromium development on Mac, so I have no idea. If it's not working for you then you might want to debug your code. This stuff is handled by browser process so just debug the main process and the code that I have referenced above. You might have to modify step 1. ``tab_capture.crx`` on step 1 is just for Windows cause it's declared in ``if(is_win)`` conditional statement. FYI, It still works with Chromium 70.x, though I have not tried it on 71.x yet. – Asesh Feb 17 '19 at 03:20
  • i tried the instructions for windows 10. unfortunately compilation fails with error message. lld-link: error: undefined symbol: int extensions::kOurNumExtensions >>> referenced by .\..\..\chrome\browser\ui\startup\startup_browser_creator.cc:645 – Anjul khanal Sep 02 '21 at 07:16
  • @onzool In your case the linker is unable to find the definition of `extensions::kOurNumExtensions`. Make sure that you have declared and defined `extensions::kOurNumExtensions` – Asesh Sep 03 '21 at 03:39
  • 2
    I successfully implement the change on Linux by including the crx file position in the group("default_extension) in the src\chrome\browser\extensions\default_extensions\BUILD.gn. However, when launched, I got an error message, "Package invalid CRX_FILE_NOT_READABLE" displayed over startup tab page and the extension was not installed. We could install the extension manually from that CRX and worked well. Can you give me any suggestion on what happened and how to fix it – patpat Oct 20 '21 at 11:26
  • 2
    I just figured it out on how to include build-in extension on Linux. Adding a copy policy that copies your CRX to your output file under extensions folder. The rest was the same. If you don't want to use mini-installer, then just include the default_extension s directory in your public deps in your extension/build.gn file. Think this also works on MacOS – patpat Oct 21 '21 at 14:59
  • @patpat Great, fell free to modify my answer and include yours to support Linux too so that it will be helpful to others. Unfortunately I don't do Chromium development on Linux. – Asesh Oct 22 '21 at 01:49
  • @Asesh, thank you for your information. Another question is how can i prevent user from removing my bundled extension? – herb Apr 07 '22 at 03:37
  • @herb I have mentioned above that you can modify `MustRemainInstalled` method, not sure if it works in the current version of Chromium. Though users can still delete `crx` files to remote it completely. There's another better way to do it, it's called component extension. It's implementation is beyond the topic of this question. Google!! – Asesh Apr 07 '22 at 14:52
  • thank you @Asesh, i modified MustRemainInstalled and MustRemainEnabled, now my extension cannot be uninstalled, but it can still be disabled. MustRemainEnabled is not called at all while MustRemainInstalled is called and works as expected. Any idea? – herb Apr 10 '22 at 02:08
  • 1
    finally i found that **UserMayModifySettings** needs to be modified to prevent extension from being disabled – herb Apr 10 '22 at 05:07
  • @patpat I am getting the same error that you mentioned even though I have the .crx file in the output folder/extension. Can you elaborate on how you resolved it? – Obaid Ur Rehman Jun 14 '22 at 23:43
  • 1
    @ObaidUrRehman Dmena has made some changes that might resolve your issue on a different platform – Asesh Jul 01 '22 at 08:35
  • Asesh I was able to install the extension using the above steps. One issue is the extension isn't auto-pinned in chromium. Is there any standard way to achieve this? – Obaid Ur Rehman Jan 04 '23 at 19:49
  • @ObaidUrRehman There's no standard way to do so. You might have to manually pin the extension. – Asesh Jan 05 '23 at 03:07