mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
38 Commits
re-enable-
...
v16.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8626aa29d1 | ||
|
|
9e0a182821 | ||
|
|
2d8fa4d225 | ||
|
|
5a7ba8ba8c | ||
|
|
ede6b45bc1 | ||
|
|
1000ddfcab | ||
|
|
63110a7159 | ||
|
|
308ef699e6 | ||
|
|
764b2a6041 | ||
|
|
5009694dc9 | ||
|
|
c07016e1d1 | ||
|
|
7c5a346464 | ||
|
|
4f54cc0d18 | ||
|
|
520e7077d4 | ||
|
|
eb41f5b70e | ||
|
|
23afedcea8 | ||
|
|
9bc6e1a584 | ||
|
|
89b08817a9 | ||
|
|
cb6a22a7a4 | ||
|
|
74c53ad0c3 | ||
|
|
88235cb2bc | ||
|
|
f8b4dc98b9 | ||
|
|
79108c7acd | ||
|
|
b0273b96c1 | ||
|
|
f7dd18466b | ||
|
|
80175c5828 | ||
|
|
2a36199b35 | ||
|
|
f5c2bab02b | ||
|
|
acca212814 | ||
|
|
ebbaa3b352 | ||
|
|
c42ed97535 | ||
|
|
cec707ce67 | ||
|
|
0080a61c1d | ||
|
|
2f543c3fd4 | ||
|
|
f6da3b43c2 | ||
|
|
6029315e1a | ||
|
|
f6748f9795 | ||
|
|
6500bcbb32 |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -8,9 +8,9 @@ body:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
|
||||
required: true
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- label: I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.
|
||||
required: true
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -8,9 +8,9 @@ body:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
|
||||
required: true
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- label: I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.
|
||||
required: true
|
||||
|
||||
@@ -7,9 +7,9 @@ body:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
|
||||
required: true
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@
|
||||
Thank you for your Pull Request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTING.md
|
||||
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
#### Checklist
|
||||
@@ -11,7 +11,7 @@ Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTIN
|
||||
|
||||
- [ ] PR description included and stakeholders cc'd
|
||||
- [ ] `npm test` passes
|
||||
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/master/docs/development/testing.md)
|
||||
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
|
||||
- [ ] relevant documentation is changed or added
|
||||
- [ ] [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).
|
||||
|
||||
|
||||
6
.github/config.yml
vendored
6
.github/config.yml
vendored
@@ -2,7 +2,7 @@
|
||||
newPRWelcomeComment: |
|
||||
💖 Thanks for opening this pull request! 💖
|
||||
|
||||
We use [semantic commit messages](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines) to streamline the release process. Before your pull request can be merged, you should **update your pull request title** to start with a semantic prefix.
|
||||
We use [semantic commit messages](https://github.com/electron/electron/blob/main/docs/development/pull-requests.md#commit-message-guidelines) to streamline the release process. Before your pull request can be merged, you should **update your pull request title** to start with a semantic prefix.
|
||||
|
||||
Examples of commit messages with semantic prefixes:
|
||||
|
||||
@@ -12,9 +12,9 @@ newPRWelcomeComment: |
|
||||
|
||||
Things that will help get your PR across the finish line:
|
||||
|
||||
- Follow the JavaScript, C++, and Python [coding style](https://github.com/electron/electron/blob/master/docs/development/coding-style.md).
|
||||
- Follow the JavaScript, C++, and Python [coding style](https://github.com/electron/electron/blob/main/docs/development/coding-style.md).
|
||||
- Run `npm run lint` locally to catch formatting errors earlier.
|
||||
- Document any user-facing changes you've made following the [documentation styleguide](https://github.com/electron/electron/blob/master/docs/styleguide.md).
|
||||
- Document any user-facing changes you've made following the [documentation styleguide](https://github.com/electron/electron/blob/main/docs/styleguide.md).
|
||||
- Include tests when adding/changing behavior.
|
||||
- Include screenshots and animated GIFs whenever possible.
|
||||
|
||||
|
||||
1
BUILD.gn
1
BUILD.gn
@@ -379,6 +379,7 @@ source_set("electron_lib") {
|
||||
"//ppapi/shared_impl",
|
||||
"//printing/buildflags",
|
||||
"//services/device/public/cpp/geolocation",
|
||||
"//services/device/public/cpp/hid",
|
||||
"//services/device/public/mojom",
|
||||
"//services/proxy_resolver:lib",
|
||||
"//services/video_capture/public/mojom:constants",
|
||||
|
||||
@@ -36,7 +36,7 @@ Having the original text _as well as_ the translation can help mitigate translat
|
||||
|
||||
Responses to posted issues may or may not be in the original language.
|
||||
|
||||
**Please note** that using non-English as an attempt to circumvent our [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) will be an immediate, and possibly indefinite, ban from the project.
|
||||
**Please note** that using non-English as an attempt to circumvent our [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) will be an immediate, and possibly indefinite, ban from the project.
|
||||
|
||||
## [Pull Requests](https://electronjs.org/docs/development/pull-requests)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
16.0.0-nightly.20210922
|
||||
16.0.0-alpha.7
|
||||
@@ -1,7 +1,7 @@
|
||||
[](https://electronjs.org)
|
||||
|
||||
[](https://circleci.com/gh/electron/electron/tree/master)
|
||||
[](https://ci.appveyor.com/project/electron-bot/electron-ljo26/branch/master)
|
||||
[](https://circleci.com/gh/electron/electron/tree/main)
|
||||
[](https://ci.appveyor.com/project/electron-bot/electron-ljo26/branch/main)
|
||||
[](https://discord.com/invite/electron)
|
||||
|
||||
:memo: Available Translations: 🇨🇳 🇧🇷 🇪🇸 🇯🇵 🇷🇺 🇫🇷 🇺🇸 🇩🇪.
|
||||
@@ -16,7 +16,7 @@ Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important
|
||||
announcements.
|
||||
|
||||
This project adheres to the Contributor Covenant
|
||||
[code of conduct](https://github.com/electron/electron/tree/master/CODE_OF_CONDUCT.md).
|
||||
[code of conduct](https://github.com/electron/electron/tree/main/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable
|
||||
behavior to [coc@electronjs.org](mailto:coc@electronjs.org).
|
||||
|
||||
@@ -97,6 +97,6 @@ and more can be found in the [support document](docs/tutorial/support.md#finding
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/electron/electron/blob/master/LICENSE)
|
||||
[MIT](https://github.com/electron/electron/blob/main/LICENSE)
|
||||
|
||||
When using the Electron or other GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos).
|
||||
|
||||
@@ -10,7 +10,7 @@ Report security bugs in third-party modules to the person or team maintaining th
|
||||
|
||||
## The Electron Security Notification Process
|
||||
|
||||
For context on Electron's security notification process, please see the [Notifications](https://github.com/electron/governance/blob/master/wg-security/membership-and-notifications.md#notifications) section of the Security WG's [Membership and Notifications](https://github.com/electron/governance/blob/master/wg-security/membership-and-notifications.md) Governance document.
|
||||
For context on Electron's security notification process, please see the [Notifications](https://github.com/electron/governance/blob/main/wg-security/membership-and-notifications.md#notifications) section of the Security WG's [Membership and Notifications](https://github.com/electron/governance/blob/main/wg-security/membership-and-notifications.md) Governance document.
|
||||
|
||||
## Learning More About Security
|
||||
|
||||
|
||||
@@ -1072,7 +1072,7 @@ indicates success while any other value indicates failure according to Chromium
|
||||
Linux.
|
||||
* `secureDnsMode` String (optional) - Can be "off", "automatic" or "secure".
|
||||
Configures the DNS-over-HTTP mode. When "off", no DoH lookups will be
|
||||
performed. When "automatic", DoH lookups will be peformed first if DoH is
|
||||
performed. When "automatic", DoH lookups will be performed first if DoH is
|
||||
available, and insecure DNS lookups will be performed as a fallback. When
|
||||
"secure", only DoH lookups will be performed. Defaults to "automatic".
|
||||
* `secureDnsServers` String[] (optional) - A list of DNS-over-HTTP
|
||||
|
||||
@@ -234,6 +234,7 @@ expanding and collapsing the dialog.
|
||||
* `title` String (optional) - Title of the message box, some platforms will not show it.
|
||||
* `detail` String (optional) - Extra information of the message.
|
||||
* `icon` ([NativeImage](native-image.md) | String) (optional)
|
||||
* `textWidth` Integer (optional) _macOS_ - Custom width of the text in the message box.
|
||||
* `cancelId` Integer (optional) - The index of the button to be used to cancel the dialog, via
|
||||
the `Esc` key. By default this is assigned to the first button with "cancel" or "no" as the
|
||||
label. If no such labeled buttons exist and this option is not set, `0` will be used as the
|
||||
@@ -285,6 +286,7 @@ If `browserWindow` is not shown dialog will not be attached to it. In such case
|
||||
* `checkboxChecked` Boolean (optional) - Initial checked state of the
|
||||
checkbox. `false` by default.
|
||||
* `icon` [NativeImage](native-image.md) (optional)
|
||||
* `textWidth` Integer (optional) _macOS_ - Custom width of the text in the message box.
|
||||
* `cancelId` Integer (optional) - The index of the button to be used to cancel the dialog, via
|
||||
the `Esc` key. By default this is assigned to the first button with "cancel" or "no" as the
|
||||
label. If no such labeled buttons exist and this option is not set, `0` will be used as the
|
||||
|
||||
@@ -180,6 +180,96 @@ Emitted when a hunspell dictionary file download fails. For details
|
||||
on the failure you should collect a netlog and inspect the download
|
||||
request.
|
||||
|
||||
#### Event: 'select-hid-device'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Object
|
||||
* `deviceList` [HIDDevice[]](structures/hid-device.md)
|
||||
* `frame` [WebFrameMain](web-frame-main.md)
|
||||
* `callback` Function
|
||||
* `deviceId` String | null (optional)
|
||||
|
||||
Emitted when a HID device needs to be selected when a call to
|
||||
`navigator.hid.requestDevice` is made. `callback` should be called with
|
||||
`deviceId` to be selected; passing no arguments to `callback` will
|
||||
cancel the request. Additionally, permissioning on `navigator.hid` can
|
||||
be further managed by using [ses.setPermissionCheckHandler(handler)](#sessetpermissioncheckhandlerhandler)
|
||||
and [ses.setDevicePermissionHandler(handler)`](#sessetdevicepermissionhandlerhandler).
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow()
|
||||
|
||||
win.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'hid') {
|
||||
// Add logic here to determine if permission should be given to allow HID selection
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Optionally, retrieve previously persisted devices from a persistent store
|
||||
const grantedDevices = fetchGrantedDevices()
|
||||
|
||||
win.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (new URL(details.origin).hostname === 'some-host' && details.deviceType === 'hid') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.hid.requestDevice` first)
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
win.webContents.session.on('select-hid-device', (event, details, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedDevice = details.deviceList.find((device) => {
|
||||
return device.vendorId === '9025' && device.productId === '67'
|
||||
})
|
||||
callback(selectedPort?.deviceId)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Event: 'hid-device-added'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Object
|
||||
* `device` [HIDDevice[]](structures/hid-device.md)
|
||||
* `frame` [WebFrameMain](web-frame-main.md)
|
||||
|
||||
Emitted when a new HID device becomes available. For example, when a new USB device is plugged in.
|
||||
|
||||
This event will only be emitted after `navigator.hid.requestDevice` has been called and `select-hid-device` has fired.
|
||||
|
||||
#### Event: 'hid-device-removed'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Object
|
||||
* `device` [HIDDevice[]](structures/hid-device.md)
|
||||
* `frame` [WebFrameMain](web-frame-main.md)
|
||||
|
||||
Emitted when a HID device has been removed. For example, this event will fire when a USB device is unplugged.
|
||||
|
||||
This event will only be emitted after `navigator.hid.requestDevice` has been called and `select-hid-device` has fired.
|
||||
|
||||
#### Event: 'select-serial-port'
|
||||
|
||||
Returns:
|
||||
@@ -525,7 +615,7 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `webContents` ([WebContents](web-contents.md) | null) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin. All cross origin sub frames making permission checks will pass a `null` webContents to this handler, while certain other permission checks such as `notifications` checks will always pass `null`. You should use `embeddingOrigin` and `requestingOrigin` to determine what origin the owning frame and the requesting frame are on respectively.
|
||||
* `permission` String - Type of permission check. Valid values are `midiSysex`, `notifications`, `geolocation`, `media`,`mediaKeySystem`,`midi`, `pointerLock`, `fullscreen`, `openExternal`, or `serial`.
|
||||
* `permission` String - Type of permission check. Valid values are `midiSysex`, `notifications`, `geolocation`, `media`,`mediaKeySystem`,`midi`, `pointerLock`, `fullscreen`, `openExternal`, `hid`, or `serial`.
|
||||
* `requestingOrigin` String - The origin URL of the permission check
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `embeddingOrigin` String (optional) - The origin of the frame embedding the frame that made the permission check. Only set for cross-origin sub frames making permission checks.
|
||||
@@ -553,6 +643,71 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
|
||||
})
|
||||
```
|
||||
|
||||
#### `ses.setDevicePermissionHandler(handler)`
|
||||
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `details` Object
|
||||
* `deviceType` String - The type of device that permission is being requested on, can be `hid`.
|
||||
* `origin` String - The origin URL of the device permission check.
|
||||
* `device` [HIDDevice](structures/hid-device.md) - the device that permission is being requested for.
|
||||
* `frame` [WebFrameMain](web-frame-main.md) - WebFrameMain checking the device permission.
|
||||
|
||||
Sets the handler which can be used to respond to device permission checks for the `session`.
|
||||
Returning `true` will allow the device to be permitted and `false` will reject it.
|
||||
To clear the handler, call `setDevicePermissionHandler(null)`.
|
||||
This handler can be used to provide default permissioning to devices without first calling for permission
|
||||
to devices (eg via `navigator.hid.requestDevice`). If this handler is not defined, the default device
|
||||
permissions as granted through device selection (eg via `navigator.hid.requestDevice`) will be used.
|
||||
Additionally, the default behavior of Electron is to store granted device permision through the lifetime
|
||||
of the corresponding WebContents. If longer term storage is needed, a developer can store granted device
|
||||
permissions (eg when handling the `select-hid-device` event) and then read from that storage with `setDevicePermissionHandler`.
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow()
|
||||
|
||||
win.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'hid') {
|
||||
// Add logic here to determine if permission should be given to allow HID selection
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Optionally, retrieve previously persisted devices from a persistent store
|
||||
const grantedDevices = fetchGrantedDevices()
|
||||
|
||||
win.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (new URL(details.origin).hostname === 'some-host' && details.deviceType === 'hid') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.hid.requestDevice` first)
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
win.webContents.session.on('select-hid-device', (event, details, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedDevice = details.deviceList.find((device) => {
|
||||
return device.vendorId === '9025' && device.productId === '67'
|
||||
})
|
||||
callback(selectedPort?.deviceId)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### `ses.clearHostResolverCache()`
|
||||
|
||||
Returns `Promise<void>` - Resolves when the operation is complete.
|
||||
|
||||
8
docs/api/structures/hid-device.md
Normal file
8
docs/api/structures/hid-device.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# HIDDevice Object
|
||||
|
||||
* `deviceId` String - Unique identifier for the device.
|
||||
* `name` String - Name of the device.
|
||||
* `vendorId` Integer - The USB vendor ID.
|
||||
* `productId` Integer - The USB product ID.
|
||||
* `serialNumber` String (optional) - The USB device serial number.
|
||||
* `guid` String (optional) - Unique identifier for the HID interface. A device may have multiple HID interfaces.
|
||||
@@ -1827,7 +1827,8 @@ End subscribing for frame presentation events.
|
||||
#### `contents.startDrag(item)`
|
||||
|
||||
* `item` Object
|
||||
* `file` String[] | String - The path(s) to the file(s) being dragged.
|
||||
* `file` String - The path to the file being dragged.
|
||||
* `files` String[] (optional) - The paths to the files being dragged. (`files` will override `file` field)
|
||||
* `icon` [NativeImage](native-image.md) | String - The image must be
|
||||
non-empty on macOS.
|
||||
|
||||
|
||||
@@ -1025,3 +1025,78 @@ Emitted when DevTools is focused / opened.
|
||||
|
||||
[runtime-enabled-features]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/runtime_enabled_features.json5?l=70
|
||||
[chrome-webview]: https://developer.chrome.com/docs/extensions/reference/webviewTag/
|
||||
|
||||
### Event: 'context-menu'
|
||||
|
||||
Returns:
|
||||
|
||||
* `params` Object
|
||||
* `x` Integer - x coordinate.
|
||||
* `y` Integer - y coordinate.
|
||||
* `linkURL` String - URL of the link that encloses the node the context menu
|
||||
was invoked on.
|
||||
* `linkText` String - Text associated with the link. May be an empty
|
||||
string if the contents of the link are an image.
|
||||
* `pageURL` String - URL of the top level page that the context menu was
|
||||
invoked on.
|
||||
* `frameURL` String - URL of the subframe that the context menu was invoked
|
||||
on.
|
||||
* `srcURL` String - Source URL for the element that the context menu
|
||||
was invoked on. Elements with source URLs are images, audio and video.
|
||||
* `mediaType` String - Type of the node the context menu was invoked on. Can
|
||||
be `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`.
|
||||
* `hasImageContents` Boolean - Whether the context menu was invoked on an image
|
||||
which has non-empty contents.
|
||||
* `isEditable` Boolean - Whether the context is editable.
|
||||
* `selectionText` String - Text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `titleText` String - Title text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `altText` String - Alt text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `suggestedFilename` String - Suggested filename to be used when saving file through 'Save
|
||||
Link As' option of context menu.
|
||||
* `selectionRect` [Rectangle](structures/rectangle.md) - Rect representing the coordinates in the document space of the selection.
|
||||
* `selectionStartOffset` Number - Start position of the selection text.
|
||||
* `referrerPolicy` [Referrer](structures/referrer.md) - The referrer policy of the frame on which the menu is invoked.
|
||||
* `misspelledWord` String - The misspelled word under the cursor, if any.
|
||||
* `dictionarySuggestions` String[] - An array of suggested words to show the
|
||||
user to replace the `misspelledWord`. Only available if there is a misspelled
|
||||
word and spellchecker is enabled.
|
||||
* `frameCharset` String - The character encoding of the frame on which the
|
||||
menu was invoked.
|
||||
* `inputFieldType` String - If the context menu was invoked on an input
|
||||
field, the type of that field. Possible values are `none`, `plainText`,
|
||||
`password`, `other`.
|
||||
* `spellcheckEnabled` Boolean - If the context is editable, whether or not spellchecking is enabled.
|
||||
* `menuSourceType` String - Input source that invoked the context menu.
|
||||
Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`, `longPress`, `longTap`, `touchHandle`, `stylus`, `adjustSelection`, or `adjustSelectionReset`.
|
||||
* `mediaFlags` Object - The flags for the media element the context menu was
|
||||
invoked on.
|
||||
* `inError` Boolean - Whether the media element has crashed.
|
||||
* `isPaused` Boolean - Whether the media element is paused.
|
||||
* `isMuted` Boolean - Whether the media element is muted.
|
||||
* `hasAudio` Boolean - Whether the media element has audio.
|
||||
* `isLooping` Boolean - Whether the media element is looping.
|
||||
* `isControlsVisible` Boolean - Whether the media element's controls are
|
||||
visible.
|
||||
* `canToggleControls` Boolean - Whether the media element's controls are
|
||||
toggleable.
|
||||
* `canPrint` Boolean - Whether the media element can be printed.
|
||||
* `canSave` Boolean - Whether or not the media element can be downloaded.
|
||||
* `canShowPictureInPicture` Boolean - Whether the media element can show picture-in-picture.
|
||||
* `isShowingPictureInPicture` Boolean - Whether the media element is currently showing picture-in-picture.
|
||||
* `canRotate` Boolean - Whether the media element can be rotated.
|
||||
* `canLoop` Boolean - Whether the media element can be looped.
|
||||
* `editFlags` Object - These flags indicate whether the renderer believes it
|
||||
is able to perform the corresponding action.
|
||||
* `canUndo` Boolean - Whether the renderer believes it can undo.
|
||||
* `canRedo` Boolean - Whether the renderer believes it can redo.
|
||||
* `canCut` Boolean - Whether the renderer believes it can cut.
|
||||
* `canCopy` Boolean - Whether the renderer believes it can copy.
|
||||
* `canPaste` Boolean - Whether the renderer believes it can paste.
|
||||
* `canDelete` Boolean - Whether the renderer believes it can delete.
|
||||
* `canSelectAll` Boolean - Whether the renderer believes it can select all.
|
||||
* `canEditRichly` Boolean - Whether the renderer believes it can edit text richly.
|
||||
|
||||
Emitted when there is a new context menu that needs to be handled.
|
||||
|
||||
@@ -69,6 +69,18 @@ Electron apps.
|
||||
See [here](#removed-desktopcapturergetsources-in-the-renderer) for details on
|
||||
how to replace this API in your app.
|
||||
|
||||
## Planned Breaking API Changes (15.0)
|
||||
|
||||
### Default Changed: `nativeWindowOpen` defaults to `true`
|
||||
|
||||
Prior to Electron 15, `window.open` was by default shimmed to use
|
||||
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
|
||||
to open synchronously scriptable child windows, among other incompatibilities.
|
||||
`nativeWindowOpen` is no longer experimental, and is now the default.
|
||||
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
## Planned Breaking API Changes (14.0)
|
||||
|
||||
### Removed: `remote` module
|
||||
@@ -119,16 +131,6 @@ ensure your code works with this property enabled. It has been enabled by defau
|
||||
|
||||
You will be affected by this change if you use either `webFrame.executeJavaScript` or `webFrame.executeJavaScriptInIsolatedWorld`. You will need to ensure that values returned by either of those methods are supported by the [Context Bridge API](api/context-bridge.md#parameter--error--return-type-support) as these methods use the same value passing semantics.
|
||||
|
||||
### Default Changed: `nativeWindowOpen` defaults to `true`
|
||||
|
||||
Prior to Electron 14, `window.open` was by default shimmed to use
|
||||
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
|
||||
to open synchronously scriptable child windows, among other incompatibilities.
|
||||
`nativeWindowOpen` is no longer experimental, and is now the default.
|
||||
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
### Removed: BrowserWindowConstructorOptions inheriting from parent windows
|
||||
|
||||
Prior to Electron 14, windows opened with `window.open` would inherit
|
||||
@@ -632,7 +634,7 @@ error.
|
||||
### API Changed: `shell.openItem` is now `shell.openPath`
|
||||
|
||||
The `shell.openItem` API has been replaced with an asynchronous `shell.openPath` API.
|
||||
You can see the original API proposal and reasoning [here](https://github.com/electron/governance/blob/master/wg-api/spec-documents/shell-openitem.md).
|
||||
You can see the original API proposal and reasoning [here](https://github.com/electron/governance/blob/main/wg-api/spec-documents/shell-openitem.md).
|
||||
|
||||
## Planned Breaking API Changes (8.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](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md)
|
||||
* [Contributing to Electron](https://github.com/electron/electron/blob/master/CONTRIBUTING.md)
|
||||
* [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md)
|
||||
* [Contributing to Electron](https://github.com/electron/electron/blob/main/CONTRIBUTING.md)
|
||||
* [Issues](issues.md)
|
||||
* [Pull Requests](pull-requests.md)
|
||||
* [Documentation Styleguide](coding-style.md#documentation)
|
||||
|
||||
@@ -8,7 +8,7 @@ Example Use Case:
|
||||
* We need `VS15.9` and we have `VS15.7` installed; this would require us to update an Azure image.
|
||||
|
||||
1. Identify the image you wish to modify.
|
||||
* In [appveyor.yml](https://github.com/electron/electron/blob/master/appveyor.yml), the image is identified by the property *image*.
|
||||
* In [appveyor.yml](https://github.com/electron/electron/blob/main/appveyor.yml), the image is identified by the property *image*.
|
||||
* The names used correspond to the *"images"* defined for a build cloud, eg the [libcc-20 cloud](https://windows-ci.electronjs.org/build-clouds/8).
|
||||
* Find the image you wish to modify in the build cloud and make note of the **VHD Blob Path** for that image, which is the value for that corresponding key.
|
||||
* You will need this URI path to copy into a new image.
|
||||
|
||||
@@ -65,8 +65,8 @@ origin URLs.
|
||||
$ cd src/electron
|
||||
$ git remote remove origin
|
||||
$ git remote add origin https://github.com/electron/electron
|
||||
$ git checkout master
|
||||
$ git branch --set-upstream-to=origin/master
|
||||
$ git checkout main
|
||||
$ git branch --set-upstream-to=origin/main
|
||||
$ cd -
|
||||
```
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ you prefer a graphical interface.
|
||||
* **.lldbinit**: Create or edit `~/.lldbinit` to allow Chromium code to be properly source-mapped.
|
||||
|
||||
```text
|
||||
command script import ~/electron/src/tools/lldb/lldbinit.py
|
||||
# e.g: ['~/electron/src/tools/lldb']
|
||||
script sys.path[:0] = ['<...path/to/electron/src/tools/lldb>']
|
||||
script import lldbinit
|
||||
```
|
||||
|
||||
## Attaching to and Debugging Electron
|
||||
|
||||
@@ -72,4 +72,4 @@ to try NW.js.
|
||||
|
||||
[nwjs]: https://nwjs.io/
|
||||
[electron-modules]: https://www.npmjs.com/search?q=electron
|
||||
[node-bindings]: https://github.com/electron/electron/tree/master/lib/common
|
||||
[node-bindings]: https://github.com/electron/electron/tree/main/lib/common
|
||||
|
||||
@@ -45,10 +45,10 @@ Once you've built the project locally, you're ready to start making changes!
|
||||
### Step 3: Branch
|
||||
|
||||
To keep your development environment organized, create local branches to
|
||||
hold your work. These should be branched directly off of the `master` branch.
|
||||
hold your work. These should be branched directly off of the `main` branch.
|
||||
|
||||
```sh
|
||||
$ git checkout -b my-branch -t upstream/master
|
||||
$ git checkout -b my-branch -t upstream/main
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
@@ -134,11 +134,11 @@ Once you have committed your changes, it is a good idea to use `git rebase`
|
||||
|
||||
```sh
|
||||
$ git fetch upstream
|
||||
$ git rebase upstream/master
|
||||
$ git rebase upstream/main
|
||||
```
|
||||
|
||||
This ensures that your working branch has the latest changes from `electron/electron`
|
||||
master.
|
||||
main.
|
||||
|
||||
### Step 7: Test
|
||||
|
||||
@@ -189,7 +189,7 @@ the requirements below.
|
||||
|
||||
Bug fixes and new features should include tests and possibly benchmarks.
|
||||
|
||||
Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTING.md
|
||||
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
||||
-->
|
||||
```
|
||||
|
||||
@@ -222,7 +222,7 @@ seem unfamiliar, refer to this
|
||||
#### Approval and Request Changes Workflow
|
||||
|
||||
All pull requests require approval from a
|
||||
[Code Owner](https://github.com/electron/electron/blob/master/.github/CODEOWNERS)
|
||||
[Code Owner](https://github.com/electron/electron/blob/main/.github/CODEOWNERS)
|
||||
of the area you modified in order to land. Whenever a maintainer reviews a pull
|
||||
request they may request changes. These may be small, such as fixing a typo, or
|
||||
may involve substantive changes. Such requests are intended to be helpful, but
|
||||
|
||||
17
docs/fiddles/features/web-bluetooth/index.html
Normal file
17
docs/fiddles/features/web-bluetooth/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>Web Bluetooth API</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Web Bluetooth API</h1>
|
||||
|
||||
<button id="clickme">Test Bluetooth</button>
|
||||
|
||||
<p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
30
docs/fiddles/features/web-bluetooth/main.js
Normal file
30
docs/fiddles/features/web-bluetooth/main.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
|
||||
event.preventDefault()
|
||||
if (deviceList && deviceList.length > 0) {
|
||||
callback(deviceList[0].deviceId)
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
8
docs/fiddles/features/web-bluetooth/renderer.js
Normal file
8
docs/fiddles/features/web-bluetooth/renderer.js
Normal file
@@ -0,0 +1,8 @@
|
||||
async function testIt() {
|
||||
const device = await navigator.bluetooth.requestDevice({
|
||||
acceptAllDevices: true
|
||||
})
|
||||
document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
|
||||
}
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
21
docs/fiddles/features/web-hid/index.html
Normal file
21
docs/fiddles/features/web-hid/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>WebHID API</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebHID API</h1>
|
||||
|
||||
<button id="clickme">Test WebHID</button>
|
||||
|
||||
<h3>HID devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
|
||||
<div id="granted-devices"></div>
|
||||
|
||||
<h3>HID devices automatically granted access via <i>select-hid-device</i></h3>
|
||||
<div id="granted-devices2"></div>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
50
docs/fiddles/features/web-hid/main.js
Normal file
50
docs/fiddles/features/web-hid/main.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('select-hid-device', (event, details, callback) => {
|
||||
event.preventDefault()
|
||||
if (details.deviceList && details.deviceList.length > 0) {
|
||||
callback(details.deviceList[0].deviceId)
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('hid-device-added', (event, device) => {
|
||||
console.log('hid-device-added FIRED WITH', device)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('hid-device-removed', (event, device) => {
|
||||
console.log('hid-device-removed FIRED WITH', device)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'hid' && details.securityOrigin === 'file:///') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (details.deviceType === 'hid' && details.origin === 'file://') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
19
docs/fiddles/features/web-hid/renderer.js
Normal file
19
docs/fiddles/features/web-hid/renderer.js
Normal file
@@ -0,0 +1,19 @@
|
||||
async function testIt() {
|
||||
const grantedDevices = await navigator.hid.getDevices()
|
||||
let grantedDeviceList = ''
|
||||
grantedDevices.forEach(device => {
|
||||
grantedDeviceList += `<hr>${device.productName}</hr>`
|
||||
})
|
||||
document.getElementById('granted-devices').innerHTML = grantedDeviceList
|
||||
const grantedDevices2 = await navigator.hid.requestDevice({
|
||||
filters: []
|
||||
})
|
||||
|
||||
grantedDeviceList = ''
|
||||
grantedDevices2.forEach(device => {
|
||||
grantedDeviceList += `<hr>${device.productName}</hr>`
|
||||
})
|
||||
document.getElementById('granted-devices2').innerHTML = grantedDeviceList
|
||||
}
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
16
docs/fiddles/features/web-serial/index.html
Normal file
16
docs/fiddles/features/web-serial/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>Web Serial API</title>
|
||||
<body>
|
||||
<h1>Web Serial API</h1>
|
||||
|
||||
<button id="clickme">Test Web Serial API</button>
|
||||
|
||||
<p>Matching Arduino Uno device: <strong id="device-name""></strong></p>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
54
docs/fiddles/features/web-serial/main.js
Normal file
54
docs/fiddles/features/web-serial/main.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
|
||||
event.preventDefault()
|
||||
if (portList && portList.length > 0) {
|
||||
callback(portList[0].portId)
|
||||
} else {
|
||||
callback('') //Could not find any matching devices
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('serial-port-added', (event, port) => {
|
||||
console.log('serial-port-added FIRED WITH', port)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
|
||||
console.log('serial-port-removed FIRED WITH', port)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'serial' && details.securityOrigin === 'file:///') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (details.deviceType === 'serial' && details.origin === 'file://') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
19
docs/fiddles/features/web-serial/renderer.js
Normal file
19
docs/fiddles/features/web-serial/renderer.js
Normal file
@@ -0,0 +1,19 @@
|
||||
async function testIt() {
|
||||
const filters = [
|
||||
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
|
||||
{ usbVendorId: 0x2341, usbProductId: 0x0001 }
|
||||
];
|
||||
try {
|
||||
const port = await navigator.serial.requestPort({filters});
|
||||
const portInfo = port.getInfo();
|
||||
document.getElementById('device-name').innerHTML = `vendorId: ${portInfo.usbVendorId} | productId: ${portInfo.usbProductId} `
|
||||
} catch (ex) {
|
||||
if (ex.name === 'NotFoundError') {
|
||||
document.getElementById('device-name').innerHTML = 'Device NOT found'
|
||||
} else {
|
||||
document.getElementById('device-name').innerHTML = ex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
@@ -1,5 +1,5 @@
|
||||
// Modules to control application life and create native browser window
|
||||
const { app, BrowserWindow, ipcMain, shell } = require('electron')
|
||||
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
let mainWindow;
|
||||
|
||||
99
docs/tutorial/devices.md
Normal file
99
docs/tutorial/devices.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Device Access
|
||||
|
||||
Like Chromium based browsers, Electron provides access to device hardware
|
||||
through web APIs. For the most part these APIs work like they do in a browser,
|
||||
but there are some differences that need to be taken into account. The primary
|
||||
difference between Electron and browsers is what happens when device access is
|
||||
requested. In a browser, users are presented with a popup where they can grant
|
||||
access to an individual device. In Electron APIs are provided which can be
|
||||
used by a developer to either automatically pick a device or prompt users to
|
||||
pick a device via a developer created interface.
|
||||
|
||||
## Web Bluetooth API
|
||||
|
||||
The [Web Bluetooth API](https://web.dev/bluetooth/) can be used to communicate
|
||||
with bluetooth devices. In order to use this API in Electron, developers will
|
||||
need to handle the [`select-bluetooth-device` event on the webContents](../api/web-contents.md#event-select-bluetooth-device)
|
||||
associated with the device request.
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
the first available bluetooth device when the `Test Bluetooth` button is
|
||||
clicked.
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/web-bluetooth'
|
||||
|
||||
```
|
||||
|
||||
## WebHID API
|
||||
|
||||
The [WebHID API](https://web.dev/hid/) can be used to access HID devices such
|
||||
as keyboards and gamepads. Electron provides several APIs for working with
|
||||
the WebHID API:
|
||||
|
||||
* The [`select-hid-device` event on the Session](../api/session.md#event-select-hid-device)
|
||||
can be used to select a HID device when a call to
|
||||
`navigator.hid.requestDevice` is made. Additionally the [`hid-device-added`](../api/session.md#event-hid-device-added)
|
||||
and [`hid-device-removed`](../api/session.md#event-hid-device-removed) events
|
||||
on the Session can be used to handle devices being plugged in or unplugged during the
|
||||
`navigator.hid.requestDevice` process.
|
||||
* [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
can be used to provide default permissioning to devices without first calling
|
||||
for permission to devices via `navigator.hid.requestDevice`. Additionally,
|
||||
the default behavior of Electron is to store granted device permision through
|
||||
the lifetime of the corresponding WebContents. If longer term storage is
|
||||
needed, a developer can store granted device permissions (eg when handling
|
||||
the `select-hid-device` event) and then read from that storage with
|
||||
`setDevicePermissionHandler`.
|
||||
* [`ses.setPermissionCheckHandler(handler)`](../api/session.md#sessetpermissioncheckhandlerhandler)
|
||||
can be used to disable HID access for specific origins.
|
||||
|
||||
### Blocklist
|
||||
|
||||
By default Electron employs the same [blocklist](https://github.com/WICG/webhid/blob/main/blocklist.txt)
|
||||
used by Chromium. If you wish to override this behavior, you can do so by
|
||||
setting the `disable-hid-blocklist` flag:
|
||||
|
||||
```javascript
|
||||
app.commandLine.appendSwitch('disable-hid-blocklist')
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
HID devices through [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
and through [`select-hid-device` event on the Session](../api/session.md#event-select-hid-device)
|
||||
when the `Test WebHID` button is clicked.
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/web-hid'
|
||||
|
||||
```
|
||||
|
||||
## Web Serial API
|
||||
|
||||
The [Web Serial API](https://web.dev/serial/) can be used to access serial
|
||||
devices that are connected via serial port, USB, or Bluetooth. In order to use
|
||||
this API in Electron, developers will need to handle the
|
||||
[`select-serial-port` event on the Session](../api/session.md#event-select-serial-port)
|
||||
associated with the serial port request.
|
||||
|
||||
There are several additional APIs for working with the Web Serial API:
|
||||
|
||||
* The [`serial-port-added`](../api/session.md#event-serial-port-added)
|
||||
and [`serial-port-removed`](../api/session.md#event-serial-port-removed) events
|
||||
on the Session can be used to handle devices being plugged in or unplugged during the
|
||||
`navigator.serial.requestPort` process.
|
||||
* [`ses.setPermissionCheckHandler(handler)`](../api/session.md#sessetpermissioncheckhandlerhandler)
|
||||
can be used to disable serial access for specific origins.
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
the first available Arduino Uno serial device (if connected) through
|
||||
[`select-serial-port` event on the Session](../api/session.md#event-select-serial-port)
|
||||
when the `Test Web Serial` button is clicked.
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/web-serial'
|
||||
|
||||
```
|
||||
@@ -51,4 +51,4 @@ Somewhere in the Electron binary there will be a sequence of bytes that look lik
|
||||
|
||||
To flip a fuse you find its position in the fuse wire and change it to "0" or "1" depending on the state you'd like.
|
||||
|
||||
You can view the current schema [here](https://github.com/electron/electron/blob/master/build/fuses/fuses.json5).
|
||||
You can view the current schema [here](https://github.com/electron/electron/blob/main/build/fuses/fuses.json5).
|
||||
|
||||
@@ -23,7 +23,7 @@ your responsibility to ensure that the code is not malicious.
|
||||
## Reporting Security Issues
|
||||
|
||||
For information on how to properly disclose an Electron vulnerability,
|
||||
see [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md)
|
||||
see [SECURITY.md](https://github.com/electron/electron/tree/main/SECURITY.md)
|
||||
|
||||
## Chromium Security Issues and Upgrades
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Finding Support
|
||||
|
||||
If you have a security concern,
|
||||
please see the [security document](https://github.com/electron/electron/tree/master/SECURITY.md).
|
||||
please see the [security document](https://github.com/electron/electron/tree/main/SECURITY.md).
|
||||
|
||||
If you're looking for programming help,
|
||||
for answers to questions,
|
||||
@@ -26,7 +26,7 @@ you can interact with the community in these locations:
|
||||
* [`electron-pl`](https://electronpl.github.io) *(Poland)*
|
||||
|
||||
If you'd like to contribute to Electron,
|
||||
see the [contributing document](https://github.com/electron/electron/blob/master/CONTRIBUTING.md).
|
||||
see the [contributing document](https://github.com/electron/electron/blob/main/CONTRIBUTING.md).
|
||||
|
||||
If you've found a bug in a [supported version](#supported-versions) of Electron,
|
||||
please report it with the [issue tracker](../development/issues.md).
|
||||
@@ -50,15 +50,15 @@ as the 4.2.x series are supported. We only support the latest minor release
|
||||
for each stable release series. This means that in the case of a security fix
|
||||
6.1.x will receive the fix, but we will not release a new version of 6.0.x.
|
||||
|
||||
The latest stable release unilaterally receives all fixes from `master`,
|
||||
The latest stable release unilaterally receives all fixes from `main`,
|
||||
and the version prior to that receives the vast majority of those fixes
|
||||
as time and bandwidth warrants. The oldest supported release line will receive
|
||||
only security fixes directly.
|
||||
|
||||
All supported release lines will accept external pull requests to backport
|
||||
fixes previously merged to `master`, though this may be on a case-by-case
|
||||
fixes previously merged to `main`, though this may be on a case-by-case
|
||||
basis for some older supported lines. All contested decisions around release
|
||||
line backports will be resolved by the [Releases Working Group](https://github.com/electron/governance/tree/master/wg-releases) as an agenda item at their weekly meeting the week the backport PR is raised.
|
||||
line backports will be resolved by the [Releases Working Group](https://github.com/electron/governance/tree/main/wg-releases) as an agenda item at their weekly meeting the week the backport PR is raised.
|
||||
|
||||
When an API is changed or removed in a way that breaks existing functionality, the
|
||||
previous functionality will be supported for a minimum of two major versions when
|
||||
|
||||
@@ -125,5 +125,7 @@
|
||||
</message>
|
||||
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS" desc="The accessibility text which will be read by a screen reader when there are notifcatications">
|
||||
{UNREAD_NOTIFICATIONS, plural, =1 {1 Unread Notification} other {# Unread Notifications}}
|
||||
</message>
|
||||
</message>
|
||||
<message name="IDS_HID_CHOOSER_ITEM_WITHOUT_NAME" desc="User option displaying the device IDs for a Human Interface Device (HID) without a device name.">
|
||||
Unknown Device (<ph name="DEVICE_ID">$1<ex>1234:abcd</ex></ph>) </message>
|
||||
</grit-part>
|
||||
|
||||
@@ -84,6 +84,7 @@ auto_filenames = {
|
||||
"docs/api/structures/file-filter.md",
|
||||
"docs/api/structures/file-path-with-headers.md",
|
||||
"docs/api/structures/gpu-feature-status.md",
|
||||
"docs/api/structures/hid-device.md",
|
||||
"docs/api/structures/input-event.md",
|
||||
"docs/api/structures/io-counters.md",
|
||||
"docs/api/structures/ipc-main-event.md",
|
||||
|
||||
@@ -387,6 +387,14 @@ filenames = {
|
||||
"shell/browser/font/electron_font_access_delegate.h",
|
||||
"shell/browser/font_defaults.cc",
|
||||
"shell/browser/font_defaults.h",
|
||||
"shell/browser/hid/electron_hid_delegate.cc",
|
||||
"shell/browser/hid/electron_hid_delegate.h",
|
||||
"shell/browser/hid/hid_chooser_context.cc",
|
||||
"shell/browser/hid/hid_chooser_context.h",
|
||||
"shell/browser/hid/hid_chooser_context_factory.cc",
|
||||
"shell/browser/hid/hid_chooser_context_factory.h",
|
||||
"shell/browser/hid/hid_chooser_controller.cc",
|
||||
"shell/browser/hid/hid_chooser_controller.h",
|
||||
"shell/browser/javascript_environment.cc",
|
||||
"shell/browser/javascript_environment.h",
|
||||
"shell/browser/lib/bluetooth_chooser.cc",
|
||||
|
||||
@@ -168,6 +168,7 @@ const messageBox = (sync: boolean, window: BrowserWindow | null, options?: Messa
|
||||
defaultId = -1,
|
||||
detail = '',
|
||||
icon = null,
|
||||
textWidth = 0,
|
||||
noLink = false,
|
||||
message = '',
|
||||
title = '',
|
||||
@@ -225,7 +226,8 @@ const messageBox = (sync: boolean, window: BrowserWindow | null, options?: Messa
|
||||
detail,
|
||||
checkboxLabel,
|
||||
checkboxChecked,
|
||||
icon
|
||||
icon,
|
||||
textWidth
|
||||
};
|
||||
|
||||
if (sync) {
|
||||
|
||||
@@ -666,9 +666,9 @@ WebContents.prototype._init = function () {
|
||||
postBody
|
||||
};
|
||||
windowOpenOverriddenOptions = this._callWindowOpenHandler(event, details);
|
||||
// if attempting to use this API with the deprecated window.open event,
|
||||
// if attempting to use this API with the deprecated new-window event,
|
||||
// windowOpenOverriddenOptions will always return null. This ensures
|
||||
// short-term backwards compatibility until window.open is removed.
|
||||
// short-term backwards compatibility until new-window is removed.
|
||||
const parsedFeatures = parseFeatures(rawFeatures);
|
||||
const overriddenFeatures: BrowserWindowConstructorOptions = {
|
||||
...parsedFeatures.options,
|
||||
|
||||
@@ -38,6 +38,10 @@ ipcMainInternal.handle(IPC_MESSAGES.BROWSER_GET_LAST_WEB_PREFERENCES, function (
|
||||
return event.sender.getLastWebPreferences();
|
||||
});
|
||||
|
||||
ipcMainInternal.handle(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO, function (event) {
|
||||
return event.sender._getProcessMemoryInfo();
|
||||
});
|
||||
|
||||
// Methods not listed in this set are called directly in the renderer process.
|
||||
const allowedClipboardMethods = (() => {
|
||||
switch (process.platform) {
|
||||
|
||||
@@ -4,6 +4,7 @@ export const enum IPC_MESSAGES {
|
||||
BROWSER_PRELOAD_ERROR = 'BROWSER_PRELOAD_ERROR',
|
||||
BROWSER_SANDBOX_LOAD = 'BROWSER_SANDBOX_LOAD',
|
||||
BROWSER_WINDOW_CLOSE = 'BROWSER_WINDOW_CLOSE',
|
||||
BROWSER_GET_PROCESS_MEMORY_INFO = 'BROWSER_GET_PROCESS_MEMORY_INFO',
|
||||
|
||||
GUEST_INSTANCE_VISIBILITY_CHANGE = 'GUEST_INSTANCE_VISIBILITY_CHANGE',
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ v8Util.setHiddenValue(global, 'ipcNative', {
|
||||
}
|
||||
});
|
||||
|
||||
process.getProcessMemoryInfo = () => {
|
||||
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
|
||||
};
|
||||
|
||||
// Use electron module after everything is ready.
|
||||
const { webFrameInit } = require('@electron/internal/renderer/web-frame-init') as typeof webFrameInitModule;
|
||||
webFrameInit();
|
||||
|
||||
@@ -86,6 +86,10 @@ Object.assign(preloadProcess, processProps);
|
||||
Object.assign(process, binding.process);
|
||||
Object.assign(process, processProps);
|
||||
|
||||
process.getProcessMemoryInfo = preloadProcess.getProcessMemoryInfo = () => {
|
||||
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
|
||||
};
|
||||
|
||||
Object.defineProperty(preloadProcess, 'noDeprecation', {
|
||||
get () {
|
||||
return process.noDeprecation;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "16.0.0-nightly.20210922",
|
||||
"version": "16.0.0-alpha.7",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,3 +3,4 @@ expose_aes-cfb.patch
|
||||
expose_des-ede3.patch
|
||||
fix_sync_evp_get_cipherbynid_and_evp_get_cipherbyname.patch
|
||||
add_maskhash_to_rsa_pss_params_st_for_compat.patch
|
||||
enable_x509_v_flag_trusted_first_flag.patch
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Juan Cruz Viotti <jv@jviotti.com>
|
||||
Date: Thu, 30 Sep 2021 13:39:23 -0400
|
||||
Subject: Enable X509_V_FLAG_TRUSTED_FIRST flag
|
||||
|
||||
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
|
||||
|
||||
diff --git a/crypto/x509/x509_vpm.c b/crypto/x509/x509_vpm.c
|
||||
index 5a881d64c30076404cc800fff9e943bb0b30d2ac..29d5341efc8eb7ae6f90bdde5a8032e99f75c98e 100644
|
||||
--- a/crypto/x509/x509_vpm.c
|
||||
+++ b/crypto/x509/x509_vpm.c
|
||||
@@ -528,7 +528,7 @@ static const X509_VERIFY_PARAM default_table[] = {
|
||||
(char *)"default", /* X509 default parameters */
|
||||
0, /* Check time */
|
||||
0, /* internal flags */
|
||||
- 0, /* flags */
|
||||
+ X509_V_FLAG_TRUSTED_FIRST, /* flags */
|
||||
0, /* purpose */
|
||||
0, /* trust */
|
||||
100, /* depth */
|
||||
@@ -100,7 +100,6 @@ build_do_not_depend_on_packed_resource_integrity.patch
|
||||
refactor_restore_base_adaptcallbackforrepeating.patch
|
||||
hack_to_allow_gclient_sync_with_host_os_mac_on_linux_in_ci.patch
|
||||
don_t_run_pcscan_notifythreadcreated_if_pcscan_is_disabled.patch
|
||||
add_gin_wrappable_crash_key.patch
|
||||
logging_win32_only_create_a_console_if_logging_to_stderr.patch
|
||||
fix_media_key_usage_with_globalshortcuts.patch
|
||||
feat_expose_raw_response_headers_from_urlloader.patch
|
||||
@@ -108,3 +107,5 @@ chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch
|
||||
fix_chrome_root_store_codegen_for_cross-compile_builds.patch
|
||||
process_singleton.patch
|
||||
fix_expose_decrementcapturercount_in_web_contents_impl.patch
|
||||
cherry-pick-ec42dfd3545f.patch
|
||||
cherry-pick-39090918efac.patch
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: VerteDinde <khammond@slack-corp.com>
|
||||
Date: Thu, 15 Jul 2021 12:16:50 -0700
|
||||
Subject: chore: add gin::wrappable wrapperinfo crash key
|
||||
|
||||
This patch adds an additional crash key for gin::Wrappable, to help
|
||||
debug a crash that is occurring during garbage collection in SecondWeakCallback.
|
||||
|
||||
The crash seems to be due to a class that is holding a reference to
|
||||
gin::Wrappable even after being deleted. This added crash key compares
|
||||
the soon-to-be-deleted WrapperInfo with known WrapperInfo components to
|
||||
help determine where the crash originated from.
|
||||
|
||||
This patch should not be upstreamed, and can be removed in Electron 15 and
|
||||
beyond once we identify the cause of the crash.
|
||||
|
||||
diff --git a/gin/BUILD.gn b/gin/BUILD.gn
|
||||
index c6059fdb0e0f74ee3ef78c5517634ed5a36f1b10..e16b2ec43b98c3b8724fd85085a33fe52a1e1979 100644
|
||||
--- a/gin/BUILD.gn
|
||||
+++ b/gin/BUILD.gn
|
||||
@@ -88,6 +88,10 @@ component("gin") {
|
||||
frameworks = [ "CoreFoundation.framework" ]
|
||||
}
|
||||
|
||||
+ if (!is_mas_build) {
|
||||
+ public_deps += [ "//components/crash/core/common:crash_key" ]
|
||||
+ }
|
||||
+
|
||||
configs += [
|
||||
"//tools/v8_context_snapshot:use_v8_context_snapshot",
|
||||
"//v8:external_startup_data",
|
||||
diff --git a/gin/wrappable.cc b/gin/wrappable.cc
|
||||
index fe07eb94a8e679859bba6d76ff0d6ee86bd0c67e..ecb0aa2c4ec57e1814f4c94194e775440f4e35ee 100644
|
||||
--- a/gin/wrappable.cc
|
||||
+++ b/gin/wrappable.cc
|
||||
@@ -8,6 +8,11 @@
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "gin/per_isolate_data.h"
|
||||
|
||||
+#if !defined(MAS_BUILD)
|
||||
+#include "components/crash/core/common/crash_key.h"
|
||||
+#include "electron/shell/common/crash_keys.h"
|
||||
+#endif
|
||||
+
|
||||
namespace gin {
|
||||
|
||||
WrappableBase::WrappableBase() = default;
|
||||
@@ -36,6 +41,15 @@ void WrappableBase::FirstWeakCallback(
|
||||
void WrappableBase::SecondWeakCallback(
|
||||
const v8::WeakCallbackInfo<WrappableBase>& data) {
|
||||
WrappableBase* wrappable = data.GetParameter();
|
||||
+
|
||||
+#if !defined(MAS_BUILD)
|
||||
+ WrapperInfo* wrapperInfo = static_cast<WrapperInfo*>(data.GetInternalField(0));
|
||||
+ std::string location = electron::crash_keys::GetCrashValueForGinWrappable(wrapperInfo);
|
||||
+
|
||||
+ static crash_reporter::CrashKeyString<32> crash_key("gin-wrappable-fatal.location");
|
||||
+ crash_reporter::ScopedCrashKeyString auto_clear(&crash_key, location);
|
||||
+#endif
|
||||
+
|
||||
delete wrappable;
|
||||
}
|
||||
|
||||
457
patches/chromium/cherry-pick-39090918efac.patch
Normal file
457
patches/chromium/cherry-pick-39090918efac.patch
Normal file
@@ -0,0 +1,457 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: cfredric <cfredric@chromium.org>
|
||||
Date: Mon, 27 Sep 2021 22:14:18 +0000
|
||||
Subject: Consider HTTPS and WSS schemes identically for FPS.
|
||||
|
||||
This modifies the FPS implementation to normalize wss:// URLs into
|
||||
https:// URLs when determining the same-partiness of a request.
|
||||
|
||||
This allows SameParty cookies to be sent on same-party WSS connection
|
||||
requests. A browsertest is included to verify this.
|
||||
|
||||
Bug: 1251688
|
||||
Change-Id: Id277288982805e0d29c6683e0c13d4b7c7cfe359
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3182786
|
||||
Reviewed-by: Maksim Orlovich <morlovich@chromium.org>
|
||||
Reviewed-by: Shuran Huang <shuuran@chromium.org>
|
||||
Commit-Queue: Chris Fredrickson <cfredric@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#925457}
|
||||
|
||||
diff --git a/chrome/browser/net/websocket_browsertest.cc b/chrome/browser/net/websocket_browsertest.cc
|
||||
index 0714f0d0231d677edd0f0cdf82f4129ddc43a5c2..6f2f101743fbd470bafe90d7e5d14351ee0ff708 100644
|
||||
--- a/chrome/browser/net/websocket_browsertest.cc
|
||||
+++ b/chrome/browser/net/websocket_browsertest.cc
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "base/test/bind.h"
|
||||
#include "build/build_config.h"
|
||||
#include "chrome/browser/chrome_notification_types.h"
|
||||
+#include "chrome/browser/profiles/profile.h"
|
||||
#include "chrome/browser/ui/browser.h"
|
||||
#include "chrome/browser/ui/login/login_handler.h"
|
||||
#include "chrome/browser/ui/login/login_handler_test_utils.h"
|
||||
@@ -45,25 +46,31 @@
|
||||
#include "mojo/public/cpp/system/data_pipe.h"
|
||||
#include "net/base/network_isolation_key.h"
|
||||
#include "net/cookies/site_for_cookies.h"
|
||||
+#include "net/dns/mock_host_resolver.h"
|
||||
#include "net/test/embedded_test_server/embedded_test_server.h"
|
||||
#include "net/test/spawned_test_server/spawned_test_server.h"
|
||||
#include "net/test/test_data_directory.h"
|
||||
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
||||
+#include "services/network/public/cpp/network_switches.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "services/network/public/mojom/websocket.mojom.h"
|
||||
+#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "url/gurl.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace {
|
||||
|
||||
+using SSLOptions = net::SpawnedTestServer::SSLOptions;
|
||||
+
|
||||
class WebSocketBrowserTest : public InProcessBrowserTest {
|
||||
public:
|
||||
- WebSocketBrowserTest()
|
||||
+ explicit WebSocketBrowserTest(
|
||||
+ SSLOptions::ServerCertificate cert = SSLOptions::CERT_OK)
|
||||
: ws_server_(net::SpawnedTestServer::TYPE_WS,
|
||||
net::GetWebSocketTestDataDirectory()),
|
||||
wss_server_(net::SpawnedTestServer::TYPE_WSS,
|
||||
- SSLOptions(SSLOptions::CERT_OK),
|
||||
+ SSLOptions(cert),
|
||||
net::GetWebSocketTestDataDirectory()) {}
|
||||
|
||||
protected:
|
||||
@@ -145,7 +152,6 @@ class WebSocketBrowserTest : public InProcessBrowserTest {
|
||||
net::SpawnedTestServer wss_server_;
|
||||
|
||||
private:
|
||||
- typedef net::SpawnedTestServer::SSLOptions SSLOptions;
|
||||
std::unique_ptr<content::TitleWatcher> watcher_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WebSocketBrowserTest);
|
||||
@@ -162,37 +168,72 @@ class WebSocketBrowserTestWithAllowFileAccessFromFiles
|
||||
};
|
||||
|
||||
// Framework for tests using the connect_to.html page served by a separate HTTP
|
||||
-// server.
|
||||
+// or HTTPS server.
|
||||
class WebSocketBrowserConnectToTest : public WebSocketBrowserTest {
|
||||
protected:
|
||||
- WebSocketBrowserConnectToTest() {
|
||||
- http_server_.ServeFilesFromSourceDirectory(
|
||||
- net::GetWebSocketTestDataDirectory());
|
||||
- }
|
||||
+ explicit WebSocketBrowserConnectToTest(
|
||||
+ SSLOptions::ServerCertificate cert = SSLOptions::CERT_OK)
|
||||
+ : WebSocketBrowserTest(cert) {}
|
||||
|
||||
// The title watcher and HTTP server are set up automatically by the test
|
||||
// framework. Each test case still needs to configure and start the
|
||||
// WebSocket server(s) it needs.
|
||||
void SetUpOnMainThread() override {
|
||||
+ server().ServeFilesFromSourceDirectory(
|
||||
+ net::GetWebSocketTestDataDirectory());
|
||||
WebSocketBrowserTest::SetUpOnMainThread();
|
||||
- ASSERT_TRUE(http_server_.Start());
|
||||
+ ASSERT_TRUE(server().Start());
|
||||
}
|
||||
|
||||
- // Supply a ws: or wss: URL to connect to.
|
||||
- void ConnectTo(GURL url) {
|
||||
- ASSERT_TRUE(http_server_.Started());
|
||||
+ // Supply a ws: or wss: URL to connect to. Serves connect_to.html from the
|
||||
+ // server's default host.
|
||||
+ void ConnectTo(const GURL& url) {
|
||||
+ ConnectTo(server().base_url().host(), url);
|
||||
+ }
|
||||
+
|
||||
+ // Supply a ws: or wss: URL to connect to via loading `host`/connect_to.html.
|
||||
+ void ConnectTo(const std::string& host, const GURL& url) {
|
||||
+ ASSERT_TRUE(server().Started());
|
||||
std::string query("url=" + url.spec());
|
||||
GURL::Replacements replacements;
|
||||
replacements.SetQueryStr(query);
|
||||
- ui_test_utils::NavigateToURL(browser(),
|
||||
- http_server_.GetURL("/connect_to.html")
|
||||
- .ReplaceComponents(replacements));
|
||||
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
|
||||
+ browser(), server()
|
||||
+ .GetURL(host, "/connect_to.html")
|
||||
+ .ReplaceComponents(replacements)));
|
||||
}
|
||||
|
||||
- private:
|
||||
+ virtual net::EmbeddedTestServer& server() = 0;
|
||||
+};
|
||||
+
|
||||
+// Concrete impl for tests that use connect_to.html over HTTP.
|
||||
+class WebSocketBrowserHTTPConnectToTest : public WebSocketBrowserConnectToTest {
|
||||
+ protected:
|
||||
+ net::EmbeddedTestServer& server() override { return http_server_; }
|
||||
+
|
||||
net::EmbeddedTestServer http_server_;
|
||||
};
|
||||
|
||||
+// Concrete impl for tests that use connect_to.html over HTTPS.
|
||||
+class WebSocketBrowserHTTPSConnectToTest
|
||||
+ : public WebSocketBrowserConnectToTest {
|
||||
+ protected:
|
||||
+ explicit WebSocketBrowserHTTPSConnectToTest(
|
||||
+ SSLOptions::ServerCertificate cert = SSLOptions::CERT_OK)
|
||||
+ : WebSocketBrowserConnectToTest(cert),
|
||||
+ https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
|
||||
+
|
||||
+ void SetUpOnMainThread() override {
|
||||
+ host_resolver()->AddRule("*", "127.0.0.1");
|
||||
+ server().SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
|
||||
+ WebSocketBrowserConnectToTest::SetUpOnMainThread();
|
||||
+ }
|
||||
+
|
||||
+ net::EmbeddedTestServer& server() override { return https_server_; }
|
||||
+
|
||||
+ net::EmbeddedTestServer https_server_;
|
||||
+};
|
||||
+
|
||||
// Automatically fill in any login prompts that appear with the supplied
|
||||
// credentials.
|
||||
class AutoLogin : public content::NotificationObserver {
|
||||
@@ -352,7 +393,7 @@ IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest,
|
||||
EXPECT_EQ("PASS", WaitAndGetTitle());
|
||||
}
|
||||
|
||||
-IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
|
||||
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserHTTPConnectToTest,
|
||||
WebSocketBasicAuthInWSURL) {
|
||||
// Launch a basic-auth-protected WebSocket server.
|
||||
ws_server_.set_websocket_basic_auth(true);
|
||||
@@ -364,7 +405,7 @@ IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
|
||||
EXPECT_EQ("PASS", WaitAndGetTitle());
|
||||
}
|
||||
|
||||
-IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
|
||||
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserHTTPConnectToTest,
|
||||
WebSocketBasicAuthInWSURLBadCreds) {
|
||||
// Launch a basic-auth-protected WebSocket server.
|
||||
ws_server_.set_websocket_basic_auth(true);
|
||||
@@ -376,7 +417,7 @@ IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
|
||||
EXPECT_EQ("FAIL", WaitAndGetTitle());
|
||||
}
|
||||
|
||||
-IN_PROC_BROWSER_TEST_F(WebSocketBrowserConnectToTest,
|
||||
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserHTTPConnectToTest,
|
||||
WebSocketBasicAuthNoCreds) {
|
||||
// Launch a basic-auth-protected WebSocket server.
|
||||
ws_server_.set_websocket_basic_auth(true);
|
||||
@@ -420,8 +461,7 @@ IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, MAYBE_WebSocketAppliesHSTS) {
|
||||
https_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
|
||||
net::SpawnedTestServer wss_server(
|
||||
net::SpawnedTestServer::TYPE_WSS,
|
||||
- net::SpawnedTestServer::SSLOptions(
|
||||
- net::SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN),
|
||||
+ SSLOptions(SSLOptions::CERT_COMMON_NAME_IS_DOMAIN),
|
||||
net::GetWebSocketTestDataDirectory());
|
||||
// This test sets HSTS on localhost. To avoid being redirected to https, start
|
||||
// the http server on 127.0.0.1 instead.
|
||||
@@ -711,4 +751,43 @@ IN_PROC_BROWSER_TEST_F(WebSocketBrowserTestWithAllowFileAccessFromFiles,
|
||||
EXPECT_EQ("FILE", WaitAndGetTitle());
|
||||
}
|
||||
|
||||
+// A test fixture that enables First-Party Sets.
|
||||
+class FirstPartySetsWebSocketBrowserTest
|
||||
+ : public WebSocketBrowserHTTPSConnectToTest {
|
||||
+ public:
|
||||
+ FirstPartySetsWebSocketBrowserTest()
|
||||
+ : WebSocketBrowserHTTPSConnectToTest(SSLOptions::CERT_TEST_NAMES) {}
|
||||
+
|
||||
+ void SetUpCommandLine(base::CommandLine* command_line) override {
|
||||
+ WebSocketBrowserTest::SetUpCommandLine(command_line);
|
||||
+ command_line->AppendSwitchASCII(
|
||||
+ network::switches::kUseFirstPartySet,
|
||||
+ "https://a.test,https://b.test,https://c.test");
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+IN_PROC_BROWSER_TEST_F(FirstPartySetsWebSocketBrowserTest,
|
||||
+ SendsSamePartyCookies) {
|
||||
+ ASSERT_TRUE(wss_server_.Start());
|
||||
+
|
||||
+ ASSERT_TRUE(content::SetCookie(browser()->profile(),
|
||||
+ server().GetURL("a.test", "/"),
|
||||
+ "same-party-cookie=1; SameParty; Secure"));
|
||||
+ ASSERT_TRUE(content::SetCookie(browser()->profile(),
|
||||
+ server().GetURL("a.test", "/"),
|
||||
+ "same-site-cookie=1; SameSite=Lax; Secure"));
|
||||
+
|
||||
+ content::DOMMessageQueue message_queue;
|
||||
+ ConnectTo("b.test", wss_server_.GetURL("a.test", "echo-request-headers"));
|
||||
+
|
||||
+ std::string message;
|
||||
+ EXPECT_TRUE(message_queue.WaitForMessage(&message));
|
||||
+ // Only the SameParty cookie should have been sent, since it was a cross-site
|
||||
+ // but same-party connection.
|
||||
+ EXPECT_THAT(message, testing::HasSubstr("same-party-cookie=1"));
|
||||
+ EXPECT_THAT(message, testing::Not(testing::HasSubstr("same-site-cookie=1")));
|
||||
+
|
||||
+ EXPECT_EQ("PASS", WaitAndGetTitle());
|
||||
+}
|
||||
+
|
||||
} // namespace
|
||||
diff --git a/net/data/websocket/connect_to.html b/net/data/websocket/connect_to.html
|
||||
index 05c653fc5d2ab9a333efea5b4c5eee83a03bbe07..8a6d78214fe5974cbb0ec62b61f4d7fdcdf42c3b 100644
|
||||
--- a/net/data/websocket/connect_to.html
|
||||
+++ b/net/data/websocket/connect_to.html
|
||||
@@ -29,6 +29,17 @@ ws.onclose = function()
|
||||
document.title = 'FAIL';
|
||||
}
|
||||
|
||||
+ws.onmessage = function(evt)
|
||||
+{
|
||||
+ domAutomationController.send(evt.data);
|
||||
+}
|
||||
+
|
||||
+ws.onerror = function(evt)
|
||||
+{
|
||||
+ console.error(`WebSocket error: '${evt.message}'`);
|
||||
+}
|
||||
+
|
||||
+
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc
|
||||
index 5157cde07dc141f64509a50df848013c0b60dac5..4d947a6f7211b265a22e8e1d2445a9977572d6ac 100644
|
||||
--- a/net/test/spawned_test_server/base_test_server.cc
|
||||
+++ b/net/test/spawned_test_server/base_test_server.cc
|
||||
@@ -156,6 +156,8 @@ base::FilePath BaseTestServer::SSLOptions::GetCertificateFile() const {
|
||||
FILE_PATH_LITERAL("key_usage_rsa_digitalsignature.pem"));
|
||||
case CERT_AUTO:
|
||||
return base::FilePath();
|
||||
+ case CERT_TEST_NAMES:
|
||||
+ return base::FilePath(FILE_PATH_LITERAL("test_names.pem"));
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
@@ -247,6 +249,14 @@ GURL BaseTestServer::GetURL(const std::string& path) const {
|
||||
return GURL(GetScheme() + "://" + host_port_pair_.ToString() + "/" + path);
|
||||
}
|
||||
|
||||
+GURL BaseTestServer::GetURL(const std::string& hostname,
|
||||
+ const std::string& relative_url) const {
|
||||
+ GURL local_url = GetURL(relative_url);
|
||||
+ GURL::Replacements replace_host;
|
||||
+ replace_host.SetHostStr(hostname);
|
||||
+ return local_url.ReplaceComponents(replace_host);
|
||||
+}
|
||||
+
|
||||
GURL BaseTestServer::GetURLWithUser(const std::string& path,
|
||||
const std::string& user) const {
|
||||
return GURL(GetScheme() + "://" + user + "@" + host_port_pair_.ToString() +
|
||||
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
|
||||
index a3854cb9d81bcccae33a67b3952517a8d82521e9..aab6ab6c3e9d1480c74a46a8a97d2be112685ff1 100644
|
||||
--- a/net/test/spawned_test_server/base_test_server.h
|
||||
+++ b/net/test/spawned_test_server/base_test_server.h
|
||||
@@ -81,6 +81,11 @@ class BaseTestServer {
|
||||
// A certificate with invalid notBefore and notAfter times. Windows'
|
||||
// certificate library will not parse this certificate.
|
||||
CERT_BAD_VALIDITY,
|
||||
+
|
||||
+ // A certificate that covers a number of test names. See [test_names] in
|
||||
+ // net/data/ssl/scripts/ee.cnf. More may be added by editing this list and
|
||||
+ // and rerunning net/data/ssl/scripts/generate-test-certs.sh.
|
||||
+ CERT_TEST_NAMES,
|
||||
};
|
||||
|
||||
// Bitmask of key exchange algorithms that the test server supports and that
|
||||
@@ -276,6 +281,8 @@ class BaseTestServer {
|
||||
bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT;
|
||||
|
||||
GURL GetURL(const std::string& path) const;
|
||||
+ GURL GetURL(const std::string& hostname,
|
||||
+ const std::string& relative_url) const;
|
||||
|
||||
GURL GetURLWithUser(const std::string& path,
|
||||
const std::string& user) const;
|
||||
diff --git a/services/network/first_party_sets/first_party_sets.cc b/services/network/first_party_sets/first_party_sets.cc
|
||||
index 1650c28d8b6c61b30531e1e2ef3e2869d8450360..826b403a2a9702c255ee9b7ea38dc74b9e18791d 100644
|
||||
--- a/services/network/first_party_sets/first_party_sets.cc
|
||||
+++ b/services/network/first_party_sets/first_party_sets.cc
|
||||
@@ -91,16 +91,17 @@ bool FirstPartySets::IsContextSamePartyWithSite(
|
||||
const net::SchemefulSite* top_frame_site,
|
||||
const std::set<net::SchemefulSite>& party_context,
|
||||
bool infer_singleton_sets) const {
|
||||
- const net::SchemefulSite* site_owner = FindOwner(site, infer_singleton_sets);
|
||||
- if (!site_owner)
|
||||
+ const absl::optional<net::SchemefulSite> site_owner =
|
||||
+ FindOwner(site, infer_singleton_sets);
|
||||
+ if (!site_owner.has_value())
|
||||
return false;
|
||||
|
||||
const auto is_owned_by_site_owner =
|
||||
- [this, site_owner,
|
||||
+ [this, &site_owner,
|
||||
infer_singleton_sets](const net::SchemefulSite& context_site) -> bool {
|
||||
- const net::SchemefulSite* context_owner =
|
||||
+ const absl::optional<net::SchemefulSite> context_owner =
|
||||
FindOwner(context_site, infer_singleton_sets);
|
||||
- return context_owner && *context_owner == *site_owner;
|
||||
+ return context_owner.has_value() && *context_owner == *site_owner;
|
||||
};
|
||||
|
||||
if (top_frame_site && !is_owned_by_site_owner(*top_frame_site))
|
||||
@@ -131,7 +132,8 @@ net::FirstPartySetsContextType FirstPartySets::ComputeContextType(
|
||||
const absl::optional<net::SchemefulSite>& top_frame_site,
|
||||
const std::set<net::SchemefulSite>& party_context) const {
|
||||
constexpr bool infer_singleton_sets = true;
|
||||
- const net::SchemefulSite* site_owner = FindOwner(site, infer_singleton_sets);
|
||||
+ const absl::optional<net::SchemefulSite> site_owner =
|
||||
+ FindOwner(site, infer_singleton_sets);
|
||||
// Note: the `party_context` consists of the intermediate frames (for frame
|
||||
// requests) or intermediate frames and current frame for subresource
|
||||
// requests.
|
||||
@@ -152,18 +154,22 @@ net::FirstPartySetsContextType FirstPartySets::ComputeContextType(
|
||||
: net::FirstPartySetsContextType::kTopResourceMatchMixed;
|
||||
}
|
||||
|
||||
-const net::SchemefulSite* FirstPartySets::FindOwner(
|
||||
+const absl::optional<net::SchemefulSite> FirstPartySets::FindOwner(
|
||||
const net::SchemefulSite& site,
|
||||
bool infer_singleton_sets) const {
|
||||
- const auto it = sets_.find(site);
|
||||
- if (it == sets_.end())
|
||||
- return infer_singleton_sets ? &site : nullptr;
|
||||
- return &it->second;
|
||||
+ net::SchemefulSite normalized_site = site;
|
||||
+ normalized_site.ConvertWebSocketToHttp();
|
||||
+ const auto it = sets_.find(normalized_site);
|
||||
+ if (it != sets_.end())
|
||||
+ return it->second;
|
||||
+ if (infer_singleton_sets)
|
||||
+ return normalized_site;
|
||||
+ return absl::nullopt;
|
||||
}
|
||||
|
||||
bool FirstPartySets::IsInNontrivialFirstPartySet(
|
||||
const net::SchemefulSite& site) const {
|
||||
- return base::Contains(sets_, site);
|
||||
+ return FindOwner(site, /*infer_singleton_sets=*/false).has_value();
|
||||
}
|
||||
|
||||
base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>>
|
||||
@@ -244,7 +250,8 @@ base::flat_set<net::SchemefulSite> FirstPartySets::ComputeSetsDiff(
|
||||
for (const auto& old_pair : old_sets) {
|
||||
const net::SchemefulSite& old_member = old_pair.first;
|
||||
const net::SchemefulSite& old_owner = old_pair.second;
|
||||
- const net::SchemefulSite* current_owner = FindOwner(old_member, false);
|
||||
+ const absl::optional<net::SchemefulSite> current_owner =
|
||||
+ FindOwner(old_member, false);
|
||||
// Look for the removed sites and the ones have owner changed.
|
||||
if (!current_owner || *current_owner != old_owner) {
|
||||
result.emplace(old_member);
|
||||
diff --git a/services/network/first_party_sets/first_party_sets.h b/services/network/first_party_sets/first_party_sets.h
|
||||
index 8158b555856526170051cba72a08312a5528de51..fc87e5155667befc5a94bbe539ca71dc47d5f7d1 100644
|
||||
--- a/services/network/first_party_sets/first_party_sets.h
|
||||
+++ b/services/network/first_party_sets/first_party_sets.h
|
||||
@@ -97,11 +97,12 @@ class FirstPartySets {
|
||||
base::OnceCallback<void(const std::string&)> callback);
|
||||
|
||||
private:
|
||||
- // Returns a pointer to `site`'s owner (optionally inferring a singleton set
|
||||
- // if necessary), or `nullptr` if `site` has no owner. Must not return
|
||||
- // `nullptr` if `infer_singleton_sets` is true.
|
||||
- const net::SchemefulSite* FindOwner(const net::SchemefulSite& site,
|
||||
- bool infer_singleton_sets) const;
|
||||
+ // Returns `site`'s owner (optionally inferring a singleton set if necessary),
|
||||
+ // or `nullopt` if `site` has no owner. Must not return `nullopt` if
|
||||
+ // `infer_singleton_sets` is true.
|
||||
+ const absl::optional<net::SchemefulSite> FindOwner(
|
||||
+ const net::SchemefulSite& site,
|
||||
+ bool infer_singleton_sets) const;
|
||||
|
||||
// We must ensure there's no intersection between the manually-specified set
|
||||
// and the sets that came from Component Updater. (When reconciling the
|
||||
diff --git a/services/network/first_party_sets/first_party_sets_unittest.cc b/services/network/first_party_sets/first_party_sets_unittest.cc
|
||||
index 2055619f4c999cbfd5a5ee4780e2eb5c1dad5816..52eb8e8a3d87172353c64bba972311db2889c693 100644
|
||||
--- a/services/network/first_party_sets/first_party_sets_unittest.cc
|
||||
+++ b/services/network/first_party_sets/first_party_sets_unittest.cc
|
||||
@@ -1167,6 +1167,8 @@ TEST_F(FirstPartySetsTest, ComputeContext) {
|
||||
net::SchemefulSite nonmember1(GURL("https://nonmember1.test"));
|
||||
net::SchemefulSite member(GURL("https://member1.test"));
|
||||
net::SchemefulSite owner(GURL("https://example.test"));
|
||||
+ net::SchemefulSite wss_member(GURL("wss://member1.test"));
|
||||
+ net::SchemefulSite wss_nonmember(GURL("wss://nonmember.test"));
|
||||
|
||||
// Works as usual for sites that are in First-Party sets.
|
||||
EXPECT_THAT(sets().ComputeContext(member, &member, {member}),
|
||||
@@ -1180,10 +1182,17 @@ TEST_F(FirstPartySetsTest, ComputeContext) {
|
||||
EXPECT_THAT(sets().ComputeContext(member, &member, {member, owner}),
|
||||
net::SamePartyContext(SamePartyContextType::kSameParty));
|
||||
|
||||
+ // Works if the site is provided with WSS scheme instead of HTTPS.
|
||||
+ EXPECT_THAT(sets().ComputeContext(wss_member, &member, {member, owner}),
|
||||
+ net::SamePartyContext(SamePartyContextType::kSameParty));
|
||||
+
|
||||
EXPECT_THAT(sets().ComputeContext(nonmember, &member, {member}),
|
||||
net::SamePartyContext(SamePartyContextType::kCrossParty));
|
||||
EXPECT_THAT(sets().ComputeContext(member, &nonmember, {member}),
|
||||
net::SamePartyContext(SamePartyContextType::kCrossParty));
|
||||
+ EXPECT_THAT(
|
||||
+ sets().ComputeContext(wss_nonmember, &wss_member, {member, owner}),
|
||||
+ net::SamePartyContext(SamePartyContextType::kCrossParty));
|
||||
|
||||
// Top&resource differs from Ancestors.
|
||||
EXPECT_THAT(sets().ComputeContext(member, &member, {nonmember}),
|
||||
@@ -1225,6 +1234,12 @@ TEST_F(FirstPartySetsTest, IsInNontrivialFirstPartySet) {
|
||||
EXPECT_TRUE(sets().IsInNontrivialFirstPartySet(
|
||||
net::SchemefulSite(GURL("https://member1.test"))));
|
||||
|
||||
+ EXPECT_TRUE(sets().IsInNontrivialFirstPartySet(
|
||||
+ net::SchemefulSite(GURL("wss://member1.test"))));
|
||||
+
|
||||
+ EXPECT_FALSE(sets().IsInNontrivialFirstPartySet(
|
||||
+ net::SchemefulSite(GURL("ws://member1.test"))));
|
||||
+
|
||||
EXPECT_FALSE(sets().IsInNontrivialFirstPartySet(
|
||||
net::SchemefulSite(GURL("https://nonmember.test"))));
|
||||
}
|
||||
626
patches/chromium/cherry-pick-ec42dfd3545f.patch
Normal file
626
patches/chromium/cherry-pick-ec42dfd3545f.patch
Normal file
@@ -0,0 +1,626 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shuran Huang <shuuran@chromium.org>
|
||||
Date: Fri, 24 Sep 2021 00:47:47 +0000
|
||||
Subject: Add functions to pass in persisted FPSs and compute diffs.
|
||||
|
||||
Pass the persisted FPSs and a callback that takes a FPSs into Network
|
||||
Service. The persisted FPSs is parsed and compared to the current FPSs
|
||||
in the FirstPartySets class, then call the callback with the current
|
||||
FPSs. The function that passes in the persisted FPSs and the callback
|
||||
has not been called anywhere yet.
|
||||
|
||||
Bug: 1219656
|
||||
Change-Id: I08c531aa08d3aeeb772c1eb9a3a453a07b0349d3
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3103693
|
||||
Commit-Queue: Shuran Huang <shuuran@chromium.org>
|
||||
Reviewed-by: Will Harris <wfh@chromium.org>
|
||||
Reviewed-by: Matt Menke <mmenke@chromium.org>
|
||||
Reviewed-by: Chris Fredrickson <cfredric@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#924570}
|
||||
|
||||
diff --git a/services/network/first_party_sets/first_party_sets.cc b/services/network/first_party_sets/first_party_sets.cc
|
||||
index f7e732e88d6e6ebc5daed9169d5eee336a9de8c1..1650c28d8b6c61b30531e1e2ef3e2869d8450360 100644
|
||||
--- a/services/network/first_party_sets/first_party_sets.cc
|
||||
+++ b/services/network/first_party_sets/first_party_sets.cc
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "base/logging.h"
|
||||
#include "base/ranges/algorithm.h"
|
||||
#include "base/strings/string_split.h"
|
||||
+#include "base/task/post_task.h"
|
||||
#include "net/base/schemeful_site.h"
|
||||
#include "net/cookies/cookie_constants.h"
|
||||
#include "net/cookies/same_party_context.h"
|
||||
@@ -72,12 +73,16 @@ void FirstPartySets::SetManuallySpecifiedSet(const std::string& flag_value) {
|
||||
flag_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY));
|
||||
|
||||
ApplyManuallySpecifiedSet();
|
||||
+ manual_sets_ready_ = true;
|
||||
+ ClearSiteDataOnChangedSetsIfReady();
|
||||
}
|
||||
|
||||
base::flat_map<net::SchemefulSite, net::SchemefulSite>*
|
||||
FirstPartySets::ParseAndSet(base::StringPiece raw_sets) {
|
||||
sets_ = FirstPartySetParser::ParseSetsFromComponentUpdater(raw_sets);
|
||||
ApplyManuallySpecifiedSet();
|
||||
+ component_sets_ready_ = true;
|
||||
+ ClearSiteDataOnChangedSetsIfReady();
|
||||
return &sets_;
|
||||
}
|
||||
|
||||
@@ -218,4 +223,48 @@ void FirstPartySets::ApplyManuallySpecifiedSet() {
|
||||
sets_.emplace(manual_owner, manual_owner);
|
||||
}
|
||||
|
||||
+void FirstPartySets::SetPersistedSets(base::StringPiece raw_sets) {
|
||||
+ raw_persisted_sets_ = std::string(raw_sets);
|
||||
+ persisted_sets_ready_ = true;
|
||||
+ ClearSiteDataOnChangedSetsIfReady();
|
||||
+}
|
||||
+
|
||||
+void FirstPartySets::SetOnSiteDataCleared(
|
||||
+ base::OnceCallback<void(const std::string&)> callback) {
|
||||
+ on_site_data_cleared_ = std::move(callback);
|
||||
+ ClearSiteDataOnChangedSetsIfReady();
|
||||
+}
|
||||
+
|
||||
+base::flat_set<net::SchemefulSite> FirstPartySets::ComputeSetsDiff(
|
||||
+ const base::flat_map<net::SchemefulSite, net::SchemefulSite>& old_sets) {
|
||||
+ if (old_sets.empty())
|
||||
+ return {};
|
||||
+
|
||||
+ base::flat_set<net::SchemefulSite> result;
|
||||
+ for (const auto& old_pair : old_sets) {
|
||||
+ const net::SchemefulSite& old_member = old_pair.first;
|
||||
+ const net::SchemefulSite& old_owner = old_pair.second;
|
||||
+ const net::SchemefulSite* current_owner = FindOwner(old_member, false);
|
||||
+ // Look for the removed sites and the ones have owner changed.
|
||||
+ if (!current_owner || *current_owner != old_owner) {
|
||||
+ result.emplace(old_member);
|
||||
+ }
|
||||
+ }
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
+void FirstPartySets::ClearSiteDataOnChangedSetsIfReady() {
|
||||
+ if (!persisted_sets_ready_ || !component_sets_ready_ || !manual_sets_ready_ ||
|
||||
+ on_site_data_cleared_.is_null())
|
||||
+ return;
|
||||
+
|
||||
+ base::flat_set<net::SchemefulSite> diff = ComputeSetsDiff(
|
||||
+ FirstPartySetParser::DeserializeFirstPartySets(raw_persisted_sets_));
|
||||
+
|
||||
+ // TODO(shuuran@chromium.org): Implement site state clearing.
|
||||
+
|
||||
+ std::move(on_site_data_cleared_)
|
||||
+ .Run(FirstPartySetParser::SerializeFirstPartySets(sets_));
|
||||
+}
|
||||
+
|
||||
} // namespace network
|
||||
diff --git a/services/network/first_party_sets/first_party_sets.h b/services/network/first_party_sets/first_party_sets.h
|
||||
index 81e0e1080d965947a2ebc1635638c25ad75a1bf7..8158b555856526170051cba72a08312a5528de51 100644
|
||||
--- a/services/network/first_party_sets/first_party_sets.h
|
||||
+++ b/services/network/first_party_sets/first_party_sets.h
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
+#include "base/callback.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "net/base/schemeful_site.h"
|
||||
@@ -87,6 +88,14 @@ class FirstPartySets {
|
||||
// the members of the set includes the owner.
|
||||
base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> Sets() const;
|
||||
|
||||
+ // Sets the `raw_persisted_sets_`, which is a JSON-encoded
|
||||
+ // string representation of a map of site -> site.
|
||||
+ void SetPersistedSets(base::StringPiece persisted_sets);
|
||||
+ // Sets the `on_site_data_cleared_` callback, which takes input of a
|
||||
+ // JSON-encoded string representation of a map of site -> site.
|
||||
+ void SetOnSiteDataCleared(
|
||||
+ base::OnceCallback<void(const std::string&)> callback);
|
||||
+
|
||||
private:
|
||||
// Returns a pointer to `site`'s owner (optionally inferring a singleton set
|
||||
// if necessary), or `nullptr` if `site` has no owner. Must not return
|
||||
@@ -101,6 +110,19 @@ class FirstPartySets {
|
||||
// `manually_specified_set_`.
|
||||
void ApplyManuallySpecifiedSet();
|
||||
|
||||
+ // Compares the map `old_sets` to `sets_` and returns the set of sites that:
|
||||
+ // 1) were in `old_sets` but are no longer in `sets_`, i.e. leave the FPSs;
|
||||
+ // or, 2) mapped to a different owner site.
|
||||
+ base::flat_set<net::SchemefulSite> ComputeSetsDiff(
|
||||
+ const base::flat_map<net::SchemefulSite, net::SchemefulSite>& old_sets);
|
||||
+
|
||||
+ // Checks the required inputs have been received, and if so, computes the diff
|
||||
+ // between the `sets_` and the parsed `raw_persisted_sets_`, and clears the
|
||||
+ // site data of the set of sites based on the diff.
|
||||
+ //
|
||||
+ // TODO(shuuran@chromium.org): Implement the code to clear site state.
|
||||
+ void ClearSiteDataOnChangedSetsIfReady();
|
||||
+
|
||||
// Represents the mapping of site -> site, where keys are members of sets, and
|
||||
// values are owners of the sets. Owners are explicitly represented as members
|
||||
// of the set.
|
||||
@@ -108,6 +130,22 @@ class FirstPartySets {
|
||||
absl::optional<
|
||||
std::pair<net::SchemefulSite, base::flat_set<net::SchemefulSite>>>
|
||||
manually_specified_set_;
|
||||
+
|
||||
+ std::string raw_persisted_sets_;
|
||||
+
|
||||
+ bool persisted_sets_ready_ = false;
|
||||
+ bool component_sets_ready_ = false;
|
||||
+ bool manual_sets_ready_ = false;
|
||||
+
|
||||
+ // The callback runs after the site state clearing is completed.
|
||||
+ base::OnceCallback<void(const std::string&)> on_site_data_cleared_;
|
||||
+
|
||||
+ FRIEND_TEST_ALL_PREFIXES(FirstPartySets, ComputeSetsDiff_SitesJoined);
|
||||
+ FRIEND_TEST_ALL_PREFIXES(FirstPartySets, ComputeSetsDiff_SitesLeft);
|
||||
+ FRIEND_TEST_ALL_PREFIXES(FirstPartySets, ComputeSetsDiff_OwnerChanged);
|
||||
+ FRIEND_TEST_ALL_PREFIXES(FirstPartySets, ComputeSetsDiff_OwnerLeft);
|
||||
+ FRIEND_TEST_ALL_PREFIXES(FirstPartySets, ComputeSetsDiff_OwnerMemberRotate);
|
||||
+ FRIEND_TEST_ALL_PREFIXES(FirstPartySets, ComputeSetsDiff_EmptySets);
|
||||
};
|
||||
|
||||
} // namespace network
|
||||
diff --git a/services/network/first_party_sets/first_party_sets_unittest.cc b/services/network/first_party_sets/first_party_sets_unittest.cc
|
||||
index b929315d9b857e0f86d1d726f7cefefb7ad8e54c..2055619f4c999cbfd5a5ee4780e2eb5c1dad5816 100644
|
||||
--- a/services/network/first_party_sets/first_party_sets_unittest.cc
|
||||
+++ b/services/network/first_party_sets/first_party_sets_unittest.cc
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <initializer_list>
|
||||
|
||||
#include "base/json/json_reader.h"
|
||||
+#include "base/test/bind.h"
|
||||
#include "net/base/schemeful_site.h"
|
||||
#include "net/cookies/cookie_constants.h"
|
||||
#include "net/cookies/same_party_context.h"
|
||||
@@ -204,6 +205,30 @@ TEST(FirstPartySets, SetsManuallySpecified_Invalid_RegisteredDomain_Member) {
|
||||
EXPECT_THAT(sets.ParseAndSet("[]"), Pointee(IsEmpty()));
|
||||
}
|
||||
|
||||
+TEST(FirstPartySets, SetsManuallySpecified_Valid_EmptyValue) {
|
||||
+ FirstPartySets sets;
|
||||
+ sets.SetManuallySpecifiedSet("");
|
||||
+
|
||||
+ // Set non-empty existing sets to distinguish the failure case from the no-op
|
||||
+ // case when processing the manually-specified sets.
|
||||
+ const std::string existing_sets = R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )";
|
||||
+ ASSERT_TRUE(base::JSONReader::Read(existing_sets));
|
||||
+
|
||||
+ EXPECT_THAT(sets.ParseAndSet(existing_sets),
|
||||
+ Pointee(UnorderedElementsAre(
|
||||
+ Pair(SerializesTo("https://example.test"),
|
||||
+ SerializesTo("https://example.test")),
|
||||
+ Pair(SerializesTo("https://member.test"),
|
||||
+ SerializesTo("https://example.test")))));
|
||||
+}
|
||||
+
|
||||
TEST(FirstPartySets, SetsManuallySpecified_Valid_SingleMember) {
|
||||
FirstPartySets sets;
|
||||
sets.SetManuallySpecifiedSet("https://example.test,https://member.test");
|
||||
@@ -469,6 +494,311 @@ TEST(FirstPartySets, SetsManuallySpecified_PrunesInducedSingletons) {
|
||||
SerializesTo("https://example.test")))));
|
||||
}
|
||||
|
||||
+TEST(FirstPartySets, ComputeSetsDiff_SitesJoined) {
|
||||
+ auto old_sets = base::flat_map<net::SchemefulSite, net::SchemefulSite>{
|
||||
+ {net::SchemefulSite(GURL("https://example.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member1.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member3.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))}};
|
||||
+
|
||||
+ // Consistency check the reviewer-friendly JSON format matches the input.
|
||||
+ ASSERT_THAT(FirstPartySets().ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test", "https://member3.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )"),
|
||||
+ Pointee(old_sets));
|
||||
+
|
||||
+ FirstPartySets sets;
|
||||
+ sets.ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test", "https://member3.test"]
|
||||
+ },
|
||||
+ {
|
||||
+ "owner": "https://foo.test",
|
||||
+ "members": ["https://member2.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )");
|
||||
+ // "https://foo.test" and "https://member2.test" joined FPSs. We don't clear
|
||||
+ // site data upon joining, so the computed diff should be empty set.
|
||||
+ EXPECT_THAT(sets.ComputeSetsDiff(old_sets), IsEmpty());
|
||||
+}
|
||||
+
|
||||
+TEST(FirstPartySets, ComputeSetsDiff_SitesLeft) {
|
||||
+ auto old_sets = base::flat_map<net::SchemefulSite, net::SchemefulSite>{
|
||||
+ {net::SchemefulSite(GURL("https://example.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member1.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member3.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://foo.test")),
|
||||
+ net::SchemefulSite(GURL("https://foo.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member2.test")),
|
||||
+ net::SchemefulSite(GURL("https://foo.test"))}};
|
||||
+
|
||||
+ // Consistency check the reviewer-friendly JSON format matches the input.
|
||||
+ ASSERT_THAT(FirstPartySets().ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test", "https://member3.test"]
|
||||
+ },
|
||||
+ {
|
||||
+ "owner": "https://foo.test",
|
||||
+ "members": ["https://member2.test"]
|
||||
+ },
|
||||
+ ]
|
||||
+ )"),
|
||||
+ Pointee(old_sets));
|
||||
+
|
||||
+ FirstPartySets sets;
|
||||
+ sets.ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test"]
|
||||
+ },
|
||||
+ ]
|
||||
+ )");
|
||||
+ // Expected diff: "https://foo.test", "https://member2.test" and
|
||||
+ // "https://member3.test" left FPSs.
|
||||
+ EXPECT_THAT(sets.ComputeSetsDiff(old_sets),
|
||||
+ UnorderedElementsAre(SerializesTo("https://foo.test"),
|
||||
+ SerializesTo("https://member2.test"),
|
||||
+ SerializesTo("https://member3.test")));
|
||||
+}
|
||||
+
|
||||
+TEST(FirstPartySets, ComputeSetsDiff_OwnerChanged) {
|
||||
+ auto old_sets = base::flat_map<net::SchemefulSite, net::SchemefulSite>{
|
||||
+ {net::SchemefulSite(GURL("https://example.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member1.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://foo.test")),
|
||||
+ net::SchemefulSite(GURL("https://foo.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member2.test")),
|
||||
+ net::SchemefulSite(GURL("https://foo.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member3.test")),
|
||||
+ net::SchemefulSite(GURL("https://foo.test"))}};
|
||||
+
|
||||
+ // Consistency check the reviewer-friendly JSON format matches the input.
|
||||
+ ASSERT_THAT(FirstPartySets().ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test"]
|
||||
+ },
|
||||
+ {
|
||||
+ "owner": "https://foo.test",
|
||||
+ "members": ["https://member2.test", "https://member3.test"]
|
||||
+ },
|
||||
+ ]
|
||||
+ )"),
|
||||
+ Pointee(old_sets));
|
||||
+
|
||||
+ FirstPartySets sets;
|
||||
+ sets.ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test", "https://member3.test"]
|
||||
+ },
|
||||
+ {
|
||||
+ "owner": "https://foo.test",
|
||||
+ "members": ["https://member2.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )");
|
||||
+ // Expected diff: "https://member3.test" changed owner.
|
||||
+ EXPECT_THAT(sets.ComputeSetsDiff(old_sets),
|
||||
+ UnorderedElementsAre(SerializesTo("https://member3.test")));
|
||||
+}
|
||||
+
|
||||
+TEST(FirstPartySets, ComputeSetsDiff_OwnerLeft) {
|
||||
+ auto old_sets = base::flat_map<net::SchemefulSite, net::SchemefulSite>{
|
||||
+ {net::SchemefulSite(GURL("https://example.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://foo.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://bar.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))}};
|
||||
+
|
||||
+ // Consistency check the reviewer-friendly JSON format matches the input.
|
||||
+ ASSERT_THAT(FirstPartySets().ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://foo.test", "https://bar.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )"),
|
||||
+ Pointee(old_sets));
|
||||
+
|
||||
+ FirstPartySets sets;
|
||||
+ sets.ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://foo.test",
|
||||
+ "members": ["https://bar.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )");
|
||||
+ // Expected diff: "https://example.test" left FPSs, "https://foo.test" and
|
||||
+ // "https://bar.test" changed owner.
|
||||
+ // It would be valid to only have example.test in the diff, but our logic
|
||||
+ // isn't sophisticated enough yet to know that foo.test and bar.test don't
|
||||
+ // need to be included in the result.
|
||||
+ EXPECT_THAT(sets.ComputeSetsDiff(old_sets),
|
||||
+ UnorderedElementsAre(SerializesTo("https://example.test"),
|
||||
+ SerializesTo("https://foo.test"),
|
||||
+ SerializesTo("https://bar.test")));
|
||||
+}
|
||||
+
|
||||
+TEST(FirstPartySets, ComputeSetsDiff_OwnerMemberRotate) {
|
||||
+ auto old_sets = base::flat_map<net::SchemefulSite, net::SchemefulSite>{
|
||||
+ {net::SchemefulSite(GURL("https://example.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://foo.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))}};
|
||||
+
|
||||
+ // Consistency check the reviewer-friendly JSON format matches the input.
|
||||
+ ASSERT_THAT(FirstPartySets().ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://foo.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )"),
|
||||
+ Pointee(old_sets));
|
||||
+
|
||||
+ FirstPartySets sets;
|
||||
+ sets.ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://foo.test",
|
||||
+ "members": ["https://example.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )");
|
||||
+ // Expected diff: "https://example.test" and "https://foo.test" changed owner.
|
||||
+ // It would be valid to not include example.test and foo.test in the result,
|
||||
+ // but our logic isn't sophisticated enough yet to know that.ß
|
||||
+ EXPECT_THAT(sets.ComputeSetsDiff(old_sets),
|
||||
+ UnorderedElementsAre(SerializesTo("https://example.test"),
|
||||
+ SerializesTo("https://foo.test")));
|
||||
+}
|
||||
+
|
||||
+TEST(FirstPartySets, ComputeSetsDiff_EmptySets) {
|
||||
+ // Empty old_sets.
|
||||
+ FirstPartySets sets;
|
||||
+ sets.ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test"]
|
||||
+ },
|
||||
+ ]
|
||||
+ )");
|
||||
+ EXPECT_THAT(sets.ComputeSetsDiff({}), IsEmpty());
|
||||
+
|
||||
+ // Empty current sets.
|
||||
+ auto old_sets = base::flat_map<net::SchemefulSite, net::SchemefulSite>{
|
||||
+ {net::SchemefulSite(GURL("https://example.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))},
|
||||
+ {net::SchemefulSite(GURL("https://member1.test")),
|
||||
+ net::SchemefulSite(GURL("https://example.test"))}};
|
||||
+ // Consistency check the reviewer-friendly JSON format matches the input.
|
||||
+ ASSERT_THAT(FirstPartySets().ParseAndSet(R"(
|
||||
+ [
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test"]
|
||||
+ }
|
||||
+ ]
|
||||
+ )"),
|
||||
+ Pointee(old_sets));
|
||||
+ EXPECT_THAT(FirstPartySets().ComputeSetsDiff(old_sets),
|
||||
+ UnorderedElementsAre(SerializesTo("https://example.test"),
|
||||
+ SerializesTo("https://member1.test")));
|
||||
+}
|
||||
+
|
||||
+TEST(FirstPartySets, ClearSiteDataOnChangedSetsIfReady_NotReady) {
|
||||
+ int callback_calls = 0;
|
||||
+ auto callback = base::BindLambdaForTesting(
|
||||
+ [&](const std::string& got) { callback_calls++; });
|
||||
+ // component sets not ready.
|
||||
+ {
|
||||
+ FirstPartySets sets;
|
||||
+ callback_calls = 0;
|
||||
+ sets.SetPersistedSets("{}");
|
||||
+ sets.SetManuallySpecifiedSet("");
|
||||
+ sets.SetOnSiteDataCleared(callback);
|
||||
+ EXPECT_EQ(callback_calls, 0);
|
||||
+ }
|
||||
+ // manual sets not ready.
|
||||
+ {
|
||||
+ FirstPartySets sets;
|
||||
+ callback_calls = 0;
|
||||
+ sets.ParseAndSet("[]");
|
||||
+ sets.SetPersistedSets("{}");
|
||||
+ sets.SetOnSiteDataCleared(callback);
|
||||
+ EXPECT_EQ(callback_calls, 0);
|
||||
+ }
|
||||
+ // persisted sets not ready.
|
||||
+ {
|
||||
+ FirstPartySets sets;
|
||||
+ callback_calls = 0;
|
||||
+ sets.ParseAndSet("[]");
|
||||
+ sets.SetManuallySpecifiedSet("");
|
||||
+ sets.SetOnSiteDataCleared(callback);
|
||||
+ EXPECT_EQ(callback_calls, 0);
|
||||
+ }
|
||||
+ // callback not set.
|
||||
+ {
|
||||
+ FirstPartySets sets;
|
||||
+ callback_calls = 0;
|
||||
+ sets.ParseAndSet("[]");
|
||||
+ sets.SetManuallySpecifiedSet("");
|
||||
+ sets.SetPersistedSets("{}");
|
||||
+ EXPECT_EQ(callback_calls, 0);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// The callback only runs when `old_sets` is generated and `sets` has merged the
|
||||
+// inputs from Component Updater and command line flag.
|
||||
+TEST(FirstPartySets, ClearSiteDataOnChangedSetsIfReady_Ready) {
|
||||
+ FirstPartySets sets;
|
||||
+ int callback_calls = 0;
|
||||
+ sets.ParseAndSet(R"([
|
||||
+ {
|
||||
+ "owner": "https://example.test",
|
||||
+ "members": ["https://member1.test"]
|
||||
+ }
|
||||
+ ])");
|
||||
+ sets.SetManuallySpecifiedSet("https://example2.test,https://member2.test");
|
||||
+ sets.SetPersistedSets(
|
||||
+ R"({"https://example.test":"https://example.test",
|
||||
+ "https://member1.test":"https://example.test"})");
|
||||
+ sets.SetOnSiteDataCleared(base::BindLambdaForTesting([&](const std::string&
|
||||
+ got) {
|
||||
+ EXPECT_EQ(
|
||||
+ got,
|
||||
+ R"({"https://member1.test":"https://example.test","https://member2.test":"https://example2.test"})");
|
||||
+ callback_calls++;
|
||||
+ }));
|
||||
+ EXPECT_EQ(callback_calls, 1);
|
||||
+}
|
||||
+
|
||||
class FirstPartySetsTest : public ::testing::Test {
|
||||
public:
|
||||
FirstPartySetsTest() {
|
||||
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
|
||||
index 5d598ff6c626510bf1da00d35bc9ba4179bac93e..c850d6950aa7777cdc5c2056d4b80c9dd44d3af9 100644
|
||||
--- a/services/network/network_service.cc
|
||||
+++ b/services/network/network_service.cc
|
||||
@@ -343,8 +343,7 @@ void NetworkService::Initialize(mojom::NetworkServiceParamsPtr params,
|
||||
}
|
||||
|
||||
first_party_sets_ = std::make_unique<FirstPartySets>();
|
||||
- if (net::cookie_util::IsFirstPartySetsEnabled() &&
|
||||
- command_line->HasSwitch(switches::kUseFirstPartySet)) {
|
||||
+ if (net::cookie_util::IsFirstPartySetsEnabled()) {
|
||||
first_party_sets_->SetManuallySpecifiedSet(
|
||||
command_line->GetSwitchValueASCII(switches::kUseFirstPartySet));
|
||||
}
|
||||
@@ -785,6 +784,14 @@ void NetworkService::SetFirstPartySets(const std::string& raw_sets) {
|
||||
first_party_sets_->ParseAndSet(raw_sets);
|
||||
}
|
||||
|
||||
+void NetworkService::SetPersistedFirstPartySetsAndGetCurrentSets(
|
||||
+ const std::string& persisted_sets,
|
||||
+ mojom::NetworkService::SetPersistedFirstPartySetsAndGetCurrentSetsCallback
|
||||
+ callback) {
|
||||
+ first_party_sets_->SetPersistedSets(persisted_sets);
|
||||
+ first_party_sets_->SetOnSiteDataCleared(std::move(callback));
|
||||
+}
|
||||
+
|
||||
void NetworkService::SetExplicitlyAllowedPorts(
|
||||
const std::vector<uint16_t>& ports) {
|
||||
net::SetExplicitlyAllowedPorts(ports);
|
||||
diff --git a/services/network/network_service.h b/services/network/network_service.h
|
||||
index 1da4505fc9fe478e00353cd55e615878ea875aa0..963e22f6d5e957684dc56dd6e3ae31fa430a355e 100644
|
||||
--- a/services/network/network_service.h
|
||||
+++ b/services/network/network_service.h
|
||||
@@ -204,6 +204,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService
|
||||
void BindTestInterface(
|
||||
mojo::PendingReceiver<mojom::NetworkServiceTest> receiver) override;
|
||||
void SetFirstPartySets(const std::string& raw_sets) override;
|
||||
+ void SetPersistedFirstPartySetsAndGetCurrentSets(
|
||||
+ const std::string& persisted_sets,
|
||||
+ mojom::NetworkService::SetPersistedFirstPartySetsAndGetCurrentSetsCallback
|
||||
+ callback) override;
|
||||
void SetExplicitlyAllowedPorts(const std::vector<uint16_t>& ports) override;
|
||||
|
||||
// Returns an HttpAuthHandlerFactory for the given NetworkContext.
|
||||
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
|
||||
index bd8ed7abe5870d23e3b0430f29f0448cf425fabc..dffa318ab85d6851c54d0f3c38ab6bb72f274f40 100644
|
||||
--- a/services/network/network_service_unittest.cc
|
||||
+++ b/services/network/network_service_unittest.cc
|
||||
@@ -925,6 +925,7 @@ class NetworkServiceTestWithService : public testing::Test {
|
||||
void SetUp() override {
|
||||
test_server_.AddDefaultHandlers(base::FilePath(kServicesTestData));
|
||||
ASSERT_TRUE(test_server_.Start());
|
||||
+ scoped_features_.InitAndEnableFeature(net::features::kFirstPartySets);
|
||||
service_ = NetworkService::CreateForTesting();
|
||||
service_->Bind(network_service_.BindNewPipeAndPassReceiver());
|
||||
}
|
||||
@@ -989,7 +990,7 @@ class NetworkServiceTestWithService : public testing::Test {
|
||||
mojo::Remote<mojom::NetworkContext> network_context_;
|
||||
mojo::Remote<mojom::URLLoader> loader_;
|
||||
|
||||
- DISALLOW_COPY_AND_ASSIGN(NetworkServiceTestWithService);
|
||||
+ base::test::ScopedFeatureList scoped_features_;
|
||||
};
|
||||
|
||||
// Verifies that loading a URL through the network service's mojo interface
|
||||
@@ -1169,6 +1170,18 @@ TEST_F(NetworkServiceTestWithService, GetNetworkList) {
|
||||
run_loop.Run();
|
||||
}
|
||||
|
||||
+TEST_F(NetworkServiceTestWithService,
|
||||
+ SetPersistedFirstPartySetsAndGetCurrentSets) {
|
||||
+ base::RunLoop run_loop;
|
||||
+ network_service_->SetPersistedFirstPartySetsAndGetCurrentSets(
|
||||
+ "", base::BindLambdaForTesting([&](const std::string& got) {
|
||||
+ EXPECT_EQ(got, "{}");
|
||||
+ run_loop.Quit();
|
||||
+ }));
|
||||
+ network_service_->SetFirstPartySets("");
|
||||
+ run_loop.Run();
|
||||
+}
|
||||
+
|
||||
class TestNetworkChangeManagerClient
|
||||
: public mojom::NetworkChangeManagerClient {
|
||||
public:
|
||||
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
|
||||
index fe5450b20b3c4a8490e853dd236bf6baefa90b81..59fbbde6ffc30d51304a72f402eee7c664ea111b 100644
|
||||
--- a/services/network/public/mojom/network_service.mojom
|
||||
+++ b/services/network/public/mojom/network_service.mojom
|
||||
@@ -373,6 +373,14 @@ interface NetworkService {
|
||||
// cleared (except for the manually-specified set, if one exists).
|
||||
SetFirstPartySets(string raw_sets);
|
||||
|
||||
+ // Sets the First-Party Sets data that was persisted to compare it with the
|
||||
+ // current First-Party Sets data set by `SetFirstPartySets()`, which is
|
||||
+ // considered more up-to-date, and returns a serialized version of the current
|
||||
+ // one. Both input and output are in format of the JSON-encoded string
|
||||
+ // representation of a map of site -> site.
|
||||
+ SetPersistedFirstPartySetsAndGetCurrentSets(string persisted_sets)
|
||||
+ => (string up_to_date_sets);
|
||||
+
|
||||
// Sets the list of ports which will be permitted even if they normally would
|
||||
// be restricted.
|
||||
SetExplicitlyAllowedPorts(array<uint16> ports);
|
||||
@@ -25,3 +25,4 @@ add_should_read_node_options_from_env_option_to_disable_node_options.patch
|
||||
repl_fix_crash_when_sharedarraybuffer_disabled.patch
|
||||
fix_readbarrier_undefined_symbol_error_on_woa_arm64.patch
|
||||
fix_-wunreachable-code-return.patch
|
||||
fix_crash_creating_private_key_with_unsupported_algorithm.patch
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Thu, 23 Sep 2021 12:29:23 +0200
|
||||
Subject: fix: crash creating private key with unsupported algorithm
|
||||
|
||||
This patch fixes an issue where some calls to crypto.createPrivateKey
|
||||
made with algorithms unsupported by BoringSSL cause a crash when invoking
|
||||
methods on their return values. This was happening because BoringSSL
|
||||
shims some algorithms but doesn't implement them and so attempted to
|
||||
created keys with them will fail (see https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/include/openssl/evp.h;l=835-837?q=ed448&ss=chromium)
|
||||
|
||||
Node.js returned false in initEdRaw (see: https://github.com/nodejs/node/blob/20cf47004e7801ede1588d2de8785c0100f6ab38/src/crypto/crypto_keys.cc#L1106)
|
||||
but then did nothing with the return value, meaning that if no pkey was
|
||||
created successfully that a key object was still returned but attempts
|
||||
to use the data_ field would crash the process as data_ was never
|
||||
assigned. This is fixed by checking the return value of initEdRaw at the
|
||||
JavaScript level and throwing an error if the function returns false.
|
||||
|
||||
This patch will be upstreamed in some form.
|
||||
|
||||
diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js
|
||||
index 4d5545ab1aae1ad392ac1fea878b887f4a8736c4..bde806e71a7b299ec2855de8e78c3e9ab449f508 100644
|
||||
--- a/lib/internal/crypto/keys.js
|
||||
+++ b/lib/internal/crypto/keys.js
|
||||
@@ -437,15 +437,19 @@ function getKeyObjectHandleFromJwk(key, ctx) {
|
||||
|
||||
const handle = new KeyObjectHandle();
|
||||
if (isPublic) {
|
||||
- handle.initEDRaw(
|
||||
+ if (!handle.initEDRaw(
|
||||
`NODE-${key.crv.toUpperCase()}`,
|
||||
keyData,
|
||||
- kKeyTypePublic);
|
||||
+ kKeyTypePublic)) {
|
||||
+ throw new Error('Failed to create key - unsupported algorithm');
|
||||
+ }
|
||||
} else {
|
||||
- handle.initEDRaw(
|
||||
+ if (!handle.initEDRaw(
|
||||
`NODE-${key.crv.toUpperCase()}`,
|
||||
keyData,
|
||||
- kKeyTypePrivate);
|
||||
+ kKeyTypePrivate)) {
|
||||
+ throw new Error('Failed to create key - unsupported algorithm');
|
||||
+ }
|
||||
}
|
||||
|
||||
return handle;
|
||||
@@ -7,3 +7,6 @@ do_not_export_private_v8_symbols_on_windows.patch
|
||||
fix_build_deprecated_attirbute_for_older_msvc_versions.patch
|
||||
fix_disable_implies_dcheck_for_node_stream_array_buffers.patch
|
||||
cppgc-js_support_eager_traced_value_in_ephemeron_pairs.patch
|
||||
regexp_add_a_currently_failing_cctest_for_irregexp_reentrancy.patch
|
||||
regexp_allow_reentrant_irregexp_execution.patch
|
||||
regexp_remove_the_stack_parameter_from_regexp_matchers.patch
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jakob Gruber <jgruber@chromium.org>
|
||||
Date: Mon, 6 Sep 2021 08:29:33 +0200
|
||||
Subject: Add a (currently failing) cctest for irregexp reentrancy
|
||||
|
||||
The test should be enabled once reentrancy is supported.
|
||||
|
||||
Bug: v8:11382
|
||||
Change-Id: Ifb90d8a6fd8bf9f05e9ca2405d4e04e013ce7ee3
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3138201
|
||||
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
|
||||
Auto-Submit: Jakob Gruber <jgruber@chromium.org>
|
||||
Reviewed-by: Patrick Thier <pthier@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#76667}
|
||||
|
||||
diff --git a/test/cctest/cctest.status b/test/cctest/cctest.status
|
||||
index 0a6626ce332ae3ad3e49cb99404646c22c866b71..9c28520ed56998173c105b9d8a2ca3c4489b916e 100644
|
||||
--- a/test/cctest/cctest.status
|
||||
+++ b/test/cctest/cctest.status
|
||||
@@ -136,6 +136,9 @@
|
||||
'test-strings/Traverse': [PASS, HEAVY],
|
||||
'test-swiss-name-dictionary-csa/DeleteAtBoundaries': [PASS, HEAVY],
|
||||
'test-swiss-name-dictionary-csa/SameH2': [PASS, HEAVY],
|
||||
+
|
||||
+ # TODO(v8:11382): Reenable once irregexp is reentrant.
|
||||
+ 'test-regexp/RegExpInterruptReentrantExecution': [FAIL],
|
||||
}], # ALWAYS
|
||||
|
||||
##############################################################################
|
||||
@@ -670,6 +673,9 @@
|
||||
|
||||
# Instruction cache flushing is disabled in jitless mode.
|
||||
'test-icache/*': [SKIP],
|
||||
+
|
||||
+ # Tests generated irregexp code.
|
||||
+ 'test-regexp/RegExpInterruptReentrantExecution': [SKIP],
|
||||
}], # lite_mode or variant == jitless
|
||||
|
||||
##############################################################################
|
||||
diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc
|
||||
index dc5e2ea50898fbf684f5f4655d8b50982d4ebbbd..f7cbc54499464acf1a7de45251a6118340ec51fd 100644
|
||||
--- a/test/cctest/test-api.cc
|
||||
+++ b/test/cctest/test-api.cc
|
||||
@@ -21734,10 +21734,6 @@ TEST(RegExpInterruptAndMakeSubjectTwoByteExternal) {
|
||||
// experimental engine.
|
||||
i::FLAG_enable_experimental_regexp_engine_on_excessive_backtracks = false;
|
||||
RegExpInterruptTest test;
|
||||
- // We want to be stuck regexp execution, so no fallback to linear-time
|
||||
- // engine.
|
||||
- // TODO(mbid,v8:10765): Find a way to test interrupt support of the
|
||||
- // experimental engine.
|
||||
test.RunTest(RegExpInterruptTest::MakeSubjectTwoByteExternal);
|
||||
}
|
||||
|
||||
diff --git a/test/cctest/test-regexp.cc b/test/cctest/test-regexp.cc
|
||||
index 27204f7f519229cc4c21a10dd0a44222d4b6edd6..2692748e623d3d52780ff89a97f4300bcd981cbd 100644
|
||||
--- a/test/cctest/test-regexp.cc
|
||||
+++ b/test/cctest/test-regexp.cc
|
||||
@@ -2346,6 +2346,50 @@ TEST(UnicodePropertyEscapeCodeSize) {
|
||||
}
|
||||
}
|
||||
|
||||
+namespace {
|
||||
+
|
||||
+struct RegExpExecData {
|
||||
+ i::Isolate* isolate;
|
||||
+ i::Handle<i::JSRegExp> regexp;
|
||||
+ i::Handle<i::String> subject;
|
||||
+};
|
||||
+
|
||||
+i::Handle<i::Object> RegExpExec(const RegExpExecData* d) {
|
||||
+ return i::RegExp::Exec(d->isolate, d->regexp, d->subject, 0,
|
||||
+ d->isolate->regexp_last_match_info())
|
||||
+ .ToHandleChecked();
|
||||
+}
|
||||
+
|
||||
+void ReenterRegExp(v8::Isolate* isolate, void* data) {
|
||||
+ RegExpExecData* d = static_cast<RegExpExecData*>(data);
|
||||
+ i::Handle<i::Object> result = RegExpExec(d);
|
||||
+ CHECK(result->IsNull());
|
||||
+}
|
||||
+
|
||||
+} // namespace
|
||||
+
|
||||
+// Tests reentrant irregexp calls.
|
||||
+TEST(RegExpInterruptReentrantExecution) {
|
||||
+ CHECK(!i::FLAG_jitless);
|
||||
+ i::FLAG_regexp_tier_up = false; // Enter irregexp, not the interpreter.
|
||||
+
|
||||
+ LocalContext context;
|
||||
+ v8::Isolate* isolate = context->GetIsolate();
|
||||
+ v8::HandleScope scope(isolate);
|
||||
+
|
||||
+ RegExpExecData d;
|
||||
+ d.isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
+ d.regexp = v8::Utils::OpenHandle(
|
||||
+ *v8::RegExp::New(context.local(), v8_str("(a*)*x"), v8::RegExp::kNone)
|
||||
+ .ToLocalChecked());
|
||||
+ d.subject = v8::Utils::OpenHandle(*v8_str("aaaa"));
|
||||
+
|
||||
+ isolate->RequestInterrupt(&ReenterRegExp, &d);
|
||||
+
|
||||
+ i::Handle<i::Object> result = RegExpExec(&d);
|
||||
+ CHECK(result->IsNull());
|
||||
+}
|
||||
+
|
||||
#undef CHECK_PARSE_ERROR
|
||||
#undef CHECK_SIMPLE
|
||||
#undef CHECK_MIN_MAX
|
||||
1736
patches/v8/regexp_allow_reentrant_irregexp_execution.patch
Normal file
1736
patches/v8/regexp_allow_reentrant_irregexp_execution.patch
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,397 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jakob Gruber <jgruber@chromium.org>
|
||||
Date: Wed, 22 Sep 2021 14:42:48 +0200
|
||||
Subject: Remove the `stack` parameter from regexp matchers
|
||||
|
||||
The argument is no longer in use.
|
||||
|
||||
Bug: v8:11382
|
||||
Change-Id: I7febc7fe7ef17ae462c700f0dba3ca1beade3021
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3173681
|
||||
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
|
||||
Reviewed-by: Patrick Thier <pthier@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#77017}
|
||||
|
||||
diff --git a/src/builtins/builtins-regexp-gen.cc b/src/builtins/builtins-regexp-gen.cc
|
||||
index 6e4307b404405c4c78614a956c64b4a86f5fe0fe..6c2d3b44a39a6bcce4e48ac621b21f54371e9b6f 100644
|
||||
--- a/src/builtins/builtins-regexp-gen.cc
|
||||
+++ b/src/builtins/builtins-regexp-gen.cc
|
||||
@@ -436,8 +436,6 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
|
||||
// External constants.
|
||||
TNode<ExternalReference> isolate_address =
|
||||
ExternalConstant(ExternalReference::isolate_address(isolate()));
|
||||
- TNode<ExternalReference> regexp_stack_memory_top_address = ExternalConstant(
|
||||
- ExternalReference::address_of_regexp_stack_memory_top_address(isolate()));
|
||||
TNode<ExternalReference> static_offsets_vector_address = ExternalConstant(
|
||||
ExternalReference::address_of_static_offsets_vector(isolate()));
|
||||
|
||||
@@ -606,26 +604,18 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
|
||||
MachineType arg5_type = type_int32;
|
||||
TNode<Int32T> arg5 = SmiToInt32(register_count);
|
||||
|
||||
- // Argument 6: Start (high end) of backtracking stack memory area. This
|
||||
- // argument is ignored in the interpreter.
|
||||
- TNode<RawPtrT> stack_top = UncheckedCast<RawPtrT>(
|
||||
- Load(MachineType::Pointer(), regexp_stack_memory_top_address));
|
||||
+ // Argument 6: Indicate that this is a direct call from JavaScript.
|
||||
+ MachineType arg6_type = type_int32;
|
||||
+ TNode<Int32T> arg6 = Int32Constant(RegExp::CallOrigin::kFromJs);
|
||||
|
||||
- MachineType arg6_type = type_ptr;
|
||||
- TNode<RawPtrT> arg6 = stack_top;
|
||||
+ // Argument 7: Pass current isolate address.
|
||||
+ MachineType arg7_type = type_ptr;
|
||||
+ TNode<ExternalReference> arg7 = isolate_address;
|
||||
|
||||
- // Argument 7: Indicate that this is a direct call from JavaScript.
|
||||
- MachineType arg7_type = type_int32;
|
||||
- TNode<Int32T> arg7 = Int32Constant(RegExp::CallOrigin::kFromJs);
|
||||
-
|
||||
- // Argument 8: Pass current isolate address.
|
||||
- MachineType arg8_type = type_ptr;
|
||||
- TNode<ExternalReference> arg8 = isolate_address;
|
||||
-
|
||||
- // Argument 9: Regular expression object. This argument is ignored in native
|
||||
+ // Argument 8: Regular expression object. This argument is ignored in native
|
||||
// irregexp code.
|
||||
- MachineType arg9_type = type_tagged;
|
||||
- TNode<JSRegExp> arg9 = regexp;
|
||||
+ MachineType arg8_type = type_tagged;
|
||||
+ TNode<JSRegExp> arg8 = regexp;
|
||||
|
||||
// TODO(v8:11880): avoid roundtrips between cdc and code.
|
||||
TNode<RawPtrT> code_entry = LoadCodeObjectEntry(code);
|
||||
@@ -640,8 +630,7 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
|
||||
std::make_pair(arg1_type, arg1), std::make_pair(arg2_type, arg2),
|
||||
std::make_pair(arg3_type, arg3), std::make_pair(arg4_type, arg4),
|
||||
std::make_pair(arg5_type, arg5), std::make_pair(arg6_type, arg6),
|
||||
- std::make_pair(arg7_type, arg7), std::make_pair(arg8_type, arg8),
|
||||
- std::make_pair(arg9_type, arg9)));
|
||||
+ std::make_pair(arg7_type, arg7), std::make_pair(arg8_type, arg8)));
|
||||
|
||||
// Check the result.
|
||||
// We expect exactly one result since we force the called regexp to behave
|
||||
diff --git a/src/regexp/arm/regexp-macro-assembler-arm.cc b/src/regexp/arm/regexp-macro-assembler-arm.cc
|
||||
index 10766db4cf1d41ad0fee0754c6eaeebe46ace500..6e79ffd8adf8417c356bb36e1dbb2d0bfc290858 100644
|
||||
--- a/src/regexp/arm/regexp-macro-assembler-arm.cc
|
||||
+++ b/src/regexp/arm/regexp-macro-assembler-arm.cc
|
||||
@@ -38,14 +38,12 @@ namespace internal {
|
||||
* Each call to a public method should retain this convention.
|
||||
*
|
||||
* The stack will have the following structure:
|
||||
- * - fp[56] Address regexp (address of the JSRegExp object; unused in
|
||||
+ * - fp[52] Address regexp (address of the JSRegExp object; unused in
|
||||
* native code, passed to match signature of
|
||||
* the interpreter)
|
||||
- * - fp[52] Isolate* isolate (address of the current isolate)
|
||||
- * - fp[48] direct_call (if 1, direct call from JavaScript code,
|
||||
+ * - fp[48] Isolate* isolate (address of the current isolate)
|
||||
+ * - fp[44] direct_call (if 1, direct call from JavaScript code,
|
||||
* if 0, call through the runtime system).
|
||||
- * - fp[44] stack_area_base (high end of the memory area to use as
|
||||
- * backtracking stack).
|
||||
* - fp[40] capture array size (may fit multiple sets of matches)
|
||||
* - fp[36] int* capture_array (int[num_saved_registers_], for output).
|
||||
* --- sp when called ---
|
||||
@@ -82,7 +80,6 @@ namespace internal {
|
||||
* Address end,
|
||||
* int* capture_output_array,
|
||||
* int num_capture_registers,
|
||||
- * byte* stack_area_base,
|
||||
* bool direct_call = false,
|
||||
* Isolate* isolate,
|
||||
* Address regexp);
|
||||
diff --git a/src/regexp/arm/regexp-macro-assembler-arm.h b/src/regexp/arm/regexp-macro-assembler-arm.h
|
||||
index a76f9dea70264d79d57ebd6c60b100bc9e0a499d..5aad6c1d85d574f4db307b6edcdda89ed25d5ca8 100644
|
||||
--- a/src/regexp/arm/regexp-macro-assembler-arm.h
|
||||
+++ b/src/regexp/arm/regexp-macro-assembler-arm.h
|
||||
@@ -91,15 +91,13 @@ class V8_EXPORT_PRIVATE RegExpMacroAssemblerARM
|
||||
static const int kFramePointer = 0;
|
||||
|
||||
// Above the frame pointer - Stored registers and stack passed parameters.
|
||||
- // Register 4..11.
|
||||
static const int kStoredRegisters = kFramePointer;
|
||||
// Return address (stored from link register, read into pc on return).
|
||||
static const int kReturnAddress = kStoredRegisters + 8 * kPointerSize;
|
||||
// Stack parameters placed by caller.
|
||||
static const int kRegisterOutput = kReturnAddress + kPointerSize;
|
||||
static const int kNumOutputRegisters = kRegisterOutput + kPointerSize;
|
||||
- static const int kStackHighEnd = kNumOutputRegisters + kPointerSize;
|
||||
- static const int kDirectCall = kStackHighEnd + kPointerSize;
|
||||
+ static const int kDirectCall = kNumOutputRegisters + kPointerSize;
|
||||
static const int kIsolate = kDirectCall + kPointerSize;
|
||||
|
||||
// Below the frame pointer.
|
||||
diff --git a/src/regexp/arm64/regexp-macro-assembler-arm64.cc b/src/regexp/arm64/regexp-macro-assembler-arm64.cc
|
||||
index 6192461fa32879469d56d36fb788b5de33038d77..4611a323afacb5accfb3886581879e60eb1c9f12 100644
|
||||
--- a/src/regexp/arm64/regexp-macro-assembler-arm64.cc
|
||||
+++ b/src/regexp/arm64/regexp-macro-assembler-arm64.cc
|
||||
@@ -66,14 +66,12 @@ namespace internal {
|
||||
* ^^^^^^^^^ fp ^^^^^^^^^
|
||||
* - fp[-8] direct_call 1 => Direct call from JavaScript code.
|
||||
* 0 => Call through the runtime system.
|
||||
- * - fp[-16] stack_base High end of the memory area to use as
|
||||
- * the backtracking stack.
|
||||
- * - fp[-24] output_size Output may fit multiple sets of matches.
|
||||
- * - fp[-32] input Handle containing the input string.
|
||||
- * - fp[-40] success_counter
|
||||
+ * - fp[-16] output_size Output may fit multiple sets of matches.
|
||||
+ * - fp[-24] input Handle containing the input string.
|
||||
+ * - fp[-32] success_counter
|
||||
* ^^^^^^^^^^^^^ From here and downwards we store 32 bit values ^^^^^^^^^^^^^
|
||||
- * - fp[-44] register N Capture registers initialized with
|
||||
- * - fp[-48] register N + 1 non_position_value.
|
||||
+ * - fp[-40] register N Capture registers initialized with
|
||||
+ * - fp[-44] register N + 1 non_position_value.
|
||||
* ... The first kNumCachedRegisters (N) registers
|
||||
* ... are cached in x0 to x7.
|
||||
* ... Only positions must be stored in the first
|
||||
@@ -95,7 +93,6 @@ namespace internal {
|
||||
* Address end,
|
||||
* int* capture_output_array,
|
||||
* int num_capture_registers,
|
||||
- * byte* stack_area_base,
|
||||
* bool direct_call = false,
|
||||
* Isolate* isolate,
|
||||
* Address regexp);
|
||||
@@ -750,11 +747,10 @@ Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) {
|
||||
// x3: byte* input_end
|
||||
// x4: int* output array
|
||||
// x5: int output array size
|
||||
- // x6: Address stack_base
|
||||
- // x7: int direct_call
|
||||
-
|
||||
- // sp[8]: address of the current isolate
|
||||
- // sp[0]: secondary link/return address used by native call
|
||||
+ // x6: int direct_call
|
||||
+ // x7: Isolate* isolate
|
||||
+ //
|
||||
+ // sp[0]: secondary link/return address used by native call
|
||||
|
||||
// Tell the system that we have a stack frame. Because the type is MANUAL, no
|
||||
// code is generated.
|
||||
diff --git a/src/regexp/arm64/regexp-macro-assembler-arm64.h b/src/regexp/arm64/regexp-macro-assembler-arm64.h
|
||||
index 204ee68dc868142693e9959170c71df3f72f97ce..f3869a72b631f9fb42b275d2edd8f3cfe1cfd8bb 100644
|
||||
--- a/src/regexp/arm64/regexp-macro-assembler-arm64.h
|
||||
+++ b/src/regexp/arm64/regexp-macro-assembler-arm64.h
|
||||
@@ -102,16 +102,12 @@ class V8_EXPORT_PRIVATE RegExpMacroAssemblerARM64
|
||||
// Callee-saved registers (x19-x28).
|
||||
static const int kNumCalleeSavedRegisters = 10;
|
||||
static const int kCalleeSavedRegisters = kReturnAddress + kSystemPointerSize;
|
||||
- // Stack parameter placed by caller.
|
||||
- // It is placed above the FP, LR and the callee-saved registers.
|
||||
- static const int kIsolate =
|
||||
- kCalleeSavedRegisters + kNumCalleeSavedRegisters * kSystemPointerSize;
|
||||
|
||||
// Below the frame pointer.
|
||||
// Register parameters stored by setup code.
|
||||
- static const int kDirectCall = -kSystemPointerSize;
|
||||
- static const int kStackHighEnd = kDirectCall - kSystemPointerSize;
|
||||
- static const int kOutputSize = kStackHighEnd - kSystemPointerSize;
|
||||
+ static const int kIsolate = -kSystemPointerSize;
|
||||
+ static const int kDirectCall = kIsolate - kSystemPointerSize;
|
||||
+ static const int kOutputSize = kDirectCall - kSystemPointerSize;
|
||||
static const int kInput = kOutputSize - kSystemPointerSize;
|
||||
// When adding local variables remember to push space for them in
|
||||
// the frame in GetCode.
|
||||
diff --git a/src/regexp/experimental/experimental.cc b/src/regexp/experimental/experimental.cc
|
||||
index c05a010d06f34405128ffb9a9d67d86a20fcb83d..c3d701715aa88be3f5a8009319a09b032c85f4ad 100644
|
||||
--- a/src/regexp/experimental/experimental.cc
|
||||
+++ b/src/regexp/experimental/experimental.cc
|
||||
@@ -192,8 +192,7 @@ int32_t ExperimentalRegExp::ExecRaw(Isolate* isolate,
|
||||
int32_t ExperimentalRegExp::MatchForCallFromJs(
|
||||
Address subject, int32_t start_position, Address input_start,
|
||||
Address input_end, int* output_registers, int32_t output_register_count,
|
||||
- Address backtrack_stack, RegExp::CallOrigin call_origin, Isolate* isolate,
|
||||
- Address regexp) {
|
||||
+ RegExp::CallOrigin call_origin, Isolate* isolate, Address regexp) {
|
||||
DCHECK(FLAG_enable_experimental_regexp_engine);
|
||||
DCHECK_NOT_NULL(isolate);
|
||||
DCHECK_NOT_NULL(output_registers);
|
||||
diff --git a/src/regexp/experimental/experimental.h b/src/regexp/experimental/experimental.h
|
||||
index 5987fb4d7732f47c5f31cc0fab7b11e252c864f8..cdc683e97e901bd3e63332a04f69c6d31f964931 100644
|
||||
--- a/src/regexp/experimental/experimental.h
|
||||
+++ b/src/regexp/experimental/experimental.h
|
||||
@@ -34,7 +34,6 @@ class ExperimentalRegExp final : public AllStatic {
|
||||
Address input_start, Address input_end,
|
||||
int* output_registers,
|
||||
int32_t output_register_count,
|
||||
- Address backtrack_stack,
|
||||
RegExp::CallOrigin call_origin,
|
||||
Isolate* isolate, Address regexp);
|
||||
static MaybeHandle<Object> Exec(
|
||||
diff --git a/src/regexp/ia32/regexp-macro-assembler-ia32.cc b/src/regexp/ia32/regexp-macro-assembler-ia32.cc
|
||||
index 51d63b2531e2bc85fb115de23d7b6a6f40b36f11..8369b88e22c300890fe4ddb1bbba62093e8b23d8 100644
|
||||
--- a/src/regexp/ia32/regexp-macro-assembler-ia32.cc
|
||||
+++ b/src/regexp/ia32/regexp-macro-assembler-ia32.cc
|
||||
@@ -40,8 +40,6 @@ namespace internal {
|
||||
* - Isolate* isolate (address of the current isolate)
|
||||
* - direct_call (if 1, direct call from JavaScript code, if 0
|
||||
* call through the runtime system)
|
||||
- * - stack_area_base (high end of the memory area to use as
|
||||
- * backtracking stack)
|
||||
* - capture array size (may fit multiple sets of matches)
|
||||
* - int* capture_array (int[num_saved_registers_], for output).
|
||||
* - end of input (address of end of string)
|
||||
@@ -74,7 +72,6 @@ namespace internal {
|
||||
* Address end,
|
||||
* int* capture_output_array,
|
||||
* int num_capture_registers,
|
||||
- * byte* stack_area_base,
|
||||
* bool direct_call = false,
|
||||
* Isolate* isolate
|
||||
* Address regexp);
|
||||
diff --git a/src/regexp/ia32/regexp-macro-assembler-ia32.h b/src/regexp/ia32/regexp-macro-assembler-ia32.h
|
||||
index 861795da900d91111386e4f8e660f7f94ea46a33..01914a6b8b5abb96a4eec8d844e2d1aea7cbf231 100644
|
||||
--- a/src/regexp/ia32/regexp-macro-assembler-ia32.h
|
||||
+++ b/src/regexp/ia32/regexp-macro-assembler-ia32.h
|
||||
@@ -105,8 +105,7 @@ class V8_EXPORT_PRIVATE RegExpMacroAssemblerIA32
|
||||
// one set of capture results. For the case of non-global regexp, we ignore
|
||||
// this value.
|
||||
static const int kNumOutputRegisters = kRegisterOutput + kSystemPointerSize;
|
||||
- static const int kStackHighEnd = kNumOutputRegisters + kSystemPointerSize;
|
||||
- static const int kDirectCall = kStackHighEnd + kSystemPointerSize;
|
||||
+ static const int kDirectCall = kNumOutputRegisters + kSystemPointerSize;
|
||||
static const int kIsolate = kDirectCall + kSystemPointerSize;
|
||||
// Below the frame pointer - local stack variables.
|
||||
// When adding local variables remember to push space for them in
|
||||
diff --git a/src/regexp/regexp-interpreter.cc b/src/regexp/regexp-interpreter.cc
|
||||
index f9a959d2582a25cd60a97453e696f8f1f0560ce8..a2f23f78c308162240c5adf8904e732ce3bf6159 100644
|
||||
--- a/src/regexp/regexp-interpreter.cc
|
||||
+++ b/src/regexp/regexp-interpreter.cc
|
||||
@@ -1111,7 +1111,7 @@ IrregexpInterpreter::Result IrregexpInterpreter::MatchInternal(
|
||||
// builtin.
|
||||
IrregexpInterpreter::Result IrregexpInterpreter::MatchForCallFromJs(
|
||||
Address subject, int32_t start_position, Address, Address,
|
||||
- int* output_registers, int32_t output_register_count, Address,
|
||||
+ int* output_registers, int32_t output_register_count,
|
||||
RegExp::CallOrigin call_origin, Isolate* isolate, Address regexp) {
|
||||
DCHECK_NOT_NULL(isolate);
|
||||
DCHECK_NOT_NULL(output_registers);
|
||||
diff --git a/src/regexp/regexp-interpreter.h b/src/regexp/regexp-interpreter.h
|
||||
index a4d79184b08963598bbc42a91541c8df695bf4c5..e9dedd781b5b83d4017f8b2bd4353f1d57013a67 100644
|
||||
--- a/src/regexp/regexp-interpreter.h
|
||||
+++ b/src/regexp/regexp-interpreter.h
|
||||
@@ -36,9 +36,8 @@ class V8_EXPORT_PRIVATE IrregexpInterpreter : public AllStatic {
|
||||
// RETRY is returned if a retry through the runtime is needed (e.g. when
|
||||
// interrupts have been scheduled or the regexp is marked for tier-up).
|
||||
//
|
||||
- // Arguments input_start, input_end and backtrack_stack are
|
||||
- // unused. They are only passed to match the signature of the native irregex
|
||||
- // code.
|
||||
+ // Arguments input_start and input_end are unused. They are only passed to
|
||||
+ // match the signature of the native irregex code.
|
||||
//
|
||||
// Arguments output_registers and output_register_count describe the results
|
||||
// array, which will contain register values of all captures if SUCCESS is
|
||||
@@ -47,7 +46,6 @@ class V8_EXPORT_PRIVATE IrregexpInterpreter : public AllStatic {
|
||||
Address input_start, Address input_end,
|
||||
int* output_registers,
|
||||
int32_t output_register_count,
|
||||
- Address backtrack_stack,
|
||||
RegExp::CallOrigin call_origin,
|
||||
Isolate* isolate, Address regexp);
|
||||
|
||||
diff --git a/src/regexp/regexp-macro-assembler.cc b/src/regexp/regexp-macro-assembler.cc
|
||||
index 1f5875afb8850da4136da6633d5b0ad9f52803e3..ced89ad4386b9d037851529f098c8aa111f2a478 100644
|
||||
--- a/src/regexp/regexp-macro-assembler.cc
|
||||
+++ b/src/regexp/regexp-macro-assembler.cc
|
||||
@@ -306,23 +306,21 @@ int NativeRegExpMacroAssembler::Execute(
|
||||
String input, // This needs to be the unpacked (sliced, cons) string.
|
||||
int start_offset, const byte* input_start, const byte* input_end,
|
||||
int* output, int output_size, Isolate* isolate, JSRegExp regexp) {
|
||||
- // Ensure that the minimum stack has been allocated.
|
||||
RegExpStackScope stack_scope(isolate);
|
||||
- Address stack_base = stack_scope.stack()->memory_top();
|
||||
|
||||
bool is_one_byte = String::IsOneByteRepresentationUnderneath(input);
|
||||
Code code = FromCodeT(CodeT::cast(regexp.Code(is_one_byte)));
|
||||
RegExp::CallOrigin call_origin = RegExp::CallOrigin::kFromRuntime;
|
||||
|
||||
- using RegexpMatcherSig = int(
|
||||
- Address input_string, int start_offset, const byte* input_start,
|
||||
- const byte* input_end, int* output, int output_size, Address stack_base,
|
||||
- int call_origin, Isolate* isolate, Address regexp);
|
||||
+ using RegexpMatcherSig =
|
||||
+ // NOLINTNEXTLINE(readability/casting)
|
||||
+ int(Address input_string, int start_offset, const byte* input_start,
|
||||
+ const byte* input_end, int* output, int output_size, int call_origin,
|
||||
+ Isolate* isolate, Address regexp);
|
||||
|
||||
auto fn = GeneratedCode<RegexpMatcherSig>::FromCode(code);
|
||||
- int result =
|
||||
- fn.Call(input.ptr(), start_offset, input_start, input_end, output,
|
||||
- output_size, stack_base, call_origin, isolate, regexp.ptr());
|
||||
+ int result = fn.Call(input.ptr(), start_offset, input_start, input_end,
|
||||
+ output, output_size, call_origin, isolate, regexp.ptr());
|
||||
DCHECK_GE(result, SMALLEST_REGEXP_RESULT);
|
||||
|
||||
if (result == EXCEPTION && !isolate->has_pending_exception()) {
|
||||
diff --git a/src/regexp/x64/regexp-macro-assembler-x64.cc b/src/regexp/x64/regexp-macro-assembler-x64.cc
|
||||
index abcbed18aaa9bdc4a497962714bffde74d581173..e4bff5dafa9f12c14805c72e51f973252b97a5a7 100644
|
||||
--- a/src/regexp/x64/regexp-macro-assembler-x64.cc
|
||||
+++ b/src/regexp/x64/regexp-macro-assembler-x64.cc
|
||||
@@ -47,14 +47,12 @@ namespace internal {
|
||||
* Each call to a C++ method should retain these registers.
|
||||
*
|
||||
* The stack will have the following content, in some order, indexable from the
|
||||
- * frame pointer (see, e.g., kStackHighEnd):
|
||||
+ * frame pointer (see, e.g., kDirectCall):
|
||||
* - Address regexp (address of the JSRegExp object; unused in native
|
||||
* code, passed to match signature of interpreter)
|
||||
* - Isolate* isolate (address of the current isolate)
|
||||
* - direct_call (if 1, direct call from JavaScript code, if 0 call
|
||||
* through the runtime system)
|
||||
- * - stack_area_base (high end of the memory area to use as
|
||||
- * backtracking stack)
|
||||
* - capture array size (may fit multiple sets of matches)
|
||||
* - int* capture_array (int[num_saved_registers_], for output).
|
||||
* - end of input (address of end of string)
|
||||
@@ -85,7 +83,6 @@ namespace internal {
|
||||
* Address end,
|
||||
* int* capture_output_array,
|
||||
* int num_capture_registers,
|
||||
- * byte* stack_area_base,
|
||||
* bool direct_call = false,
|
||||
* Isolate* isolate,
|
||||
* Address regexp);
|
||||
@@ -849,8 +846,6 @@ Handle<HeapObject> RegExpMacroAssemblerX64::GetCode(Handle<String> source) {
|
||||
}
|
||||
|
||||
// Initialize backtrack stack pointer.
|
||||
- // TODO(jgruber): Remove the kStackHighEnd parameter (and others like
|
||||
- // kIsolate).
|
||||
LoadRegExpStackPointerFromMemory(backtrack_stackpointer());
|
||||
|
||||
__ jmp(&start_label_);
|
||||
diff --git a/src/regexp/x64/regexp-macro-assembler-x64.h b/src/regexp/x64/regexp-macro-assembler-x64.h
|
||||
index 74a3c95b06c771078ab03e6787e5912315421bb2..6f89ba9f8cf45430dc0edc7f2241a9aca34324c0 100644
|
||||
--- a/src/regexp/x64/regexp-macro-assembler-x64.h
|
||||
+++ b/src/regexp/x64/regexp-macro-assembler-x64.h
|
||||
@@ -108,9 +108,8 @@ class V8_EXPORT_PRIVATE RegExpMacroAssemblerX64
|
||||
// this value. NumOutputRegisters is passed as 32-bit value. The upper
|
||||
// 32 bit of this 64-bit stack slot may contain garbage.
|
||||
static const int kNumOutputRegisters = kRegisterOutput + kSystemPointerSize;
|
||||
- static const int kStackHighEnd = kNumOutputRegisters + kSystemPointerSize;
|
||||
// DirectCall is passed as 32 bit int (values 0 or 1).
|
||||
- static const int kDirectCall = kStackHighEnd + kSystemPointerSize;
|
||||
+ static const int kDirectCall = kNumOutputRegisters + kSystemPointerSize;
|
||||
static const int kIsolate = kDirectCall + kSystemPointerSize;
|
||||
#else
|
||||
// In AMD64 ABI Calling Convention, the first six integer parameters
|
||||
@@ -121,13 +120,12 @@ class V8_EXPORT_PRIVATE RegExpMacroAssemblerX64
|
||||
static const int kInputStart = kStartIndex - kSystemPointerSize;
|
||||
static const int kInputEnd = kInputStart - kSystemPointerSize;
|
||||
static const int kRegisterOutput = kInputEnd - kSystemPointerSize;
|
||||
-
|
||||
// For the case of global regular expression, we have room to store at least
|
||||
// one set of capture results. For the case of non-global regexp, we ignore
|
||||
// this value.
|
||||
static const int kNumOutputRegisters = kRegisterOutput - kSystemPointerSize;
|
||||
- static const int kStackHighEnd = kFrameAlign;
|
||||
- static const int kDirectCall = kStackHighEnd + kSystemPointerSize;
|
||||
+
|
||||
+ static const int kDirectCall = kFrameAlign;
|
||||
static const int kIsolate = kDirectCall + kSystemPointerSize;
|
||||
#endif
|
||||
|
||||
@@ -232,9 +232,8 @@ def remove_patch_filename(patch):
|
||||
def export_patches(repo, out_dir, patch_range=None, dry_run=False):
|
||||
if patch_range is None:
|
||||
patch_range, num_patches = guess_base_commit(repo)
|
||||
sys.stderr.write(
|
||||
"Exporting {} patches in {} since {}\n".format(num_patches, repo, patch_range[0:7])
|
||||
)
|
||||
sys.stderr.write("Exporting {} patches in {} since {}\n".format(
|
||||
num_patches, repo, patch_range[0:7]))
|
||||
patch_data = format_patch(repo, patch_range)
|
||||
patches = split_patches(patch_data)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from util import SRC_DIR
|
||||
from lib.util import SRC_DIR
|
||||
|
||||
PYYAML_LIB_DIR = os.path.join(SRC_DIR, 'third_party', 'pyyaml', 'lib')
|
||||
sys.path.append(PYYAML_LIB_DIR)
|
||||
|
||||
@@ -29,8 +29,21 @@ const IS_WINDOWS = process.platform === 'win32';
|
||||
|
||||
function spawnAndCheckExitCode (cmd, args, opts) {
|
||||
opts = Object.assign({ stdio: 'inherit' }, opts);
|
||||
const status = childProcess.spawnSync(cmd, args, opts).status;
|
||||
if (status) process.exit(status);
|
||||
const { error, status, signal } = childProcess.spawnSync(cmd, args, opts);
|
||||
if (error) {
|
||||
// the subsprocess failed or timed out
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
if (status === null) {
|
||||
// the subprocess terminated due to a signal
|
||||
console.error(signal);
|
||||
process.exit(1);
|
||||
}
|
||||
if (status !== 0) {
|
||||
// `status` is an exit code
|
||||
process.exit(status);
|
||||
}
|
||||
}
|
||||
|
||||
function cpplint (args) {
|
||||
@@ -91,7 +104,7 @@ const LINTERS = [{
|
||||
const rcfile = path.join(DEPOT_TOOLS, 'pylintrc');
|
||||
const args = ['--rcfile=' + rcfile, ...filenames];
|
||||
const env = Object.assign({ PYTHONPATH: path.join(SOURCE_ROOT, 'script') }, process.env);
|
||||
spawnAndCheckExitCode('pylint.py', args, { env });
|
||||
spawnAndCheckExitCode('pylint', args, { env });
|
||||
}
|
||||
}, {
|
||||
key: 'javascript',
|
||||
|
||||
@@ -135,7 +135,7 @@ def main():
|
||||
json.load(f) # Make sure it's not an empty file
|
||||
print("Using existing mtime cache for patches")
|
||||
return 0
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
|
||||
@@ -100,7 +100,8 @@ def main():
|
||||
# Upload libcxx_objects.zip for linux only
|
||||
libcxx_objects = get_zip_name('libcxx-objects', ELECTRON_VERSION)
|
||||
libcxx_objects_zip = os.path.join(OUT_DIR, libcxx_objects)
|
||||
shutil.copy2(os.path.join(OUT_DIR, 'libcxx_objects.zip'), libcxx_objects_zip)
|
||||
shutil.copy2(os.path.join(OUT_DIR, 'libcxx_objects.zip'),
|
||||
libcxx_objects_zip)
|
||||
upload_electron(release, libcxx_objects_zip, args)
|
||||
|
||||
# Upload headers.zip and abi_headers.zip as non-platform specific
|
||||
|
||||
@@ -48,7 +48,8 @@ def main():
|
||||
# FIXME: Enable after ELECTRON_ENABLE_LOGGING works again
|
||||
# env['ELECTRON_ENABLE_LOGGING'] = 'true'
|
||||
testargs = [electron, test_path]
|
||||
if sys.platform != 'linux' and (platform.machine() == 'ARM64' or os.environ.get('TARGET_ARCH') == 'arm64'):
|
||||
if sys.platform != 'linux' and (platform.machine() == 'ARM64' or
|
||||
os.environ.get('TARGET_ARCH') == 'arm64'):
|
||||
testargs.append('--disable-accelerated-video-decode')
|
||||
subprocess.check_call(testargs, env=env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
||||
@@ -39,10 +39,7 @@ void BrowserWindow::OverrideNSWindowContentView(
|
||||
|
||||
void BrowserWindow::UpdateDraggableRegions(
|
||||
const std::vector<mojom::DraggableRegionPtr>& regions) {
|
||||
if (window_->has_frame())
|
||||
return;
|
||||
|
||||
if (!web_contents())
|
||||
if (window_->has_frame() || !web_contents())
|
||||
return;
|
||||
|
||||
// All ControlRegionViews should be added as children of the WebContentsView,
|
||||
@@ -78,8 +75,13 @@ void BrowserWindow::UpdateDraggableRegions(
|
||||
DraggableRegionsToSkRegion(regions), webViewWidth, webViewHeight);
|
||||
}
|
||||
|
||||
// Draggable regions on BrowserViews are independent from those of
|
||||
// BrowserWindows, so if a BrowserView with different draggable regions than
|
||||
// the BrowserWindow it belongs to is superimposed on top of that window, the
|
||||
// draggable regions of the BrowserView take precedence over those of the
|
||||
// BrowserWindow.
|
||||
for (NativeBrowserView* view : window_->browser_views()) {
|
||||
view->UpdateDraggableRegions(drag_exclude_rects);
|
||||
view->UpdateDraggableRegions(view->GetDraggableRegions());
|
||||
}
|
||||
|
||||
// Create and add a ControlRegionView for each region that needs to be
|
||||
|
||||
@@ -236,13 +236,11 @@ std::u16string Menu::GetToolTipAt(int index) const {
|
||||
return model_->GetToolTipAt(index);
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
std::u16string Menu::GetAcceleratorTextAtForTesting(int index) const {
|
||||
ui::Accelerator accelerator;
|
||||
model_->GetAcceleratorAtWithParams(index, true, &accelerator);
|
||||
return accelerator.GetShortcutText();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Menu::IsItemCheckedAt(int index) const {
|
||||
return model_->IsItemCheckedAt(index);
|
||||
@@ -297,9 +295,7 @@ v8::Local<v8::ObjectTemplate> Menu::FillObjectTemplate(
|
||||
.SetMethod("isVisibleAt", &Menu::IsVisibleAt)
|
||||
.SetMethod("popupAt", &Menu::PopupAt)
|
||||
.SetMethod("closePopupAt", &Menu::ClosePopupAt)
|
||||
#if DCHECK_IS_ON()
|
||||
.SetMethod("getAcceleratorTextAt", &Menu::GetAcceleratorTextAtForTesting)
|
||||
#endif
|
||||
.SetMethod("_getAcceleratorTextAt", &Menu::GetAcceleratorTextAtForTesting)
|
||||
#if defined(OS_MAC)
|
||||
.SetMethod("_getUserAcceleratorAt", &Menu::GetUserAcceleratorAt)
|
||||
#endif
|
||||
|
||||
@@ -79,9 +79,7 @@ class Menu : public gin::Wrappable<Menu>,
|
||||
int positioning_item,
|
||||
base::OnceClosure callback) = 0;
|
||||
virtual void ClosePopupAt(int32_t window_id) = 0;
|
||||
#if DCHECK_IS_ON()
|
||||
virtual std::u16string GetAcceleratorTextAtForTesting(int index) const;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<ElectronMenuModel> model_;
|
||||
Menu* parent_ = nullptr;
|
||||
|
||||
@@ -34,9 +34,7 @@ class MenuMac : public Menu {
|
||||
int positioning_item,
|
||||
base::OnceClosure callback);
|
||||
void ClosePopupAt(int32_t window_id) override;
|
||||
#if DCHECK_IS_ON()
|
||||
std::u16string GetAcceleratorTextAtForTesting(int index) const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
friend class Menu;
|
||||
|
||||
@@ -170,7 +170,6 @@ void MenuMac::ClosePopupAt(int32_t window_id) {
|
||||
std::move(close_popup));
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
std::u16string MenuMac::GetAcceleratorTextAtForTesting(int index) const {
|
||||
// A least effort to get the real shortcut text of NSMenuItem, the code does
|
||||
// not need to be perfect since it is test only.
|
||||
@@ -206,7 +205,6 @@ std::u16string MenuMac::GetAcceleratorTextAtForTesting(int index) const {
|
||||
text += key;
|
||||
return text;
|
||||
}
|
||||
#endif
|
||||
|
||||
void MenuMac::ClosePopupOnUI(int32_t window_id) {
|
||||
auto controller = popup_controllers_.find(window_id);
|
||||
|
||||
@@ -47,7 +47,10 @@ printing::PrinterList GetPrinterList() {
|
||||
// TODO(deepak1556): Deprecate this api in favor of an
|
||||
// async version and post a non blocing task call.
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
print_backend->EnumeratePrinters(&printers);
|
||||
printing::mojom::ResultCode code =
|
||||
print_backend->EnumeratePrinters(&printers);
|
||||
if (code != printing::mojom::ResultCode::kSuccess)
|
||||
LOG(INFO) << "Failed to enumerate printers";
|
||||
}
|
||||
return printers;
|
||||
}
|
||||
|
||||
@@ -644,6 +644,18 @@ void Session::SetPermissionCheckHandler(v8::Local<v8::Value> val,
|
||||
permission_manager->SetPermissionCheckHandler(handler);
|
||||
}
|
||||
|
||||
void Session::SetDevicePermissionHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args) {
|
||||
ElectronPermissionManager::DeviceCheckHandler handler;
|
||||
if (!(val->IsNull() || gin::ConvertFromV8(args->isolate(), val, &handler))) {
|
||||
args->ThrowTypeError("Must pass null or function");
|
||||
return;
|
||||
}
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
browser_context()->GetPermissionControllerDelegate());
|
||||
permission_manager->SetDevicePermissionHandler(handler);
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> Session::ClearHostResolverCache(gin::Arguments* args) {
|
||||
v8::Isolate* isolate = args->isolate();
|
||||
gin_helper::Promise<void> promise(isolate);
|
||||
@@ -1148,6 +1160,8 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
|
||||
&Session::SetPermissionRequestHandler)
|
||||
.SetMethod("setPermissionCheckHandler",
|
||||
&Session::SetPermissionCheckHandler)
|
||||
.SetMethod("setDevicePermissionHandler",
|
||||
&Session::SetDevicePermissionHandler)
|
||||
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)
|
||||
.SetMethod("clearAuthCache", &Session::ClearAuthCache)
|
||||
.SetMethod("allowNTLMCredentialsForDomains",
|
||||
|
||||
@@ -104,6 +104,8 @@ class Session : public gin::Wrappable<Session>,
|
||||
gin::Arguments* args);
|
||||
void SetPermissionCheckHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args);
|
||||
void SetDevicePermissionHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args);
|
||||
v8::Local<v8::Promise> ClearHostResolverCache(gin::Arguments* args);
|
||||
v8::Local<v8::Promise> ClearAuthCache();
|
||||
void AllowNTLMCredentialsForDomains(const std::string& domains);
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#include "ppapi/buildflags/buildflags.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "printing/print_job_constants.h"
|
||||
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
|
||||
#include "services/service_manager/public/cpp/interface_provider.h"
|
||||
#include "shell/browser/api/electron_api_browser_window.h"
|
||||
#include "shell/browser/api/electron_api_debugger.h"
|
||||
@@ -99,6 +100,7 @@
|
||||
#include "shell/browser/web_view_guest_delegate.h"
|
||||
#include "shell/browser/web_view_manager.h"
|
||||
#include "shell/common/api/electron_api_native_image.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/color_util.h"
|
||||
#include "shell/common/electron_constants.h"
|
||||
#include "shell/common/gin_converters/base_converter.h"
|
||||
@@ -424,7 +426,7 @@ bool IsDeviceNameValid(const std::u16string& device_name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::u16string GetDefaultPrinterAsync() {
|
||||
std::pair<std::string, std::u16string> GetDefaultPrinterAsync() {
|
||||
#if defined(OS_WIN)
|
||||
// Blocking is needed here because Windows printer drivers are oftentimes
|
||||
// not thread-safe and have to be accessed on the UI thread.
|
||||
@@ -435,18 +437,25 @@ std::u16string GetDefaultPrinterAsync() {
|
||||
printing::PrintBackend::CreateInstance(
|
||||
g_browser_process->GetApplicationLocale());
|
||||
std::string printer_name;
|
||||
print_backend->GetDefaultPrinterName(printer_name);
|
||||
printing::mojom::ResultCode code =
|
||||
print_backend->GetDefaultPrinterName(printer_name);
|
||||
|
||||
// Some devices won't have a default printer, so we should
|
||||
// also check for existing printers and pick the first
|
||||
// one should it exist.
|
||||
// We don't want to return if this fails since some devices won't have a
|
||||
// default printer.
|
||||
if (code != printing::mojom::ResultCode::kSuccess)
|
||||
LOG(ERROR) << "Failed to get default printer name";
|
||||
|
||||
// Check for existing printers and pick the first one should it exist.
|
||||
if (printer_name.empty()) {
|
||||
printing::PrinterList printers;
|
||||
print_backend->EnumeratePrinters(&printers);
|
||||
if (print_backend->EnumeratePrinters(&printers) !=
|
||||
printing::mojom::ResultCode::kSuccess)
|
||||
return std::make_pair("Failed to enumerate printers", std::u16string());
|
||||
if (!printers.empty())
|
||||
printer_name = printers.front().printer_name;
|
||||
}
|
||||
return base::UTF8ToUTF16(printer_name);
|
||||
|
||||
return std::make_pair(std::string(), base::UTF8ToUTF16(printer_name));
|
||||
}
|
||||
|
||||
// Copied from
|
||||
@@ -918,14 +927,18 @@ void WebContents::InitWithWebContents(
|
||||
}
|
||||
|
||||
WebContents::~WebContents() {
|
||||
// clear out objects that have been granted permissions so that when
|
||||
// WebContents::RenderFrameDeleted is called as a result of WebContents
|
||||
// destruction it doesn't try to clear out a granted_devices_
|
||||
// on a destructed object.
|
||||
granted_devices_.clear();
|
||||
|
||||
if (!inspectable_web_contents_) {
|
||||
WebContentsDestroyed();
|
||||
return;
|
||||
}
|
||||
|
||||
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
|
||||
if (guest_delegate_)
|
||||
guest_delegate_->WillDestroy();
|
||||
|
||||
// This event is only for internal use, which is emitted when WebContents is
|
||||
// being destroyed.
|
||||
@@ -947,18 +960,31 @@ WebContents::~WebContents() {
|
||||
// InspectableWebContents will be automatically destroyed.
|
||||
}
|
||||
|
||||
void WebContents::DeleteThisIfAlive() {
|
||||
// It is possible that the FirstWeakCallback has been called but the
|
||||
// SecondWeakCallback has not, in this case the garbage collection of
|
||||
// WebContents has already started and we should not |delete this|.
|
||||
// Calling |GetWrapper| can detect this corner case.
|
||||
auto* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> wrapper;
|
||||
if (!GetWrapper(isolate).ToLocal(&wrapper))
|
||||
return;
|
||||
delete this;
|
||||
}
|
||||
|
||||
void WebContents::Destroy() {
|
||||
// The content::WebContents should be destroyed asyncronously when possible
|
||||
// as user may choose to destroy WebContents during an event of it.
|
||||
if (Browser::Get()->is_shutting_down() || IsGuest() ||
|
||||
type_ == Type::kBrowserView) {
|
||||
delete this;
|
||||
DeleteThisIfAlive();
|
||||
} else {
|
||||
base::PostTask(FROM_HERE, {content::BrowserThread::UI},
|
||||
base::BindOnce(
|
||||
[](base::WeakPtr<WebContents> contents) {
|
||||
if (contents)
|
||||
delete contents.get();
|
||||
contents->DeleteThisIfAlive();
|
||||
},
|
||||
GetWeakPtr()));
|
||||
}
|
||||
@@ -1427,6 +1453,12 @@ void WebContents::RenderFrameDeleted(
|
||||
// - Cross-origin navigation creates a new RFH in a separate process which
|
||||
// is swapped by content::RenderFrameHostManager.
|
||||
//
|
||||
|
||||
// clear out objects that have been granted permissions
|
||||
if (!granted_devices_.empty()) {
|
||||
granted_devices_.erase(render_frame_host->GetFrameTreeNodeId());
|
||||
}
|
||||
|
||||
// WebFrameMain::FromRenderFrameHost(rfh) will use the RFH's FrameTreeNode ID
|
||||
// to find an existing instance of WebFrameMain. During a cross-origin
|
||||
// navigation, the deleted RFH will be the old host which was swapped out. In
|
||||
@@ -1672,6 +1704,9 @@ void WebContents::MessageTo(int32_t web_contents_id,
|
||||
gin::Handle<WebFrameMain> web_frame_main =
|
||||
WebFrameMain::From(JavascriptEnvironment::GetIsolate(), frame);
|
||||
|
||||
if (!web_frame_main->CheckRenderFrame())
|
||||
return;
|
||||
|
||||
int32_t sender_id = ID();
|
||||
web_frame_main->GetRendererApi()->Message(false /* internal */, channel,
|
||||
std::move(arguments), sender_id);
|
||||
@@ -1920,6 +1955,10 @@ void WebContents::WebContentsDestroyed() {
|
||||
return;
|
||||
wrapper->SetAlignedPointerInInternalField(0, nullptr);
|
||||
|
||||
// Tell WebViewGuestDelegate that the WebContents has been destroyed.
|
||||
if (guest_delegate_)
|
||||
guest_delegate_->WillDestroy();
|
||||
|
||||
Observe(nullptr);
|
||||
Emit("destroyed");
|
||||
}
|
||||
@@ -2410,7 +2449,8 @@ void WebContents::OnGetDefaultPrinter(
|
||||
printing::CompletionCallback print_callback,
|
||||
std::u16string device_name,
|
||||
bool silent,
|
||||
std::u16string default_printer) {
|
||||
// <error, default_printer>
|
||||
std::pair<std::string, std::u16string> info) {
|
||||
// The content::WebContents might be already deleted at this point, and the
|
||||
// PrintViewManagerElectron class does not do null check.
|
||||
if (!web_contents()) {
|
||||
@@ -2419,8 +2459,14 @@ void WebContents::OnGetDefaultPrinter(
|
||||
return;
|
||||
}
|
||||
|
||||
std::u16string printer_name =
|
||||
device_name.empty() ? default_printer : device_name;
|
||||
if (!info.first.empty()) {
|
||||
if (print_callback)
|
||||
std::move(print_callback).Run(false, info.first);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has passed a deviceName use it, otherwise use default printer.
|
||||
std::u16string printer_name = device_name.empty() ? info.second : device_name;
|
||||
|
||||
// If there are no valid printers available on the network, we bail.
|
||||
if (printer_name.empty() || !IsDeviceNameValid(printer_name)) {
|
||||
@@ -3189,6 +3235,26 @@ void WebContents::SetImageAnimationPolicy(const std::string& new_policy) {
|
||||
web_contents()->OnWebPreferencesChanged();
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> WebContents::GetProcessMemoryInfo(v8::Isolate* isolate) {
|
||||
gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
auto* frame_host = web_contents()->GetMainFrame();
|
||||
if (!frame_host) {
|
||||
promise.RejectWithErrorMessage("Failed to create memory dump");
|
||||
return handle;
|
||||
}
|
||||
|
||||
auto pid = frame_host->GetProcess()->GetProcess().Pid();
|
||||
v8::Global<v8::Context> context(isolate, isolate->GetCurrentContext());
|
||||
memory_instrumentation::MemoryInstrumentation::GetInstance()
|
||||
->RequestGlobalDumpForPid(
|
||||
pid, std::vector<std::string>(),
|
||||
base::BindOnce(&ElectronBindings::DidReceiveMemoryDump,
|
||||
std::move(context), std::move(promise), pid));
|
||||
return handle;
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> WebContents::TakeHeapSnapshot(
|
||||
v8::Isolate* isolate,
|
||||
const base::FilePath& file_path) {
|
||||
@@ -3237,6 +3303,42 @@ v8::Local<v8::Promise> WebContents::TakeHeapSnapshot(
|
||||
return handle;
|
||||
}
|
||||
|
||||
void WebContents::GrantDevicePermission(
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::PermissionType permissionType,
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
granted_devices_[render_frame_host->GetFrameTreeNodeId()][permissionType]
|
||||
[origin]
|
||||
.push_back(
|
||||
std::make_unique<base::Value>(device->Clone()));
|
||||
}
|
||||
|
||||
std::vector<base::Value> WebContents::GetGrantedDevices(
|
||||
const url::Origin& origin,
|
||||
content::PermissionType permissionType,
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
const auto& devices_for_frame_host_it =
|
||||
granted_devices_.find(render_frame_host->GetFrameTreeNodeId());
|
||||
if (devices_for_frame_host_it == granted_devices_.end())
|
||||
return {};
|
||||
|
||||
const auto& current_devices_it =
|
||||
devices_for_frame_host_it->second.find(permissionType);
|
||||
if (current_devices_it == devices_for_frame_host_it->second.end())
|
||||
return {};
|
||||
|
||||
const auto& origin_devices_it = current_devices_it->second.find(origin);
|
||||
if (origin_devices_it == current_devices_it->second.end())
|
||||
return {};
|
||||
|
||||
std::vector<base::Value> results;
|
||||
for (const auto& object : origin_devices_it->second)
|
||||
results.push_back(object->Clone());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void WebContents::UpdatePreferredSize(content::WebContents* web_contents,
|
||||
const gfx::Size& pref_size) {
|
||||
Emit("preferred-size-changed", pref_size);
|
||||
@@ -3778,6 +3880,7 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
|
||||
.SetMethod("takeHeapSnapshot", &WebContents::TakeHeapSnapshot)
|
||||
.SetMethod("setImageAnimationPolicy",
|
||||
&WebContents::SetImageAnimationPolicy)
|
||||
.SetMethod("_getProcessMemoryInfo", &WebContents::GetProcessMemoryInfo)
|
||||
.SetProperty("id", &WebContents::ID)
|
||||
.SetProperty("session", &WebContents::Session)
|
||||
.SetProperty("hostWebContents", &WebContents::HostWebContents)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "content/common/frame.mojom.h"
|
||||
#include "content/public/browser/devtools_agent_host.h"
|
||||
#include "content/public/browser/keyboard_event_processing_result.h"
|
||||
#include "content/public/browser/permission_type.h"
|
||||
#include "content/public/browser/render_widget_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_delegate.h"
|
||||
@@ -91,6 +92,11 @@ class OffScreenWebContentsView;
|
||||
|
||||
namespace api {
|
||||
|
||||
using DevicePermissionMap = std::map<
|
||||
int,
|
||||
std::map<content::PermissionType,
|
||||
std::map<url::Origin, std::vector<std::unique_ptr<base::Value>>>>>;
|
||||
|
||||
// Wrapper around the content::WebContents.
|
||||
class WebContents : public gin::Wrappable<WebContents>,
|
||||
public gin_helper::EventEmitterMixin<WebContents>,
|
||||
@@ -215,7 +221,8 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
printing::CompletionCallback print_callback,
|
||||
std::u16string device_name,
|
||||
bool silent,
|
||||
std::u16string default_printer);
|
||||
// <error, default_printer_name>
|
||||
std::pair<std::string, std::u16string> info);
|
||||
void Print(gin::Arguments* args);
|
||||
// Print current page as PDF.
|
||||
v8::Local<v8::Promise> PrintToPDF(base::DictionaryValue settings);
|
||||
@@ -316,6 +323,7 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
|
||||
v8::Local<v8::Promise> TakeHeapSnapshot(v8::Isolate* isolate,
|
||||
const base::FilePath& file_path);
|
||||
v8::Local<v8::Promise> GetProcessMemoryInfo(v8::Isolate* isolate);
|
||||
|
||||
// Properties.
|
||||
int32_t ID() const { return id_; }
|
||||
@@ -419,6 +427,21 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
electron::mojom::ElectronBrowser::DoGetZoomLevelCallback callback);
|
||||
void SetImageAnimationPolicy(const std::string& new_policy);
|
||||
|
||||
// Grants |origin| access to |device|.
|
||||
// To be used in place of ObjectPermissionContextBase::GrantObjectPermission.
|
||||
void GrantDevicePermission(const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::PermissionType permissionType,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
// Returns the list of devices that |origin| has been granted permission to
|
||||
// access. To be used in place of
|
||||
// ObjectPermissionContextBase::GetGrantedObjects.
|
||||
std::vector<base::Value> GetGrantedDevices(
|
||||
const url::Origin& origin,
|
||||
content::PermissionType permissionType,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
private:
|
||||
// Does not manage lifetime of |web_contents|.
|
||||
WebContents(v8::Isolate* isolate, content::WebContents* web_contents);
|
||||
@@ -430,6 +453,9 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
WebContents(v8::Isolate* isolate, const gin_helper::Dictionary& options);
|
||||
~WebContents() override;
|
||||
|
||||
// Delete this if garbage collection has not started.
|
||||
void DeleteThisIfAlive();
|
||||
|
||||
// Creates a InspectableWebContents object and takes ownership of
|
||||
// |web_contents|.
|
||||
void InitWithWebContents(std::unique_ptr<content::WebContents> web_contents,
|
||||
@@ -762,6 +788,9 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
// Stores the frame thats currently in fullscreen, nullptr if there is none.
|
||||
content::RenderFrameHost* fullscreen_frame_ = nullptr;
|
||||
|
||||
// In-memory cache that holds objects that have been granted permissions.
|
||||
DevicePermissionMap granted_devices_;
|
||||
|
||||
base::WeakPtrFactory<WebContents> weak_factory_{this};
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WebContents);
|
||||
|
||||
@@ -1705,4 +1705,10 @@ device::GeolocationManager* ElectronBrowserClient::GetGeolocationManager() {
|
||||
#endif
|
||||
}
|
||||
|
||||
content::HidDelegate* ElectronBrowserClient::GetHidDelegate() {
|
||||
if (!hid_delegate_)
|
||||
hid_delegate_ = std::make_unique<ElectronHidDelegate>();
|
||||
return hid_delegate_.get();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "services/metrics/public/cpp/ukm_source_id.h"
|
||||
#include "shell/browser/bluetooth/electron_bluetooth_delegate.h"
|
||||
#include "shell/browser/font/electron_font_access_delegate.h"
|
||||
#include "shell/browser/hid/electron_hid_delegate.h"
|
||||
#include "shell/browser/serial/electron_serial_delegate.h"
|
||||
#include "third_party/blink/public/mojom/badging/badging.mojom-forward.h"
|
||||
|
||||
@@ -96,6 +97,8 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||
|
||||
content::BluetoothDelegate* GetBluetoothDelegate() override;
|
||||
|
||||
content::HidDelegate* GetHidDelegate() override;
|
||||
|
||||
device::GeolocationManager* GetGeolocationManager() override;
|
||||
|
||||
protected:
|
||||
@@ -309,6 +312,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||
std::unique_ptr<ElectronSerialDelegate> serial_delegate_;
|
||||
std::unique_ptr<ElectronBluetoothDelegate> bluetooth_delegate_;
|
||||
std::unique_ptr<ElectronFontAccessDelegate> font_access_delegate_;
|
||||
std::unique_ptr<ElectronHidDelegate> hid_delegate_;
|
||||
|
||||
#if defined(OS_MAC)
|
||||
ElectronBrowserMainParts* browser_main_parts_ = nullptr;
|
||||
|
||||
@@ -17,9 +17,17 @@
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "gin/data_object_builder.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/electron_browser_client.h"
|
||||
#include "shell/browser/electron_browser_main_parts.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
#include "shell/browser/web_contents_preferences.h"
|
||||
#include "shell/common/gin_converters/content_converter.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -117,6 +125,11 @@ void ElectronPermissionManager::SetPermissionCheckHandler(
|
||||
check_handler_ = handler;
|
||||
}
|
||||
|
||||
void ElectronPermissionManager::SetDevicePermissionHandler(
|
||||
const DeviceCheckHandler& handler) {
|
||||
device_permission_handler_ = handler;
|
||||
}
|
||||
|
||||
void ElectronPermissionManager::RequestPermission(
|
||||
content::PermissionType permission,
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
@@ -282,6 +295,71 @@ bool ElectronPermissionManager::CheckPermissionWithDetails(
|
||||
mutable_details);
|
||||
}
|
||||
|
||||
bool ElectronPermissionManager::CheckDevicePermission(
|
||||
content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::RenderFrameHost* render_frame_host) const {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
api::WebContents* api_web_contents = api::WebContents::From(web_contents);
|
||||
if (device_permission_handler_.is_null()) {
|
||||
if (api_web_contents) {
|
||||
std::vector<base::Value> granted_devices =
|
||||
api_web_contents->GetGrantedDevices(origin, permission,
|
||||
render_frame_host);
|
||||
|
||||
for (const auto& granted_device : granted_devices) {
|
||||
if (permission ==
|
||||
static_cast<content::PermissionType>(
|
||||
WebContentsPermissionHelper::PermissionType::HID)) {
|
||||
if (device->FindIntKey(kHidVendorIdKey) !=
|
||||
granted_device.FindIntKey(kHidVendorIdKey) ||
|
||||
device->FindIntKey(kHidProductIdKey) !=
|
||||
granted_device.FindIntKey(kHidProductIdKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* serial_number =
|
||||
granted_device.FindStringKey(kHidSerialNumberKey);
|
||||
const auto* device_serial_number =
|
||||
device->FindStringKey(kHidSerialNumberKey);
|
||||
|
||||
if (serial_number && device_serial_number &&
|
||||
*device_serial_number == *serial_number)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
|
||||
.Set("deviceType", permission)
|
||||
.Set("origin", origin.Serialize())
|
||||
.Set("device", device->Clone())
|
||||
.Set("frame", render_frame_host)
|
||||
.Build();
|
||||
return device_permission_handler_.Run(details);
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronPermissionManager::GrantDevicePermission(
|
||||
content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::RenderFrameHost* render_frame_host) const {
|
||||
if (device_permission_handler_.is_null()) {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
api::WebContents* api_web_contents = api::WebContents::From(web_contents);
|
||||
if (api_web_contents)
|
||||
api_web_contents->GrantDevicePermission(origin, device, permission,
|
||||
render_frame_host);
|
||||
}
|
||||
}
|
||||
|
||||
blink::mojom::PermissionStatus
|
||||
ElectronPermissionManager::GetPermissionStatusForFrame(
|
||||
content::PermissionType permission,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "base/callback.h"
|
||||
#include "base/containers/id_map.h"
|
||||
#include "content/public/browser/permission_controller_delegate.h"
|
||||
#include "gin/dictionary.h"
|
||||
|
||||
namespace base {
|
||||
class DictionaryValue;
|
||||
@@ -42,9 +43,13 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
||||
const GURL& requesting_origin,
|
||||
const base::Value&)>;
|
||||
|
||||
using DeviceCheckHandler =
|
||||
base::RepeatingCallback<bool(const v8::Local<v8::Object>&)>;
|
||||
|
||||
// Handler to dispatch permission requests in JS.
|
||||
void SetPermissionRequestHandler(const RequestHandler& handler);
|
||||
void SetPermissionCheckHandler(const CheckHandler& handler);
|
||||
void SetDevicePermissionHandler(const DeviceCheckHandler& handler);
|
||||
|
||||
// content::PermissionControllerDelegate:
|
||||
void RequestPermission(content::PermissionType permission,
|
||||
@@ -81,6 +86,16 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
||||
const GURL& requesting_origin,
|
||||
const base::DictionaryValue* details) const;
|
||||
|
||||
bool CheckDevicePermission(content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* object,
|
||||
content::RenderFrameHost* render_frame_host) const;
|
||||
|
||||
void GrantDevicePermission(content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* object,
|
||||
content::RenderFrameHost* render_frame_host) const;
|
||||
|
||||
protected:
|
||||
void OnPermissionResponse(int request_id,
|
||||
int permission_id,
|
||||
@@ -108,6 +123,7 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
||||
|
||||
RequestHandler request_handler_;
|
||||
CheckHandler check_handler_;
|
||||
DeviceCheckHandler device_permission_handler_;
|
||||
|
||||
PendingRequestsMap pending_requests_;
|
||||
|
||||
|
||||
163
shell/browser/hid/electron_hid_delegate.cc
Normal file
163
shell/browser/hid/electron_hid_delegate.cc
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/hid/electron_hid_delegate.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
#include "shell/browser/hid/hid_chooser_context_factory.h"
|
||||
#include "shell/browser/hid/hid_chooser_controller.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
electron::HidChooserContext* GetChooserContext(
|
||||
content::RenderFrameHost* frame) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
auto* browser_context = web_contents->GetBrowserContext();
|
||||
return electron::HidChooserContextFactory::GetForBrowserContext(
|
||||
browser_context);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace electron {
|
||||
|
||||
ElectronHidDelegate::ElectronHidDelegate() = default;
|
||||
|
||||
ElectronHidDelegate::~ElectronHidDelegate() = default;
|
||||
|
||||
std::unique_ptr<content::HidChooser> ElectronHidDelegate::RunChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
content::HidChooser::Callback callback) {
|
||||
electron::HidChooserContext* chooser_context =
|
||||
GetChooserContext(render_frame_host);
|
||||
if (!device_observation_.IsObserving())
|
||||
device_observation_.Observe(chooser_context);
|
||||
|
||||
HidChooserController* controller = ControllerForFrame(render_frame_host);
|
||||
if (controller) {
|
||||
DeleteControllerForFrame(render_frame_host);
|
||||
}
|
||||
AddControllerForFrame(render_frame_host, std::move(filters),
|
||||
std::move(callback));
|
||||
|
||||
// Return a nullptr because the return value isn't used for anything, eg
|
||||
// there is no mechanism to cancel navigator.hid.requestDevice(). The return
|
||||
// value is simply used in Chromium to cleanup the chooser UI once the serial
|
||||
// service is destroyed.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ElectronHidDelegate::CanRequestDevicePermission(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto* permission_helper =
|
||||
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||
return permission_helper->CheckHIDAccessPermission(
|
||||
web_contents->GetMainFrame()->GetLastCommittedOrigin());
|
||||
}
|
||||
|
||||
bool ElectronHidDelegate::HasDevicePermission(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
auto* chooser_context = GetChooserContext(render_frame_host);
|
||||
const auto& origin =
|
||||
render_frame_host->GetMainFrame()->GetLastCommittedOrigin();
|
||||
return chooser_context->HasDevicePermission(origin, device,
|
||||
render_frame_host);
|
||||
}
|
||||
|
||||
device::mojom::HidManager* ElectronHidDelegate::GetHidManager(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
auto* chooser_context = GetChooserContext(render_frame_host);
|
||||
return chooser_context->GetHidManager();
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::AddObserver(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
Observer* observer) {
|
||||
observer_list_.AddObserver(observer);
|
||||
auto* chooser_context = GetChooserContext(render_frame_host);
|
||||
if (!device_observation_.IsObserving())
|
||||
device_observation_.Observe(chooser_context);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::RemoveObserver(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
content::HidDelegate::Observer* observer) {
|
||||
observer_list_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
const device::mojom::HidDeviceInfo* ElectronHidDelegate::GetDeviceInfo(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
const std::string& guid) {
|
||||
auto* chooser_context = GetChooserContext(render_frame_host);
|
||||
return chooser_context->GetDeviceInfo(guid);
|
||||
}
|
||||
|
||||
bool ElectronHidDelegate::IsFidoAllowedForOrigin(const url::Origin& origin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnDeviceAdded(
|
||||
const device::mojom::HidDeviceInfo& device_info) {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceAdded(device_info);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnDeviceRemoved(
|
||||
const device::mojom::HidDeviceInfo& device_info) {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceRemoved(device_info);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnDeviceChanged(
|
||||
const device::mojom::HidDeviceInfo& device_info) {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceChanged(device_info);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnHidManagerConnectionError() {
|
||||
device_observation_.Reset();
|
||||
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnHidManagerConnectionError();
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnHidChooserContextShutdown() {
|
||||
device_observation_.Reset();
|
||||
}
|
||||
|
||||
HidChooserController* ElectronHidDelegate::ControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
auto mapping = controller_map_.find(render_frame_host);
|
||||
return mapping == controller_map_.end() ? nullptr : mapping->second.get();
|
||||
}
|
||||
|
||||
HidChooserController* ElectronHidDelegate::AddControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
content::HidChooser::Callback callback) {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto controller = std::make_unique<HidChooserController>(
|
||||
render_frame_host, std::move(filters), std::move(callback), web_contents,
|
||||
weak_factory_.GetWeakPtr());
|
||||
controller_map_.insert(
|
||||
std::make_pair(render_frame_host, std::move(controller)));
|
||||
return ControllerForFrame(render_frame_host);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::DeleteControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
controller_map_.erase(render_frame_host);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
84
shell/browser/hid/electron_hid_delegate.h
Normal file
84
shell/browser/hid/electron_hid_delegate.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2019 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_HID_ELECTRON_HID_DELEGATE_H_
|
||||
#define SHELL_BROWSER_HID_ELECTRON_HID_DELEGATE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "base/observer_list.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "content/public/browser/hid_delegate.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class HidChooserController;
|
||||
|
||||
class ElectronHidDelegate : public content::HidDelegate,
|
||||
public HidChooserContext::DeviceObserver {
|
||||
public:
|
||||
ElectronHidDelegate();
|
||||
ElectronHidDelegate(ElectronHidDelegate&) = delete;
|
||||
ElectronHidDelegate& operator=(ElectronHidDelegate&) = delete;
|
||||
~ElectronHidDelegate() override;
|
||||
|
||||
// content::HidDelegate:
|
||||
std::unique_ptr<content::HidChooser> RunChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
content::HidChooser::Callback callback) override;
|
||||
bool CanRequestDevicePermission(
|
||||
content::RenderFrameHost* render_frame_host) override;
|
||||
bool HasDevicePermission(content::RenderFrameHost* render_frame_host,
|
||||
const device::mojom::HidDeviceInfo& device) override;
|
||||
device::mojom::HidManager* GetHidManager(
|
||||
content::RenderFrameHost* render_frame_host) override;
|
||||
void AddObserver(content::RenderFrameHost* render_frame_host,
|
||||
content::HidDelegate::Observer* observer) override;
|
||||
void RemoveObserver(content::RenderFrameHost* render_frame_host,
|
||||
content::HidDelegate::Observer* observer) override;
|
||||
const device::mojom::HidDeviceInfo* GetDeviceInfo(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
const std::string& guid) override;
|
||||
bool IsFidoAllowedForOrigin(const url::Origin& origin) override;
|
||||
|
||||
// HidChooserContext::DeviceObserver:
|
||||
void OnDeviceAdded(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnDeviceRemoved(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnDeviceChanged(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnHidManagerConnectionError() override;
|
||||
void OnHidChooserContextShutdown() override;
|
||||
|
||||
void DeleteControllerForFrame(content::RenderFrameHost* render_frame_host);
|
||||
|
||||
private:
|
||||
HidChooserController* ControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
HidChooserController* AddControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
content::HidChooser::Callback callback);
|
||||
|
||||
base::ScopedObservation<HidChooserContext,
|
||||
HidChooserContext::DeviceObserver,
|
||||
&HidChooserContext::AddDeviceObserver,
|
||||
&HidChooserContext::RemoveDeviceObserver>
|
||||
device_observation_{this};
|
||||
base::ObserverList<content::HidDelegate::Observer> observer_list_;
|
||||
|
||||
std::unordered_map<content::RenderFrameHost*,
|
||||
std::unique_ptr<HidChooserController>>
|
||||
controller_map_;
|
||||
|
||||
base::WeakPtrFactory<ElectronHidDelegate> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_HID_ELECTRON_HID_DELEGATE_H_
|
||||
272
shell/browser/hid/hid_chooser_context.cc
Normal file
272
shell/browser/hid/hid_chooser_context.cc
Normal file
@@ -0,0 +1,272 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "components/content_settings/core/common/content_settings_types.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "content/public/browser/device_service.h"
|
||||
#include "electron/grit/electron_resources.h"
|
||||
#include "services/device/public/cpp/hid/hid_blocklist.h"
|
||||
#include "services/device/public/cpp/hid/hid_switches.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
const char kHidDeviceNameKey[] = "name";
|
||||
const char kHidGuidKey[] = "guid";
|
||||
const char kHidVendorIdKey[] = "vendorId";
|
||||
const char kHidProductIdKey[] = "productId";
|
||||
const char kHidSerialNumberKey[] = "serialNumber";
|
||||
|
||||
HidChooserContext::HidChooserContext(ElectronBrowserContext* context)
|
||||
: browser_context_(context) {}
|
||||
|
||||
HidChooserContext::~HidChooserContext() {
|
||||
// Notify observers that the chooser context is about to be destroyed.
|
||||
// Observers must remove themselves from the observer lists.
|
||||
for (auto& observer : device_observer_list_) {
|
||||
observer.OnHidChooserContextShutdown();
|
||||
DCHECK(!device_observer_list_.HasObserver(&observer));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
std::u16string HidChooserContext::DisplayNameFromDeviceInfo(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
if (device.product_name.empty()) {
|
||||
auto device_id_string = base::ASCIIToUTF16(
|
||||
base::StringPrintf("%04X:%04X", device.vendor_id, device.product_id));
|
||||
return l10n_util::GetStringFUTF16(IDS_HID_CHOOSER_ITEM_WITHOUT_NAME,
|
||||
device_id_string);
|
||||
}
|
||||
return base::UTF8ToUTF16(device.product_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool HidChooserContext::CanStorePersistentEntry(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
return !device.serial_number.empty() && !device.product_name.empty();
|
||||
}
|
||||
|
||||
// static
|
||||
base::Value HidChooserContext::DeviceInfoToValue(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
base::Value value(base::Value::Type::DICTIONARY);
|
||||
value.SetStringKey(
|
||||
kHidDeviceNameKey,
|
||||
base::UTF16ToUTF8(HidChooserContext::DisplayNameFromDeviceInfo(device)));
|
||||
value.SetIntKey(kHidVendorIdKey, device.vendor_id);
|
||||
value.SetIntKey(kHidProductIdKey, device.product_id);
|
||||
if (HidChooserContext::CanStorePersistentEntry(device)) {
|
||||
// Use the USB serial number as a persistent identifier. If it is
|
||||
// unavailable, only ephemeral permissions may be granted.
|
||||
value.SetStringKey(kHidSerialNumberKey, device.serial_number);
|
||||
} else {
|
||||
// The GUID is a temporary ID created on connection that remains valid until
|
||||
// the device is disconnected. Ephemeral permissions are keyed by this ID
|
||||
// and must be granted again each time the device is connected.
|
||||
value.SetStringKey(kHidGuidKey, device.guid);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void HidChooserContext::GrantDevicePermission(
|
||||
const url::Origin& origin,
|
||||
const device::mojom::HidDeviceInfo& device,
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
DCHECK(base::Contains(devices_, device.guid));
|
||||
if (CanStorePersistentEntry(device)) {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto* permission_helper =
|
||||
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||
permission_helper->GrantHIDDevicePermission(
|
||||
origin, DeviceInfoToValue(device), render_frame_host);
|
||||
} else {
|
||||
ephemeral_devices_[origin].insert(device.guid);
|
||||
}
|
||||
}
|
||||
|
||||
bool HidChooserContext::HasDevicePermission(
|
||||
const url::Origin& origin,
|
||||
const device::mojom::HidDeviceInfo& device,
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kDisableHidBlocklist) &&
|
||||
device::HidBlocklist::IsDeviceExcluded(device))
|
||||
return false;
|
||||
|
||||
auto it = ephemeral_devices_.find(origin);
|
||||
if (it != ephemeral_devices_.end() &&
|
||||
base::Contains(it->second, device.guid)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto* permission_helper =
|
||||
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||
return permission_helper->CheckHIDDevicePermission(
|
||||
origin, DeviceInfoToValue(device), render_frame_host);
|
||||
}
|
||||
|
||||
void HidChooserContext::AddDeviceObserver(DeviceObserver* observer) {
|
||||
EnsureHidManagerConnection();
|
||||
device_observer_list_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void HidChooserContext::RemoveDeviceObserver(DeviceObserver* observer) {
|
||||
device_observer_list_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
void HidChooserContext::GetDevices(
|
||||
device::mojom::HidManager::GetDevicesCallback callback) {
|
||||
if (!is_initialized_) {
|
||||
EnsureHidManagerConnection();
|
||||
pending_get_devices_requests_.push(std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> device_list;
|
||||
device_list.reserve(devices_.size());
|
||||
for (const auto& pair : devices_)
|
||||
device_list.push_back(pair.second->Clone());
|
||||
base::SequencedTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, base::BindOnce(std::move(callback), std::move(device_list)));
|
||||
}
|
||||
|
||||
const device::mojom::HidDeviceInfo* HidChooserContext::GetDeviceInfo(
|
||||
const std::string& guid) {
|
||||
DCHECK(is_initialized_);
|
||||
auto it = devices_.find(guid);
|
||||
return it == devices_.end() ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
device::mojom::HidManager* HidChooserContext::GetHidManager() {
|
||||
EnsureHidManagerConnection();
|
||||
return hid_manager_.get();
|
||||
}
|
||||
|
||||
base::WeakPtr<HidChooserContext> HidChooserContext::AsWeakPtr() {
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
void HidChooserContext::DeviceAdded(device::mojom::HidDeviceInfoPtr device) {
|
||||
DCHECK(device);
|
||||
|
||||
// Update the device list.
|
||||
if (!base::Contains(devices_, device->guid))
|
||||
devices_.insert({device->guid, device->Clone()});
|
||||
|
||||
// Notify all observers.
|
||||
for (auto& observer : device_observer_list_)
|
||||
observer.OnDeviceAdded(*device);
|
||||
}
|
||||
|
||||
void HidChooserContext::DeviceRemoved(device::mojom::HidDeviceInfoPtr device) {
|
||||
DCHECK(device);
|
||||
DCHECK(base::Contains(devices_, device->guid));
|
||||
|
||||
// Update the device list.
|
||||
devices_.erase(device->guid);
|
||||
|
||||
// Notify all device observers.
|
||||
for (auto& observer : device_observer_list_)
|
||||
observer.OnDeviceRemoved(*device);
|
||||
|
||||
// Next we'll notify observers for revoked permissions. If the device does not
|
||||
// support persistent permissions then device permissions are revoked on
|
||||
// disconnect.
|
||||
if (CanStorePersistentEntry(*device))
|
||||
return;
|
||||
|
||||
std::vector<url::Origin> revoked_origins;
|
||||
for (auto& map_entry : ephemeral_devices_) {
|
||||
if (map_entry.second.erase(device->guid) > 0)
|
||||
revoked_origins.push_back(map_entry.first);
|
||||
}
|
||||
if (revoked_origins.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
void HidChooserContext::DeviceChanged(device::mojom::HidDeviceInfoPtr device) {
|
||||
DCHECK(device);
|
||||
DCHECK(base::Contains(devices_, device->guid));
|
||||
|
||||
// Update the device list.
|
||||
devices_[device->guid] = device->Clone();
|
||||
|
||||
// Notify all observers.
|
||||
for (auto& observer : device_observer_list_)
|
||||
observer.OnDeviceChanged(*device);
|
||||
}
|
||||
|
||||
void HidChooserContext::EnsureHidManagerConnection() {
|
||||
if (hid_manager_)
|
||||
return;
|
||||
|
||||
mojo::PendingRemote<device::mojom::HidManager> manager;
|
||||
content::GetDeviceService().BindHidManager(
|
||||
manager.InitWithNewPipeAndPassReceiver());
|
||||
SetUpHidManagerConnection(std::move(manager));
|
||||
}
|
||||
|
||||
void HidChooserContext::SetUpHidManagerConnection(
|
||||
mojo::PendingRemote<device::mojom::HidManager> manager) {
|
||||
hid_manager_.Bind(std::move(manager));
|
||||
hid_manager_.set_disconnect_handler(base::BindOnce(
|
||||
&HidChooserContext::OnHidManagerConnectionError, base::Unretained(this)));
|
||||
|
||||
hid_manager_->GetDevicesAndSetClient(
|
||||
client_receiver_.BindNewEndpointAndPassRemote(),
|
||||
base::BindOnce(&HidChooserContext::InitDeviceList,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void HidChooserContext::InitDeviceList(
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> devices) {
|
||||
for (auto& device : devices)
|
||||
devices_.insert({device->guid, std::move(device)});
|
||||
|
||||
is_initialized_ = true;
|
||||
|
||||
while (!pending_get_devices_requests_.empty()) {
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> device_list;
|
||||
device_list.reserve(devices.size());
|
||||
for (const auto& entry : devices_)
|
||||
device_list.push_back(entry.second->Clone());
|
||||
std::move(pending_get_devices_requests_.front())
|
||||
.Run(std::move(device_list));
|
||||
pending_get_devices_requests_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void HidChooserContext::OnHidManagerConnectionError() {
|
||||
hid_manager_.reset();
|
||||
client_receiver_.reset();
|
||||
devices_.clear();
|
||||
|
||||
std::vector<url::Origin> revoked_origins;
|
||||
revoked_origins.reserve(ephemeral_devices_.size());
|
||||
for (const auto& map_entry : ephemeral_devices_)
|
||||
revoked_origins.push_back(map_entry.first);
|
||||
ephemeral_devices_.clear();
|
||||
|
||||
// Notify all device observers.
|
||||
for (auto& observer : device_observer_list_)
|
||||
observer.OnHidManagerConnectionError();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
136
shell/browser/hid/hid_chooser_context.h
Normal file
136
shell/browser/hid/hid_chooser_context.h
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_H_
|
||||
#define SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/queue.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/unguessable_token.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "mojo/public/cpp/bindings/associated_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
#include "mojo/public/cpp/bindings/remote.h"
|
||||
#include "services/device/public/mojom/hid.mojom.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
extern const char kHidDeviceNameKey[];
|
||||
extern const char kHidGuidKey[];
|
||||
extern const char kHidVendorIdKey[];
|
||||
extern const char kHidProductIdKey[];
|
||||
extern const char kHidSerialNumberKey[];
|
||||
|
||||
// Manages the internal state and connection to the device service for the
|
||||
// Human Interface Device (HID) chooser UI.
|
||||
class HidChooserContext : public KeyedService,
|
||||
public device::mojom::HidManagerClient {
|
||||
public:
|
||||
// This observer can be used to be notified when HID devices are connected or
|
||||
// disconnected.
|
||||
class DeviceObserver : public base::CheckedObserver {
|
||||
public:
|
||||
virtual void OnDeviceAdded(const device::mojom::HidDeviceInfo&) = 0;
|
||||
virtual void OnDeviceRemoved(const device::mojom::HidDeviceInfo&) = 0;
|
||||
virtual void OnDeviceChanged(const device::mojom::HidDeviceInfo&) = 0;
|
||||
virtual void OnHidManagerConnectionError() = 0;
|
||||
|
||||
// Called when the HidChooserContext is shutting down. Observers must remove
|
||||
// themselves before returning.
|
||||
virtual void OnHidChooserContextShutdown() = 0;
|
||||
};
|
||||
|
||||
explicit HidChooserContext(ElectronBrowserContext* context);
|
||||
HidChooserContext(const HidChooserContext&) = delete;
|
||||
HidChooserContext& operator=(const HidChooserContext&) = delete;
|
||||
~HidChooserContext() override;
|
||||
|
||||
// Returns a human-readable string identifier for |device|.
|
||||
static std::u16string DisplayNameFromDeviceInfo(
|
||||
const device::mojom::HidDeviceInfo& device);
|
||||
|
||||
// Returns true if a persistent permission can be granted for |device|.
|
||||
static bool CanStorePersistentEntry(
|
||||
const device::mojom::HidDeviceInfo& device);
|
||||
|
||||
static base::Value DeviceInfoToValue(
|
||||
const device::mojom::HidDeviceInfo& device);
|
||||
|
||||
// HID-specific interface for granting and checking permissions.
|
||||
void GrantDevicePermission(const url::Origin& origin,
|
||||
const device::mojom::HidDeviceInfo& device,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
bool HasDevicePermission(const url::Origin& origin,
|
||||
const device::mojom::HidDeviceInfo& device,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
// For ScopedObserver.
|
||||
void AddDeviceObserver(DeviceObserver* observer);
|
||||
void RemoveDeviceObserver(DeviceObserver* observer);
|
||||
|
||||
// Forward HidManager::GetDevices.
|
||||
void GetDevices(device::mojom::HidManager::GetDevicesCallback callback);
|
||||
|
||||
// Only call this if you're sure |devices_| has been initialized before-hand.
|
||||
// The returned raw pointer is owned by |devices_| and will be destroyed when
|
||||
// the device is removed.
|
||||
const device::mojom::HidDeviceInfo* GetDeviceInfo(const std::string& guid);
|
||||
|
||||
device::mojom::HidManager* GetHidManager();
|
||||
|
||||
base::WeakPtr<HidChooserContext> AsWeakPtr();
|
||||
|
||||
private:
|
||||
// device::mojom::HidManagerClient implementation:
|
||||
void DeviceAdded(device::mojom::HidDeviceInfoPtr device_info) override;
|
||||
void DeviceRemoved(device::mojom::HidDeviceInfoPtr device_info) override;
|
||||
void DeviceChanged(device::mojom::HidDeviceInfoPtr device_info) override;
|
||||
|
||||
void EnsureHidManagerConnection();
|
||||
void SetUpHidManagerConnection(
|
||||
mojo::PendingRemote<device::mojom::HidManager> manager);
|
||||
void InitDeviceList(std::vector<device::mojom::HidDeviceInfoPtr> devices);
|
||||
void OnHidManagerInitializedForTesting(
|
||||
device::mojom::HidManager::GetDevicesCallback callback,
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> devices);
|
||||
void OnHidManagerConnectionError();
|
||||
|
||||
ElectronBrowserContext* browser_context_;
|
||||
|
||||
bool is_initialized_ = false;
|
||||
base::queue<device::mojom::HidManager::GetDevicesCallback>
|
||||
pending_get_devices_requests_;
|
||||
|
||||
// Tracks the set of devices to which an origin has access to.
|
||||
std::map<url::Origin, std::set<std::string>> ephemeral_devices_;
|
||||
|
||||
// Map from device GUID to device info.
|
||||
std::map<std::string, device::mojom::HidDeviceInfoPtr> devices_;
|
||||
|
||||
mojo::Remote<device::mojom::HidManager> hid_manager_;
|
||||
mojo::AssociatedReceiver<device::mojom::HidManagerClient> client_receiver_{
|
||||
this};
|
||||
base::ObserverList<DeviceObserver> device_observer_list_;
|
||||
|
||||
base::WeakPtrFactory<HidChooserContext> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_H_
|
||||
55
shell/browser/hid/hid_chooser_context_factory.cc
Normal file
55
shell/browser/hid/hid_chooser_context_factory.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
#include "shell/browser/hid/hid_chooser_context_factory.h"
|
||||
|
||||
#include "components/keyed_service/content/browser_context_dependency_manager.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
// static
|
||||
HidChooserContextFactory* HidChooserContextFactory::GetInstance() {
|
||||
static base::NoDestructor<HidChooserContextFactory> factory;
|
||||
return factory.get();
|
||||
}
|
||||
|
||||
// static
|
||||
HidChooserContext* HidChooserContextFactory::GetForBrowserContext(
|
||||
content::BrowserContext* context) {
|
||||
return static_cast<HidChooserContext*>(
|
||||
GetInstance()->GetServiceForBrowserContext(context, true));
|
||||
}
|
||||
|
||||
// static
|
||||
HidChooserContext* HidChooserContextFactory::GetForBrowserContextIfExists(
|
||||
content::BrowserContext* context) {
|
||||
return static_cast<HidChooserContext*>(
|
||||
GetInstance()->GetServiceForBrowserContext(context, /*create=*/false));
|
||||
}
|
||||
|
||||
HidChooserContextFactory::HidChooserContextFactory()
|
||||
: BrowserContextKeyedServiceFactory(
|
||||
"HidChooserContext",
|
||||
BrowserContextDependencyManager::GetInstance()) {}
|
||||
|
||||
HidChooserContextFactory::~HidChooserContextFactory() = default;
|
||||
|
||||
KeyedService* HidChooserContextFactory::BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const {
|
||||
auto* browser_context =
|
||||
static_cast<electron::ElectronBrowserContext*>(context);
|
||||
return new HidChooserContext(browser_context);
|
||||
}
|
||||
|
||||
content::BrowserContext* HidChooserContextFactory::GetBrowserContextToUse(
|
||||
content::BrowserContext* context) const {
|
||||
return context;
|
||||
}
|
||||
|
||||
void HidChooserContextFactory::BrowserContextShutdown(
|
||||
content::BrowserContext* context) {}
|
||||
|
||||
} // namespace electron
|
||||
42
shell/browser/hid/hid_chooser_context_factory.h
Normal file
42
shell/browser/hid/hid_chooser_context_factory.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 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_HID_HID_CHOOSER_CONTEXT_FACTORY_H_
|
||||
#define SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_FACTORY_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class HidChooserContext;
|
||||
|
||||
class HidChooserContextFactory : public BrowserContextKeyedServiceFactory {
|
||||
public:
|
||||
static HidChooserContext* GetForBrowserContext(
|
||||
content::BrowserContext* context);
|
||||
static HidChooserContext* GetForBrowserContextIfExists(
|
||||
content::BrowserContext* context);
|
||||
static HidChooserContextFactory* GetInstance();
|
||||
|
||||
private:
|
||||
friend base::NoDestructor<HidChooserContextFactory>;
|
||||
|
||||
HidChooserContextFactory();
|
||||
~HidChooserContextFactory() override;
|
||||
|
||||
// BrowserContextKeyedBaseFactory:
|
||||
KeyedService* BuildServiceInstanceFor(
|
||||
content::BrowserContext* profile) const override;
|
||||
content::BrowserContext* GetBrowserContextToUse(
|
||||
content::BrowserContext* context) const override;
|
||||
void BrowserContextShutdown(content::BrowserContext* context) override;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HidChooserContextFactory);
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_FACTORY_H_
|
||||
366
shell/browser/hid/hid_chooser_controller.cc
Normal file
366
shell/browser/hid/hid_chooser_controller.cc
Normal file
@@ -0,0 +1,366 @@
|
||||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/hid/hid_chooser_controller.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/ranges/algorithm.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "gin/data_object_builder.h"
|
||||
#include "services/device/public/cpp/hid/hid_blocklist.h"
|
||||
#include "services/device/public/cpp/hid/hid_switches.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
#include "shell/browser/hid/hid_chooser_context_factory.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/content_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/process_util.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::string PhysicalDeviceIdFromDeviceInfo(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
// A single physical device may expose multiple HID interfaces, each
|
||||
// represented by a HidDeviceInfo object. When a device exposes multiple
|
||||
// HID interfaces, the HidDeviceInfo objects will share a common
|
||||
// |physical_device_id|. Group these devices so that a single chooser item
|
||||
// is shown for each physical device. If a device's physical device ID is
|
||||
// empty, use its GUID instead.
|
||||
return device.physical_device_id.empty() ? device.guid
|
||||
: device.physical_device_id;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gin {
|
||||
|
||||
template <>
|
||||
struct Converter<device::mojom::HidDeviceInfoPtr> {
|
||||
static v8::Local<v8::Value> ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const device::mojom::HidDeviceInfoPtr& device) {
|
||||
base::Value value = electron::HidChooserContext::DeviceInfoToValue(*device);
|
||||
value.SetStringKey("deviceId", PhysicalDeviceIdFromDeviceInfo(*device));
|
||||
return gin::ConvertToV8(isolate, value);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
namespace electron {
|
||||
|
||||
HidChooserController::HidChooserController(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
content::HidChooser::Callback callback,
|
||||
content::WebContents* web_contents,
|
||||
base::WeakPtr<ElectronHidDelegate> hid_delegate)
|
||||
: WebContentsObserver(web_contents),
|
||||
filters_(std::move(filters)),
|
||||
callback_(std::move(callback)),
|
||||
origin_(content::WebContents::FromRenderFrameHost(render_frame_host)
|
||||
->GetMainFrame()
|
||||
->GetLastCommittedOrigin()),
|
||||
frame_tree_node_id_(render_frame_host->GetFrameTreeNodeId()),
|
||||
hid_delegate_(hid_delegate),
|
||||
render_frame_host_id_(render_frame_host->GetGlobalId()) {
|
||||
chooser_context_ = HidChooserContextFactory::GetForBrowserContext(
|
||||
web_contents->GetBrowserContext())
|
||||
->AsWeakPtr();
|
||||
DCHECK(chooser_context_);
|
||||
|
||||
chooser_context_->GetHidManager()->GetDevices(base::BindOnce(
|
||||
&HidChooserController::OnGotDevices, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
HidChooserController::~HidChooserController() {
|
||||
if (callback_)
|
||||
std::move(callback_).Run(std::vector<device::mojom::HidDeviceInfoPtr>());
|
||||
}
|
||||
|
||||
api::Session* HidChooserController::GetSession() {
|
||||
if (!web_contents()) {
|
||||
return nullptr;
|
||||
}
|
||||
return api::Session::FromBrowserContext(web_contents()->GetBrowserContext());
|
||||
}
|
||||
|
||||
void HidChooserController::OnDeviceAdded(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
if (!DisplayDevice(device))
|
||||
return;
|
||||
if (AddDeviceInfo(device)) {
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
|
||||
.Set("device", device.Clone())
|
||||
.Set("frame", rfh)
|
||||
.Build();
|
||||
session->Emit("hid-device-added", details);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void HidChooserController::OnDeviceRemoved(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
auto id = PhysicalDeviceIdFromDeviceInfo(device);
|
||||
auto items_it = std::find(items_.begin(), items_.end(), id);
|
||||
if (items_it == items_.end())
|
||||
return;
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
|
||||
.Set("device", device.Clone())
|
||||
.Set("frame", rfh)
|
||||
.Build();
|
||||
session->Emit("hid-device-removed", details);
|
||||
}
|
||||
RemoveDeviceInfo(device);
|
||||
}
|
||||
|
||||
void HidChooserController::OnDeviceChanged(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
bool has_chooser_item =
|
||||
base::Contains(items_, PhysicalDeviceIdFromDeviceInfo(device));
|
||||
if (!DisplayDevice(device)) {
|
||||
if (has_chooser_item)
|
||||
OnDeviceRemoved(device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!has_chooser_item) {
|
||||
OnDeviceAdded(device);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the item to replace the old device info with |device|.
|
||||
UpdateDeviceInfo(device);
|
||||
}
|
||||
|
||||
void HidChooserController::OnDeviceChosen(gin::Arguments* args) {
|
||||
std::string device_id;
|
||||
if (!args->GetNext(&device_id) || device_id.empty()) {
|
||||
RunCallback({});
|
||||
} else {
|
||||
auto find_it = device_map_.find(device_id);
|
||||
if (find_it != device_map_.end()) {
|
||||
auto& device_infos = find_it->second;
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> devices;
|
||||
devices.reserve(device_infos.size());
|
||||
for (auto& device : device_infos) {
|
||||
chooser_context_->GrantDevicePermission(origin_, *device,
|
||||
web_contents()->GetMainFrame());
|
||||
devices.push_back(device->Clone());
|
||||
}
|
||||
RunCallback(std::move(devices));
|
||||
} else {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
node::Environment* env = node::Environment::GetCurrent(isolate);
|
||||
EmitWarning(env, "The device id " + device_id + " was not found.",
|
||||
"UnknownHIDDeviceId");
|
||||
RunCallback({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HidChooserController::OnHidManagerConnectionError() {
|
||||
observation_.Reset();
|
||||
}
|
||||
|
||||
void HidChooserController::OnHidChooserContextShutdown() {
|
||||
observation_.Reset();
|
||||
}
|
||||
|
||||
void HidChooserController::OnGotDevices(
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> devices) {
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> devicesToDisplay;
|
||||
devicesToDisplay.reserve(devices.size());
|
||||
|
||||
for (auto& device : devices) {
|
||||
if (DisplayDevice(*device)) {
|
||||
if (AddDeviceInfo(*device)) {
|
||||
devicesToDisplay.push_back(device->Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to HidChooserContext for OnDeviceAdded/Removed events after the
|
||||
// enumeration.
|
||||
if (chooser_context_)
|
||||
observation_.Observe(chooser_context_.get());
|
||||
bool prevent_default = false;
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
|
||||
.Set("deviceList", devicesToDisplay)
|
||||
.Set("frame", rfh)
|
||||
.Build();
|
||||
prevent_default =
|
||||
session->Emit("select-hid-device", details,
|
||||
base::AdaptCallbackForRepeating(
|
||||
base::BindOnce(&HidChooserController::OnDeviceChosen,
|
||||
weak_factory_.GetWeakPtr())));
|
||||
}
|
||||
if (!prevent_default) {
|
||||
RunCallback({});
|
||||
}
|
||||
}
|
||||
|
||||
bool HidChooserController::DisplayDevice(
|
||||
const device::mojom::HidDeviceInfo& device) const {
|
||||
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kDisableHidBlocklist)) {
|
||||
// Do not pass the device to the chooser if it is excluded by the blocklist.
|
||||
if (device::HidBlocklist::IsDeviceExcluded(device))
|
||||
return false;
|
||||
|
||||
// Do not pass the device to the chooser if it has a top-level collection
|
||||
// with the FIDO usage page.
|
||||
//
|
||||
// Note: The HID blocklist also blocks top-level collections with the FIDO
|
||||
// usage page, but will not block the device if it has other (non-FIDO)
|
||||
// collections. The check below will exclude the device from the chooser
|
||||
// if it has any top-level FIDO collection.
|
||||
auto find_it =
|
||||
std::find_if(device.collections.begin(), device.collections.end(),
|
||||
[](const device::mojom::HidCollectionInfoPtr& c) {
|
||||
return c->usage->usage_page == device::mojom::kPageFido;
|
||||
});
|
||||
if (find_it != device.collections.end())
|
||||
return false;
|
||||
}
|
||||
|
||||
return FilterMatchesAny(device);
|
||||
}
|
||||
|
||||
bool HidChooserController::FilterMatchesAny(
|
||||
const device::mojom::HidDeviceInfo& device) const {
|
||||
if (filters_.empty())
|
||||
return true;
|
||||
|
||||
for (const auto& filter : filters_) {
|
||||
if (filter->device_ids) {
|
||||
if (filter->device_ids->is_vendor()) {
|
||||
if (filter->device_ids->get_vendor() != device.vendor_id)
|
||||
continue;
|
||||
} else if (filter->device_ids->is_vendor_and_product()) {
|
||||
const auto& vendor_and_product =
|
||||
filter->device_ids->get_vendor_and_product();
|
||||
if (vendor_and_product->vendor != device.vendor_id)
|
||||
continue;
|
||||
if (vendor_and_product->product != device.product_id)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter->usage) {
|
||||
if (filter->usage->is_page()) {
|
||||
const uint16_t usage_page = filter->usage->get_page();
|
||||
auto find_it =
|
||||
std::find_if(device.collections.begin(), device.collections.end(),
|
||||
[=](const device::mojom::HidCollectionInfoPtr& c) {
|
||||
return usage_page == c->usage->usage_page;
|
||||
});
|
||||
if (find_it == device.collections.end())
|
||||
continue;
|
||||
} else if (filter->usage->is_usage_and_page()) {
|
||||
const auto& usage_and_page = filter->usage->get_usage_and_page();
|
||||
auto find_it = std::find_if(
|
||||
device.collections.begin(), device.collections.end(),
|
||||
[&usage_and_page](const device::mojom::HidCollectionInfoPtr& c) {
|
||||
return usage_and_page->usage_page == c->usage->usage_page &&
|
||||
usage_and_page->usage == c->usage->usage;
|
||||
});
|
||||
if (find_it == device.collections.end())
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HidChooserController::AddDeviceInfo(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
auto id = PhysicalDeviceIdFromDeviceInfo(device);
|
||||
auto find_it = device_map_.find(id);
|
||||
if (find_it != device_map_.end()) {
|
||||
find_it->second.push_back(device.Clone());
|
||||
return false;
|
||||
}
|
||||
// A new device was connected. Append it to the end of the chooser list.
|
||||
device_map_[id].push_back(device.Clone());
|
||||
items_.push_back(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HidChooserController::RemoveDeviceInfo(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
auto id = PhysicalDeviceIdFromDeviceInfo(device);
|
||||
auto find_it = device_map_.find(id);
|
||||
DCHECK(find_it != device_map_.end());
|
||||
auto& device_infos = find_it->second;
|
||||
base::EraseIf(device_infos,
|
||||
[&device](const device::mojom::HidDeviceInfoPtr& d) {
|
||||
return d->guid == device.guid;
|
||||
});
|
||||
if (!device_infos.empty())
|
||||
return false;
|
||||
// A device was disconnected. Remove it from the chooser list.
|
||||
device_map_.erase(find_it);
|
||||
base::Erase(items_, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HidChooserController::UpdateDeviceInfo(
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
auto id = PhysicalDeviceIdFromDeviceInfo(device);
|
||||
auto physical_device_it = device_map_.find(id);
|
||||
DCHECK(physical_device_it != device_map_.end());
|
||||
auto& device_infos = physical_device_it->second;
|
||||
auto device_it = base::ranges::find_if(
|
||||
device_infos, [&device](const device::mojom::HidDeviceInfoPtr& d) {
|
||||
return d->guid == device.guid;
|
||||
});
|
||||
DCHECK(device_it != device_infos.end());
|
||||
*device_it = device.Clone();
|
||||
}
|
||||
|
||||
void HidChooserController::RunCallback(
|
||||
std::vector<device::mojom::HidDeviceInfoPtr> devices) {
|
||||
if (callback_) {
|
||||
std::move(callback_).Run(std::move(devices));
|
||||
}
|
||||
}
|
||||
|
||||
void HidChooserController::RenderFrameDeleted(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
if (hid_delegate_) {
|
||||
hid_delegate_->DeleteControllerForFrame(render_frame_host);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
126
shell/browser/hid/hid_chooser_controller.h
Normal file
126
shell/browser/hid/hid_chooser_controller.h
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_HID_HID_CHOOSER_CONTROLLER_H_
|
||||
#define SHELL_BROWSER_HID_HID_CHOOSER_CONTROLLER_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "content/public/browser/global_routing_id.h"
|
||||
#include "content/public/browser/hid_chooser.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "services/device/public/mojom/hid.mojom-forward.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/hid/electron_hid_delegate.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "third_party/blink/public/mojom/hid/hid.mojom.h"
|
||||
|
||||
namespace content {
|
||||
class RenderFrameHost;
|
||||
} // namespace content
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ElectronHidDelegate;
|
||||
|
||||
class HidChooserContext;
|
||||
|
||||
// HidChooserController provides data for the WebHID API permission prompt.
|
||||
class HidChooserController
|
||||
: public content::WebContentsObserver,
|
||||
public electron::HidChooserContext::DeviceObserver {
|
||||
public:
|
||||
// Construct a chooser controller for Human Interface Devices (HID).
|
||||
// |render_frame_host| is used to initialize the chooser strings and to access
|
||||
// the requesting and embedding origins. |callback| is called when the chooser
|
||||
// is closed, either by selecting an item or by dismissing the chooser dialog.
|
||||
// The callback is called with the selected device, or nullptr if no device is
|
||||
// selected.
|
||||
HidChooserController(content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
content::HidChooser::Callback callback,
|
||||
content::WebContents* web_contents,
|
||||
base::WeakPtr<ElectronHidDelegate> hid_delegate);
|
||||
HidChooserController(HidChooserController&) = delete;
|
||||
HidChooserController& operator=(HidChooserController&) = delete;
|
||||
~HidChooserController() override;
|
||||
|
||||
// HidChooserContext::DeviceObserver:
|
||||
void OnDeviceAdded(const device::mojom::HidDeviceInfo& device_info) override;
|
||||
void OnDeviceRemoved(
|
||||
const device::mojom::HidDeviceInfo& device_info) override;
|
||||
void OnDeviceChanged(
|
||||
const device::mojom::HidDeviceInfo& device_info) override;
|
||||
void OnHidManagerConnectionError() override;
|
||||
void OnHidChooserContextShutdown() override;
|
||||
|
||||
// content::WebContentsObserver:
|
||||
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
|
||||
|
||||
private:
|
||||
api::Session* GetSession();
|
||||
void OnGotDevices(std::vector<device::mojom::HidDeviceInfoPtr> devices);
|
||||
bool DisplayDevice(const device::mojom::HidDeviceInfo& device) const;
|
||||
bool FilterMatchesAny(const device::mojom::HidDeviceInfo& device) const;
|
||||
|
||||
// Add |device_info| to |device_map_|. The device is added to the chooser item
|
||||
// representing the physical device. If the chooser item does not yet exist, a
|
||||
// new item is appended. Returns true if an item was appended.
|
||||
bool AddDeviceInfo(const device::mojom::HidDeviceInfo& device_info);
|
||||
|
||||
// Remove |device_info| from |device_map_|. The device info is removed from
|
||||
// the chooser item representing the physical device. If this would cause the
|
||||
// item to be empty, the chooser item is removed. Does nothing if the device
|
||||
// is not in the chooser item. Returns true if an item was removed.
|
||||
bool RemoveDeviceInfo(const device::mojom::HidDeviceInfo& device_info);
|
||||
|
||||
// Update the information for the device described by |device_info| in the
|
||||
// |device_map_|.
|
||||
void UpdateDeviceInfo(const device::mojom::HidDeviceInfo& device_info);
|
||||
|
||||
void RunCallback(std::vector<device::mojom::HidDeviceInfoPtr> devices);
|
||||
void OnDeviceChosen(gin::Arguments* args);
|
||||
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters_;
|
||||
content::HidChooser::Callback callback_;
|
||||
const url::Origin origin_;
|
||||
const int frame_tree_node_id_;
|
||||
|
||||
// The lifetime of the chooser context is tied to the browser context used to
|
||||
// create it, and may be destroyed while the chooser is still active.
|
||||
base::WeakPtr<HidChooserContext> chooser_context_;
|
||||
|
||||
// Information about connected devices and their HID interfaces. A single
|
||||
// physical device may expose multiple HID interfaces. Keys are physical
|
||||
// device IDs, values are collections of HidDeviceInfo objects representing
|
||||
// the HID interfaces hosted by the physical device.
|
||||
std::map<std::string, std::vector<device::mojom::HidDeviceInfoPtr>>
|
||||
device_map_;
|
||||
|
||||
// An ordered list of physical device IDs that determines the order of items
|
||||
// in the chooser.
|
||||
std::vector<std::string> items_;
|
||||
|
||||
base::ScopedObservation<HidChooserContext,
|
||||
HidChooserContext::DeviceObserver,
|
||||
&HidChooserContext::AddDeviceObserver,
|
||||
&HidChooserContext::RemoveDeviceObserver>
|
||||
observation_{this};
|
||||
|
||||
base::WeakPtr<ElectronHidDelegate> hid_delegate_;
|
||||
|
||||
content::GlobalRenderFrameHostId render_frame_host_id_;
|
||||
|
||||
base::WeakPtrFactory<HidChooserController> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_HID_HID_CHOOSER_CONTROLLER_H_
|
||||
@@ -59,7 +59,12 @@ const NSAutoresizingMaskOptions kDefaultAutoResizingMask =
|
||||
}
|
||||
|
||||
- (BOOL)mouseDownCanMoveWindow {
|
||||
return NO;
|
||||
return
|
||||
[self.window respondsToSelector:@selector(performWindowDragWithEvent:)];
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent*)event {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)shouldIgnoreMouseEvent {
|
||||
@@ -81,16 +86,15 @@ const NSAutoresizingMaskOptions kDefaultAutoResizingMask =
|
||||
- (void)mouseDown:(NSEvent*)event {
|
||||
[super mouseDown:event];
|
||||
|
||||
if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) {
|
||||
if ([self.window respondsToSelector:@selector(performWindowDragWithEvent:)]) {
|
||||
// According to Google, using performWindowDragWithEvent:
|
||||
// does not generate a NSWindowWillMoveNotification. Hence post one.
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:NSWindowWillMoveNotification
|
||||
object:self];
|
||||
|
||||
if (@available(macOS 10.11, *)) {
|
||||
[self.window performWindowDragWithEvent:event];
|
||||
}
|
||||
[self.window performWindowDragWithEvent:event];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,7 +106,7 @@ const NSAutoresizingMaskOptions kDefaultAutoResizingMask =
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent*)event {
|
||||
if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) {
|
||||
if ([self.window respondsToSelector:@selector(performWindowDragWithEvent:)]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,11 @@ void FlipWindowStyle(HWND handle, bool on, DWORD flag) {
|
||||
else
|
||||
style &= ~flag;
|
||||
::SetWindowLong(handle, GWL_STYLE, style);
|
||||
// Window's frame styles are cached so we need to call SetWindowPos
|
||||
// with the SWP_FRAMECHANGED flag to update cache properly.
|
||||
::SetWindowPos(handle, 0, 0, 0, 0, 0, // ignored
|
||||
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER);
|
||||
}
|
||||
|
||||
gfx::Rect DIPToScreenRect(HWND hwnd, const gfx::Rect& pixel_bounds) {
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 16,0,0,20210922
|
||||
PRODUCTVERSION 16,0,0,20210922
|
||||
FILEVERSION 16,0,0,7
|
||||
PRODUCTVERSION 16,0,0,7
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <gmodule.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/files/file_util.h"
|
||||
@@ -131,24 +132,25 @@ class FileChooserDialog {
|
||||
: parent_(
|
||||
static_cast<electron::NativeWindowViews*>(settings.parent_window)),
|
||||
filters_(settings.filters) {
|
||||
const char* confirm_text = gtk_util::kOkLabel;
|
||||
|
||||
if (!settings.button_label.empty())
|
||||
confirm_text = settings.button_label.c_str();
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
confirm_text = gtk_util::kSaveLabel;
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
confirm_text = gtk_util::kOpenLabel;
|
||||
|
||||
InitGtkFileChooserNativeSupport();
|
||||
|
||||
auto label = settings.button_label;
|
||||
|
||||
if (*supports_gtk_file_chooser_native) {
|
||||
dialog_ = GTK_FILE_CHOOSER(
|
||||
dl_gtk_file_chooser_native_new(settings.title.c_str(), NULL, action,
|
||||
confirm_text, gtk_util::kCancelLabel));
|
||||
dialog_ = GTK_FILE_CHOOSER(dl_gtk_file_chooser_native_new(
|
||||
settings.title.c_str(), NULL, action,
|
||||
label.empty() ? nullptr : label.c_str(), nullptr));
|
||||
} else {
|
||||
const char* confirm_text = gtk_util::GetOkLabel();
|
||||
if (!label.empty())
|
||||
confirm_text = label.c_str();
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
confirm_text = gtk_util::GetSaveLabel();
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
confirm_text = gtk_util::GetOpenLabel();
|
||||
|
||||
dialog_ = GTK_FILE_CHOOSER(gtk_file_chooser_dialog_new(
|
||||
settings.title.c_str(), NULL, action, gtk_util::kCancelLabel,
|
||||
settings.title.c_str(), NULL, action, gtk_util::GetCancelLabel(),
|
||||
GTK_RESPONSE_CANCEL, confirm_text, GTK_RESPONSE_ACCEPT, NULL));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,36 +8,71 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "third_party/skia/include/core/SkColor.h"
|
||||
#include "third_party/skia/include/core/SkUnPreMultiply.h"
|
||||
#include "ui/gtk/gtk_compat.h" // nogncheck
|
||||
|
||||
namespace gtk_util {
|
||||
|
||||
// Copied from L40-L55 in
|
||||
// https://cs.chromium.org/chromium/src/chrome/browser/ui/libgtkui/select_file_dialog_impl_gtk.cc
|
||||
#if GTK_CHECK_VERSION(3, 90, 0)
|
||||
// GTK stock items have been deprecated. The docs say to switch to using the
|
||||
// strings "_Open", etc. However this breaks i18n. We could supply our own
|
||||
// internationalized strings, but the "_" in these strings is significant: it's
|
||||
// the keyboard shortcut to select these actions. TODO: Provide
|
||||
// internationalized strings when GTK provides support for it.
|
||||
const char* const kCancelLabel = "_Cancel";
|
||||
const char* const kNoLabel = "_No";
|
||||
const char* const kOkLabel = "_OK";
|
||||
const char* const kOpenLabel = "_Open";
|
||||
const char* const kSaveLabel = "_Save";
|
||||
const char* const kYesLabel = "_Yes";
|
||||
#else
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
const char* const kCancelLabel = GTK_STOCK_CANCEL;
|
||||
const char* const kNoLabel = GTK_STOCK_NO;
|
||||
const char* const kOkLabel = GTK_STOCK_OK;
|
||||
const char* const kOpenLabel = GTK_STOCK_OPEN;
|
||||
const char* const kSaveLabel = GTK_STOCK_SAVE;
|
||||
const char* const kYesLabel = GTK_STOCK_YES;
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
#endif
|
||||
// The following utilities are pulled from
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gtk/select_file_dialog_impl_gtk.cc;l=43-74
|
||||
|
||||
const char* GettextPackage() {
|
||||
static base::NoDestructor<std::string> gettext_package(
|
||||
"gtk" + base::NumberToString(gtk::GtkVersion().components()[0]) + "0");
|
||||
return gettext_package->c_str();
|
||||
}
|
||||
|
||||
const char* GtkGettext(const char* str) {
|
||||
return g_dgettext(GettextPackage(), str);
|
||||
}
|
||||
|
||||
const char* GetCancelLabel() {
|
||||
if (!gtk::GtkCheckVersion(4))
|
||||
return "gtk-cancel"; // In GTK3, this is GTK_STOCK_CANCEL.
|
||||
static const char* cancel = GtkGettext("_Cancel");
|
||||
return cancel;
|
||||
}
|
||||
|
||||
const char* GetOpenLabel() {
|
||||
if (!gtk::GtkCheckVersion(4))
|
||||
return "gtk-open"; // In GTK3, this is GTK_STOCK_OPEN.
|
||||
static const char* open = GtkGettext("_Open");
|
||||
return open;
|
||||
}
|
||||
|
||||
const char* GetSaveLabel() {
|
||||
if (!gtk::GtkCheckVersion(4))
|
||||
return "gtk-save"; // In GTK3, this is GTK_STOCK_SAVE.
|
||||
static const char* save = GtkGettext("_Save");
|
||||
return save;
|
||||
}
|
||||
|
||||
const char* GetOkLabel() {
|
||||
if (!gtk::GtkCheckVersion(4))
|
||||
return "gtk-ok"; // In GTK3, this is GTK_STOCK_OK.
|
||||
static const char* ok = GtkGettext("_Ok");
|
||||
return ok;
|
||||
}
|
||||
|
||||
const char* GetNoLabel() {
|
||||
if (!gtk::GtkCheckVersion(4))
|
||||
return "gtk-no"; // In GTK3, this is GTK_STOCK_NO.
|
||||
static const char* no = GtkGettext("_No");
|
||||
return no;
|
||||
}
|
||||
|
||||
const char* GetYesLabel() {
|
||||
if (!gtk::GtkCheckVersion(4))
|
||||
return "gtk-yes"; // In GTK3, this is GTK_STOCK_YES.
|
||||
static const char* yes = GtkGettext("_Yes");
|
||||
return yes;
|
||||
}
|
||||
|
||||
GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap) {
|
||||
if (bitmap.isNull())
|
||||
|
||||
@@ -11,14 +11,15 @@ class SkBitmap;
|
||||
|
||||
namespace gtk_util {
|
||||
|
||||
/* These are `const char*` rather than the project-preferred `const char[]`
|
||||
because they must fit the type of an external dependency */
|
||||
extern const char* const kCancelLabel;
|
||||
extern const char* const kNoLabel;
|
||||
extern const char* const kOkLabel;
|
||||
extern const char* const kOpenLabel;
|
||||
extern const char* const kSaveLabel;
|
||||
extern const char* const kYesLabel;
|
||||
const char* GettextPackage();
|
||||
const char* GtkGettext(const char* str);
|
||||
|
||||
const char* GetCancelLabel();
|
||||
const char* GetOpenLabel();
|
||||
const char* GetSaveLabel();
|
||||
const char* GetOkLabel();
|
||||
const char* GetNoLabel();
|
||||
const char* GetYesLabel();
|
||||
|
||||
// Convert and copy a SkBitmap to a GdkPixbuf. NOTE: this uses BGRAToRGBA, so
|
||||
// it is an expensive operation. The returned GdkPixbuf will have a refcount of
|
||||
|
||||
@@ -38,6 +38,7 @@ struct MessageBoxSettings {
|
||||
std::string checkbox_label;
|
||||
bool checkbox_checked = false;
|
||||
gfx::ImageSkia icon;
|
||||
int text_width = 0;
|
||||
|
||||
MessageBoxSettings();
|
||||
MessageBoxSettings(const MessageBoxSettings&);
|
||||
|
||||
@@ -145,13 +145,13 @@ class GtkMessageBox : public NativeWindowObserver {
|
||||
const char* TranslateToStock(int id, const std::string& text) {
|
||||
const std::string lower = base::ToLowerASCII(text);
|
||||
if (lower == "cancel")
|
||||
return gtk_util::kCancelLabel;
|
||||
return gtk_util::GetCancelLabel();
|
||||
if (lower == "no")
|
||||
return gtk_util::kNoLabel;
|
||||
return gtk_util::GetNoLabel();
|
||||
if (lower == "ok")
|
||||
return gtk_util::kOkLabel;
|
||||
return gtk_util::GetOkLabel();
|
||||
if (lower == "yes")
|
||||
return gtk_util::kYesLabel;
|
||||
return gtk_util::GetYesLabel();
|
||||
return text.c_str();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,12 @@ NSAlert* CreateNSAlert(const MessageBoxSettings& settings) {
|
||||
[alert setIcon:image];
|
||||
}
|
||||
|
||||
if (settings.text_width > 0) {
|
||||
NSRect rect = NSMakeRect(0, 0, settings.text_width, 0);
|
||||
NSView* accessoryView = [[NSView alloc] initWithFrame:rect];
|
||||
[alert setAccessoryView:[accessoryView autorelease]];
|
||||
}
|
||||
|
||||
return alert;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ int FramelessView::ResizingBorderHitTest(const gfx::Point& point) {
|
||||
: false;
|
||||
|
||||
// https://github.com/electron/electron/issues/611
|
||||
// If window isn't resizable, we should always return HTCLIENT, otherwise the
|
||||
// If window isn't resizable, we should always return HTNOWHERE, otherwise the
|
||||
// hover state of DOM will not be cleared probably.
|
||||
if (!can_ever_resize)
|
||||
return HTCLIENT;
|
||||
return HTNOWHERE;
|
||||
|
||||
// Don't allow overlapping resize handles when the window is maximized or
|
||||
// fullscreen, as it can't be resized in those states.
|
||||
|
||||
@@ -94,6 +94,28 @@ bool WebContentsPermissionHelper::CheckPermission(
|
||||
details);
|
||||
}
|
||||
|
||||
bool WebContentsPermissionHelper::CheckDevicePermission(
|
||||
content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::RenderFrameHost* render_frame_host) const {
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
web_contents_->GetBrowserContext()->GetPermissionControllerDelegate());
|
||||
return permission_manager->CheckDevicePermission(permission, origin, device,
|
||||
render_frame_host);
|
||||
}
|
||||
|
||||
void WebContentsPermissionHelper::GrantDevicePermission(
|
||||
content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::RenderFrameHost* render_frame_host) const {
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
web_contents_->GetBrowserContext()->GetPermissionControllerDelegate());
|
||||
permission_manager->GrantDevicePermission(permission, origin, device,
|
||||
render_frame_host);
|
||||
}
|
||||
|
||||
void WebContentsPermissionHelper::RequestFullscreenPermission(
|
||||
base::OnceCallback<void(bool)> callback) {
|
||||
RequestPermission(
|
||||
@@ -168,6 +190,32 @@ bool WebContentsPermissionHelper::CheckSerialAccessPermission(
|
||||
static_cast<content::PermissionType>(PermissionType::SERIAL), &details);
|
||||
}
|
||||
|
||||
bool WebContentsPermissionHelper::CheckHIDAccessPermission(
|
||||
const url::Origin& embedding_origin) const {
|
||||
base::DictionaryValue details;
|
||||
details.SetString("securityOrigin", embedding_origin.GetURL().spec());
|
||||
return CheckPermission(
|
||||
static_cast<content::PermissionType>(PermissionType::HID), &details);
|
||||
}
|
||||
|
||||
bool WebContentsPermissionHelper::CheckHIDDevicePermission(
|
||||
const url::Origin& origin,
|
||||
base::Value device,
|
||||
content::RenderFrameHost* render_frame_host) const {
|
||||
return CheckDevicePermission(
|
||||
static_cast<content::PermissionType>(PermissionType::HID), origin,
|
||||
&device, render_frame_host);
|
||||
}
|
||||
|
||||
void WebContentsPermissionHelper::GrantHIDDevicePermission(
|
||||
const url::Origin& origin,
|
||||
base::Value device,
|
||||
content::RenderFrameHost* render_frame_host) const {
|
||||
return GrantDevicePermission(
|
||||
static_cast<content::PermissionType>(PermissionType::HID), origin,
|
||||
&device, render_frame_host);
|
||||
}
|
||||
|
||||
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsPermissionHelper)
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -23,7 +23,8 @@ class WebContentsPermissionHelper
|
||||
POINTER_LOCK = static_cast<int>(content::PermissionType::NUM) + 1,
|
||||
FULLSCREEN,
|
||||
OPEN_EXTERNAL,
|
||||
SERIAL
|
||||
SERIAL,
|
||||
HID
|
||||
};
|
||||
|
||||
// Asynchronous Requests
|
||||
@@ -41,6 +42,15 @@ class WebContentsPermissionHelper
|
||||
bool CheckMediaAccessPermission(const GURL& security_origin,
|
||||
blink::mojom::MediaStreamType type) const;
|
||||
bool CheckSerialAccessPermission(const url::Origin& embedding_origin) const;
|
||||
bool CheckHIDAccessPermission(const url::Origin& embedding_origin) const;
|
||||
bool CheckHIDDevicePermission(
|
||||
const url::Origin& origin,
|
||||
base::Value device,
|
||||
content::RenderFrameHost* render_frame_host) const;
|
||||
void GrantHIDDevicePermission(
|
||||
const url::Origin& origin,
|
||||
base::Value device,
|
||||
content::RenderFrameHost* render_frame_host) const;
|
||||
|
||||
private:
|
||||
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
|
||||
@@ -54,6 +64,16 @@ class WebContentsPermissionHelper
|
||||
bool CheckPermission(content::PermissionType permission,
|
||||
const base::DictionaryValue* details) const;
|
||||
|
||||
bool CheckDevicePermission(content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::RenderFrameHost* render_frame_host) const;
|
||||
|
||||
void GrantDevicePermission(content::PermissionType permission,
|
||||
const url::Origin& origin,
|
||||
const base::Value* device,
|
||||
content::RenderFrameHost* render_frame_host) const;
|
||||
|
||||
content::WebContents* web_contents_;
|
||||
|
||||
WEB_CONTENTS_USER_DATA_KEY_DECL();
|
||||
|
||||
@@ -106,28 +106,6 @@ bool IsSameOrigin(const GURL& l, const GURL& r) {
|
||||
return url::Origin::Create(l).IsSameOriginWith(url::Origin::Create(r));
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
std::vector<v8::Global<v8::Value>> weakly_tracked_values;
|
||||
|
||||
void WeaklyTrackValue(v8::Isolate* isolate, v8::Local<v8::Value> value) {
|
||||
v8::Global<v8::Value> global_value(isolate, value);
|
||||
global_value.SetWeak();
|
||||
weakly_tracked_values.push_back(std::move(global_value));
|
||||
}
|
||||
|
||||
void ClearWeaklyTrackedValues() {
|
||||
weakly_tracked_values.clear();
|
||||
}
|
||||
|
||||
std::vector<v8::Local<v8::Value>> GetWeaklyTrackedValues(v8::Isolate* isolate) {
|
||||
std::vector<v8::Local<v8::Value>> locals;
|
||||
for (const auto& value : weakly_tracked_values) {
|
||||
if (!value.IsEmpty())
|
||||
locals.push_back(value.Get(isolate));
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
// This causes a fatal error by creating a circular extension dependency.
|
||||
void TriggerFatalErrorForTesting(v8::Isolate* isolate) {
|
||||
static const char* aDeps[] = {"B"};
|
||||
@@ -141,7 +119,6 @@ void TriggerFatalErrorForTesting(v8::Isolate* isolate) {
|
||||
void RunUntilIdle() {
|
||||
base::RunLoop().RunUntilIdle();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
@@ -156,13 +133,8 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
dict.SetMethod("requestGarbageCollectionForTesting",
|
||||
&RequestGarbageCollectionForTesting);
|
||||
dict.SetMethod("isSameOrigin", &IsSameOrigin);
|
||||
#if DCHECK_IS_ON()
|
||||
dict.SetMethod("triggerFatalErrorForTesting", &TriggerFatalErrorForTesting);
|
||||
dict.SetMethod("getWeaklyTrackedValues", &GetWeaklyTrackedValues);
|
||||
dict.SetMethod("clearWeaklyTrackedValues", &ClearWeaklyTrackedValues);
|
||||
dict.SetMethod("weaklyTrackValue", &WeaklyTrackValue);
|
||||
dict.SetMethod("runUntilIdle", &RunUntilIdle);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user