diff --git a/.circleci/config.yml b/.circleci/config.yml index 2e6d122b24..8be8bc3bd0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -230,6 +230,18 @@ step-maybe-notify-slack-success: &step-maybe-notify-slack-success fi when: on_success +step-maybe-cleanup-arm64-mac: &step-maybe-cleanup-arm64-mac + run: + name: Cleanup after testing + command: | + if [ "$TARGET_ARCH" == "arm64" ] &&[ "`uname`" == "Darwin" ]; then + killall Electron || echo "No Electron processes left running" + killall Safari || echo "No Safari processes left running" + rm -rf ~/Library/Application\ Support/Electron* + rm -rf ~/Library/Application\ Support/electron* + fi + when: always + step-checkout-electron: &step-checkout-electron checkout: path: src/electron @@ -1340,6 +1352,8 @@ steps-tests: &steps-tests - *step-maybe-notify-slack-failure + - *step-maybe-cleanup-arm64-mac + steps-test-nan: &steps-test-nan steps: - attach_workspace: diff --git a/ELECTRON_VERSION b/ELECTRON_VERSION index 85caea238e..450026c06d 100644 --- a/ELECTRON_VERSION +++ b/ELECTRON_VERSION @@ -1 +1 @@ -14.0.0-nightly.20210315 \ No newline at end of file +14.0.0-nightly.20210323 \ No newline at end of file diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index edac2f4063..8bb4b4bd75 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -267,7 +267,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. be the absolute file path to the script. When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope. See example - [here](process.md#event-loaded). + [here](context-bridge.md#exposing-node-global-symbols). * `sandbox` Boolean (optional) - If set, this will sandbox the renderer associated with the window, making it compatible with the Chromium OS-level sandbox and disabling the Node.js engine. This is not the same as @@ -1316,6 +1316,8 @@ The native type of the handle is `HWND` on Windows, `NSView*` on macOS, and * `message` Integer * `callback` Function + * `wParam` any - The `wParam` provided to the WndProc + * `lParam` any - The `lParam` provided to the WndProc Hooks a windows message. The `callback` is called when the message is received in the WndProc. diff --git a/docs/api/context-bridge.md b/docs/api/context-bridge.md index ab157f79f6..04eb887bb7 100644 --- a/docs/api/context-bridge.md +++ b/docs/api/context-bridge.md @@ -33,7 +33,7 @@ page you load in your renderer executes code in this world. ### Isolated World -When `contextIsolation` is enabled in your `webPreferences`, your `preload` scripts run in an +When `contextIsolation` is enabled in your `webPreferences` (this is the default behavior since Electron 12.0.0), your `preload` scripts run in an "Isolated World". You can read more about context isolation and what it affects in the [security](../tutorial/security.md#3-enable-context-isolation-for-remote-content) docs. @@ -110,3 +110,22 @@ has been included below for completeness: | `Symbol` | N/A | ❌ | ❌ | Symbols cannot be copied across contexts so they are dropped | If the type you care about is not in the above table, it is probably not supported. + +### Exposing Node Global Symbols + +The `contextBridge` can be used by the preload script to give your renderer access to Node APIs. +The table of supported types described above also applies to Node APIs that you expose through `contextBridge`. +Please note that many Node APIs grant access to local system resources. +Be very cautious about which globals and APIs you expose to untrusted remote content. + +```javascript +const { contextBridge } = require('electron') +const crypto = require('crypto') +contextBridge.exposeInMainWorld('nodeCrypto', { + sha256sum (data) { + const hash = crypto.createHash('sha256') + hash.update(data) + return hash.digest('hex') + } +}) +``` diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index c03f8a7e03..8331bc8d43 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -77,7 +77,8 @@ The `crashReporter` module has the following methods: ### `crashReporter.start(options)` * `options` Object - * `submitURL` String - URL that crash reports will be sent to as POST. + * `submitURL` String (optional) - URL that crash reports will be sent to as + POST. Required unless `uploadToServer` is `false`. * `productName` String (optional) - Defaults to `app.name`. * `companyName` String (optional) _Deprecated_ - Deprecated alias for `{ globalExtra: { _companyName: ... } }`. diff --git a/docs/api/process.md b/docs/api/process.md index ee00672129..3f0d2ed867 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -30,11 +30,13 @@ In sandboxed renderers the `process` object contains only a subset of the APIs: * `arch` * `platform` * `sandboxed` +* `contextIsolated` * `type` * `version` * `versions` * `mas` * `windowsStore` +* `contextId` ## Events @@ -43,19 +45,6 @@ In sandboxed renderers the `process` object contains only a subset of the APIs: Emitted when Electron has loaded its internal initialization script and is beginning to load the web page or the main script. -It can be used by the preload script to add removed Node global symbols back to -the global scope when node integration is turned off: - -```javascript -// preload.js -const _setImmediate = setImmediate -const _clearImmediate = clearImmediate -process.once('loaded', () => { - global.setImmediate = _setImmediate - global.clearImmediate = _clearImmediate -}) -``` - ## Properties ### `process.defaultApp` _Readonly_ @@ -93,6 +82,11 @@ A `String` representing the path to the resources directory. A `Boolean`. When the renderer process is sandboxed, this property is `true`, otherwise it is `undefined`. +### `process.contextIsolated` _Readonly_ + +A `Boolean` that indicates whether the current renderer context has `contextIsolation` enabled. +It is `undefined` in the main process. + ### `process.throwDeprecation` A `Boolean` that controls whether or not deprecation warnings will be thrown as @@ -133,6 +127,13 @@ A `String` representing Electron's version string. A `Boolean`. If the app is running as a Windows Store app (appx), this property is `true`, for otherwise it is `undefined`. +### `process.contextId` _Readonly_ + +A `String` (optional) representing a globally unique ID of the current JavaScript context. +Each frame has its own JavaScript context. When contextIsolation is enabled, the isolated +world also has a separate JavaScript context. +This property is only available in the renderer process. + ## Methods The `process` object has the following methods: diff --git a/docs/development/issues.md b/docs/development/issues.md index 7478433cdd..ec0ad5dc04 100644 --- a/docs/development/issues.md +++ b/docs/development/issues.md @@ -33,8 +33,7 @@ contributing, and more. Please use the issue tracker for bugs only! To submit a bug report: When opening a new issue in the [`electron/electron` issue tracker](https://github.com/electron/electron/issues/new/choose), users -will be presented with [a template](https://github.com/electron/electron/blob/master/.github/ISSUE_TEMPLATE/Bug_report.md) -that should be filled in. +will be presented with a template that should be filled in. If you believe that you have found a bug in Electron, please fill out the template to the best of your ability. diff --git a/docs/fiddles/quick-start/index.html b/docs/fiddles/quick-start/index.html index a3855d2640..f008d867a0 100644 --- a/docs/fiddles/quick-start/index.html +++ b/docs/fiddles/quick-start/index.html @@ -8,9 +8,9 @@

Hello World!

- We are using node , - Chrome , - and Electron . + We are using Node.js , + Chromium , + and Electron .

diff --git a/docs/fiddles/quick-start/main.js b/docs/fiddles/quick-start/main.js index bfc856e606..519a67947c 100644 --- a/docs/fiddles/quick-start/main.js +++ b/docs/fiddles/quick-start/main.js @@ -1,19 +1,27 @@ const { app, BrowserWindow } = require('electron') +const path = require('path') function createWindow () { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { - nodeIntegration: true, - contextIsolation: false + preload: path.join(__dirname, 'preload.js') } }) win.loadFile('index.html') } -app.whenReady().then(createWindow) +app.whenReady().then(() => { + createWindow() + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } + }) +}) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { @@ -21,8 +29,3 @@ app.on('window-all-closed', () => { } }) -app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } -}) diff --git a/docs/fiddles/quick-start/preload.js b/docs/fiddles/quick-start/preload.js new file mode 100644 index 0000000000..7674d01224 --- /dev/null +++ b/docs/fiddles/quick-start/preload.js @@ -0,0 +1,11 @@ +window.addEventListener('DOMContentLoaded', () => { + const replaceText = (selector, text) => { + const element = document.getElementById(selector) + if (element) element.innerText = text + } + + for (const type of ['chrome', 'node', 'electron']) { + replaceText(`${type}-version`, process.versions[type]) + } +}) + diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 1358e523f9..4c7c078806 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -32,6 +32,7 @@ From a development perspective, an Electron application is essentially a Node.js my-electron-app/ ├── package.json ├── main.js +├── preload.js └── index.html ``` @@ -55,45 +56,49 @@ The main script may look as follows: ```javascript fiddle='docs/fiddles/quick-start' const { app, BrowserWindow } = require('electron') +const path = require('path') function createWindow () { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { - nodeIntegration: true + preload: path.join(__dirname, 'preload.js') } }) win.loadFile('index.html') } -app.whenReady().then(createWindow) +app.whenReady().then(() => { + createWindow() + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } + }) +}) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) - -app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } -}) ``` ##### What is going on above? 1. Line 1: First, you import the `app` and `BrowserWindow` modules of the `electron` package to be able to manage your application's lifecycle events, as well as create and control browser windows. -2. Line 3: After that, you define a function that creates a [new browser window](../api/browser-window.md#new-browserwindowoptions) with node integration enabled, loads `index.html` file into this window (line 12, we will discuss the file later). -3. Line 15: You create a new browser window by invoking the `createWindow` function once the Electron application [is initialized](../api/app.md#appwhenready). -4. Line 17: You add a new listener that tries to quit the application when it no longer has any open windows. This listener is a no-op on macOS due to the operating system's [window management behavior](https://support.apple.com/en-ca/guide/mac-help/mchlp2469/mac). -5. Line 23: You add a new listener that creates a new browser window only if when the application has no visible windows after being activated. For example, after launching the application for the first time, or re-launching the already running application. +2. Line 2: Second, you import the `path` package which provides utility functions for file paths. +3. Line 4: After that, you define a function that creates a [new browser window](../api/browser-window.md#new-browserwindowoptions) with a preload script, loads `index.html` file into this window (line 13, we will discuss the file later). +4. Line 16: You create a new browser window by invoking the `createWindow` function once the Electron application [is initialized](../api/app.md#appwhenready). +5. Line 18: You add a new listener that creates a new browser window only if when the application has no visible windows after being activated. For example, after launching the application for the first time, or re-launching the already running application. +6. Line 25: You add a new listener that tries to quit the application when it no longer has any open windows. This listener is a no-op on macOS due to the operating system's [window management behavior](https://support.apple.com/en-ca/guide/mac-help/mchlp2469/mac). #### Create a web page -This is the web page you want to display once the application is initialized. This web page represents the Renderer process. You can create multiple browser windows, where each window uses its own independent Renderer. Each window can optionally be granted with full access to Node.js API through the `nodeIntegration` preference. +This is the web page you want to display once the application is initialized. This web page represents the Renderer process. You can create multiple browser windows, where each window uses its own independent Renderer. You can optionally grant access to additional Node.js APIs by exposing them from your preload script. The `index.html` page looks as follows: @@ -108,14 +113,38 @@ The `index.html` page looks as follows:

Hello World!

- We are using node , - Chrome , - and Electron . + We are using Node.js , + Chromium , + and Electron .

``` +#### Define a preload script + +Your preload script acts as a bridge between Node.js and your web page. It allows you to expose specific APIs and behaviors to your web page rather than insecurely exposing the entire Node.js API. In this example we will use the preload script to read version information from the `process` object and update the web page with that info. + +```javascript fiddle='docs/fiddles/quick-start' +window.addEventListener('DOMContentLoaded', () => { + const replaceText = (selector, text) => { + const element = document.getElementById(selector) + if (element) element.innerText = text + } + + for (const type of ['chrome', 'node', 'electron']) { + replaceText(`${type}-version`, process.versions[type]) + } +}) +``` + +##### What's going on above? + +1. On line 1: First you define an event listener that tells you when the web page has loaded +2. On line 2: Second you define a utility function used to set the text of the placeholders in the `index.html` +3. On line 7: Next you loop through the list of components whose version you want to display +4. On line 8: Finally, you call `replaceText` to look up the version placeholders in `index.html` and set their text value to the values from `process.versions` + #### Modify your package.json file Your Electron application uses the `package.json` file as the main entry point (as any other Node.js application). The main script of your application is `main.js`, so modify the `package.json` file accordingly: @@ -283,7 +312,7 @@ ipcRenderer.invoke('perform-action', ...args) ##### Node.js API -> NOTE: To access the Node.js API from the Renderer process, you need to set the `nodeIntegration` preference to `true`. +> NOTE: To access the Node.js API from the Renderer process, you need to set the `nodeIntegration` preference to `true` and the `contextIsolation` preference to `false`. Please note that access to the Node.js API in any renderer that loads remote content is not recommended for [security reasons](../tutorial/security.md#2-do-not-enable-nodejs-integration-for-remote-content). Electron exposes full access to Node.js API and its modules both in the Main and the Renderer processes. For example, you can read all the files from the root directory: diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 61ff0e1cb4..197c29a5ff 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -86,12 +86,12 @@ const driver = new webdriver.Builder() // The "9515" is the port opened by chrome driver. .usingServer('http://localhost:9515') .withCapabilities({ - chromeOptions: { + 'goog:chromeOptions': { // Here is the path to your Electron binary. binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' } }) - .forBrowser('electron') + .forBrowser('chrome') // note: use .forBrowser('electron') for selenium-webdriver <= 3.6.0 .build() driver.get('http://www.google.com') diff --git a/lib/browser/api/crash-reporter.ts b/lib/browser/api/crash-reporter.ts index 99672c690d..16a9b1b5cb 100644 --- a/lib/browser/api/crash-reporter.ts +++ b/lib/browser/api/crash-reporter.ts @@ -10,13 +10,13 @@ class CrashReporter { extra = {}, globalExtra = {}, ignoreSystemCrashHandler = false, - submitURL, + submitURL = '', uploadToServer = true, rateLimit = false, compress = true } = options || {}; - if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start'); + if (uploadToServer && !submitURL) throw new Error('submitURL must be specified when uploadToServer is true'); 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/web-contents.ts b/lib/browser/api/web-contents.ts index 01fd54aa3b..e9aab2cf82 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -94,6 +94,7 @@ const defaultPrintingSetting = { pagesPerSheet: 1, isFirstRequest: false, previewUIID: 0, + // True, if the document source is modifiable. e.g. HTML and not PDF. previewModifiable: true, printToPDF: true, deviceName: 'Save as PDF', diff --git a/lib/renderer/init.ts b/lib/renderer/init.ts index c41fa56188..3bb3c2d6ef 100644 --- a/lib/renderer/init.ts +++ b/lib/renderer/init.ts @@ -39,6 +39,10 @@ require('@electron/internal/common/init'); // The global variable will be used by ipc for event dispatching const v8Util = process._linkedBinding('electron_common_v8_util'); +// Expose process.contextId +const contextId = v8Util.getHiddenValue(global, 'contextId'); +Object.defineProperty(process, 'contextId', { enumerable: true, value: contextId }); + const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); const ipcRenderer = require('@electron/internal/renderer/api/ipc-renderer').default; diff --git a/lib/sandboxed_renderer/init.ts b/lib/sandboxed_renderer/init.ts index a23082ef04..f82f0cbe00 100644 --- a/lib/sandboxed_renderer/init.ts +++ b/lib/sandboxed_renderer/init.ts @@ -89,6 +89,10 @@ Object.defineProperty(preloadProcess, 'noDeprecation', { } }); +// Expose process.contextId +const contextId = v8Util.getHiddenValue(global, 'contextId'); +Object.defineProperty(preloadProcess, 'contextId', { enumerable: true, value: contextId }); + process.on('loaded', () => (preloadProcess as events.EventEmitter).emit('loaded')); process.on('exit', () => (preloadProcess as events.EventEmitter).emit('exit')); (process as events.EventEmitter).on('document-start', () => (preloadProcess as events.EventEmitter).emit('document-start')); diff --git a/package.json b/package.json index dd820127f8..b56a254ad0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "14.0.0-nightly.20210315", + "version": "14.0.0-nightly.20210323", "repository": "https://github.com/electron/electron", "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", "devDependencies": { diff --git a/shell/browser/api/content_tracing.idl b/shell/browser/api/content_tracing.idl new file mode 100644 index 0000000000..74199b9f9c --- /dev/null +++ b/shell/browser/api/content_tracing.idl @@ -0,0 +1,26 @@ +enum RecordingMode { "record-until-full", "record-continuously", "record-as-much-as-possible", "trace-to-console" }; + +dictionary TraceConfig { + Recordingmode recording_mode; + unsigned long trace_buffer_size_in_kb; + unsigned long trace_buffer_size_in_events; + boolean enable_argument_filter; + sequence included_categories; + sequence excluded_categories; + sequence included_process_ids; + sequence histogram_names; + object memory_dump_config; +}; + +dictionary TraceCategoriesAndOptions { + DOMString categoryFilter; + DOMString traceOptions; +}; + +interface ContentTracing { + Promise> getCategories(); + Promise startRecording(TraceConfig config); + Promise startRecording(TraceCategoriesAndOptions categoriesAndOptions); + Promise stopRecording(optional DOMString resultFilePath); + Promise getTraceBufferUsage(); +}; diff --git a/shell/browser/api/electron_api_base_window.cc b/shell/browser/api/electron_api_base_window.cc index edd62803e0..19f624d75a 100644 --- a/shell/browser/api/electron_api_base_window.cc +++ b/shell/browser/api/electron_api_base_window.cc @@ -1114,6 +1114,8 @@ int32_t BaseWindow::GetID() const { } void BaseWindow::ResetBrowserViews() { + v8::HandleScope scope(isolate()); + for (auto& item : browser_views_) { gin::Handle browser_view; if (gin::ConvertFromV8(isolate(), diff --git a/shell/browser/api/electron_api_desktop_capturer.cc b/shell/browser/api/electron_api_desktop_capturer.cc index 4f2331c5af..d0ac57d4c5 100644 --- a/shell/browser/api/electron_api_desktop_capturer.cc +++ b/shell/browser/api/electron_api_desktop_capturer.cc @@ -162,6 +162,9 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) { v8::Locker locker(isolate); v8::HandleScope scope(isolate); gin_helper::CallMethod(this, "_onerror", "Failed to get sources."); + + Unpin(); + return; } @@ -195,12 +198,19 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) { v8::Locker locker(isolate); v8::HandleScope scope(isolate); gin_helper::CallMethod(this, "_onfinished", captured_sources_); + + Unpin(); } } // static gin::Handle DesktopCapturer::Create(v8::Isolate* isolate) { - return gin::CreateHandle(isolate, new DesktopCapturer(isolate)); + auto handle = gin::CreateHandle(isolate, new DesktopCapturer(isolate)); + + // Keep reference alive until capturing has finished. + handle->Pin(isolate); + + return handle; } gin::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( diff --git a/shell/browser/api/electron_api_desktop_capturer.h b/shell/browser/api/electron_api_desktop_capturer.h index 3ef7c0bd1f..9664836a0f 100644 --- a/shell/browser/api/electron_api_desktop_capturer.h +++ b/shell/browser/api/electron_api_desktop_capturer.h @@ -13,12 +13,14 @@ #include "chrome/browser/media/webrtc/native_desktop_media_list.h" #include "gin/handle.h" #include "gin/wrappable.h" +#include "shell/common/gin_helper/pinnable.h" namespace electron { namespace api { class DesktopCapturer : public gin::Wrappable, + public gin_helper::Pinnable, public DesktopMediaListObserver { public: struct Source { diff --git a/shell/browser/api/electron_api_menu_mac.mm b/shell/browser/api/electron_api_menu_mac.mm index bab36f6a65..49d7934187 100644 --- a/shell/browser/api/electron_api_menu_mac.mm +++ b/shell/browser/api/electron_api_menu_mac.mm @@ -88,7 +88,7 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, } // If no preferred item is specified, try to show all of the menu items. - if (!positioning_item) { + if (!item) { CGFloat windowBottom = CGRectGetMinY([view window].frame); CGFloat lowestMenuPoint = windowBottom + position.y - [menu size].height; CGFloat screenBottom = CGRectGetMinY([view window].screen.frame); diff --git a/shell/browser/api/electron_api_protocol.cc b/shell/browser/api/electron_api_protocol.cc index 8e4329b523..952e32d0d3 100644 --- a/shell/browser/api/electron_api_protocol.cc +++ b/shell/browser/api/electron_api_protocol.cc @@ -10,6 +10,7 @@ #include "base/command_line.h" #include "base/stl_util.h" +#include "content/common/url_schemes.h" #include "content/public/browser/child_process_security_policy.h" #include "gin/object_template_builder.h" #include "shell/browser/browser.h" @@ -124,6 +125,13 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower, } if (custom_scheme.options.allowServiceWorkers) { service_worker_schemes.push_back(custom_scheme.scheme); + // There is no API to add service worker scheme, but there is an API to + // return const reference to the schemes vector. + // If in future the API is changed to return a copy instead of reference, + // the compilation will fail, and we should add a patch at that time. + auto& mutable_schemes = const_cast&>( + content::GetServiceWorkerSchemes()); + mutable_schemes.push_back(custom_scheme.scheme); } if (custom_scheme.options.stream) { g_streaming_schemes.push_back(custom_scheme.scheme); diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 859779fe13..ee3de4b51a 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -915,6 +915,7 @@ WebContents::~WebContents() { return; } + inspectable_web_contents_->GetView()->SetDelegate(nullptr); if (guest_delegate_) guest_delegate_->WillDestroy(); @@ -1761,6 +1762,7 @@ void WebContents::DevToolsOpened() { v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); DCHECK(inspectable_web_contents_); + DCHECK(inspectable_web_contents_->GetDevToolsWebContents()); auto handle = FromOrCreate( isolate, inspectable_web_contents_->GetDevToolsWebContents()); devtools_web_contents_.Reset(isolate, handle.ToV8()); diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index 95afaf080f..fca40a30cc 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -1363,22 +1363,26 @@ void ElectronBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories( int render_process_id, int render_frame_id, NonNetworkURLLoaderFactoryMap* factories) { - content::RenderFrameHost* frame_host = - content::RenderFrameHost::FromID(render_process_id, render_frame_id); - content::WebContents* web_contents = - content::WebContents::FromRenderFrameHost(frame_host); + auto* render_process_host = + content::RenderProcessHost::FromID(render_process_id); + DCHECK(render_process_host); + if (!render_process_host || !render_process_host->GetBrowserContext()) + return; + + ProtocolRegistry::FromBrowserContext(render_process_host->GetBrowserContext()) + ->RegisterURLLoaderFactories(URLLoaderFactoryType::kDocumentSubResource, + factories); - if (web_contents) { - ProtocolRegistry::FromBrowserContext(web_contents->GetBrowserContext()) - ->RegisterURLLoaderFactories(URLLoaderFactoryType::kDocumentSubResource, - factories); - } #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) auto factory = extensions::CreateExtensionURLLoaderFactory(render_process_id, render_frame_id); if (factory) factories->emplace(extensions::kExtensionScheme, std::move(factory)); + content::RenderFrameHost* frame_host = + content::RenderFrameHost::FromID(render_process_id, render_frame_id); + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(frame_host); if (!web_contents) return; diff --git a/shell/browser/native_browser_view_mac.mm b/shell/browser/native_browser_view_mac.mm index 7478220587..ce0e3ff889 100644 --- a/shell/browser/native_browser_view_mac.mm +++ b/shell/browser/native_browser_view_mac.mm @@ -317,8 +317,9 @@ void NativeBrowserViewMac::UpdateDraggableRegions( const auto window_content_view_height = NSHeight(window_content_view.bounds); for (const auto& rect : drag_exclude_rects) { const auto x = rect.x() + offset.x(); - const auto y = window_content_view_height - rect.bottom() + offset.y(); + const auto y = window_content_view_height - (rect.bottom() + offset.y()); const auto exclude_rect = NSMakeRect(x, y, rect.width(), rect.height()); + const auto drag_region_view_exclude_rect = [window_content_view convertRect:exclude_rect toView:drag_region_view]; diff --git a/shell/browser/native_window.h b/shell/browser/native_window.h index e48f4d2f3d..0da492ef9b 100644 --- a/shell/browser/native_window.h +++ b/shell/browser/native_window.h @@ -206,6 +206,7 @@ class NativeWindow : public base::SupportsUserData, virtual void SetTrafficLightPosition(base::Optional position) = 0; virtual base::Optional GetTrafficLightPosition() const = 0; virtual void RedrawTrafficLights() = 0; + virtual void UpdateFrame() = 0; #endif // Touchbar API diff --git a/shell/browser/native_window_mac.h b/shell/browser/native_window_mac.h index 45a7342dcc..88f65ea420 100644 --- a/shell/browser/native_window_mac.h +++ b/shell/browser/native_window_mac.h @@ -14,6 +14,7 @@ #include "base/mac/scoped_nsobject.h" #include "shell/browser/native_window.h" +#include "ui/display/display_observer.h" #include "ui/native_theme/native_theme_observer.h" #include "ui/views/controls/native/native_view_host.h" @@ -27,7 +28,9 @@ namespace electron { class RootViewMac; -class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver { +class NativeWindowMac : public NativeWindow, + public ui::NativeThemeObserver, + public display::DisplayObserver { public: NativeWindowMac(const gin_helper::Dictionary& options, NativeWindow* parent); ~NativeWindowMac() override; @@ -124,6 +127,7 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver { void SetTrafficLightPosition(base::Optional position) override; base::Optional GetTrafficLightPosition() const override; void RedrawTrafficLights() override; + void UpdateFrame() override; void SetTouchBar( std::vector items) override; void RefreshTouchBarItem(const std::string& item_id) override; @@ -188,6 +192,10 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver { // ui::NativeThemeObserver: void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; + // display::DisplayObserver: + void OnDisplayMetricsChanged(const display::Display& display, + uint32_t changed_metrics) override; + private: // Add custom layers to the content view. void AddContentViewLayers(); diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index 21fff3e724..618119a6eb 100644 --- a/shell/browser/native_window_mac.mm +++ b/shell/browser/native_window_mac.mm @@ -39,6 +39,7 @@ #include "shell/common/process_util.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/webrtc/modules/desktop_capture/mac/window_list_utils.h" +#include "ui/display/screen.h" #include "ui/gfx/skia_util.h" #include "ui/gl/gpu_switching_manager.h" #include "ui/views/background.h" @@ -258,6 +259,7 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options, NativeWindow* parent) : NativeWindow(options, parent), root_view_(new RootViewMac(this)) { ui::NativeTheme::GetInstanceForNativeUi()->AddObserver(this); + display::Screen::GetScreen()->AddObserver(this); int width = 800, height = 600; options.Get(options::kWidth, &width); @@ -882,6 +884,17 @@ void NativeWindowMac::SetExcludedFromShownWindowsMenu(bool excluded) { [window setExcludedFromWindowsMenu:excluded]; } +void NativeWindowMac::OnDisplayMetricsChanged(const display::Display& display, + uint32_t changed_metrics) { + // We only want to force screen recalibration if we're in simpleFullscreen + // mode. + if (!is_simple_fullscreen_) + return; + + base::PostTask(FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce(&NativeWindow::UpdateFrame, GetWeakPtr())); +} + void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) { NSWindow* window = GetNativeWindow().GetNativeNSWindow(); @@ -1396,6 +1409,13 @@ void NativeWindowMac::RedrawTrafficLights() { [buttons_view_ setNeedsDisplayForButtons]; } +// In simpleFullScreen mode, update the frame for new bounds. +void NativeWindowMac::UpdateFrame() { + NSWindow* window = GetNativeWindow().GetNativeNSWindow(); + NSRect fullscreenFrame = [window.screen frame]; + [window setFrame:fullscreenFrame display:YES animate:YES]; +} + void NativeWindowMac::SetTouchBar( std::vector items) { if (@available(macOS 10.12.2, *)) { @@ -1551,6 +1571,7 @@ void NativeWindowMac::NotifyWindowWillLeaveFullScreen() { void NativeWindowMac::Cleanup() { DCHECK(!IsClosed()); ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this); + display::Screen::GetScreen()->RemoveObserver(this); [NSEvent removeMonitor:wheel_event_monitor_]; } diff --git a/shell/browser/resources/win/electron.rc b/shell/browser/resources/win/electron.rc index edc04e307f..c54bbbb036 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,20210315 - PRODUCTVERSION 14,0,0,20210315 + FILEVERSION 14,0,0,20210323 + PRODUCTVERSION 14,0,0,20210323 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L diff --git a/shell/browser/ui/inspectable_web_contents.h b/shell/browser/ui/inspectable_web_contents.h index 69a000ccf1..70f60bc860 100644 --- a/shell/browser/ui/inspectable_web_contents.h +++ b/shell/browser/ui/inspectable_web_contents.h @@ -201,12 +201,6 @@ class InspectableWebContents void AddDevToolsExtensionsToClient(); #endif - bool frontend_loaded_ = false; - scoped_refptr agent_host_; - std::unique_ptr frontend_host_; - std::unique_ptr - embedder_message_dispatcher_; - DevToolsContentsResizingStrategy contents_resizing_strategy_; gfx::Rect devtools_bounds_; bool can_dock_ = true; @@ -228,6 +222,12 @@ class InspectableWebContents bool is_guest_; std::unique_ptr view_; + bool frontend_loaded_ = false; + scoped_refptr agent_host_; + std::unique_ptr frontend_host_; + std::unique_ptr + embedder_message_dispatcher_; + class NetworkResourceLoader; std::set, base::UniquePtrComparator> loaders_; diff --git a/shell/common/node_bindings.cc b/shell/common/node_bindings.cc index b6efb9b0e2..e8f942a7fa 100644 --- a/shell/common/node_bindings.cc +++ b/shell/common/node_bindings.cc @@ -363,21 +363,22 @@ void NodeBindings::Initialize() { // Parse and set Node.js cli flags. SetNodeCliFlags(); - // pass non-null program name to argv so it doesn't crash - // trying to index into a nullptr - int argc = 1; - int exec_argc = 0; - const char* prog_name = "electron"; - const char** argv = &prog_name; - const char** exec_argv = nullptr; - std::unique_ptr env(base::Environment::Create()); SetNodeOptions(env.get()); - // TODO(codebytere): this is going to be deprecated in the near future - // in favor of Init(std::vector* argv, - // std::vector* exec_argv) - node::Init(&argc, argv, &exec_argc, &exec_argv); + std::vector argv = {"electron"}; + std::vector exec_argv; + std::vector errors; + + int exit_code = node::InitializeNodeWithArgs(&argv, &exec_argv, &errors); + + for (const std::string& error : errors) { + fprintf(stderr, "%s: %s\n", argv[0].c_str(), error.c_str()); + } + + if (exit_code != 0) { + exit(exit_code); + } #if defined(OS_WIN) // uv_init overrides error mode to suppress the default crash dialog, bring @@ -533,15 +534,13 @@ void NodeBindings::LoadEnvironment(node::Environment* env) { void NodeBindings::PrepareMessageLoop() { #if !defined(OS_WIN) int handle = uv_backend_fd(uv_loop_); -#else - HANDLE handle = uv_loop_->iocp; -#endif // If the backend fd hasn't changed, don't proceed. if (handle == handle_) return; handle_ = handle; +#endif // Add dummy handle for libuv, otherwise libuv would quit when there is // nothing to do. diff --git a/shell/common/node_bindings.h b/shell/common/node_bindings.h index efc8b40364..3af029e387 100644 --- a/shell/common/node_bindings.h +++ b/shell/common/node_bindings.h @@ -159,9 +159,7 @@ class NodeBindings { // Isolate data used in creating the environment node::IsolateData* isolate_data_ = nullptr; -#if defined(OS_WIN) - HANDLE handle_; -#else +#if !defined(OS_WIN) int handle_ = -1; #endif diff --git a/shell/common/platform_util_win.cc b/shell/common/platform_util_win.cc index f66fc80650..8ffffa109e 100644 --- a/shell/common/platform_util_win.cc +++ b/shell/common/platform_util_win.cc @@ -32,6 +32,7 @@ #include "base/win/windows_version.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "net/base/escape.h" #include "shell/common/electron_paths.h" #include "ui/base/win/shell.h" #include "url/gurl.h" @@ -241,7 +242,8 @@ std::string OpenExternalOnWorkerThread( // Quote the input scheme to be sure that the command does not have // parameters unexpected by the external program. This url should already // have been escaped. - std::wstring escaped_url = L"\"" + base::UTF8ToWide(url.spec()) + L"\""; + std::wstring escaped_url = + L"\"" + base::UTF8ToWide(net::EscapeExternalHandlerValue(url.spec())) + std::wstring working_dir = options.working_dir.value(); if (reinterpret_cast( diff --git a/shell/renderer/api/electron_api_context_bridge.cc b/shell/renderer/api/electron_api_context_bridge.cc index 0af41c50ec..64b13604fb 100644 --- a/shell/renderer/api/electron_api_context_bridge.cc +++ b/shell/renderer/api/electron_api_context_bridge.cc @@ -11,6 +11,7 @@ #include #include +#include "base/feature_list.h" #include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" #include "content/public/renderer/render_frame.h" @@ -25,6 +26,12 @@ #include "third_party/blink/public/web/web_element.h" #include "third_party/blink/public/web/web_local_frame.h" +namespace features { + +const base::Feature kContextBridgeMutability{"ContextBridgeMutability", + base::FEATURE_DISABLED_BY_DEFAULT}; +} + namespace electron { namespace api { @@ -554,6 +561,12 @@ void ExposeAPIInMainWorld(v8::Isolate* isolate, if (maybe_proxy.IsEmpty()) return; auto proxy = maybe_proxy.ToLocalChecked(); + + if (base::FeatureList::IsEnabled(features::kContextBridgeMutability)) { + global.Set(key, proxy); + return; + } + if (proxy->IsObject() && !proxy->IsTypedArray() && !DeepFreeze(v8::Local::Cast(proxy), main_context)) return; diff --git a/shell/renderer/electron_renderer_client.cc b/shell/renderer/electron_renderer_client.cc index a577a34e4a..74284e4128 100644 --- a/shell/renderer/electron_renderer_client.cc +++ b/shell/renderer/electron_renderer_client.cc @@ -146,9 +146,8 @@ void ElectronRendererClient::DidCreateScriptContext( // Add Electron extended APIs. electron_bindings_->BindTo(env->isolate(), env->process_object()); - AddRenderBindings(env->isolate(), env->process_object()); gin_helper::Dictionary process_dict(env->isolate(), env->process_object()); - process_dict.SetReadOnly("isMainFrame", render_frame->IsMainFrame()); + BindProcess(env->isolate(), &process_dict, render_frame); // Load everything. node_bindings_->LoadEnvironment(env); diff --git a/shell/renderer/electron_sandboxed_renderer_client.cc b/shell/renderer/electron_sandboxed_renderer_client.cc index c6b58c06f7..96d6eccb34 100644 --- a/shell/renderer/electron_sandboxed_renderer_client.cc +++ b/shell/renderer/electron_sandboxed_renderer_client.cc @@ -131,7 +131,7 @@ ElectronSandboxedRendererClient::~ElectronSandboxedRendererClient() = default; void ElectronSandboxedRendererClient::InitializeBindings( v8::Local binding, v8::Local context, - bool is_main_frame) { + content::RenderFrame* render_frame) { auto* isolate = context->GetIsolate(); gin_helper::Dictionary b(isolate, binding); b.SetMethod("get", GetBinding); @@ -141,13 +141,13 @@ void ElectronSandboxedRendererClient::InitializeBindings( b.Set("process", process); ElectronBindings::BindProcess(isolate, &process, metrics_.get()); + BindProcess(isolate, &process, render_frame); process.SetMethod("uptime", Uptime); process.Set("argv", base::CommandLine::ForCurrentProcess()->argv()); process.SetReadOnly("pid", base::GetCurrentProcId()); process.SetReadOnly("sandboxed", true); process.SetReadOnly("type", "renderer"); - process.SetReadOnly("isMainFrame", is_main_frame); } void ElectronSandboxedRendererClient::RenderFrameCreated( @@ -218,8 +218,7 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext( // argument. auto* isolate = context->GetIsolate(); auto binding = v8::Object::New(isolate); - InitializeBindings(binding, context, render_frame->IsMainFrame()); - AddRenderBindings(isolate, binding); + InitializeBindings(binding, context, render_frame); std::vector> sandbox_preload_bundle_params = { node::FIXED_ONE_BYTE_STRING(isolate, "binding")}; diff --git a/shell/renderer/electron_sandboxed_renderer_client.h b/shell/renderer/electron_sandboxed_renderer_client.h index ab481b422f..623896ff8f 100644 --- a/shell/renderer/electron_sandboxed_renderer_client.h +++ b/shell/renderer/electron_sandboxed_renderer_client.h @@ -21,7 +21,7 @@ class ElectronSandboxedRendererClient : public RendererClientBase { void InitializeBindings(v8::Local binding, v8::Local context, - bool is_main_frame); + content::RenderFrame* render_frame); // electron::RendererClientBase: void DidCreateScriptContext(v8::Handle context, content::RenderFrame* render_frame) override; diff --git a/shell/renderer/printing/print_render_frame_helper_delegate.cc b/shell/renderer/printing/print_render_frame_helper_delegate.cc index c554d4d723..8eb83a3be2 100644 --- a/shell/renderer/printing/print_render_frame_helper_delegate.cc +++ b/shell/renderer/printing/print_render_frame_helper_delegate.cc @@ -11,6 +11,7 @@ #if BUILDFLAG(ENABLE_EXTENSIONS) #include "extensions/common/constants.h" +#include "extensions/renderer/guest_view/mime_handler_view/post_message_support.h" #endif // BUILDFLAG(ENABLE_EXTENSIONS) namespace electron { @@ -50,6 +51,20 @@ bool PrintRenderFrameHelperDelegate::IsPrintPreviewEnabled() { bool PrintRenderFrameHelperDelegate::OverridePrint( blink::WebLocalFrame* frame) { +#if BUILDFLAG(ENABLE_EXTENSIONS) + auto* post_message_support = + extensions::PostMessageSupport::FromWebLocalFrame(frame); + if (post_message_support) { + // This message is handled in chrome/browser/resources/pdf/pdf_viewer.js and + // instructs the PDF plugin to print. This is to make window.print() on a + // PDF plugin document correctly print the PDF. See + // https://crbug.com/448720. + base::DictionaryValue message; + message.SetString("type", "print"); + post_message_support->PostMessageFromValue(message); + return true; + } +#endif // BUILDFLAG(ENABLE_EXTENSIONS) return false; } diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index 57b19b76ef..1a93fda7b1 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -137,9 +137,13 @@ void RendererClientBase::DidCreateScriptContext( global.SetHidden("contextId", context_id); } -void RendererClientBase::AddRenderBindings( - v8::Isolate* isolate, - v8::Local binding_object) {} +void RendererClientBase::BindProcess(v8::Isolate* isolate, + gin_helper::Dictionary* process, + content::RenderFrame* render_frame) { + process->SetReadOnly("isMainFrame", render_frame->IsMainFrame()); + process->SetReadOnly("contextIsolated", + render_frame->GetBlinkPreferences().context_isolation); +} void RendererClientBase::RenderThreadStarted() { auto* command_line = base::CommandLine::ForCurrentProcess(); diff --git a/shell/renderer/renderer_client_base.h b/shell/renderer/renderer_client_base.h index 14b766b8cf..e0a6d4016c 100644 --- a/shell/renderer/renderer_client_base.h +++ b/shell/renderer/renderer_client_base.h @@ -13,6 +13,7 @@ #include "content/public/renderer/content_renderer_client.h" #include "electron/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h" +#include "shell/common/gin_helper/dictionary.h" #include "third_party/blink/public/web/web_local_frame.h" // In SHARED_INTERMEDIATE_DIR. #include "widevine_cdm_version.h" // NOLINT(build/include_directory) @@ -92,8 +93,9 @@ class RendererClientBase : public content::ContentRendererClient #endif protected: - void AddRenderBindings(v8::Isolate* isolate, - v8::Local binding_object); + void BindProcess(v8::Isolate* isolate, + gin_helper::Dictionary* process, + content::RenderFrame* render_frame); // content::ContentRendererClient: void RenderThreadStarted() override; diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index cbbcee1895..ceae307529 100644 --- a/spec-main/api-app-spec.ts +++ b/spec-main/api-app-spec.ts @@ -143,7 +143,8 @@ describe('app module', () => { }); }); - describe('app.exit(exitCode)', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(!process.env.IS_ASAN)('app.exit(exitCode)', () => { let appProcess: cp.ChildProcess | null = null; afterEach(() => { @@ -209,7 +210,7 @@ describe('app module', () => { }); }); - // TODO(jeremy): figure out why these tests time out under ASan + // Running child app under ASan might receive SIGKILL because of OOM. ifdescribe(!process.env.IS_ASAN)('app.requestSingleInstanceLock', () => { it('prevents the second launch of app', async function () { this.timeout(120000); @@ -252,7 +253,8 @@ describe('app module', () => { }); }); - describe('app.relaunch', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(!process.env.IS_ASAN)('app.relaunch', () => { let server: net.Server | null = null; const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-app-relaunch' : '/tmp/electron-app-relaunch'; @@ -852,7 +854,8 @@ describe('app module', () => { }); }); - describe('getAppPath', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(!process.env.IS_ASAN)('getAppPath', () => { it('works for directories with package.json', async () => { const { appPath } = await runTestApp('app-path'); expect(appPath).to.equal(path.resolve(fixturesPath, 'api/app-path')); @@ -1128,13 +1131,7 @@ describe('app module', () => { }); }); - describe('app launch through uri', () => { - before(function () { - if (process.platform !== 'win32') { - this.skip(); - } - }); - + ifdescribe(process.platform === 'win32')('app launch through uri', () => { it('does not launch for argument following a URL', async () => { const appPath = path.join(fixturesPath, 'api', 'quit-app'); // App should exit with non 123 code. @@ -1334,7 +1331,8 @@ describe('app module', () => { }); }); - describe('sandbox options', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(!process.env.IS_ASAN)('sandbox options', () => { let appProcess: cp.ChildProcess = null as any; let server: net.Server = null as any; const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-mixed-sandbox' : '/tmp/electron-mixed-sandbox'; @@ -1375,8 +1373,7 @@ describe('app module', () => { }); describe('when app.enableSandbox() is called', () => { - // TODO(jeremy): figure out why this times out under ASan - ifit(!process.env.IS_ASAN)('adds --enable-sandbox to all renderer processes', done => { + it('adds --enable-sandbox to all renderer processes', done => { const appPath = path.join(fixturesPath, 'api', 'mixed-sandbox-app'); appProcess = cp.spawn(process.execPath, [appPath, '--app-enable-sandbox']); @@ -1401,8 +1398,7 @@ describe('app module', () => { }); describe('when the app is launched with --enable-sandbox', () => { - // TODO(jeremy): figure out why this times out under ASan - ifit(!process.env.IS_ASAN)('adds --enable-sandbox to all renderer processes', done => { + it('adds --enable-sandbox to all renderer processes', done => { const appPath = path.join(fixturesPath, 'api', 'mixed-sandbox-app'); appProcess = cp.spawn(process.execPath, [appPath, '--enable-sandbox']); @@ -1561,7 +1557,8 @@ describe('app module', () => { }); }); - describe('commandLine.hasSwitch (existing argv)', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(!process.env.IS_ASAN)('commandLine.hasSwitch (existing argv)', () => { it('returns true when present', async () => { const { hasSwitch } = await runTestApp('command-line', '--foobar'); expect(hasSwitch).to.equal(true); @@ -1589,7 +1586,8 @@ describe('app module', () => { }); }); - describe('commandLine.getSwitchValue (existing argv)', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(!process.env.IS_ASAN)('commandLine.getSwitchValue (existing argv)', () => { it('returns the value when present', async () => { const { getSwitchValue } = await runTestApp('command-line', '--foobar=test'); expect(getSwitchValue).to.equal('test'); @@ -1616,7 +1614,8 @@ describe('app module', () => { }); }); -describe('default behavior', () => { +// Running child app under ASan might receive SIGKILL because of OOM. +ifdescribe(!process.env.IS_ASAN)('default behavior', () => { describe('application menu', () => { it('creates the default menu if the app does not set it', async () => { const result = await runTestApp('default-menu'); diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index bc673855bb..81e57f32fc 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -2487,9 +2487,11 @@ describe('BrowserWindow module', () => { expect(test.env).to.deep.equal(process.env); expect(test.execPath).to.equal(process.helperExecPath); expect(test.sandboxed).to.be.true('sandboxed'); + expect(test.contextIsolated).to.be.false('contextIsolated'); expect(test.type).to.equal('renderer'); expect(test.version).to.equal(process.version); expect(test.versions).to.deep.equal(process.versions); + expect(test.contextId).to.be.a('string'); if (process.platform === 'linux' && test.osSandbox) { expect(test.creationTime).to.be.null('creation time'); @@ -4304,6 +4306,19 @@ describe('BrowserWindow module', () => { const [, data] = await p; expect(data.pageContext.openedLocation).to.equal('about:blank'); }); + it('reports process.contextIsolated', async () => { + const iw = new BrowserWindow({ + show: false, + webPreferences: { + contextIsolation: true, + preload: path.join(fixtures, 'api', 'isolated-process.js') + } + }); + const p = emittedOnce(ipcMain, 'context-isolation'); + iw.loadURL('about:blank'); + const [, contextIsolation] = await p; + expect(contextIsolation).to.be.true('contextIsolation'); + }); }); describe('reloading with allowRendererProcessReuse enabled', () => { diff --git a/spec-main/api-context-bridge-spec.ts b/spec-main/api-context-bridge-spec.ts index 743a660902..176711af2b 100644 --- a/spec-main/api-context-bridge-spec.ts +++ b/spec-main/api-context-bridge-spec.ts @@ -5,6 +5,7 @@ import * as fs from 'fs-extra'; import * as http from 'http'; import * as os from 'os'; import * as path from 'path'; +import * as cp from 'child_process'; import { closeWindow } from './window-helpers'; import { emittedOnce } from './events-helpers'; @@ -1165,3 +1166,31 @@ describe('contextBridge', () => { generateTests(true); generateTests(false); }); + +describe('ContextBridgeMutability', () => { + it('should not make properties unwriteable and read-only if ContextBridgeMutability is on', async () => { + const appPath = path.join(fixturesPath, 'context-bridge-mutability'); + const appProcess = cp.spawn(process.execPath, ['--enable-logging', '--enable-features=ContextBridgeMutability', appPath]); + + let output = ''; + appProcess.stdout.on('data', data => { output += data; }); + await emittedOnce(appProcess, 'exit'); + + expect(output).to.include('some-modified-text'); + expect(output).to.include('obj-modified-prop'); + expect(output).to.include('1,2,5,3,4'); + }); + + it('should make properties unwriteable and read-only if ContextBridgeMutability is off', async () => { + const appPath = path.join(fixturesPath, 'context-bridge-mutability'); + const appProcess = cp.spawn(process.execPath, ['--enable-logging', appPath]); + + let output = ''; + appProcess.stdout.on('data', data => { output += data; }); + await emittedOnce(appProcess, 'exit'); + + expect(output).to.include('some-text'); + expect(output).to.include('obj-prop'); + expect(output).to.include('1,2,3,4'); + }); +}); diff --git a/spec-main/api-crash-reporter-spec.ts b/spec-main/api-crash-reporter-spec.ts index 1136d9a522..739d5c09a8 100644 --- a/spec-main/api-crash-reporter-spec.ts +++ b/spec-main/api-crash-reporter-spec.ts @@ -361,7 +361,13 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_ it('requires that the submitURL option be specified', () => { expect(() => { crashReporter.start({} as any); - }).to.throw('submitURL is a required option to crashReporter.start'); + }).to.throw('submitURL must be specified when uploadToServer is true'); + }); + + it('allows the submitURL option to be omitted when uploadToServer is false', () => { + expect(() => { + crashReporter.start({ uploadToServer: false } as any); + }).not.to.throw(); }); it('can be called twice', async () => { diff --git a/spec-main/api-protocol-spec.ts b/spec-main/api-protocol-spec.ts index 54483eb98a..720d5bcaa1 100644 --- a/spec-main/api-protocol-spec.ts +++ b/spec-main/api-protocol-spec.ts @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { v4 } from 'uuid'; import { protocol, webContents, WebContents, session, BrowserWindow, ipcMain } from 'electron/main'; import { AddressInfo } from 'net'; import * as ChildProcess from 'child_process'; @@ -704,7 +705,7 @@ describe('protocol module', () => { }); describe('protocol.registerSchemeAsPrivileged', () => { - // TODO(jeremy): figure out why this times out under ASan + // Running child app under ASan might receive SIGKILL because of OOM. ifit(!process.env.IS_ASAN)('does not crash on exit', async () => { const appPath = path.join(__dirname, 'fixtures', 'api', 'custom-protocol-shutdown.js'); const appProcess = ChildProcess.spawn(process.execPath, ['--enable-logging', appPath]); @@ -724,6 +725,36 @@ describe('protocol module', () => { }); }); + describe('protocol.registerSchemesAsPrivileged allowServiceWorkers', () => { + const { serviceWorkerScheme } = global as any; + protocol.registerStringProtocol(serviceWorkerScheme, (request, cb) => { + if (request.url.endsWith('.js')) { + cb({ + mimeType: 'text/javascript', + charset: 'utf-8', + data: 'console.log("Loaded")' + }); + } else { + cb({ + mimeType: 'text/html', + charset: 'utf-8', + data: '' + }); + } + }); + after(() => protocol.unregisterProtocol(serviceWorkerScheme)); + + it('should fail when registering invalid service worker', async () => { + await contents.loadURL(`${serviceWorkerScheme}://${v4()}.com`); + await expect(contents.executeJavaScript(`navigator.serviceWorker.register('${v4()}.notjs', {scope: './'})`)).to.be.rejected(); + }); + + it('should be able to register service worker for custom scheme', async () => { + await contents.loadURL(`${serviceWorkerScheme}://${v4()}.com`); + await contents.executeJavaScript(`navigator.serviceWorker.register('${v4()}.js', {scope: './'})`); + }); + }); + describe.skip('protocol.registerSchemesAsPrivileged standard', () => { const standardScheme = (global as any).standardScheme; const origin = `${standardScheme}://fake-host`; diff --git a/spec-main/api-web-contents-spec.ts b/spec-main/api-web-contents-spec.ts index ed30aa76c6..058af2649f 100644 --- a/spec-main/api-web-contents-spec.ts +++ b/spec-main/api-web-contents-spec.ts @@ -3,7 +3,6 @@ import { AddressInfo } from 'net'; import * as path from 'path'; import * as fs from 'fs'; import * as http from 'http'; -import * as ChildProcess from 'child_process'; import { BrowserWindow, ipcMain, webContents, session, WebContents, app } from 'electron/main'; import { clipboard } from 'electron/common'; import { emittedOnce } from './events-helpers'; @@ -1268,16 +1267,6 @@ describe('webContents module', () => { }); }); - describe('create()', () => { - it('does not crash on exit', async () => { - const appPath = path.join(fixturesPath, 'api', 'leak-exit-webcontents.js'); - const electronPath = process.execPath; - const appProcess = ChildProcess.spawn(electronPath, [appPath]); - const [code] = await emittedOnce(appProcess, 'close'); - expect(code).to.equal(0); - }); - }); - const crashPrefs = [ { nodeIntegration: true @@ -2016,13 +2005,6 @@ describe('webContents module', () => { }); contents.loadURL('about:blank').then(() => contents.forcefullyCrashRenderer()); }); - - it('does not crash main process when quiting in it', async () => { - const appPath = path.join(mainFixturesPath, 'apps', 'quit', 'main.js'); - const appProcess = ChildProcess.spawn(process.execPath, [appPath]); - const [code] = await emittedOnce(appProcess, 'close'); - expect(code).to.equal(0); - }); }); it('emits a cancelable event before creating a child webcontents', async () => { diff --git a/spec-main/api-web-contents-view-spec.ts b/spec-main/api-web-contents-view-spec.ts index 4388dc2678..b8b7b26030 100644 --- a/spec-main/api-web-contents-view-spec.ts +++ b/spec-main/api-web-contents-view-spec.ts @@ -1,7 +1,3 @@ -import { expect } from 'chai'; -import * as ChildProcess from 'child_process'; -import * as path from 'path'; -import { emittedOnce } from './events-helpers'; import { closeWindow } from './window-helpers'; import { BaseWindow, WebContentsView } from 'electron/main'; @@ -15,22 +11,6 @@ describe('WebContentsView', () => { w.setContentView(new WebContentsView({})); }); - describe('new WebContentsView()', () => { - it('does not crash on exit', async () => { - const appPath = path.join(__dirname, 'fixtures', 'api', 'leak-exit-webcontentsview.js'); - const electronPath = process.execPath; - const appProcess = ChildProcess.spawn(electronPath, ['--enable-logging', appPath]); - let output = ''; - appProcess.stdout.on('data', data => { output += data; }); - appProcess.stderr.on('data', data => { output += data; }); - const [code] = await emittedOnce(appProcess, 'exit'); - if (code !== 0) { - console.log(code, output); - } - expect(code).to.equal(0); - }); - }); - function triggerGCByAllocation () { const arr = []; for (let i = 0; i < 1000000; i++) { diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index d753932064..c019114785 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -18,8 +18,6 @@ const features = process._linkedBinding('electron_common_features'); const fixturesPath = path.resolve(__dirname, '..', 'spec', 'fixtures'); -const isAsan = process.env.IS_ASAN; - describe('reporting api', () => { // TODO(nornagon): this started failing a lot on CI. Figure out why and fix // it. @@ -298,7 +296,8 @@ describe('web security', () => { }); }); -describe('command line switches', () => { +// Running child app under ASan might receive SIGKILL because of OOM. +ifdescribe(!process.env.IS_ASAN)('command line switches', () => { let appProcess: ChildProcess.ChildProcessWithoutNullStreams | undefined; afterEach(() => { if (appProcess && !appProcess.killed) { @@ -343,8 +342,7 @@ describe('command line switches', () => { ifit(process.platform === 'linux')('should not change LC_ALL when --lang is not set', async () => testLocale('', lcAll, true)); }); - // TODO(nornagon): figure out why these tests fail under ASan. - ifdescribe(!isAsan)('--remote-debugging-pipe switch', () => { + describe('--remote-debugging-pipe switch', () => { it('should expose CDP via pipe', async () => { const electronPath = process.execPath; appProcess = ChildProcess.spawn(electronPath, ['--remote-debugging-pipe'], { @@ -386,8 +384,7 @@ describe('command line switches', () => { }); }); - // TODO(nornagon): figure out why these tests fail under ASan. - ifdescribe(!isAsan)('--remote-debugging-port switch', () => { + describe('--remote-debugging-port switch', () => { it('should display the discovery page', (done) => { const electronPath = process.execPath; let output = ''; diff --git a/spec-main/crash-spec.ts b/spec-main/crash-spec.ts index eb9718dac5..4457e9bcb8 100644 --- a/spec-main/crash-spec.ts +++ b/spec-main/crash-spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as cp from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; +import { ifdescribe } from './spec-helpers'; const fixturePath = path.resolve(__dirname, 'fixtures', 'crash-cases'); @@ -30,7 +31,8 @@ const runFixtureAndEnsureCleanExit = (args: string[]) => { }); }; -describe('crash cases', () => { +// Running child app under ASan might receive SIGKILL because of OOM. +ifdescribe(!process.env.IS_ASAN)('crash cases', () => { afterEach(() => { for (const child of children) { child.kill(); diff --git a/spec-main/fixtures/api/context-bridge/context-bridge-mutability/index.html b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/index.html new file mode 100644 index 0000000000..980a3d7f32 --- /dev/null +++ b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/index.html @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/spec-main/fixtures/api/context-bridge/context-bridge-mutability/main.js b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/main.js new file mode 100644 index 0000000000..622ef6acbf --- /dev/null +++ b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/main.js @@ -0,0 +1,20 @@ +const { app, BrowserWindow } = require('electron'); +const path = require('path'); + +let win; +app.whenReady().then(function () { + win = new BrowserWindow({ + webPreferences: { + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + + win.loadFile('index.html'); + + win.webContents.on('console-message', (event, level, message) => { + console.log(message); + }); + + win.webContents.on('did-finish-load', () => app.quit()); +}); diff --git a/spec-main/fixtures/api/context-bridge/context-bridge-mutability/package.json b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/package.json new file mode 100644 index 0000000000..d1fc13838e --- /dev/null +++ b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/package.json @@ -0,0 +1,4 @@ +{ + "name": "context-bridge-mutability", + "main": "main.js" +} \ No newline at end of file diff --git a/spec-main/fixtures/api/context-bridge/context-bridge-mutability/preload.js b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/preload.js new file mode 100644 index 0000000000..e3d3d9abfa --- /dev/null +++ b/spec-main/fixtures/api/context-bridge/context-bridge-mutability/preload.js @@ -0,0 +1,5 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('str', 'some-text'); +contextBridge.exposeInMainWorld('obj', { prop: 'obj-prop' }); +contextBridge.exposeInMainWorld('arr', [1, 2, 3, 4]); diff --git a/spec-main/fixtures/apps/libuv-hang/index.html b/spec-main/fixtures/apps/libuv-hang/index.html new file mode 100644 index 0000000000..a3534d419a --- /dev/null +++ b/spec-main/fixtures/apps/libuv-hang/index.html @@ -0,0 +1,13 @@ + + + + + + + Hello World! + + +

