diff --git a/.circleci/config.yml b/.circleci/config.yml index 078e934443dd..5ee057c7f974 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -313,7 +313,7 @@ step-setup-goma-for-build: &step-setup-goma-for-build npm install mkdir third_party node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })" - node -e "require('./src/utils/goma.js').ensure()" + third_party/goma/goma_ctl.py ensure_start echo 'export GN_GOMA_FILE='`node -e "console.log(require('./src/utils/goma.js').gnFilePath)"` >> $BASH_ENV echo 'export LOCAL_GOMA_DIR='`node -e "console.log(require('./src/utils/goma.js').dir)"` >> $BASH_ENV cd .. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ab2ac48d731a..d10b60555cb0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,9 +13,3 @@ DEPS @electron/wg-upgrades # Security WG /lib/browser/rpc-server.ts @electron/wg-security - -# Remote Change Disliker -/lib/browser/remote/ @nornagon -/lib/renderer/remote/ @nornagon -/lib/renderer/api/remote.ts @nornagon -/docs/api/remote.md @nornagon diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index a1a336a131e1..000000000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve Electron - ---- - - - -### Preflight Checklist - - -* [ ] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project. -* [ ] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to. -* [ ] I have searched the issue tracker for an issue that matches the one I want to file, without success. - -### Issue Details - -* **Electron Version:** - * -* **Operating System:** - * -* **Last Known Working Electron version:** - * - -### Expected Behavior - - -### Actual Behavior - - -### To Reproduce - - - - - - - - -### Screenshots - - -### Additional Information - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000000..fdb80175f21f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,73 @@ +name: Bug Report +description: Report an Electron bug +title: "[Bug]: " +labels: "bug :beetle:" +body: +- type: checkboxes + attributes: + label: Preflight Checklist + description: Please ensure you've completed all of the following. + options: + - label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project. + required: true + - label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to. + required: true + - label: I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success. + required: true +- type: input + attributes: + label: Electron Version + description: What version of Electron are you using? + placeholder: 12.0.0 + validations: + required: true +- type: dropdown + attributes: + label: What operating system are you using? + options: + - Windows + - macOS + - Ubuntu + - Other Linux + - Other (specify below) + validations: + required: true +- type: input + attributes: + label: Operating System Version + description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a. + placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04" + validations: + required: true +- type: dropdown + attributes: + label: What arch are you using? + options: + - x64 + - ia32 + - arm64 (including Apple Silicon) + - Other (specify below) + validations: + required: true +- type: input + attributes: + label: Last Known Working Electron version + description: What is the last version of Electron this worked in, if applicable? + placeholder: 11.0.0 +- type: textarea + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Actual Behavior + description: A clear description of what actually happens. + validations: + required: true +- type: input + attributes: + label: Testcase Gist URL + description: If you can reproduce the issue in a standalone test case, please use [Electron Fiddle](https://github.com/electron/fiddle) to create one and to publish it as a [GitHub gist](https://gist.github.com) and put the gist URL here. This is **the best way** to ensure this issue is triaged quickly. + placeholder: https://gist.github.com/... diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 182c8dc9c394..807685c5fd72 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,18 +1,19 @@ name: Feature Request -about: Suggest an idea for Electron +description: Suggest an idea for Electron title: "[Feature Request]: " -labels: "enhancement ✨" +labels: "enhancement :sparkles:" body: -- type: textarea +- type: checkboxes attributes: label: Preflight Checklist - description: Please ensure you've completed the following steps by replacing [ ] with [x] - value: | - * [ ] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project. - * [ ] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to. - * [ ] I have searched the issue tracker for a feature request that matches the one I want to file, without success. - validations: - required: true + description: Please ensure you've completed all of the following. + options: + - label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project. + required: true + - label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to. + required: true + - label: I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success. + required: true - type: textarea attributes: label: Problem Description diff --git a/.github/ISSUE_TEMPLATE/mac_app_store_private_api_rejection.md b/.github/ISSUE_TEMPLATE/mac_app_store_private_api_rejection.md deleted file mode 100644 index cefd5800e2a7..000000000000 --- a/.github/ISSUE_TEMPLATE/mac_app_store_private_api_rejection.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Mac App Store Private API Rejection -about: Your app was rejected from the Mac App Store for using private API's - ---- - - - -### Preflight Checklist - - -* [ ] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project. -* [ ] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to. - -### Issue Details - -* **Electron Version:** - * - -### Rejection Email - - -### Additional Information - diff --git a/.github/ISSUE_TEMPLATE/mac_app_store_private_api_rejection.yml b/.github/ISSUE_TEMPLATE/mac_app_store_private_api_rejection.yml new file mode 100644 index 000000000000..497816fe3372 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mac_app_store_private_api_rejection.yml @@ -0,0 +1,30 @@ +name: Report Mac App Store Private API Rejection +description: Your app was rejected from the Mac App Store for using private API's +title: "[MAS Rejection]: " +body: +- type: checkboxes + attributes: + label: Preflight Checklist + description: Please ensure you've completed all of the following. + options: + - label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project. + required: true + - label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to. + required: true +- type: input + attributes: + label: Electron Version + description: What version of Electron are you using? + placeholder: 12.0.0 + validations: + required: true +- type: textarea + attributes: + label: Rejection Email + description: Paste the contents of your rejection email here, censoring any private information such as app names. + validations: + required: true +- type: textarea + attributes: + label: Additional Information + description: Add any other context about the problem here. diff --git a/ELECTRON_VERSION b/ELECTRON_VERSION index 0c1238f208e5..85caea238e4a 100644 --- a/ELECTRON_VERSION +++ b/ELECTRON_VERSION @@ -1 +1 @@ -14.0.0-nightly.20210304 \ No newline at end of file +14.0.0-nightly.20210315 \ No newline at end of file diff --git a/build/webpack/webpack.config.base.js b/build/webpack/webpack.config.base.js index b23ffb95e774..8d2ff9929b4a 100644 --- a/build/webpack/webpack.config.base.js +++ b/build/webpack/webpack.config.base.js @@ -61,13 +61,6 @@ module.exports = ({ ); } - if (defines.ENABLE_REMOTE_MODULE === 'false') { - ignoredModules.push( - '@electron/internal/browser/remote/server', - '@electron/internal/renderer/api/remote' - ); - } - if (defines.ENABLE_VIEWS_API === 'false') { ignoredModules.push( '@electron/internal/browser/api/views/image-view.js' diff --git a/buildflags/BUILD.gn b/buildflags/BUILD.gn index 9bd2aba0036a..9cbb8227f5f5 100644 --- a/buildflags/BUILD.gn +++ b/buildflags/BUILD.gn @@ -12,7 +12,6 @@ buildflag_header("buildflags") { "ENABLE_DESKTOP_CAPTURER=$enable_desktop_capturer", "ENABLE_RUN_AS_NODE=$enable_run_as_node", "ENABLE_OSR=$enable_osr", - "ENABLE_REMOTE_MODULE=$enable_remote_module", "ENABLE_VIEWS_API=$enable_views_api", "ENABLE_PDF_VIEWER=$enable_pdf_viewer", "ENABLE_TTS=$enable_tts", diff --git a/buildflags/buildflags.gni b/buildflags/buildflags.gni index 95de99fe0a33..5adc739ef7bb 100644 --- a/buildflags/buildflags.gni +++ b/buildflags/buildflags.gni @@ -10,8 +10,6 @@ declare_args() { enable_osr = true - enable_remote_module = true - enable_views_api = true enable_pdf_viewer = true diff --git a/default_app/default_app.ts b/default_app/default_app.ts index 7c2d8a3b9eec..6fe7b11c128b 100644 --- a/default_app/default_app.ts +++ b/default_app/default_app.ts @@ -52,8 +52,7 @@ async function createWindow () { webPreferences: { preload: path.resolve(__dirname, 'preload.js'), contextIsolation: true, - sandbox: true, - enableRemoteModule: false + sandbox: true }, useContentSize: true, show: false diff --git a/docs/README.md b/docs/README.md index dee27988c76c..e94ca55e2456 100644 --- a/docs/README.md +++ b/docs/README.md @@ -146,7 +146,6 @@ These individual tutorials expand on topics discussed in the guide above. * [contextBridge](api/context-bridge.md) * [desktopCapturer](api/desktop-capturer.md) * [ipcRenderer](api/ipc-renderer.md) -* [remote](api/remote.md) * [webFrame](api/web-frame.md) ### Modules for Both Processes: diff --git a/docs/api/app.md b/docs/api/app.md index 2f01e65a91f6..9bafce2dc192 100755 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -507,64 +507,6 @@ Returns: Emitted when `desktopCapturer.getSources()` is called in the renderer process of `webContents`. Calling `event.preventDefault()` will make it return empty sources. -### Event: 'remote-require' _Deprecated_ - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) -* `moduleName` String - -Emitted when `remote.require()` is called in the renderer process of `webContents`. -Calling `event.preventDefault()` will prevent the module from being returned. -Custom value can be returned by setting `event.returnValue`. - -### Event: 'remote-get-global' _Deprecated_ - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) -* `globalName` String - -Emitted when `remote.getGlobal()` is called in the renderer process of `webContents`. -Calling `event.preventDefault()` will prevent the global from being returned. -Custom value can be returned by setting `event.returnValue`. - -### Event: 'remote-get-builtin' _Deprecated_ - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) -* `moduleName` String - -Emitted when `remote.getBuiltin()` is called in the renderer process of `webContents`. -Calling `event.preventDefault()` will prevent the module from being returned. -Custom value can be returned by setting `event.returnValue`. - -### Event: 'remote-get-current-window' _Deprecated_ - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) - -Emitted when `remote.getCurrentWindow()` is called in the renderer process of `webContents`. -Calling `event.preventDefault()` will prevent the object from being returned. -Custom value can be returned by setting `event.returnValue`. - -### Event: 'remote-get-current-web-contents' _Deprecated_ - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) - -Emitted when `remote.getCurrentWebContents()` is called in the renderer process of `webContents`. -Calling `event.preventDefault()` will prevent the object from being returned. -Custom value can be returned by setting `event.returnValue`. - ## Methods The `app` object has the following methods: diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 77d3e4c4d514..cc6c7a189871 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -118,6 +118,9 @@ Returns `String` - The current update feed URL. Asks the server whether there is an update. You must call `setFeedURL` before using this API. +**Note:** If an update is available it will be downloaded automatically. +Calling `autoUpdater.checkForUpdates()` twice will download the update two times. + ### `autoUpdater.quitAndInstall()` Restarts the app and installs the update after it has been downloaded. It diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 9b0b475435a4..edac2f406325 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -273,8 +273,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. OS-level sandbox and disabling the Node.js engine. This is not the same as the `nodeIntegration` option and the APIs available to the preload script are more limited. Read more about the option [here](sandbox-option.md). - * `enableRemoteModule` Boolean (optional) - Whether to enable the [`remote`](remote.md) module. - Default is `false`. * `session` [Session](session.md#class-session) (optional) - Sets the session used by the page. Instead of passing the Session object directly, you can also choose to use the `partition` option instead, which accepts a partition string. When @@ -339,7 +337,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. more details. * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and the specified `preload` script in a separate JavaScript context. Defaults - to `false`. The context that the `preload` script runs in will only have + to `true`. The context that the `preload` script runs in will only have access to its own dedicated `document` and `window` globals, as well as its own set of JavaScript builtins (`Array`, `Object`, `JSON`, etc.), which are all invisible to the loaded content. The Electron API will only @@ -351,8 +349,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. context in the dev tools by selecting the 'Electron Isolated Context' entry in the combo box at the top of the Console tab. * `worldSafeExecuteJavaScript` Boolean (optional) - If true, values returned from `webFrame.executeJavaScript` will be sanitized to ensure JS values - can't unsafely cross between worlds when using `contextIsolation`. The default - is `false`. In Electron 12, the default will be changed to `true`. _Deprecated_ + can't unsafely cross between worlds when using `contextIsolation`. Defaults to `true`. _Deprecated_ * `nativeWindowOpen` Boolean (optional) - Whether to use native `window.open()`. Defaults to `false`. Child windows will always have node integration disabled unless `nodeIntegrationInSubFrames` is true. **Note:** This option is currently @@ -1371,7 +1368,7 @@ Returns `Boolean` - Whether the window's document has been edited. Returns `Promise` - Resolves with a [NativeImage](native-image.md) -Captures a snapshot of the page within `rect`. Omitting `rect` will capture the whole visible page. +Captures a snapshot of the page within `rect`. Omitting `rect` will capture the whole visible page. If the page is not visible, `rect` may be empty. #### `win.loadURL(url[, options])` diff --git a/docs/api/command-line-switches.md b/docs/api/command-line-switches.md index 902e2c836c84..fe8e7c75aebf 100644 --- a/docs/api/command-line-switches.md +++ b/docs/api/command-line-switches.md @@ -66,11 +66,6 @@ Forces the maximum disk space to be used by the disk cache, in bytes. Enables caller stack logging for the following APIs (filtering events): * `desktopCapturer.getSources()` / `desktop-capturer-get-sources` -* `remote.require()` / `remote-require` -* `remote.getGlobal()` / `remote-get-builtin` -* `remote.getBuiltin()` / `remote-get-global` -* `remote.getCurrentWindow()` / `remote-get-current-window` -* `remote.getCurrentWebContents()` / `remote-get-current-web-contents` ### --enable-logging diff --git a/docs/api/cookies.md b/docs/api/cookies.md index 4eab77d4d599..cd598bfeece8 100644 --- a/docs/api/cookies.md +++ b/docs/api/cookies.md @@ -45,6 +45,8 @@ The following events are available on instances of `Cookies`: #### Event: 'changed' +Returns: + * `event` Event * `cookie` [Cookie](structures/cookie.md) - The cookie that was changed. * `cause` String - The cause of the change with one of the following values: diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 0f5e5b53ac78..846389932b3e 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -8,11 +8,11 @@ Process: [Main](../glossary.md#main-process) The `powerMonitor` module emits the following events: -### Event: 'suspend' _macOS_ _Windows_ +### Event: 'suspend' Emitted when the system is suspending. -### Event: 'resume' _macOS_ _Windows_ +### Event: 'resume' Emitted when system is resuming. diff --git a/docs/api/remote.md b/docs/api/remote.md deleted file mode 100644 index 063330550d3a..000000000000 --- a/docs/api/remote.md +++ /dev/null @@ -1,217 +0,0 @@ -# remote - -> Use main process modules from the renderer process. - -Process: [Renderer](../glossary.md#renderer-process) - -> ⚠️ WARNING ⚠️ -> The `remote` module is [deprecated](https://github.com/electron/electron/issues/21408). -> Instead of `remote`, use [`ipcRenderer`](ipc-renderer.md) and -> [`ipcMain`](ipc-main.md). -> -> Read more about why the `remote` module is deprecated [here](https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31). -> -> If you still want to use `remote` despite the performance and security -> concerns, see [@electron/remote](https://github.com/electron/remote). - -The `remote` module provides a simple way to do inter-process communication -(IPC) between the renderer process (web page) and the main process. - -In Electron, GUI-related modules (such as `dialog`, `menu` etc.) are only -available in the main process, not in the renderer process. In order to use them -from the renderer process, the `ipc` module is necessary to send inter-process -messages to the main process. With the `remote` module, you can invoke methods -of the main process object without explicitly sending inter-process messages, -similar to Java's [RMI][rmi]. An example of creating a browser window from a -renderer process: - -```javascript -const { BrowserWindow } = require('electron').remote -const win = new BrowserWindow({ width: 800, height: 600 }) -win.loadURL('https://github.com') -``` - -**Note:** For the reverse (access the renderer process from the main process), -you can use [webContents.executeJavaScript](web-contents.md#contentsexecutejavascriptcode-usergesture). - -**Note:** The remote module can be disabled for security reasons in the following contexts: - -* [`BrowserWindow`](browser-window.md) - by setting the `enableRemoteModule` option to `false`. -* [``](webview-tag.md) - by setting the `enableremotemodule` attribute to `false`. - -## Remote Objects - -Each object (including functions) returned by the `remote` module represents an -object in the main process (we call it a remote object or remote function). -When you invoke methods of a remote object, call a remote function, or create -a new object with the remote constructor (function), you are actually sending -synchronous inter-process messages. - -In the example above, both [`BrowserWindow`](browser-window.md) and `win` were remote objects and -`new BrowserWindow` didn't create a `BrowserWindow` object in the renderer -process. Instead, it created a `BrowserWindow` object in the main process and -returned the corresponding remote object in the renderer process, namely the -`win` object. - -**Note:** Only [enumerable properties][enumerable-properties] which are present -when the remote object is first referenced are accessible via remote. - -**Note:** Arrays and Buffers are copied over IPC when accessed via the `remote` -module. Modifying them in the renderer process does not modify them in the main -process and vice versa. - -## Lifetime of Remote Objects - -Electron makes sure that as long as the remote object in the renderer process -lives (in other words, has not been garbage collected), the corresponding object -in the main process will not be released. When the remote object has been -garbage collected, the corresponding object in the main process will be -dereferenced. - -If the remote object is leaked in the renderer process (e.g. stored in a map but -never freed), the corresponding object in the main process will also be leaked, -so you should be very careful not to leak remote objects. - -Primary value types like strings and numbers, however, are sent by copy. - -## Passing callbacks to the main process - -Code in the main process can accept callbacks from the renderer - for instance -the `remote` module - but you should be extremely careful when using this -feature. - -First, in order to avoid deadlocks, the callbacks passed to the main process -are called asynchronously. You should not expect the main process to -get the return value of the passed callbacks. - -For instance you can't use a function from the renderer process in an -`Array.map` called in the main process: - -```javascript -// main process mapNumbers.js -exports.withRendererCallback = (mapper) => { - return [1, 2, 3].map(mapper) -} - -exports.withLocalCallback = () => { - return [1, 2, 3].map(x => x + 1) -} -``` - -```javascript -// renderer process -const mapNumbers = require('electron').remote.require('./mapNumbers') -const withRendererCb = mapNumbers.withRendererCallback(x => x + 1) -const withLocalCb = mapNumbers.withLocalCallback() - -console.log(withRendererCb, withLocalCb) -// [undefined, undefined, undefined], [2, 3, 4] -``` - -As you can see, the renderer callback's synchronous return value was not as -expected, and didn't match the return value of an identical callback that lives -in the main process. - -Second, the callbacks passed to the main process will persist until the -main process garbage-collects them. - -For example, the following code seems innocent at first glance. It installs a -callback for the `close` event on a remote object: - -```javascript -require('electron').remote.getCurrentWindow().on('close', () => { - // window was closed... -}) -``` - -But remember the callback is referenced by the main process until you -explicitly uninstall it. If you do not, each time you reload your window the -callback will be installed again, leaking one callback for each restart. - -To make things worse, since the context of previously installed callbacks has -been released, exceptions will be raised in the main process when the `close` -event is emitted. - -To avoid this problem, ensure you clean up any references to renderer callbacks -passed to the main process. This involves cleaning up event handlers, or -ensuring the main process is explicitly told to dereference callbacks that came -from a renderer process that is exiting. - -## Accessing built-in modules in the main process - -The built-in modules in the main process are added as getters in the `remote` -module, so you can use them directly like the `electron` module. - -```javascript -const app = require('electron').remote.app -console.log(app) -``` - -## Methods - -The `remote` module has the following methods: - -### `remote.getCurrentWindow()` - -Returns [`BrowserWindow`](browser-window.md) - The window to which this web page -belongs. - -**Note:** Do not use `removeAllListeners` on [`BrowserWindow`](browser-window.md). -Use of this can remove all [`blur`](https://developer.mozilla.org/en-US/docs/Web/Events/blur) -listeners, disable click events on touch bar buttons, and other unintended -consequences. - -### `remote.getCurrentWebContents()` - -Returns [`WebContents`](web-contents.md) - The web contents of this web page. - -### `remote.getGlobal(name)` - -* `name` String - -Returns `any` - The global variable of `name` (e.g. `global[name]`) in the main -process. - -## Properties - -### `remote.require` - -A `NodeJS.Require` function equivalent to `require(module)` in the main process. -Modules specified by their relative path will resolve relative to the entrypoint -of the main process. - -e.g. - -```sh -project/ -├── main -│   ├── foo.js -│   └── index.js -├── package.json -└── renderer - └── index.js -``` - -```js -// main process: main/index.js -const { app } = require('electron') -app.whenReady().then(() => { /* ... */ }) -``` - -```js -// some relative module: main/foo.js -module.exports = 'bar' -``` - -```js -// renderer process: renderer/index.js -const foo = require('electron').remote.require('./foo') // bar -``` - -### `remote.process` _Readonly_ - -A `NodeJS.Process` object. The `process` object in the main process. This is the same as -`remote.getGlobal('process')` but is cached. - -[rmi]: https://en.wikipedia.org/wiki/Java_remote_method_invocation -[enumerable-properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties diff --git a/docs/api/session.md b/docs/api/session.md index 25b3af870453..c89ef396d72a 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -197,9 +197,7 @@ be managed by using [ses.setPermissionCheckHandler(handler)](#sessetpermissionch with the `serial` permission. Because this is an experimental feature it is disabled by default. To enable this feature, you -will need to use the `--enable-features=ElectronSerialChooser` command line switch. Additionally -because this is an experimental Chromium feature you will need to set `enableBlinkFeatures: 'Serial'` -on the `webPreferences` property when opening a BrowserWindow. +will need to use the `--enable-features=ElectronSerialChooser` command line switch. ```javascript const { app, BrowserWindow } = require('electron') @@ -210,10 +208,7 @@ app.commandLine.appendSwitch('enable-features', 'ElectronSerialChooser') app.whenReady().then(() => { win = new BrowserWindow({ width: 800, - height: 600, - webPreferences: { - enableBlinkFeatures: 'Serial' - } + height: 600 }) win.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => { event.preventDefault() diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 43a799836ed7..9cc07bca670f 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -839,59 +839,6 @@ Returns: Emitted when `desktopCapturer.getSources()` is called in the renderer process. Calling `event.preventDefault()` will make it return empty sources. -#### Event: 'remote-require' _Deprecated_ - -Returns: - -* `event` IpcMainEvent -* `moduleName` String - -Emitted when `remote.require()` is called in the renderer process. -Calling `event.preventDefault()` will prevent the module from being returned. -Custom value can be returned by setting `event.returnValue`. - -#### Event: 'remote-get-global' _Deprecated_ - -Returns: - -* `event` IpcMainEvent -* `globalName` String - -Emitted when `remote.getGlobal()` is called in the renderer process. -Calling `event.preventDefault()` will prevent the global from being returned. -Custom value can be returned by setting `event.returnValue`. - -#### Event: 'remote-get-builtin' _Deprecated_ - -Returns: - -* `event` IpcMainEvent -* `moduleName` String - -Emitted when `remote.getBuiltin()` is called in the renderer process. -Calling `event.preventDefault()` will prevent the module from being returned. -Custom value can be returned by setting `event.returnValue`. - -#### Event: 'remote-get-current-window' _Deprecated_ - -Returns: - -* `event` IpcMainEvent - -Emitted when `remote.getCurrentWindow()` is called in the renderer process. -Calling `event.preventDefault()` will prevent the object from being returned. -Custom value can be returned by setting `event.returnValue`. - -#### Event: 'remote-get-current-web-contents' _Deprecated_ - -Returns: - -* `event` IpcMainEvent - -Emitted when `remote.getCurrentWebContents()` is called in the renderer process. -Calling `event.preventDefault()` will prevent the object from being returned. -Custom value can be returned by setting `event.returnValue`. - #### Event: 'preferred-size-changed' Returns: diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 10fbd67bb23c..f08f7f5f6b9a 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -130,15 +130,6 @@ inside the `webview`. All your preloads will load for every iframe, you can use `process.isMainFrame` to determine if you are in the main frame or not. This option is disabled by default in the guest page. -### `enableremotemodule` - -```html - -``` - -A `Boolean`. When this attribute is `false` the guest page in `webview` will not have access -to the [`remote`](remote.md) module. The remote module is unavailable by default. - ### `plugins` ```html diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 8ff7aae726b9..2d523a91f0d1 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -83,14 +83,14 @@ const mainWindow = new BrowserWindow() mainWindow.webContents.setWindowOpenHandler(({ url }) => { if (url.startsWith('https://github.com/')) { - return true + return { action: 'allow' } } - return false + return { action: 'deny' } }) mainWindow.webContents.on('did-create-window', (childWindow) => { // For example... - childWindow.webContents('will-navigate', (e) => { + childWindow.webContents.on('will-navigate', (e) => { e.preventDefault() }) }) diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index f3128cf33518..23c0c9cf09a2 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -160,6 +160,9 @@ the previous behavior, `contextIsolation: false` must be specified in WebPrefere We [recommend having contextIsolation enabled](https://github.com/electron/electron/blob/master/docs/tutorial/security.md#3-enable-context-isolation-for-remote-content) for the security of your application. +Another implication is that `require()` cannot be used in the renderer process unless +`nodeIntegration` is `true` and `contextIsolation` is `false`. + For more details see: https://github.com/electron/electron/issues/23506 ### Removed: `crashReporter.getCrashesDirectory()` diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 9c22244c509d..1358e523f920 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -167,7 +167,8 @@ The simplest and the fastest way to distribute your newly created app is using 1. Import Electron Forge to your app folder: ```sh - npx @electron-forge/cli import + npm install --save-dev @electron-forge/cli + npx electron-forge import ✔ Checking your system ✔ Initializing Git Repository diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index 4667b26e647a..f22de2a6befe 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -44,7 +44,7 @@ Chromium shared library and Node.js. Vulnerabilities affecting these components may impact the security of your application. By updating Electron to the latest version, you ensure that critical vulnerabilities (such as *nodeIntegration bypasses*) are already patched and cannot be exploited in your application. For more information, -see "[Use a current version of Electron](#17-use-a-current-version-of-electron)". +see "[Use a current version of Electron](#15-use-a-current-version-of-electron)". * **Evaluate your dependencies.** While NPM provides half a million reusable packages, it is your responsibility to choose trusted 3rd-party libraries. If you use outdated @@ -99,9 +99,7 @@ You should at least follow these steps to improve the security of your applicati 12. [Disable or limit navigation](#12-disable-or-limit-navigation) 13. [Disable or limit creation of new windows](#13-disable-or-limit-creation-of-new-windows) 14. [Do not use `openExternal` with untrusted content](#14-do-not-use-openexternal-with-untrusted-content) -15. [Disable the `remote` module](#15-disable-the-remote-module) -16. [Filter the `remote` module](#16-filter-the-remote-module) -17. [Use a current version of Electron](#17-use-a-current-version-of-electron) +15. [Use a current version of Electron](#15-use-a-current-version-of-electron) To automate the detection of misconfigurations and insecure patterns, it is possible to use @@ -665,134 +663,7 @@ const { shell } = require('electron') shell.openExternal('https://example.com/index.html') ``` -## 15) Disable the `remote` module - -The `remote` module provides a way for the renderer processes to -access APIs normally only available in the main process. Using it, a -renderer can invoke methods of a main process object without explicitly sending -inter-process messages. If your desktop application does not run untrusted -content, this can be a useful way to have your renderer processes access and -work with modules that are only available to the main process, such as -GUI-related modules (dialogs, menus, etc.). - -However, if your app can run untrusted content and even if you -[sandbox][sandbox] your renderer processes accordingly, the `remote` module -makes it easy for malicious code to escape the sandbox and have access to -system resources via the higher privileges of the main process. Therefore, -it should be disabled in such circumstances. - -### Why? - -`remote` uses an internal IPC channel to communicate with the main process. -"Prototype pollution" attacks can grant malicious code access to the internal -IPC channel, which can then be used to escape the sandbox by mimicking `remote` -IPC messages and getting access to main process modules running with higher -privileges. - -Additionally, it's possible for preload scripts to accidentally leak modules to a -sandboxed renderer. Leaking `remote` arms malicious code with a multitude -of main process modules with which to perform an attack. - -Disabling the `remote` module eliminates these attack vectors. Enabling -context isolation also prevents the "prototype pollution" attacks from -succeeding. - -### How? - -```js -// Bad if the renderer can run untrusted content -const mainWindow = new BrowserWindow({ - webPreferences: { - enableRemoteModule: true - } -}) -``` - -```js -// Good -const mainWindow = new BrowserWindow({ - webPreferences: { - enableRemoteModule: false - } -}) -``` - -```html - - - - - -``` - -> **Note:** The default value of `enableRemoteModule` is `false` starting -> from Electron 10. For prior versions, you need to explicitly disable -> the `remote` module by the means above. - -## 16) Filter the `remote` module - -If you cannot disable the `remote` module, you should filter the globals, -Node, and Electron modules (so-called built-ins) accessible via `remote` -that your application does not require. This can be done by blocking -certain modules entirely and by replacing others with proxies that -expose only the functionality that your app needs. - -### Why? - -Due to the system access privileges of the main process, functionality -provided by the main process modules may be dangerous in the hands of -malicious code running in a compromised renderer process. By limiting -the set of accessible modules to the minimum that your app needs and -filtering out the others, you reduce the toolset that malicious code -can use to attack the system. - -Note that the safest option is to -[fully disable the remote module](#15-disable-the-remote-module). If -you choose to filter access rather than completely disable the module, -you must be very careful to ensure that no escalation of privilege is -possible through the modules you allow past the filter. - -### How? - -```js -const readOnlyFsProxy = require(/* ... */) // exposes only file read functionality - -const allowedModules = new Set(['crypto']) -const proxiedModules = new Map([['fs', readOnlyFsProxy]]) -const allowedElectronModules = new Set(['shell']) -const allowedGlobals = new Set() - -app.on('remote-require', (event, webContents, moduleName) => { - if (proxiedModules.has(moduleName)) { - event.returnValue = proxiedModules.get(moduleName) - } - if (!allowedModules.has(moduleName)) { - event.preventDefault() - } -}) - -app.on('remote-get-builtin', (event, webContents, moduleName) => { - if (!allowedElectronModules.has(moduleName)) { - event.preventDefault() - } -}) - -app.on('remote-get-global', (event, webContents, globalName) => { - if (!allowedGlobals.has(globalName)) { - event.preventDefault() - } -}) - -app.on('remote-get-current-window', (event, webContents) => { - event.preventDefault() -}) - -app.on('remote-get-current-web-contents', (event, webContents) => { - event.preventDefault() -}) -``` - -## 17) Use a current version of Electron +## 15) Use a current version of Electron You should strive for always using the latest available version of Electron. Whenever a new major version is released, you should attempt to update your diff --git a/filenames.auto.gni b/filenames.auto.gni index 7d09066af7f3..c3d558869129 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -43,7 +43,6 @@ auto_filenames = { "docs/api/power-save-blocker.md", "docs/api/process.md", "docs/api/protocol.md", - "docs/api/remote.md", "docs/api/sandbox-option.md", "docs/api/screen.md", "docs/api/service-workers.md", @@ -135,29 +134,21 @@ auto_filenames = { ] sandbox_bundle_deps = [ - "lib/browser/api/module-names.ts", - "lib/common/api/clipboard.ts", "lib/common/api/deprecate.ts", - "lib/common/api/module-list.ts", - "lib/common/api/shell.ts", "lib/common/define-properties.ts", "lib/common/ipc-messages.ts", - "lib/common/remote/ipc-messages.ts", "lib/common/type-utils.ts", "lib/common/web-view-events.ts", "lib/common/web-view-methods.ts", - "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", "lib/renderer/api/crash-reporter.ts", "lib/renderer/api/desktop-capturer.ts", "lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/native-image.ts", - "lib/renderer/api/remote.ts", "lib/renderer/api/web-frame.ts", "lib/renderer/inspector.ts", "lib/renderer/ipc-renderer-internal-utils.ts", "lib/renderer/ipc-renderer-internal.ts", - "lib/renderer/remote/callbacks-registry.ts", "lib/renderer/security-warnings.ts", "lib/renderer/web-frame-init.ts", "lib/renderer/web-view/guest-view-internal.ts", @@ -242,8 +233,6 @@ auto_filenames = { "lib/browser/ipc-main-internal.ts", "lib/browser/message-port-main.ts", "lib/browser/navigation-controller.ts", - "lib/browser/remote/objects-registry.ts", - "lib/browser/remote/server.ts", "lib/browser/rpc-server.ts", "lib/common/api/clipboard.ts", "lib/common/api/deprecate.ts", @@ -253,7 +242,6 @@ auto_filenames = { "lib/common/init.ts", "lib/common/ipc-messages.ts", "lib/common/parse-features-string.ts", - "lib/common/remote/ipc-messages.ts", "lib/common/reset-search-paths.ts", "lib/common/type-utils.ts", "lib/common/web-view-events.ts", @@ -269,7 +257,6 @@ auto_filenames = { ] renderer_bundle_deps = [ - "lib/browser/api/module-names.ts", "lib/common/api/clipboard.ts", "lib/common/api/deprecate.ts", "lib/common/api/module-list.ts", @@ -277,12 +264,10 @@ auto_filenames = { "lib/common/define-properties.ts", "lib/common/init.ts", "lib/common/ipc-messages.ts", - "lib/common/remote/ipc-messages.ts", "lib/common/reset-search-paths.ts", "lib/common/type-utils.ts", "lib/common/web-view-events.ts", "lib/common/web-view-methods.ts", - "lib/common/webpack-globals-provider.ts", "lib/common/webpack-provider.ts", "lib/renderer/api/context-bridge.ts", "lib/renderer/api/crash-reporter.ts", @@ -291,13 +276,11 @@ auto_filenames = { "lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/module-list.ts", "lib/renderer/api/native-image.ts", - "lib/renderer/api/remote.ts", "lib/renderer/api/web-frame.ts", "lib/renderer/init.ts", "lib/renderer/inspector.ts", "lib/renderer/ipc-renderer-internal-utils.ts", "lib/renderer/ipc-renderer-internal.ts", - "lib/renderer/remote/callbacks-registry.ts", "lib/renderer/security-warnings.ts", "lib/renderer/web-frame-init.ts", "lib/renderer/web-view/guest-view-internal.ts", @@ -315,7 +298,6 @@ auto_filenames = { ] worker_bundle_deps = [ - "lib/browser/api/module-names.ts", "lib/common/api/clipboard.ts", "lib/common/api/deprecate.ts", "lib/common/api/module-list.ts", @@ -323,10 +305,8 @@ auto_filenames = { "lib/common/define-properties.ts", "lib/common/init.ts", "lib/common/ipc-messages.ts", - "lib/common/remote/ipc-messages.ts", "lib/common/reset-search-paths.ts", "lib/common/type-utils.ts", - "lib/common/webpack-globals-provider.ts", "lib/common/webpack-provider.ts", "lib/renderer/api/context-bridge.ts", "lib/renderer/api/crash-reporter.ts", @@ -335,11 +315,9 @@ auto_filenames = { "lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/module-list.ts", "lib/renderer/api/native-image.ts", - "lib/renderer/api/remote.ts", "lib/renderer/api/web-frame.ts", "lib/renderer/ipc-renderer-internal-utils.ts", "lib/renderer/ipc-renderer-internal.ts", - "lib/renderer/remote/callbacks-registry.ts", "lib/worker/init.ts", "package.json", "tsconfig.electron.json", diff --git a/lib/asar/fs-wrapper.ts b/lib/asar/fs-wrapper.ts index 569e19ff7fc9..5f0d0587a31f 100644 --- a/lib/asar/fs-wrapper.ts +++ b/lib/asar/fs-wrapper.ts @@ -532,15 +532,17 @@ export const wrapFsWithAsar = (fs: Record) => { return fs.readFile(realPath, options, callback); } + const buffer = Buffer.alloc(info.size); + const fd = archive.getFd(); + if (!(fd >= 0)) { + const error = createError(AsarError.NOT_FOUND, { asarPath, filePath }); + nextTick(callback, [error]); + return; + } + logASARAccess(asarPath, filePath, info.offset); - archive.read(info.offset, info.size).then((buf) => { - const buffer = Buffer.from(buf); - callback(null, encoding ? buffer.toString(encoding) : buffer); - }, (err) => { - const error: AsarErrorObject = new Error(`EINVAL, ${err.message} while reading ${filePath} in ${asarPath}`); - error.code = 'EINVAL'; - error.errno = -22; - callback(error); + fs.read(fd, buffer, 0, info.size, info.offset, (error: Error) => { + callback(error, encoding ? buffer.toString(encoding) : buffer); }); }; @@ -573,19 +575,13 @@ export const wrapFsWithAsar = (fs: Record) => { } const { encoding } = options; + const buffer = Buffer.alloc(info.size); + const fd = archive.getFd(); + if (!(fd >= 0)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath }); logASARAccess(asarPath, filePath, info.offset); - let arrayBuffer: ArrayBuffer; - try { - arrayBuffer = archive.readSync(info.offset, info.size); - } catch (err) { - const error: AsarErrorObject = new Error(`EINVAL, ${err.message} while reading ${filePath} in ${asarPath}`); - error.code = 'EINVAL'; - error.errno = -22; - throw error; - } - const buffer = Buffer.from(arrayBuffer); - return encoding ? buffer.toString(encoding) : buffer; + fs.readSync(fd, buffer, 0, info.size, info.offset); + return (encoding) ? buffer.toString(encoding) : buffer; }; const { readdir } = fs; @@ -697,17 +693,12 @@ export const wrapFsWithAsar = (fs: Record) => { return [str, str.length > 0]; } + const buffer = Buffer.alloc(info.size); + const fd = archive.getFd(); + if (!(fd >= 0)) return []; + logASARAccess(asarPath, filePath, info.offset); - let arrayBuffer: ArrayBuffer; - try { - arrayBuffer = archive.readSync(info.offset, info.size); - } catch (err) { - const error: AsarErrorObject = new Error(`EINVAL, ${err.message} while reading ${filePath} in ${asarPath}`); - error.code = 'EINVAL'; - error.errno = -22; - throw error; - } - const buffer = Buffer.from(arrayBuffer); + fs.readSync(fd, buffer, 0, info.size, info.offset); const str = buffer.toString('utf8'); return [str, str.length > 0]; }; diff --git a/lib/browser/api/crash-reporter.ts b/lib/browser/api/crash-reporter.ts index 14c3ff347a59..99672c690d8b 100644 --- a/lib/browser/api/crash-reporter.ts +++ b/lib/browser/api/crash-reporter.ts @@ -18,7 +18,7 @@ class CrashReporter { if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start'); - if (!compress) { + if (!compress && uploadToServer) { deprecate.log('Sending uncompressed crash reports is deprecated and will be removed in a future version of Electron. Set { compress: true } to opt-in to the new behavior. Crash reports will be uploaded gzipped, which most crash reporting servers support.'); } diff --git a/lib/browser/api/module-names.ts b/lib/browser/api/module-names.ts deleted file mode 100644 index ba628497f86a..000000000000 --- a/lib/browser/api/module-names.ts +++ /dev/null @@ -1,50 +0,0 @@ -// TODO: Figure out a way to not duplicate this information between here and module-list -// It is currently duplicated as module-list "require"s all the browser API file and the -// remote module in the renderer process depends on that file. As a result webpack -// includes all the browser API files in the renderer process as well and we want to avoid that - -// Browser side modules, please sort alphabetically. -export const browserModuleNames = [ - 'app', - 'autoUpdater', - 'BaseWindow', - 'BrowserView', - 'BrowserWindow', - 'contentTracing', - 'crashReporter', - 'dialog', - 'globalShortcut', - 'ipcMain', - 'inAppPurchase', - 'Menu', - 'MenuItem', - 'nativeImage', - 'nativeTheme', - 'net', - 'netLog', - 'MessageChannelMain', - 'Notification', - 'powerMonitor', - 'powerSaveBlocker', - 'protocol', - 'screen', - 'session', - 'ShareMenu', - 'systemPreferences', - 'TouchBar', - 'Tray', - 'View', - 'webContents', - 'WebContentsView', - 'webFrameMain' -]; - -if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) { - browserModuleNames.push('desktopCapturer'); -} - -if (BUILDFLAG(ENABLE_VIEWS_API)) { - browserModuleNames.push( - 'ImageView' - ); -} diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 63d8a6de5d45..01fd54aa3b9d 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -498,10 +498,6 @@ WebContents.prototype._init = function () { this._windowOpenHandler = null; - // Every remote callback from renderer process would add a listener to the - // render-view-deleted event, so ignore the listeners warning. - this.setMaxListeners(0); - // Dispatch IPC messages to the ipc module. this.on('-ipc-message' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) { addSenderFrameToEvent(event); @@ -592,6 +588,7 @@ WebContents.prototype._init = function () { // it's technically a BrowserWindowConstructorOptions option because // we need to access it in the renderer at init time. backgroundColor: windowOpenOverriddenOptions.backgroundColor, + transparent: windowOpenOverriddenOptions.transparent, ...windowOpenOverriddenOptions.webPreferences } : undefined; this._setNextChildWebPreferences( diff --git a/lib/browser/guest-view-manager.ts b/lib/browser/guest-view-manager.ts index 6aa9ed821323..f33339d7723a 100644 --- a/lib/browser/guest-view-manager.ts +++ b/lib/browser/guest-view-manager.ts @@ -168,7 +168,6 @@ const attachGuest = function (event: Electron.IpcMainInvokeEvent, guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false, - enableRemoteModule: params.enableremotemodule, plugins: params.plugins, zoomFactor: embedder.zoomFactor, disablePopups: !params.allowpopups, @@ -188,7 +187,6 @@ const attachGuest = function (event: Electron.IpcMainInvokeEvent, ['javascript', false], ['nativeWindowOpen', true], ['nodeIntegration', false], - ['enableRemoteModule', false], ['sandbox', true], ['nodeIntegrationInSubFrames', false], ['enableWebSQL', false] diff --git a/lib/browser/guest-window-manager.ts b/lib/browser/guest-window-manager.ts index 3ad1665799ac..0200151447bd 100644 --- a/lib/browser/guest-window-manager.ts +++ b/lib/browser/guest-window-manager.ts @@ -191,7 +191,6 @@ const securityWebPreferences: { [key: string]: boolean } = { javascript: false, nativeWindowOpen: true, nodeIntegration: false, - enableRemoteModule: false, sandbox: true, webviewTag: false, nodeIntegrationInSubFrames: false, diff --git a/lib/browser/init.ts b/lib/browser/init.ts index 1b07488c6f08..cd40032de8db 100644 --- a/lib/browser/init.ts +++ b/lib/browser/init.ts @@ -132,10 +132,6 @@ app._setDefaultAppPaths(packagePath); // Load the chrome devtools support. require('@electron/internal/browser/devtools'); -if (BUILDFLAG(ENABLE_REMOTE_MODULE)) { - require('@electron/internal/browser/remote/server'); -} - // Load protocol module to ensure it is populated on app ready require('@electron/internal/browser/api/protocol'); diff --git a/lib/browser/remote/objects-registry.ts b/lib/browser/remote/objects-registry.ts deleted file mode 100644 index ba146106469f..000000000000 --- a/lib/browser/remote/objects-registry.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { WebContents } from 'electron/main'; - -const getOwnerKey = (webContents: WebContents, contextId: string) => { - return `${webContents.id}-${contextId}`; -}; - -class ObjectsRegistry { - private nextId: number = 0 - - // Stores all objects by ref-counting. - // (id) => {object, count} - private storage: Record = {} - - // Stores the IDs + refCounts of objects referenced by WebContents. - // (ownerKey) => { id: refCount } - private owners: Record> = {} - - private electronIds = new WeakMap(); - - // Register a new object and return its assigned ID. If the object is already - // registered then the already assigned ID would be returned. - add (webContents: WebContents, contextId: string, obj: any) { - // Get or assign an ID to the object. - const id = this.saveToStorage(obj); - - // Add object to the set of referenced objects. - const ownerKey = getOwnerKey(webContents, contextId); - let owner = this.owners[ownerKey]; - if (!owner) { - owner = this.owners[ownerKey] = new Map(); - this.registerDeleteListener(webContents, contextId); - } - if (!owner.has(id)) { - owner.set(id, 0); - // Increase reference count if not referenced before. - this.storage[id].count++; - } - - owner.set(id, owner.get(id)! + 1); - return id; - } - - // Get an object according to its ID. - get (id: number) { - const pointer = this.storage[id]; - if (pointer != null) return pointer.object; - } - - // Dereference an object according to its ID. - // Note that an object may be double-freed (cleared when page is reloaded, and - // then garbage collected in old page). - remove (webContents: WebContents, contextId: string, id: number) { - const ownerKey = getOwnerKey(webContents, contextId); - const owner = this.owners[ownerKey]; - if (owner && owner.has(id)) { - const newRefCount = owner.get(id)! - 1; - - // Only completely remove if the number of references GCed in the - // renderer is the same as the number of references we sent them - if (newRefCount <= 0) { - // Remove the reference in owner. - owner.delete(id); - // Dereference from the storage. - this.dereference(id); - } else { - owner.set(id, newRefCount); - } - } - } - - // Clear all references to objects refrenced by the WebContents. - clear (webContents: WebContents, contextId: string) { - const ownerKey = getOwnerKey(webContents, contextId); - const owner = this.owners[ownerKey]; - if (!owner) return; - - for (const id of owner.keys()) this.dereference(id); - - delete this.owners[ownerKey]; - } - - // Private: Saves the object into storage and assigns an ID for it. - saveToStorage (object: any) { - let id = this.electronIds.get(object); - if (!id) { - id = ++this.nextId; - this.storage[id] = { - count: 0, - object: object - }; - this.electronIds.set(object, id); - } - return id; - } - - // Private: Dereference the object from store. - dereference (id: number) { - const pointer = this.storage[id]; - if (pointer == null) { - return; - } - pointer.count -= 1; - if (pointer.count === 0) { - this.electronIds.delete(pointer.object); - delete this.storage[id]; - } - } - - // Private: Clear the storage when renderer process is destroyed. - registerDeleteListener (webContents: WebContents, contextId: string) { - // contextId => ${processHostId}-${contextCount} - const processHostId = contextId.split('-')[0]; - const listener = (_: any, deletedProcessHostId: string) => { - if (deletedProcessHostId && - deletedProcessHostId.toString() === processHostId) { - webContents.removeListener('render-view-deleted' as any, listener); - this.clear(webContents, contextId); - } - }; - // Note that the "render-view-deleted" event may not be emitted on time when - // the renderer process get destroyed because of navigation, we rely on the - // renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to - // guard this situation. - webContents.on('render-view-deleted' as any, listener); - } -} - -export default new ObjectsRegistry(); diff --git a/lib/browser/remote/server.ts b/lib/browser/remote/server.ts deleted file mode 100644 index 5dd28bed3e1c..000000000000 --- a/lib/browser/remote/server.ts +++ /dev/null @@ -1,519 +0,0 @@ -import * as electron from 'electron/main'; -import { EventEmitter } from 'events'; -import objectsRegistry from '@electron/internal/browser/remote/objects-registry'; -import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; -import { isPromise, isSerializableObject, deserialize, serialize } from '@electron/internal/common/type-utils'; -import type { MetaTypeFromRenderer, ObjectMember, MetaType, ObjProtoDescriptor } from '@electron/internal/common/remote/types'; -import { IPC_MESSAGES } from '@electron/internal/common/remote/ipc-messages'; - -const v8Util = process._linkedBinding('electron_common_v8_util'); -const eventBinding = process._linkedBinding('electron_browser_event'); -const features = process._linkedBinding('electron_common_features'); - -if (!features.isRemoteModuleEnabled()) { - throw new Error('remote module is disabled'); -} - -// The internal properties of Function. -const FUNCTION_PROPERTIES = [ - 'length', 'name', 'arguments', 'caller', 'prototype' -]; - -type RendererFunctionId = [string, number] // [contextId, funcId] -type FinalizerInfo = { id: RendererFunctionId, webContents: electron.WebContents, frameId: [number, number] }; -type CallIntoRenderer = (...args: any[]) => void - -// The remote functions in renderer processes. -const rendererFunctionCache = new Map>(); -// eslint-disable-next-line no-undef -const finalizationRegistry = new FinalizationRegistry((fi: FinalizerInfo) => { - const mapKey = fi.id[0] + '~' + fi.id[1]; - const ref = rendererFunctionCache.get(mapKey); - if (ref !== undefined && ref.deref() === undefined) { - rendererFunctionCache.delete(mapKey); - if (!fi.webContents.isDestroyed()) { - try { - fi.webContents._sendToFrameInternal(fi.frameId, IPC_MESSAGES.RENDERER_RELEASE_CALLBACK, fi.id[0], fi.id[1]); - } catch (error) { - console.warn(`_sendToFrameInternal() failed: ${error}`); - } - } - } -}); - -function getCachedRendererFunction (id: RendererFunctionId): CallIntoRenderer | undefined { - const mapKey = id[0] + '~' + id[1]; - const ref = rendererFunctionCache.get(mapKey); - if (ref !== undefined) { - const deref = ref.deref(); - if (deref !== undefined) return deref; - } -} -function setCachedRendererFunction (id: RendererFunctionId, wc: electron.WebContents, frameId: [number, number], value: CallIntoRenderer) { - // eslint-disable-next-line no-undef - const wr = new WeakRef(value); - const mapKey = id[0] + '~' + id[1]; - rendererFunctionCache.set(mapKey, wr); - finalizationRegistry.register(value, { - id, - webContents: wc, - frameId - } as FinalizerInfo); - return value; -} - -const locationInfo = new WeakMap(); - -// Return the description of object's members: -const getObjectMembers = function (object: any): ObjectMember[] { - let names = Object.getOwnPropertyNames(object); - // For Function, we should not override following properties even though they - // are "own" properties. - if (typeof object === 'function') { - names = names.filter((name) => { - return !FUNCTION_PROPERTIES.includes(name); - }); - } - // Map properties to descriptors. - return names.map((name) => { - const descriptor = Object.getOwnPropertyDescriptor(object, name)!; - let type: ObjectMember['type']; - let writable = false; - if (descriptor.get === undefined && typeof object[name] === 'function') { - type = 'method'; - } else { - if (descriptor.set || descriptor.writable) writable = true; - type = 'get'; - } - return { name, enumerable: descriptor.enumerable, writable, type }; - }); -}; - -// Return the description of object's prototype. -const getObjectPrototype = function (object: any): ObjProtoDescriptor { - const proto = Object.getPrototypeOf(object); - if (proto === null || proto === Object.prototype) return null; - return { - members: getObjectMembers(proto), - proto: getObjectPrototype(proto) - }; -}; - -// Convert a real value into meta data. -const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType { - // Determine the type of value. - let type: MetaType['type']; - - switch (typeof value) { - case 'object': - // Recognize certain types of objects. - if (value instanceof Buffer) { - type = 'buffer'; - } else if (value && value.constructor && value.constructor.name === 'NativeImage') { - type = 'nativeimage'; - } else if (Array.isArray(value)) { - type = 'array'; - } else if (value instanceof Error) { - type = 'error'; - } else if (isSerializableObject(value)) { - type = 'value'; - } else if (isPromise(value)) { - type = 'promise'; - } else if (Object.prototype.hasOwnProperty.call(value, 'callee') && value.length != null) { - // Treat the arguments object as array. - type = 'array'; - } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) { - // Treat simple objects as value. - type = 'value'; - } else { - type = 'object'; - } - break; - case 'function': - type = 'function'; - break; - default: - type = 'value'; - break; - } - - // Fill the meta object according to value's type. - if (type === 'array') { - return { - type, - members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject)) - }; - } else if (type === 'nativeimage') { - return { type, value: serialize(value) }; - } else if (type === 'object' || type === 'function') { - return { - type, - name: value.constructor ? value.constructor.name : '', - // Reference the original value if it's an object, because when it's - // passed to renderer we would assume the renderer keeps a reference of - // it. - id: objectsRegistry.add(sender, contextId, value), - members: getObjectMembers(value), - proto: getObjectPrototype(value) - }; - } else if (type === 'buffer') { - return { type, value }; - } else if (type === 'promise') { - // Add default handler to prevent unhandled rejections in main process - // Instead they should appear in the renderer process - value.then(function () {}, function () {}); - - return { - type, - then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) { - value.then(onFulfilled, onRejected); - }) - }; - } else if (type === 'error') { - return { - type, - value, - members: Object.keys(value).map(name => ({ - name, - value: valueToMeta(sender, contextId, value[name]) - })) - }; - } else { - return { - type: 'value', - value - }; - } -}; - -const throwRPCError = function (message: string) { - const error = new Error(message) as Error & {code: string, errno: number}; - error.code = 'EBADRPC'; - error.errno = -72; - throw error; -}; - -const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => { - const location = locationInfo.get(callIntoRenderer); - let message = 'Attempting to call a function in a renderer window that has been closed or released.' + - `\nFunction provided here: ${location}`; - - if (sender instanceof EventEmitter) { - const remoteEvents = sender.eventNames().filter((eventName) => { - return sender.listeners(eventName).includes(callIntoRenderer); - }); - - if (remoteEvents.length > 0) { - message += `\nRemote event names: ${remoteEvents.join(', ')}`; - remoteEvents.forEach((eventName) => { - sender.removeListener(eventName, callIntoRenderer); - }); - } - } - - console.warn(message); -}; - -const fakeConstructor = (constructor: Function, name: string) => - new Proxy(Object, { - get (target, prop, receiver) { - if (prop === 'name') { - return name; - } else { - return Reflect.get(target, prop, receiver); - } - } - }); - -// Convert array of meta data from renderer into array of real values. -const unwrapArgs = function (sender: electron.WebContents, frameId: [number, number], contextId: string, args: any[]) { - const metaToValue = function (meta: MetaTypeFromRenderer): any { - switch (meta.type) { - case 'nativeimage': - return deserialize(meta.value); - case 'value': - return meta.value; - case 'remote-object': - return objectsRegistry.get(meta.id); - case 'array': - return unwrapArgs(sender, frameId, contextId, meta.value); - case 'buffer': - return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength); - case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }); - case 'object': { - const ret: any = meta.name !== 'Object' ? Object.create({ - constructor: fakeConstructor(Object, meta.name) - }) : {}; - - for (const { name, value } of meta.members) { - ret[name] = metaToValue(value); - } - return ret; - } - case 'function-with-return-value': { - const returnValue = metaToValue(meta.value); - return function () { - return returnValue; - }; - } - case 'function': { - // Merge contextId and meta.id, since meta.id can be the same in - // different webContents. - const objectId: [string, number] = [contextId, meta.id]; - - // Cache the callbacks in renderer. - const cachedFunction = getCachedRendererFunction(objectId); - if (cachedFunction !== undefined) { return cachedFunction; } - - const callIntoRenderer = function (this: any, ...args: any[]) { - let succeed = false; - if (!sender.isDestroyed()) { - try { - succeed = sender._sendToFrameInternal(frameId, IPC_MESSAGES.RENDERER_CALLBACK, contextId, meta.id, valueToMeta(sender, contextId, args)); - } catch (error) { - console.warn(`_sendToFrameInternal() failed: ${error}`); - } - } - if (!succeed) { - removeRemoteListenersAndLogWarning(this, callIntoRenderer); - } - }; - locationInfo.set(callIntoRenderer, meta.location); - Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }); - - setCachedRendererFunction(objectId, sender, frameId, callIntoRenderer); - return callIntoRenderer; - } - default: - throw new TypeError(`Unknown type: ${(meta as any).type}`); - } - }; - return args.map(metaToValue); -}; - -const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) { - const webPreferences = contents.getLastWebPreferences() || {}; - return webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : false; -}; - -const isRemoteModuleEnabledCache = new WeakMap(); - -export const isRemoteModuleEnabled = function (contents: electron.WebContents) { - if (!isRemoteModuleEnabledCache.has(contents)) { - isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents)); - } - - return isRemoteModuleEnabledCache.get(contents); -}; - -const handleRemoteCommand = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, contextId: string, ...args: any[]) => void) { - ipcMainInternal.on(channel, (event, contextId: string, ...args: any[]) => { - let returnValue; - if (!isRemoteModuleEnabled(event.sender)) { - event.returnValue = null; - return; - } - - try { - returnValue = handler(event, contextId, ...args); - } catch (error) { - returnValue = { - type: 'exception', - value: valueToMeta(event.sender, contextId, error) - }; - } - - if (returnValue !== undefined) { - event.returnValue = returnValue; - } - }); -}; - -const emitCustomEvent = function (contents: electron.WebContents, eventName: string, ...args: any[]) { - const event = eventBinding.createWithSender(contents); - - electron.app.emit(eventName, event, contents, ...args); - contents.emit(eventName, event, ...args); - - return event; -}; - -const logStack = function (contents: electron.WebContents, code: string, stack: string | undefined) { - if (stack) { - console.warn(`WebContents (${contents.id}): ${code}`, stack); - } -}; - -handleRemoteCommand(IPC_MESSAGES.BROWSER_WRONG_CONTEXT_ERROR, function (event, contextId, passedContextId, id) { - const objectId: [string, number] = [passedContextId, id]; - const cachedFunction = getCachedRendererFunction(objectId); - if (cachedFunction === undefined) { - // Do nothing if the error has already been reported before. - return; - } - removeRemoteListenersAndLogWarning(event.sender, cachedFunction); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_REQUIRE, function (event, contextId, moduleName, stack) { - logStack(event.sender, `remote.require('${moduleName}')`, stack); - const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName); - - if (customEvent.returnValue === undefined) { - if (customEvent.defaultPrevented) { - throw new Error(`Blocked remote.require('${moduleName}')`); - } else { - customEvent.returnValue = process.mainModule.require(moduleName); - } - } - - return valueToMeta(event.sender, contextId, customEvent.returnValue); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_BUILTIN, function (event, contextId, moduleName, stack) { - logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack); - const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName); - - if (customEvent.returnValue === undefined) { - if (customEvent.defaultPrevented) { - throw new Error(`Blocked remote.getBuiltin('${moduleName}')`); - } else { - customEvent.returnValue = (electron as any)[moduleName]; - } - } - - return valueToMeta(event.sender, contextId, customEvent.returnValue); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_GLOBAL, function (event, contextId, globalName, stack) { - logStack(event.sender, `remote.getGlobal('${globalName}')`, stack); - const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName); - - if (customEvent.returnValue === undefined) { - if (customEvent.defaultPrevented) { - throw new Error(`Blocked remote.getGlobal('${globalName}')`); - } else { - customEvent.returnValue = (global as any)[globalName]; - } - } - - return valueToMeta(event.sender, contextId, customEvent.returnValue); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_CURRENT_WINDOW, function (event, contextId, stack) { - logStack(event.sender, 'remote.getCurrentWindow()', stack); - const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window'); - - if (customEvent.returnValue === undefined) { - if (customEvent.defaultPrevented) { - throw new Error('Blocked remote.getCurrentWindow()'); - } else { - customEvent.returnValue = event.sender.getOwnerBrowserWindow(); - } - } - - return valueToMeta(event.sender, contextId, customEvent.returnValue); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_CURRENT_WEB_CONTENTS, function (event, contextId, stack) { - logStack(event.sender, 'remote.getCurrentWebContents()', stack); - const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents'); - - if (customEvent.returnValue === undefined) { - if (customEvent.defaultPrevented) { - throw new Error('Blocked remote.getCurrentWebContents()'); - } else { - customEvent.returnValue = event.sender; - } - } - - return valueToMeta(event.sender, contextId, customEvent.returnValue); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_CONSTRUCTOR, function (event, contextId, id, args) { - args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); - const constructor = objectsRegistry.get(id); - - if (constructor == null) { - throwRPCError(`Cannot call constructor on missing remote object ${id}`); - } - - return valueToMeta(event.sender, contextId, new constructor(...args)); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_FUNCTION_CALL, function (event, contextId, id, args) { - args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); - const func = objectsRegistry.get(id); - - if (func == null) { - throwRPCError(`Cannot call function on missing remote object ${id}`); - } - - try { - return valueToMeta(event.sender, contextId, func(...args), true); - } catch (error) { - const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); - (err as any).cause = error; - throw err; - } -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_CONSTRUCTOR, function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); - const object = objectsRegistry.get(id); - - if (object == null) { - throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`); - } - - return valueToMeta(event.sender, contextId, new object[method](...args)); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_CALL, function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); - const object = objectsRegistry.get(id); - - if (object == null) { - throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`); - } - - try { - return valueToMeta(event.sender, contextId, object[method](...args), true); - } catch (error) { - const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); - (err as any).cause = error; - throw err; - } -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_SET, function (event, contextId, id, name, args) { - args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); - const obj = objectsRegistry.get(id); - - if (obj == null) { - throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`); - } - - obj[name] = args[0]; - return null; -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_GET, function (event, contextId, id, name) { - const obj = objectsRegistry.get(id); - - if (obj == null) { - throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`); - } - - return valueToMeta(event.sender, contextId, obj[name]); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_DEREFERENCE, function (event, contextId, id) { - objectsRegistry.remove(event.sender, contextId, id); -}); - -handleRemoteCommand(IPC_MESSAGES.BROWSER_CONTEXT_RELEASE, (event, contextId) => { - objectsRegistry.clear(event.sender, contextId); -}); diff --git a/lib/common/parse-features-string.ts b/lib/common/parse-features-string.ts index 57c481a775fb..21731be2a7d8 100644 --- a/lib/common/parse-features-string.ts +++ b/lib/common/parse-features-string.ts @@ -75,7 +75,7 @@ export function parseWebViewWebPreferences (preferences: string) { return parseCommaSeparatedKeyValue(preferences, false).parsed; } -const allowedWebPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'javascript', 'contextIsolation', 'webviewTag'] as const; +const allowedWebPreferences = ['zoomFactor', 'nodeIntegration', 'javascript', 'contextIsolation', 'webviewTag'] as const; type AllowedWebPreference = (typeof allowedWebPreferences)[number]; /** diff --git a/lib/common/remote/types.ts b/lib/common/remote/types.ts deleted file mode 100644 index 7fab28905158..000000000000 --- a/lib/common/remote/types.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { Size } from 'electron/main'; -import type { NativeImage } from 'electron/common'; - -export type ObjectMember = { - name: string, - value?: any, - enumerable?: boolean, - writable?: boolean, - type?: 'method' | 'get' -} - -export type ObjProtoDescriptor = { - members: ObjectMember[], - proto: ObjProtoDescriptor -} | null - -export type MetaType = { - type: 'object' | 'function', - name: string, - members: ObjectMember[], - proto: ObjProtoDescriptor, - id: number, -} | { - type: 'value', - value: any, -} | { - type: 'buffer', - value: Uint8Array, -} | { - type: 'array', - members: MetaType[] -} | { - type: 'error', - value: Error, - members: ObjectMember[] -} | { - type: 'exception', - value: MetaType, -} | { - type: 'promise', - then: MetaType -} | { - type: 'nativeimage' - value: NativeImage -} - -export type MetaTypeFromRenderer = { - type: 'value', - value: any -} | { - type: 'remote-object', - id: number -} | { - type: 'array', - value: MetaTypeFromRenderer[] -} | { - type: 'buffer', - value: Uint8Array -} | { - type: 'promise', - then: MetaTypeFromRenderer -} | { - type: 'object', - name: string, - members: { - name: string, - value: MetaTypeFromRenderer - }[] -} | { - type: 'function-with-return-value', - value: MetaTypeFromRenderer -} | { - type: 'function', - id: number, - location: string, - length: number -} | { - type: 'nativeimage', - value: { - size: Size, - buffer: Buffer, - scaleFactor: number, - dataURL: string - }[] -} diff --git a/lib/renderer/api/module-list.ts b/lib/renderer/api/module-list.ts index 0263284d9a64..4ae57b180e1b 100644 --- a/lib/renderer/api/module-list.ts +++ b/lib/renderer/api/module-list.ts @@ -1,7 +1,3 @@ -const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); - -const enableRemoteModule = getWebPreference(window, 'enableRemoteModule'); - // Renderer side modules, please sort alphabetically. export const rendererModuleList: ElectronInternal.ModuleEntry[] = [ { name: 'contextBridge', loader: () => require('./context-bridge') }, @@ -17,10 +13,3 @@ if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) { loader: () => require('@electron/internal/renderer/api/desktop-capturer') }); } - -if (BUILDFLAG(ENABLE_REMOTE_MODULE) && enableRemoteModule) { - rendererModuleList.push({ - name: 'remote', - loader: () => require('@electron/internal/renderer/api/remote') - }); -} diff --git a/lib/renderer/api/remote.ts b/lib/renderer/api/remote.ts deleted file mode 100644 index 35a9a5b1ba9e..000000000000 --- a/lib/renderer/api/remote.ts +++ /dev/null @@ -1,395 +0,0 @@ -import { CallbacksRegistry } from '../remote/callbacks-registry'; -import { isPromise, isSerializableObject, serialize, deserialize } from '../../common/type-utils'; -import { MetaTypeFromRenderer, ObjectMember, ObjProtoDescriptor, MetaType } from '../../common/remote/types'; -import { ipcRendererInternal } from '../ipc-renderer-internal'; -import type { BrowserWindow, WebContents } from 'electron/main'; -import deprecate from '@electron/internal/common/api/deprecate'; -import { browserModuleNames } from '@electron/internal/browser/api/module-names'; -import { commonModuleList } from '@electron/internal/common/api/module-list'; -import { IPC_MESSAGES } from '@electron/internal/common/remote/ipc-messages'; - -deprecate.log('The remote module is deprecated. Use https://github.com/electron/remote instead.'); - -const v8Util = process._linkedBinding('electron_common_v8_util'); -const { hasSwitch } = process._linkedBinding('electron_common_command_line'); - -const callbacksRegistry = new CallbacksRegistry(); -const remoteObjectCache = new Map(); -const finalizationRegistry = new FinalizationRegistry((id: number) => { - const ref = remoteObjectCache.get(id); - if (ref !== undefined && ref.deref() === undefined) { - remoteObjectCache.delete(id); - ipcRendererInternal.send(IPC_MESSAGES.BROWSER_DEREFERENCE, contextId, id, 0); - } -}); - -const electronIds = new WeakMap(); -const isReturnValue = new WeakSet(); - -function getCachedRemoteObject (id: number) { - const ref = remoteObjectCache.get(id); - if (ref !== undefined) { - const deref = ref.deref(); - if (deref !== undefined) return deref; - } -} -function setCachedRemoteObject (id: number, value: any) { - const wr = new WeakRef(value); - remoteObjectCache.set(id, wr); - finalizationRegistry.register(value, id); - return value; -} - -// An unique ID that can represent current context. -const contextId = v8Util.getHiddenValue(global, 'contextId'); - -// Notify the main process when current context is going to be released. -// Note that when the renderer process is destroyed, the message may not be -// sent, we also listen to the "render-view-deleted" event in the main process -// to guard that situation. -process.on('exit', () => { - const command = IPC_MESSAGES.BROWSER_CONTEXT_RELEASE; - ipcRendererInternal.send(command, contextId); -}); - -const IS_REMOTE_PROXY = Symbol('is-remote-proxy'); - -// Convert the arguments object into an array of meta data. -function wrapArgs (args: any[], visited = new Set()): any { - const valueToMeta = (value: any): any => { - // Check for circular reference. - if (visited.has(value)) { - return { - type: 'value', - value: null - }; - } - - if (value && value.constructor && value.constructor.name === 'NativeImage') { - return { type: 'nativeimage', value: serialize(value) }; - } else if (Array.isArray(value)) { - visited.add(value); - const meta = { - type: 'array', - value: wrapArgs(value, visited) - }; - visited.delete(value); - return meta; - } else if (value instanceof Buffer) { - return { - type: 'buffer', - value - }; - } else if (isSerializableObject(value)) { - return { - type: 'value', - value - }; - } else if (typeof value === 'object') { - if (isPromise(value)) { - return { - type: 'promise', - then: valueToMeta(function (onFulfilled: Function, onRejected: Function) { - value.then(onFulfilled, onRejected); - }) - }; - } else if (electronIds.has(value)) { - return { - type: 'remote-object', - id: electronIds.get(value) - }; - } - - const meta: MetaTypeFromRenderer = { - type: 'object', - name: value.constructor ? value.constructor.name : '', - members: [] - }; - visited.add(value); - for (const prop in value) { // eslint-disable-line guard-for-in - meta.members.push({ - name: prop, - value: valueToMeta(value[prop]) - }); - } - visited.delete(value); - return meta; - } else if (typeof value === 'function' && isReturnValue.has(value)) { - return { - type: 'function-with-return-value', - value: valueToMeta(value()) - }; - } else if (typeof value === 'function') { - return { - type: 'function', - id: callbacksRegistry.add(value), - location: callbacksRegistry.getLocation(value), - length: value.length - }; - } else { - return { - type: 'value', - value - }; - } - }; - return args.map(valueToMeta); -} - -// Populate object's members from descriptors. -// The |ref| will be kept referenced by |members|. -// This matches |getObjectMembers| in rpc-server. -function setObjectMembers (ref: any, object: any, metaId: number, members: ObjectMember[]) { - if (!Array.isArray(members)) return; - - for (const member of members) { - if (Object.prototype.hasOwnProperty.call(object, member.name)) continue; - - const descriptor: PropertyDescriptor = { enumerable: member.enumerable }; - if (member.type === 'method') { - const remoteMemberFunction = function (this: any, ...args: any[]) { - let command; - if (this && this.constructor === remoteMemberFunction) { - command = IPC_MESSAGES.BROWSER_MEMBER_CONSTRUCTOR; - } else { - command = IPC_MESSAGES.BROWSER_MEMBER_CALL; - } - const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args)); - return metaToValue(ret); - }; - - let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name); - - descriptor.get = () => { - descriptorFunction.ref = ref; // The member should reference its object. - return descriptorFunction; - }; - // Enable monkey-patch the method - descriptor.set = (value) => { - descriptorFunction = value; - return value; - }; - descriptor.configurable = true; - } else if (member.type === 'get') { - descriptor.get = () => { - const command = IPC_MESSAGES.BROWSER_MEMBER_GET; - const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name); - return metaToValue(meta); - }; - - if (member.writable) { - descriptor.set = (value) => { - const args = wrapArgs([value]); - const command = IPC_MESSAGES.BROWSER_MEMBER_SET; - const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args); - if (meta != null) metaToValue(meta); - return value; - }; - } - } - - Object.defineProperty(object, member.name, descriptor); - } -} - -// Populate object's prototype from descriptor. -// This matches |getObjectPrototype| in rpc-server. -function setObjectPrototype (ref: any, object: any, metaId: number, descriptor: ObjProtoDescriptor) { - if (descriptor === null) return; - const proto = {}; - setObjectMembers(ref, proto, metaId, descriptor.members); - setObjectPrototype(ref, proto, metaId, descriptor.proto); - Object.setPrototypeOf(object, proto); -} - -// Wrap function in Proxy for accessing remote properties -function proxyFunctionProperties (remoteMemberFunction: Function, metaId: number, name: string) { - let loaded = false; - - // Lazily load function properties - const loadRemoteProperties = () => { - if (loaded) return; - loaded = true; - const command = IPC_MESSAGES.BROWSER_MEMBER_GET; - const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name); - setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members); - }; - - return new Proxy(remoteMemberFunction as any, { - set: (target, property, value) => { - if (property !== 'ref') loadRemoteProperties(); - target[property] = value; - return true; - }, - get: (target, property) => { - if (property === IS_REMOTE_PROXY) return true; - if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties(); - const value = target[property]; - if (property === 'toString' && typeof value === 'function') { - return value.bind(target); - } - return value; - }, - ownKeys: (target) => { - loadRemoteProperties(); - return Object.getOwnPropertyNames(target); - }, - getOwnPropertyDescriptor: (target, property) => { - const descriptor = Object.getOwnPropertyDescriptor(target, property); - if (descriptor) return descriptor; - loadRemoteProperties(); - return Object.getOwnPropertyDescriptor(target, property); - } - }); -} - -// Convert meta data from browser into real value. -function metaToValue (meta: MetaType): any { - if (meta.type === 'value') { - return meta.value; - } else if (meta.type === 'array') { - return meta.members.map((member) => metaToValue(member)); - } else if (meta.type === 'nativeimage') { - return deserialize(meta.value); - } else if (meta.type === 'buffer') { - return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength); - } else if (meta.type === 'promise') { - return Promise.resolve({ then: metaToValue(meta.then) }); - } else if (meta.type === 'error') { - return metaToError(meta); - } else if (meta.type === 'exception') { - if (meta.value.type === 'error') { throw metaToError(meta.value); } else { throw new Error(`Unexpected value type in exception: ${meta.value.type}`); } - } else { - let ret; - if ('id' in meta) { - const cached = getCachedRemoteObject(meta.id); - if (cached !== undefined) { return cached; } - } - - // A shadow class to represent the remote function object. - if (meta.type === 'function') { - const remoteFunction = function (this: any, ...args: any[]) { - let command; - if (this && this.constructor === remoteFunction) { - command = IPC_MESSAGES.BROWSER_CONSTRUCTOR; - } else { - command = IPC_MESSAGES.BROWSER_FUNCTION_CALL; - } - const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args)); - return metaToValue(obj); - }; - ret = remoteFunction; - } else { - ret = {}; - } - - setObjectMembers(ret, ret, meta.id, meta.members); - setObjectPrototype(ret, ret, meta.id, meta.proto); - if (ret.constructor && (ret.constructor as any)[IS_REMOTE_PROXY]) { - Object.defineProperty(ret.constructor, 'name', { value: meta.name }); - } - - // Track delegate obj's lifetime & tell browser to clean up when object is GCed. - electronIds.set(ret, meta.id); - setCachedRemoteObject(meta.id, ret); - return ret; - } -} - -function metaToError (meta: { type: 'error', value: any, members: ObjectMember[] }) { - const obj = meta.value; - for (const { name, value } of meta.members) { - obj[name] = metaToValue(value); - } - return obj; -} - -function handleMessage (channel: string, handler: Function) { - ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => { - if (passedContextId === contextId) { - handler(id, ...args); - } else { - // Message sent to an un-exist context, notify the error to main process. - ipcRendererInternal.send(IPC_MESSAGES.BROWSER_WRONG_CONTEXT_ERROR, contextId, passedContextId, id); - } - }); -} - -const enableStacks = hasSwitch('enable-api-filtering-logging'); - -function getCurrentStack (): string | undefined { - const target = { stack: undefined as string | undefined }; - if (enableStacks) { - Error.captureStackTrace(target, getCurrentStack); - } - return target.stack; -} - -// Browser calls a callback in renderer. -handleMessage(IPC_MESSAGES.RENDERER_CALLBACK, (id: number, args: any) => { - callbacksRegistry.apply(id, metaToValue(args)); -}); - -// A callback in browser is released. -handleMessage(IPC_MESSAGES.RENDERER_RELEASE_CALLBACK, (id: number) => { - callbacksRegistry.remove(id); -}); - -exports.require = (module: string) => { - const command = IPC_MESSAGES.BROWSER_REQUIRE; - const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack()); - return metaToValue(meta); -}; - -// Alias to remote.require('electron').xxx. -export function getBuiltin (module: string) { - const command = IPC_MESSAGES.BROWSER_GET_BUILTIN; - const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack()); - return metaToValue(meta); -} - -export function getCurrentWindow (): BrowserWindow { - const command = IPC_MESSAGES.BROWSER_GET_CURRENT_WINDOW; - const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack()); - return metaToValue(meta); -} - -// Get current WebContents object. -export function getCurrentWebContents (): WebContents { - const command = IPC_MESSAGES.BROWSER_GET_CURRENT_WEB_CONTENTS; - const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack()); - return metaToValue(meta); -} - -// Get a global object in browser. -export function getGlobal (name: string): T { - const command = IPC_MESSAGES.BROWSER_GET_GLOBAL; - const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack()); - return metaToValue(meta); -} - -// Get the process object in browser. -Object.defineProperty(exports, 'process', { - get: () => exports.getGlobal('process') -}); - -// Create a function that will return the specified value when called in browser. -export function createFunctionWithReturnValue (returnValue: T): () => T { - const func = () => returnValue; - isReturnValue.add(func); - return func; -} - -const addBuiltinProperty = (name: string) => { - Object.defineProperty(exports, name, { - get: () => exports.getBuiltin(name) - }); -}; - -const browserModules = commonModuleList.concat(browserModuleNames.map(name => ({ name, loader: () => {} }))); - -// And add a helper receiver for each one. -browserModules - .filter((m) => !m.private) - .map((m) => m.name) - .forEach(addBuiltinProperty); diff --git a/lib/renderer/remote/callbacks-registry.ts b/lib/renderer/remote/callbacks-registry.ts deleted file mode 100644 index a413214d1c83..000000000000 --- a/lib/renderer/remote/callbacks-registry.ts +++ /dev/null @@ -1,59 +0,0 @@ -export class CallbacksRegistry { - private nextId: number = 0 - private callbacks = new Map() - private callbackIds = new WeakMap(); - private locationInfo = new WeakMap(); - - add (callback: Function) { - // The callback is already added. - let id = this.callbackIds.get(callback); - if (id != null) return id; - - id = this.nextId += 1; - - // Capture the location of the function and put it in the ID string, - // so that release errors can be tracked down easily. - const regexp = /at (.*)/gi; - const stackString = (new Error()).stack; - if (!stackString) return; - - let filenameAndLine: string; - let match; - - while ((match = regexp.exec(stackString)) !== null) { - const location = match[1]; - if (location.includes('(native)')) continue; - if (location.includes('()')) continue; - if (location.includes('electron/js2c')) continue; - - const ref = /([^/^)]*)\)?$/gi.exec(location); - if (ref) filenameAndLine = ref![1]; - break; - } - - this.callbacks.set(id, callback); - this.callbackIds.set(callback, id); - this.locationInfo.set(callback, filenameAndLine!); - return id; - } - - get (id: number) { - return this.callbacks.get(id) || function () {}; - } - - getLocation (callback: Function) { - return this.locationInfo.get(callback); - } - - apply (id: number, ...args: any[]) { - return this.get(id).apply(global, ...args); - } - - remove (id: number) { - const callback = this.callbacks.get(id); - if (callback) { - this.callbackIds.delete(callback); - this.callbacks.delete(id); - } - } -} diff --git a/lib/renderer/security-warnings.ts b/lib/renderer/security-warnings.ts index 23f538fb1d36..bae73abf6636 100644 --- a/lib/renderer/security-warnings.ts +++ b/lib/renderer/security-warnings.ts @@ -266,27 +266,6 @@ const warnAboutAllowedPopups = function () { // #13 Disable or limit creation of new windows // #14 Do not use `openExternal` with untrusted content -// #15 on the checklist: Disable the `remote` module -// Logs a warning message about the remote module - -const warnAboutRemoteModuleWithRemoteContent = function (webPreferences?: Electron.WebPreferences) { - if (!webPreferences || isLocalhost()) return; - const remoteModuleEnabled = webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : true; - if (!remoteModuleEnabled) return; - - if (getIsRemoteProtocol()) { - const warning = `This renderer process has "enableRemoteModule" enabled - and attempted to load remote content from '${window.location}'. This - exposes users of this app to unnecessary security risks.\n${moreInformation}`; - - console.warn('%cElectron Security Warning (enableRemoteModule)', - 'font-weight: bold;', warning); - } -}; - -// Currently missing since we can't easily programmatically check for it: -// #16 Filter the `remote` module - const logSecurityWarnings = function ( webPreferences: Electron.WebPreferences | undefined, nodeIntegration: boolean ) { @@ -298,7 +277,6 @@ const logSecurityWarnings = function ( warnAboutEnableBlinkFeatures(webPreferences); warnAboutInsecureCSP(); warnAboutAllowedPopups(); - warnAboutRemoteModuleWithRemoteContent(webPreferences); }; const getWebPreferences = async function () { diff --git a/lib/renderer/web-view/web-view-attributes.ts b/lib/renderer/web-view/web-view-attributes.ts index 764088bb11ae..253baf1b9310 100644 --- a/lib/renderer/web-view/web-view-attributes.ts +++ b/lib/renderer/web-view/web-view-attributes.ts @@ -259,20 +259,6 @@ class WebPreferencesAttribute extends WebViewAttribute { } } -class EnableRemoteModuleAttribute extends WebViewAttribute { - constructor (webViewImpl: WebViewImpl) { - super(WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, webViewImpl); - } - - public getValue () { - return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false'; - } - - public setValue (value: any) { - this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false'); - } -} - // Sets up all of the webview attributes. WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION, new PartitionAttribute(this)); @@ -284,7 +270,6 @@ WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this)); this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this)); this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this)); - this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, new EnableRemoteModuleAttribute(this)); this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD, new PreloadAttribute(this)); this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES, new BlinkFeaturesAttribute(this)); this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES, new DisableBlinkFeaturesAttribute(this)); diff --git a/lib/renderer/web-view/web-view-constants.ts b/lib/renderer/web-view/web-view-constants.ts index 02bf33400f48..2ee9ba8dcffd 100644 --- a/lib/renderer/web-view/web-view-constants.ts +++ b/lib/renderer/web-view/web-view-constants.ts @@ -6,7 +6,6 @@ export const enum WEB_VIEW_CONSTANTS { ATTRIBUTE_HTTPREFERRER = 'httpreferrer', ATTRIBUTE_NODEINTEGRATION = 'nodeintegration', ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES = 'nodeintegrationinsubframes', - ATTRIBUTE_ENABLEREMOTEMODULE = 'enableremotemodule', ATTRIBUTE_PLUGINS = 'plugins', ATTRIBUTE_DISABLEWEBSECURITY = 'disablewebsecurity', ATTRIBUTE_ALLOWPOPUPS = 'allowpopups', diff --git a/lib/renderer/web-view/web-view-element.ts b/lib/renderer/web-view/web-view-element.ts index 6034ef587bc7..4b15d45be4f2 100644 --- a/lib/renderer/web-view/web-view-element.ts +++ b/lib/renderer/web-view/web-view-element.ts @@ -29,7 +29,6 @@ const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, - WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD, WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES, WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES, diff --git a/lib/sandboxed_renderer/api/module-list.ts b/lib/sandboxed_renderer/api/module-list.ts index 69f78937ad40..3c148ecee36b 100644 --- a/lib/sandboxed_renderer/api/module-list.ts +++ b/lib/sandboxed_renderer/api/module-list.ts @@ -1,7 +1,3 @@ -const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); - -const enableRemoteModule = getWebPreference(window, 'enableRemoteModule'); - export const moduleList: ElectronInternal.ModuleEntry[] = [ { name: 'contextBridge', @@ -37,10 +33,3 @@ if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) { loader: () => require('@electron/internal/renderer/api/desktop-capturer') }); } - -if (BUILDFLAG(ENABLE_REMOTE_MODULE) && enableRemoteModule) { - moduleList.push({ - name: 'remote', - loader: () => require('@electron/internal/renderer/api/remote') - }); -} diff --git a/package.json b/package.json index 273ff45a7f33..dd820127f834 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "14.0.0-nightly.20210304", + "version": "14.0.0-nightly.20210315", "repository": "https://github.com/electron/electron", "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", "devDependencies": { diff --git a/patches/chromium/.patches b/patches/chromium/.patches index ad688acde96e..e4a5d7b50b97 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -107,3 +107,4 @@ add_trustedauthclient_to_urlloaderfactory.patch fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch disable_unload_metrics.patch fix_add_check_for_sandbox_then_result.patch +moves_background_color_setter_of_webview_to_blinks_webprefs_logic.patch diff --git a/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch b/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch index 2aaa6a59c2a5..f658016d747f 100644 --- a/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch +++ b/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch @@ -8,10 +8,10 @@ WebPreferences of in-process child windows, rather than relying on process-level command line switches, as before. diff --git a/third_party/blink/common/web_preferences/web_preferences.cc b/third_party/blink/common/web_preferences/web_preferences.cc -index 758b0b1616ecf86b7dd090adce94395851d9baf2..55f20eb6266368c65fc0ec80d52caa332f85ecfb 100644 +index 758b0b1616ecf86b7dd090adce94395851d9baf2..43eed39329d5d4337471a2ae8512714d6c6cb841 100644 --- a/third_party/blink/common/web_preferences/web_preferences.cc +++ b/third_party/blink/common/web_preferences/web_preferences.cc -@@ -146,6 +146,29 @@ WebPreferences::WebPreferences() +@@ -146,6 +146,28 @@ WebPreferences::WebPreferences() navigate_on_drag_drop(true), v8_cache_options(blink::mojom::V8CacheOptions::kDefault), record_whole_document(false), @@ -21,7 +21,6 @@ index 758b0b1616ecf86b7dd090adce94395851d9baf2..55f20eb6266368c65fc0ec80d52caa33 + background_color(base::EmptyString()), + opener_id(0), + context_isolation(false), -+ enable_remote_module(false), + world_safe_execute_javascript(false), + guest_instance_id(0), + hidden_page(false), @@ -42,7 +41,7 @@ index 758b0b1616ecf86b7dd090adce94395851d9baf2..55f20eb6266368c65fc0ec80d52caa33 accelerated_video_decode_enabled(false), animation_policy( diff --git a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc -index ba1ba323ec45296c33b5931652a001d6bd24dbe0..663d47894592499531ff924c78b518325020dc04 100644 +index ba1ba323ec45296c33b5931652a001d6bd24dbe0..178cae9c389e48733fde982f4906d9748004dbe3 100644 --- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc +++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc @@ -24,6 +24,11 @@ bool StructTraitslazy_frame_loading_distance_thresholds_px) || !data.ReadLazyImageLoadingDistanceThresholdsPx( -@@ -152,6 +157,27 @@ bool StructTraitsnavigate_on_drag_drop = data.navigate_on_drag_drop(); out->v8_cache_options = data.v8_cache_options(); out->record_whole_document = data.record_whole_document(); @@ -66,7 +65,6 @@ index ba1ba323ec45296c33b5931652a001d6bd24dbe0..663d47894592499531ff924c78b51832 + out->disable_electron_site_instance_overrides = data.disable_electron_site_instance_overrides(); + out->opener_id = data.opener_id(); + out->context_isolation = data.context_isolation(); -+ out->enable_remote_module = data.enable_remote_module(); + out->world_safe_execute_javascript = data.world_safe_execute_javascript(); + out->guest_instance_id = data.guest_instance_id(); + out->hidden_page = data.hidden_page(); @@ -86,7 +84,7 @@ index ba1ba323ec45296c33b5931652a001d6bd24dbe0..663d47894592499531ff924c78b51832 out->accelerated_video_decode_enabled = data.accelerated_video_decode_enabled(); diff --git a/third_party/blink/public/common/web_preferences/web_preferences.h b/third_party/blink/public/common/web_preferences/web_preferences.h -index e1fb30cfba2656989141f0a53ec3e6202c9b0409..ff9da0f6e66ed6565a64c33cc60f39510075c1f4 100644 +index e1fb30cfba2656989141f0a53ec3e6202c9b0409..f630fe020bd426e9093ce352a8e9ac17170101ca 100644 --- a/third_party/blink/public/common/web_preferences/web_preferences.h +++ b/third_party/blink/public/common/web_preferences/web_preferences.h @@ -9,6 +9,7 @@ @@ -97,7 +95,7 @@ index e1fb30cfba2656989141f0a53ec3e6202c9b0409..ff9da0f6e66ed6565a64c33cc60f3951 #include "base/strings/string16.h" #include "base/time/time.h" #include "build/build_config.h" -@@ -161,6 +162,29 @@ struct BLINK_COMMON_EXPORT WebPreferences { +@@ -161,6 +162,28 @@ struct BLINK_COMMON_EXPORT WebPreferences { blink::mojom::V8CacheOptions v8_cache_options; bool record_whole_document; @@ -107,7 +105,6 @@ index e1fb30cfba2656989141f0a53ec3e6202c9b0409..ff9da0f6e66ed6565a64c33cc60f3951 + std::string background_color; + int opener_id; + bool context_isolation; -+ bool enable_remote_module; + bool world_safe_execute_javascript; + int guest_instance_id; + bool hidden_page; @@ -128,7 +125,7 @@ index e1fb30cfba2656989141f0a53ec3e6202c9b0409..ff9da0f6e66ed6565a64c33cc60f3951 // only controls whether or not the "document.cookie" field is properly // connected to the backing store, for instance if you wanted to be able to diff --git a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h -index ae180b30284c17c7319925531440161f66b873c7..2857c7fdcb18b6f9d858c038ee2a9784c141766b 100644 +index ae180b30284c17c7319925531440161f66b873c7..6ba055814a8385052d7798be56de53691dbe3343 100644 --- a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h +++ b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h @@ -6,6 +6,7 @@ @@ -139,7 +136,7 @@ index ae180b30284c17c7319925531440161f66b873c7..2857c7fdcb18b6f9d858c038ee2a9784 #include "mojo/public/cpp/bindings/struct_traits.h" #include "net/nqe/effective_connection_type.h" #include "third_party/blink/public/common/common_export.h" -@@ -441,6 +442,88 @@ struct BLINK_COMMON_EXPORT StructTraits +Date: Mon, 8 Mar 2021 16:27:39 -0800 +Subject: moves background_color setter of WebView to blinks webprefs logic + +background_color can be updated at runtime, as such we need to apply the +new background color to the WebView in the ApplyPreferences method. +There is no current way to attach an observer to these prefs so patching +is our only option. + +Ideally we could add an embedder observer pattern here but that can be +done in future work. + +diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc +index ba01fb7cbbff1535401cdc687570c9a86e2011dd..fdb61d9116a2822709e3fb3ea9c4803a8c99511d 100644 +--- a/third_party/blink/renderer/core/exported/web_view_impl.cc ++++ b/third_party/blink/renderer/core/exported/web_view_impl.cc +@@ -154,6 +154,7 @@ + #include "third_party/blink/renderer/core/timing/dom_window_performance.h" + #include "third_party/blink/renderer/core/timing/window_performance.h" + #include "third_party/blink/renderer/platform/fonts/font_cache.h" ++#include "third_party/blink/renderer/platform/graphics/color.h" + #include "third_party/blink/renderer/platform/graphics/image.h" + #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" + #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h" +@@ -1765,6 +1766,14 @@ void WebView::ApplyWebPreferences(const web_pref::WebPreferences& prefs, + + RuntimeEnabledFeatures::SetTranslateServiceEnabled( + prefs.translate_service_available); ++ ++ SkColor color = SK_ColorTRANSPARENT; ++ if (!prefs.guest_instance_id) { // not inside electron tag, which is always transparent. ++ Color blink_color; ++ if (blink_color.SetFromString(WebString::FromASCII(prefs.background_color))) ++ color = static_cast(blink_color); ++ } ++ web_view->SetBaseBackgroundColor(color); + } + + void WebViewImpl::ThemeChanged() { diff --git a/patches/node/.patches b/patches/node/.patches index bf2a25bdc4ec..2a5106fe3a9b 100644 --- a/patches/node/.patches +++ b/patches/node/.patches @@ -16,7 +16,6 @@ build_modify_js2c_py_to_allow_injection_of_original-fs_and_custom_embedder_js.pa refactor_allow_embedder_overriding_of_internal_fs_calls.patch chore_prevent_warn_non_context-aware_native_modules_being_loaded.patch chore_read_nobrowserglobals_from_global_not_process.patch -build_bring_back_node_with_ltcg_configuration.patch enable_31_bit_smis_on_64bit_arch_and_ptr_compression.patch fix_use_crypto_impls_for_compat.patch fix_comment_out_incompatible_crypto_modules.patch @@ -32,3 +31,4 @@ remove_makeexternal_case_for_uncached_internal_strings.patch fix_remove_outdated_--experimental-wasm-bigint_flag.patch fix_crypto_tests_to_run_with_bssl.patch build_add_mjs_support_to_js2c.patch +src_inline_asynccleanuphookhandle_in_headers.patch diff --git a/patches/node/build_bring_back_node_with_ltcg_configuration.patch b/patches/node/build_bring_back_node_with_ltcg_configuration.patch deleted file mode 100644 index f4305a8c6742..000000000000 --- a/patches/node/build_bring_back_node_with_ltcg_configuration.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Deepak Mohan -Date: Wed, 16 Oct 2019 13:41:12 -0700 -Subject: build: bring back node_with_ltcg configuration - -This was moved to code node.gyp as part of https://github.com/nodejs/node/pull/25931 -which caused native modules size increase which were depending on -this configuration transitively https://github.com/nodejs/node/issues/29501. -THe fix for this should land in node-gyp as discussed in above issue, -landing this as temporary patch. - -diff --git a/common.gypi b/common.gypi -index d37d29736ead82aca6c89cc7625ca4d9a053da32..ffb80656d4a2117b7ee4cd5bd2d7aabfef16122b 100644 ---- a/common.gypi -+++ b/common.gypi -@@ -19,7 +19,7 @@ - 'node_use_v8_platform%': 'true', - 'node_use_bundled_v8%': 'true', - 'node_module_version%': '', -- 'node_with_ltcg%': '', -+ 'node_with_ltcg%': 'true', - 'node_shared_openssl%': 'false', - - 'node_tag%': '', -@@ -232,6 +232,26 @@ - 'cflags': [ '-fPIC' ], - 'ldflags': [ '-fPIC' ] - }], -+ ['node_with_ltcg=="true"', { -+ 'msvs_settings': { -+ 'VCCLCompilerTool': { -+ 'WholeProgramOptimization': 'true' # /GL, whole program optimization, needed for LTCG -+ }, -+ 'VCLibrarianTool': { -+ 'AdditionalOptions': [ -+ '/LTCG:INCREMENTAL', # incremental link-time code generation -+ ] -+ }, -+ 'VCLinkerTool': { -+ 'OptimizeReferences': 2, # /OPT:REF -+ 'EnableCOMDATFolding': 2, # /OPT:ICF -+ 'LinkIncremental': 1, # disable incremental linking -+ 'AdditionalOptions': [ -+ '/LTCG:INCREMENTAL', # incremental link-time code generation -+ ] -+ } -+ } -+ }] - ], - 'msvs_settings': { - 'VCCLCompilerTool': { diff --git a/patches/node/enable_31_bit_smis_on_64bit_arch_and_ptr_compression.patch b/patches/node/enable_31_bit_smis_on_64bit_arch_and_ptr_compression.patch index ee48ebfa929a..860a6ab4b5a6 100644 --- a/patches/node/enable_31_bit_smis_on_64bit_arch_and_ptr_compression.patch +++ b/patches/node/enable_31_bit_smis_on_64bit_arch_and_ptr_compression.patch @@ -8,7 +8,7 @@ node modules will have different (wrong) ideas about how v8 structs are laid out in memory on 64-bit machines, and will summarily fail to work. diff --git a/common.gypi b/common.gypi -index ffb80656d4a2117b7ee4cd5bd2d7aabfef16122b..2f6e8fbb302d133631acd33e90dd1b6661364b32 100644 +index d37d29736ead82aca6c89cc7625ca4d9a053da32..b6aa3c0504fd108ada368d30f3021d1c171a4879 100644 --- a/common.gypi +++ b/common.gypi @@ -64,7 +64,7 @@ diff --git a/patches/node/fix_add_v8_enable_reverse_jsargs_defines_in_common_gypi.patch b/patches/node/fix_add_v8_enable_reverse_jsargs_defines_in_common_gypi.patch index 2af238f4199a..2678bc722865 100644 --- a/patches/node/fix_add_v8_enable_reverse_jsargs_defines_in_common_gypi.patch +++ b/patches/node/fix_add_v8_enable_reverse_jsargs_defines_in_common_gypi.patch @@ -6,7 +6,7 @@ Subject: fix: add v8_enable_reverse_jsargs defines in common.gypi This can be removed once node upgrades V8 and inevitably has to do this exact same thing. Also hi node people if you are looking at this. diff --git a/common.gypi b/common.gypi -index 2f6e8fbb302d133631acd33e90dd1b6661364b32..1f05305dee3fe5558d41765b5fac4ccfdd843eb5 100644 +index b6aa3c0504fd108ada368d30f3021d1c171a4879..9a2552ab3c1ba44b57b2d3b1ddf2becaa32ebbda 100644 --- a/common.gypi +++ b/common.gypi @@ -65,6 +65,7 @@ @@ -25,7 +25,7 @@ index 2f6e8fbb302d133631acd33e90dd1b6661364b32..1f05305dee3fe5558d41765b5fac4ccf ##### end V8 defaults ##### # When building native modules using 'npm install' with the system npm, -@@ -392,6 +394,9 @@ +@@ -372,6 +374,9 @@ ['v8_enable_pointer_compression == 1 or v8_enable_31bit_smis_on_64bit_arch == 1', { 'defines': ['V8_31BIT_SMIS_ON_64BIT_ARCH'], }], diff --git a/patches/node/src_inline_asynccleanuphookhandle_in_headers.patch b/patches/node/src_inline_asynccleanuphookhandle_in_headers.patch new file mode 100644 index 000000000000..9ecdeed761ca --- /dev/null +++ b/patches/node/src_inline_asynccleanuphookhandle_in_headers.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tyler Ang-Wanek +Date: Tue, 19 Jan 2021 07:39:14 -0700 +Subject: src: inline AsyncCleanupHookHandle in headers + +Fixes: https://github.com/nodejs/node/issues/36349 + +PR-URL: https://github.com/nodejs/node/pull/37000 +Reviewed-By: Anna Henningsen +Reviewed-By: Rich Trott +Reviewed-By: James M Snell + +diff --git a/src/api/hooks.cc b/src/api/hooks.cc +index a719a861dbe9d8d9ca67c3bb5920b14b0df16d83..8f191aad7e2dcfbedddeaeb88f47ed721ef51cf1 100644 +--- a/src/api/hooks.cc ++++ b/src/api/hooks.cc +@@ -133,7 +133,7 @@ static void RunAsyncCleanupHook(void* arg) { + info->fun(info->arg, FinishAsyncCleanupHook, info); + } + +-AsyncCleanupHookHandle AddEnvironmentCleanupHook( ++ACHHandle* AddEnvironmentCleanupHookInternal( + Isolate* isolate, + AsyncCleanupHook fun, + void* arg) { +@@ -145,11 +145,11 @@ AsyncCleanupHookHandle AddEnvironmentCleanupHook( + info->arg = arg; + info->self = info; + env->AddCleanupHook(RunAsyncCleanupHook, info.get()); +- return AsyncCleanupHookHandle(new ACHHandle { info }); ++ return new ACHHandle { info }; + } + +-void RemoveEnvironmentCleanupHook( +- AsyncCleanupHookHandle handle) { ++void RemoveEnvironmentCleanupHookInternal( ++ ACHHandle* handle) { + if (handle->info->started) return; + handle->info->self.reset(); + handle->info->env->RemoveCleanupHook(RunAsyncCleanupHook, handle->info.get()); +diff --git a/src/node.h b/src/node.h +index f150725b54ee1315476d202797963369490d5152..7ab2ed9345c83cb4c1f51c0cc3050abc6571e3fa 100644 +--- a/src/node.h ++++ b/src/node.h +@@ -905,12 +905,26 @@ struct ACHHandle; + struct NODE_EXTERN DeleteACHHandle { void operator()(ACHHandle*) const; }; + typedef std::unique_ptr AsyncCleanupHookHandle; + +-NODE_EXTERN AsyncCleanupHookHandle AddEnvironmentCleanupHook( ++/* This function is not intended to be used externally, it exists to aid in ++ * keeping ABI compatibility between Node and Electron. */ ++NODE_EXTERN ACHHandle* AddEnvironmentCleanupHookInternal( + v8::Isolate* isolate, + void (*fun)(void* arg, void (*cb)(void*), void* cbarg), + void* arg); ++inline AsyncCleanupHookHandle AddEnvironmentCleanupHook( ++ v8::Isolate* isolate, ++ void (*fun)(void* arg, void (*cb)(void*), void* cbarg), ++ void* arg) { ++ return AsyncCleanupHookHandle(AddEnvironmentCleanupHookInternal(isolate, fun, ++ arg)); ++} + +-NODE_EXTERN void RemoveEnvironmentCleanupHook(AsyncCleanupHookHandle holder); ++/* This function is not intended to be used externally, it exists to aid in ++ * keeping ABI compatibility between Node and Electron. */ ++NODE_EXTERN void RemoveEnvironmentCleanupHookInternal(ACHHandle* holder); ++inline void RemoveEnvironmentCleanupHook(AsyncCleanupHookHandle holder) { ++ RemoveEnvironmentCleanupHookInternal(holder.get()); ++} + + /* Returns the id of the current execution context. If the return value is + * zero then no execution has been set. This will happen if the user handles diff --git a/shell/browser/api/electron_api_base_window.cc b/shell/browser/api/electron_api_base_window.cc index 3b0164cbe983..ff233e56bcbf 100644 --- a/shell/browser/api/electron_api_base_window.cc +++ b/shell/browser/api/electron_api_base_window.cc @@ -114,8 +114,7 @@ BaseWindow::BaseWindow(gin_helper::Arguments* args, } BaseWindow::~BaseWindow() { - if (!window_->IsClosed()) - window_->CloseImmediately(); + CloseImmediately(); // Destroy the native window in next tick because the native code might be // iterating all windows. @@ -318,6 +317,11 @@ void BaseWindow::SetContentView(gin::Handle view) { window_->SetContentView(view->view()); } +void BaseWindow::CloseImmediately() { + if (!window_->IsClosed()) + window_->CloseImmediately(); +} + void BaseWindow::Close() { window_->Close(); } diff --git a/shell/browser/api/electron_api_base_window.h b/shell/browser/api/electron_api_base_window.h index 02a2e258dc64..25b2c9e7c5ba 100644 --- a/shell/browser/api/electron_api_base_window.h +++ b/shell/browser/api/electron_api_base_window.h @@ -92,6 +92,7 @@ class BaseWindow : public gin_helper::TrackableObject, // Public APIs of NativeWindow. void SetContentView(gin::Handle view); void Close(); + virtual void CloseImmediately(); virtual void Focus(); virtual void Blur(); bool IsFocused(); diff --git a/shell/browser/api/electron_api_browser_view.cc b/shell/browser/api/electron_api_browser_view.cc index ac8137ab2cbe..3cb2a6fa6a66 100644 --- a/shell/browser/api/electron_api_browser_view.cc +++ b/shell/browser/api/electron_api_browser_view.cc @@ -100,11 +100,9 @@ BrowserView::BrowserView(gin::Arguments* args, } BrowserView::~BrowserView() { - if (api_web_contents_) { // destroy() is called - // Destroy WebContents asynchronously unless app is shutting down, - // because destroy() might be called inside WebContents's event handler. + if (api_web_contents_) { // destroy() called without closing WebContents api_web_contents_->RemoveObserver(this); - api_web_contents_->DestroyWebContents(!Browser::Get()->is_shutting_down()); + api_web_contents_->Destroy(); } } diff --git a/shell/browser/api/electron_api_browser_window.cc b/shell/browser/api/electron_api_browser_window.cc index 75d4daf51c4c..1ba361cbc379 100644 --- a/shell/browser/api/electron_api_browser_window.cc +++ b/shell/browser/api/electron_api_browser_window.cc @@ -104,13 +104,16 @@ BrowserWindow::BrowserWindow(gin::Arguments* args, } BrowserWindow::~BrowserWindow() { - // FIXME This is a hack rather than a proper fix preventing shutdown crashes. - if (api_web_contents_) + if (api_web_contents_) { + // Cleanup the observers if user destroyed this instance directly instead of + // gracefully closing content::WebContents. + auto* host = web_contents()->GetRenderViewHost(); + if (host) + host->GetWidget()->RemoveInputEventObserver(this); api_web_contents_->RemoveObserver(this); - // Note that the OnWindowClosed will not be called after the destructor runs, - // since the window object is managed by the BaseWindow class. - if (web_contents()) - Cleanup(); + // Destroy the WebContents. + OnCloseContents(); + } } void BrowserWindow::OnInputEvent(const blink::WebInputEvent& event) { @@ -173,34 +176,14 @@ void BrowserWindow::OnRendererUnresponsive(content::RenderProcessHost*) { ScheduleUnresponsiveEvent(50); } +void BrowserWindow::WebContentsDestroyed() { + api_web_contents_ = nullptr; + CloseImmediately(); +} + void BrowserWindow::OnCloseContents() { - // On some machines it may happen that the window gets destroyed for twice, - // checking web_contents() can effectively guard against that. - // https://github.com/electron/electron/issues/16202. - // - // TODO(zcbenz): We should find out the root cause and improve the closing - // procedure of BrowserWindow. - if (!web_contents()) - return; - - // Close all child windows before closing current window. - v8::Locker locker(isolate()); - v8::HandleScope handle_scope(isolate()); - for (v8::Local value : child_windows_.Values(isolate())) { - gin::Handle child; - if (gin::ConvertFromV8(isolate(), value, &child) && !child.IsEmpty()) - child->window()->CloseImmediately(); - } - - // When the web contents is gone, close the window immediately, but the - // memory will not be freed until you call delete. - // In this way, it would be safe to manage windows via smart pointers. If you - // want to free memory when the window is closed, you can do deleting by - // overriding the OnWindowClosed method in the observer. - window()->CloseImmediately(); - - // Do not sent "unresponsive" event after window is closed. - window_unresponsive_closure_.Cancel(); + BaseWindow::ResetBrowserViews(); + api_web_contents_->Destroy(); } void BrowserWindow::OnRendererResponsive(content::RenderProcessHost*) { @@ -268,30 +251,22 @@ void BrowserWindow::OnCloseButtonClicked(bool* prevent_default) { web_contents()->Close(); } -void BrowserWindow::OnWindowClosed() { - // We need to reset the browser views before we call Cleanup, because the - // browser views are child views of the main web contents view, which will be - // deleted by Cleanup. - BaseWindow::ResetBrowserViews(); - Cleanup(); - // See BaseWindow::OnWindowClosed on why calling InvalidateWeakPtrs. - weak_factory_.InvalidateWeakPtrs(); - BaseWindow::OnWindowClosed(); -} - void BrowserWindow::OnWindowBlur() { - web_contents()->StoreFocus(); + if (api_web_contents_) + web_contents()->StoreFocus(); BaseWindow::OnWindowBlur(); } void BrowserWindow::OnWindowFocus() { - web_contents()->RestoreFocus(); - + // focus/blur events might be emitted while closing window. + if (api_web_contents_) { + web_contents()->RestoreFocus(); #if !defined(OS_MAC) - if (!api_web_contents_->IsDevToolsOpened()) - web_contents()->Focus(); + if (!api_web_contents_->IsDevToolsOpened()) + web_contents()->Focus(); #endif + } BaseWindow::OnWindowFocus(); } @@ -325,6 +300,22 @@ void BrowserWindow::OnWindowLeaveFullScreen() { BaseWindow::OnWindowLeaveFullScreen(); } +void BrowserWindow::CloseImmediately() { + // Close all child windows before closing current window. + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + for (v8::Local value : child_windows_.Values(isolate())) { + gin::Handle child; + if (gin::ConvertFromV8(isolate(), value, &child) && !child.IsEmpty()) + child->window()->CloseImmediately(); + } + + BaseWindow::CloseImmediately(); + + // Do not sent "unresponsive" event after window is closed. + window_unresponsive_closure_.Cancel(); +} + void BrowserWindow::Focus() { if (api_web_contents_->IsOffScreen()) FocusOnWebView(); @@ -381,7 +372,7 @@ void BrowserWindow::RemoveBrowserView(v8::Local value) { void BrowserWindow::SetTopBrowserView(v8::Local value, gin_helper::Arguments* args) { BaseWindow::SetTopBrowserView(value, args); -#if defined(OS_MACOSX) +#if defined(OS_MAC) UpdateDraggableRegions(draggable_regions_); #endif } @@ -448,17 +439,6 @@ void BrowserWindow::NotifyWindowUnresponsive() { } } -void BrowserWindow::Cleanup() { - auto* host = web_contents()->GetRenderViewHost(); - if (host) - host->GetWidget()->RemoveInputEventObserver(this); - - // Destroy WebContents asynchronously unless app is shutting down, - // because destroy() might be called inside WebContents's event handler. - api_web_contents_->DestroyWebContents(!Browser::Get()->is_shutting_down()); - Observe(nullptr); -} - void BrowserWindow::OnWindowShow() { web_contents()->WasShown(); BaseWindow::OnWindowShow(); diff --git a/shell/browser/api/electron_api_browser_window.h b/shell/browser/api/electron_api_browser_window.h index a3423b3a83fb..f0547d8039ad 100644 --- a/shell/browser/api/electron_api_browser_window.h +++ b/shell/browser/api/electron_api_browser_window.h @@ -54,6 +54,7 @@ class BrowserWindow : public BaseWindow, void OnRendererUnresponsive(content::RenderProcessHost*) override; void OnRendererResponsive( content::RenderProcessHost* render_process_host) override; + void WebContentsDestroyed() override; // ExtendedWebContentsObserver: void OnCloseContents() override; @@ -73,11 +74,11 @@ class BrowserWindow : public BaseWindow, void OnWindowIsKeyChanged(bool is_key) override; // BaseWindow: - void OnWindowClosed() override; void OnWindowBlur() override; void OnWindowFocus() override; void OnWindowResize() override; void OnWindowLeaveFullScreen() override; + void CloseImmediately() override; void Focus() override; void Blur() override; void SetBackgroundColor(const std::string& color_name) override; @@ -114,9 +115,6 @@ class BrowserWindow : public BaseWindow, // Dispatch unresponsive event to observers. void NotifyWindowUnresponsive(); - // Cleanup our WebContents observers. - void Cleanup(); - // Closure that would be called when window is unresponsive when closing, // it should be cancelled when we can prove that the window is responsive. base::CancelableClosure window_unresponsive_closure_; diff --git a/shell/browser/api/electron_api_system_preferences_mac.mm b/shell/browser/api/electron_api_system_preferences_mac.mm index 96d757f5f975..2595a1c7eedb 100644 --- a/shell/browser/api/electron_api_system_preferences_mac.mm +++ b/shell/browser/api/electron_api_system_preferences_mac.mm @@ -26,9 +26,11 @@ #include "shell/browser/mac/dict_util.h" #include "shell/browser/mac/electron_application.h" #include "shell/browser/ui/cocoa/NSColor+Hex.h" +#include "shell/common/color_util.h" #include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/value_converter.h" #include "shell/common/process_util.h" +#include "skia/ext/skia_utils_mac.h" #include "ui/native_theme/native_theme.h" namespace gin { @@ -388,7 +390,8 @@ std::string SystemPreferences::GetAccentColor() { if (@available(macOS 10.14, *)) sysColor = [NSColor controlAccentColor]; - return base::SysNSStringToUTF8([sysColor RGBAValue]); + return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor), + false /* include_hash */); } std::string SystemPreferences::GetSystemColor(gin_helper::ErrorThrower thrower, @@ -417,7 +420,7 @@ std::string SystemPreferences::GetSystemColor(gin_helper::ErrorThrower thrower, return ""; } - return base::SysNSStringToUTF8([sysColor hexadecimalValue]); + return ToRGBHex(skia::NSSystemColorToSkColor(sysColor)); } bool SystemPreferences::CanPromptTouchID() { @@ -575,7 +578,7 @@ std::string SystemPreferences::GetColor(gin_helper::ErrorThrower thrower, } if (sysColor) - return base::SysNSStringToUTF8([sysColor hexadecimalValue]); + return ToRGBHex(skia::NSSystemColorToSkColor(sysColor)); return ""; } diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index cf97c5b2c4fd..60a75e4e9378 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -910,51 +910,49 @@ void WebContents::InitWithWebContents(content::WebContents* web_contents, } WebContents::~WebContents() { - MarkDestroyed(); - // The destroy() is called. - if (inspectable_web_contents_) { -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - if (type_ == Type::kBackgroundPage) { - // Background pages are owned by extensions::ExtensionHost - inspectable_web_contents_->ReleaseWebContents(); - } -#endif - - inspectable_web_contents_->GetView()->SetDelegate(nullptr); - - if (web_contents()) { - RenderViewDeleted(web_contents()->GetRenderViewHost()); - } - - if (type_ == Type::kBrowserWindow && owner_window()) { - // For BrowserWindow we should close the window and clean up everything - // before WebContents is destroyed. - for (ExtendedWebContentsObserver& observer : observers_) - observer.OnCloseContents(); - // BrowserWindow destroys WebContents asynchronously, manually emit the - // destroyed event here. - WebContentsDestroyed(); - } else if (Browser::Get()->is_shutting_down()) { - // Destroy WebContents directly when app is shutting down. - DestroyWebContents(false /* async */); - } else { - // Destroy WebContents asynchronously unless app is shutting down, - // because destroy() might be called inside WebContents's event handler. - bool is_browser_view = type_ == Type::kBrowserView; - DestroyWebContents(!(IsGuest() || is_browser_view) /* async */); - // The WebContentsDestroyed will not be called automatically because we - // destroy the webContents in the next tick. So we have to manually - // call it here to make sure "destroyed" event is emitted. - WebContentsDestroyed(); - } + if (!inspectable_web_contents_) { + WebContentsDestroyed(); + return; } -} -void WebContents::DestroyWebContents(bool async) { + if (guest_delegate_) + guest_delegate_->WillDestroy(); + // This event is only for internal use, which is emitted when WebContents is // being destroyed. Emit("will-destroy"); - ResetManagedWebContents(async); + + // For guest view based on OOPIF, the WebContents is released by the embedder + // frame, and we need to clear the reference to the memory. + bool not_owned_by_this = IsGuest() && attached_; +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + // And background pages are owned by extensions::ExtensionHost. + if (type_ == Type::kBackgroundPage) + not_owned_by_this = true; +#endif + if (not_owned_by_this) { + inspectable_web_contents_->ReleaseWebContents(); + WebContentsDestroyed(); + } + + // InspectableWebContents will be automatically destroyed. +} + +void WebContents::Destroy() { + // The content::WebContents should be destroyed asyncronously when possible + // as user may choose to destroy WebContents during an event of it. + if (Browser::Get()->is_shutting_down() || IsGuest() || + type_ == Type::kBrowserView) { + delete this; + } else { + base::PostTask(FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce( + [](base::WeakPtr contents) { + if (contents) + delete contents.get(); + }, + GetWeakPtr())); + } } bool WebContents::DidAddMessageToConsole( @@ -1060,11 +1058,21 @@ void WebContents::AddNewContents( v8::HandleScope handle_scope(isolate); auto api_web_contents = CreateAndTake(isolate, std::move(new_contents), Type::kBrowserWindow); + + // We call RenderFrameCreated here as at this point the empty "about:blank" + // render frame has already been created. If the window never navigates again + // RenderFrameCreated won't be called and certain prefs like + // "kBackgroundColor" will not be applied. + auto* frame = api_web_contents->MainFrame(); + if (frame) { + api_web_contents->HandleNewRenderFrame(frame); + } + if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture, initial_rect.x(), initial_rect.y(), initial_rect.width(), initial_rect.height(), tracker->url, tracker->frame_name, tracker->referrer, tracker->raw_features, tracker->body)) { - api_web_contents->DestroyWebContents(false /* async */); + api_web_contents->Destroy(); } } @@ -1134,8 +1142,6 @@ void WebContents::CloseContents(content::WebContents* source) { autofill_driver_factory->CloseAllPopups(); } - if (inspectable_web_contents_) - inspectable_web_contents_->GetView()->SetDelegate(nullptr); for (ExtendedWebContentsObserver& observer : observers_) observer.OnCloseContents(); } @@ -1351,7 +1357,7 @@ void WebContents::BeforeUnloadFired(bool proceed, // there are two virtual functions named BeforeUnloadFired. } -void WebContents::RenderFrameCreated( +void WebContents::HandleNewRenderFrame( content::RenderFrameHost* render_frame_host) { auto* rwhv = render_frame_host->GetView(); if (!rwhv) @@ -1380,6 +1386,11 @@ void WebContents::RenderFrameCreated( WebFrameMain::RenderFrameCreated(render_frame_host); } +void WebContents::RenderFrameCreated( + content::RenderFrameHost* render_frame_host) { + HandleNewRenderFrame(render_frame_host); +} + void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) { // This event is necessary for tracking any states with respect to // intermediate render view hosts aka speculative render view hosts. Currently @@ -1812,21 +1823,6 @@ void WebContents::SetOwnerWindow(content::WebContents* web_contents, #endif } -void WebContents::ResetManagedWebContents(bool async) { - if (async) { - // Browser context should be destroyed only after the WebContents, - // this is guaranteed in the sync mode by the order of declaration, - // in the async version we maintain a reference until the WebContents - // is destroyed. - // //electron/patches/chromium/content_browser_main_loop.patch - // is required to get the right quit closure for the main message loop. - base::ThreadTaskRunnerHandle::Get()->DeleteSoon( - FROM_HERE, inspectable_web_contents_.release()); - } else { - inspectable_web_contents_.reset(); - } -} - content::WebContents* WebContents::GetWebContents() const { if (!inspectable_web_contents_) return nullptr; @@ -1839,7 +1835,8 @@ content::WebContents* WebContents::GetDevToolsWebContents() const { return inspectable_web_contents_->GetDevToolsWebContents(); } -void WebContents::MarkDestroyed() { +void WebContents::WebContentsDestroyed() { + // Clear the pointer stored in wrapper. if (GetAllWebContents().Lookup(id_)) GetAllWebContents().Remove(id_); v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); @@ -1848,52 +1845,9 @@ void WebContents::MarkDestroyed() { if (!GetWrapper(isolate).ToLocal(&wrapper)) return; wrapper->SetAlignedPointerInInternalField(0, nullptr); -} -// There are three ways of destroying a webContents: -// 1. call webContents.destroy(); -// 2. garbage collection; -// 3. user closes the window of webContents; -// 4. the embedder detaches the frame. -// For webview only #4 will happen, for BrowserWindow both #1 and #3 may -// happen. The #2 should never happen for webContents, because webview is -// managed by GuestViewManager, and BrowserWindow's webContents is managed -// by api::BrowserWindow. -// For #1, the destructor will do the cleanup work and we only need to make -// sure "destroyed" event is emitted. For #3, the content::WebContents will -// be destroyed on close, and WebContentsDestroyed would be called for it, so -// we need to make sure the api::WebContents is also deleted. -// For #4, the WebContents will be destroyed by embedder. -void WebContents::WebContentsDestroyed() { - // Give chance for guest delegate to cleanup its observers - // since the native class is only destroyed in the next tick. - if (guest_delegate_) - guest_delegate_->WillDestroy(); - - // Cleanup relationships with other parts. - - // We can not call Destroy here because we need to call Emit first, but we - // also do not want any method to be used, so just mark as destroyed here. - MarkDestroyed(); - - Observe(nullptr); // this->web_contents() will return nullptr + Observe(nullptr); Emit("destroyed"); - - // For guest view based on OOPIF, the WebContents is released by the embedder - // frame, and we need to clear the reference to the memory. - if (IsGuest() && inspectable_web_contents_) { - inspectable_web_contents_->ReleaseWebContents(); - ResetManagedWebContents(false); - } - - // Destroy the native class in next tick. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce( - [](base::WeakPtr wc) { - if (wc) - delete wc.get(); - }, - GetWeakPtr())); } void WebContents::NavigationEntryCommitted( @@ -2820,6 +2774,18 @@ v8::Local WebContents::CapturePage(gin::Arguments* args) { return handle; } +#if !defined(OS_MAC) + // If the view's renderer is suspended this may fail on Windows/Linux - + // bail if so. See CopyFromSurface in + // content/public/browser/render_widget_host_view.h. + auto* rfh = web_contents()->GetMainFrame(); + if (rfh && + rfh->GetVisibilityState() == blink::mojom::PageVisibilityState::kHidden) { + promise.Resolve(gfx::Image()); + return handle; + } +#endif // defined(OS_MAC) + // Capture full page if user doesn't specify a |rect|. const gfx::Size view_size = rect.IsEmpty() ? view->GetViewBounds().size() : rect.size(); @@ -2886,6 +2852,7 @@ bool WebContents::IsGuest() const { void WebContents::AttachToIframe(content::WebContents* embedder_web_contents, int embedder_frame_id) { + attached_ = true; if (guest_delegate_) guest_delegate_->AttachToIframe(embedder_web_contents, embedder_frame_id); } @@ -3538,17 +3505,11 @@ v8::Local WebContents::FillObjectTemplate( gin::CreateFunctionTemplate( isolate, base::BindRepeating(&gin_helper::Destroyable::IsDestroyed), options)); - templ->Set( - gin::StringToSymbol(isolate, "destroy"), - gin::CreateFunctionTemplate( - isolate, base::BindRepeating([](gin::Handle handle) { - delete handle.get(); - }), - options)); // We use gin_helper::ObjectTemplateBuilder instead of // gin::ObjectTemplateBuilder here to handle the fact that WebContents is // destroyable. return gin_helper::ObjectTemplateBuilder(isolate, templ) + .SetMethod("destroy", &WebContents::Destroy) .SetMethod("getBackgroundThrottling", &WebContents::GetBackgroundThrottling) .SetMethod("setBackgroundThrottling", diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index acf560fb78ff..2c3dacc63dab 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -144,23 +144,9 @@ class WebContents : public gin::Wrappable, v8::Local); const char* GetTypeName() override; + void Destroy(); base::WeakPtr GetWeakPtr() { return weak_factory_.GetWeakPtr(); } - // Destroy the managed content::WebContents instance. - // - // Note: The |async| should only be |true| when users are expecting to use the - // webContents immediately after the call. Always pass |false| if you are not - // sure. - // See https://github.com/electron/electron/issues/8930. - // - // Note: When destroying a webContents member inside a destructor, the |async| - // should always be |false|, otherwise the destroy task might be delayed after - // normal shutdown procedure, resulting in an assertion. - // The normal pattern for calling this method in destructor is: - // api_web_contents_->DestroyWebContents(!Browser::Get()->is_shutting_down()) - // See https://github.com/electron/electron/issues/15133. - void DestroyWebContents(bool async); - bool GetBackgroundThrottling() const; void SetBackgroundThrottling(bool allowed); int GetProcessID() const; @@ -210,6 +196,7 @@ class WebContents : public gin::Wrappable, void IncrementCapturerCount(gin::Arguments* args); void DecrementCapturerCount(gin::Arguments* args); bool IsBeingCaptured(); + void HandleNewRenderFrame(content::RenderFrameHost* render_frame_host); #if BUILDFLAG(ENABLE_PRINTING) void OnGetDefaultPrinter(base::Value print_settings, @@ -362,8 +349,6 @@ class WebContents : public gin::Wrappable, return EmitCustomEvent(name, event, std::forward(args)...); } - void MarkDestroyed(); - WebContents* embedder() { return embedder_; } #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) @@ -672,9 +657,6 @@ class WebContents : public gin::Wrappable, std::string* class_name) override; #endif - // Destroy the managed InspectableWebContents object. - void ResetManagedWebContents(bool async); - // DevTools index event callbacks. void OnDevToolsIndexingWorkCalculated(int request_id, const std::string& file_system_path, @@ -706,6 +688,9 @@ class WebContents : public gin::Wrappable, // The host webcontents that may contain this webcontents. WebContents* embedder_ = nullptr; + // Whether the guest view has been attached. + bool attached_ = false; + // The zoom controller for this webContents. WebContentsZoomController* zoom_controller_ = nullptr; diff --git a/shell/browser/api/electron_api_web_contents_view.cc b/shell/browser/api/electron_api_web_contents_view.cc index 48e32af4d0e9..cb89c927422e 100644 --- a/shell/browser/api/electron_api_web_contents_view.cc +++ b/shell/browser/api/electron_api_web_contents_view.cc @@ -44,18 +44,8 @@ WebContentsView::WebContentsView(v8::Isolate* isolate, } WebContentsView::~WebContentsView() { - if (api_web_contents_) { // destroy() is called - // Destroy WebContents asynchronously, as we might be in GC currently and - // WebContents emits an event in its destructor. - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce( - [](base::WeakPtr contents) { - if (contents) - contents->DestroyWebContents( - !Browser::Get()->is_shutting_down()); - }, - api_web_contents_->GetWeakPtr())); - } + if (api_web_contents_) // destroy() called without closing WebContents + api_web_contents_->Destroy(); } gin::Handle WebContentsView::GetWebContents(v8::Isolate* isolate) { diff --git a/shell/browser/badging/badge_manager.cc b/shell/browser/badging/badge_manager.cc index 4cbd5595b01d..d99be915ca9e 100755 --- a/shell/browser/badging/badge_manager.cc +++ b/shell/browser/badging/badge_manager.cc @@ -47,6 +47,27 @@ void BadgeManager::BindFrameReceiver( std::move(context)); } +void BadgeManager::BindServiceWorkerReceiver( + content::RenderProcessHost* service_worker_process_host, + const GURL& service_worker_scope, + mojo::PendingReceiver receiver) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + auto* browser_context = service_worker_process_host->GetBrowserContext(); + + auto* badge_manager = + badging::BadgeManagerFactory::GetInstance()->GetForBrowserContext( + browser_context); + if (!badge_manager) + return; + + auto context = std::make_unique( + service_worker_process_host->GetID(), service_worker_scope); + + badge_manager->receivers_.Add(badge_manager, std::move(receiver), + std::move(context)); +} + std::string BadgeManager::GetBadgeString(base::Optional badge_content) { if (!badge_content) return "•"; diff --git a/shell/browser/badging/badge_manager.h b/shell/browser/badging/badge_manager.h index a4cb546b986f..5df9679b5d14 100644 --- a/shell/browser/badging/badge_manager.h +++ b/shell/browser/badging/badge_manager.h @@ -37,6 +37,10 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService { static void BindFrameReceiver( content::RenderFrameHost* frame, mojo::PendingReceiver receiver); + static void BindServiceWorkerReceiver( + content::RenderProcessHost* service_worker_process_host, + const GURL& service_worker_scope, + mojo::PendingReceiver receiver); // Determines the text to put on the badge based on some badge_content. static std::string GetBadgeString(base::Optional badge_content); @@ -66,6 +70,21 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService { int frame_id_; }; + // The BindingContext for ServiceWorkerGlobalScope execution contexts. + class ServiceWorkerBindingContext final : public BindingContext { + public: + ServiceWorkerBindingContext(int process_id, const GURL& scope) + : process_id_(process_id), scope_(scope) {} + ~ServiceWorkerBindingContext() override = default; + + int GetProcessId() { return process_id_; } + GURL GetScope() { return scope_; } + + private: + int process_id_; + GURL scope_; + }; + // blink::mojom::BadgeService: // Note: These are private to stop them being called outside of mojo as they // require a mojo binding context. diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index 2aa0087658ec..c87941672fa1 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -1785,4 +1785,12 @@ content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() { return bluetooth_delegate_.get(); } +void ElectronBrowserClient::BindBadgeServiceReceiverFromServiceWorker( + content::RenderProcessHost* service_worker_process_host, + const GURL& service_worker_scope, + mojo::PendingReceiver receiver) { + badging::BadgeManager::BindServiceWorkerReceiver( + service_worker_process_host, service_worker_scope, std::move(receiver)); +} + } // namespace electron diff --git a/shell/browser/electron_browser_client.h b/shell/browser/electron_browser_client.h index 3a73958b01db..68df0626de3e 100644 --- a/shell/browser/electron_browser_client.h +++ b/shell/browser/electron_browser_client.h @@ -73,6 +73,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient, void RegisterBrowserInterfaceBindersForFrame( content::RenderFrameHost* render_frame_host, mojo::BinderMapWithContext* map) override; + void BindBadgeServiceReceiverFromServiceWorker( + content::RenderProcessHost* service_worker_process_host, + const GURL& service_worker_scope, + mojo::PendingReceiver receiver) override; #if defined(OS_LINUX) void GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index ea13ec7978a6..21fff3e72474 100644 --- a/shell/browser/native_window_mac.mm +++ b/shell/browser/native_window_mac.mm @@ -996,8 +996,10 @@ void NativeWindowMac::SetBackgroundColor(SkColor color) { } SkColor NativeWindowMac::GetBackgroundColor() { - return skia::CGColorRefToSkColor( - [[[window_ contentView] layer] backgroundColor]); + CGColorRef color = [[[window_ contentView] layer] backgroundColor]; + if (!color) + return SK_ColorTRANSPARENT; + return skia::CGColorRefToSkColor(color); } void NativeWindowMac::SetHasShadow(bool has_shadow) { diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index 646a0368ed52..480445142b31 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -956,7 +956,10 @@ bool NativeWindowViews::IsTabletMode() const { } SkColor NativeWindowViews::GetBackgroundColor() { - return root_view_->background()->get_color(); + auto* background = root_view_->background(); + if (!background) + return SK_ColorTRANSPARENT; + return background->get_color(); } void NativeWindowViews::SetBackgroundColor(SkColor background_color) { diff --git a/shell/browser/resources/win/electron.rc b/shell/browser/resources/win/electron.rc index 643696f3c7bc..edc04e307ff5 100644 --- a/shell/browser/resources/win/electron.rc +++ b/shell/browser/resources/win/electron.rc @@ -50,8 +50,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 14,0,0,20210304 - PRODUCTVERSION 14,0,0,20210304 + FILEVERSION 14,0,0,20210315 + PRODUCTVERSION 14,0,0,20210315 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L diff --git a/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h index ba247812bb8b..e82d2e3df8c3 100644 --- a/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h +++ b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h @@ -34,13 +34,13 @@ using electron::InspectableWebContentsViewMac; BOOL devtools_visible_; BOOL devtools_docked_; BOOL devtools_is_first_responder_; + BOOL attached_to_window_; DevToolsContentsResizingStrategy strategy_; } - (instancetype)initWithInspectableWebContentsViewMac: (InspectableWebContentsViewMac*)view; -- (void)removeObservers; - (void)notifyDevToolsFocused; - (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate; - (BOOL)isDevToolsVisible; diff --git a/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm index cdd10716c0ee..1d69831c23fa 100644 --- a/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm +++ b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm @@ -35,18 +35,7 @@ devtools_visible_ = NO; devtools_docked_ = NO; devtools_is_first_responder_ = NO; - - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(viewDidBecomeFirstResponder:) - name:kViewDidBecomeFirstResponder - object:nil]; - - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(parentWindowBecameMain:) - name:NSWindowDidBecomeMainNotification - object:nil]; + attached_to_window_ = NO; if (inspectableWebContentsView_->inspectable_web_contents()->IsGuest()) { fake_view_.reset([[NSView alloc] init]); @@ -69,14 +58,34 @@ return self; } -- (void)removeObservers { +- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; } - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize { [self adjustSubviews]; } +- (void)viewDidMoveToWindow { + if (attached_to_window_ && !self.window) { + attached_to_window_ = NO; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } else if (!attached_to_window_ && self.window) { + attached_to_window_ = YES; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(viewDidBecomeFirstResponder:) + name:kViewDidBecomeFirstResponder + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(parentWindowBecameMain:) + name:NSWindowDidBecomeMainNotification + object:nil]; + } +} + - (IBAction)showDevTools:(id)sender { inspectableWebContentsView_->inspectable_web_contents()->ShowDevTools(true); } @@ -253,8 +262,7 @@ - (void)viewDidBecomeFirstResponder:(NSNotification*)notification { auto* inspectable_web_contents = inspectableWebContentsView_->inspectable_web_contents(); - if (!inspectable_web_contents || inspectable_web_contents->IsGuest()) - return; + DCHECK(inspectable_web_contents); auto* webContents = inspectable_web_contents->GetWebContents(); auto* webContentsView = webContents->GetNativeView().GetNativeNSView(); diff --git a/shell/browser/ui/inspectable_web_contents.cc b/shell/browser/ui/inspectable_web_contents.cc index 041de4ffe400..6d804138fa3b 100644 --- a/shell/browser/ui/inspectable_web_contents.cc +++ b/shell/browser/ui/inspectable_web_contents.cc @@ -371,13 +371,8 @@ InspectableWebContents::InspectableWebContents( InspectableWebContents::~InspectableWebContents() { g_web_contents_instances_.remove(this); // Unsubscribe from devtools and Clean up resources. - if (GetDevToolsWebContents()) { - if (managed_devtools_web_contents_) - managed_devtools_web_contents_->SetDelegate(nullptr); - // Calling this also unsubscribes the observer, so WebContentsDestroyed - // won't be called again. + if (GetDevToolsWebContents()) WebContentsDestroyed(); - } // Let destructor destroy managed_devtools_web_contents_. } @@ -416,6 +411,8 @@ bool InspectableWebContents::IsGuest() const { void InspectableWebContents::ReleaseWebContents() { web_contents_.release(); + WebContentsDestroyed(); + view_.reset(); } void InspectableWebContents::SetDockState(const std::string& state) { @@ -936,6 +933,9 @@ void InspectableWebContents::RenderFrameHostChanged( } void InspectableWebContents::WebContentsDestroyed() { + if (managed_devtools_web_contents_) + managed_devtools_web_contents_->SetDelegate(nullptr); + frontend_loaded_ = false; external_devtools_web_contents_ = nullptr; Observe(nullptr); diff --git a/shell/browser/ui/inspectable_web_contents_view_mac.mm b/shell/browser/ui/inspectable_web_contents_view_mac.mm index 9d3aaa0830e5..2523a933e5b2 100644 --- a/shell/browser/ui/inspectable_web_contents_view_mac.mm +++ b/shell/browser/ui/inspectable_web_contents_view_mac.mm @@ -26,7 +26,6 @@ InspectableWebContentsViewMac::InspectableWebContentsViewMac( initWithInspectableWebContentsViewMac:this]) {} InspectableWebContentsViewMac::~InspectableWebContentsViewMac() { - [view_ removeObservers]; CloseDevTools(); } diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index 32029ef99f19..8cab86df11f9 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/shell/browser/web_contents_preferences.cc @@ -422,11 +422,6 @@ void WebContentsPreferences::OverrideWebkitPrefs( // Run Electron APIs and preload script in isolated world prefs->context_isolation = IsEnabled(options::kContextIsolation, true); -#if BUILDFLAG(ENABLE_REMOTE_MODULE) - // Whether to enable the remote module - prefs->enable_remote_module = IsEnabled(options::kEnableRemoteModule, false); -#endif - prefs->world_safe_execute_javascript = IsEnabled(options::kWorldSafeExecuteJavaScript, true); diff --git a/shell/common/api/BUILD.gn b/shell/common/api/BUILD.gn index a9faed10a3fc..f018422d560f 100644 --- a/shell/common/api/BUILD.gn +++ b/shell/common/api/BUILD.gn @@ -14,9 +14,4 @@ mojom("mojo") { # interfaces aready included in blink_common.dll overridden_deps = [ "//third_party/blink/public/mojom:mojom_core" ] component_deps = [ "//third_party/blink/public/common" ] - - enabled_features = [] - if (enable_remote_module) { - enabled_features += [ "enable_remote_module" ] - } } diff --git a/shell/common/api/electron_api_asar.cc b/shell/common/api/electron_api_asar.cc index b72c9304e02a..d449e29db791 100644 --- a/shell/common/api/electron_api_asar.cc +++ b/shell/common/api/electron_api_asar.cc @@ -4,7 +4,6 @@ #include -#include "base/numerics/safe_math.h" #include "gin/handle.h" #include "gin/object_template_builder.h" #include "gin/wrappable.h" @@ -13,9 +12,6 @@ #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_helper/dictionary.h" -#include "shell/common/gin_helper/error_thrower.h" -#include "shell/common/gin_helper/function_template_extensions.h" -#include "shell/common/gin_helper/promise.h" #include "shell/common/node_includes.h" #include "shell/common/node_util.h" @@ -42,8 +38,7 @@ class Archive : public gin::Wrappable { .SetMethod("readdir", &Archive::Readdir) .SetMethod("realpath", &Archive::Realpath) .SetMethod("copyFileOut", &Archive::CopyFileOut) - .SetMethod("read", &Archive::Read) - .SetMethod("readSync", &Archive::ReadSync); + .SetMethod("getFd", &Archive::GetFD); } const char* GetTypeName() override { return "Archive"; } @@ -109,68 +104,15 @@ class Archive : public gin::Wrappable { return gin::ConvertToV8(isolate, new_path); } - v8::Local ReadSync(gin_helper::ErrorThrower thrower, - uint64_t offset, - uint64_t length) { - base::CheckedNumeric safe_offset(offset); - base::CheckedNumeric safe_end = safe_offset + length; - if (!safe_end.IsValid() || - safe_end.ValueOrDie() > archive_->file()->length()) { - thrower.ThrowError("Out of bounds read"); - return v8::Local(); - } - auto array_buffer = v8::ArrayBuffer::New(thrower.isolate(), length); - auto backing_store = array_buffer->GetBackingStore(); - memcpy(backing_store->Data(), archive_->file()->data() + offset, length); - return array_buffer; - } - - v8::Local Read(v8::Isolate* isolate, - uint64_t offset, - uint64_t length) { - gin_helper::Promise> promise(isolate); - v8::Local handle = promise.GetHandle(); - - base::CheckedNumeric safe_offset(offset); - base::CheckedNumeric safe_end = safe_offset + length; - if (!safe_end.IsValid() || - safe_end.ValueOrDie() > archive_->file()->length()) { - promise.RejectWithErrorMessage("Out of bounds read"); - return handle; - } - - auto backing_store = v8::ArrayBuffer::NewBackingStore(isolate, length); - base::ThreadPool::PostTaskAndReplyWithResult( - FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, - base::BindOnce(&Archive::ReadOnIO, isolate, archive_, - std::move(backing_store), offset, length), - base::BindOnce(&Archive::ResolveReadOnUI, std::move(promise))); - - return handle; + // Return the file descriptor. + int GetFD() const { + if (!archive_) + return -1; + return archive_->GetFD(); } private: - static std::unique_ptr ReadOnIO( - v8::Isolate* isolate, - std::shared_ptr archive, - std::unique_ptr backing_store, - uint64_t offset, - uint64_t length) { - memcpy(backing_store->Data(), archive->file()->data() + offset, length); - return backing_store; - } - - static void ResolveReadOnUI( - gin_helper::Promise> promise, - std::unique_ptr backing_store) { - v8::HandleScope scope(promise.isolate()); - v8::Context::Scope context_scope(promise.GetContext()); - auto array_buffer = - v8::ArrayBuffer::New(promise.isolate(), std::move(backing_store)); - promise.Resolve(array_buffer); - } - - std::shared_ptr archive_; + std::unique_ptr archive_; DISALLOW_COPY_AND_ASSIGN(Archive); }; diff --git a/shell/common/api/electron_api_v8_util.cc b/shell/common/api/electron_api_v8_util.cc index 1879d302a501..a0133ec52584 100644 --- a/shell/common/api/electron_api_v8_util.cc +++ b/shell/common/api/electron_api_v8_util.cc @@ -138,6 +138,10 @@ void TriggerFatalErrorForTesting(v8::Isolate* isolate) { v8::ExtensionConfiguration config(1, bDeps); v8::Context::New(isolate, &config); } + +void RunUntilIdle() { + base::RunLoop().RunUntilIdle(); +} #endif void Initialize(v8::Local exports, @@ -158,6 +162,7 @@ void Initialize(v8::Local exports, dict.SetMethod("getWeaklyTrackedValues", &GetWeaklyTrackedValues); dict.SetMethod("clearWeaklyTrackedValues", &ClearWeaklyTrackedValues); dict.SetMethod("weaklyTrackValue", &WeaklyTrackValue); + dict.SetMethod("runUntilIdle", &RunUntilIdle); #endif } diff --git a/shell/common/api/features.cc b/shell/common/api/features.cc index a53573f7c8f5..92208cfba036 100644 --- a/shell/common/api/features.cc +++ b/shell/common/api/features.cc @@ -22,10 +22,6 @@ bool IsOffscreenRenderingEnabled() { return BUILDFLAG(ENABLE_OSR); } -bool IsRemoteModuleEnabled() { - return BUILDFLAG(ENABLE_REMOTE_MODULE); -} - bool IsPDFViewerEnabled() { return BUILDFLAG(ENABLE_PDF_VIEWER); } @@ -78,7 +74,6 @@ void Initialize(v8::Local exports, dict.SetMethod("isBuiltinSpellCheckerEnabled", &IsBuiltinSpellCheckerEnabled); dict.SetMethod("isDesktopCapturerEnabled", &IsDesktopCapturerEnabled); dict.SetMethod("isOffscreenRenderingEnabled", &IsOffscreenRenderingEnabled); - dict.SetMethod("isRemoteModuleEnabled", &IsRemoteModuleEnabled); dict.SetMethod("isPDFViewerEnabled", &IsPDFViewerEnabled); dict.SetMethod("isRunAsNodeEnabled", &IsRunAsNodeEnabled); dict.SetMethod("isFakeLocationProviderEnabled", diff --git a/shell/common/asar/archive.cc b/shell/common/asar/archive.cc index 06ba2c7e2504..89c5a8d418c9 100644 --- a/shell/common/asar/archive.cc +++ b/shell/common/asar/archive.cc @@ -117,51 +117,78 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, } // namespace -Archive::Archive(const base::FilePath& path) : path_(path) { +Archive::Archive(const base::FilePath& path) + : path_(path), file_(base::File::FILE_OK) { base::ThreadRestrictions::ScopedAllowIO allow_io; - if (base::PathExists(path_) && !file_.Initialize(path_)) { - LOG(ERROR) << "Failed to open ASAR archive at '" << path_.value() << "'"; - } + file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ); +#if defined(OS_WIN) + fd_ = _open_osfhandle(reinterpret_cast(file_.GetPlatformFile()), 0); +#elif defined(OS_POSIX) + fd_ = file_.GetPlatformFile(); +#endif } -Archive::~Archive() {} +Archive::~Archive() { +#if defined(OS_WIN) + if (fd_ != -1) { + _close(fd_); + // Don't close the handle since we already closed the fd. + file_.TakePlatformFile(); + } +#endif + base::ThreadRestrictions::ScopedAllowIO allow_io; + file_.Close(); +} bool Archive::Init() { if (!file_.IsValid()) { + if (file_.error_details() != base::File::FILE_ERROR_NOT_FOUND) { + LOG(WARNING) << "Opening " << path_.value() << ": " + << base::File::ErrorToString(file_.error_details()); + } return false; } - if (file_.length() < 8) { - LOG(ERROR) << "Malformed ASAR file at '" << path_.value() - << "' (too short)"; + std::vector buf; + int len; + + buf.resize(8); + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + len = file_.ReadAtCurrentPos(buf.data(), buf.size()); + } + if (len != static_cast(buf.size())) { + PLOG(ERROR) << "Failed to read header size from " << path_.value(); return false; } uint32_t size; - base::PickleIterator size_pickle( - base::Pickle(reinterpret_cast(file_.data()), 8)); - if (!size_pickle.ReadUInt32(&size)) { - LOG(ERROR) << "Failed to read header size at '" << path_.value() << "'"; + if (!base::PickleIterator(base::Pickle(buf.data(), buf.size())) + .ReadUInt32(&size)) { + LOG(ERROR) << "Failed to parse header size from " << path_.value(); return false; } - if (file_.length() - 8 < size) { - LOG(ERROR) << "Malformed ASAR file at '" << path_.value() - << "' (incorrect header)"; + buf.resize(size); + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + len = file_.ReadAtCurrentPos(buf.data(), buf.size()); + } + if (len != static_cast(buf.size())) { + PLOG(ERROR) << "Failed to read header from " << path_.value(); return false; } - base::PickleIterator header_pickle( - base::Pickle(reinterpret_cast(file_.data() + 8), size)); std::string header; - if (!header_pickle.ReadString(&header)) { - LOG(ERROR) << "Failed to read header string at '" << path_.value() << "'"; + if (!base::PickleIterator(base::Pickle(buf.data(), buf.size())) + .ReadString(&header)) { + LOG(ERROR) << "Failed to parse header from " << path_.value(); return false; } base::Optional value = base::JSONReader::Read(header); if (!value || !value->is_dict()) { - LOG(ERROR) << "Header was not valid JSON at '" << path_.value() << "'"; + LOG(ERROR) << "Failed to parse header"; return false; } @@ -264,24 +291,11 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { return true; } - base::CheckedNumeric safe_offset(info.offset); - auto safe_end = safe_offset + info.size; - if (!safe_end.IsValid() || safe_end.ValueOrDie() > file_.length()) - return false; - auto temp_file = std::make_unique(); base::FilePath::StringType ext = path.Extension(); - if (!temp_file->Init(ext)) + if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size)) return false; - base::File dest(temp_file->path(), - base::File::FLAG_OPEN | base::File::FLAG_WRITE); - if (!dest.IsValid()) - return false; - - dest.WriteAtCurrentPos( - reinterpret_cast(file_.data() + info.offset), info.size); - #if defined(OS_POSIX) if (info.executable) { // chmod a+x temp_file; @@ -294,4 +308,8 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { return true; } +int Archive::GetFD() const { + return fd_; +} + } // namespace asar diff --git a/shell/common/asar/archive.h b/shell/common/asar/archive.h index 86d84aa8e7f8..d97cb03cc781 100644 --- a/shell/common/asar/archive.h +++ b/shell/common/asar/archive.h @@ -11,7 +11,6 @@ #include "base/files/file.h" #include "base/files/file_path.h" -#include "base/files/memory_mapped_file.h" namespace base { class DictionaryValue; @@ -62,13 +61,16 @@ class Archive { // For unpacked file, this method will return its real path. bool CopyFileOut(const base::FilePath& path, base::FilePath* out); - base::MemoryMappedFile* file() { return &file_; } + // Returns the file's fd. + int GetFD() const; + base::FilePath path() const { return path_; } base::DictionaryValue* header() const { return header_.get(); } private: base::FilePath path_; - base::MemoryMappedFile file_; + base::File file_; + int fd_ = -1; uint32_t header_size_ = 0; std::unique_ptr header_; diff --git a/shell/common/asar/scoped_temporary_file.cc b/shell/common/asar/scoped_temporary_file.cc index e51b9dad3b87..b9bc500f04fb 100644 --- a/shell/common/asar/scoped_temporary_file.cc +++ b/shell/common/asar/scoped_temporary_file.cc @@ -48,4 +48,27 @@ bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) { return true; } +bool ScopedTemporaryFile::InitFromFile(base::File* src, + const base::FilePath::StringType& ext, + uint64_t offset, + uint64_t size) { + if (!src->IsValid()) + return false; + + if (!Init(ext)) + return false; + + std::vector buf(size); + int len = src->Read(offset, buf.data(), buf.size()); + if (len != static_cast(size)) + return false; + + base::File dest(path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE); + if (!dest.IsValid()) + return false; + + return dest.WriteAtCurrentPos(buf.data(), buf.size()) == + static_cast(size); +} + } // namespace asar diff --git a/shell/common/asar/scoped_temporary_file.h b/shell/common/asar/scoped_temporary_file.h index 15f65290a926..e42a244e1220 100644 --- a/shell/common/asar/scoped_temporary_file.h +++ b/shell/common/asar/scoped_temporary_file.h @@ -27,6 +27,12 @@ class ScopedTemporaryFile { // Init an empty temporary file with a certain extension. bool Init(const base::FilePath::StringType& ext); + // Init an temporary file and fill it with content of |path|. + bool InitFromFile(base::File* src, + const base::FilePath::StringType& ext, + uint64_t offset, + uint64_t size); + base::FilePath path() const { return path_; } private: diff --git a/shell/common/color_util.cc b/shell/common/color_util.cc index 69b5e3593818..9e15bc618f39 100644 --- a/shell/common/color_util.cc +++ b/shell/common/color_util.cc @@ -51,4 +51,14 @@ std::string ToRGBHex(SkColor color) { SkColorGetG(color), SkColorGetB(color)); } +std::string ToRGBAHex(SkColor color, bool include_hash) { + std::string color_str = base::StringPrintf( + "%02X%02X%02X%02X", SkColorGetR(color), SkColorGetG(color), + SkColorGetB(color), SkColorGetA(color)); + if (include_hash) { + return "#" + color_str; + } + return color_str; +} + } // namespace electron diff --git a/shell/common/color_util.h b/shell/common/color_util.h index 9329185a8142..9411e8ae352e 100644 --- a/shell/common/color_util.h +++ b/shell/common/color_util.h @@ -17,6 +17,8 @@ SkColor ParseHexColor(const std::string& color_string); // Convert color to RGB hex value like "#ABCDEF" std::string ToRGBHex(SkColor color); +std::string ToRGBAHex(SkColor color, bool include_hash = true); + } // namespace electron #endif // SHELL_COMMON_COLOR_UTIL_H_ diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index 61d746badbe4..fd8aa10844f1 100644 --- a/shell/common/options_switches.cc +++ b/shell/common/options_switches.cc @@ -193,10 +193,6 @@ const char kHiddenPage[] = "hiddenPage"; const char kSpellcheck[] = "spellcheck"; #endif -#if BUILDFLAG(ENABLE_REMOTE_MODULE) -const char kEnableRemoteModule[] = "enableRemoteModule"; -#endif - const char kEnableWebSQL[] = "enableWebSQL"; const char kEnablePreferredSizeMode[] = "enablePreferredSizeMode"; diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 9b51f46d1753..4a46a1c781e8 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -99,10 +99,6 @@ extern const char kHiddenPage[]; extern const char kSpellcheck[]; #endif -#if BUILDFLAG(ENABLE_REMOTE_MODULE) -extern const char kEnableRemoteModule[]; -#endif - } // namespace options // Following are actually command line switches, should be moved to other files. diff --git a/shell/renderer/api/electron_api_web_frame.cc b/shell/renderer/api/electron_api_web_frame.cc index 460dbaa8fc49..af814a9916dc 100644 --- a/shell/renderer/api/electron_api_web_frame.cc +++ b/shell/renderer/api/electron_api_web_frame.cc @@ -427,10 +427,6 @@ v8::Local GetWebPreference(v8::Isolate* isolate, return gin::ConvertToV8(isolate, prefs.opener_id); } else if (pref_name == options::kContextIsolation) { return gin::ConvertToV8(isolate, prefs.context_isolation); -#if BUILDFLAG(ENABLE_REMOTE_MODULE) - } else if (pref_name == options::kEnableRemoteModule) { - return gin::ConvertToV8(isolate, prefs.enable_remote_module); -#endif } else if (pref_name == options::kWorldSafeExecuteJavaScript) { return gin::ConvertToV8(isolate, prefs.world_safe_execute_javascript); } else if (pref_name == options::kGuestInstanceID) { diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index 9189c3f878a9..e1fde902b1d6 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -253,22 +253,6 @@ void RendererClientBase::RenderFrameCreated( // DidCreateDocumentElement event. new ElectronApiServiceImpl(render_frame, this); - content::RenderView* render_view = render_frame->GetRenderView(); - if (render_frame->IsMainFrame() && render_view) { - blink::WebView* webview = render_view->GetWebView(); - if (webview) { - auto prefs = render_frame->GetBlinkPreferences(); - if (prefs.guest_instance_id) { // webview. - webview->SetBaseBackgroundColor(SK_ColorTRANSPARENT); - } else { // normal window. - std::string name = prefs.background_color; - SkColor color = - name.empty() ? SK_ColorTRANSPARENT : ParseHexColor(name); - webview->SetBaseBackgroundColor(color); - } - } - } - #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) auto* dispatcher = extensions_renderer_client_->GetDispatcher(); // ExtensionFrameHelper destroys itself when the RenderFrame is destroyed. diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index c2f442778a53..cbbcee18956f 100644 --- a/spec-main/api-app-spec.ts +++ b/spec-main/api-app-spec.ts @@ -295,9 +295,9 @@ describe('app module', () => { const child = cp.spawn(process.execPath, [appPath]); child.stdout.on('data', (c) => console.log(c.toString())); child.stderr.on('data', (c) => console.log(c.toString())); - child.on('exit', (code) => { + child.on('exit', (code, signal) => { if (code !== 0) { - done(`Process exited with code ${code}`); + console.log(`Process exited with code "${code}" signal "${signal}"`); } }); }); @@ -466,101 +466,6 @@ describe('app module', () => { expect(webContents).to.equal(w.webContents); }); }); - - ifdescribe(features.isRemoteModuleEnabled())('remote module filtering', () => { - it('should emit remote-require event when remote.require() is invoked', async () => { - w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - await w.loadURL('about:blank'); - - const promise = emittedOnce(app, 'remote-require'); - w.webContents.executeJavaScript('require(\'electron\').remote.require(\'test\')'); - - const [, webContents, moduleName] = await promise; - expect(webContents).to.equal(w.webContents); - expect(moduleName).to.equal('test'); - }); - - it('should emit remote-get-global event when remote.getGlobal() is invoked', async () => { - w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - await w.loadURL('about:blank'); - - const promise = emittedOnce(app, 'remote-get-global'); - w.webContents.executeJavaScript('require(\'electron\').remote.getGlobal(\'test\')'); - - const [, webContents, globalName] = await promise; - expect(webContents).to.equal(w.webContents); - expect(globalName).to.equal('test'); - }); - - it('should emit remote-get-builtin event when remote.getBuiltin() is invoked', async () => { - w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - await w.loadURL('about:blank'); - - const promise = emittedOnce(app, 'remote-get-builtin'); - w.webContents.executeJavaScript('require(\'electron\').remote.app'); - - const [, webContents, moduleName] = await promise; - expect(webContents).to.equal(w.webContents); - expect(moduleName).to.equal('app'); - }); - - it('should emit remote-get-current-window event when remote.getCurrentWindow() is invoked', async () => { - w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - await w.loadURL('about:blank'); - - const promise = emittedOnce(app, 'remote-get-current-window'); - w.webContents.executeJavaScript('{ require(\'electron\').remote.getCurrentWindow() }'); - - const [, webContents] = await promise; - expect(webContents).to.equal(w.webContents); - }); - - it('should emit remote-get-current-web-contents event when remote.getCurrentWebContents() is invoked', async () => { - w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - await w.loadURL('about:blank'); - - const promise = emittedOnce(app, 'remote-get-current-web-contents'); - w.webContents.executeJavaScript('{ require(\'electron\').remote.getCurrentWebContents() }'); - - const [, webContents] = await promise; - expect(webContents).to.equal(w.webContents); - }); - }); }); describe('app.badgeCount', () => { diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index dd02458ff676..bc673855bbfa 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -1338,6 +1338,22 @@ describe('BrowserWindow module', () => { expect(image.isEmpty()).to.equal(true); }); + it('resolves after the window is hidden', async () => { + const w = new BrowserWindow({ show: false }); + w.loadFile(path.join(fixtures, 'pages', 'a.html')); + await emittedOnce(w, 'ready-to-show'); + w.show(); + + const visibleImage = await w.capturePage(); + expect(visibleImage.isEmpty()).to.equal(false); + + w.hide(); + + const hiddenImage = await w.capturePage(); + const isEmpty = process.platform !== 'darwin'; + expect(hiddenImage.isEmpty()).to.equal(isEmpty); + }); + it('preserves transparency', async () => { const w = new BrowserWindow({ show: false, transparent: true }); w.loadFile(path.join(fixtures, 'pages', 'theme-color.html')); @@ -2015,21 +2031,6 @@ describe('BrowserWindow module', () => { const [, test] = await emittedOnce(ipcMain, 'answer'); expect(test).to.eql('preload'); }); - ifit(features.isRemoteModuleEnabled())('can successfully delete the Buffer global', async () => { - const preload = path.join(__dirname, 'fixtures', 'remote', 'delete-buffer.js'); - const w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false, - preload - } - }); - w.loadFile(path.join(fixtures, 'api', 'preload.html')); - const [, test] = await emittedOnce(ipcMain, 'answer'); - expect(test).to.eql(Buffer.from('buffer')); - }); it('has synchronous access to all eventual window APIs', async () => { const preload = path.join(fixtures, 'module', 'access-blink-apis.js'); const w = new BrowserWindow({ @@ -2142,61 +2143,6 @@ describe('BrowserWindow module', () => { }); }); - ifdescribe(features.isRemoteModuleEnabled())('"enableRemoteModule" option', () => { - const generateSpecs = (description: string, sandbox: boolean) => { - describe(description, () => { - const preload = path.join(__dirname, 'fixtures', 'remote', 'preload-remote.js'); - - it('disables the remote module by default', async () => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - preload, - sandbox - } - }); - const p = emittedOnce(ipcMain, 'remote'); - w.loadFile(path.join(fixtures, 'api', 'blank.html')); - const [, remote] = await p; - expect(remote).to.equal('undefined'); - }); - - it('disables the remote module when false', async () => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - preload, - sandbox, - enableRemoteModule: false - } - }); - const p = emittedOnce(ipcMain, 'remote'); - w.loadFile(path.join(fixtures, 'api', 'blank.html')); - const [, remote] = await p; - expect(remote).to.equal('undefined'); - }); - - it('enables the remote module when true', async () => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - preload, - sandbox, - enableRemoteModule: true - } - }); - const p = emittedOnce(ipcMain, 'remote'); - w.loadFile(path.join(fixtures, 'api', 'blank.html')); - const [, remote] = await p; - expect(remote).to.equal('object'); - }); - }); - }; - - generateSpecs('without sandbox', false); - generateSpecs('with sandbox', true); - }); - describe('"sandbox" option', () => { const preload = path.join(path.resolve(__dirname, 'fixtures'), 'module', 'preload-sandbox.js'); @@ -2512,85 +2458,6 @@ describe('BrowserWindow module', () => { w.loadFile(path.join(fixtures, 'pages', 'window-open.html')); }); - // see #9387 - ifit(features.isRemoteModuleEnabled())('properly manages remote object references after page reload', (done) => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - preload, - sandbox: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - w.loadFile(path.join(__dirname, 'fixtures', 'api', 'sandbox.html'), { search: 'reload-remote' }); - - ipcMain.on('get-remote-module-path', (event) => { - event.returnValue = path.join(fixtures, 'module', 'hello.js'); - }); - - let reload = false; - ipcMain.on('reloaded', (event) => { - event.returnValue = reload; - reload = !reload; - }); - - ipcMain.once('reload', (event) => { - event.sender.reload(); - }); - - ipcMain.once('answer', (event, arg) => { - ipcMain.removeAllListeners('reloaded'); - ipcMain.removeAllListeners('get-remote-module-path'); - try { - expect(arg).to.equal('hi'); - done(); - } catch (e) { - done(e); - } - }); - }); - - ifit(features.isRemoteModuleEnabled())('properly manages remote object references after page reload in child window', (done) => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - preload, - sandbox: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload } } })); - - w.loadFile(path.join(__dirname, 'fixtures', 'api', 'sandbox.html'), { search: 'reload-remote-child' }); - - ipcMain.on('get-remote-module-path', (event) => { - event.returnValue = path.join(fixtures, 'module', 'hello-child.js'); - }); - - let reload = false; - ipcMain.on('reloaded', (event) => { - event.returnValue = reload; - reload = !reload; - }); - - ipcMain.once('reload', (event) => { - event.sender.reload(); - }); - - ipcMain.once('answer', (event, arg) => { - ipcMain.removeAllListeners('reloaded'); - ipcMain.removeAllListeners('get-remote-module-path'); - try { - expect(arg).to.equal('hi child window'); - done(); - } catch (e) { - done(e); - } - }); - }); - it('validates process APIs access in sandboxed renderer', async () => { const w = new BrowserWindow({ show: false, diff --git a/spec-main/api-callbacks-registry-spec.ts b/spec-main/api-callbacks-registry-spec.ts deleted file mode 100644 index 6753518d9cb0..000000000000 --- a/spec-main/api-callbacks-registry-spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect } from 'chai'; -import { CallbacksRegistry } from '../lib/renderer/remote/callbacks-registry'; -import { ifdescribe } from './spec-helpers'; - -const features = process._linkedBinding('electron_common_features'); - -ifdescribe(features.isRemoteModuleEnabled())('CallbacksRegistry module', () => { - let registry: CallbacksRegistry; - - beforeEach(() => { - registry = new CallbacksRegistry(); - }); - - it('adds a callback to the registry', () => { - const cb = () => [1, 2, 3, 4, 5]; - const key = registry.add(cb); - - expect(key).to.exist('key'); - }); - - it('returns a specified callback if it is in the registry', () => { - const cb = () => [1, 2, 3, 4, 5]; - const key = registry.add(cb); - expect(key).to.exist('key'); - const callback = registry.get(key!); - - expect(callback.toString()).equal(cb.toString()); - }); - - it('returns an empty function if the cb doesnt exist', () => { - const callback = registry.get(1); - - expect(callback).to.be.a('function'); - }); - - it('removes a callback to the registry', () => { - const cb = () => [1, 2, 3, 4, 5]; - const key = registry.add(cb); - expect(key).to.exist('key'); - - registry.remove(key!); - const afterCB = registry.get(key!); - - expect(afterCB).to.be.a('function'); - expect(afterCB.toString()).to.not.equal(cb.toString()); - }); -}); diff --git a/spec-main/api-remote-spec.ts b/spec-main/api-remote-spec.ts deleted file mode 100644 index e9934a73e5fb..000000000000 --- a/spec-main/api-remote-spec.ts +++ /dev/null @@ -1,1049 +0,0 @@ -import * as path from 'path'; -import { expect } from 'chai'; -import { closeAllWindows } from './window-helpers'; -import { ifdescribe } from './spec-helpers'; - -import { ipcMain, BrowserWindow } from 'electron/main'; -import { emittedOnce } from './events-helpers'; -import { NativeImage } from 'electron/common'; -import { serialize, deserialize } from '../lib/common/type-utils'; -import { protocol, nativeImage } from 'electron'; - -const features = process._linkedBinding('electron_common_features'); - -const expectPathsEqual = (path1: string, path2: string) => { - if (process.platform === 'win32') { - path1 = path1.toLowerCase(); - path2 = path2.toLowerCase(); - } - expect(path1).to.equal(path2); -}; - -function makeRemotely (windowGetter: () => BrowserWindow) { - async function remotely (script: Function, ...args: any[]) { - // executeJavaScript obfuscates the error if the script throws, so catch any - // errors manually. - const assembledScript = `(async function() { - try { - return { result: await Promise.resolve((${script})(...${JSON.stringify(args)})) } - } catch (e) { - return { error: e.message, stack: e.stack } - } - })()`; - const { result, error, stack } = await windowGetter().webContents.executeJavaScript(assembledScript); - if (error) { - const e = new Error(error); - e.stack = stack; - throw e; - } - return result; - } - remotely.it = (...vars: any[]) => (name: string, fn: Function) => { - it(name, async () => { - await remotely(fn, ...vars); - }); - }; - return remotely; -} - -function makeWindow () { - let w: BrowserWindow; - before(async () => { - w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, enableRemoteModule: true, contextIsolation: false } }); - await w.loadURL('about:blank'); - await w.webContents.executeJavaScript(`{ - const chai_1 = window.chai_1 = require('chai') - chai_1.use(require('chai-as-promised')) - chai_1.use(require('dirty-chai')) - null - }`); - }); - after(closeAllWindows); - return () => w; -} - -function makeEachWindow () { - let w: BrowserWindow; - beforeEach(async () => { - w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, enableRemoteModule: true, contextIsolation: false } }); - await w.loadURL('about:blank'); - await w.webContents.executeJavaScript(`{ - const chai_1 = window.chai_1 = require('chai') - chai_1.use(require('chai-as-promised')) - chai_1.use(require('dirty-chai')) - null - }`); - }); - afterEach(closeAllWindows); - return () => w; -} - -describe('typeUtils serialization/deserialization', () => { - it('serializes and deserializes an empty NativeImage', () => { - const image = nativeImage.createEmpty(); - const serializedImage = serialize(image); - const empty = deserialize(serializedImage); - - expect(empty.isEmpty()).to.be.true(); - expect(empty.getAspectRatio()).to.equal(1); - expect(empty.toDataURL()).to.equal('data:image/png;base64,'); - expect(empty.toDataURL({ scaleFactor: 2.0 })).to.equal('data:image/png;base64,'); - expect(empty.getSize()).to.deep.equal({ width: 0, height: 0 }); - expect(empty.getBitmap()).to.be.empty(); - expect(empty.getBitmap({ scaleFactor: 2.0 })).to.be.empty(); - expect(empty.toBitmap()).to.be.empty(); - expect(empty.toBitmap({ scaleFactor: 2.0 })).to.be.empty(); - expect(empty.toJPEG(100)).to.be.empty(); - expect(empty.toPNG()).to.be.empty(); - expect(empty.toPNG({ scaleFactor: 2.0 })).to.be.empty(); - }); - - it('serializes and deserializes a non-empty NativeImage', () => { - const dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg=='; - const image = nativeImage.createFromDataURL(dataURL); - const serializedImage = serialize(image); - const nonEmpty = deserialize(serializedImage); - - expect(nonEmpty.isEmpty()).to.be.false(); - expect(nonEmpty.getAspectRatio()).to.equal(1); - expect(nonEmpty.toDataURL()).to.not.be.empty(); - expect(nonEmpty.toBitmap({ scaleFactor: 1.0 })).to.deep.equal(image.toBitmap({ scaleFactor: 1.0 })); - expect(nonEmpty.getSize()).to.deep.equal({ width: 2, height: 2 }); - expect(nonEmpty.getBitmap()).to.not.be.empty(); - expect(nonEmpty.toPNG()).to.not.be.empty(); - }); - - it('serializes and deserializes a non-empty NativeImage with multiple representations', () => { - const image = nativeImage.createEmpty(); - - const dataURL1 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII='; - image.addRepresentation({ scaleFactor: 1.0, dataURL: dataURL1 }); - - const dataURL2 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg=='; - image.addRepresentation({ scaleFactor: 2.0, dataURL: dataURL2 }); - - const serializedImage = serialize(image); - const nonEmpty = deserialize(serializedImage); - - expect(nonEmpty.isEmpty()).to.be.false(); - expect(nonEmpty.getAspectRatio()).to.equal(1); - expect(nonEmpty.getSize()).to.deep.equal({ width: 1, height: 1 }); - expect(nonEmpty.getBitmap()).to.not.be.empty(); - expect(nonEmpty.getBitmap({ scaleFactor: 1.0 })).to.not.be.empty(); - expect(nonEmpty.getBitmap({ scaleFactor: 2.0 })).to.not.be.empty(); - expect(nonEmpty.toBitmap()).to.not.be.empty(); - expect(nonEmpty.toBitmap({ scaleFactor: 1.0 })).to.deep.equal(image.toBitmap({ scaleFactor: 1.0 })); - expect(nonEmpty.toBitmap({ scaleFactor: 2.0 })).to.deep.equal(image.toBitmap({ scaleFactor: 2.0 })); - expect(nonEmpty.toPNG()).to.not.be.empty(); - expect(nonEmpty.toPNG({ scaleFactor: 1.0 })).to.not.be.empty(); - expect(nonEmpty.toPNG({ scaleFactor: 2.0 })).to.not.be.empty(); - expect(nonEmpty.toDataURL()).to.not.be.empty(); - }); - - it('serializes and deserializes an Array', () => { - const array = [1, 2, 3, 4, 5]; - const serialized = serialize(array); - const deserialized = deserialize(serialized); - - expect(deserialized).to.deep.equal(array); - }); - - it('serializes and deserializes a Buffer', () => { - const buffer = Buffer.from('hello world!', 'utf-8'); - const serialized = serialize(buffer); - const deserialized = deserialize(serialized); - - expect(deserialized).to.deep.equal(buffer); - }); - - it('serializes and deserializes a Boolean', () => { - const bool = true; - const serialized = serialize(bool); - const deserialized = deserialize(serialized); - - expect(deserialized).to.equal(bool); - }); - - it('serializes and deserializes a Date', () => { - const date = new Date(); - const serialized = serialize(date); - const deserialized = deserialize(serialized); - - expect(deserialized).to.equal(date); - }); - - it('serializes and deserializes a Number', () => { - const number = 42; - const serialized = serialize(number); - const deserialized = deserialize(serialized); - - expect(deserialized).to.equal(number); - }); - - it('serializes and deserializes a Regexp', () => { - const regex = new RegExp('ab+c'); - const serialized = serialize(regex); - const deserialized = deserialize(serialized); - - expect(deserialized).to.equal(regex); - }); - - it('serializes and deserializes a String', () => { - const str = 'hello world'; - const serialized = serialize(str); - const deserialized = deserialize(serialized); - - expect(deserialized).to.equal(str); - }); - - it('serializes and deserializes an Error', () => { - const err = new Error('oh crap'); - const serialized = serialize(err); - const deserialized = deserialize(serialized); - - expect(deserialized).to.equal(err); - }); - - it('serializes and deserializes a simple Object', () => { - const obj = { hello: 'world', 'answer-to-everything': 42 }; - const serialized = serialize(obj); - const deserialized = deserialize(serialized); - - expect(deserialized).to.deep.equal(obj); - }); -}); - -ifdescribe(features.isRemoteModuleEnabled())('remote module', () => { - const fixtures = path.join(__dirname, 'fixtures', 'remote'); - - describe('', () => { - const w = makeWindow(); - const remotely = makeRemotely(w); - - describe('remote.getGlobal filtering', () => { - it('can return custom values', async () => { - w().webContents.once('remote-get-global', (event, name) => { - event.returnValue = name; - }); - expect(await remotely(() => require('electron').remote.getGlobal('test'))).to.equal('test'); - }); - - it('throws when no returnValue set', async () => { - w().webContents.once('remote-get-global', (event) => { - event.preventDefault(); - }); - await expect(remotely(() => require('electron').remote.getGlobal('test'))).to.eventually.be.rejected('Blocked remote.getGlobal(\'test\')'); - }); - }); - - describe('remote.getBuiltin filtering', () => { - it('can return custom values', async () => { - w().webContents.once('remote-get-builtin', (event, name) => { - event.returnValue = name; - }); - expect(await remotely(() => (require('electron').remote as any).getBuiltin('test'))).to.equal('test'); - }); - - it('throws when no returnValue set', async () => { - w().webContents.once('remote-get-builtin', (event) => { - event.preventDefault(); - }); - await expect(remotely(() => (require('electron').remote as any).getBuiltin('test'))).to.eventually.be.rejected('Blocked remote.getGlobal(\'test\')'); - }); - }); - - describe('remote.require filtering', () => { - it('can return custom values', async () => { - w().webContents.once('remote-require', (event, name) => { - event.returnValue = name; - }); - expect(await remotely(() => require('electron').remote.require('test'))).to.equal('test'); - }); - - it('throws when no returnValue set', async () => { - w().webContents.once('remote-require', (event) => { - event.preventDefault(); - }); - await expect(remotely(() => require('electron').remote.require('test'))).to.eventually.be.rejected('Blocked remote.require(\'test\')'); - }); - }); - - describe('remote.getCurrentWindow filtering', () => { - it('can return custom value', async () => { - w().webContents.once('remote-get-current-window', (e) => { - e.returnValue = 'some window'; - }); - expect(await remotely(() => require('electron').remote.getCurrentWindow())).to.equal('some window'); - }); - - it('throws when no returnValue set', async () => { - w().webContents.once('remote-get-current-window', (event) => { - event.preventDefault(); - }); - await expect(remotely(() => require('electron').remote.getCurrentWindow())).to.eventually.be.rejected('Blocked remote.getCurrentWindow()'); - }); - }); - - describe('remote.getCurrentWebContents filtering', () => { - it('can return custom value', async () => { - w().webContents.once('remote-get-current-web-contents', (event) => { - event.returnValue = 'some web contents'; - }); - expect(await remotely(() => require('electron').remote.getCurrentWebContents())).to.equal('some web contents'); - }); - - it('throws when no returnValue set', async () => { - w().webContents.once('remote-get-current-web-contents', (event) => { - event.preventDefault(); - }); - await expect(remotely(() => require('electron').remote.getCurrentWebContents())).to.eventually.be.rejected('Blocked remote.getCurrentWebContents()'); - }); - }); - }); - - describe('remote references', () => { - const w = makeEachWindow(); - it('render-view-deleted is sent when page is destroyed', (done) => { - w().webContents.once('render-view-deleted' as any, () => { - done(); - }); - w().destroy(); - }); - - // The ELECTRON_BROWSER_CONTEXT_RELEASE message relies on this to work. - it('message can be sent on exit when page is being navigated', async () => { - after(() => { ipcMain.removeAllListeners('SENT_ON_EXIT'); }); - w().webContents.once('did-finish-load', () => { - w().webContents.loadURL('about:blank'); - }); - w().loadFile(path.join(fixtures, 'send-on-exit.html')); - await emittedOnce(ipcMain, 'SENT_ON_EXIT'); - }); - }); - - describe('remote function in renderer', () => { - afterEach(() => { - ipcMain.removeAllListeners('done'); - }); - afterEach(closeAllWindows); - - it('works when created in preload script', async () => { - const preload = path.join(fixtures, 'preload-remote-function.js'); - const w = new BrowserWindow({ - show: false, - webPreferences: { - preload, - enableRemoteModule: true, - contextIsolation: false - } - }); - w.loadURL('about:blank'); - await emittedOnce(ipcMain, 'done'); - }); - }); - - describe('remote objects registry', () => { - it('does not dereference until the render view is deleted (regression)', async () => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - - const message = emittedOnce(ipcMain, 'error-message'); - w.loadFile(path.join(fixtures, 'render-view-deleted.html')); - const [, msg] = await message; - expect(msg).to.match(/^Cannot call method 'getURL' on missing remote object/); - }); - }); - - describe('nativeImage serialization', () => { - const w = makeWindow(); - const remotely = makeRemotely(w); - - it('can serialize an empty nativeImage from renderer to main', async () => { - const getImageEmpty = (img: NativeImage) => img.isEmpty(); - - w().webContents.once('remote-get-global', (event) => { - event.returnValue = getImageEmpty; - }); - - await expect(remotely(() => { - const emptyImage = require('electron').nativeImage.createEmpty(); - return require('electron').remote.getGlobal('someFunction')(emptyImage); - })).to.eventually.be.true(); - }); - - it('can serialize an empty nativeImage from main to renderer', async () => { - w().webContents.once('remote-get-global', (event) => { - const emptyImage = require('electron').nativeImage.createEmpty(); - event.returnValue = emptyImage; - }); - - await expect(remotely(() => { - const image = require('electron').remote.getGlobal('someFunction'); - return image.isEmpty(); - })).to.eventually.be.true(); - }); - - it('can serialize a non-empty nativeImage from renderer to main', async () => { - const getImageSize = (img: NativeImage) => img.getSize(); - - w().webContents.once('remote-get-global', (event) => { - event.returnValue = getImageSize; - }); - - await expect(remotely(() => { - const { nativeImage } = require('electron'); - const nonEmptyImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg=='); - return require('electron').remote.getGlobal('someFunction')(nonEmptyImage); - })).to.eventually.deep.equal({ width: 2, height: 2 }); - }); - - it('can serialize a non-empty nativeImage from main to renderer', async () => { - w().webContents.once('remote-get-global', (event) => { - const nonEmptyImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg=='); - event.returnValue = nonEmptyImage; - }); - - await expect(remotely(() => { - const image = require('electron').remote.getGlobal('someFunction'); - return image.getSize(); - })).to.eventually.deep.equal({ width: 2, height: 2 }); - }); - - it('can properly create a menu with an nativeImage icon in the renderer', async () => { - await expect(remotely(() => { - const { remote, nativeImage } = require('electron'); - remote.Menu.buildFromTemplate([ - { - label: 'hello', - icon: nativeImage.createEmpty() - } - ]); - })).to.be.fulfilled(); - }); - }); - - describe('remote listeners', () => { - afterEach(closeAllWindows); - - it('detaches listeners subscribed to destroyed renderers, and shows a warning', async () => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - contextIsolation: false - } - }); - await w.loadFile(path.join(fixtures, 'remote-event-handler.html')); - w.webContents.reload(); - await emittedOnce(w.webContents, 'did-finish-load'); - - const expectedMessage = [ - 'Attempting to call a function in a renderer window that has been closed or released.', - 'Function provided here: remote-event-handler.html:11:33', - 'Remote event names: remote-handler, other-remote-handler' - ].join('\n'); - - expect(w.webContents.listenerCount('remote-handler')).to.equal(2); - let warnMessage: string | null = null; - const originalWarn = console.warn; - let warned: Function; - const warnPromise = new Promise(resolve => { - warned = resolve; - }); - try { - console.warn = (message: string) => { - warnMessage = message; - warned(); - }; - w.webContents.emit('remote-handler', { sender: w.webContents }); - await warnPromise; - } finally { - console.warn = originalWarn; - } - expect(w.webContents.listenerCount('remote-handler')).to.equal(1); - expect(warnMessage).to.equal(expectedMessage); - }); - }); - - describe('remote.require', () => { - const w = makeWindow(); - const remotely = makeRemotely(w); - - remotely.it()('should returns same object for the same module', () => { - const { remote } = require('electron'); - const a = remote.require('electron'); - const b = remote.require('electron'); - expect(a).to.equal(b); - }); - - remotely.it(path.join(fixtures, 'id.js'))('should work when object contains id property', (module: string) => { - const { id } = require('electron').remote.require(module); - expect(id).to.equal(1127); - }); - - remotely.it(path.join(fixtures, 'no-prototype.js'))('should work when object has no prototype', (module: string) => { - const a = require('electron').remote.require(module); - expect(a.foo.bar).to.equal('baz'); - expect(a.foo.baz).to.equal(false); - expect(a.bar).to.equal(1234); - expect(a.getConstructorName(Object.create(null))).to.equal(''); - expect(a.getConstructorName(new (class {})())).to.equal(''); - }); - - it('should search module from the user app', async () => { - expectPathsEqual( - path.normalize(await remotely(() => { - const { remote } = require('electron'); - return (remote as any).process.mainModule.filename; - })), - path.resolve(__dirname, 'index.js') - ); - expectPathsEqual( - path.normalize(await remotely(() => { - const { remote } = require('electron'); - return (remote as any).process.mainModule.paths[0]; - })), - path.resolve(__dirname, 'node_modules') - ); - }); - - remotely.it(fixtures)('should work with function properties', (fixtures: string) => { - const path = require('path'); - - { - const a = require('electron').remote.require(path.join(fixtures, 'export-function-with-properties.js')); - expect(typeof a).to.equal('function'); - expect(a.bar).to.equal('baz'); - } - - { - const a = require('electron').remote.require(path.join(fixtures, 'function-with-properties.js')); - expect(typeof a).to.equal('object'); - expect(a.foo()).to.equal('hello'); - expect(a.foo.bar).to.equal('baz'); - expect(a.foo.nested.prop).to.equal('yes'); - expect(a.foo.method1()).to.equal('world'); - expect(a.foo.method1.prop1()).to.equal(123); - } - - { - const a = require('electron').remote.require(path.join(fixtures, 'function-with-missing-properties.js')).setup(); - expect(a.bar()).to.equal(true); - expect(a.bar.baz).to.be.undefined(); - } - }); - - remotely.it(fixtures)('should work with static class members', (fixtures: string) => { - const path = require('path'); - const a = require('electron').remote.require(path.join(fixtures, 'remote-static.js')); - expect(typeof a.Foo).to.equal('function'); - expect(a.Foo.foo()).to.equal(3); - expect(a.Foo.bar).to.equal('baz'); - expect(new a.Foo().baz()).to.equal(123); - }); - - remotely.it(fixtures)('includes the length of functions specified as arguments', (fixtures: string) => { - const path = require('path'); - const a = require('electron').remote.require(path.join(fixtures, 'function-with-args.js')); - /* eslint-disable @typescript-eslint/no-unused-vars */ - expect(a((a: any, b: any, c: any) => {})).to.equal(3); - expect(a((a: any) => {})).to.equal(1); - expect(a((...args: any[]) => {})).to.equal(0); - /* eslint-enable @typescript-eslint/no-unused-vars */ - }); - - remotely.it(fixtures)('handles circular references in arrays and objects', (fixtures: string) => { - const path = require('path'); - const a = require('electron').remote.require(path.join(fixtures, 'circular.js')); - - let arrayA: any[] = ['foo']; - const arrayB = [arrayA, 'bar']; - arrayA.push(arrayB); - expect(a.returnArgs(arrayA, arrayB)).to.deep.equal([ - ['foo', [null, 'bar']], - [['foo', null], 'bar'] - ]); - - let objectA: any = { foo: 'bar' }; - const objectB = { baz: objectA }; - objectA.objectB = objectB; - expect(a.returnArgs(objectA, objectB)).to.deep.equal([ - { foo: 'bar', objectB: { baz: null } }, - { baz: { foo: 'bar', objectB: null } } - ]); - - arrayA = [1, 2, 3]; - expect(a.returnArgs({ foo: arrayA }, { bar: arrayA })).to.deep.equal([ - { foo: [1, 2, 3] }, - { bar: [1, 2, 3] } - ]); - - objectA = { foo: 'bar' }; - expect(a.returnArgs({ foo: objectA }, { bar: objectA })).to.deep.equal([ - { foo: { foo: 'bar' } }, - { bar: { foo: 'bar' } } - ]); - - arrayA = []; - arrayA.push(arrayA); - expect(a.returnArgs(arrayA)).to.deep.equal([ - [null] - ]); - - objectA = {}; - objectA.foo = objectA; - objectA.bar = 'baz'; - expect(a.returnArgs(objectA)).to.deep.equal([ - { foo: null, bar: 'baz' } - ]); - - objectA = {}; - objectA.foo = { bar: objectA }; - objectA.bar = 'baz'; - expect(a.returnArgs(objectA)).to.deep.equal([ - { foo: { bar: null }, bar: 'baz' } - ]); - }); - }); - - describe('remote.createFunctionWithReturnValue', () => { - const remotely = makeRemotely(makeWindow()); - - remotely.it(fixtures)('should be called in browser synchronously', async (fixtures: string) => { - const { remote } = require('electron'); - const path = require('path'); - const buf = Buffer.from('test'); - const call = remote.require(path.join(fixtures, 'call.js')); - const result = call.call((remote as any).createFunctionWithReturnValue(buf)); - expect(result).to.be.an.instanceOf(Uint8Array); - }); - }); - - describe('remote modules', () => { - const remotely = makeRemotely(makeWindow()); - - const mainModules = Object.keys(require('electron')); - remotely.it(mainModules)('includes browser process modules as properties', (mainModules: string[]) => { - const { remote } = require('electron'); - const remoteModules = mainModules.filter(name => (remote as any)[name]); - expect(remoteModules).to.be.deep.equal(mainModules); - }); - - remotely.it(fixtures)('returns toString() of original function via toString()', (fixtures: string) => { - const path = require('path'); - const { readText } = require('electron').remote.clipboard; - expect(readText.toString().startsWith('function')).to.be.true(); - - const { functionWithToStringProperty } = require('electron').remote.require(path.join(fixtures, 'to-string-non-function.js')); - expect(functionWithToStringProperty.toString).to.equal('hello'); - }); - - const protocolKeys = Object.getOwnPropertyNames(protocol); - remotely.it(protocolKeys)('remote.protocol returns all keys', (protocolKeys: [string]) => { - const protocol = require('electron').remote.protocol; - const remoteKeys = Object.getOwnPropertyNames(protocol); - expect(remoteKeys).to.deep.equal(protocolKeys); - for (const key of remoteKeys) { - expect(typeof (protocol as any)[key]).to.equal('function'); - } - }); - }); - - describe('remote object in renderer', () => { - const win = makeWindow(); - const remotely = makeRemotely(win); - - remotely.it(fixtures)('can change its properties', (fixtures: string) => { - const module = require('path').join(fixtures, 'property.js'); - const property = require('electron').remote.require(module); - expect(property.property).to.equal(1127); - property.property = null; - expect(property.property).to.equal(null); - property.property = undefined; - expect(property.property).to.equal(undefined); - property.property = 1007; - expect(property.property).to.equal(1007); - - expect(property.getFunctionProperty()).to.equal('foo-browser'); - property.func.property = 'bar'; - expect(property.getFunctionProperty()).to.equal('bar-browser'); - property.func.property = 'foo'; // revert back - - const property2 = require('electron').remote.require(module); - expect(property2.property).to.equal(1007); - - property.property = 1127; // revert back - }); - - remotely.it(fixtures)('rethrows errors getting/setting properties', (fixtures: string) => { - const foo = require('electron').remote.require(require('path').join(fixtures, 'error-properties.js')); - - expect(() => { - // eslint-disable-next-line - foo.bar - }).to.throw('getting error'); - - expect(() => { - foo.bar = 'test'; - }).to.throw('setting error'); - }); - - remotely.it(fixtures)('can set a remote property with a remote object', (fixtures: string) => { - const { remote } = require('electron'); - const foo = remote.require(require('path').join(fixtures, 'remote-object-set.js')); - foo.bar = remote.getCurrentWindow(); - }); - - remotely.it(fixtures)('can construct an object from its member', (fixtures: string) => { - const call = require('electron').remote.require(require('path').join(fixtures, 'call.js')); - const obj = new call.constructor(); - expect(obj.test).to.equal('test'); - }); - - remotely.it(fixtures)('can reassign and delete its member functions', (fixtures: string) => { - const remoteFunctions = require('electron').remote.require(require('path').join(fixtures, 'function.js')); - expect(remoteFunctions.aFunction()).to.equal(1127); - - remoteFunctions.aFunction = () => { return 1234; }; - expect(remoteFunctions.aFunction()).to.equal(1234); - - expect(delete remoteFunctions.aFunction).to.equal(true); - }); - - remotely.it('is referenced by its members', () => { - const stringify = require('electron').remote.getGlobal('JSON').stringify; - global.gc(); - stringify({}); - }); - - it('can handle objects without constructors', async () => { - win().webContents.once('remote-get-global', (event) => { - class Foo { bar () { return 'bar'; } } - Foo.prototype.constructor = undefined as any; - event.returnValue = new Foo(); - }); - expect(await remotely(() => require('electron').remote.getGlobal('test').bar())).to.equal('bar'); - }); - }); - - describe('remote value in browser', () => { - const remotely = makeRemotely(makeWindow()); - const print = path.join(fixtures, 'print_name.js'); - - remotely.it(print)('preserves NaN', (print: string) => { - const printName = require('electron').remote.require(print); - expect(printName.getNaN()).to.be.NaN(); - expect(printName.echo(NaN)).to.be.NaN(); - }); - - remotely.it(print)('preserves Infinity', (print: string) => { - const printName = require('electron').remote.require(print); - expect(printName.getInfinity()).to.equal(Infinity); - expect(printName.echo(Infinity)).to.equal(Infinity); - }); - - remotely.it(print)('keeps its constructor name for objects', (print: string) => { - const printName = require('electron').remote.require(print); - const buf = Buffer.from('test'); - expect(printName.print(buf)).to.equal('Buffer'); - }); - - remotely.it(print)('supports instanceof Boolean', (print: string) => { - const printName = require('electron').remote.require(print); - const obj = Boolean(true); - expect(printName.print(obj)).to.equal('Boolean'); - expect(printName.echo(obj)).to.deep.equal(obj); - }); - - remotely.it(print)('supports instanceof Number', (print: string) => { - const printName = require('electron').remote.require(print); - const obj = Number(42); - expect(printName.print(obj)).to.equal('Number'); - expect(printName.echo(obj)).to.deep.equal(obj); - }); - - remotely.it(print)('supports instanceof String', (print: string) => { - const printName = require('electron').remote.require(print); - const obj = String('Hello World!'); - expect(printName.print(obj)).to.equal('String'); - expect(printName.echo(obj)).to.deep.equal(obj); - }); - - remotely.it(print)('supports instanceof Date', (print: string) => { - const printName = require('electron').remote.require(print); - const now = new Date(); - expect(printName.print(now)).to.equal('Date'); - expect(printName.echo(now)).to.deep.equal(now); - }); - - remotely.it(print)('supports instanceof RegExp', (print: string) => { - const printName = require('electron').remote.require(print); - const regexp = RegExp('.*'); - expect(printName.print(regexp)).to.equal('RegExp'); - expect(printName.echo(regexp)).to.deep.equal(regexp); - }); - - remotely.it(print)('supports instanceof Buffer', (print: string) => { - const printName = require('electron').remote.require(print); - const buffer = Buffer.from('test'); - expect(buffer.equals(printName.echo(buffer))).to.be.true(); - - const objectWithBuffer = { a: 'foo', b: Buffer.from('bar') }; - expect(objectWithBuffer.b.equals(printName.echo(objectWithBuffer).b)).to.be.true(); - - const arrayWithBuffer = [1, 2, Buffer.from('baz')]; - expect((arrayWithBuffer[2] as Buffer).equals(printName.echo(arrayWithBuffer)[2])).to.be.true(); - }); - - remotely.it(print)('supports instanceof ArrayBuffer', (print: string) => { - const printName = require('electron').remote.require(print); - const buffer = new ArrayBuffer(8); - const view = new DataView(buffer); - - view.setFloat64(0, Math.PI); - expect(printName.echo(buffer)).to.deep.equal(buffer); - expect(printName.print(buffer)).to.equal('ArrayBuffer'); - }); - - const arrayTests: [string, number[]][] = [ - ['Int8Array', [1, 2, 3, 4]], - ['Uint8Array', [1, 2, 3, 4]], - ['Uint8ClampedArray', [1, 2, 3, 4]], - ['Int16Array', [0x1234, 0x2345, 0x3456, 0x4567]], - ['Uint16Array', [0x1234, 0x2345, 0x3456, 0x4567]], - ['Int32Array', [0x12345678, 0x23456789]], - ['Uint32Array', [0x12345678, 0x23456789]], - ['Float32Array', [0.5, 1.0, 1.5]], - ['Float64Array', [0.5, 1.0, 1.5]] - ]; - - arrayTests.forEach(([arrayType, values]) => { - remotely.it(print, arrayType, values)(`supports instanceof ${arrayType}`, (print: string, arrayType: string, values: number[]) => { - const printName = require('electron').remote.require(print); - expect([...printName.typedArray(arrayType, values)]).to.deep.equal(values); - - const int8values = new ((window as any)[arrayType])(values); - expect(printName.typedArray(arrayType, int8values)).to.deep.equal(int8values); - expect(printName.print(int8values)).to.equal(arrayType); - }); - }); - - describe('constructing a Uint8Array', () => { - remotely.it()('does not crash', () => { - const RUint8Array = require('electron').remote.getGlobal('Uint8Array'); - new RUint8Array() // eslint-disable-line - }); - }); - }); - - describe('remote promise', () => { - const remotely = makeRemotely(makeWindow()); - - remotely.it(fixtures)('can be used as promise in each side', async (fixtures: string) => { - const promise = require('electron').remote.require(require('path').join(fixtures, 'promise.js')); - const value = await promise.twicePromise(Promise.resolve(1234)); - expect(value).to.equal(2468); - }); - - remotely.it(fixtures)('handles rejections via catch(onRejected)', async (fixtures: string) => { - const promise = require('electron').remote.require(require('path').join(fixtures, 'rejected-promise.js')); - const error = await new Promise(resolve => { - promise.reject(Promise.resolve(1234)).catch(resolve); - }); - expect(error.message).to.equal('rejected'); - }); - - remotely.it(fixtures)('handles rejections via then(onFulfilled, onRejected)', async (fixtures: string) => { - const promise = require('electron').remote.require(require('path').join(fixtures, 'rejected-promise.js')); - const error = await new Promise(resolve => { - promise.reject(Promise.resolve(1234)).then(() => {}, resolve); - }); - expect(error.message).to.equal('rejected'); - }); - - it('does not emit unhandled rejection events in the main process', (done) => { - function onUnhandledRejection () { - done(new Error('Unexpected unhandledRejection event')); - } - process.once('unhandledRejection', onUnhandledRejection); - - remotely(async (fixtures: string) => { - const promise = require('electron').remote.require(require('path').join(fixtures, 'unhandled-rejection.js')); - return new Promise((resolve, reject) => { - promise.reject().then(() => { - reject(new Error('Promise was not rejected')); - }).catch((error: Error) => { - resolve(error); - }); - }); - }, fixtures).then(error => { - try { - expect(error.message).to.equal('rejected'); - done(); - } catch (e) { - done(e); - } finally { - process.off('unhandledRejection', onUnhandledRejection); - } - }); - }); - - it('emits unhandled rejection events in the renderer process', (done) => { - remotely((module: string) => new Promise((resolve, reject) => { - const promise = require('electron').remote.require(module); - - window.addEventListener('unhandledrejection', function handler (event) { - event.preventDefault(); - window.removeEventListener('unhandledrejection', handler); - resolve(event.reason.message); - }); - - promise.reject().then(() => { - reject(new Error('Promise was not rejected')); - }); - }), path.join(fixtures, 'unhandled-rejection.js')).then( - (message) => { - try { - expect(message).to.equal('rejected'); - done(); - } catch (e) { - done(e); - } - }, - done - ); - }); - - before(() => { - (global as any).returnAPromise = (value: any) => new Promise((resolve) => setTimeout(() => resolve(value), 100)); - }); - after(() => { - delete (global as any).returnAPromise; - }); - remotely.it()('using a promise based method resolves correctly when global Promise is overridden', async () => { - const { remote } = require('electron'); - const original = global.Promise; - try { - expect(await remote.getGlobal('returnAPromise')(123)).to.equal(123); - global.Promise = { resolve: () => ({}) } as any; - expect(await remote.getGlobal('returnAPromise')(456)).to.equal(456); - } finally { - global.Promise = original; - } - }); - }); - - describe('remote webContents', () => { - const remotely = makeRemotely(makeWindow()); - - it('can return same object with different getters', async () => { - const equal = await remotely(() => { - const { remote } = require('electron'); - const contents1 = remote.getCurrentWindow().webContents; - const contents2 = remote.getCurrentWebContents(); - return contents1 === contents2; - }); - expect(equal).to.be.true(); - }); - }); - - describe('remote class', () => { - const remotely = makeRemotely(makeWindow()); - - remotely.it(fixtures)('can get methods', (fixtures: string) => { - const { base } = require('electron').remote.require(require('path').join(fixtures, 'class.js')); - expect(base.method()).to.equal('method'); - }); - - remotely.it(fixtures)('can get properties', (fixtures: string) => { - const { base } = require('electron').remote.require(require('path').join(fixtures, 'class.js')); - expect(base.readonly).to.equal('readonly'); - }); - - remotely.it(fixtures)('can change properties', (fixtures: string) => { - const { base } = require('electron').remote.require(require('path').join(fixtures, 'class.js')); - expect(base.value).to.equal('old'); - base.value = 'new'; - expect(base.value).to.equal('new'); - base.value = 'old'; - }); - - remotely.it(fixtures)('has unenumerable methods', (fixtures: string) => { - const { base } = require('electron').remote.require(require('path').join(fixtures, 'class.js')); - expect(base).to.not.have.ownProperty('method'); - expect(Object.getPrototypeOf(base)).to.have.ownProperty('method'); - }); - - remotely.it(fixtures)('keeps prototype chain in derived class', (fixtures: string) => { - const { derived } = require('electron').remote.require(require('path').join(fixtures, 'class.js')); - expect(derived.method()).to.equal('method'); - expect(derived.readonly).to.equal('readonly'); - expect(derived).to.not.have.ownProperty('method'); - const proto = Object.getPrototypeOf(derived); - expect(proto).to.not.have.ownProperty('method'); - expect(Object.getPrototypeOf(proto)).to.have.ownProperty('method'); - }); - - remotely.it(fixtures)('is referenced by methods in prototype chain', (fixtures: string) => { - let { derived } = require('electron').remote.require(require('path').join(fixtures, 'class.js')); - const method = derived.method; - derived = null; - global.gc(); - expect(method()).to.equal('method'); - }); - }); - - describe('remote exception', () => { - const remotely = makeRemotely(makeWindow()); - - remotely.it(fixtures)('throws errors from the main process', (fixtures: string) => { - const throwFunction = require('electron').remote.require(require('path').join(fixtures, 'exception.js')); - expect(() => { - throwFunction(); - }).to.throw(/undefined/); - }); - - remotely.it(fixtures)('tracks error cause', (fixtures: string) => { - const throwFunction = require('electron').remote.require(require('path').join(fixtures, 'exception.js')); - try { - throwFunction(new Error('error from main')); - expect.fail(); - } catch (e) { - expect(e.message).to.match(/Could not call remote function/); - expect(e.cause.message).to.equal('error from main'); - } - }); - }); - - describe('gc behavior', () => { - const win = makeWindow(); - const remotely = makeRemotely(win); - it('is resilient to gc happening between request and response', async () => { - const obj = { x: 'y' }; - win().webContents.on('remote-get-global', (event) => { - event.returnValue = obj; - }); - await remotely(() => { - const { ipc } = process._linkedBinding('electron_renderer_ipc'); - const originalSendSync = ipc.sendSync.bind(ipc) as any; - ipc.sendSync = (...args: any[]): any => { - const ret = originalSendSync(...args); - (window as any).gc(); - return ret; - }; - - for (let i = 0; i < 100; i++) { - // eslint-disable-next-line - require('electron').remote.getGlobal('test').x; - } - }); - }); - }); -}); diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index 7d30afddb033..d75393206439 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -318,8 +318,14 @@ describe('command line switches', () => { let output = ''; appProcess.stdout.on('data', (data) => { output += data; }); + let stderr = ''; + appProcess.stderr.on('data', (data) => { stderr += data; }); + + const [code, signal] = await emittedOnce(appProcess, 'exit'); + if (code !== 0) { + throw new Error(`Process exited with code "${code}" signal "${signal}" output "${output}" stderr "${stderr}"`); + } - await emittedOnce(appProcess.stdout, 'end'); output = output.replace(/(\r\n|\n|\r)/gm, ''); expect(output).to.equal(result); }; @@ -332,8 +338,7 @@ describe('command line switches', () => { // The LC_ALL env should not be set to DOM locale string. expect(lcAll).to.not.equal(app.getLocale()); }); - // TODO(jeremy): figure out why this times out under ASan - ifit(process.platform === 'linux' && !process.env.IS_ASAN)('should not change LC_ALL', async () => testLocale('fr', lcAll, true)); + ifit(process.platform === 'linux')('should not change LC_ALL', async () => testLocale('fr', lcAll, true)); ifit(process.platform === 'linux')('should not change LC_ALL when setting invalid locale', async () => testLocale('asdfkl', lcAll, true)); ifit(process.platform === 'linux')('should not change LC_ALL when --lang is not set', async () => testLocale('', lcAll, true)); }); @@ -1578,12 +1583,6 @@ describe('navigator.clipboard', () => { ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.setAppBadge/clearAppBadge', () => { let w: BrowserWindow; - before(async () => { - w = new BrowserWindow({ - show: false - }); - await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html')); - }); const expectedBadgeCount = 42; @@ -1603,30 +1602,96 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se return badgeCount; } - after(() => { - app.badgeCount = 0; - closeAllWindows(); + describe('in the renderer', () => { + before(async () => { + w = new BrowserWindow({ + show: false + }); + await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html')); + }); + + after(() => { + app.badgeCount = 0; + closeAllWindows(); + }); + + it('setAppBadge can set a numerical value', async () => { + const result = await fireAppBadgeAction('set', expectedBadgeCount); + expect(result).to.equal('success'); + expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); + }); + + it('setAppBadge can set an empty(dot) value', async () => { + const result = await fireAppBadgeAction('set'); + expect(result).to.equal('success'); + expect(waitForBadgeCount(0)).to.eventually.equal(0); + }); + + it('clearAppBadge can clear a value', async () => { + let result = await fireAppBadgeAction('set', expectedBadgeCount); + expect(result).to.equal('success'); + expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); + result = await fireAppBadgeAction('clear'); + expect(result).to.equal('success'); + expect(waitForBadgeCount(0)).to.eventually.equal(0); + }); }); - it('setAppBadge can set a numerical value', async () => { - const result = await fireAppBadgeAction('set', expectedBadgeCount); - expect(result).to.equal('success'); - expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); - }); + describe('in a service worker', () => { + beforeEach(async () => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + partition: 'sw-file-scheme-spec', + contextIsolation: false + } + }); + }); - it('setAppBadge can set an empty(dot) value', async () => { - const result = await fireAppBadgeAction('set'); - expect(result).to.equal('success'); - expect(waitForBadgeCount(0)).to.eventually.equal(0); - }); + afterEach(() => { + app.badgeCount = 0; + closeAllWindows(); + }); - it('clearAppBadge can clear a value', async () => { - let result = await fireAppBadgeAction('set', expectedBadgeCount); - expect(result).to.equal('success'); - expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); - result = await fireAppBadgeAction('clear'); - expect(result).to.equal('success'); - expect(waitForBadgeCount(0)).to.eventually.equal(0); + it('setAppBadge can be called in a ServiceWorker', (done) => { + w.webContents.on('ipc-message', (event, channel, message) => { + if (channel === 'reload') { + w.webContents.reload(); + } else if (channel === 'error') { + done(message); + } else if (channel === 'response') { + expect(message).to.equal('SUCCESS setting app badge'); + expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); + session.fromPartition('sw-file-scheme-spec').clearStorageData({ + storages: ['serviceworkers'] + }).then(() => done()); + } + }); + w.webContents.on('crashed', () => done(new Error('WebContents crashed.'))); + w.loadFile(path.join(fixturesPath, 'pages', 'service-worker', 'badge-index.html'), { search: '?setBadge' }); + }); + + it('clearAppBadge can be called in a ServiceWorker', (done) => { + w.webContents.on('ipc-message', (event, channel, message) => { + if (channel === 'reload') { + w.webContents.reload(); + } else if (channel === 'setAppBadge') { + expect(message).to.equal('SUCCESS setting app badge'); + expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); + } else if (channel === 'error') { + done(message); + } else if (channel === 'response') { + expect(message).to.equal('SUCCESS clearing app badge'); + expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); + session.fromPartition('sw-file-scheme-spec').clearStorageData({ + storages: ['serviceworkers'] + }).then(() => done()); + } + }); + w.webContents.on('crashed', () => done(new Error('WebContents crashed.'))); + w.loadFile(path.join(fixturesPath, 'pages', 'service-worker', 'badge-index.html'), { search: '?clearBadge' }); + }); }); }); diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index fdd2bf8e20df..ade02bee72d0 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -6,7 +6,7 @@ import { AddressInfo } from 'net'; import * as path from 'path'; import * as fs from 'fs'; import * as WebSocket from 'ws'; -import { emittedOnce, emittedNTimes } from './events-helpers'; +import { emittedOnce, emittedNTimes, emittedUntil } from './events-helpers'; const uuid = require('uuid'); @@ -150,7 +150,9 @@ describe('chrome extensions', () => { const loadedPromise = emittedOnce(customSession, 'extension-loaded'); const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const [, loadedExtension] = await loadedPromise; - const [, readyExtension] = await emittedOnce(customSession, 'extension-ready'); + const [, readyExtension] = await emittedUntil(customSession, 'extension-ready', (event: Event, extension: Extension) => { + return extension.name !== 'Chromium PDF Viewer'; + }); expect(loadedExtension).to.deep.equal(extension); expect(readyExtension).to.deep.equal(extension); diff --git a/spec-main/fixtures/crash-cases/transparent-window-get-background-color/index.js b/spec-main/fixtures/crash-cases/transparent-window-get-background-color/index.js new file mode 100644 index 000000000000..5742ffa171e0 --- /dev/null +++ b/spec-main/fixtures/crash-cases/transparent-window-get-background-color/index.js @@ -0,0 +1,14 @@ +const { app, BrowserWindow } = require('electron'); + +function createWindow () { + // Create the browser window. + const mainWindow = new BrowserWindow({ + transparent: true + }); + mainWindow.getBackgroundColor(); +} + +app.on('ready', () => { + createWindow(); + setTimeout(() => app.quit()); +}); diff --git a/spec-main/fixtures/remote/call.js b/spec-main/fixtures/remote/call.js deleted file mode 100644 index 60315154e35e..000000000000 --- a/spec-main/fixtures/remote/call.js +++ /dev/null @@ -1,7 +0,0 @@ -exports.call = function (func) { - return func(); -}; - -exports.constructor = function () { - this.test = 'test'; -}; diff --git a/spec-main/fixtures/remote/circular.js b/spec-main/fixtures/remote/circular.js deleted file mode 100644 index e21b595bebb5..000000000000 --- a/spec-main/fixtures/remote/circular.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.returnArgs = function (...args) { - return args; -}; diff --git a/spec-main/fixtures/remote/class.js b/spec-main/fixtures/remote/class.js deleted file mode 100644 index ca6a83685da5..000000000000 --- a/spec-main/fixtures/remote/class.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -let value = 'old'; - -class BaseClass { - method () { - return 'method'; - } - - get readonly () { - return 'readonly'; - } - - get value () { - return value; - } - - set value (val) { - value = val; - } -} - -class DerivedClass extends BaseClass { -} - -module.exports = { - base: new BaseClass(), - derived: new DerivedClass() -}; diff --git a/spec-main/fixtures/remote/delete-buffer.js b/spec-main/fixtures/remote/delete-buffer.js deleted file mode 100644 index abbacb741497..000000000000 --- a/spec-main/fixtures/remote/delete-buffer.js +++ /dev/null @@ -1,11 +0,0 @@ -const path = require('path'); -const { remote } = require('electron'); -const { Buffer } = window; - -delete window.Buffer; -delete global.Buffer; - -// Test that remote.js doesn't use Buffer global -remote.require(path.join(__dirname, 'print_name.js')).echo(Buffer.from('bar')); - -window.test = Buffer.from('buffer'); diff --git a/spec-main/fixtures/remote/error-properties.js b/spec-main/fixtures/remote/error-properties.js deleted file mode 100644 index ae7a30c0dcc0..000000000000 --- a/spec-main/fixtures/remote/error-properties.js +++ /dev/null @@ -1,11 +0,0 @@ -class Foo { - set bar (value) { - throw new Error('setting error'); - } - - get bar () { - throw new Error('getting error'); - } -} - -module.exports = new Foo(); diff --git a/spec-main/fixtures/remote/exception.js b/spec-main/fixtures/remote/exception.js deleted file mode 100644 index ca47ca0cf5f2..000000000000 --- a/spec-main/fixtures/remote/exception.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function (error) { - throw error; -}; diff --git a/spec-main/fixtures/remote/export-function-with-properties.js b/spec-main/fixtures/remote/export-function-with-properties.js deleted file mode 100644 index a38142d20dfe..000000000000 --- a/spec-main/fixtures/remote/export-function-with-properties.js +++ /dev/null @@ -1,4 +0,0 @@ -function foo () {} -foo.bar = 'baz'; - -module.exports = foo; diff --git a/spec-main/fixtures/remote/function-with-args.js b/spec-main/fixtures/remote/function-with-args.js deleted file mode 100644 index b30317e975f0..000000000000 --- a/spec-main/fixtures/remote/function-with-args.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function (cb) { - return cb.length; -}; diff --git a/spec-main/fixtures/remote/function-with-missing-properties.js b/spec-main/fixtures/remote/function-with-missing-properties.js deleted file mode 100644 index 3770eca4f5a8..000000000000 --- a/spec-main/fixtures/remote/function-with-missing-properties.js +++ /dev/null @@ -1,13 +0,0 @@ -exports.setup = function () { - const foo = {}; - - foo.bar = function () { - return delete foo.bar.baz && delete foo.bar; - }; - - foo.bar.baz = function () { - return 3; - }; - - return foo; -}; diff --git a/spec-main/fixtures/remote/function-with-properties.js b/spec-main/fixtures/remote/function-with-properties.js deleted file mode 100644 index d6d82d65b0be..000000000000 --- a/spec-main/fixtures/remote/function-with-properties.js +++ /dev/null @@ -1,17 +0,0 @@ -function foo () { - return 'hello'; -} -foo.bar = 'baz'; -foo.nested = { - prop: 'yes' -}; -foo.method1 = function () { - return 'world'; -}; -foo.method1.prop1 = function () { - return 123; -}; - -module.exports = { - foo: foo -}; diff --git a/spec-main/fixtures/remote/function.js b/spec-main/fixtures/remote/function.js deleted file mode 100644 index 485d990798bc..000000000000 --- a/spec-main/fixtures/remote/function.js +++ /dev/null @@ -1 +0,0 @@ -exports.aFunction = function () { return 1127; }; diff --git a/spec-main/fixtures/remote/id.js b/spec-main/fixtures/remote/id.js deleted file mode 100644 index 5bfae457fe0b..000000000000 --- a/spec-main/fixtures/remote/id.js +++ /dev/null @@ -1 +0,0 @@ -exports.id = 1127; diff --git a/spec-main/fixtures/remote/no-prototype.js b/spec-main/fixtures/remote/no-prototype.js deleted file mode 100644 index 54eaeb847721..000000000000 --- a/spec-main/fixtures/remote/no-prototype.js +++ /dev/null @@ -1,11 +0,0 @@ -const foo = Object.create(null); -foo.bar = 'baz'; -foo.baz = false; -module.exports = { - foo: foo, - bar: 1234, - anonymous: new (class {})(), - getConstructorName: function (value) { - return value.constructor.name; - } -}; diff --git a/spec-main/fixtures/remote/preload-remote-function.js b/spec-main/fixtures/remote/preload-remote-function.js deleted file mode 100644 index f33eff6ef611..000000000000 --- a/spec-main/fixtures/remote/preload-remote-function.js +++ /dev/null @@ -1,5 +0,0 @@ -const { remote, ipcRenderer } = require('electron'); -remote.getCurrentWindow().rendererFunc = () => { - ipcRenderer.send('done'); -}; -remote.getCurrentWindow().rendererFunc(); diff --git a/spec-main/fixtures/remote/preload-remote.js b/spec-main/fixtures/remote/preload-remote.js deleted file mode 100644 index 035487be29ac..000000000000 --- a/spec-main/fixtures/remote/preload-remote.js +++ /dev/null @@ -1,5 +0,0 @@ -const { ipcRenderer, remote } = require('electron'); - -window.onload = function () { - ipcRenderer.send('remote', typeof remote); -}; diff --git a/spec-main/fixtures/remote/print_name.js b/spec-main/fixtures/remote/print_name.js deleted file mode 100644 index 047cab14d2d5..000000000000 --- a/spec-main/fixtures/remote/print_name.js +++ /dev/null @@ -1,36 +0,0 @@ -exports.print = function (obj) { - return obj.constructor.name; -}; - -exports.echo = function (obj) { - return obj; -}; - -const typedArrays = { - Int8Array, - Uint8Array, - Uint8ClampedArray, - Int16Array, - Uint16Array, - Int32Array, - Uint32Array, - Float32Array, - Float64Array -}; - -exports.typedArray = function (type, values) { - const constructor = typedArrays[type]; - const array = new constructor(values.length); - for (let i = 0; i < values.length; ++i) { - array[i] = values[i]; - } - return array; -}; - -exports.getNaN = function () { - return NaN; -}; - -exports.getInfinity = function () { - return Infinity; -}; diff --git a/spec-main/fixtures/remote/promise.js b/spec-main/fixtures/remote/promise.js deleted file mode 100644 index b9b568855e30..000000000000 --- a/spec-main/fixtures/remote/promise.js +++ /dev/null @@ -1,5 +0,0 @@ -exports.twicePromise = function (promise) { - return promise.then(function (value) { - return value * 2; - }); -}; diff --git a/spec-main/fixtures/remote/property.js b/spec-main/fixtures/remote/property.js deleted file mode 100644 index 6d15b3d3f89f..000000000000 --- a/spec-main/fixtures/remote/property.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.property = 1127; - -function func () { - -} -func.property = 'foo'; -exports.func = func; - -exports.getFunctionProperty = () => { - return `${func.property}-${process.type}`; -}; diff --git a/spec-main/fixtures/remote/rejected-promise.js b/spec-main/fixtures/remote/rejected-promise.js deleted file mode 100644 index 74c939b2d86c..000000000000 --- a/spec-main/fixtures/remote/rejected-promise.js +++ /dev/null @@ -1,5 +0,0 @@ -exports.reject = function (promise) { - return promise.then(function () { - throw Error('rejected'); - }); -}; diff --git a/spec-main/fixtures/remote/remote-event-handler.html b/spec-main/fixtures/remote/remote-event-handler.html deleted file mode 100644 index 30c3cfb36ad7..000000000000 --- a/spec-main/fixtures/remote/remote-event-handler.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/spec-main/fixtures/remote/remote-object-set.js b/spec-main/fixtures/remote/remote-object-set.js deleted file mode 100644 index c0e29ac3994a..000000000000 --- a/spec-main/fixtures/remote/remote-object-set.js +++ /dev/null @@ -1,11 +0,0 @@ -const { BrowserWindow } = require('electron'); - -class Foo { - set bar (value) { // eslint-disable-line accessor-pairs - if (!(value instanceof BrowserWindow)) { - throw new Error('setting error'); - } - } -} - -module.exports = new Foo(); diff --git a/spec-main/fixtures/remote/remote-static.js b/spec-main/fixtures/remote/remote-static.js deleted file mode 100644 index 1e005ef5dded..000000000000 --- a/spec-main/fixtures/remote/remote-static.js +++ /dev/null @@ -1,15 +0,0 @@ -class Foo { - static foo () { - return 3; - } - - baz () { - return 123; - } -} - -Foo.bar = 'baz'; - -module.exports = { - Foo: Foo -}; diff --git a/spec-main/fixtures/remote/render-view-deleted.html b/spec-main/fixtures/remote/render-view-deleted.html deleted file mode 100644 index bfc281eb4298..000000000000 --- a/spec-main/fixtures/remote/render-view-deleted.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - diff --git a/spec-main/fixtures/remote/send-on-exit.html b/spec-main/fixtures/remote/send-on-exit.html deleted file mode 100644 index 8be9b4b06a77..000000000000 --- a/spec-main/fixtures/remote/send-on-exit.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/spec-main/fixtures/remote/to-string-non-function.js b/spec-main/fixtures/remote/to-string-non-function.js deleted file mode 100644 index 1c47cfe29035..000000000000 --- a/spec-main/fixtures/remote/to-string-non-function.js +++ /dev/null @@ -1,4 +0,0 @@ -function hello () { -} -hello.toString = 'hello'; -module.exports = { functionWithToStringProperty: hello }; diff --git a/spec-main/fixtures/remote/unhandled-rejection.js b/spec-main/fixtures/remote/unhandled-rejection.js deleted file mode 100644 index bd0a7b653827..000000000000 --- a/spec-main/fixtures/remote/unhandled-rejection.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.reject = function () { - return Promise.reject(new Error('rejected')); -}; diff --git a/spec-main/index.js b/spec-main/index.js index febd107f6cd7..60c6d10602e0 100644 --- a/spec-main/index.js +++ b/spec-main/index.js @@ -131,6 +131,10 @@ app.whenReady().then(async () => { chai.use(require('chai-as-promised')); chai.use(require('dirty-chai')); + // Show full object diff + // https://github.com/chaijs/chai/issues/469 + chai.config.truncateThreshold = 0; + const runner = mocha.run(cb); }).catch((err) => { console.error('An error occurred while running the spec-main spec runner'); diff --git a/spec-main/security-warnings-spec.ts b/spec-main/security-warnings-spec.ts index b1081202052a..cf25d8530dc8 100644 --- a/spec-main/security-warnings-spec.ts +++ b/spec-main/security-warnings-spec.ts @@ -123,10 +123,7 @@ describe('security warnings', () => { it('should warn about insecure Content-Security-Policy', async () => { w = new BrowserWindow({ show: false, - webPreferences: { - enableRemoteModule: false, - ...webPreferences - } + webPreferences }); useCsp = false; @@ -138,10 +135,7 @@ describe('security warnings', () => { it('should warn about insecure Content-Security-Policy (Trusted Types)', async () => { w = new BrowserWindow({ show: false, - webPreferences: { - enableRemoteModule: false, - ...webPreferences - } + webPreferences }); useCsp = false; @@ -207,7 +201,7 @@ describe('security warnings', () => { it('should warn about insecure resources', async () => { w = new BrowserWindow({ show: false, - webPreferences: { ...webPreferences } + webPreferences }); w.loadURL(`${serverUrl}/insecure-resources.html`); @@ -225,27 +219,6 @@ describe('security warnings', () => { const [,, message] = await emittedUntil(w.webContents, 'console-message', messageContainsSecurityWarning); expect(message).to.not.include('insecure-resources.html'); }); - - it('should warn about enabled remote module with remote content', async () => { - w = new BrowserWindow({ - show: false, - webPreferences - }); - - w.loadURL(`${serverUrl}/base-page-security.html`); - const [,, message] = await emittedUntil(w.webContents, 'console-message', messageContainsSecurityWarning); - expect(message).to.include('enableRemoteModule'); - }); - - it('should not warn about enabled remote module with remote content from localhost', async () => { - w = new BrowserWindow({ - show: false, - webPreferences - }); - w.loadURL(`${serverUrl}/base-page-security-onload-message.html`); - const [,, message] = await emittedUntil(w.webContents, 'console-message', isLoaded); - expect(message).to.not.include('enableRemoteModule'); - }); }); }; diff --git a/spec-main/spellchecker-spec.ts b/spec-main/spellchecker-spec.ts index 7a46cb21a823..e4ef5ae81e12 100644 --- a/spec-main/spellchecker-spec.ts +++ b/spec-main/spellchecker-spec.ts @@ -7,10 +7,36 @@ import { emittedOnce } from './events-helpers'; import { ifit, ifdescribe, delay } from './spec-helpers'; const features = process._linkedBinding('electron_common_features'); +const v8Util = process._linkedBinding('electron_common_v8_util'); ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { let w: BrowserWindow; + async function rightClick () { + const contextMenuPromise = emittedOnce(w.webContents, 'context-menu'); + w.webContents.sendInputEvent({ + type: 'mouseDown', + button: 'right', + x: 43, + y: 42 + }); + return (await contextMenuPromise)[1] as Electron.ContextMenuParams; + } + + // When the page is just loaded, the spellchecker might not be ready yet. Since + // there is no event to know the state of spellchecker, the only reliable way + // to detect spellchecker is to keep checking with a busy loop. + async function rightClickUntil (fn: (params: Electron.ContextMenuParams) => boolean) { + const now = Date.now(); + const timeout = 10 * 1000; + let contextMenuParams = await rightClick(); + while (!fn(contextMenuParams) && (Date.now() - now < timeout)) { + await delay(100); + contextMenuParams = await rightClick(); + } + return contextMenuParams; + } + beforeEach(async () => { w = new BrowserWindow({ show: false, @@ -28,25 +54,13 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { await closeWindow(w); }); - // Context menu test can not run on Windows, and it is not reliable on ARM - // CI machines. - const shouldRun = process.platform !== 'win32' && - process.arch !== 'arm' && - process.arch !== 'arm64'; + // Context menu test can not run on Windows. + const shouldRun = process.platform !== 'win32'; ifit(shouldRun)('should detect correctly spelled words as correct', async () => { await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautiful and lovely"'); await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()'); - const contextMenuPromise = emittedOnce(w.webContents, 'context-menu'); - // Wait for spellchecker to load - await delay(500); - w.webContents.sendInputEvent({ - type: 'mouseDown', - button: 'right', - x: 43, - y: 42 - }); - const contextMenuParams: Electron.ContextMenuParams = (await contextMenuPromise)[1]; + const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.selectionText.length > 0); expect(contextMenuParams.misspelledWord).to.eq(''); expect(contextMenuParams.dictionarySuggestions).to.have.lengthOf(0); }); @@ -54,16 +68,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { ifit(shouldRun)('should detect incorrectly spelled words as incorrect', async () => { await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"'); await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()'); - const contextMenuPromise = emittedOnce(w.webContents, 'context-menu'); - // Wait for spellchecker to load - await delay(500); - w.webContents.sendInputEvent({ - type: 'mouseDown', - button: 'right', - x: 43, - y: 42 - }); - const contextMenuParams: Electron.ContextMenuParams = (await contextMenuPromise)[1]; + const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0); expect(contextMenuParams.misspelledWord).to.eq('Beautifulllll'); expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1); }); @@ -74,16 +79,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { w.webContents.session.setSpellCheckerLanguages(['en-US']); await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"'); await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()'); - const contextMenuPromise = emittedOnce(w.webContents, 'context-menu'); - // Wait for spellchecker to load - await delay(500); - w.webContents.sendInputEvent({ - type: 'mouseDown', - button: 'right', - x: 43, - y: 42 - }); - const contextMenuParams: Electron.ContextMenuParams = (await contextMenuPromise)[1]; + const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0); expect(contextMenuParams.misspelledWord).to.eq('Beautifulllll'); expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1); }); @@ -91,8 +87,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { ifit(shouldRun)('should expose webFrame spellchecker correctly', async () => { await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"'); await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()'); - // Wait for spellchecker to load - await delay(500); + await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0); const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr); @@ -110,16 +105,17 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { ifit(shouldRun)('can be dynamically changed', async () => { await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"'); await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()'); - // Wait for spellchecker to load - await delay(500); + await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0); const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr); w.webContents.session.spellCheckerEnabled = false; + v8Util.runUntilIdle(); expect(w.webContents.session.spellCheckerEnabled).to.be.false(); expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(false); w.webContents.session.spellCheckerEnabled = true; + v8Util.runUntilIdle(); expect(w.webContents.session.spellCheckerEnabled).to.be.true(); expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(true); }); diff --git a/spec-main/webview-spec.ts b/spec-main/webview-spec.ts index b9b67e975b73..46b164a9dc58 100644 --- a/spec-main/webview-spec.ts +++ b/spec-main/webview-spec.ts @@ -3,11 +3,8 @@ import * as url from 'url'; import { BrowserWindow, session, ipcMain, app, WebContents } from 'electron/main'; import { closeAllWindows } from './window-helpers'; import { emittedOnce, emittedUntil } from './events-helpers'; -import { ifdescribe } from './spec-helpers'; import { expect } from 'chai'; -const features = process._linkedBinding('electron_common_features'); - async function loadWebView (w: WebContents, attributes: Record, openDevTools: boolean = false): Promise { await w.executeJavaScript(` new Promise((resolve, reject) => { @@ -654,52 +651,6 @@ describe(' tag', function () { }); }); - ifdescribe(features.isRemoteModuleEnabled())('enableremotemodule attribute', () => { - let w: BrowserWindow; - beforeEach(async () => { - w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true } }); - await w.loadURL('about:blank'); - }); - afterEach(closeAllWindows); - - const generateSpecs = (description: string, sandbox: boolean) => { - describe(description, () => { - const preload = `file://${fixtures}/module/preload-disable-remote.js`; - const src = `file://${fixtures}/api/blank.html`; - - it('enables the remote module by default', async () => { - loadWebView(w.webContents, { - preload, - src, - sandbox: sandbox.toString() - }); - const [, webViewContents] = await emittedOnce(app, 'web-contents-created'); - const [, , message] = await emittedUntil(webViewContents, 'console-message', (event: any, level: any, message: string) => !/deprecated/.test(message)); - - const typeOfRemote = JSON.parse(message); - expect(typeOfRemote).to.equal('object'); - }); - - it('disables the remote module when false', async () => { - loadWebView(w.webContents, { - preload, - src, - sandbox: sandbox.toString(), - enableremotemodule: 'false' - }); - const [, webViewContents] = await emittedOnce(app, 'web-contents-created'); - const [, , message] = await emittedOnce(webViewContents, 'console-message'); - - const typeOfRemote = JSON.parse(message); - expect(typeOfRemote).to.equal('undefined'); - }); - }); - }; - - generateSpecs('without sandbox', false); - generateSpecs('with sandbox', true); - }); - describe('DOM events', () => { afterEach(closeAllWindows); it('receives extra properties on DOM events when contextIsolation is enabled', async () => { diff --git a/spec/fixtures/api/relaunch/main.js b/spec/fixtures/api/relaunch/main.js index 48da277383c3..272c41f4421a 100644 --- a/spec/fixtures/api/relaunch/main.js +++ b/spec/fixtures/api/relaunch/main.js @@ -14,10 +14,9 @@ app.whenReady().then(() => { client.end(String(lastArg === '--second')); }); client.once('end', () => { + if (lastArg !== '--second') { + app.relaunch({ args: process.argv.slice(1).concat('--second') }); + } app.exit(0); }); - - if (lastArg !== '--second') { - app.relaunch({ args: process.argv.slice(1).concat('--second') }); - } }); diff --git a/spec/fixtures/module/preload-disable-remote.js b/spec/fixtures/module/preload-disable-remote.js deleted file mode 100644 index 9b6b96cbf28f..000000000000 --- a/spec/fixtures/module/preload-disable-remote.js +++ /dev/null @@ -1,8 +0,0 @@ -setImmediate(function () { - try { - const { remote } = require('electron'); - console.log(JSON.stringify(typeof remote)); - } catch (e) { - console.log(e.message); - } -}); diff --git a/spec/fixtures/pages/service-worker/badge-index.html b/spec/fixtures/pages/service-worker/badge-index.html new file mode 100644 index 000000000000..480a60a9aee8 --- /dev/null +++ b/spec/fixtures/pages/service-worker/badge-index.html @@ -0,0 +1,31 @@ + diff --git a/spec/fixtures/pages/service-worker/service-worker-badge.js b/spec/fixtures/pages/service-worker/service-worker-badge.js new file mode 100644 index 000000000000..84215bfb3323 --- /dev/null +++ b/spec/fixtures/pages/service-worker/service-worker-badge.js @@ -0,0 +1,33 @@ +self.addEventListener('fetch', async function (event) { + const requestUrl = new URL(event.request.url); + let responseTxt; + if (requestUrl.pathname === '/echo' && + event.request.headers.has('X-Mock-Response')) { + if (requestUrl.search === '?setBadge') { + if (navigator.setAppBadge()) { + try { + await navigator.setAppBadge(42); + responseTxt = 'SUCCESS setting app badge'; + await navigator.clearAppBadge(); + } catch (ex) { + responseTxt = 'ERROR setting app badge ' + ex; + } + } else { + responseTxt = 'ERROR navigator.setAppBadge is not available in ServiceWorker!'; + } + } else if (requestUrl.search === '?clearBadge') { + if (navigator.clearAppBadge()) { + try { + await navigator.clearAppBadge(); + responseTxt = 'SUCCESS clearing app badge'; + } catch (ex) { + responseTxt = 'ERROR clearing app badge ' + ex; + } + } else { + responseTxt = 'ERROR navigator.clearAppBadge is not available in ServiceWorker!'; + } + } + const mockResponse = new Response(responseTxt); + event.respondWith(mockResponse); + } +}); diff --git a/spec/static/index.html b/spec/static/index.html index 9497bbcf8b20..1f710fc2b277 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -91,6 +91,10 @@ chai.use(require('chai-as-promised')) chai.use(require('dirty-chai')) + // Show full object diff + // https://github.com/chaijs/chai/issues/469 + chai.config.truncateThreshold = 0; + const runner = mocha.run(() => { // Ensure the callback is called after runner is defined setTimeout(() => { diff --git a/spec/static/main.js b/spec/static/main.js index c3ee64266038..f259a3612fd3 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -108,7 +108,6 @@ app.whenReady().then(async function () { webPreferences: { backgroundThrottling: false, nodeIntegration: true, - enableRemoteModule: false, webviewTag: true, contextIsolation: false } diff --git a/spec/ts-smoke/electron/renderer.ts b/spec/ts-smoke/electron/renderer.ts index 1ad24a9cc283..16bc8d2c51b0 100644 --- a/spec/ts-smoke/electron/renderer.ts +++ b/spec/ts-smoke/electron/renderer.ts @@ -2,11 +2,9 @@ import { desktopCapturer, ipcRenderer, - remote, webFrame, clipboard, crashReporter, - screen, shell } from 'electron' @@ -23,31 +21,6 @@ ipcRenderer.on('asynchronous-reply', (event, arg: any) => { ipcRenderer.send('asynchronous-message', 'ping') -// remote -// https://github.com/electron/electron/blob/master/docs/api/remote.md - -const BrowserWindow = remote.BrowserWindow -const win = new BrowserWindow({ width: 800, height: 600 }) -win.loadURL('https://github.com') - -remote.getCurrentWindow().on('close', () => { - // blabla... -}) - -remote.getCurrentWindow().capturePage().then(image => { - fs.writeFile('/tmp/screenshot.png', image.toBitmap(), err => { - console.log(err) - }) -}) - -remote.getCurrentWebContents().print() - -remote.getCurrentWindow().capturePage().then(image => { - remote.require('fs').writeFile('/tmp/screenshot.png', image.toBitmap(), (err: Error) => { - console.log(err) - }) -}) - // web-frame // https://github.com/electron/electron/blob/master/docs/api/web-frame.md @@ -166,12 +139,7 @@ holder.ondrop = function (e) { // nativeImage // https://github.com/electron/electron/blob/master/docs/api/native-image.md -const Tray = remote.Tray -const appIcon2 = new Tray('/Users/somebody/images/icon.png') -const window2 = new BrowserWindow({ icon: '/Users/somebody/images/window.png' }) const image = clipboard.readImage() -const appIcon3 = new Tray(image) -const appIcon4 = new Tray('/Users/somebody/images/icon.png') // https://github.com/electron/electron/blob/master/docs/api/process.md @@ -183,36 +151,6 @@ process.once('loaded', function () { global.clearImmediate = _clearImmediate }) -// screen -// https://github.com/electron/electron/blob/master/docs/api/screen.md - -const app = remote.app - -let mainWindow: Electron.BrowserWindow = null - -app.whenReady().then(() => { - const size = screen.getPrimaryDisplay().workAreaSize - mainWindow = new BrowserWindow({ width: size.width, height: size.height }) -}) - -app.whenReady().then(() => { - const displays = screen.getAllDisplays() - let externalDisplay: any = null - for (const i in displays) { - if (displays[i].bounds.x > 0 || displays[i].bounds.y > 0) { - externalDisplay = displays[i] - break - } - } - - if (externalDisplay) { - mainWindow = new BrowserWindow({ - x: externalDisplay.bounds.x + 50, - y: externalDisplay.bounds.y + 50 - }) - } -}) - // shell // https://github.com/electron/electron/blob/master/docs/api/shell.md diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 9a4a601a2efa..8fb6a1722ba9 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -482,17 +482,6 @@ describe(' tag', function () { }); }); - ifit(features.isRemoteModuleEnabled())('can disable the remote module', async () => { - const message = await startLoadingWebViewAndWaitForMessage(webview, { - preload: `${fixtures}/module/preload-disable-remote.js`, - src: `file://${fixtures}/api/blank.html`, - webpreferences: 'enableRemoteModule=no' - }); - - const typeOfRemote = JSON.parse(message); - expect(typeOfRemote).to.equal('undefined'); - }); - it('can disables web security and enable nodeintegration', async () => { const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js'); const src = ` `; diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 85f9d1253c8c..c97d0864f165 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -7,7 +7,6 @@ declare var binding: { get: (name: string) => any; process: NodeJS.Process; crea declare const BUILDFLAG: (flag: boolean) => boolean; declare const ENABLE_DESKTOP_CAPTURER: boolean; -declare const ENABLE_REMOTE_MODULE: boolean; declare const ENABLE_VIEWS_API: boolean; declare namespace NodeJS { @@ -15,7 +14,6 @@ declare namespace NodeJS { isBuiltinSpellCheckerEnabled(): boolean; isDesktopCapturerEnabled(): boolean; isOffscreenRenderingEnabled(): boolean; - isRemoteModuleEnabled(): boolean; isPDFViewerEnabled(): boolean; isRunAsNodeEnabled(): boolean; isFakeLocationProviderEnabled(): boolean; @@ -45,7 +43,7 @@ declare namespace NodeJS { weaklyTrackValue(value: any): void; clearWeaklyTrackedValues(): void; getWeaklyTrackedValues(): any[]; - addRemoteObjectRef(contextId: string, id: number): void; + runUntilIdle(): void; isSameOrigin(a: string, b: string): boolean; triggerFatalErrorForTesting(): void; } @@ -78,8 +76,7 @@ declare namespace NodeJS { readdir(path: string): string[] | false; realpath(path: string): string | false; copyFileOut(path: string): string | false; - read(offset: number, size: number): Promise; - readSync(offset: number, size: number): ArrayBuffer; + getFd(): number | -1; } interface AsarBinding {