Browse Source

feat: implement allowFileAccess loadExtension option (#27702)

Co-authored-by: Сковорода Никита Андреевич <[email protected]>
Co-authored-by: Samuel Maddock <[email protected]>
Jeremy Rose 4 years ago
parent
commit
c3bce9c664

+ 2 - 2
docs/api/browser-window.md

@@ -735,7 +735,7 @@ The method will also not return if the extension's manifest is missing or incomp
 is emitted.
 
 **Note:** This method is deprecated. Instead, use
-[`ses.loadExtension(path)`](session.md#sesloadextensionpath).
+[`ses.loadExtension(path)`](session.md#sesloadextensionpath-options).
 
 #### `BrowserWindow.removeExtension(name)` _Deprecated_
 
@@ -777,7 +777,7 @@ The method will also not return if the extension's manifest is missing or incomp
 is emitted.
 
 **Note:** This method is deprecated. Instead, use
-[`ses.loadExtension(path)`](session.md#sesloadextensionpath).
+[`ses.loadExtension(path)`](session.md#sesloadextensionpath-options).
 
 #### `BrowserWindow.removeDevToolsExtension(name)` _Deprecated_
 

+ 1 - 1
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')

+ 10 - 2
docs/api/session.md

@@ -743,9 +743,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<Extension>` - resolves when the extension is loaded.
 
@@ -768,7 +772,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.
 })

+ 1 - 1
docs/tutorial/devtools-extension.md

@@ -96,7 +96,7 @@ of the extension is not working as expected.
 [devtools-extension]: https://developer.chrome.com/extensions/devtools
 [session]: ../api/session.md
 [react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
-[load-extension]: ../api/session.md#sesloadextensionpath
+[load-extension]: ../api/session.md#sesloadextensionpath-options
 [extension-structure]: ../api/structures/extension.md
 [remove-extension]: ../api/session.md#sesremoveextensionextensionid
 [electron-devtools-installer]: https://github.com/MarshallOfSound/electron-devtools-installer

+ 12 - 2
shell/browser/api/electron_api_session.cc

@@ -795,7 +795,8 @@ std::vector<base::FilePath> Session::GetPreloads() const {
 
 #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
 v8::Local<v8::Promise> Session::LoadExtension(
-    const base::FilePath& extension_path) {
+    const base::FilePath& extension_path,
+    gin::Arguments* args) {
   v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
   gin_helper::Promise<const extensions::Extension*> promise(isolate);
   v8::Local<v8::Promise> handle = promise.GetHandle();
@@ -812,10 +813,19 @@ v8::Local<v8::Promise> 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::ElectronExtensionSystem*>(
       extensions::ExtensionSystem::Get(browser_context()));
   extension_system->LoadExtension(
-      extension_path,
+      extension_path, load_flags,
       base::BindOnce(
           [](gin_helper::Promise<const extensions::Extension*> promise,
              const extensions::Extension* extension,

+ 2 - 1
shell/browser/api/electron_api_session.h

@@ -136,7 +136,8 @@ class Session : public gin::Wrappable<Session>,
 #endif
 
 #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
-  v8::Local<v8::Promise> LoadExtension(const base::FilePath& extension_path);
+  v8::Local<v8::Promise> LoadExtension(const base::FilePath& extension_path,
+                                       gin::Arguments* args);
   void RemoveExtension(const std::string& extension_id);
   v8::Local<v8::Value> GetExtension(const std::string& extension_id);
   v8::Local<v8::Value> GetAllExtensions();

+ 9 - 4
shell/browser/extensions/electron_extension_loader.cc

@@ -30,7 +30,8 @@ using LoadErrorBehavior = ExtensionRegistrar::LoadErrorBehavior;
 namespace {
 
 std::pair<scoped_refptr<const Extension>, 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<scoped_refptr<const Extension>, std::string> LoadUnpacked(
     return std::make_pair(nullptr, err);
   }
 
-  int load_flags = Extension::FOLLOW_SYMLINKS_ANYWHERE;
   std::string load_error;
   scoped_refptr<Extension> extension = file_util::LoadExtension(
       extension_dir, Manifest::COMMAND_LINE, load_flags, &load_error);
@@ -76,10 +76,11 @@ ElectronExtensionLoader::~ElectronExtensionLoader() = default;
 
 void ElectronExtensionLoader::LoadExtension(
     const base::FilePath& extension_dir,
+    int load_flags,
     base::OnceCallback<void(const Extension*, const std::string&)> 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)));
 }
@@ -175,9 +176,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;

+ 1 - 0
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<void(const Extension* extension,
                                              const std::string&)> cb);
 

+ 2 - 1
shell/browser/extensions/electron_extension_system.cc

@@ -56,8 +56,9 @@ ElectronExtensionSystem::~ElectronExtensionSystem() = default;
 
 void ElectronExtensionSystem::LoadExtension(
     const base::FilePath& extension_dir,
+    int load_flags,
     base::OnceCallback<void(const Extension*, const std::string&)> cb) {
-  extension_loader_->LoadExtension(extension_dir, std::move(cb));
+  extension_loader_->LoadExtension(extension_dir, load_flags, std::move(cb));
 }
 
 void ElectronExtensionSystem::FinishInitialization() {

+ 1 - 0
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<void(const Extension*, const std::string&)> cb);
 
   // Finish initialization for the shell extension system.