Hello World!

+ + + diff --git a/spec-main/fixtures/apps/libuv-hang/main.js b/spec-main/fixtures/apps/libuv-hang/main.js new file mode 100644 index 0000000000..4ca4ef15d9 --- /dev/null +++ b/spec-main/fixtures/apps/libuv-hang/main.js @@ -0,0 +1,36 @@ +const { app, BrowserWindow, ipcMain } = require('electron'); +const path = require('path'); + +async function createWindow () { + const mainWindow = new BrowserWindow({ + show: false, + webPreferences: { + preload: path.join(__dirname, 'preload.js') + } + }); + + await mainWindow.loadFile('index.html'); +} + +app.whenReady().then(() => { + createWindow(); + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); + +let count = 0; +ipcMain.handle('reload-successful', () => { + if (count === 2) { + app.quit(); + } else { + count++; + return count; + } +}); + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') app.quit(); +}); diff --git a/spec-main/fixtures/apps/libuv-hang/preload.js b/spec-main/fixtures/apps/libuv-hang/preload.js new file mode 100644 index 0000000000..a5840f557f --- /dev/null +++ b/spec-main/fixtures/apps/libuv-hang/preload.js @@ -0,0 +1,16 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('api', { + ipcRenderer, + run: async () => { + const { promises: fs } = require('fs'); + for (let i = 0; i < 10; i++) { + const list = await fs.readdir('.', { withFileTypes: true }); + for (const file of list) { + if (file.isFile()) { + await fs.readFile(file.name, 'utf-8'); + } + } + } + } +}); diff --git a/spec-main/fixtures/apps/libuv-hang/renderer.js b/spec-main/fixtures/apps/libuv-hang/renderer.js new file mode 100644 index 0000000000..5f0a2b58b5 --- /dev/null +++ b/spec-main/fixtures/apps/libuv-hang/renderer.js @@ -0,0 +1,8 @@ +const count = localStorage.getItem('count'); + +const { run, ipcRenderer } = window.api; + +run().then(async () => { + const count = await ipcRenderer.invoke('reload-successful'); + if (count < 3) location.reload(); +}).catch(console.log); diff --git a/spec-main/fixtures/apps/quit/main.js b/spec-main/fixtures/crash-cases/quit-on-crashed-event/index.js similarity index 100% rename from spec-main/fixtures/apps/quit/main.js rename to spec-main/fixtures/crash-cases/quit-on-crashed-event/index.js diff --git a/spec/fixtures/api/leak-exit-webcontents.js b/spec-main/fixtures/crash-cases/webcontents-create-leak-exit/index.js similarity index 100% rename from spec/fixtures/api/leak-exit-webcontents.js rename to spec-main/fixtures/crash-cases/webcontents-create-leak-exit/index.js diff --git a/spec-main/fixtures/api/leak-exit-webcontentsview.js b/spec-main/fixtures/crash-cases/webcontentsview-create-leak-exit/index.js similarity index 100% rename from spec-main/fixtures/api/leak-exit-webcontentsview.js rename to spec-main/fixtures/crash-cases/webcontentsview-create-leak-exit/index.js diff --git a/spec-main/fixtures/module/preload-sandbox.js b/spec-main/fixtures/module/preload-sandbox.js index 3f216a3e03..d774c54301 100644 --- a/spec-main/fixtures/module/preload-sandbox.js +++ b/spec-main/fixtures/module/preload-sandbox.js @@ -40,9 +40,11 @@ arch: process.arch, platform: process.platform, sandboxed: process.sandboxed, + contextIsolated: process.contextIsolated, type: process.type, version: process.version, - versions: process.versions + versions: process.versions, + contextId: process.contextId }; } } else if (location.href !== 'about:blank') { diff --git a/spec-main/index.js b/spec-main/index.js index 60c6d10602..406a7b7ecf 100644 --- a/spec-main/index.js +++ b/spec-main/index.js @@ -34,9 +34,11 @@ app.commandLine.appendSwitch('use-fake-device-for-media-stream'); global.standardScheme = 'app'; global.zoomScheme = 'zoom'; +global.serviceWorkerScheme = 'sw'; protocol.registerSchemesAsPrivileged([ { scheme: global.standardScheme, privileges: { standard: true, secure: true, stream: false } }, { scheme: global.zoomScheme, privileges: { standard: true, secure: true } }, + { scheme: global.serviceWorkerScheme, privileges: { allowServiceWorkers: true, standard: true, secure: true } }, { scheme: 'cors-blob', privileges: { corsEnabled: true, supportFetchAPI: true } }, { scheme: 'cors', privileges: { corsEnabled: true, supportFetchAPI: true } }, { scheme: 'no-cors', privileges: { supportFetchAPI: true } }, diff --git a/spec-main/node-spec.ts b/spec-main/node-spec.ts index 227f9c4003..45396fdd31 100644 --- a/spec-main/node-spec.ts +++ b/spec-main/node-spec.ts @@ -7,6 +7,7 @@ import { ifdescribe, ifit } from './spec-helpers'; import { webContents, WebContents } from 'electron/main'; const features = process._linkedBinding('electron_common_features'); +const mainFixturesPath = path.resolve(__dirname, 'fixtures'); describe('node feature', () => { const fixtures = path.join(__dirname, '..', 'spec', 'fixtures'); @@ -22,6 +23,16 @@ describe('node feature', () => { }); }); + it('does not hang when using the fs module in the renderer process', async () => { + const appPath = path.join(mainFixturesPath, 'apps', 'libuv-hang', 'main.js'); + const appProcess = childProcess.spawn(process.execPath, [appPath], { + cwd: path.join(mainFixturesPath, 'apps', 'libuv-hang'), + stdio: 'inherit' + }); + const [code] = await emittedOnce(appProcess, 'close'); + expect(code).to.equal(0); + }); + describe('contexts', () => { describe('setTimeout called under Chromium event loop in browser process', () => { it('Can be scheduled in time', (done) => { @@ -123,11 +134,12 @@ describe('node feature', () => { }); }); - describe('Node.js cli flags', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(features.isRunAsNodeEnabled() && !process.env.IS_ASAN)('Node.js cli flags', () => { let child: childProcess.ChildProcessWithoutNullStreams; let exitPromise: Promise; - ifit(features.isRunAsNodeEnabled())('Prohibits crypto-related flags in ELECTRON_RUN_AS_NODE mode', (done) => { + it('Prohibits crypto-related flags in ELECTRON_RUN_AS_NODE mode', (done) => { after(async () => { const [code, signal] = await exitPromise; expect(signal).to.equal(null); @@ -165,7 +177,8 @@ describe('node feature', () => { }); }); - ifdescribe(features.isRunAsNodeEnabled())('inspector', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifdescribe(features.isRunAsNodeEnabled() && !process.env.IS_ASAN)('inspector', () => { let child: childProcess.ChildProcessWithoutNullStreams; let exitPromise: Promise; @@ -242,9 +255,8 @@ describe('node feature', () => { } }); - // IPC Electron child process not supported on Windows - // TODO(jeremy): figure out why this times out under ASan - ifit(process.platform !== 'win32' && !process.env.IS_ASAN)('does not crash when quitting with the inspector connected', function (done) { + // IPC Electron child process not supported on Windows. + ifit(process.platform !== 'win32')('does not crash when quitting with the inspector connected', function (done) { child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'delay-exit'), '--inspect=0'], { stdio: ['ipc'] }) as childProcess.ChildProcessWithoutNullStreams; @@ -304,7 +316,8 @@ describe('node feature', () => { }); }); - it('Can find a module using a package.json main field', () => { + // Running child app under ASan might receive SIGKILL because of OOM. + ifit(!process.env.IS_ASAN)('Can find a module using a package.json main field', () => { const result = childProcess.spawnSync(process.execPath, [path.resolve(fixtures, 'api', 'electron-main-module', 'app.asar')]); expect(result.status).to.equal(0); }); diff --git a/spec-main/spellchecker-spec.ts b/spec-main/spellchecker-spec.ts index e4ef5ae81e..d8f5c6ccb2 100644 --- a/spec-main/spellchecker-spec.ts +++ b/spec-main/spellchecker-spec.ts @@ -9,7 +9,11 @@ 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', () => { +ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function () { + // TODO(zcbenz): Spellchecker loads really slow on ASan, we should provide + // a small testing dictionary to make the tests load faster. + this.timeout((process.env.IS_ASAN ? 700 : 20) * 1000); + let w: BrowserWindow; async function rightClick () { @@ -28,7 +32,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => { // 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; + const timeout = (process.env.IS_ASAN ? 600 : 10) * 1000; let contextMenuParams = await rightClick(); while (!fn(contextMenuParams) && (Date.now() - now < timeout)) { await delay(100); diff --git a/spec-main/webview-spec.ts b/spec-main/webview-spec.ts index 46b164a9dc..9478457814 100644 --- a/spec-main/webview-spec.ts +++ b/spec-main/webview-spec.ts @@ -3,6 +3,7 @@ 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'; async function loadWebView (w: WebContents, attributes: Record, openDevTools: boolean = false): Promise { @@ -25,7 +26,8 @@ async function loadWebView (w: WebContents, attributes: Record, `); } -describe(' tag', function () { +// The render process of webview might receive SIGKILL because of OOM. +ifdescribe(!process.env.IS_ASAN)(' tag', function () { const fixtures = path.join(__dirname, '..', 'spec', 'fixtures'); afterEach(closeAllWindows); diff --git a/spec/api-process-spec.js b/spec/api-process-spec.js index 6eada5c945..8029107413 100644 --- a/spec/api-process-spec.js +++ b/spec/api-process-spec.js @@ -115,4 +115,10 @@ describe('process module', () => { expect(success).to.be.false(); }); }); + + describe('process.contextId', () => { + it('is a string', () => { + expect(process.contextId).to.be.a('string'); + }); + }); }); diff --git a/spec/fixtures/api/isolated-process.js b/spec/fixtures/api/isolated-process.js new file mode 100644 index 0000000000..d5e949ded5 --- /dev/null +++ b/spec/fixtures/api/isolated-process.js @@ -0,0 +1,3 @@ +const { ipcRenderer } = require('electron'); + +ipcRenderer.send('context-isolation', process.contextIsolated);