mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
48 Commits
v11.0.0-be
...
v12.0.0-ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d56ca7360 | ||
|
|
cd455c8b40 | ||
|
|
acf5d487d2 | ||
|
|
dd781c4f63 | ||
|
|
98683190b4 | ||
|
|
1815b95e74 | ||
|
|
a829ae56b2 | ||
|
|
e6fbbf4325 | ||
|
|
f6df79b927 | ||
|
|
5a8046c994 | ||
|
|
ae5776041e | ||
|
|
f7d9d68e14 | ||
|
|
2c5c51afb9 | ||
|
|
860e14c0da | ||
|
|
31322400e7 | ||
|
|
4ad9bcb8b5 | ||
|
|
29c1248e96 | ||
|
|
a6b9f9d8e5 | ||
|
|
d305fe7d30 | ||
|
|
4dc09ea9dc | ||
|
|
4484e95fc8 | ||
|
|
03e60cce8b | ||
|
|
733d56e908 | ||
|
|
43485b8705 | ||
|
|
bda6378685 | ||
|
|
1b6534b326 | ||
|
|
e9e7eee25e | ||
|
|
7e698df8f3 | ||
|
|
e0611d0946 | ||
|
|
184e72fafd | ||
|
|
3745b76da8 | ||
|
|
bf7dbff858 | ||
|
|
7c10f86c6e | ||
|
|
9803e4d526 | ||
|
|
068b464e13 | ||
|
|
a09694ae85 | ||
|
|
e06a1c2746 | ||
|
|
c5320b3951 | ||
|
|
6cc960f214 | ||
|
|
a4b6fce907 | ||
|
|
8f727b3569 | ||
|
|
95073decd3 | ||
|
|
e8ef1ef252 | ||
|
|
075502477e | ||
|
|
97755bbd85 | ||
|
|
bab69ae4d2 | ||
|
|
443540fd13 | ||
|
|
5e1950ceff |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
|
||||
|
||||
|
||||
5
BUILD.gn
5
BUILD.gn
@@ -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",
|
||||
|
||||
@@ -1 +1 @@
|
||||
11.0.0-beta.5
|
||||
12.0.0-nightly.20200911
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
target: 'isolated_renderer',
|
||||
alwaysHasNode: false
|
||||
alwaysHasNode: false,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
|
||||
@@ -2,5 +2,6 @@ module.exports = require('./webpack.config.base')({
|
||||
target: 'renderer',
|
||||
alwaysHasNode: true,
|
||||
targetDeletesNodeGlobals: true,
|
||||
wrapInitWithProfilingTimeout: true
|
||||
wrapInitWithProfilingTimeout: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = require('./webpack.config.base')({
|
||||
target: 'sandboxed_renderer',
|
||||
alwaysHasNode: false,
|
||||
wrapInitWithProfilingTimeout: true
|
||||
wrapInitWithProfilingTimeout: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
|
||||
@@ -2,5 +2,6 @@ module.exports = require('./webpack.config.base')({
|
||||
target: 'worker',
|
||||
loadElectronFromAlternateTarget: 'renderer',
|
||||
alwaysHasNode: true,
|
||||
targetDeletesNodeGlobals: true
|
||||
targetDeletesNodeGlobals: true,
|
||||
wrapInitWithTryCatch: true
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
129
patches/chromium/fix_properly_honor_printing_page_ranges.patch
Normal file
129
patches/chromium/fix_properly_honor_printing_page_ranges.patch
Normal 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
|
||||
@@ -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
|
||||
|
||||
2407
patches/node/crypto_update_certdata_to_nss_3_56.patch
Normal file
2407
patches/node/crypto_update_certdata_to_nss_3_56.patch
Normal file
File diff suppressed because it is too large
Load Diff
250
patches/node/n-api_src_provide_asynchronous_cleanup_hooks.patch
Normal file
250
patches/node/n-api_src_provide_asynchronous_cleanup_hooks.patch
Normal 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_
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 ' +
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
37
shell/common/platform_util.cc
Normal file
37
shell/common/platform_util.cc
Normal 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
|
||||
@@ -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)
|
||||
|
||||
26
shell/common/platform_util_internal.h
Normal file
26
shell/common/platform_util_internal.h
Normal 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_
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21497-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21497-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22770-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22770-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25052-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25052-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25216-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25216-comments
vendored
Normal 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}]}
|
||||
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25052
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25052
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25216
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25216
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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', () => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user