Compare commits

..

48 Commits

Author SHA1 Message Date
Electron Bot
7d56ca7360 Bump v12.0.0-nightly.20200911 2020-09-11 07:31:17 -07:00
Jeremy Rose
cd455c8b40 test: reduce flakiness in pdf test (#25407) 2020-09-10 16:47:36 -07:00
John Kleinschmidt
acf5d487d2 fix: crash on nativeImage on Windows on ARM (#25396)
* fix: nativeImage on WOA

* Remove old ldflag

* maybe this will work ¯\_(ツ)_/¯
2020-09-10 14:42:41 -04:00
Jeremy Rose
dd781c4f63 chore: deprecate remote (#25293) 2020-09-10 09:17:17 -07:00
Electron Bot
98683190b4 Bump v12.0.0-nightly.20200910 2020-09-10 07:32:25 -07:00
Samuel Maddock
1815b95e74 refactor(extensions): break load warnings into newlines (#25358) 2020-09-10 11:39:45 +09:00
Valentin Hăloiu
a829ae56b2 build: use python 2 shebang for git scripts (#25335) 2020-09-10 10:05:19 +09:00
Electron Bot
e6fbbf4325 Bump v12.0.0-nightly.20200909 2020-09-09 07:31:32 -07:00
Shelley Vohr
f6df79b927 fix: bind fake mojo service for badging (#25294)
* fix: bind fake mojo service for badging

* Add a test
2020-09-08 11:13:56 -04:00
Samuel Maddock
5a8046c994 fix(extensions): devtools now open for background pages (#22217)
refactor(extensions): remove unused InitWithBrowserContext method

fix(extensions): release background page WebContents to avoid crash

The background page WebContents instance is managed by the ExtensionHost.

fix(extensions): open background page devtools detached by default

test(extensions): add background page devtools test

chore: test fix for null web_contents()

fix: close background page devtools in test after opening
2020-09-08 20:55:40 +09:00
Erick Zhao
ae5776041e docs: full links to MD files in development README (#24963) 2020-09-08 20:37:47 +09:00
Erick Zhao
f7d9d68e14 docs: clarify a11y docs (#24973)
* docs: remove misleading sentence in a11y docs

* Update accessibility.md

* link
2020-09-08 20:37:00 +09:00
Jeremy Rose
2c5c51afb9 fix: update node certdata to NSS 3.56 (#25313) 2020-09-08 20:28:04 +09:00
Samuel Maddock
860e14c0da chore(extensions): remove old renderer code (#25347) 2020-09-08 20:11:38 +09:00
Electron Bot
31322400e7 Bump v12.0.0-nightly.20200907 2020-09-07 07:31:59 -07:00
Samuel Attard
4ad9bcb8b5 fix: handle electron script errors better (#25328) 2020-09-04 14:53:49 -07:00
Electron Bot
29c1248e96 Bump v12.0.0-nightly.20200904 2020-09-04 07:31:11 -07:00
Samuel Attard
a6b9f9d8e5 fix: only focus a webContents if the window was not initially hidden (#25292) 2020-09-04 15:57:29 +09:00
Jeremy Rose
d305fe7d30 docs: breaking changes for moveItemToTrash (#25286) 2020-09-03 12:44:16 -07:00
Jeremy Rose
4dc09ea9dc feat: [crashReporter] enable compression by default (#25288) 2020-09-03 12:43:58 -07:00
Charles Kerr
4484e95fc8 chore: graceful handling of notes with sub-lists (#25279)
* chore: graceful handling of notes with sub-lists

Handle multine release notes that contain their own bullet points.

Also, if a release note begins with a bullet point, remove it because it
will confuse the markdown parser to have two bullet points.
2020-09-03 14:42:48 -05:00
Robo
03e60cce8b fix: avoid creating client_id file for empty DIR_CRASH_DUMPS (#25296) 2020-09-03 11:52:43 -07:00
Electron Bot
733d56e908 Bump v12.0.0-nightly.20200903 2020-09-03 07:32:01 -07:00
Cheng Zhao
43485b8705 fix: multiple dock icons when calling dock.show/hide (#25269)
* fix: mulitple dock icons when calling dock.show/hide

* test: run dock.show tests after dock.hide tests
2020-09-03 20:46:24 +09:00
Jeremy Rose
bda6378685 chore: remove "no breaking changes" warning in PR template (#25285) 2020-09-02 11:56:10 -07:00
Jeremy Rose
1b6534b326 feat: add shell.trashItem() to replace shell.moveItemToTrash() (#25114) 2020-09-02 10:32:33 -07:00
Shelley Vohr
e9e7eee25e fix: provide asynchronous cleanup hooks in n-api (#25135) 2020-09-02 10:22:05 -07:00
Electron Bot
7e698df8f3 Bump v12.0.0-nightly.20200902 2020-09-02 07:32:22 -07:00
morgan-sam
e0611d0946 docs: setZoomLevel chromium zoom policy (#25105) 2020-09-02 10:06:58 +09:00
Samuel Attard
184e72fafd revert: "fix: reply notifs sometimes destroyed too early" (#25247)
* Revert "fix: reply notifs sometimes destroyed too early (#25086)"

This reverts commit bea6c9e4e1.

* nothing
2020-09-02 10:02:47 +09:00
Eryk Rakowski
3745b76da8 feat(extensions): add support for some chrome.management APIs (#25098)
* fix: initialize management policy

* fix(extensions): crash when using chrome.management

* test: add tests

* docs: add a note about chrome.management

* fix: lint errors

* fix: lint errors

* fix: remove favicon_service include

* fix: add missing management permission

* docs: more supported apis

* fix: extensions.md line endings
2020-09-02 09:59:56 +09:00
Jeremy Rose
bf7dbff858 docs: note remote deprecation in docs/api/remote (#24941) 2020-08-31 11:27:00 -07:00
John Kleinschmidt
7c10f86c6e ci: cleanup up test app directories (#25145)
* ci: cleanup up test app directories

* ci: use electron prefix for a testing apps so that the can be cleaned up

* Revert "ci: cleanup up test app directories"

This reverts commit a47daba812.

* fixup test due to app name change
2020-08-31 14:07:12 -04:00
Electron Bot
9803e4d526 Bump v12.0.0-nightly.20200831 2020-08-31 07:32:25 -07:00
Robo
068b464e13 fix: client area inset calculation when maximized for framless windows (#25052)
* adopt per monitor scale factor

* fix: client area inset calculation when maximized

* address review feedback

* pass correct glass insets to GetDwmFrameInsetsInPixels

* remove unused code

* Windows 8 and 10 use the same DWM frame calculation

Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-08-31 16:55:50 +09:00
Milan Burda
a09694ae85 chore: replace CRLF with LF (#25161)
* chore: replace CRLF with LF

* chore: replace CRLF with LF in docs and tests

Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-08-31 16:52:11 +09:00
Cheng Zhao
e06a1c2746 docs: fix supported platforms of powerMonitor (#25149) 2020-08-31 16:46:10 +09:00
Shelley Vohr
c5320b3951 fix: save dialog extensions should be deterministic (#25164) 2020-08-28 09:40:47 -07:00
Electron Bot
6cc960f214 Bump v12.0.0-nightly.20200828 2020-08-28 07:32:29 -07:00
Shelley Vohr
a4b6fce907 fix: honor pageRanges when printing (#25064) 2020-08-27 20:21:29 -07:00
Cheng Zhao
8f727b3569 fix: do not reset process_id in URLLoaderFactoryParams (#25139) 2020-08-28 10:43:08 +09:00
Shelley Vohr
95073decd3 chore: remove bv before setting owner window (#25172) 2020-08-27 16:24:52 -07:00
Shelley Vohr
e8ef1ef252 chore: wrap add/remove view in extra check (#25166) 2020-08-27 13:04:06 -07:00
Samuel Attard
075502477e chore: we do not use the nightly tag on npm (#25147) 2020-08-27 12:11:27 -07:00
Milan Burda
97755bbd85 fix: resolve RegisterSuspendResumeNotification dynamically (#25162) 2020-08-27 11:20:15 -07:00
Markus Olsson
bab69ae4d2 fix: make shell.moveItemToTrash return false on Windows when move unsuccessful (#25124) 2020-08-27 09:55:33 -07:00
Electron Bot
443540fd13 Bump v12.0.0-nightly.20200827 2020-08-27 07:31:51 -07:00
Cheng Zhao
5e1950ceff chore: force source code and markdown files to use LF line ending (#25134) 2020-08-27 10:25:39 +09:00
86 changed files with 3808 additions and 456 deletions

View File

@@ -15,7 +15,6 @@ Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTIN
- [ ] relevant documentation is changed or added
- [ ] PR title follows semantic [commit guidelines](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines)
- [ ] [PR release notes](https://github.com/electron/clerk/blob/master/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/master/README.md#examples).
- [ ] This is **NOT A BREAKING CHANGE**. Breaking changes may not be merged to master until 11-x-y is branched.
#### Release Notes

View File

@@ -1154,6 +1154,11 @@ if (is_mac) {
"/DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll",
]
if (target_cpu == "arm64") {
configs -= [ "//build/config/win:cfi_linker" ]
ldflags += [ "/guard:cf,nolongjmp" ]
}
# This is to support renaming of electron.exe. node-gyp has hard-coded
# executable names which it will recognise as node. This module definition
# file claims that the electron executable is in fact named "node.exe",

View File

@@ -1 +1 @@
11.0.0-beta.5
12.0.0-nightly.20200911

View File

@@ -10,10 +10,9 @@ config.output = {
filename: path.basename(outPath)
};
const { wrapInitWithProfilingTimeout } = config;
delete config.wrapInitWithProfilingTimeout;
const { wrapInitWithProfilingTimeout, wrapInitWithTryCatch, ...webpackConfig } = config;
webpack(config, (err, stats) => {
webpack(webpackConfig, (err, stats) => {
if (err) {
console.error(err);
process.exit(1);
@@ -21,9 +20,17 @@ webpack(config, (err, stats) => {
console.error(stats.toString('normal'));
process.exit(1);
} else {
let contents = fs.readFileSync(outPath, 'utf8');
if (wrapInitWithTryCatch) {
contents = `try {
${contents}
} catch (err) {
console.error('Electron ${webpackConfig.output.filename} script failed to run');
console.error(err);
}`;
}
if (wrapInitWithProfilingTimeout) {
const contents = fs.readFileSync(outPath, 'utf8');
const newContents = `function ___electron_webpack_init__() {
contents = `function ___electron_webpack_init__() {
${contents}
};
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
@@ -31,8 +38,8 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in
} else {
___electron_webpack_init__();
}`;
fs.writeFileSync(outPath, newContents);
}
fs.writeFileSync(outPath, contents);
process.exit(0);
}
});

View File

@@ -69,7 +69,8 @@ module.exports = ({
loadElectronFromAlternateTarget,
targetDeletesNodeGlobals,
target,
wrapInitWithProfilingTimeout
wrapInitWithProfilingTimeout,
wrapInitWithTryCatch
}) => {
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts');
if (!fs.existsSync(entry)) {
@@ -87,6 +88,7 @@ module.exports = ({
filename: `${target}.bundle.js`
},
wrapInitWithProfilingTimeout,
wrapInitWithTryCatch,
resolve: {
alias: {
'@electron/internal': path.resolve(electronRoot, 'lib'),

View File

@@ -1,4 +1,5 @@
module.exports = require('./webpack.config.base')({
target: 'isolated_renderer',
alwaysHasNode: false
alwaysHasNode: false,
wrapInitWithTryCatch: true
});

View File

@@ -2,5 +2,6 @@ module.exports = require('./webpack.config.base')({
target: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true,
wrapInitWithProfilingTimeout: true
wrapInitWithProfilingTimeout: true,
wrapInitWithTryCatch: true
});

View File

@@ -1,5 +1,6 @@
module.exports = require('./webpack.config.base')({
target: 'sandboxed_renderer',
alwaysHasNode: false,
wrapInitWithProfilingTimeout: true
wrapInitWithProfilingTimeout: true,
wrapInitWithTryCatch: true
});

View File

@@ -2,5 +2,6 @@ module.exports = require('./webpack.config.base')({
target: 'worker',
loadElectronFromAlternateTarget: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true
targetDeletesNodeGlobals: true,
wrapInitWithTryCatch: true
});

View File

@@ -56,7 +56,7 @@ an issue:
* [Accessibility](tutorial/accessibility.md)
* [Spectron](tutorial/accessibility.md#spectron)
* [Devtron](tutorial/accessibility.md#devtron)
* [Enabling Accessibility](tutorial/accessibility.md#enabling-accessibility)
* [Manually Enabling Accessibility Features](tutorial/accessibility.md#manually-enabling-accessibility-features)
* [Testing and Debugging](tutorial/application-debugging.md)
* [Debugging the Main Process](tutorial/debugging-main-process.md)
* [Debugging the Main Process with Visual Studio Code](tutorial/debugging-main-process-vscode.md)

View File

@@ -59,7 +59,7 @@ The `crashReporter` module has the following methods:
* `rateLimit` Boolean (optional) _macOS_ _Windows_ - If true, limit the
number of crashes uploaded to 1/hour. Default is `false`.
* `compress` Boolean (optional) - If true, crash reports will be compressed
and uploaded with `Content-Encoding: gzip`. Default is `false`.
and uploaded with `Content-Encoding: gzip`. Default is `true`.
* `extra` Record<String, String> (optional) - Extra string key/value
annotations that will be sent along with crash reports that are generated
in the main process. Only string values are supported. Crashes generated in

View File

@@ -102,3 +102,15 @@ The following methods of `chrome.tabs` are supported:
> **Note:** In Chrome, passing `-1` as a tab ID signifies the "currently active
> tab". Since Electron has no such concept, passing `-1` as a tab ID is not
> supported and will raise an error.
### `chrome.management`
The following methods of `chrome.management` are supported:
- `chrome.management.getAll`
- `chrome.management.get`
- `chrome.management.getSelf`
- `chrome.management.getPermissionWarningsById`
- `chrome.management.getPermissionWarningsByManifest`
- `chrome.management.onEnabled`
- `chrome.management.onDisabled`

View File

@@ -4,6 +4,16 @@
Process: [Renderer](../glossary.md#renderer-process)
> ⚠️ WARNING ⚠️
> The `remote` module is [deprecated](https://github.com/electron/electron/issues/21408).
> Instead of `remote`, use [`ipcRenderer`](ipc-renderer.md) and
> [`ipcMain`](ipc-main.md).
>
> Read more about why the `remote` module is deprecated [here](https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31).
>
> If you still want to use `remote` despite the performance and security
> concerns, see [@electron/remote](https://github.com/electron/remote).
The `remote` module provides a simple way to do inter-process communication
(IPC) between the renderer process (web page) and the main process.

View File

@@ -45,15 +45,27 @@ Returns `Promise<void>`
Open the given external protocol URL in the desktop's default manner. (For example, mailto: URLs in the user's default mail agent).
### `shell.moveItemToTrash(fullPath[, deleteOnFail])`
### `shell.moveItemToTrash(fullPath[, deleteOnFail])` _Deprecated_
* `fullPath` String
* `deleteOnFail` Boolean (optional) - Whether or not to unilaterally remove the item if the Trash is disabled or unsupported on the volume. _macOS_
Returns `Boolean` - Whether the item was successfully moved to the trash or otherwise deleted.
> NOTE: This method is deprecated. Use `shell.trashItem` instead.
Move the given file to trash and returns a boolean status for the operation.
### `shell.trashItem(path)`
* `path` String - path to the item to be moved to the trash.
Returns `Promise<void>` - Resolves when the operation has been completed.
Rejects if there was an error while deleting the requested item.
This moves a path to the OS-specific trash location (Trash on macOS, Recycle
Bin on Windows, and a desktop-environment-specific location on Linux).
### `shell.beep()`
Play the beep sound.

View File

@@ -1117,6 +1117,10 @@ increment above or below represents zooming 20% larger or smaller to default
limits of 300% and 50% of original size, respectively. The formula for this is
`scale := 1.2 ^ level`.
> **NOTE**: The zoom policy at the Chromium level is same-origin, meaning that the
> zoom level for a specific domain propagates across all instances of windows with
> the same domain. Differentiating the window URLs will make zoom work per-window.
#### `contents.getZoomLevel()`
Returns `Number` - the current zoom level.
@@ -1296,9 +1300,9 @@ Returns [`PrinterInfo[]`](structures/printer-info.md)
* `pagesPerSheet` Number (optional) - The number of pages to print per page sheet.
* `collate` Boolean (optional) - Whether the web page should be collated.
* `copies` Number (optional) - The number of copies of the web page to print.
* `pageRanges` Record<string, number> (optional) - The page range to print.
* `from` Number - the start page.
* `to` Number - the end page.
* `pageRanges` Object[] (optional) - The page range to print. On macOS, only one range is honored.
* `from` Number - Index of the first page to print (0-based).
* `to` Number - Index of the last page to print (inclusive) (0-based).
* `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
* `dpi` Record<string, number> (optional)
* `horizontal` Number (optional) - The horizontal dpi.
@@ -1324,10 +1328,10 @@ Example usage:
const options = {
silent: true,
deviceName: 'My-Printer',
pageRanges: {
pageRanges: [{
from: 0,
to: 1
}
}]
}
win.webContents.print(options, (success, errorType) => {
if (!success) console.log(errorType)
@@ -1345,8 +1349,8 @@ win.webContents.print(options, (success, errorType) => {
default margin, 1 for no margin, and 2 for minimum margin.
* `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100.
* `pageRanges` Record<string, number> (optional) - The page range to print.
* `from` Number - zero-based index of the first page to print.
* `to` Number - zero-based index of the last page to print (inclusive).
* `from` Number - Index of the first page to print (0-based).
* `to` Number - Index of the last page to print (inclusive) (0-based).
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` and `width` in microns.
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.

View File

@@ -41,6 +41,10 @@ Changes the zoom level to the specified level. The original size is 0 and each
increment above or below represents zooming 20% larger or smaller to default
limits of 300% and 50% of original size, respectively.
> **NOTE**: The zoom policy at the Chromium level is same-origin, meaning that the
> zoom level for a specific domain propagates across all instances of windows with
> the same domain. Differentiating the window URLs will make zoom work per-window.
### `webFrame.getZoomLevel()`
Returns `Number` - The current zoom level.

View File

@@ -560,9 +560,9 @@ Stops any `findInPage` request for the `webview` with the provided `action`.
* `pagesPerSheet` Number (optional) - The number of pages to print per page sheet.
* `collate` Boolean (optional) - Whether the web page should be collated.
* `copies` Number (optional) - The number of copies of the web page to print.
* `pageRanges` Record<string, number> (optional) - The page range to print.
* `from` Number - zero-based index of the first page to print.
* `to` Number - zero-based index of the last page to print (inclusive).
* `pageRanges` Object[] (optional) - The page range to print.
* `from` Number - Index of the first page to print (0-based).
* `to` Number - Index of the last page to print (inclusive) (0-based).
* `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
* `dpi` Record<string, number> (optional)
* `horizontal` Number (optional) - The horizontal dpi.
@@ -587,9 +587,9 @@ Prints `webview`'s web page. Same as `webContents.print([options])`.
default margin, 1 for no margin, and 2 for minimum margin.
and `width` in microns.
* `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100.
* `pageRanges` Record<string, number> (optional) - The page range to print.
* `from` Number - the first page to print.
* `to` Number - the last page to print (inclusive).
* `pageRanges` Record<string, number> (optional) - The page range to print. On macOS, only the first range is honored.
* `from` Number - Index of the first page to print (0-based).
* `to` Number - Index of the last page to print (inclusive) (0-based).
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
@@ -648,6 +648,10 @@ increment above or below represents zooming 20% larger or smaller to default
limits of 300% and 50% of original size, respectively. The formula for this is
`scale := 1.2 ^ level`.
> **NOTE**: The zoom policy at the Chromium level is same-origin, meaning that the
> zoom level for a specific domain propagates across all instances of windows with
> the same domain. Differentiating the window URLs will make zoom work per-window.
### `<webview>.getZoomFactor()`
Returns `Number` - the current zoom factor.

View File

@@ -12,6 +12,20 @@ This document uses the following convention to categorize breaking changes:
- **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
- **Removed:** An API or feature was removed, and is no longer supported by Electron.
## Planned Breaking API Changes (13.0)
### Removed: `shell.moveItemToTrash()`
The deprecated synchronous `shell.moveItemToTrash()` API has been removed. Use
the asynchronous `shell.trashItem()` instead.
```js
// Removed in Electron 13
shell.moveItemToTrash(path)
// Replace with
shell.trashItem(path).then(/* ... */)
```
## Planned Breaking API Changes (12.0)
### Default Changed: `contextIsolation` defaults to `true`
@@ -50,6 +64,37 @@ If your crash ingestion server does not support compressed payloads, you can
turn off compression by specifying `{ compress: false }` in the crash reporter
options.
### Deprecated: `remote` module
The `remote` module is deprecated in Electron 12, and will be removed in
Electron 14. It is replaced by the
[`@electron/remote`](https://github.com/electron/remote) module.
```js
// Deprecated in Electron 12:
const { BrowserWindow } = require('electron').remote
```
```js
// Replace with:
const { BrowserWindow } = require('@electron/remote')
// In the main process:
require('@electron/remote/main').initialize()
```
### Deprecated: `shell.moveItemToTrash()`
The synchronous `shell.moveItemToTrash()` has been replaced by the new,
asynchronous `shell.trashItem()`.
```js
// Deprecated in Electron 12
shell.moveItemToTrash(path)
// Replace with
shell.trashItem(path).then(/* ... */)
```
## Planned Breaking API Changes (11.0)
There are no breaking changes planned for 11.0.

View File

@@ -4,8 +4,8 @@ These guides are intended for people working on the Electron project itself.
For guides on Electron app development, see
[/docs/README.md](../README.md#guides-and-tutorials).
* [Code of Conduct](../../CODE_OF_CONDUCT.md)
* [Contributing to Electron](../../CONTRIBUTING.md)
* [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md)
* [Contributing to Electron](https://github.com/electron/electron/blob/master/CONTRIBUTING.md)
* [Issues](issues.md)
* [Pull Requests](pull-requests.md)
* [Documentation Styleguide](coding-style.md#documentation)

View File

@@ -1,6 +1,6 @@
# Accessibility
Making accessible applications is important and we're happy to introduce new
Making accessible applications is important and we're happy to provide
functionality to [Devtron][devtron] and [Spectron][spectron] that gives
developers the opportunity to make their apps better for everyone.
@@ -11,7 +11,7 @@ websites because they're both ultimately HTML. With Electron apps, however,
you can't use the online resources for accessibility audits because your app
doesn't have a URL to point the auditor to.
These new features bring those auditing tools to your Electron app. You can
These features bring those auditing tools to your Electron app. You can
choose to add audits to your tests with Spectron or use them within DevTools
with Devtron. Read on for a summary of the tools.
@@ -32,7 +32,7 @@ You can read more about this feature in [Spectron's documentation][spectron-a11y
## Devtron
In Devtron, there is a new accessibility tab which will allow you to audit a
In Devtron, there is an accessibility tab which will allow you to audit a
page in your app, sort and filter the results.
![devtron screenshot][devtron-screenshot]
@@ -44,26 +44,29 @@ audit rules this library uses on that [repository's wiki][a11y-devtools-wiki].
If you know of other great accessibility tools for Electron, add them to the
accessibility documentation with a pull request.
## Enabling Accessibility
## Manually enabling accessibility features
Electron applications keep accessibility disabled by default for performance
reasons but there are multiple ways to enable it.
Electron applications will automatically enable accessibility features in the
presence of assistive technology (e.g. [JAWS](https://www.freedomscientific.com/products/software/jaws/)
on Windows or [VoiceOver](https://help.apple.com/voiceover/mac/10.15/) on macOS).
See Chrome's [accessibility documentation][a11y-docs] for more details.
### Inside Application
You can also manually toggle these features either within your Electron application
or by setting flags in third-party native software.
By using [`app.setAccessibilitySupportEnabled(enabled)`][setAccessibilitySupportEnabled],
you can expose accessibility switch to users in the application preferences.
User's system assistive utilities have priority over this setting and will
override it.
### Using Electron's API
### Assistive Technology
By using the [`app.setAccessibilitySupportEnabled(enabled)`][setAccessibilitySupportEnabled]
API, you can manually expose Chrome's accessibility tree to users in the application preferences.
Note that the user's system assistive utilities have priority over this setting and
will override it.
Electron application will enable accessibility automatically when it detects
assistive technology (Windows) or VoiceOver (macOS). See Chrome's
[accessibility documentation][a11y-docs] for more details.
### Within third-party software
On macOS, third-party assistive technology can switch accessibility inside
Electron applications by setting the attribute `AXManualAccessibility`
#### macOS
On macOS, third-party assistive technology can toggle accessibility features inside
Electron applications by setting the `AXManualAccessibility` attribute
programmatically:
```objc

View File

@@ -565,7 +565,9 @@ filenames = {
"shell/common/node_util.h",
"shell/common/options_switches.cc",
"shell/common/options_switches.h",
"shell/common/platform_util.cc",
"shell/common/platform_util.h",
"shell/common/platform_util_internal.h",
"shell/common/platform_util_linux.cc",
"shell/common/platform_util_mac.mm",
"shell/common/platform_util_win.cc",
@@ -625,6 +627,8 @@ filenames = {
"shell/browser/extensions/api/resources_private/resources_private_api.h",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
"shell/browser/extensions/api/management/electron_management_api_delegate.cc",
"shell/browser/extensions/api/management/electron_management_api_delegate.h",
"shell/browser/extensions/api/tabs/tabs_api.cc",
"shell/browser/extensions/api/tabs/tabs_api.h",
"shell/browser/extensions/api/streams_private/streams_private_api.cc",

View File

@@ -13,7 +13,7 @@ class CrashReporter {
submitURL,
uploadToServer = true,
rateLimit = false,
compress = false
compress = true
} = options || {};
if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start');

View File

@@ -1 +1,7 @@
export default process._linkedBinding('electron_common_shell');
import { deprecate } from 'electron/main';
const shell = process._linkedBinding('electron_common_shell');
shell.moveItemToTrash = deprecate.renameFunction(shell.moveItemToTrash, 'shell.trashItem');
export default shell;

View File

@@ -3,9 +3,12 @@ import { isPromise, isSerializableObject, serialize, deserialize } from '../../c
import { MetaTypeFromRenderer, ObjectMember, ObjProtoDescriptor, MetaType } from '../../common/remote/types';
import { ipcRendererInternal } from '../ipc-renderer-internal';
import type { BrowserWindow, WebContents } from 'electron/main';
import deprecate from '@electron/internal/common/api/deprecate';
import { browserModuleNames } from '@electron/internal/browser/api/module-names';
import { commonModuleList } from '@electron/internal/common/api/module-list';
deprecate.log('The remote module is deprecated. Use https://github.com/electron/remote instead.');
const v8Util = process._linkedBinding('electron_common_v8_util');
const { hasSwitch } = process._linkedBinding('electron_common_command_line');

View File

@@ -1,119 +0,0 @@
import { webFrame } from 'electron';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
const v8Util = process._linkedBinding('electron_common_v8_util');
const IsolatedWorldIDs = {
/**
* Start of extension isolated world IDs, as defined in
* electron_render_frame_observer.h
*/
ISOLATED_WORLD_EXTENSIONS: 1 << 20
};
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS;
const extensionWorldId: {[key: string]: number | undefined} = {};
// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
const getIsolatedWorldIdForInstance = () => {
// TODO(samuelmaddock): allocate and cleanup IDs
return isolatedWorldIds++;
};
const escapePattern = function (pattern: string) {
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&');
};
// Check whether pattern matches.
// https://developer.chrome.com/extensions/match_patterns
const matchesPattern = function (pattern: string) {
if (pattern === '<all_urls>') return true;
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`);
const url = `${location.protocol}//${location.host}${location.pathname}`;
return url.match(regexp);
};
// Run the code with chrome API integrated.
const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
// Assign unique world ID to each extension
const worldId = extensionWorldId[extensionId] ||
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance());
// store extension ID for content script to read in isolated world
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId);
webFrame.setIsolatedWorldInfo(worldId, {
name: `${extensionId} [${worldId}]`
// TODO(samuelmaddock): read `content_security_policy` from extension manifest
// csp: manifest.content_security_policy,
});
const sources = [{ code, url }];
return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources);
};
const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
for (const { url, code } of scripts) {
runContentScript.call(window, extensionId, url, code);
}
};
const runStylesheet = function (this: any, url: string, code: string) {
webFrame.insertCSS(code);
};
const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
for (const { url, code } of css) {
runStylesheet.call(window, url, code);
}
};
// Run injected scripts.
// https://developer.chrome.com/extensions/content_scripts
const injectContentScript = function (extensionId: string, script: Electron.ContentScript) {
if (!process.isMainFrame && !script.allFrames) return;
if (!script.matches.some(matchesPattern)) return;
if (script.js) {
const fire = runAllContentScript.bind(window, script.js, extensionId);
if (script.runAt === 'document_start') {
process.once('document-start', fire);
} else if (script.runAt === 'document_end') {
process.once('document-end', fire);
} else {
document.addEventListener('DOMContentLoaded', fire);
}
}
if (script.css) {
const fire = runAllStylesheet.bind(window, script.css);
if (script.runAt === 'document_start') {
process.once('document-start', fire);
} else if (script.runAt === 'document_end') {
process.once('document-end', fire);
} else {
document.addEventListener('DOMContentLoaded', fire);
}
}
};
// Handle the request of chrome.tabs.executeJavaScript.
ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
event: Electron.Event,
extensionId: string,
url: string,
code: string
) {
return runContentScript.call(window, extensionId, url, code);
});
module.exports = (entries: Electron.ContentScriptEntry[]) => {
for (const entry of entries) {
if (entry.contentScripts) {
for (const script of entry.contentScripts) {
injectContentScript(entry.extensionId, script);
}
}
}
};

View File

@@ -1,20 +0,0 @@
export class Event {
private listeners: Function[] = []
addListener (callback: Function) {
this.listeners.push(callback);
}
removeListener (callback: Function) {
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
}
emit (...args: any[]) {
for (const listener of this.listeners) {
listener(...args);
}
}
}

View File

@@ -1,60 +0,0 @@
// Implementation of chrome.i18n.getMessage
// https://developer.chrome.com/extensions/i18n#method-getMessage
//
// Does not implement predefined messages:
// https://developer.chrome.com/extensions/i18n#overview-predefined
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
interface Placeholder {
content: string;
example?: string;
}
const getMessages = (extensionId: number) => {
try {
const data = ipcRendererUtils.invokeSync<string>('CHROME_GET_MESSAGES', extensionId);
return JSON.parse(data) || {};
} catch {
return {};
}
};
const replaceNumberedSubstitutions = (message: string, substitutions: string[]) => {
return message.replace(/\$(\d+)/, (_, number) => {
const index = parseInt(number, 10) - 1;
return substitutions[index] || '';
});
};
const replacePlaceholders = (message: string, placeholders: Record<string, Placeholder>, substitutions: string[] | string) => {
if (typeof substitutions === 'string') substitutions = [substitutions];
if (!Array.isArray(substitutions)) substitutions = [];
if (placeholders) {
Object.keys(placeholders).forEach((name: string) => {
let { content } = placeholders[name];
const substitutionsArray = Array.isArray(substitutions) ? substitutions : [];
content = replaceNumberedSubstitutions(content, substitutionsArray);
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content);
});
}
return replaceNumberedSubstitutions(message, substitutions);
};
const getMessage = (extensionId: number, messageName: string, substitutions: string[]) => {
const messages = getMessages(extensionId);
if (Object.prototype.hasOwnProperty.call(messages, messageName)) {
const { message, placeholders } = messages[messageName];
return replacePlaceholders(message, placeholders, substitutions);
}
};
exports.setup = (extensionId: number) => {
return {
getMessage (messageName: string, substitutions: string[]) {
return getMessage(extensionId, messageName, substitutions);
}
};
};

View File

@@ -1,86 +0,0 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
const getStorage = (storageType: string, extensionId: number, callback: Function) => {
if (typeof callback !== 'function') throw new TypeError('No callback provided');
ipcRendererInternal.invoke<string>('CHROME_STORAGE_READ', storageType, extensionId)
.then(data => {
if (data !== null) {
callback(JSON.parse(data));
} else {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
callback({});
}
});
};
const setStorage = (storageType: string, extensionId: number, storage: Record<string, any>, callback: Function) => {
const json = JSON.stringify(storage);
ipcRendererInternal.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json)
.then(() => {
if (callback) callback();
});
};
const getStorageManager = (storageType: string, extensionId: number) => {
return {
get (keys: string[], callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
if (keys == null) return callback(storage);
let defaults: Record<string, any> = {};
switch (typeof keys) {
case 'string':
keys = [keys];
break;
case 'object':
if (!Array.isArray(keys)) {
defaults = keys;
keys = Object.keys(keys);
}
break;
}
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
if (keys.length === 0) return callback({});
const items: Record<string, any> = {};
keys.forEach((key: string) => {
let value = storage[key];
if (value == null) value = defaults[key];
items[key] = value;
});
callback(items);
});
},
set (items: Record<string, any>, callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
Object.keys(items).forEach(name => { storage[name] = items[name]; });
setStorage(storageType, extensionId, storage, callback);
});
},
remove (keys: string[], callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
if (!Array.isArray(keys)) keys = [keys];
keys.forEach((key: string) => {
delete storage[key];
});
setStorage(storageType, extensionId, storage, callback);
});
},
clear (callback: Function) {
setStorage(storageType, extensionId, {}, callback);
}
};
};
export const setup = (extensionId: number) => ({
sync: getStorageManager('sync', extensionId),
local: getStorageManager('local', extensionId)
});

View File

@@ -1,19 +0,0 @@
import { Event } from '@electron/internal/renderer/extensions/event';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
class WebNavigation {
private onBeforeNavigate = new Event()
private onCompleted = new Event()
constructor () {
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => {
this.onBeforeNavigate.emit(details);
});
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => {
this.onCompleted.emit(details);
});
}
}
export const setup = () => new WebNavigation();

View File

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

View File

@@ -100,3 +100,4 @@ remove_some_deps_that_do_not_work_on_arm64.patch
fix_check_issecureeventinputenabled_in_constructor_before_setting.patch
skip_atk_toolchain_check.patch
worker_feat_add_hook_to_notify_script_ready.patch
fix_properly_honor_printing_page_ranges.patch

View File

@@ -0,0 +1,129 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Thu, 20 Aug 2020 10:55:48 -0700
Subject: fix: properly honor printing page ranges
The print ranges in Chromium's print job settings were not being properly
plumbed through to PMPrintSettings on mcOS. This fixes that by setting
them should they exist.
This will be upstreamed.
diff --git a/printing/printing_context_mac.h b/printing/printing_context_mac.h
index 06cdc0e1a4a50e29a148c97c964c30a939e800da..5db5cdb61be0beee4506313dcde46c499e011383 100644
--- a/printing/printing_context_mac.h
+++ b/printing/printing_context_mac.h
@@ -81,6 +81,10 @@ class PRINTING_EXPORT PrintingContextMac : public PrintingContext {
// Returns true if the orientation was set.
bool SetOrientationIsLandscape(bool landscape);
+ // Set the page range in native print info object.
+ // Returns true if the range was set.
+ bool SetPrintRangeInPrintSettings(const PageRanges& ranges);
+
// Sets duplex mode in PMPrintSettings.
// Returns true if duplex mode is set.
bool SetDuplexModeInPrintSettings(mojom::DuplexMode mode);
diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm
index 56bcfbe625537c7b1bc2a999c261836bb15896ba..f916e78de99b87e0dae4f5f87329886b658ccd93 100644
--- a/printing/printing_context_mac.mm
+++ b/printing/printing_context_mac.mm
@@ -188,7 +188,8 @@ PMPaper MatchPaper(CFArrayRef paper_list,
!SetCopiesInPrintSettings(settings_->copies()) ||
!SetCollateInPrintSettings(settings_->collate()) ||
!SetDuplexModeInPrintSettings(settings_->duplex_mode()) ||
- !SetOutputColor(settings_->color())) {
+ !SetOutputColor(settings_->color()) ||
+ !SetPrintRangeInPrintSettings(settings_->ranges()) ) {
return OnError();
}
}
@@ -341,6 +342,22 @@ PMPaper MatchPaper(CFArrayRef paper_list,
return PMSetCopies(print_settings, copies, false) == noErr;
}
+bool PrintingContextMac::SetPrintRangeInPrintSettings(const PageRanges& ranges) {
+ // Default is already NSPrintAllPages - we can safely bail.
+ if (ranges.empty())
+ return true;
+
+ auto* print_settings =
+ static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
+
+ // macOS does not allow multiple ranges, so pluck the first.
+ auto range = ranges.front();
+ bool set_first_page = PMSetFirstPage(print_settings, range.from + 1, false) == noErr;
+ bool set_last_page = PMSetLastPage(print_settings, range.to + 1, false) == noErr;
+
+ return set_first_page && set_last_page;
+}
+
bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
PMPrintSettings print_settings =
static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
diff --git a/printing/printing_context_system_dialog_win.cc b/printing/printing_context_system_dialog_win.cc
index d3c8677f30d72efc49b28f293260c74c7b8d8b4e..f6e66aaa58ab1881d64dcbb320ae8b5ac7631b28 100644
--- a/printing/printing_context_system_dialog_win.cc
+++ b/printing/printing_context_system_dialog_win.cc
@@ -52,14 +52,28 @@ void PrintingContextSystemDialogWin::AskUserForSettings(
PRINTPAGERANGE ranges[32];
dialog_options.nStartPage = START_PAGE_GENERAL;
if (max_pages) {
- // Default initialize to print all the pages.
memset(ranges, 0, sizeof(ranges));
- ranges[0].nFromPage = 1;
- ranges[0].nToPage = max_pages;
- dialog_options.nPageRanges = 1;
- dialog_options.nMaxPageRanges = base::size(ranges);
+
+ auto page_ranges = settings_->ranges();
+ if (!page_ranges.empty()) {
+ for (size_t i = 0; i < page_ranges.size(); i++) {
+ auto range = page_ranges[i];
+ ranges[i].nFromPage = range.from + 1;
+ ranges[i].nToPage = range.to + 1;
+ }
+ dialog_options.nPageRanges = page_ranges.size();
+
+ // Ensure the Pages radio button is selected.
+ dialog_options.Flags |= PD_PAGENUMS;
+ } else {
+ ranges[0].nFromPage = 1;
+ ranges[0].nToPage = max_pages;
+ dialog_options.nPageRanges = 1;
+ }
+
dialog_options.nMinPage = 1;
dialog_options.nMaxPage = max_pages;
+ dialog_options.nMaxPageRanges = base::size(ranges);
dialog_options.lpPageRanges = ranges;
} else {
// No need to bother, we don't know how many pages are available.
diff --git a/ui/gtk/printing/print_dialog_gtk.cc b/ui/gtk/printing/print_dialog_gtk.cc
index 8a78a609295a1d90208bdc7e73d7a850c319f361..2f5bc01bedf84126d8fb1597a0b813a2b7230279 100644
--- a/ui/gtk/printing/print_dialog_gtk.cc
+++ b/ui/gtk/printing/print_dialog_gtk.cc
@@ -239,6 +239,23 @@ void PrintDialogGtk::UpdateSettings(
gtk_print_settings_set_n_copies(gtk_settings_, settings->copies());
gtk_print_settings_set_collate(gtk_settings_, settings->collate());
+
+ auto print_ranges = settings->ranges();
+ if (!print_ranges.empty()) {
+ // Tell the system that we only intend to print a subset of pages.
+ gtk_print_settings_set_print_pages(gtk_settings_, GTK_PRINT_PAGES_RANGES);
+
+ GtkPageRange* ranges;
+ ranges = g_new(GtkPageRange, print_ranges.size());
+ for (size_t i = 0; i < print_ranges.size(); i++) {
+ auto range = print_ranges[i];
+ ranges[i].start = range.from;
+ ranges[i].end = range.to;
+ }
+
+ gtk_print_settings_set_page_ranges(gtk_settings_, ranges, 1);
+ g_free(ranges);
+ }
#if defined(USE_CUPS)
// Set advanced settings first so they can be overridden by user applied

View File

@@ -43,3 +43,5 @@ fix_allow_preventing_setpromiserejectcallback.patch
lib_use_non-symbols_in_isurlinstance_check.patch
feat_add_implementation_of_v8_platform_postjob.patch
fix_enable_tls_renegotiation.patch
n-api_src_provide_asynchronous_cleanup_hooks.patch
crypto_update_certdata_to_nss_3_56.patch

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,250 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Tue, 25 Aug 2020 19:34:12 -0700
Subject: n-api,src: provide asynchronous cleanup hooks
Backports https://github.com/nodejs/node/pull/34572 and
https://github.com/nodejs/node/pull/34819.
Sometimes addons need to perform cleanup actions, for example
closing libuv handles or waiting for requests to finish, that
cannot be performed synchronously.
Add C++ API and N-API functions that allow providing such
asynchronous cleanup hooks.
This patch can be removed when Electron upgrades to a version of Node.js
which contains the above referenced commit(s).
diff --git a/src/api/hooks.cc b/src/api/hooks.cc
index 037bdda6f41c825dd112b0cc9fca0ebde47c6163..3b16c0350d8a8494202144407664af41d338fe04 100644
--- a/src/api/hooks.cc
+++ b/src/api/hooks.cc
@@ -73,8 +73,35 @@ int EmitExit(Environment* env) {
.ToChecked();
}
+typedef void (*CleanupHook)(void* arg);
+typedef void (*AsyncCleanupHook)(void* arg, void(*)(void*), void*);
+
+struct AsyncCleanupHookInfo final {
+ Environment* env;
+ AsyncCleanupHook fun;
+ void* arg;
+ bool started = false;
+ // Use a self-reference to make sure the storage is kept alive while the
+ // cleanup hook is registered but not yet finished.
+ std::shared_ptr<AsyncCleanupHookInfo> self;
+};
+
+// Opaque type that is basically an alias for `shared_ptr<AsyncCleanupHookInfo>`
+// (but not publicly so for easier ABI/API changes). In particular,
+// std::shared_ptr does not generally maintain a consistent ABI even on a
+// specific platform.
+struct ACHHandle final {
+ std::shared_ptr<AsyncCleanupHookInfo> info;
+};
+// This is implemented as an operator on a struct because otherwise you can't
+// default-initialize AsyncCleanupHookHandle, because in C++ for a
+// std::unique_ptr to be default-initializable the deleter type also needs
+// to be default-initializable; in particular, function types don't satisfy
+// this.
+void DeleteACHHandle::operator ()(ACHHandle* handle) const { delete handle; }
+
void AddEnvironmentCleanupHook(Isolate* isolate,
- void (*fun)(void* arg),
+ CleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
@@ -82,13 +109,50 @@ void AddEnvironmentCleanupHook(Isolate* isolate,
}
void RemoveEnvironmentCleanupHook(Isolate* isolate,
- void (*fun)(void* arg),
+ CleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
env->RemoveCleanupHook(fun, arg);
}
+static void FinishAsyncCleanupHook(void* arg) {
+ AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
+ std::shared_ptr<AsyncCleanupHookInfo> keep_alive = info->self;
+
+ info->env->DecreaseWaitingRequestCounter();
+ info->self.reset();
+}
+
+static void RunAsyncCleanupHook(void* arg) {
+ AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
+ info->env->IncreaseWaitingRequestCounter();
+ info->started = true;
+ info->fun(info->arg, FinishAsyncCleanupHook, info);
+}
+
+AsyncCleanupHookHandle AddEnvironmentCleanupHook(
+ Isolate* isolate,
+ AsyncCleanupHook fun,
+ void* arg) {
+ Environment* env = Environment::GetCurrent(isolate);
+ CHECK_NOT_NULL(env);
+ auto info = std::make_shared<AsyncCleanupHookInfo>();
+ info->env = env;
+ info->fun = fun;
+ info->arg = arg;
+ info->self = info;
+ env->AddCleanupHook(RunAsyncCleanupHook, info.get());
+ return AsyncCleanupHookHandle(new ACHHandle { info });
+}
+
+void RemoveEnvironmentCleanupHook(
+ AsyncCleanupHookHandle handle) {
+ if (handle->info->started) return;
+ handle->info->self.reset();
+ handle->info->env->RemoveCleanupHook(RunAsyncCleanupHook, handle->info.get());
+}
+
async_id AsyncHooksGetExecutionAsyncId(Isolate* isolate) {
Environment* env = Environment::GetCurrent(isolate);
if (env == nullptr) return -1;
diff --git a/src/node.h b/src/node.h
index 60ecc3bd3499c23b297bc11e7f052aede20520c2..4c4e55e338d7b42c36818a45f6b41170c495adde 100644
--- a/src/node.h
+++ b/src/node.h
@@ -739,6 +739,20 @@ NODE_EXTERN void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
void (*fun)(void* arg),
void* arg);
+/* These are async equivalents of the above. After the cleanup hook is invoked,
+ * `cb(cbarg)` *must* be called, and attempting to remove the cleanup hook will
+ * have no effect. */
+struct ACHHandle;
+struct NODE_EXTERN DeleteACHHandle { void operator()(ACHHandle*) const; };
+typedef std::unique_ptr<ACHHandle, DeleteACHHandle> AsyncCleanupHookHandle;
+
+NODE_EXTERN AsyncCleanupHookHandle AddEnvironmentCleanupHook(
+ v8::Isolate* isolate,
+ void (*fun)(void* arg, void (*cb)(void*), void* cbarg),
+ void* arg);
+
+NODE_EXTERN void RemoveEnvironmentCleanupHook(AsyncCleanupHookHandle holder);
+
/* Returns the id of the current execution context. If the return value is
* zero then no execution has been set. This will happen if the user handles
* I/O from native code. */
diff --git a/src/node_api.cc b/src/node_api.cc
index fe24eca1b8e2d81fbafd0a1e2da38d957fbaa1c1..66168bd2c28ce6481516e63734616f487e3ec3e1 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -507,6 +507,70 @@ napi_status napi_remove_env_cleanup_hook(napi_env env,
return napi_ok;
}
+struct napi_async_cleanup_hook_handle__ {
+ napi_async_cleanup_hook_handle__(napi_env env,
+ napi_async_cleanup_hook user_hook,
+ void* user_data):
+ env_(env),
+ user_hook_(user_hook),
+ user_data_(user_data) {
+ handle_ = node::AddEnvironmentCleanupHook(env->isolate, Hook, this);
+ env->Ref();
+ }
+
+ ~napi_async_cleanup_hook_handle__() {
+ node::RemoveEnvironmentCleanupHook(std::move(handle_));
+ if (done_cb_ != nullptr)
+ done_cb_(done_data_);
+
+ // Release the `env` handle asynchronously since it would be surprising if
+ // a call to a N-API function would destroy `env` synchronously.
+ static_cast<node_napi_env>(env_)->node_env()
+ ->SetImmediate([env = env_](node::Environment*) { env->Unref(); });
+ }
+
+ static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
+ auto handle = static_cast<napi_async_cleanup_hook_handle__*>(data);
+ handle->done_cb_ = done_cb;
+ handle->done_data_ = done_data;
+ handle->user_hook_(handle, handle->user_data_);
+ }
+
+ node::AsyncCleanupHookHandle handle_;
+ napi_env env_ = nullptr;
+ napi_async_cleanup_hook user_hook_ = nullptr;
+ void* user_data_ = nullptr;
+ void (*done_cb_)(void*) = nullptr;
+ void* done_data_ = nullptr;
+};
+
+napi_status napi_add_async_cleanup_hook(
+ napi_env env,
+ napi_async_cleanup_hook hook,
+ void* arg,
+ napi_async_cleanup_hook_handle* remove_handle) {
+ CHECK_ENV(env);
+ CHECK_ARG(env, hook);
+
+ napi_async_cleanup_hook_handle__* handle =
+ new napi_async_cleanup_hook_handle__(env, hook, arg);
+
+ if (remove_handle != nullptr)
+ *remove_handle = handle;
+
+ return napi_clear_last_error(env);
+}
+
+napi_status napi_remove_async_cleanup_hook(
+ napi_async_cleanup_hook_handle remove_handle) {
+
+ if (remove_handle == nullptr)
+ return napi_invalid_arg;
+
+ delete remove_handle;
+ return napi_ok;
+}
+
napi_status napi_fatal_exception(napi_env env, napi_value err) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, err);
diff --git a/src/node_api.h b/src/node_api.h
index 2f1b45572d8130f27492eb6188c1aa611f2e01a3..577a1dcd94987202819e7a36a2d9674f13d13614 100644
--- a/src/node_api.h
+++ b/src/node_api.h
@@ -250,6 +250,19 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
#endif // NAPI_VERSION >= 4
+#ifdef NAPI_EXPERIMENTAL
+
+NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
+ napi_env env,
+ napi_async_cleanup_hook hook,
+ void* arg,
+ napi_async_cleanup_hook_handle* remove_handle);
+
+NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
+ napi_async_cleanup_hook_handle remove_handle);
+
+#endif // NAPI_EXPERIMENTAL
+
EXTERN_C_END
#endif // SRC_NODE_API_H_
diff --git a/src/node_api_types.h b/src/node_api_types.h
index 1c9a2b8aa21889c0d29fb02b234ae9698d122c2c..0e400e9676df5ba09d350fe7a2a70a1dc9e4d3d6 100644
--- a/src/node_api_types.h
+++ b/src/node_api_types.h
@@ -41,4 +41,10 @@ typedef struct {
const char* release;
} napi_node_version;
+#ifdef NAPI_EXPERIMENTAL
+typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle;
+typedef void (*napi_async_cleanup_hook)(napi_async_cleanup_hook_handle handle,
+ void* data);
+#endif // NAPI_EXPERIMENTAL
+
#endif // SRC_NODE_API_TYPES_H_

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
import argparse
import os

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
import argparse
import os

View File

@@ -6,6 +6,7 @@ const fs = require('fs');
const path = require('path');
const { GitProcess } = require('dugite');
const { Octokit } = require('@octokit/rest');
const octokit = new Octokit({
auth: process.env.ELECTRON_GITHUB_TOKEN
@@ -109,13 +110,21 @@ const getNoteFromClerk = async (ghKey) => {
return NO_NOTES;
}
if (comment.body.startsWith(PERSIST_LEAD)) {
return comment.body
let lines = comment.body
.slice(PERSIST_LEAD.length).trim() // remove PERSIST_LEAD
.split(/\r?\n/) // split into lines
.map(line => line.trim())
.filter(line => line.startsWith(QUOTE_LEAD)) // notes are quoted
.map(line => line.slice(QUOTE_LEAD.length)) // unquote the lines
.join(' ') // join the note lines
.map(line => line.slice(QUOTE_LEAD.length)); // unquote the lines
const firstLine = lines.shift();
// indent anything after the first line to ensure that
// multiline notes with their own sub-lists don't get
// parsed in the markdown as part of the top-level list
// (example: https://github.com/electron/electron/pull/25216)
lines = lines.map(line => ' ' + line);
return [firstLine, ...lines]
.join('\n') // join the lines
.trim();
}
}
@@ -532,6 +541,15 @@ function renderTrops (commit, excludeBranch) {
function renderDescription (commit) {
let note = commit.note || commit.subject || '';
note = note.trim();
// release notes bullet point every change, so if the note author
// manually started the content with a bullet point, that will confuse
// the markdown renderer -- remove the redundant bullet point
// (example: https://github.com/electron/electron/pull/25216)
if (note.startsWith('*')) {
note = note.slice(1).trim();
}
if (note.length !== 0) {
note = note[0].toUpperCase() + note.substr(1);

View File

@@ -91,7 +91,7 @@ async function createRelease (branchToTarget, isBeta) {
if (newVersion.indexOf('nightly') > 0) {
releaseBody = 'Note: This is a nightly release. Please file new issues ' +
'for any bugs you find in it.\n \n This release is published to npm ' +
'under the nightly tag and can be installed via `npm install electron@nightly`, ' +
'under the electron-nightly package and can be installed via `npm install electron-nightly`, ' +
`or \`npm install electron-nightly@${newVersion.substr(1)}\`.\n \n ${releaseNotes.text}`;
} else {
releaseBody = 'Note: This is a beta release. Please file new issues ' +

View File

@@ -1058,8 +1058,8 @@ void BaseWindow::ResetBrowserViews() {
&browser_view) &&
!browser_view.IsEmpty()) {
if (browser_view->web_contents()) {
browser_view->web_contents()->SetOwnerWindow(nullptr);
window_->RemoveBrowserView(browser_view->view());
browser_view->web_contents()->SetOwnerWindow(nullptr);
}
}

View File

@@ -132,6 +132,7 @@
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/browser/script_executor.h"
#include "extensions/browser/view_type_utils.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#endif
@@ -409,12 +410,45 @@ const void* kElectronApiWebContentsKey = &kElectronApiWebContentsKey;
} // namespace
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
WebContents::Type GetTypeFromViewType(extensions::ViewType view_type) {
switch (view_type) {
case extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE:
return WebContents::Type::BACKGROUND_PAGE;
case extensions::VIEW_TYPE_APP_WINDOW:
case extensions::VIEW_TYPE_COMPONENT:
case extensions::VIEW_TYPE_EXTENSION_DIALOG:
case extensions::VIEW_TYPE_EXTENSION_POPUP:
case extensions::VIEW_TYPE_BACKGROUND_CONTENTS:
case extensions::VIEW_TYPE_EXTENSION_GUEST:
case extensions::VIEW_TYPE_TAB_CONTENTS:
case extensions::VIEW_TYPE_INVALID:
return WebContents::Type::REMOTE;
}
}
#endif
WebContents::WebContents(v8::Isolate* isolate,
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
type_(Type::REMOTE),
id_(GetAllWebContents().Add(this)),
weak_factory_(this) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
// WebContents created by extension host will have valid ViewType set.
extensions::ViewType view_type = extensions::GetViewType(web_contents);
if (view_type != extensions::VIEW_TYPE_INVALID) {
InitWithExtensionView(isolate, web_contents, view_type);
}
extensions::ElectronExtensionWebContentsObserver::CreateForWebContents(
web_contents);
script_executor_.reset(new extensions::ScriptExecutor(web_contents));
#endif
auto session = Session::CreateFrom(isolate, GetBrowserContext());
session_.Reset(isolate, session.ToV8());
@@ -424,11 +458,7 @@ WebContents::WebContents(v8::Isolate* isolate,
web_contents->SetUserData(kElectronApiWebContentsKey,
std::make_unique<UserDataLink>(GetWeakPtr()));
InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate));
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
extensions::ElectronExtensionWebContentsObserver::CreateForWebContents(
web_contents);
script_executor_.reset(new extensions::ScriptExecutor(web_contents));
#endif
registry_.AddInterface(base::BindRepeating(&WebContents::BindElectronBrowser,
base::Unretained(this)));
receivers_.set_disconnect_handler(base::BindRepeating(
@@ -636,13 +666,39 @@ void WebContents::InitWithSessionAndOptions(
std::make_unique<UserDataLink>(GetWeakPtr()));
}
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void WebContents::InitWithExtensionView(v8::Isolate* isolate,
content::WebContents* web_contents,
extensions::ViewType view_type) {
// Must reassign type prior to calling `Init`.
type_ = GetTypeFromViewType(view_type);
if (GetType() == Type::REMOTE)
return;
// Allow toggling DevTools for background pages
Observe(web_contents);
InitWithWebContents(web_contents, GetBrowserContext(), IsGuest());
managed_web_contents()->GetView()->SetDelegate(this);
SecurityStateTabHelper::CreateForWebContents(web_contents);
}
#endif
WebContents::~WebContents() {
MarkDestroyed();
// The destroy() is called.
if (managed_web_contents()) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
if (type_ == Type::BACKGROUND_PAGE) {
// Background pages are owned by extensions::ExtensionHost
managed_web_contents()->ReleaseWebContents();
}
#endif
managed_web_contents()->GetView()->SetDelegate(nullptr);
RenderViewDeleted(web_contents()->GetRenderViewHost());
if (web_contents()) {
RenderViewDeleted(web_contents()->GetRenderViewHost());
}
if (type_ == Type::BROWSER_WINDOW && owner_window()) {
// For BrowserWindow we should close the window and clean up everything
@@ -1379,6 +1435,7 @@ void WebContents::DevToolsOpened() {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
DCHECK(managed_web_contents());
auto handle =
FromOrCreate(isolate, managed_web_contents()->GetDevToolsWebContents());
devtools_web_contents_.Reset(isolate, handle.ToV8());
@@ -1720,7 +1777,8 @@ void WebContents::OpenDevTools(gin::Arguments* args) {
return;
std::string state;
if (type_ == Type::WEB_VIEW || !owner_window()) {
if (type_ == Type::WEB_VIEW || type_ == Type::BACKGROUND_PAGE ||
!owner_window()) {
state = "detach";
}
bool activate = true;
@@ -1731,6 +1789,8 @@ void WebContents::OpenDevTools(gin::Arguments* args) {
options.Get("activate", &activate);
}
}
DCHECK(managed_web_contents());
managed_web_contents()->SetDockState(state);
managed_web_contents()->ShowDevTools(activate);
}
@@ -1739,6 +1799,7 @@ void WebContents::CloseDevTools() {
if (type_ == Type::REMOTE)
return;
DCHECK(managed_web_contents());
managed_web_contents()->CloseDevTools();
}
@@ -1746,6 +1807,7 @@ bool WebContents::IsDevToolsOpened() {
if (type_ == Type::REMOTE)
return false;
DCHECK(managed_web_contents());
return managed_web_contents()->IsDevToolsViewShowing();
}
@@ -1753,6 +1815,7 @@ bool WebContents::IsDevToolsFocused() {
if (type_ == Type::REMOTE)
return false;
DCHECK(managed_web_contents());
return managed_web_contents()->GetView()->IsDevToolsViewFocused();
}
@@ -1761,6 +1824,7 @@ void WebContents::EnableDeviceEmulation(
if (type_ == Type::REMOTE)
return;
DCHECK(web_contents());
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
auto* widget_host_impl =
@@ -1778,6 +1842,7 @@ void WebContents::DisableDeviceEmulation() {
if (type_ == Type::REMOTE)
return;
DCHECK(web_contents());
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
auto* widget_host_impl =
@@ -1805,6 +1870,7 @@ void WebContents::InspectElement(int x, int y) {
if (!enable_devtools_)
return;
DCHECK(managed_web_contents());
if (!managed_web_contents()->GetDevToolsWebContents())
OpenDevTools(nullptr);
managed_web_contents()->InspectElement(x, y);
@@ -2069,8 +2135,9 @@ void WebContents::Print(gin::Arguments* args) {
int from, to;
if (range.Get("from", &from) && range.Get("to", &to)) {
base::Value range(base::Value::Type::DICTIONARY);
range.SetIntKey(printing::kSettingPageRangeFrom, from);
range.SetIntKey(printing::kSettingPageRangeTo, to);
// Chromium uses 1-based page ranges, so increment each by 1.
range.SetIntKey(printing::kSettingPageRangeFrom, from + 1);
range.SetIntKey(printing::kSettingPageRangeTo, to + 1);
page_range_list.Append(std::move(range));
} else {
continue;

View File

@@ -48,6 +48,8 @@
#endif
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/common/view_type.h"
namespace extensions {
class ScriptExecutor;
}
@@ -144,7 +146,7 @@ class WebContents : public gin::Wrappable<WebContents>,
public mojom::ElectronBrowser {
public:
enum class Type {
BACKGROUND_PAGE, // A DevTools extension background page.
BACKGROUND_PAGE, // An extension background page.
BROWSER_WINDOW, // Used by BrowserWindow.
BROWSER_VIEW, // Used by BrowserView.
REMOTE, // Thin wrap around an existing WebContents.
@@ -453,6 +455,12 @@ class WebContents : public gin::Wrappable<WebContents>,
gin::Handle<class Session> session,
const gin_helper::Dictionary& options);
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void InitWithExtensionView(v8::Isolate* isolate,
content::WebContents* web_contents,
extensions::ViewType view_type);
#endif
// content::WebContentsDelegate:
bool DidAddMessageToConsole(content::WebContents* source,
blink::mojom::ConsoleMessageLevel level,

View File

@@ -97,6 +97,7 @@
#include "shell/common/options_switches.h"
#include "shell/common/platform_util.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/mojom/badging/badging.mojom.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/native_theme/native_theme.h"
#include "v8/include/v8.h"
@@ -1573,6 +1574,12 @@ void ElectronBrowserClient::BindHostReceiverForRenderer(
#endif
}
void BindBadgeManagerFrameReceiver(
content::RenderFrameHost* frame,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
LOG(WARNING) << "The Chromium Badging API is not available in Electron";
}
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void BindMimeHandlerService(
content::RenderFrameHost* frame_host,
@@ -1609,6 +1616,8 @@ void ElectronBrowserClient::RegisterBrowserInterfaceBindersForFrame(
mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
map->Add<network_hints::mojom::NetworkHintsHandler>(
base::BindRepeating(&BindNetworkHintsHandler));
map->Add<blink::mojom::BadgeService>(
base::BindRepeating(&BindBadgeManagerFrameReceiver));
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
map->Add<extensions::mime_handler::MimeHandlerService>(
base::BindRepeating(&BindMimeHandlerService));

View File

@@ -0,0 +1,235 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(sentialx): emit relevant events in Electron's session?
#include "shell/browser/extensions/api/management/electron_management_api_delegate.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "chrome/common/extensions/extension_metrics.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/web_application_info.h"
#include "chrome/common/webui_url_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/api/management/management_api.h"
#include "extensions/browser/api/management/management_api_constants.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/api/management.h"
#include "extensions/common/extension.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
namespace {
class ManagementSetEnabledFunctionInstallPromptDelegate
: public extensions::InstallPromptDelegate {
public:
ManagementSetEnabledFunctionInstallPromptDelegate(
content::WebContents* web_contents,
content::BrowserContext* browser_context,
const extensions::Extension* extension,
const base::Callback<void(bool)>& callback) {
// TODO(sentialx): emit event
}
~ManagementSetEnabledFunctionInstallPromptDelegate() override {}
private:
base::WeakPtrFactory<ManagementSetEnabledFunctionInstallPromptDelegate>
weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ManagementSetEnabledFunctionInstallPromptDelegate);
};
class ManagementUninstallFunctionUninstallDialogDelegate
: public extensions::UninstallDialogDelegate {
public:
ManagementUninstallFunctionUninstallDialogDelegate(
extensions::ManagementUninstallFunctionBase* function,
const extensions::Extension* target_extension,
bool show_programmatic_uninstall_ui) {
// TODO(sentialx): emit event
}
~ManagementUninstallFunctionUninstallDialogDelegate() override {}
private:
DISALLOW_COPY_AND_ASSIGN(ManagementUninstallFunctionUninstallDialogDelegate);
};
} // namespace
ElectronManagementAPIDelegate::ElectronManagementAPIDelegate() {}
ElectronManagementAPIDelegate::~ElectronManagementAPIDelegate() {}
void ElectronManagementAPIDelegate::LaunchAppFunctionDelegate(
const extensions::Extension* extension,
content::BrowserContext* context) const {
// TODO(sentialx): emit event
extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_EXTENSION_API,
extension->GetType());
}
GURL ElectronManagementAPIDelegate::GetFullLaunchURL(
const extensions::Extension* extension) const {
return extensions::AppLaunchInfo::GetFullLaunchURL(extension);
}
extensions::LaunchType ElectronManagementAPIDelegate::GetLaunchType(
const extensions::ExtensionPrefs* prefs,
const extensions::Extension* extension) const {
// TODO(sentialx)
return extensions::LAUNCH_TYPE_DEFAULT;
}
void ElectronManagementAPIDelegate::
GetPermissionWarningsByManifestFunctionDelegate(
extensions::ManagementGetPermissionWarningsByManifestFunction* function,
const std::string& manifest_str) const {
data_decoder::DataDecoder::ParseJsonIsolated(
manifest_str,
base::BindOnce(
&extensions::ManagementGetPermissionWarningsByManifestFunction::
OnParse,
function));
}
std::unique_ptr<extensions::InstallPromptDelegate>
ElectronManagementAPIDelegate::SetEnabledFunctionDelegate(
content::WebContents* web_contents,
content::BrowserContext* browser_context,
const extensions::Extension* extension,
const base::Callback<void(bool)>& callback) const {
return std::unique_ptr<ManagementSetEnabledFunctionInstallPromptDelegate>(
new ManagementSetEnabledFunctionInstallPromptDelegate(
web_contents, browser_context, extension, callback));
}
std::unique_ptr<extensions::UninstallDialogDelegate>
ElectronManagementAPIDelegate::UninstallFunctionDelegate(
extensions::ManagementUninstallFunctionBase* function,
const extensions::Extension* target_extension,
bool show_programmatic_uninstall_ui) const {
return std::unique_ptr<extensions::UninstallDialogDelegate>(
new ManagementUninstallFunctionUninstallDialogDelegate(
function, target_extension, show_programmatic_uninstall_ui));
}
bool ElectronManagementAPIDelegate::CreateAppShortcutFunctionDelegate(
extensions::ManagementCreateAppShortcutFunction* function,
const extensions::Extension* extension,
std::string* error) const {
return false; // TODO(sentialx): route event and return true
}
std::unique_ptr<extensions::AppForLinkDelegate>
ElectronManagementAPIDelegate::GenerateAppForLinkFunctionDelegate(
extensions::ManagementGenerateAppForLinkFunction* function,
content::BrowserContext* context,
const std::string& title,
const GURL& launch_url) const {
// TODO(sentialx)
return nullptr;
}
bool ElectronManagementAPIDelegate::CanContextInstallWebApps(
content::BrowserContext* context) const {
// TODO(sentialx)
return false;
}
void ElectronManagementAPIDelegate::InstallOrLaunchReplacementWebApp(
content::BrowserContext* context,
const GURL& web_app_url,
InstallOrLaunchWebAppCallback callback) const {
// TODO(sentialx)
}
bool ElectronManagementAPIDelegate::CanContextInstallAndroidApps(
content::BrowserContext* context) const {
return false;
}
void ElectronManagementAPIDelegate::CheckAndroidAppInstallStatus(
const std::string& package_name,
AndroidAppInstallStatusCallback callback) const {
std::move(callback).Run(false);
}
void ElectronManagementAPIDelegate::InstallReplacementAndroidApp(
const std::string& package_name,
InstallAndroidAppCallback callback) const {
std::move(callback).Run(false);
}
void ElectronManagementAPIDelegate::EnableExtension(
content::BrowserContext* context,
const std::string& extension_id) const {
// const extensions::Extension* extension =
// extensions::ExtensionRegistry::Get(context)->GetExtensionById(
// extension_id, extensions::ExtensionRegistry::EVERYTHING);
// TODO(sentialx): we don't have ExtensionService
// If the extension was disabled for a permissions increase, the Management
// API will have displayed a re-enable prompt to the user, so we know it's
// safe to grant permissions here.
// extensions::ExtensionSystem::Get(context)
// ->extension_service()
// ->GrantPermissionsAndEnableExtension(extension);
}
void ElectronManagementAPIDelegate::DisableExtension(
content::BrowserContext* context,
const extensions::Extension* source_extension,
const std::string& extension_id,
extensions::disable_reason::DisableReason disable_reason) const {
// TODO(sentialx): we don't have ExtensionService
// extensions::ExtensionSystem::Get(context)
// ->extension_service()
// ->DisableExtensionWithSource(source_extension, extension_id,
// disable_reason);
}
bool ElectronManagementAPIDelegate::UninstallExtension(
content::BrowserContext* context,
const std::string& transient_extension_id,
extensions::UninstallReason reason,
base::string16* error) const {
// TODO(sentialx): we don't have ExtensionService
// return extensions::ExtensionSystem::Get(context)
// ->extension_service()
// ->UninstallExtension(transient_extension_id, reason, error);
return false;
}
void ElectronManagementAPIDelegate::SetLaunchType(
content::BrowserContext* context,
const std::string& extension_id,
extensions::LaunchType launch_type) const {
// TODO(sentialx)
// extensions::SetLaunchType(context, extension_id, launch_type);
}
GURL ElectronManagementAPIDelegate::GetIconURL(
const extensions::Extension* extension,
int icon_size,
ExtensionIconSet::MatchType match,
bool grayscale) const {
GURL icon_url(base::StringPrintf("%s%s/%d/%d%s",
chrome::kChromeUIExtensionIconURL,
extension->id().c_str(), icon_size, match,
grayscale ? "?grayscale=true" : ""));
CHECK(icon_url.is_valid());
return icon_url;
}

View File

@@ -0,0 +1,86 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_
#define SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_
#include <memory>
#include <string>
#include "base/task/cancelable_task_tracker.h"
#include "extensions/browser/api/management/management_api_delegate.h"
class ElectronManagementAPIDelegate : public extensions::ManagementAPIDelegate {
public:
ElectronManagementAPIDelegate();
~ElectronManagementAPIDelegate() override;
// ManagementAPIDelegate.
void LaunchAppFunctionDelegate(
const extensions::Extension* extension,
content::BrowserContext* context) const override;
GURL GetFullLaunchURL(const extensions::Extension* extension) const override;
extensions::LaunchType GetLaunchType(
const extensions::ExtensionPrefs* prefs,
const extensions::Extension* extension) const override;
void GetPermissionWarningsByManifestFunctionDelegate(
extensions::ManagementGetPermissionWarningsByManifestFunction* function,
const std::string& manifest_str) const override;
std::unique_ptr<extensions::InstallPromptDelegate> SetEnabledFunctionDelegate(
content::WebContents* web_contents,
content::BrowserContext* browser_context,
const extensions::Extension* extension,
const base::Callback<void(bool)>& callback) const override;
std::unique_ptr<extensions::UninstallDialogDelegate>
UninstallFunctionDelegate(
extensions::ManagementUninstallFunctionBase* function,
const extensions::Extension* target_extension,
bool show_programmatic_uninstall_ui) const override;
bool CreateAppShortcutFunctionDelegate(
extensions::ManagementCreateAppShortcutFunction* function,
const extensions::Extension* extension,
std::string* error) const override;
std::unique_ptr<extensions::AppForLinkDelegate>
GenerateAppForLinkFunctionDelegate(
extensions::ManagementGenerateAppForLinkFunction* function,
content::BrowserContext* context,
const std::string& title,
const GURL& launch_url) const override;
bool CanContextInstallWebApps(
content::BrowserContext* context) const override;
void InstallOrLaunchReplacementWebApp(
content::BrowserContext* context,
const GURL& web_app_url,
ManagementAPIDelegate::InstallOrLaunchWebAppCallback callback)
const override;
bool CanContextInstallAndroidApps(
content::BrowserContext* context) const override;
void CheckAndroidAppInstallStatus(
const std::string& package_name,
ManagementAPIDelegate::AndroidAppInstallStatusCallback callback)
const override;
void InstallReplacementAndroidApp(
const std::string& package_name,
ManagementAPIDelegate::InstallAndroidAppCallback callback) const override;
void EnableExtension(content::BrowserContext* context,
const std::string& extension_id) const override;
void DisableExtension(
content::BrowserContext* context,
const extensions::Extension* source_extension,
const std::string& extension_id,
extensions::disable_reason::DisableReason disable_reason) const override;
bool UninstallExtension(content::BrowserContext* context,
const std::string& transient_extension_id,
extensions::UninstallReason reason,
base::string16* error) const override;
void SetLaunchType(content::BrowserContext* context,
const std::string& extension_id,
extensions::LaunchType launch_type) const override;
GURL GetIconURL(const extensions::Extension* extension,
int icon_size,
ExtensionIconSet::MatchType match,
bool grayscale) const override;
};
#endif // SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_

View File

@@ -52,9 +52,9 @@ std::pair<scoped_refptr<const Extension>, std::string> LoadUnpacked(
// Log warnings.
if (extension->install_warnings().size()) {
warnings += "Warnings loading extension at " +
base::UTF16ToUTF8(extension_dir.LossyDisplayName()) + ": ";
base::UTF16ToUTF8(extension_dir.LossyDisplayName()) + ":\n";
for (const auto& warning : extension->install_warnings()) {
warnings += warning.message + " ";
warnings += " " + warning.message + "\n";
}
}

View File

@@ -25,6 +25,7 @@
#include "extensions/browser/api/app_runtime/app_runtime_api.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/info_map.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/null_app_sorting.h"
#include "extensions/browser/quota_service.h"
@@ -91,6 +92,8 @@ void ElectronExtensionSystem::InitForRegularProfile(bool extensions_enabled) {
if (!browser_context_->IsOffTheRecord())
LoadComponentExtensions();
management_policy_.reset(new ManagementPolicy);
}
std::unique_ptr<base::DictionaryValue> ParseManifest(
@@ -130,7 +133,7 @@ RuntimeData* ElectronExtensionSystem::runtime_data() {
}
ManagementPolicy* ElectronExtensionSystem::management_policy() {
return nullptr;
return management_policy_.get();
}
ServiceWorkerManager* ElectronExtensionSystem::service_worker_manager() {

View File

@@ -104,6 +104,7 @@ class ElectronExtensionSystem : public ExtensionSystem {
std::unique_ptr<QuotaService> quota_service_;
std::unique_ptr<SharedUserScriptManager> shared_user_script_manager_;
std::unique_ptr<AppSorting> app_sorting_;
std::unique_ptr<ManagementPolicy> management_policy_;
std::unique_ptr<ElectronExtensionLoader> extension_loader_;

View File

@@ -9,6 +9,7 @@
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h"
#include "printing/buildflags/buildflags.h"
#include "shell/browser/extensions/api/management/electron_management_api_delegate.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#include "shell/browser/extensions/electron_messaging_delegate.h"
@@ -59,6 +60,11 @@ void ElectronExtensionsAPIClient::AttachWebContentsHelpers(
#endif
}
ManagementAPIDelegate*
ElectronExtensionsAPIClient::CreateManagementAPIDelegate() const {
return new ElectronManagementAPIDelegate;
}
std::unique_ptr<MimeHandlerViewGuestDelegate>
ElectronExtensionsAPIClient::CreateMimeHandlerViewGuestDelegate(
MimeHandlerViewGuest* guest) const {

View File

@@ -25,6 +25,7 @@ class ElectronExtensionsAPIClient : public ExtensionsAPIClient {
std::unique_ptr<MimeHandlerViewGuestDelegate>
CreateMimeHandlerViewGuestDelegate(
MimeHandlerViewGuest* guest) const override;
ManagementAPIDelegate* CreateManagementAPIDelegate() const override;
private:
std::unique_ptr<ElectronMessagingDelegate> messaging_delegate_;

View File

@@ -32,8 +32,6 @@ namespace electron {
// An ExtensionsBrowserClient that supports a single content::BrowserContext
// with no related incognito context.
// Must be initialized via InitWithBrowserContext() once the BrowserContext is
// created.
class ElectronExtensionsBrowserClient
: public extensions::ExtensionsBrowserClient {
public:
@@ -122,11 +120,6 @@ class ElectronExtensionsBrowserClient
content::RenderFrameHost* render_frame_host,
const extensions::Extension* extension) const override;
// |context| is the single BrowserContext used for IsValidContext().
// |pref_service| is used for GetPrefServiceForContext().
void InitWithBrowserContext(content::BrowserContext* context,
PrefService* pref_service);
// Sets the API client.
void SetAPIClientForTest(extensions::ExtensionsAPIClient* api_client);

View File

@@ -43,10 +43,7 @@
// https://developer.apple.com/documentation/foundation/nsusernotificationactivationtype?language=objc
if (notif.activationType ==
NSUserNotificationActivationTypeContentsClicked) {
// If a notification with a reply button is clicked and the user has not
// yet replied, we do not want to destroy the notification.
bool should_destroy = ![notif hasReplyButton];
notification->NotificationClicked(should_destroy);
notification->NotificationClicked();
} else if (notif.activationType ==
NSUserNotificationActivationTypeActionButtonClicked) {
notification->NotificationActivated();

View File

@@ -21,12 +21,10 @@ Notification::~Notification() {
delegate()->NotificationDestroyed();
}
void Notification::NotificationClicked(bool should_destroy) {
void Notification::NotificationClicked() {
if (delegate())
delegate()->NotificationClick();
if (should_destroy)
Destroy();
Destroy();
}
void Notification::NotificationDismissed() {

View File

@@ -54,7 +54,7 @@ class Notification {
virtual void Dismiss() = 0;
// Should be called by derived classes.
void NotificationClicked(bool should_destroy = true);
void NotificationClicked();
void NotificationDismissed();
void NotificationFailed();

View File

@@ -476,7 +476,7 @@ IFACEMETHODIMP ToastEventHandler::Invoke(
IInspectable* args) {
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&Notification::NotificationClicked, notification_, true));
base::BindOnce(&Notification::NotificationClicked, notification_));
if (IsDebuggingNotifications())
LOG(INFO) << "Notification clicked";

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 11,0,0,5
PRODUCTVERSION 11,0,0,5
FILEVERSION 12,0,0,20200911
PRODUCTVERSION 12,0,0,20200911
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron"
VALUE "FileVersion", "11.0.0"
VALUE "FileVersion", "12.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", "11.0.0"
VALUE "ProductVersion", "12.0.0"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -94,6 +94,25 @@ bool MoveItemToTrash(gin::Arguments* args) {
return platform_util::MoveItemToTrash(full_path, delete_on_fail);
}
v8::Local<v8::Promise> TrashItem(v8::Isolate* isolate,
const base::FilePath& path) {
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
platform_util::TrashItem(
path, base::BindOnce(
[](gin_helper::Promise<void> promise, bool success,
const std::string& error) {
if (success) {
promise.Resolve();
} else {
promise.RejectWithErrorMessage(error);
}
},
std::move(promise)));
return handle;
}
#if defined(OS_WIN)
bool WriteShortcutLink(const base::FilePath& shortcut_path,
gin_helper::Arguments* args) {
@@ -158,6 +177,7 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("openPath", &OpenPath);
dict.SetMethod("openExternal", &OpenExternal);
dict.SetMethod("moveItemToTrash", &MoveItemToTrash);
dict.SetMethod("trashItem", &TrashItem);
dict.SetMethod("beep", &platform_util::Beep);
#if defined(OS_WIN)
dict.SetMethod("writeShortcutLink", &WriteShortcutLink);

View File

@@ -5,5 +5,65 @@
"extension"
],
"location": "component"
}
}
},
"management": [
{
"channel": "stable",
"extension_types": [
"extension",
"legacy_packaged_app"
]
},
{
"channel": "stable",
"extension_types": [
"platform_app"
],
"whitelist": [
"AE27D69DBE571F4B1694F05C89B710C646792231", // Published ADT
// TODO(grv): clean up once Apps developer tool is published.
"5107DE9024C329EEA9C9A72D94C16723790C6422", // Apps Developer Tool.
"8C0B1873FFFB65E4D0F4D772879F7304CEF125C2", // Apps Editor old.
"FA0501B579070BB9CBD4FCAEC8CB0EDF22BA2F04", // Apps Editor published.
"EE17C698905F7F2E6DDC87C9C30F11E164C829F4", // Watchdog (Activity Log)
"90113DA9516526D24DAF156C629CC41C049E8882", // Watchdog Test Version
"4A4EA121622FCA3D78ED2AB534197F43D7189EE0", // Spark nightly build.
"9FDE6E7F06FCFA11D9A05041C7FF6D8AE662F5D1", // Spark release.
"50B4A905D522C06E27CA6D099E3E54BDA1F152C5", // Spark Beta channel.
"BA0C8BB92084C9741312D90D3EA882526853455F", // Spark dev channel.
"5F57A9AE8DFF5D6BB09DF8606270402612E871E5", // http://crbug.com/422624
"46578A13607D38F1DC8E280C4F499FB0A2F9565C", // http://crbug.com/819404
"898FB5A39687D210766B8998BA4530B99C9E6586", // http://crbug.com/819404
"82F30B65397BC3E4ADE627BBD857AB8A58210648", // http://crbug.com/819404
"C74B2AF138F9EDECD04D0965AB36CA66C8290466" // http://crbug.com/957772
]
},
{
"channel": "stable",
"extension_types": [
"hosted_app"
],
"whitelist": [
"B44D08FD98F1523ED5837D78D0A606EA9D6206E5" // Web Store
]
},
{
"channel": "stable",
"extension_types": [
"platform_app"
],
"session_types": [
"kiosk"
]
},
{
"channel": "stable",
"dependencies": [
"behavior:imprivata_login_screen_extension"
],
"extension_types": [
"login_screen_extension"
]
}
]
}

View File

@@ -38,6 +38,7 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = {
APIPermissionInfo::kFlagInternal},
{APIPermission::kResourcesPrivate, "resourcesPrivate",
APIPermissionInfo::kFlagCannotBeOptional},
{APIPermission::kManagement, "management"},
};
base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
return base::make_span(permissions_to_register);

View File

@@ -400,9 +400,24 @@ node::Environment* NodeBindings::CreateEnvironment(
std::unique_ptr<const char*[]> c_argv = StringVectorToArgArray(args);
isolate_data_ =
node::CreateIsolateData(context->GetIsolate(), uv_loop_, platform);
node::Environment* env = node::CreateEnvironment(
isolate_data_, context, args.size(), c_argv.get(), 0, nullptr);
DCHECK(env);
node::Environment* env;
if (browser_env_ != BrowserEnvironment::BROWSER) {
v8::TryCatch try_catch(context->GetIsolate());
env = node::CreateEnvironment(isolate_data_, context, args.size(),
c_argv.get(), 0, nullptr);
DCHECK(env);
// This will only be caught when something has gone terrible wrong as all
// electron scripts are wrapped in a try {} catch {} in run-compiler.js
if (try_catch.HasCaught()) {
LOG(ERROR) << "Failed to initialize node environment in process: "
<< process_type;
}
} else {
env = node::CreateEnvironment(isolate_data_, context, args.size(),
c_argv.get(), 0, nullptr);
DCHECK(env);
}
// Clean up the global _noBrowserGlobals that we unironically injected into
// the global scope

View File

@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "shell/common/node_util.h"
#include "base/logging.h"
#include "shell/common/node_includes.h"
#include "third_party/electron_node/src/node_native_module_env.h"
@@ -17,6 +18,7 @@ v8::MaybeLocal<v8::Value> CompileAndCall(
std::vector<v8::Local<v8::Value>>* arguments,
node::Environment* optional_env) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch try_catch(isolate);
v8::MaybeLocal<v8::Function> compiled =
node::native_module::NativeModuleEnv::LookupAndCompile(
context, id, parameters, optional_env);
@@ -24,8 +26,14 @@ v8::MaybeLocal<v8::Value> CompileAndCall(
return v8::MaybeLocal<v8::Value>();
}
v8::Local<v8::Function> fn = compiled.ToLocalChecked().As<v8::Function>();
return fn->Call(context, v8::Null(isolate), arguments->size(),
arguments->data());
v8::MaybeLocal<v8::Value> ret = fn->Call(
context, v8::Null(isolate), arguments->size(), arguments->data());
// This will only be caught when something has gone terrible wrong as all
// electron scripts are wrapped in a try {} catch {} in run-compiler.js
if (try_catch.HasCaught()) {
LOG(ERROR) << "Failed to CompileAndCall electron script: " << id;
}
return ret;
}
} // namespace util

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/platform_util.h"
#include <utility>
#include "base/bind.h"
#include "base/task/thread_pool.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "shell/common/platform_util_internal.h"
namespace platform_util {
void TrashItemOnBlockingThread(
const base::FilePath& full_path,
base::OnceCallback<void(bool, const std::string&)> callback) {
std::string error;
bool success = internal::PlatformTrashItem(full_path, &error);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), success, error));
}
void TrashItem(const base::FilePath& full_path,
base::OnceCallback<void(bool, const std::string&)> callback) {
// XXX: is continue_on_shutdown right?
base::ThreadPool::PostTask(FROM_HERE,
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&TrashItemOnBlockingThread,
full_path, std::move(callback)));
}
} // namespace platform_util

View File

@@ -40,9 +40,13 @@ void OpenExternal(const GURL& url,
const OpenExternalOptions& options,
OpenCallback callback);
// Move a file to trash.
// Move a file to trash. (Deprecated.)
bool MoveItemToTrash(const base::FilePath& full_path, bool delete_on_fail);
// Move a file to trash, asynchronously.
void TrashItem(const base::FilePath& full_path,
base::OnceCallback<void(bool, const std::string&)> callback);
void Beep();
#if defined(OS_WIN)

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_COMMON_PLATFORM_UTIL_INTERNAL_H_
#define SHELL_COMMON_PLATFORM_UTIL_INTERNAL_H_
#include "shell/common/platform_util.h"
#include <string>
namespace base {
class FilePath;
}
namespace platform_util {
namespace internal {
// Called by platform_util.cc on to invoke platform specific logic to move
// |path| to trash using a suitable handler.
bool PlatformTrashItem(const base::FilePath& path, std::string* error);
} // namespace internal
} // namespace platform_util
#endif // SHELL_COMMON_PLATFORM_UTIL_INTERNAL_H_

View File

@@ -19,6 +19,7 @@
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "shell/common/platform_util_internal.h"
#include "ui/gtk/gtk_util.h"
#include "url/gurl.h"
@@ -220,6 +221,19 @@ bool MoveItemToTrash(const base::FilePath& full_path, bool delete_on_fail) {
return XDGUtil(argv, base::FilePath(), true, platform_util::OpenCallback());
}
namespace internal {
bool PlatformTrashItem(const base::FilePath& full_path, std::string* error) {
if (!MoveItemToTrash(full_path, false)) {
// TODO(nornagon): at least include the exit code?
*error = "Failed to move item to trash";
return false;
}
return true;
}
} // namespace internal
void Beep() {
// echo '\a' > /dev/console
FILE* fp = fopen("/dev/console", "a");

View File

@@ -117,10 +117,13 @@ void OpenExternal(const GURL& url,
});
}
bool MoveItemToTrash(const base::FilePath& full_path, bool delete_on_fail) {
bool MoveItemToTrashWithError(const base::FilePath& full_path,
bool delete_on_fail,
std::string* error) {
NSString* path_string = base::SysUTF8ToNSString(full_path.value());
if (!path_string) {
LOG(WARNING) << "Invalid file path " << full_path.value();
*error = "Invalid file path: " + full_path.value();
LOG(WARNING) << *error;
return false;
}
@@ -142,14 +145,27 @@ bool MoveItemToTrash(const base::FilePath& full_path, bool delete_on_fail) {
}
if (!did_trash) {
*error = base::SysNSStringToUTF8([err localizedDescription]);
LOG(WARNING) << "NSWorkspace failed to move file " << full_path.value()
<< " to trash: "
<< base::SysNSStringToUTF8([err localizedDescription]);
<< " to trash: " << *error;
}
return did_trash;
}
bool MoveItemToTrash(const base::FilePath& path, bool delete_on_fail) {
std::string error; // ignored
return MoveItemToTrashWithError(path, delete_on_fail, &error);
}
namespace internal {
bool PlatformTrashItem(const base::FilePath& full_path, std::string* error) {
return MoveItemToTrashWithError(full_path, false, error);
}
} // namespace internal
void Beep() {
NSBeep();
}

View File

@@ -347,15 +347,15 @@ void OpenExternal(const GURL& url,
std::move(callback));
}
bool MoveItemToTrash(const base::FilePath& path, bool delete_on_fail) {
base::win::ScopedCOMInitializer com_initializer;
if (!com_initializer.Succeeded())
return false;
bool MoveItemToTrashWithError(const base::FilePath& path,
bool delete_on_fail,
std::string* error) {
Microsoft::WRL::ComPtr<IFileOperation> pfo;
if (FAILED(::CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&pfo))))
IID_PPV_ARGS(&pfo)))) {
*error = "Failed to create FileOperation instance";
return false;
}
// Elevation prompt enabled for UAC protected files. This overrides the
// SILENT, NO_UI and NOERRORUI flags.
@@ -365,38 +365,77 @@ bool MoveItemToTrash(const base::FilePath& path, bool delete_on_fail) {
// ALLOWUNDO in favor of ADDUNDORECORD.
if (FAILED(pfo->SetOperationFlags(
FOF_NO_UI | FOFX_ADDUNDORECORD | FOF_NOERRORUI | FOF_SILENT |
FOFX_SHOWELEVATIONPROMPT | FOFX_RECYCLEONDELETE)))
FOFX_SHOWELEVATIONPROMPT | FOFX_RECYCLEONDELETE))) {
*error = "Failed to set operation flags";
return false;
}
} else {
// For Windows 7 and Vista, RecycleOnDelete is the default behavior.
if (FAILED(pfo->SetOperationFlags(FOF_NO_UI | FOF_ALLOWUNDO |
FOF_NOERRORUI | FOF_SILENT |
FOFX_SHOWELEVATIONPROMPT)))
FOFX_SHOWELEVATIONPROMPT))) {
*error = "Failed to set operation flags";
return false;
}
}
// Create an IShellItem from the supplied source path.
Microsoft::WRL::ComPtr<IShellItem> delete_item;
if (FAILED(SHCreateItemFromParsingName(
path.value().c_str(), NULL,
IID_PPV_ARGS(delete_item.GetAddressOf()))))
IID_PPV_ARGS(delete_item.GetAddressOf())))) {
*error = "Failed to parse path";
return false;
}
Microsoft::WRL::ComPtr<IFileOperationProgressSink> delete_sink(
new DeleteFileProgressSink);
if (!delete_sink)
if (!delete_sink) {
*error = "Failed to create delete sink";
return false;
}
BOOL pfAnyOperationsAborted;
// Processes the queued command DeleteItem. This will trigger
// the DeleteFileProgressSink to check for Recycle Bin.
return SUCCEEDED(pfo->DeleteItem(delete_item.Get(), delete_sink.Get())) &&
SUCCEEDED(pfo->PerformOperations()) &&
SUCCEEDED(pfo->GetAnyOperationsAborted(&pfAnyOperationsAborted)) &&
!pfAnyOperationsAborted;
if (!SUCCEEDED(pfo->DeleteItem(delete_item.Get(), delete_sink.Get()))) {
*error = "Failed to enqueue DeleteItem command";
return false;
}
if (!SUCCEEDED(pfo->PerformOperations())) {
*error = "Failed to perform delete operation";
return false;
}
if (!SUCCEEDED(pfo->GetAnyOperationsAborted(&pfAnyOperationsAborted))) {
*error = "Failed to check operation status";
return false;
}
if (pfAnyOperationsAborted) {
*error = "Operation was aborted";
return false;
}
return true;
}
bool MoveItemToTrash(const base::FilePath& path, bool delete_on_fail) {
std::string error; // ignored
base::win::ScopedCOMInitializer com_initializer;
return MoveItemToTrashWithError(path, delete_on_fail, &error);
}
namespace internal {
bool PlatformTrashItem(const base::FilePath& full_path, std::string* error) {
return MoveItemToTrashWithError(full_path, false, error);
}
} // namespace internal
bool GetFolderPath(int key, base::FilePath* result) {
wchar_t system_buffer[MAX_PATH];

View File

@@ -16,8 +16,7 @@ enum WorldIDs : int32_t {
// IDs created internally by Chrome.
ISOLATED_WORLD_ID = 999,
// Numbers for isolated worlds for extensions are set in
// lib/renderer/content-script-injector.ts, and are greater than or equal to
// Numbers for isolated worlds for extensions are greater than or equal to
// this number, up to ISOLATED_WORLD_ID_EXTENSIONS_END.
ISOLATED_WORLD_ID_EXTENSIONS = 1 << 20,

View File

@@ -232,6 +232,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
await remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true
});
}, [port]);
@@ -271,6 +272,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true,
extra: { longParam: 'a'.repeat(100000) }
});
@@ -287,6 +289,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
remotely((port: number, kKeyLengthMax: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true,
extra: {
['a'.repeat(kKeyLengthMax + 10)]: 'value',
@@ -399,6 +402,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
await remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true
});
}, [port]);

View File

@@ -112,4 +112,19 @@ describe('shell module', () => {
expect(await fs.pathExists(tempPath)).to.be.false();
});
});
describe('shell.trashItem()', () => {
it('moves an item to the trash', async () => {
const dir = await fs.mkdtemp(path.resolve(app.getPath('temp'), 'electron-shell-spec-'));
const filename = path.join(dir, 'temp-to-be-deleted');
await fs.writeFile(filename, 'dummy-contents');
await shell.trashItem(filename);
expect(fs.existsSync(filename)).to.be.false();
});
it('throws when called with a nonexistent path', async () => {
const filename = path.join(app.getPath('temp'), 'does-not-exist');
await expect(shell.trashItem(filename)).to.eventually.be.rejected();
});
});
});

View File

@@ -1257,10 +1257,6 @@ 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);
});
});
it('opens when loading a pdf resource in a iframe', async () => {
@@ -1268,10 +1264,6 @@ 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);
});
});
});

View File

@@ -42,7 +42,7 @@ export const emittedNTimes = async (emitter: NodeJS.EventEmitter, eventName: str
};
export const emittedUntil = async (emitter: NodeJS.EventEmitter, eventName: string, untilFn: Function) => {
const p = new Promise<any[][]>(resolve => {
const p = new Promise<any[]>(resolve => {
const handler = (...args: any[]) => {
if (untilFn(...args)) {
emitter.removeListener(eventName, handler);

View File

@@ -37,6 +37,26 @@ describe('chrome extensions', () => {
});
});
it('does not crash when using chrome.management', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });
w.loadURL('about:blank');
await emittedOnce(w.webContents, 'dom-ready');
await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
const args: any = await emittedOnce(app, 'web-contents-created');
const wc: Electron.WebContents = args[1];
await expect(wc.executeJavaScript(`
(() => {
return new Promise((resolve) => {
chrome.management.getSelf((info) => {
resolve(info);
});
})
})();
`)).to.eventually.have.property('id');
});
it('can open WebSQLDatabase in a background page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });
@@ -297,16 +317,28 @@ describe('chrome extensions', () => {
const receivedMessage = await w.webContents.executeJavaScript('window.completionPromise');
expect(receivedMessage).to.deep.equal({ some: 'message' });
});
});
it('has session in background page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
const promise = emittedOnce(app, 'web-contents-created');
await w.loadURL('about:blank');
const [, bgPageContents] = await promise;
expect(bgPageContents.session).to.not.equal(undefined);
it('has session in background page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
const promise = emittedOnce(app, 'web-contents-created');
await w.loadURL('about:blank');
const [, bgPageContents] = await promise;
expect(bgPageContents.session).to.not.equal(undefined);
});
it('can open devtools of background page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
const promise = emittedOnce(app, 'web-contents-created');
await w.loadURL('about:blank');
const [, bgPageContents] = await promise;
expect(bgPageContents.getType()).to.equal('backgroundPage');
bgPageContents.openDevTools();
bgPageContents.closeDevTools();
});
});
describe('devtools extensions', () => {

View File

@@ -12,6 +12,7 @@ const addGlobalParam = app.commandLine.getSwitchValue('add-global-param')?.split
crashReporter.start({
productName: 'Zombies',
companyName: 'Umbrella Corporation',
compress: false,
uploadToServer,
submitURL: url,
ignoreSystemCrashHandler: true,

View File

@@ -2,6 +2,7 @@ if (process.platform === 'linux') {
process.crashReporter.start({
submitURL: process.argv[2],
productName: 'Zombies',
compress: false,
globalExtra: {
_version: process.argv[3]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/25216/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Wed, 02 Sep 2020 15:55:20 GMT","etag":"W/\"dc98adeb828ec1f60e0be31a73a31f30\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"E876:6741:2819ABF:5CAD2C6:5F4FC068","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4943","x-ratelimit-reset":"1599063118","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/684017257","html_url":"https://github.com/electron/electron/pull/25216#issuecomment-684017257","issue_url":"https://api.github.com/repos/electron/electron/issues/25216","id":684017257,"node_id":"MDEyOklzc3VlQ29tbWVudDY4NDAxNzI1Nw==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-08-31T20:22:50Z","updated_at":"2020-08-31T20:22:50Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> * Fixes the following issues for frameless when maximized on Windows:\r\n> * fix unreachable task bar when auto hidden with position top\r\n> * fix 1px extending to secondary monitor\r\n> * fix 1px overflowing into taskbar at certain resolutions\r\n> * fix white line on top of window under 4k resolutions","performed_via_github_app":null}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -106,6 +106,9 @@ describe('release notes', () => {
const newTropFix = new Commit('a6ff42c190cb5caf8f3e217748e49183a951491b', 'fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22750)');
const oldTropFix = new Commit('8751f485c5a6c8c78990bfd55a4350700f81f8cd', 'fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22749)');
// a PR that has unusual note formatting
const sublist = new Commit('61dc1c88fd34a3e8fff80c80ed79d0455970e610', 'fix: client area inset calculation when maximized for framless windows (#25052) (#25216)');
before(() => {
// location of relase-notes' octokit reply cache
const fixtureDir = path.resolve(__dirname, 'fixtures', 'release-notes');
@@ -151,6 +154,34 @@ describe('release notes', () => {
expect(results.feat[0].note).to.equal(realText);
});
describe('rendering', () => {
it('removes redundant bullet points', async function () {
const testCommit = sublist;
const version = 'v10.1.1';
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
const results: any = await notes.get(oldBranch, newBranch, version);
const rendered: any = await notes.render(results);
expect(rendered).to.not.include('* *');
});
it('indents sublists', async function () {
const testCommit = sublist;
const version = 'v10.1.1';
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
const results: any = await notes.get(oldBranch, newBranch, version);
const rendered: any = await notes.render(results);
expect(rendered).to.include([
'* Fixed the following issues for frameless when maximized on Windows:',
' * fix unreachable task bar when auto hidden with position top',
' * fix 1px extending to secondary monitor',
' * fix 1px overflowing into taskbar at certain resolutions',
' * fix white line on top of window under 4k resolutions. [#25216]'].join('\n'));
});
});
// test that when you feed in different semantic commit types,
// the parser returns them in the results' correct category
describe('semantic commit', () => {

View File

@@ -625,7 +625,7 @@ describe('<webview> tag', function () {
sandbox: sandbox.toString()
});
const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
const [, , message] = await emittedOnce(webViewContents, 'console-message');
const [, , message] = await emittedUntil(webViewContents, 'console-message', (event: any, level: any, message: string) => !/deprecated/.test(message));
const typeOfRemote = JSON.parse(message);
expect(typeOfRemote).to.equal('object');

View File

@@ -173,13 +173,7 @@ describe('nativeImage module', () => {
const imageB = nativeImage.createFromBitmap(imageA.toBitmap(), imageA.getSize());
expect(imageB.getSize()).to.deep.equal({ width: 538, height: 190 });
let imageC;
// TODO fix nativeImage.createFromBuffer from bitmaps on WOA. See https://github.com/electron/electron/issues/25069
if (process.platform === 'win32' && process.arch === 'arm64') {
imageC = nativeImage.createFromBuffer(imageA.toPNG(), { ...imageA.getSize(), scaleFactor: 2.0 });
} else {
imageC = nativeImage.createFromBuffer(imageA.toBitmap(), { ...imageA.getSize(), scaleFactor: 2.0 });
}
const imageC = nativeImage.createFromBuffer(imageA.toBitmap(), { ...imageA.getSize(), scaleFactor: 2.0 });
expect(imageC.getSize()).to.deep.equal({ width: 269, height: 95 });
});
@@ -192,8 +186,7 @@ describe('nativeImage module', () => {
});
});
// TODO fix nativeImage.createFromBuffer on WOA. See https://github.com/electron/electron/issues/25069
ifdescribe(!(process.platform === 'win32' && process.arch === 'arm64'))('createFromBuffer(buffer, options)', () => {
describe('createFromBuffer(buffer, options)', () => {
it('returns an empty image when the buffer is empty', () => {
expect(nativeImage.createFromBuffer(Buffer.from([])).isEmpty()).to.be.true();
});
@@ -347,8 +340,7 @@ describe('nativeImage module', () => {
});
describe('createFromPath(path)', () => {
// TODO fix nativeImage.createFromPath on WOA. See https://github.com/electron/electron/issues/25069
ifit(!(process.platform === 'win32' && process.arch === 'arm64'))('returns an empty image for invalid paths', () => {
it('returns an empty image for invalid paths', () => {
expect(nativeImage.createFromPath('').isEmpty()).to.be.true();
expect(nativeImage.createFromPath('does-not-exist.png').isEmpty()).to.be.true();
expect(nativeImage.createFromPath('does-not-exist.ico').isEmpty()).to.be.true();
@@ -520,8 +512,7 @@ describe('nativeImage module', () => {
});
describe('addRepresentation()', () => {
// TODO fix nativeImage.createFromBuffer on WOA. See https://github.com/electron/electron/issues/25069
ifit(!(process.platform === 'win32' && process.arch === 'arm64'))('does not add representation when the buffer is too small', () => {
it('does not add representation when the buffer is too small', () => {
const image = nativeImage.createEmpty();
image.addRepresentation({

View File

@@ -17,6 +17,14 @@ const features = process._linkedBinding('electron_common_features');
describe('chromium feature', () => {
const fixtures = path.resolve(__dirname, 'fixtures');
describe('Badging API', () => {
it('does not crash', () => {
expect(() => {
navigator.setAppBadge(42);
}).to.not.throw();
});
});
describe('heap snapshot', () => {
it('does not crash', function () {
process._linkedBinding('electron_common_v8_util').takeHeapSnapshot();