Compare commits

..

10 Commits

Author SHA1 Message Date
Electron Bot
2029ff1903 Bump v11.0.0-nightly.20200525 2020-05-25 08:32:17 -07:00
Samuel Attard
9bc5e98238 chore: tsify more of lib (#23721)
* chore: tsify more of lib

* Update lib/browser/api/session.ts

Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>

Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>
2020-05-22 12:46:22 -07:00
Shelley Vohr
762f7bcca2 refactor: use typeutils for nativeImage serialization (#23693) 2020-05-22 08:56:57 -07:00
Electron Bot
75847a0c5b Bump v11.0.0-nightly.20200522 2020-05-22 08:32:12 -07:00
Samuel Attard
e3c2ec9f7c chore: remove unused isolated-world-args (#23716) 2020-05-21 20:11:58 -07:00
Cheng Zhao
fdf40ce07a fix: read GTK dark theme setting on Linux (#23678) 2020-05-21 15:41:25 -07:00
Florian Keller
82924679fe docs: Explain console-message parameters (#23661) 2020-05-21 15:39:51 -07:00
Erick Zhao
f373cc770f docs: remove app feedback program doc (#23673) 2020-05-21 15:39:13 -07:00
Shelley Vohr
78d74bf8b4 fix: trigger about panel for about role on on win (#23687) 2020-05-21 15:38:26 -07:00
Samuel Attard
5ed2512881 fix: support 10-x-y in the release notes generator (#23709) 2020-05-21 13:55:27 -07:00
68 changed files with 417 additions and 522 deletions

View File

@@ -1 +1 @@
10.0.0-beta.2
11.0.0-nightly.20200525

View File

@@ -71,6 +71,7 @@ steps:
ELECTRON_TEST_RESULTS_DIR: junit
MOCHA_MULTI_REPORTERS: 'mocha-junit-reporter, tap'
MOCHA_REPORTER: mocha-multi-reporters
MOCHA_TIMEOUT: 120000
- task: PublishTestResults@2
displayName: 'Publish Test Results'

View File

@@ -66,7 +66,7 @@ if (defines['ENABLE_VIEWS_API'] === 'false') {
const alias = {}
for (const ignoredModule of ignoredModules) {
alias[ignoredModule] = path.resolve(electronRoot, 'lib/common/dummy.js')
alias[ignoredModule] = path.resolve(electronRoot, 'lib/common/dummy.ts')
}
module.exports = ({

View File

@@ -92,7 +92,6 @@ These individual tutorials expand on topics discussed in the guide above.
* Electron Releases & Developer Feedback
* [Versioning Policy](tutorial/electron-versioning.md)
* [Release Timelines](tutorial/electron-timelines.md)
* [App Feedback Program](tutorial/app-feedback-program.md)
* [Packaging App Source Code with asar](tutorial/application-packaging.md)
* [Generating asar Archives](tutorial/application-packaging.md#generating-asar-archives)
* [Using asar Archives](tutorial/application-packaging.md#using-asar-archives)

View File

@@ -39,7 +39,7 @@ Returns:
* `message` String - The actual console message
* `versionId` Number - The version ID of the service worker that sent the log message
* `source` String - The type of source for this message. Can be `javascript`, `xml`, `network`, `console-api`, `storage`, `app-cache`, `rendering`, `security`, `deprecation`, `worker`, `violation`, `intervention`, `recommendation` or `other`.
* `level` Number - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `level` Number - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `sourceUrl` String - The URL the message came from
* `lineNumber` Number - The line number of the source that triggered this console message

View File

@@ -737,9 +737,9 @@ Emitted when a `<webview>` has been attached to this web contents.
Returns:
* `event` Event
* `level` Integer
* `message` String
* `line` Integer
* `level` Integer - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `message` String - The actual console message
* `line` Integer - The line number of the source that triggered this console message
* `sourceId` String
Emitted when the associated window logs a console message.
@@ -907,10 +907,10 @@ Returns `String` - The URL of the current web page.
```javascript
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('http://github.com').then(() => {
const currentURL = win.webContents.getURL()
console.log(currentURL)
})
win.loadURL('http://github.com')
let currentURL = win.webContents.getURL()
console.log(currentURL)
```
#### `contents.getTitle()`

View File

@@ -755,9 +755,9 @@ Fired when page leaves fullscreen triggered by HTML API.
Returns:
* `level` Integer
* `message` String
* `line` Integer
* `level` Integer - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `message` String - The actual console message
* `line` Integer - The line number of the source that triggered this console message
* `sourceId` String
Fired when the guest window logs a console message.

View File

@@ -1,3 +0,0 @@
# Electron App Feedback Program
Electron is working on building a streamlined release process and having faster releases. To help with that, we have the App Feedback Program for large-scale Electron apps to test our beta releases and report app-specific issues to the Electron team. We use this program to help us prioritize work and get applications upgraded to the next stable release as soon as possible. There are a few requirements we expect from participants, such as attending short, online weekly check-ins. Please visit the [blog post](https://electronjs.org/blog/app-feedback-program) for details and sign-up.

View File

@@ -136,11 +136,11 @@ auto_filenames = {
sandbox_bundle_deps = [
"lib/browser/api/module-names.ts",
"lib/common/api/clipboard.js",
"lib/common/api/clipboard.ts",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/type-utils.ts",
@@ -189,15 +189,15 @@ auto_filenames = {
"lib/browser/api/auto-updater/auto-updater-native.js",
"lib/browser/api/auto-updater/auto-updater-win.js",
"lib/browser/api/auto-updater/squirrel-update-win.js",
"lib/browser/api/browser-view.js",
"lib/browser/api/browser-view.ts",
"lib/browser/api/browser-window.js",
"lib/browser/api/content-tracing.js",
"lib/browser/api/content-tracing.ts",
"lib/browser/api/crash-reporter.ts",
"lib/browser/api/desktop-capturer.ts",
"lib/browser/api/dialog.js",
"lib/browser/api/exports/electron.ts",
"lib/browser/api/global-shortcut.js",
"lib/browser/api/in-app-purchase.js",
"lib/browser/api/global-shortcut.ts",
"lib/browser/api/in-app-purchase.ts",
"lib/browser/api/ipc-main.ts",
"lib/browser/api/menu-item-roles.js",
"lib/browser/api/menu-item.js",
@@ -208,19 +208,19 @@ auto_filenames = {
"lib/browser/api/native-theme.ts",
"lib/browser/api/net-log.js",
"lib/browser/api/net.ts",
"lib/browser/api/notification.js",
"lib/browser/api/notification.ts",
"lib/browser/api/power-monitor.ts",
"lib/browser/api/power-save-blocker.js",
"lib/browser/api/power-save-blocker.ts",
"lib/browser/api/protocol.ts",
"lib/browser/api/screen.ts",
"lib/browser/api/session.js",
"lib/browser/api/session.ts",
"lib/browser/api/system-preferences.ts",
"lib/browser/api/top-level-window.js",
"lib/browser/api/touch-bar.js",
"lib/browser/api/tray.js",
"lib/browser/api/view.js",
"lib/browser/api/views/image-view.js",
"lib/browser/api/web-contents-view.js",
"lib/browser/api/tray.ts",
"lib/browser/api/view.ts",
"lib/browser/api/views/image-view.ts",
"lib/browser/api/web-contents-view.ts",
"lib/browser/api/web-contents.js",
"lib/browser/chrome-extension-shim.js",
"lib/browser/default-menu.ts",
@@ -238,11 +238,11 @@ auto_filenames = {
"lib/browser/remote/server.ts",
"lib/browser/rpc-server.js",
"lib/browser/utils.ts",
"lib/common/api/clipboard.js",
"lib/common/api/clipboard.ts",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/init.ts",
@@ -260,11 +260,11 @@ auto_filenames = {
renderer_bundle_deps = [
"lib/browser/api/module-names.ts",
"lib/common/api/clipboard.js",
"lib/common/api/clipboard.ts",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/init.ts",
@@ -302,11 +302,11 @@ auto_filenames = {
worker_bundle_deps = [
"lib/browser/api/module-names.ts",
"lib/common/api/clipboard.js",
"lib/common/api/clipboard.ts",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/init.ts",

View File

@@ -1,11 +1,10 @@
'use strict';
import { EventEmitter } from 'events';
const { EventEmitter } = require('events');
const { BrowserView } = process.electronBinding('browser_view');
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype);
BrowserView.fromWebContents = (webContents) => {
BrowserView.fromWebContents = (webContents: Electron.WebContents) => {
for (const view of BrowserView.getAllViews()) {
if (view.webContents.equal(webContents)) return view;
}
@@ -13,4 +12,4 @@ BrowserView.fromWebContents = (webContents) => {
return null;
};
module.exports = BrowserView;
export default BrowserView;

View File

@@ -1,2 +0,0 @@
'use strict';
module.exports = process.electronBinding('content_tracing');

View File

@@ -0,0 +1 @@
export default process.electronBinding('content_tracing');

View File

@@ -1,3 +0,0 @@
'use strict';
module.exports = process.electronBinding('global_shortcut').globalShortcut;

View File

@@ -0,0 +1 @@
export default process.electronBinding('global_shortcut').globalShortcut;

View File

@@ -1,22 +1,24 @@
'use strict';
import { EventEmitter } from 'events';
const { deprecate } = require('electron');
let _inAppPurchase;
if (process.platform === 'darwin') {
const { EventEmitter } = require('events');
const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase');
// inAppPurchase is an EventEmitter.
Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype);
EventEmitter.call(inAppPurchase);
module.exports = inAppPurchase;
_inAppPurchase = inAppPurchase;
} else {
module.exports = {
purchaseProduct: (productID, quantity, callback) => {
_inAppPurchase = new EventEmitter();
Object.assign(_inAppPurchase, {
purchaseProduct: () => {
throw new Error('The inAppPurchase module can only be used on macOS');
},
canMakePayments: () => false,
getReceiptURL: () => ''
};
});
}
export default _inAppPurchase;

View File

@@ -1,7 +0,0 @@
'use strict';
const { Notification, isSupported } = process.electronBinding('notification');
Notification.isSupported = isSupported;
module.exports = Notification;

View File

@@ -0,0 +1,5 @@
const { Notification: ElectronNotification, isSupported } = process.electronBinding('notification');
ElectronNotification.isSupported = isSupported;
export default ElectronNotification;

View File

@@ -1,3 +0,0 @@
'use strict';
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker;

View File

@@ -0,0 +1 @@
export default process.electronBinding('power_save_blocker').powerSaveBlocker;

View File

@@ -1,17 +0,0 @@
'use strict';
const { EventEmitter } = require('events');
const { app, deprecate } = require('electron');
const { fromPartition } = process.electronBinding('session');
// Public API.
Object.defineProperties(exports, {
defaultSession: {
enumerable: true,
get () { return fromPartition(''); }
},
fromPartition: {
enumerable: true,
value: fromPartition
}
});

View File

@@ -0,0 +1,8 @@
const { fromPartition } = process.electronBinding('session');
export default {
fromPartition,
get defaultSession () {
return fromPartition('');
}
};

View File

@@ -1,5 +1,3 @@
'use strict';
const { Tray } = process.electronBinding('tray');
module.exports = Tray;
export default Tray;

View File

@@ -1,5 +1,3 @@
'use strict';
const { View } = process.electronBinding('view');
module.exports = View;
export default View;

View File

@@ -1,8 +1,7 @@
const electron = require('electron');
import { View } from 'electron';
const { View } = electron;
const { ImageView } = process.electronBinding('image_view');
Object.setPrototypeOf(ImageView.prototype, View.prototype);
module.exports = ImageView;
export default ImageView;

View File

@@ -1,10 +1,7 @@
'use strict';
import { View } from 'electron';
const electron = require('electron');
const { View } = electron;
const { WebContentsView } = process.electronBinding('web_contents_view');
Object.setPrototypeOf(WebContentsView.prototype, View.prototype);
module.exports = WebContentsView;
export default WebContentsView;

View File

@@ -4,13 +4,12 @@ import * as electron from 'electron';
import { EventEmitter } from 'events';
import objectsRegistry from './objects-registry';
import { ipcMainInternal } from '../ipc-main-internal';
import { isPromise, isSerializableObject, deserialize, serialize } from '@electron/internal/common/type-utils';
import { isPromise, isSerializableObject, deserialize } from '@electron/internal/common/type-utils';
import { Size } from 'electron/main';
const v8Util = process.electronBinding('v8_util');
const eventBinding = process.electronBinding('event');
const features = process.electronBinding('features');
const { NativeImage } = process.electronBinding('native_image');
if (!features.isRemoteModuleEnabled()) {
throw new Error('remote module is disabled');
@@ -115,9 +114,6 @@ type MetaType = {
} | {
type: 'promise',
then: MetaType
} | {
type: 'nativeimage'
value: electron.NativeImage
}
// Convert a real value into meta data.
@@ -128,8 +124,6 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
// Recognize certain types of objects.
if (value instanceof Buffer) {
type = 'buffer';
} else if (value instanceof NativeImage) {
type = 'nativeimage';
} else if (Array.isArray(value)) {
type = 'array';
} else if (value instanceof Error) {
@@ -153,8 +147,6 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
type,
members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
};
} else if (type === 'nativeimage') {
return { type, value: serialize(value) };
} else if (type === 'object' || type === 'function') {
return {
type,

View File

@@ -1,13 +1,11 @@
'use strict';
const clipboard = process.electronBinding('clipboard');
if (process.type === 'renderer') {
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const typeUtils = require('@electron/internal/common/type-utils');
const makeRemoteMethod = function (method) {
return (...args) => {
const makeRemoteMethod = function (method: keyof Electron.Clipboard) {
return (...args: any[]) => {
args = typeUtils.serialize(args);
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD_SYNC', method, ...args);
return typeUtils.deserialize(result);
@@ -16,7 +14,7 @@ if (process.type === 'renderer') {
if (process.platform === 'linux') {
// On Linux we could not access clipboard in renderer process.
for (const method of Object.keys(clipboard)) {
for (const method of Object.keys(clipboard) as (keyof Electron.Clipboard)[]) {
clipboard[method] = makeRemoteMethod(method);
}
} else if (process.platform === 'darwin') {
@@ -26,4 +24,4 @@ if (process.type === 'renderer') {
}
}
module.exports = clipboard;
export default clipboard;

View File

@@ -1,5 +1,3 @@
'use strict';
const { nativeImage } = process.electronBinding('native_image');
module.exports = nativeImage;
export default nativeImage;

View File

@@ -1,3 +0,0 @@
'use strict';
module.exports = process.electronBinding('shell');

1
lib/common/api/shell.ts Normal file
View File

@@ -0,0 +1 @@
export default process.electronBinding('shell');

View File

@@ -5,7 +5,7 @@ const { hasSwitch } = process.electronBinding('command_line');
const { NativeImage } = process.electronBinding('native_image');
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry');
const { isPromise, isSerializableObject, serialize, deserialize } = require('@electron/internal/common/type-utils');
const { isPromise, isSerializableObject, serialize } = require('@electron/internal/common/type-utils');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const callbacksRegistry = new CallbacksRegistry();
@@ -219,7 +219,6 @@ function metaToValue (meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
nativeimage: () => deserialize(meta.value),
buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
error: () => metaToError(meta),

View File

@@ -84,9 +84,6 @@ const appPath = parseOption('app-path', null);
const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value));
const openerId = parseOption('opener-id', null, value => parseInt(value));
// The arguments to be passed to isolated world.
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled };
// The webContents preload script is loaded after the session preload scripts.
if (preloadScript) {
preloadScripts.push(preloadScript);
@@ -116,11 +113,6 @@ if (process.isMainFrame) {
webViewInit(contextIsolation, webviewTag, guestInstanceId);
}
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs);
}
if (nodeIntegration) {
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/cjs/helpers') // eslint-disable-line

View File

@@ -125,11 +125,7 @@ class LocationProxy {
}
private getGuestURL (): URL | null {
const maybeURL = this._invokeWebContentsMethodSync('getURL') as string;
// When there's no previous frame the url will be blank, so accountfor that here
// to prevent url parsing errors on an empty string.
const urlString = maybeURL !== '' ? maybeURL : 'about:blank';
const urlString = this._invokeWebContentsMethodSync('getURL') as string;
try {
return new URL(urlString);
} catch (e) {

View File

@@ -124,9 +124,6 @@ const isHiddenPage = hasSwitch('hidden-page');
const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides');
const usesNativeWindowOpen = true;
// The arguments to be passed to isolated world.
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled };
switch (window.location.protocol) {
case 'devtools:': {
// Override some inspector APIs.
@@ -152,11 +149,6 @@ if (process.isMainFrame) {
webViewInit(contextIsolation, isWebViewTagEnabled, guestInstanceId);
}
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs);
}
// Wrap the script into a function executed in global scope. It won't have
// access to the current scope, so we'll expose a few objects as arguments:
//

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "10.0.0-beta.2",
"version": "11.0.0-nightly.20200525",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -3,8 +3,17 @@ From: Jeremy Apthorp <jeremya@chromium.org>
Date: Wed, 10 Oct 2018 15:07:34 -0700
Subject: command-ismediakey.patch
Override MediaKeysListener::IsMediaKeycode and associated functions to also listen for
Volume Up, Volume Down, and Mute.
Override MediaKeysListener::IsMediaKeycode to also listen for Volume Up, Volume Down,
and Mute. We also need to patch out Chromium's usage of RemoteCommandCenterDelegate, as
it uses MPRemoteCommandCenter. MPRemoteCommandCenter makes it such that GlobalShortcuts
in Electron will not work as intended, because by design an app does not receive remote
control events until it begins playing audio. This means that a media shortcut would not kick
into effect until you, for example, began playing a YouTube video which sort of defeats the
purpose of GlobalShortcuts.
At the moment there is no upstream possibility for this; but perhaps Chromium may
consider some kind of switch, enabled by default, which would conditionally choose to avoid usage of
RemoteCommandCenterDelegate on macOS.
Also apply electron/electron@0f67b1866a9f00b852370e721affa4efda623f3a
and electron/electron@d2368d2d3b3de9eec4cc32b6aaf035cc89921bf1 as
@@ -86,19 +95,3 @@ index 85378bb565de617b1bd611d28c8714361747a357..36de4c0b0353be2418dacd388e92d7c3
return event;
}
diff --git a/ui/base/accelerators/system_media_controls_media_keys_listener.cc b/ui/base/accelerators/system_media_controls_media_keys_listener.cc
index 9d6084ceaccfd071549e63e3015f55ef292312ec..3f6af8b1b49bf0f226e9336c222884b07bf69e55 100644
--- a/ui/base/accelerators/system_media_controls_media_keys_listener.cc
+++ b/ui/base/accelerators/system_media_controls_media_keys_listener.cc
@@ -65,6 +65,11 @@ bool SystemMediaControlsMediaKeysListener::StartWatchingMediaKey(
case VKEY_MEDIA_STOP:
service_->SetIsStopEnabled(true);
break;
+ case VKEY_VOLUME_DOWN:
+ case VKEY_VOLUME_UP:
+ case VKEY_VOLUME_MUTE:
+ // Do nothing.
+ break;
default:
NOTREACHED();
}

View File

@@ -39,6 +39,12 @@ class GHKey {
this.repo = repo;
this.number = number;
}
static NewFromPull (pull) {
const owner = pull.base.repo.owner.login;
const repo = pull.base.repo.name;
const number = pull.number;
return new GHKey(owner, repo, number);
}
}
class Commit {
@@ -289,9 +295,33 @@ async function runRetryable (fn, maxRetries) {
if (lastError.status !== 404) throw lastError;
}
const getPullCacheFilename = ghKey => `${ghKey.owner}-${ghKey.repo}-pull-${ghKey.number}`;
const getCommitPulls = async (owner, repo, hash) => {
const name = `${owner}-${repo}-commit-${hash}`;
const retryableFunc = () => octokit.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: hash });
const ret = await checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
// only merged pulls belong in release notes
if (ret && ret.data) {
ret.data = ret.data.filter(pull => pull.merged_at);
}
// cache the pulls
if (ret && ret.data) {
for (const pull of ret.data) {
const cachefile = getPullCacheFilename(GHKey.NewFromPull(pull));
const payload = { ...ret, data: pull };
await checkCache(cachefile, () => payload);
}
}
return ret;
};
const getPullRequest = async (ghKey) => {
const { number, owner, repo } = ghKey;
const name = `${owner}-${repo}-pull-${number}`;
const name = getPullCacheFilename(ghKey);
const retryableFunc = () => octokit.pulls.get({ pull_number: number, owner, repo });
return checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
};
@@ -306,10 +336,20 @@ const getComments = async (ghKey) => {
const addRepoToPool = async (pool, repo, from, to) => {
const commonAncestor = await getCommonAncestor(repo.dir, from, to);
// add the commits
const oldHashes = await getLocalCommitHashes(repo.dir, from);
oldHashes.forEach(hash => { pool.processedHashes.add(hash); });
// mark the old branch's commits as old news
for (const oldHash of await getLocalCommitHashes(repo.dir, from)) {
pool.processedHashes.add(oldHash);
}
// get the new branch's commits and the pulls associated with them
const commits = await getLocalCommits(repo, commonAncestor, to);
for (const commit of commits) {
const { owner, repo, hash } = commit;
for (const pull of (await getCommitPulls(owner, repo, hash)).data) {
commit.prKeys.add(GHKey.NewFromPull(pull));
}
}
pool.commits.push(...commits);
// add the pulls
@@ -317,8 +357,7 @@ const addRepoToPool = async (pool, repo, from, to) => {
let prKey;
for (prKey of commit.prKeys.values()) {
const pull = await getPullRequest(prKey);
if (!pull || !pull.data) break; // couldn't get it
if (pool.pulls[prKey.number]) break; // already have it
if (!pull || !pull.data) continue; // couldn't get it
pool.pulls[prKey.number] = pull;
parsePullText(pull, commit);
}

View File

@@ -266,9 +266,6 @@ void BrowserWindow::OnCloseButtonClicked(bool* prevent_default) {
// Already closed by renderer
return;
// Required to make beforeunload handler work.
api_web_contents_->NotifyUserActivation();
if (web_contents()->NeedToFireBeforeUnloadOrUnload())
web_contents()->DispatchBeforeUnload(false /* auto_cancel */);
else

View File

@@ -29,9 +29,7 @@ bool RegisteringMediaKeyForUntrustedClient(const ui::Accelerator& accelerator) {
if (base::mac::IsAtLeastOS10_14()) {
constexpr ui::KeyboardCode mediaKeys[] = {
ui::VKEY_MEDIA_PLAY_PAUSE, ui::VKEY_MEDIA_NEXT_TRACK,
ui::VKEY_MEDIA_PREV_TRACK, ui::VKEY_MEDIA_STOP,
ui::VKEY_VOLUME_UP, ui::VKEY_VOLUME_DOWN,
ui::VKEY_VOLUME_MUTE};
ui::VKEY_MEDIA_PREV_TRACK, ui::VKEY_MEDIA_STOP};
if (std::find(std::begin(mediaKeys), std::end(mediaKeys),
accelerator.key_code()) != std::end(mediaKeys)) {
@@ -62,7 +60,7 @@ GlobalShortcut::~GlobalShortcut() {
void GlobalShortcut::OnKeyPressed(const ui::Accelerator& accelerator) {
if (accelerator_callback_map_.find(accelerator) ==
accelerator_callback_map_.end()) {
// This should never occur, because if it does, GlobalShortcutListener
// This should never occur, because if it does, GlobalGlobalShortcutListener
// notifies us with wrong accelerator.
NOTREACHED();
return;

View File

@@ -756,8 +756,6 @@ void WebContents::BeforeUnloadFired(content::WebContents* tab,
*proceed_to_fire_unload = proceed;
else
*proceed_to_fire_unload = true;
// Note that Chromium does not emit this for navigations.
Emit("before-unload-fired", proceed);
}
void WebContents::SetContentsBounds(content::WebContents* source,
@@ -1548,9 +1546,6 @@ void WebContents::LoadURL(const GURL& url,
// Calling LoadURLWithParams() can trigger JS which destroys |this|.
auto weak_this = GetWeakPtr();
// Required to make beforeunload handler work.
NotifyUserActivation();
params.transition_type = ui::PAGE_TRANSITION_TYPED;
params.should_clear_history_list = true;
params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE;
@@ -2680,15 +2675,6 @@ void WebContents::GrantOriginAccess(const GURL& url) {
url::Origin::Create(url));
}
void WebContents::NotifyUserActivation() {
auto* frame = web_contents()->GetMainFrame();
if (!frame)
return;
mojo::AssociatedRemote<mojom::ElectronRenderer> renderer;
frame->GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
renderer->NotifyUserActivation();
}
v8::Local<v8::Promise> WebContents::TakeHeapSnapshot(
const base::FilePath& file_path) {
gin_helper::Promise<void> promise(isolate());

View File

@@ -367,9 +367,6 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
// the specified URL.
void GrantOriginAccess(const GURL& url);
// Notifies the web page that there is user interaction.
void NotifyUserActivation();
v8::Local<v8::Promise> TakeHeapSnapshot(const base::FilePath& file_path);
// Properties.

View File

@@ -1454,27 +1454,25 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
[effect_view setState:NSVisualEffectStateActive];
// Make frameless Vibrant windows have rounded corners.
if (!has_frame()) {
CGFloat radius = 5.0f; // default corner radius
CGFloat dimension = 2 * radius + 1;
NSSize size = NSMakeSize(dimension, dimension);
NSImage* maskImage = [NSImage imageWithSize:size
flipped:NO
drawingHandler:^BOOL(NSRect rect) {
NSBezierPath* bezierPath = [NSBezierPath
bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[[NSColor blackColor] set];
[bezierPath fill];
return YES;
}];
[maskImage setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
[maskImage setResizingMode:NSImageResizingModeStretch];
CGFloat radius = 5.0f; // default corner radius
CGFloat dimension = 2 * radius + 1;
NSSize size = NSMakeSize(dimension, dimension);
NSImage* maskImage = [NSImage imageWithSize:size
flipped:NO
drawingHandler:^BOOL(NSRect rect) {
NSBezierPath* bezierPath = [NSBezierPath
bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[[NSColor blackColor] set];
[bezierPath fill];
return YES;
}];
[maskImage setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
[maskImage setResizingMode:NSImageResizingModeStretch];
[effect_view setMaskImage:maskImage];
[window_ setCornerMask:maskImage];
}
[effect_view setMaskImage:maskImage];
[window_ setCornerMask:maskImage];
[[window_ contentView] addSubview:effect_view
positioned:NSWindowBelow

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 10,0,0,2
PRODUCTVERSION 10,0,0,2
FILEVERSION 11,0,0,20200525
PRODUCTVERSION 11,0,0,20200525
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron"
VALUE "FileVersion", "10.0.0"
VALUE "FileVersion", "11.0.0"
VALUE "InternalName", "electron.exe"
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "electron.exe"
VALUE "ProductName", "Electron"
VALUE "ProductVersion", "10.0.0"
VALUE "ProductVersion", "11.0.0"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -10,7 +10,6 @@
#include "base/callback.h"
#include "base/mac/scoped_nsobject.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
namespace electron {
@@ -25,13 +24,15 @@ class ElectronMenuModel;
// as it only maintains weak references.
@interface ElectronMenuController : NSObject <NSMenuDelegate> {
@protected
base::WeakPtr<electron::ElectronMenuModel> model_;
electron::ElectronMenuModel* model_; // weak
base::scoped_nsobject<NSMenu> menu_;
BOOL isMenuOpen_;
BOOL useDefaultAccelerator_;
base::OnceClosure closeCallback;
}
@property(nonatomic, assign) electron::ElectronMenuModel* model;
// Builds a NSMenu from the pre-built model (must not be nil). Changes made
// to the contents of the model after calling this will not be noticed.
- (id)initWithModel:(electron::ElectronMenuModel*)model
@@ -45,9 +46,6 @@ class ElectronMenuModel;
// Programmatically close the constructed menu.
- (void)cancel;
- (electron::ElectronMenuModel*)model;
- (void)setModel:(electron::ElectronMenuModel*)model;
// Access to the constructed menu if the complex initializer was used. If the
// default initializer was used, then this will create the menu on first call.
- (NSMenu*)menu;

View File

@@ -8,7 +8,6 @@
#include <utility>
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
@@ -88,44 +87,6 @@ NSMenu* MakeEmptySubmenu() {
} // namespace
// This class stores a base::WeakPtr<electron::ElectronMenuModel> as an
// Objective-C object, which allows it to be stored in the representedObject
// field of an NSMenuItem.
@interface WeakPtrToElectronMenuModelAsNSObject : NSObject
+ (instancetype)weakPtrForModel:(electron::ElectronMenuModel*)model;
+ (electron::ElectronMenuModel*)getFrom:(id)instance;
- (instancetype)initWithModel:(electron::ElectronMenuModel*)model;
- (electron::ElectronMenuModel*)menuModel;
@end
@implementation WeakPtrToElectronMenuModelAsNSObject {
base::WeakPtr<electron::ElectronMenuModel> _model;
}
+ (instancetype)weakPtrForModel:(electron::ElectronMenuModel*)model {
return [[[WeakPtrToElectronMenuModelAsNSObject alloc] initWithModel:model]
autorelease];
}
+ (electron::ElectronMenuModel*)getFrom:(id)instance {
return
[base::mac::ObjCCastStrict<WeakPtrToElectronMenuModelAsNSObject>(instance)
menuModel];
}
- (instancetype)initWithModel:(electron::ElectronMenuModel*)model {
if ((self = [super init])) {
_model = model->GetWeakPtr();
}
return self;
}
- (electron::ElectronMenuModel*)menuModel {
return _model.get();
}
@end
// Menu item is located for ease of removing it from the parent owner
static base::scoped_nsobject<NSMenuItem> recentDocumentsMenuItem_;
@@ -134,18 +95,12 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
@implementation ElectronMenuController
- (electron::ElectronMenuModel*)model {
return model_.get();
}
@synthesize model = model_;
- (void)setModel:(electron::ElectronMenuModel*)model {
model_ = model->GetWeakPtr();
}
- (instancetype)initWithModel:(electron::ElectronMenuModel*)model
useDefaultAccelerator:(BOOL)use {
- (id)initWithModel:(electron::ElectronMenuModel*)model
useDefaultAccelerator:(BOOL)use {
if ((self = [super init])) {
model_ = model->GetWeakPtr();
model_ = model;
isMenuOpen_ = NO;
useDefaultAccelerator_ = use;
[self menu];
@@ -160,7 +115,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// while its context menu is still open.
[self cancel];
model_ = nullptr;
model_ = nil;
[super dealloc];
}
@@ -181,7 +137,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
itemWithTitle:@"Electron"] submenu] itemWithTitle:openTitle] retain]);
}
model_ = model->GetWeakPtr();
model_ = model;
[menu_ removeAllItems];
const int count = model->GetItemCount();
@@ -197,8 +153,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
if (isMenuOpen_) {
[menu_ cancelTracking];
isMenuOpen_ = NO;
if (model_)
model_->MenuWillClose();
model_->MenuWillClose();
if (!closeCallback.is_null()) {
base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback));
}
@@ -335,8 +290,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// model. Setting the target to |self| allows this class to participate
// in validation of the menu items.
[item setTag:index];
[item setRepresentedObject:[WeakPtrToElectronMenuModelAsNSObject
weakPtrForModel:model]];
NSValue* modelObject = [NSValue valueWithPointer:model];
[item setRepresentedObject:modelObject]; // Retains |modelObject|.
ui::Accelerator accelerator;
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
&accelerator)) {
@@ -376,8 +331,9 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
return NO;
NSInteger modelIndex = [item tag];
electron::ElectronMenuModel* model = [WeakPtrToElectronMenuModelAsNSObject
getFrom:[(id)item representedObject]];
electron::ElectronMenuModel* model =
static_cast<electron::ElectronMenuModel*>(
[[(id)item representedObject] pointerValue]);
DCHECK(model);
if (model) {
BOOL checked = model->IsItemCheckedAt(modelIndex);
@@ -396,7 +352,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
- (void)itemSelected:(id)sender {
NSInteger modelIndex = [sender tag];
electron::ElectronMenuModel* model =
[WeakPtrToElectronMenuModelAsNSObject getFrom:[sender representedObject]];
static_cast<electron::ElectronMenuModel*>(
[[sender representedObject] pointerValue]);
DCHECK(model);
if (model) {
NSEvent* event = [NSApp currentEvent];
@@ -412,7 +369,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
[menu_ setDelegate:self];
if (model_)
[self populateWithModel:model_.get()];
[self populateWithModel:model_];
return menu_.get();
}
@@ -422,8 +379,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
- (void)menuWillOpen:(NSMenu*)menu {
isMenuOpen_ = YES;
if (model_)
model_->MenuWillShow();
model_->MenuWillShow();
}
- (void)menuDidClose:(NSMenu*)menu {

View File

@@ -7,7 +7,6 @@
#include <map>
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "ui/base/models/simple_menu_model.h"
@@ -70,10 +69,6 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
void MenuWillClose() override;
void MenuWillShow() override;
base::WeakPtr<ElectronMenuModel> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
using SimpleMenuModel::GetSubmenuModelAt;
ElectronMenuModel* GetSubmenuModelAt(int index);
@@ -85,8 +80,6 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
std::map<int, base::string16> sublabels_; // command id -> sublabel
base::ObserverList<Observer> observers_;
base::WeakPtrFactory<ElectronMenuModel> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ElectronMenuModel);
};

View File

@@ -22,8 +22,6 @@ interface ElectronRenderer {
string context_id,
int32 object_id);
NotifyUserActivation();
TakeHeapSnapshot(handle file) => (bool success);
};

View File

@@ -93,8 +93,8 @@ bool IsRunningInDesktopBridgeImpl() {
}
}
UINT32 length = PACKAGE_FAMILY_NAME_MAX_LENGTH;
wchar_t packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH];
UINT32 length;
wchar_t packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH + 1];
HANDLE proc = GetCurrentProcess();
LONG result =
(*get_package_family_namePtr)(proc, &length, packageFamilyName);

View File

@@ -236,12 +236,6 @@ void ElectronApiServiceImpl::DereferenceRemoteJSCallback(
}
#endif
void ElectronApiServiceImpl::NotifyUserActivation() {
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
if (frame)
frame->NotifyUserActivation();
}
void ElectronApiServiceImpl::TakeHeapSnapshot(
mojo::ScopedHandle file,
TakeHeapSnapshotCallback callback) {

View File

@@ -39,7 +39,6 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
void DereferenceRemoteJSCallback(const std::string& context_id,
int32_t object_id) override;
#endif
void NotifyUserActivation() override;
void TakeHeapSnapshot(mojo::ScopedHandle file,
TakeHeapSnapshotCallback callback) override;

View File

@@ -25,7 +25,6 @@ declare namespace Electron {
constructor(args: {show: boolean})
setContentView(view: View): void
}
class View {}
class WebContentsView {
constructor(options: BrowserWindowConstructorOptions)
}

View File

@@ -7,7 +7,7 @@ import * as http from 'http';
import { AddressInfo } from 'net';
import { app, BrowserWindow, BrowserView, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents } from 'electron/main';
import { emittedOnce, emittedUntil } from './events-helpers';
import { emittedOnce } from './events-helpers';
import { ifit, ifdescribe } from './spec-helpers';
import { closeWindow, closeAllWindows } from './window-helpers';
@@ -38,10 +38,6 @@ const expectBoundsEqual = (actual: any, expected: any) => {
}
};
const isBeforeUnload = (event: Event, level: number, message: string) => {
return (message === 'beforeunload');
};
describe('BrowserWindow module', () => {
describe('BrowserWindow constructor', () => {
it('allows passing void 0 as the webContents', async () => {
@@ -99,11 +95,16 @@ describe('BrowserWindow module', () => {
fs.unlinkSync(test);
expect(String(content)).to.equal('unload');
});
it('should emit beforeunload handler', async () => {
await w.loadFile(path.join(fixtures, 'api', 'beforeunload-false.html'));
const beforeunload = new Promise(resolve => {
ipcMain.once('onbeforeunload', (e) => {
e.returnValue = null;
resolve();
});
});
w.close();
await emittedOnce(w.webContents, 'before-unload-fired');
await beforeunload;
});
describe('when invoked synchronously inside navigation observer', () => {
@@ -184,11 +185,13 @@ describe('BrowserWindow module', () => {
fs.unlinkSync(test);
expect(content).to.equal('close');
});
it('should emit beforeunload event', async function () {
// TODO(nornagon): deflake this test.
this.retries(3);
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-false.html'));
w.webContents.executeJavaScript('window.close()', true);
await emittedOnce(w.webContents, 'before-unload-fired');
w.webContents.executeJavaScript('run()', true);
const [e] = await emittedOnce(ipcMain, 'onbeforeunload');
e.returnValue = null;
});
});
@@ -2626,31 +2629,32 @@ describe('BrowserWindow module', () => {
});
describe('beforeunload handler', function () {
// TODO(nornagon): I feel like these tests _oughtn't_ be flakey, but
// beforeunload is in general not reliable on the web, so i'm not going to
// worry about it too much for now.
this.retries(3);
let w: BrowserWindow = null as unknown as BrowserWindow;
beforeEach(() => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
});
afterEach(closeAllWindows);
it('returning undefined would not prevent close', async () => {
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-undefined.html'));
const wait = emittedOnce(w, 'closed');
w.close();
await wait;
afterEach(() => {
ipcMain.removeAllListeners('onbeforeunload');
});
afterEach(closeAllWindows);
it('returning undefined would not prevent close', (done) => {
w.once('closed', () => { done(); });
w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-undefined.html'));
});
it('returning false would prevent close', async () => {
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-false.html'));
w.close();
const [, proceed] = await emittedOnce(w.webContents, 'before-unload-fired');
expect(proceed).to.equal(false);
w.webContents.executeJavaScript('run()', true);
const [e] = await emittedOnce(ipcMain, 'onbeforeunload');
e.returnValue = null;
});
it('returning empty string would prevent close', async () => {
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-empty-string.html'));
w.close();
const [, proceed] = await emittedOnce(w.webContents, 'before-unload-fired');
expect(proceed).to.equal(false);
it('returning empty string would prevent close', (done) => {
ipcMain.once('onbeforeunload', (e) => { e.returnValue = null; done(); });
w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-empty-string.html'));
});
it('emits for each close attempt', async () => {
@@ -2659,16 +2663,46 @@ describe('BrowserWindow module', () => {
const destroyListener = () => { expect.fail('Close was not prevented'); };
w.webContents.once('destroyed', destroyListener);
await w.webContents.executeJavaScript('installBeforeUnload(2)', true);
w.close();
await emittedOnce(w.webContents, 'before-unload-fired');
w.close();
await emittedOnce(w.webContents, 'before-unload-fired');
await w.webContents.executeJavaScript('preventNextBeforeUnload()', true);
{
const p = emittedOnce(ipcMain, 'onbeforeunload');
w.close();
const [e] = await p;
e.returnValue = null;
}
await w.webContents.executeJavaScript('preventNextBeforeUnload()', true);
// Hi future test refactorer! I don't know what event this timeout allows
// to occur, but without it, this test becomes flaky at this point and
// sometimes the window gets closed even though a `beforeunload` handler
// has been installed. I looked for events being emitted by the
// `webContents` during this timeout period and found nothing, so it
// might be some sort of internal timeout being applied by the content/
// layer, or blink?
//
// In any case, this incantation reduces flakiness. I'm going to add a
// summoning circle for good measure.
//
// 🕯 🕯
// 🕯 🕯
// 🕯 🕯
await new Promise(resolve => setTimeout(resolve, 1000));
// 🕯 🕯
// 🕯 🕯
// 🕯 🕯
{
const p = emittedOnce(ipcMain, 'onbeforeunload');
w.close();
const [e] = await p;
e.returnValue = null;
}
w.webContents.removeListener('destroyed', destroyListener);
const wait = emittedOnce(w, 'closed');
const p = emittedOnce(w.webContents, 'destroyed');
w.close();
await wait;
await p;
});
it('emits for each reload attempt', async () => {
@@ -2677,14 +2711,19 @@ describe('BrowserWindow module', () => {
const navigationListener = () => { expect.fail('Reload was not prevented'); };
w.webContents.once('did-start-navigation', navigationListener);
await w.webContents.executeJavaScript('installBeforeUnload(2)', true);
await w.webContents.executeJavaScript('preventNextBeforeUnload()', true);
w.reload();
// Chromium does not emit 'before-unload-fired' on WebContents for
// navigations, so we have to use other ways to know if beforeunload
// is fired.
await emittedUntil(w.webContents, 'console-message', isBeforeUnload);
{
const [e] = await emittedOnce(ipcMain, 'onbeforeunload');
e.returnValue = null;
}
await w.webContents.executeJavaScript('preventNextBeforeUnload()', true);
w.reload();
await emittedUntil(w.webContents, 'console-message', isBeforeUnload);
{
const [e] = await emittedOnce(ipcMain, 'onbeforeunload');
e.returnValue = null;
}
w.webContents.removeListener('did-start-navigation', navigationListener);
w.reload();
@@ -2697,14 +2736,19 @@ describe('BrowserWindow module', () => {
const navigationListener = () => { expect.fail('Reload was not prevented'); };
w.webContents.once('did-start-navigation', navigationListener);
await w.webContents.executeJavaScript('installBeforeUnload(2)', true);
await w.webContents.executeJavaScript('preventNextBeforeUnload()', true);
w.loadURL('about:blank');
// Chromium does not emit 'before-unload-fired' on WebContents for
// navigations, so we have to use other ways to know if beforeunload
// is fired.
await emittedUntil(w.webContents, 'console-message', isBeforeUnload);
{
const [e] = await emittedOnce(ipcMain, 'onbeforeunload');
e.returnValue = null;
}
await w.webContents.executeJavaScript('preventNextBeforeUnload()', true);
w.loadURL('about:blank');
await emittedUntil(w.webContents, 'console-message', isBeforeUnload);
{
const [e] = await emittedOnce(ipcMain, 'onbeforeunload');
e.returnValue = null;
}
w.webContents.removeListener('did-start-navigation', navigationListener);
w.loadURL('about:blank');
@@ -4128,7 +4172,7 @@ describe('BrowserWindow module', () => {
window.postMessage({openedLocation}, '*')
`);
const [, data] = await p;
expect(data.pageContext.openedLocation).to.equal('about:blank');
expect(data.pageContext.openedLocation).to.equal('');
});
});

View File

@@ -38,21 +38,4 @@ ifdescribe(process.platform !== 'win32')('globalShortcut module', () => {
expect(globalShortcut.isRegistered(accelerators[0])).to.be.false('first unregistered');
expect(globalShortcut.isRegistered(accelerators[1])).to.be.false('second unregistered');
});
it('does not crash when registering media keys as global shortcuts', () => {
const accelerators = [
'VolumeUp',
'VolumeDown',
'VolumeMute',
'MediaNextTrack',
'MediaPreviousTrack',
'MediaStop', 'MediaPlayPause'
];
expect(() => {
globalShortcut.registerAll(accelerators, () => {});
}).to.not.throw();
globalShortcut.unregisterAll();
});
});

View File

@@ -10,6 +10,7 @@ import { closeWindow } from './window-helpers';
const fixturesPath = path.resolve(__dirname, 'fixtures');
describe('Menu module', function () {
this.timeout(5000);
describe('Menu.buildFromTemplate', () => {
it('should be able to attach extra fields', () => {
const menu = Menu.buildFromTemplate([
@@ -884,14 +885,9 @@ describe('Menu module', function () {
const appProcess = cp.spawn(process.execPath, [appPath]);
let output = '';
await new Promise((resolve) => {
appProcess.stdout.on('data', data => {
output += data;
if (data.indexOf('Window has') > -1) {
resolve();
}
});
});
appProcess.stdout.on('data', data => { output += data; });
await emittedOnce(appProcess, 'exit');
expect(output).to.include('Window has no menu');
});

View File

@@ -366,11 +366,11 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
const w = makeWindow();
const remotely = makeRemotely(w);
it('can serialize an empty nativeImage from renderer to main', async () => {
const getImageEmpty = (img: NativeImage) => img.isEmpty();
it('can serialize an empty nativeImage', async () => {
const getEmptyImage = (img: NativeImage) => img.isEmpty();
w().webContents.once('remote-get-global', (event) => {
event.returnValue = getImageEmpty;
event.returnValue = getEmptyImage;
});
await expect(remotely(() => {
@@ -379,23 +379,11 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
})).to.eventually.be.true();
});
it('can serialize an empty nativeImage from main to renderer', async () => {
w().webContents.once('remote-get-global', (event) => {
const emptyImage = require('electron').nativeImage.createEmpty();
event.returnValue = emptyImage;
});
await expect(remotely(() => {
const image = require('electron').remote.getGlobal('someFunction');
return image.isEmpty();
})).to.eventually.be.true();
});
it('can serialize a non-empty nativeImage from renderer to main', async () => {
const getImageSize = (img: NativeImage) => img.getSize();
it('can serialize a non-empty nativeImage', async () => {
const getNonEmptyImage = (img: NativeImage) => img.getSize();
w().webContents.once('remote-get-global', (event) => {
event.returnValue = getImageSize;
event.returnValue = getNonEmptyImage;
});
await expect(remotely(() => {
@@ -405,18 +393,6 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
})).to.eventually.deep.equal({ width: 2, height: 2 });
});
it('can serialize a non-empty nativeImage from main to renderer', async () => {
w().webContents.once('remote-get-global', (event) => {
const nonEmptyImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg==');
event.returnValue = nonEmptyImage;
});
await expect(remotely(() => {
const image = require('electron').remote.getGlobal('someFunction');
return image.getSize();
})).to.eventually.deep.equal({ width: 2, height: 2 });
});
it('can properly create a menu with an nativeImage icon in the renderer', async () => {
await expect(remotely(() => {
const { remote, nativeImage } = require('electron');

View File

@@ -42,22 +42,23 @@ describe('webContents module', () => {
});
describe('will-prevent-unload event', function () {
// TODO(nornagon): de-flake this properly
this.retries(3);
afterEach(closeAllWindows);
it('does not emit if beforeunload returns undefined', async () => {
it('does not emit if beforeunload returns undefined', (done) => {
const w = new BrowserWindow({ show: false });
w.once('closed', () => done());
w.webContents.once('will-prevent-unload', () => {
expect.fail('should not have fired');
});
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-undefined.html'));
const wait = emittedOnce(w, 'closed');
w.close();
await wait;
w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-undefined.html'));
});
it('emits if beforeunload returns false', async () => {
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-false.html'));
w.close();
w.webContents.executeJavaScript('run()', true);
await emittedOnce(w.webContents, 'will-prevent-unload');
});
@@ -65,9 +66,8 @@ describe('webContents module', () => {
const w = new BrowserWindow({ show: false });
w.webContents.once('will-prevent-unload', event => event.preventDefault());
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'close-beforeunload-false.html'));
const wait = emittedOnce(w, 'closed');
w.close();
await wait;
w.webContents.executeJavaScript('run()', true);
await emittedOnce(w, 'closed');
});
});

View File

@@ -1233,10 +1233,7 @@ describe('chromium features', () => {
w.loadURL(pdfSource);
const [, contents] = await emittedOnce(app, 'web-contents-created');
expect(contents.getURL()).to.equal('chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html');
await new Promise((resolve) => {
contents.on('did-finish-load', resolve);
contents.on('did-frame-finish-load', resolve);
});
await emittedOnce(contents, 'did-finish-load');
});
it('opens when loading a pdf resource in a iframe', async () => {
@@ -1244,10 +1241,7 @@ describe('chromium features', () => {
w.loadFile(path.join(__dirname, 'fixtures', 'pages', 'pdf-in-iframe.html'));
const [, contents] = await emittedOnce(app, 'web-contents-created');
expect(contents.getURL()).to.equal('chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html');
await new Promise((resolve) => {
contents.on('did-finish-load', resolve);
contents.on('did-frame-finish-load', resolve);
});
await emittedOnce(contents, 'did-finish-load');
});
});

View File

@@ -294,12 +294,11 @@ describe('chrome extensions', () => {
it('loads a devtools extension', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
customSession.loadExtension(path.join(fixtures, 'extensions', 'devtools-extension'));
const winningMessage = emittedOnce(ipcMain, 'winning');
const w = new BrowserWindow({ show: true, webPreferences: { session: customSession, nodeIntegration: true } });
await w.loadURL(url);
w.webContents.openDevTools();
showLastDevToolsPanel(w);
await winningMessage;
await emittedOnce(ipcMain, 'winning');
});
});

View File

@@ -1,14 +1,15 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
function installBeforeUnload(removeAfterNTimes) {
let count = 0
function preventNextBeforeUnload() {
window.addEventListener('beforeunload', function handler(e) {
setTimeout(() => console.log('beforeunload'))
if (++count <= removeAfterNTimes) {
e.preventDefault();
e.returnValue = '';
}
e.preventDefault();
e.returnValue = '';
window.removeEventListener('beforeunload', handler)
setTimeout(function() {
require('electron').ipcRenderer.sendSync('onbeforeunload')
}, 0);
return false;
})
}
</script>

View File

@@ -4,11 +4,16 @@
// Only prevent unload on the first window close
var unloadPrevented = false;
window.onbeforeunload = function() {
setTimeout(function() {
require('electron').ipcRenderer.sendSync('onbeforeunload');
}, 0);
if (!unloadPrevented) {
unloadPrevented = true;
return '';
}
}
window.onload = () => window.close();
</script>
</body>
</html>

View File

@@ -1,14 +1,23 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
// Only prevent unload on the first window close
var unloadPrevented = false;
window.onbeforeunload = function() {
if (!unloadPrevented) {
unloadPrevented = true;
console.log('prevent')
return false;
function run() {
// Only prevent unload on the first window close
var unloadPrevented = false;
window.onbeforeunload = function() {
setTimeout(function() {
require('electron').ipcRenderer.sendSync('onbeforeunload');
}, 0);
if (!unloadPrevented) {
unloadPrevented = true;
return false;
}
}
// unload events don't get run unless load events have run.
if (document.readyState === 'complete')
window.close()
else
window.onload = () => window.close()
}
</script>
</body>

View File

@@ -2,7 +2,11 @@
<body>
<script type="text/javascript" charset="utf-8">
window.onbeforeunload = function() {
setTimeout(function() {
require('electron').ipcRenderer.sendSync('onbeforeunload');
}, 0);
}
window.onload = () => window.close();
</script>
</body>
</html>

View File

@@ -37,7 +37,7 @@ protocol.registerSchemesAsPrivileged([
{ scheme: 'bar', privileges: { standard: true } }
]);
app.whenReady().then(async () => {
app.whenReady().then(() => {
require('ts-node/register');
const argv = require('yargs')
@@ -68,45 +68,53 @@ app.whenReady().then(async () => {
if (argv.grep) mocha.grep(argv.grep);
if (argv.invert) mocha.invert();
const filter = (file) => {
if (!/-spec\.[tj]s$/.test(file)) {
return false;
}
// This allows you to run specific modules only:
// npm run test -match=menu
const moduleMatch = process.env.npm_config_match
? new RegExp(process.env.npm_config_match, 'g')
: null;
if (moduleMatch && !moduleMatch.test(file)) {
return false;
}
const baseElectronDir = path.resolve(__dirname, '..');
if (argv.files && !argv.files.includes(path.relative(baseElectronDir, file))) {
return false;
}
return true;
};
const getFiles = require('../spec/static/get-files');
const testFiles = await getFiles(__dirname, { filter });
testFiles.sort().forEach((file) => {
mocha.addFile(file);
// Read all test files.
const walker = require('walkdir').walk(__dirname, {
no_recurse: true
});
const cb = () => {
// Ensure the callback is called after runner is defined
process.nextTick(() => {
process.exit(runner.failures);
// This allows you to run specific modules only:
// npm run test -match=menu
const moduleMatch = process.env.npm_config_match
? new RegExp(process.env.npm_config_match, 'g')
: null;
const testFiles = [];
walker.on('file', (file) => {
if (/-spec\.[tj]s$/.test(file) &&
(!moduleMatch || moduleMatch.test(file))) {
testFiles.push(file);
}
});
const baseElectronDir = path.resolve(__dirname, '..');
walker.on('end', () => {
testFiles.sort();
testFiles.forEach((file) => {
if (!argv.files || argv.files.includes(path.relative(baseElectronDir, file))) {
mocha.addFile(file);
}
});
};
const cb = () => {
// Ensure the callback is called after runner is defined
process.nextTick(() => {
process.exit(runner.failures);
});
};
// Set up chai in the correct order
const chai = require('chai');
chai.use(require('chai-as-promised'));
chai.use(require('dirty-chai'));
// Set up chai in the correct order
const chai = require('chai');
chai.use(require('chai-as-promised'));
chai.use(require('dirty-chai'));
const runner = mocha.run(cb);
const runner = mocha.run(cb);
});
});
function partition (xs, f) {
const trues = [];
const falses = [];
xs.forEach(x => (f(x) ? trues : falses).push(x));
return [trues, falses];
}

View File

@@ -4,6 +4,9 @@
// Only prevent unload on the first window close
var unloadPrevented = false;
window.onbeforeunload = function() {
setTimeout(function() {
require('electron').ipcRenderer.sendSync('onbeforeunload');
}, 0);
if (!unloadPrevented) {
unloadPrevented = true;
return false;

View File

@@ -1,15 +0,0 @@
async function getFiles (directoryPath, { filter = null } = {}) {
const files = [];
const walker = require('walkdir').walk(directoryPath, {
no_recurse: true
});
walker.on('file', (file) => {
if (!filter || filter(file)) {
files.push(file);
}
});
await new Promise((resolve) => walker.on('end', resolve));
return files;
}
module.exports = getFiles;

View File

@@ -1,7 +1,7 @@
<body>
<script src="jquery-2.0.3.min.js"></script>
<script type="text/javascript" charset="utf-8">
(async function() {
(function() {
// Deprecated APIs are still supported and should be tested.
process.throwDeprecation = false
@@ -49,45 +49,47 @@
if (query.grep) mocha.grep(query.grep)
if (query.invert) mocha.invert()
const filter = (file) => {
if (!/-spec\.js$/.test(file)) {
return false
}
const files = query.files ? query.files.split(',') : undefined
// This allows you to run specific modules only:
// npm run test -match=menu
const moduleMatch = process.env.npm_config_match
? new RegExp(process.env.npm_config_match, 'g')
: null
if (moduleMatch && !moduleMatch.test(file)) {
return false
}
const files = query.files ? query.files.split(',') : undefined
const baseElectronDir = path.resolve(__dirname, '..', '..')
if (files && !files.includes(path.relative(baseElectronDir, file))) {
return false
}
return true
}
const getFiles = require('./get-files')
const testFiles = await getFiles(path.dirname(__dirname), { filter })
testFiles.sort().forEach((file) => {
mocha.addFile(file)
// Read all test files.
const walker = require('walkdir').walk(path.dirname(__dirname), {
no_recurse: true
})
// Set up chai in the correct order
const chai = require('chai')
chai.use(require('chai-as-promised'))
chai.use(require('dirty-chai'))
// This allows you to run specific modules only:
// npm run test -match=menu
const moduleMatch = process.env.npm_config_match
? new RegExp(process.env.npm_config_match, 'g')
: null
const runner = mocha.run(() => {
// Ensure the callback is called after runner is defined
setTimeout(() => {
ipcRenderer.send('process.exit', runner.failures)
}, 0)
const testFiles = []
walker.on('file', (file) => {
if (/-spec\.js$/.test(file) && (!moduleMatch || moduleMatch.test(file))) {
testFiles.push(file)
}
})
const baseElectronDir = path.resolve(__dirname, '..', '..')
walker.on('end', () => {
testFiles.sort()
testFiles.forEach((file) => {
if (!files || files.includes(path.relative(baseElectronDir, file))) {
mocha.addFile(file)
}
})
// Set up chai in the correct order
const chai = require('chai')
chai.use(require('chai-as-promised'))
chai.use(require('dirty-chai'))
const runner = mocha.run(() => {
// Ensure the callback is called after runner is defined
setTimeout(() => {
ipcRenderer.send('process.exit', runner.failures)
}, 0)
})
})
})()
</script>

View File

@@ -87,6 +87,8 @@ declare namespace Electron {
namespace Main {
const deprecate: ElectronInternal.DeprecationUtil;
}
class View {}
}
declare namespace ElectronInternal {