Browse Source

Merge pull request #10430 from electron/global-preloads

Add ability to set per-session preload scripts
Cheng Zhao 7 years ago
parent
commit
95cb601f2b

+ 18 - 0
atom/browser/api/atom_api_session.cc

@@ -17,6 +17,7 @@
 #include "atom/browser/atom_permission_manager.h"
 #include "atom/browser/browser.h"
 #include "atom/browser/net/atom_cert_verifier.h"
+#include "atom/browser/session_preferences.h"
 #include "atom/common/native_mate_converters/callback.h"
 #include "atom/common/native_mate_converters/content_converter.h"
 #include "atom/common/native_mate_converters/file_path_converter.h"
@@ -447,6 +448,8 @@ Session::Session(v8::Isolate* isolate, AtomBrowserContext* browser_context)
   content::BrowserContext::GetDownloadManager(browser_context)->
       AddObserver(this);
 
+  new SessionPreferences(browser_context);
+
   Init(isolate);
   AttachAsUserData(browser_context);
 }
@@ -680,6 +683,19 @@ void Session::CreateInterruptedDownload(const mate::Dictionary& options) {
       length, last_modified, etag, base::Time::FromDoubleT(start_time)));
 }
 
+void Session::SetPreloads(
+    const std::vector<base::FilePath::StringType>& preloads) {
+  auto* prefs = SessionPreferences::FromBrowserContext(browser_context());
+  DCHECK(prefs);
+  prefs->set_preloads(preloads);
+}
+
+std::vector<base::FilePath::StringType> Session::GetPreloads() const {
+  auto* prefs = SessionPreferences::FromBrowserContext(browser_context());
+  DCHECK(prefs);
+  return prefs->preloads();
+}
+
 v8::Local<v8::Value> Session::Cookies(v8::Isolate* isolate) {
   if (cookies_.IsEmpty()) {
     auto handle = Cookies::Create(isolate, browser_context());
@@ -766,6 +782,8 @@ void Session::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("getBlobData", &Session::GetBlobData)
       .SetMethod("createInterruptedDownload",
                  &Session::CreateInterruptedDownload)
+      .SetMethod("setPreloads", &Session::SetPreloads)
+      .SetMethod("getPreloads", &Session::GetPreloads)
       .SetProperty("cookies", &Session::Cookies)
       .SetProperty("protocol", &Session::Protocol)
       .SetProperty("webRequest", &Session::WebRequest);

+ 3 - 0
atom/browser/api/atom_api_session.h

@@ -6,6 +6,7 @@
 #define ATOM_BROWSER_API_ATOM_API_SESSION_H_
 
 #include <string>
+#include <vector>
 
 #include "atom/browser/api/trackable_object.h"
 #include "atom/browser/atom_blob_reader.h"
@@ -81,6 +82,8 @@ class Session: public mate::TrackableObject<Session>,
   void GetBlobData(const std::string& uuid,
                    const AtomBlobReader::CompletionCallback& callback);
   void CreateInterruptedDownload(const mate::Dictionary& options);
+  void SetPreloads(const std::vector<base::FilePath::StringType>& preloads);
+  std::vector<base::FilePath::StringType> GetPreloads() const;
   v8::Local<v8::Value> Cookies(v8::Isolate* isolate);
   v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
   v8::Local<v8::Value> WebRequest(v8::Isolate* isolate);

+ 1 - 1
atom/browser/api/atom_api_window.cc

@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "atom/browser/api/atom_api_window.h"
-#include "atom/common/native_mate_converters/value_converter.h"
 
 #include "atom/browser/api/atom_api_browser_view.h"
 #include "atom/browser/api/atom_api_menu.h"
@@ -17,6 +16,7 @@
 #include "atom/common/native_mate_converters/gurl_converter.h"
 #include "atom/common/native_mate_converters/image_converter.h"
 #include "atom/common/native_mate_converters/string16_converter.h"
+#include "atom/common/native_mate_converters/value_converter.h"
 #include "atom/common/options_switches.h"
 #include "base/command_line.h"
 #include "base/threading/thread_task_runner_handle.h"

+ 5 - 1
atom/browser/atom_browser_client.cc

@@ -17,6 +17,7 @@
 #include "atom/browser/atom_speech_recognition_manager_delegate.h"
 #include "atom/browser/child_web_contents_tracker.h"
 #include "atom/browser/native_window.h"
+#include "atom/browser/session_preferences.h"
 #include "atom/browser/web_contents_permission_helper.h"
 #include "atom/browser/web_contents_preferences.h"
 #include "atom/browser/window_list.h"
@@ -277,9 +278,12 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
   }
 
   content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
-  if (web_contents)
+  if (web_contents) {
     WebContentsPreferences::AppendExtraCommandLineSwitches(
         web_contents, command_line);
+    SessionPreferences::AppendExtraCommandLineSwitches(
+        web_contents->GetBrowserContext(), command_line);
+  }
 }
 
 void AtomBrowserClient::DidCreatePpapiPlugin(

+ 61 - 0
atom/browser/session_preferences.cc

@@ -0,0 +1,61 @@
+// Copyright (c) 2017 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "atom/browser/session_preferences.h"
+
+#include "atom/common/options_switches.h"
+#include "base/command_line.h"
+#include "base/memory/ptr_util.h"
+
+namespace atom {
+
+namespace {
+
+#if defined(OS_WIN)
+const base::FilePath::CharType kPathDelimiter = FILE_PATH_LITERAL(';');
+#else
+const base::FilePath::CharType kPathDelimiter = FILE_PATH_LITERAL(':');
+#endif
+
+}  // namespace
+
+// static
+int SessionPreferences::kLocatorKey = 0;
+
+SessionPreferences::SessionPreferences(content::BrowserContext* context) {
+  context->SetUserData(&kLocatorKey, base::WrapUnique(this));
+}
+
+SessionPreferences::~SessionPreferences() {
+}
+
+// static
+SessionPreferences* SessionPreferences::FromBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<SessionPreferences*>(context->GetUserData(&kLocatorKey));
+}
+
+// static
+void SessionPreferences::AppendExtraCommandLineSwitches(
+    content::BrowserContext* context, base::CommandLine* command_line) {
+  SessionPreferences* self = FromBrowserContext(context);
+  if (!self)
+    return;
+
+  base::FilePath::StringType preloads;
+  for (const auto& preload : self->preloads()) {
+    if (!base::FilePath(preload).IsAbsolute()) {
+      LOG(ERROR) << "preload script must have absolute path: " << preload;
+      continue;
+    }
+    if (preloads.empty())
+      preloads = preload;
+    else
+      preloads += kPathDelimiter + preload;
+  }
+  if (!preloads.empty())
+    command_line->AppendSwitchNative(switches::kPreloadScripts, preloads);
+}
+
+}  // namespace atom

