diff --git a/docs/api/extensions.md b/docs/api/extensions.md index 7eb2a18bc20d..3bd27f4fd1c7 100644 --- a/docs/api/extensions.md +++ b/docs/api/extensions.md @@ -15,7 +15,7 @@ extension capabilities. Electron only supports loading unpacked extensions (i.e., `.crx` files do not work). Extensions are installed per-`session`. To load an extension, call -[`ses.loadExtension`](session.md#sesloadextensionpath): +[`ses.loadExtension`](session.md#sesloadextensionpath-options): ```js const { session } = require('electron') diff --git a/docs/api/session.md b/docs/api/session.md index 73e197e523ba..25b3af870453 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -750,9 +750,13 @@ will not work on non-persistent (in-memory) sessions. **Note:** On macOS and Windows 10 this word will be removed from the OS custom dictionary as well -#### `ses.loadExtension(path)` +#### `ses.loadExtension(path[, options])` * `path` String - Path to a directory containing an unpacked Chrome extension +* `options` Object (optional) + * `allowFileAccess` Boolean - Whether to allow the extension to read local files over `file://` + protocol and inject content scripts into `file://` pages. This is required e.g. for loading + devtools extensions on `file://` URLs. Defaults to false. Returns `Promise` - resolves when the extension is loaded. @@ -775,7 +779,11 @@ const { app, session } = require('electron') const path = require('path') app.on('ready', async () => { - await session.defaultSession.loadExtension(path.join(__dirname, 'react-devtools')) + await session.defaultSession.loadExtension( + path.join(__dirname, 'react-devtools'), + // allowFileAccess is required to load the devtools extension on file:// URLs. + { allowFileAccess: true } + ) // Note that in order to use the React DevTools extension, you'll need to // download and unzip a copy of the extension. }) diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index 9bc7117a2bfd..7b904da9d1d8 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -795,7 +795,8 @@ std::vector Session::GetPreloads() const { #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) v8::Local Session::LoadExtension( - const base::FilePath& extension_path) { + const base::FilePath& extension_path, + gin::Arguments* args) { v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); gin_helper::Promise promise(isolate); v8::Local handle = promise.GetHandle(); @@ -812,10 +813,19 @@ v8::Local Session::LoadExtension( return handle; } + int load_flags = extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE; + gin_helper::Dictionary options; + if (args->GetNext(&options)) { + bool allowFileAccess = false; + options.Get("allowFileAccess", &allowFileAccess); + if (allowFileAccess) + load_flags |= extensions::Extension::ALLOW_FILE_ACCESS; + } + auto* extension_system = static_cast( extensions::ExtensionSystem::Get(browser_context())); extension_system->LoadExtension( - extension_path, + extension_path, load_flags, base::BindOnce( [](gin_helper::Promise promise, const extensions::Extension* extension, diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index 8c69ccc0f2c4..e76d67c0cb93 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -136,7 +136,8 @@ class Session : public gin::Wrappable, #endif #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - v8::Local LoadExtension(const base::FilePath& extension_path); + v8::Local LoadExtension(const base::FilePath& extension_path, + gin::Arguments* args); void RemoveExtension(const std::string& extension_id); v8::Local GetExtension(const std::string& extension_id); v8::Local GetAllExtensions(); diff --git a/shell/browser/extensions/electron_extension_loader.cc b/shell/browser/extensions/electron_extension_loader.cc index 3ef18839bb37..73bb4f19fcc9 100644 --- a/shell/browser/extensions/electron_extension_loader.cc +++ b/shell/browser/extensions/electron_extension_loader.cc @@ -30,7 +30,8 @@ using LoadErrorBehavior = ExtensionRegistrar::LoadErrorBehavior; namespace { std::pair, std::string> LoadUnpacked( - const base::FilePath& extension_dir) { + const base::FilePath& extension_dir, + int load_flags) { // app_shell only supports unpacked extensions. // NOTE: If you add packed extension support consider removing the flag // FOLLOW_SYMLINKS_ANYWHERE below. Packed extensions should not have symlinks. @@ -40,7 +41,6 @@ std::pair, std::string> LoadUnpacked( return std::make_pair(nullptr, err); } - int load_flags = Extension::FOLLOW_SYMLINKS_ANYWHERE; std::string load_error; scoped_refptr extension = file_util::LoadExtension( extension_dir, Manifest::COMMAND_LINE, load_flags, &load_error); @@ -75,10 +75,11 @@ ElectronExtensionLoader::~ElectronExtensionLoader() = default; void ElectronExtensionLoader::LoadExtension( const base::FilePath& extension_dir, + int load_flags, base::OnceCallback cb) { base::PostTaskAndReplyWithResult( GetExtensionFileTaskRunner().get(), FROM_HERE, - base::BindOnce(&LoadUnpacked, extension_dir), + base::BindOnce(&LoadUnpacked, extension_dir, load_flags), base::BindOnce(&ElectronExtensionLoader::FinishExtensionLoad, weak_factory_.GetWeakPtr(), std::move(cb))); } @@ -174,9 +175,13 @@ void ElectronExtensionLoader::LoadExtensionForReload( LoadErrorBehavior load_error_behavior) { CHECK(!path.empty()); + // TODO(nornagon): we should save whether file access was granted + // when loading this extension and retain it here. As is, reloading an + // extension will cause the file access permission to be dropped. + int load_flags = Extension::FOLLOW_SYMLINKS_ANYWHERE; base::PostTaskAndReplyWithResult( GetExtensionFileTaskRunner().get(), FROM_HERE, - base::BindOnce(&LoadUnpacked, path), + base::BindOnce(&LoadUnpacked, path, load_flags), base::BindOnce(&ElectronExtensionLoader::FinishExtensionReload, weak_factory_.GetWeakPtr(), extension_id)); did_schedule_reload_ = true; diff --git a/shell/browser/extensions/electron_extension_loader.h b/shell/browser/extensions/electron_extension_loader.h index ed78d37015f6..c08a7924e880 100644 --- a/shell/browser/extensions/electron_extension_loader.h +++ b/shell/browser/extensions/electron_extension_loader.h @@ -37,6 +37,7 @@ class ElectronExtensionLoader : public ExtensionRegistrar::Delegate { // Loads an unpacked extension from a directory synchronously. Returns the // extension on success, or nullptr otherwise. void LoadExtension(const base::FilePath& extension_dir, + int load_flags, base::OnceCallback cb); diff --git a/shell/browser/extensions/electron_extension_system.cc b/shell/browser/extensions/electron_extension_system.cc index df64d0970022..ba88fa1c7663 100644 --- a/shell/browser/extensions/electron_extension_system.cc +++ b/shell/browser/extensions/electron_extension_system.cc @@ -55,8 +55,9 @@ ElectronExtensionSystem::~ElectronExtensionSystem() = default; void ElectronExtensionSystem::LoadExtension( const base::FilePath& extension_dir, + int load_flags, base::OnceCallback cb) { - extension_loader_->LoadExtension(extension_dir, std::move(cb)); + extension_loader_->LoadExtension(extension_dir, load_flags, std::move(cb)); } void ElectronExtensionSystem::FinishInitialization() { diff --git a/shell/browser/extensions/electron_extension_system.h b/shell/browser/extensions/electron_extension_system.h index 39f65dbee92c..d6f6b7b3b2f4 100644 --- a/shell/browser/extensions/electron_extension_system.h +++ b/shell/browser/extensions/electron_extension_system.h @@ -41,6 +41,7 @@ class ElectronExtensionSystem : public ExtensionSystem { // success, or nullptr otherwise. void LoadExtension( const base::FilePath& extension_dir, + int load_flags, base::OnceCallback cb); // Finish initialization for the shell extension system.