diff --git a/.circleci/config.yml b/.circleci/config.yml index 2e6d122b247b..8be8bc3bd080 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 85caea238e4a..450026c06df2 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 edac2f406325..8bb4b4bd75dc 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 ab157f79f64f..04eb887bb78a 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 c03f8a7e0398..8331bc8d43f5 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 ee00672129ff..3f0d2ed867cd 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 7478433cdd53..ec0ad5dc04f3 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 a3855d2640d8..f008d867a0f8 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 bfc856e60635..519a67947cdb 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 000000000000..7674d012240c --- /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 1358e523f920..4c7c07880645 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 61ff0e1cb409..197c29a5ffcb 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 99672c690d8b..16a9b1b5cb0f 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 01fd54aa3b9d..e9aab2cf8299 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 c41fa561888d..3bb3c2d6eff7 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 a23082ef04ad..f82f0cbe0058 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 dd820127f834..b56a254ad0c4 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 000000000000..74199b9f9c64 --- /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 edd62803e09a..19f624d75afb 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 4f2331c5afae..d0ac57d4c510 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 3ef7c0bd1f16..9664836a0f3c 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 bab36f6a6544..49d793418768 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 8e4329b5234e..952e32d0d356 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 859779fe13e8..ee3de4b51ac8 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 95afaf080f66..fca40a30cc04 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 747822058769..ce0e3ff8891b 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 e48f4d2f3d70..0da492ef9b7d 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 45a7342dcc4b..88f65ea4201e 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 21fff3e72474..618119a6eb48 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 edc04e307ff5..c54bbbb03616 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 69a000ccf152..70f60bc86020 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 b6efb9b0e244..e8f942a7fa00 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 efc8b40364ef..3af029e387e8 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 f66fc80650f2..8ffffa109ec1 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 0af41c50ecd1..64b13604fb9f 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 a577a34e4a09..74284e41285c 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 c6b58c06f72a..96d6eccb34af 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 ab481b422f6a..623896ff8f1c 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 c554d4d723a4..8eb83a3be249 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 57b19b76ef21..1a93fda7b11f 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 14b766b8cfdd..e0a6d4016cb1 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 cbbcee18956f..ceae3075295b 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 bc673855bbfa..81e57f32fcdc 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 743a6609020c..176711af2b9f 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 1136d9a522b6..739d5c09a8d9 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 54483eb98a0f..720d5bcaa10e 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 ed30aa76c6a3..058af2649fa8 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 4388dc2678be..b8b7b2603049 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 d75393206439..c019114785a4 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 eb9718dac5ac..4457e9bcb864 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 000000000000..980a3d7f32c3 --- /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 000000000000..622ef6acbf9c --- /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 000000000000..d1fc13838e5f --- /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 000000000000..e3d3d9abfad5 --- /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 000000000000..a3534d419a6f --- /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 000000000000..4ca4ef15d902 --- /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 000000000000..a5840f557f56 --- /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 000000000000..5f0a2b58b514 --- /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 3f216a3e03fb..d774c54301f3 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 60c6d10602e0..406a7b7ecf3e 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 227f9c4003ae..45396fdd3183 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 e4ef5ae81e12..d8f5c6ccb2a7 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 46b164a9dc58..9478457814f2 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 6eada5c945fe..802910741322 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 000000000000..d5e949ded56f --- /dev/null +++ b/spec/fixtures/api/isolated-process.js @@ -0,0 +1,3 @@ +const { ipcRenderer } = require('electron'); + +ipcRenderer.send('context-isolation', process.contextIsolated);