+ 46 - 0
atom/browser/session_preferences.h

@@ -0,0 +1,46 @@
+// Copyright (c) 2017 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_BROWSER_SESSION_PREFERENCES_H_
+#define ATOM_BROWSER_SESSION_PREFERENCES_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/supports_user_data.h"
+#include "content/public/browser/browser_context.h"
+
+namespace base {
+class CommandLine;
+}
+
+namespace atom {
+
+class SessionPreferences : public base::SupportsUserData::Data {
+ public:
+  static SessionPreferences* FromBrowserContext(
+      content::BrowserContext* context);
+  static void AppendExtraCommandLineSwitches(
+      content::BrowserContext* context, base::CommandLine* command_line);
+
+  explicit SessionPreferences(content::BrowserContext* context);
+  ~SessionPreferences() override;
+
+  void set_preloads(const std::vector<base::FilePath::StringType>& preloads) {
+    preloads_ = preloads;
+  }
+  const std::vector<base::FilePath::StringType>& preloads() const {
+    return preloads_;
+  }
+
+ private:
+  // The user data key.
+  static int kLocatorKey;
+
+  std::vector<base::FilePath::StringType> preloads_;
+};
+
+}  // namespace atom
+
+#endif  // ATOM_BROWSER_SESSION_PREFERENCES_H_

+ 3 - 3
atom/browser/web_contents_preferences.h

@@ -61,14 +61,14 @@ class WebContentsPreferences
  private:
   friend class content::WebContentsUserData<WebContentsPreferences>;
 
+  // Get preferences value as integer possibly coercing it from a string
+  bool GetInteger(const std::string& attributeName, int* intValue);
+
   static std::vector<WebContentsPreferences*> instances_;
 
   content::WebContents* web_contents_;
   base::DictionaryValue web_preferences_;
 
-  // Get preferences value as integer possibly coercing it from a string
-  bool GetInteger(const std::string& attributeName, int* intValue);
-
   DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences);
 };
 

+ 1 - 0
atom/common/options_switches.cc

@@ -180,6 +180,7 @@ const char kAppPath[] = "app-path";
 const char kBackgroundColor[]  = "background-color";
 const char kPreloadScript[]    = "preload";
 const char kPreloadURL[]       = "preload-url";
+const char kPreloadScripts[]   = "preload-scripts";
 const char kNodeIntegration[]  = "node-integration";
 const char kContextIsolation[] = "context-isolation";
 const char kGuestInstanceID[]  = "guest-instance-id";

+ 1 - 0
atom/common/options_switches.h

@@ -91,6 +91,7 @@ extern const char kAppPath[];
 extern const char kBackgroundColor[];
 extern const char kPreloadScript[];
 extern const char kPreloadURL[];
+extern const char kPreloadScripts[];
 extern const char kNodeIntegration[];
 extern const char kContextIsolation[];
 extern const char kGuestInstanceID[];

+ 12 - 0
docs/api/session.md

@@ -384,6 +384,18 @@ the initial state will be `interrupted`. The download will start only when the
 
 Clears the session’s HTTP authentication cache.
 
+#### `ses.setPreloads(preloads)`
+
+* `preloads` String[] - An array of absolute path to preload scripts
+
+Adds scripts that will be executed on ALL web contents that are associated with
+this session just before normal `preload` scripts run.
+
+#### `ses.getPreloads()`
+
+Returns `String[]` an array of paths to preload scripts that have been
+registered.
+
 ### Instance Properties
 
 The following properties are available on instances of `Session`:

+ 2 - 0
filenames.gypi

@@ -283,6 +283,8 @@
       'atom/browser/relauncher.h',
       'atom/browser/render_process_preferences.cc',
       'atom/browser/render_process_preferences.h',
+      'atom/browser/session_preferences.cc',
+      'atom/browser/session_preferences.h',
       'atom/browser/ui/accelerator_util.cc',
       'atom/browser/ui/accelerator_util.h',
       'atom/browser/ui/accelerator_util_mac.mm',

+ 10 - 2
lib/renderer/init.js

@@ -67,6 +67,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev
 let nodeIntegration = 'false'
 let webviewTag = 'false'
 let preloadScript = null
+let preloadScripts = []
 let isBackgroundPage = false
 let appPath = null
 for (let arg of process.argv) {
@@ -86,9 +87,16 @@ for (let arg of process.argv) {
     appPath = arg.substr(arg.indexOf('=') + 1)
   } else if (arg.indexOf('--webview-tag=') === 0) {
     webviewTag = arg.substr(arg.indexOf('=') + 1)
+  } else if (arg.indexOf('--preload-scripts') === 0) {
+    preloadScripts = arg.substr(arg.indexOf('=') + 1).split(path.delimiter)
   }
 }
 
+// The webContents preload script is loaded after the session preload scripts.
+if (preloadScript) {
+  preloadScripts.push(preloadScript)
+}
+
 if (window.location.protocol === 'chrome-devtools:') {
   // Override some inspector APIs.
   require('./inspector')
@@ -171,8 +179,8 @@ if (nodeIntegration === 'true') {
   })
 }
 
-// Load the script specfied by the "preload" attribute.
-if (preloadScript) {
+// Load the preload scripts.
+for (const preloadScript of preloadScripts) {
   try {
     require(preloadScript)
   } catch (error) {

+ 38 - 1
spec/api-browser-window-spec.js

@@ -9,7 +9,7 @@ const http = require('http')
 const {closeWindow} = require('./window-helpers')
 
 const {ipcRenderer, remote, screen} = require('electron')
-const {app, ipcMain, BrowserWindow, BrowserView, protocol, webContents} = remote
+const {app, ipcMain, BrowserWindow, BrowserView, protocol, session, webContents} = remote
 
 const isCI = remote.getGlobal('isCi')
 const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled')
@@ -1021,6 +1021,43 @@ describe('BrowserWindow module', () => {
       })
     })
 
+    describe('session preload scripts', function () {
+      const preloads = [
+        path.join(fixtures, 'module', 'set-global-preload-1.js'),
+        path.join(fixtures, 'module', 'set-global-preload-2.js')
+      ]
+      const defaultSession = session.defaultSession
+
+      beforeEach(() => {
+        assert.deepEqual(defaultSession.getPreloads(), [])
+        defaultSession.setPreloads(preloads)
+      })
+      afterEach(() => {
+        defaultSession.setPreloads([])
+      })
+
+      it('can set multiple session preload script', function () {
+        assert.deepEqual(defaultSession.getPreloads(), preloads)
+      })
+
+      it('loads the script before other scripts in window including normal preloads', function (done) {
+        ipcMain.once('vars', function (event, preload1, preload2, preload3) {
+          assert.equal(preload1, 'preload-1')
+          assert.equal(preload2, 'preload-1-2')
+          assert.equal(preload3, 'preload-1-2-3')
+          done()
+        })
+        w.destroy()
+        w = new BrowserWindow({
+          show: false,
+          webPreferences: {
+            preload: path.join(fixtures, 'module', 'set-global-preload-3.js')
+          }
+        })
+        w.loadURL('file://' + path.join(fixtures, 'api', 'preloads.html'))
+      })
+    })
+
     describe('"node-integration" option', () => {
       it('disables node integration when specified to false', (done) => {
         const preload = path.join(fixtures, 'module', 'send-later.js')

+ 7 - 0
spec/fixtures/api/preloads.html

@@ -0,0 +1,7 @@
+<html>
+<body>
+<script type="text/javascript" charset="utf-8">
+require('electron').ipcRenderer.send('vars', preload1, preload2, preload3);
+</script>
+</body>
+</html>

+ 1 - 0
spec/fixtures/module/set-global-preload-1.js

@@ -0,0 +1 @@
+window.preload1 = 'preload-1'

+ 1 - 0
spec/fixtures/module/set-global-preload-2.js

@@ -0,0 +1 @@
+window.preload2 = window.preload1 + '-2'

+ 1 - 0
spec/fixtures/module/set-global-preload-3.js

@@ -0,0 +1 @@
+window.preload3 = window.preload2 + '-3'

+ 1 - 1
spec/fixtures/module/set-global.js

@@ -1 +1 @@
-window.test = 'preload'
+if (!window.test) window.test = 'preload'