mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
17 Commits
v31.0.0-ni
...
v31.0.0-ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
344aba0838 | ||
|
|
41ba963392 | ||
|
|
43a9f70d19 | ||
|
|
0a7df0ef3d | ||
|
|
38ef9a7690 | ||
|
|
ba3b647fd7 | ||
|
|
76f7bbb0a8 | ||
|
|
22c149812c | ||
|
|
42164d7081 | ||
|
|
3eb94b72ba | ||
|
|
752f2eb124 | ||
|
|
beafbfd511 | ||
|
|
d54645e554 | ||
|
|
0bf53a3876 | ||
|
|
62d4b21819 | ||
|
|
61457c9498 | ||
|
|
c6102b9278 |
@@ -1,3 +1,17 @@
|
||||
{
|
||||
"extends": "@electron/lint-roller/configs/markdownlint.json"
|
||||
"extends": "@electron/lint-roller/configs/markdownlint.json",
|
||||
"no-angle-brackets": true,
|
||||
"no-inline-html": {
|
||||
"allowed_elements": [
|
||||
"br",
|
||||
"details",
|
||||
"img",
|
||||
"li",
|
||||
"summary",
|
||||
"ul",
|
||||
"unknown",
|
||||
"Tabs",
|
||||
"TabItem",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
BUILD.gn
1
BUILD.gn
@@ -475,6 +475,7 @@ source_set("electron_lib") {
|
||||
"//net:extras",
|
||||
"//net:net_resources",
|
||||
"//printing/buildflags",
|
||||
"//services/device/public/cpp/bluetooth:bluetooth",
|
||||
"//services/device/public/cpp/geolocation",
|
||||
"//services/device/public/cpp/hid",
|
||||
"//services/device/public/mojom",
|
||||
|
||||
@@ -33,6 +33,8 @@ static_library("chrome") {
|
||||
"//chrome/browser/devtools/visual_logging.h",
|
||||
"//chrome/browser/extensions/global_shortcut_listener.cc",
|
||||
"//chrome/browser/extensions/global_shortcut_listener.h",
|
||||
"//chrome/browser/file_system_access/file_system_access_features.cc",
|
||||
"//chrome/browser/file_system_access/file_system_access_features.h",
|
||||
"//chrome/browser/icon_loader.cc",
|
||||
"//chrome/browser/icon_loader.h",
|
||||
"//chrome/browser/icon_manager.cc",
|
||||
|
||||
@@ -17,6 +17,8 @@ following properties:
|
||||
method.
|
||||
* `url` string (optional) - The request URL. Must be provided in the absolute
|
||||
form with the protocol scheme specified as http or https.
|
||||
* `headers` Record\<string, string | string[]\> (optional) - Headers to be sent
|
||||
with the request.
|
||||
* `session` Session (optional) - The [`Session`](session.md) instance with
|
||||
which the request is associated.
|
||||
* `partition` string (optional) - The name of the [`partition`](session.md)
|
||||
|
||||
@@ -51,6 +51,18 @@ Unsupported options are:
|
||||
--http-parser
|
||||
```
|
||||
|
||||
If the [`nodeOptions` fuse](../tutorial/fuses.md#L27) is disabled, `NODE_OPTIONS` will be ignored.
|
||||
|
||||
### `NODE_EXTRA_CA_CERTS`
|
||||
|
||||
See [Node.js cli documentation](https://github.com/nodejs/node/blob/main/doc/api/cli.md#node_extra_ca_certsfile) for details.
|
||||
|
||||
```sh
|
||||
export NODE_EXTRA_CA_CERTS=/path/to/cert.pem
|
||||
```
|
||||
|
||||
If the [`nodeOptions` fuse](../tutorial/fuses.md#L27) is disabled, `NODE_EXTRA_CA_CERTS` will be ignored.
|
||||
|
||||
### `GOOGLE_API_KEY`
|
||||
|
||||
Geolocation support in Electron requires the use of Google Cloud Platform's
|
||||
@@ -92,6 +104,8 @@ you would when running the normal Node.js executable, with the exception of the
|
||||
These flags are disabled owing to the fact that Electron uses BoringSSL instead of OpenSSL when building Node.js'
|
||||
`crypto` module, and so will not work as designed.
|
||||
|
||||
If the [`runAsNode` fuse](../tutorial/fuses.md#L13) is disabled, `ELECTRON_RUN_AS_NODE` will be ignored.
|
||||
|
||||
### `ELECTRON_NO_ATTACH_CONSOLE` _Windows_
|
||||
|
||||
Don't attach to the current console session.
|
||||
|
||||
@@ -818,15 +818,10 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
* `top-level-storage-access` - Allow top-level sites to request third-party cookie access on behalf of embedded content originating from another site in the same related website set using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
|
||||
* `window-management` - Request access to enumerate screens using the [`getScreenDetails`](https://developer.chrome.com/en/articles/multi-screen-window-placement/) API.
|
||||
* `unknown` - An unrecognized permission request.
|
||||
* `fileSystem` - Request access to read, write, and file management capabilities using the [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API).
|
||||
* `callback` Function
|
||||
* `permissionGranted` boolean - Allow or deny the permission.
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `externalURL` string (optional) - The url of the `openExternal` request.
|
||||
* `securityOrigin` string (optional) - The security origin of the `media` request.
|
||||
* `mediaTypes` string[] (optional) - The types of media access being requested, elements can be `video`
|
||||
or `audio`
|
||||
* `requestingUrl` string - The last URL the requesting frame loaded
|
||||
* `isMainFrame` boolean - Whether the frame making the request is the main frame
|
||||
* `details` [PermissionRequest](structures/permission-request.md) | [FilesystemPermissionRequest](structures/filesystem-permission-request.md) | [MediaAccessPermissionRequest](structures/media-access-permission-request.md) | [OpenExternalPermissionRequest](structures/open-external-permission-request.md) - Additional information about the permission being requested.
|
||||
|
||||
Sets the handler which can be used to respond to permission requests for the `session`.
|
||||
Calling `callback(true)` will allow the permission and `callback(false)` will reject it.
|
||||
@@ -1428,23 +1423,34 @@ is emitted.
|
||||
Returns `string | null` - The absolute file system path where data for this
|
||||
session is persisted on disk. For in memory sessions this returns `null`.
|
||||
|
||||
#### `ses.clearData()`
|
||||
#### `ses.clearData([options])`
|
||||
|
||||
* `options` Object (optional)
|
||||
* `dataTypes` String[] (optional) - The types of data to clear. By default, this will clear all types of data.
|
||||
* `backgroundFetch` - Background Fetch
|
||||
* `cache` - Cache
|
||||
* `cookies` - Cookies
|
||||
* `downloads` - Downloads
|
||||
* `fileSystems` - File Systems
|
||||
* `indexedDB` - IndexedDB
|
||||
* `localStorage` - Local Storage
|
||||
* `serviceWorkers` - Service Workers
|
||||
* `webSQL` - WebSQL
|
||||
* `origins` String[] (optional) - Clear data for only these origins. Cannot be used with `excludeOrigins`.
|
||||
* `excludeOrigins` String[] (optional) - Clear data for all origins except these ones. Cannot be used with `origins`.
|
||||
* `avoidClosingConnections` boolean (optional) - Skips deleting cookies that would close current network connections. (Default: `false`)
|
||||
* `originMatchingMode` String (optional) - The behavior for matching data to origins.
|
||||
* `third-parties-included` (default) - Storage is matched on origin in first-party contexts and top-level-site in third-party contexts.
|
||||
* `origin-in-all-contexts` - Storage is matched on origin only in all contexts.
|
||||
|
||||
Returns `Promise<void>` - resolves when all data has been cleared.
|
||||
|
||||
This method clears many different types of data, inlcuding:
|
||||
|
||||
* Cache
|
||||
* Cookies
|
||||
* Downloads
|
||||
* IndexedDB
|
||||
* Local Storage
|
||||
* Service Workers
|
||||
* And more...
|
||||
Clears various different types of data.
|
||||
|
||||
This method clears more types of data and is more thourough than the
|
||||
`clearStorageData` method, however it is currently less configurable than that
|
||||
method.
|
||||
`clearStorageData` method.
|
||||
|
||||
**Note:** Cookies are stored at a broader scope than origins. When removing cookies and filtering by `origins` (or `excludeOrigins`), the cookies will be removed at the [registrable domain](https://url.spec.whatwg.org/#host-registrable-domain) level. For example, clearing cookies for the origin `https://really.specific.origin.example.com/` will end up clearing all cookies for `example.com`. Clearing cookies for the origin `https://my.website.example.co.uk/` will end up clearing all cookies for `example.co.uk`.
|
||||
|
||||
For more information, refer to Chromium's [`BrowsingDataRemover` interface](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/browsing_data_remover.h).
|
||||
|
||||
|
||||
5
docs/api/structures/filesystem-permission-request.md
Normal file
5
docs/api/structures/filesystem-permission-request.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# FilesystemPermissionRequest Object extends `PermissionRequest`
|
||||
|
||||
* `filePath` string (optional) - The path of the `fileSystem` request.
|
||||
* `isDirectory` boolean (optional) - Whether the `fileSystem` request is a directory.
|
||||
* `fileAccessType` string (optional) - The access type of the `fileSystem` request. Can be `writable` or `readable`.
|
||||
5
docs/api/structures/media-access-permission-request.md
Normal file
5
docs/api/structures/media-access-permission-request.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# MediaAccessPermissionRequest Object extends `PermissionRequest`
|
||||
|
||||
* `securityOrigin` string (optional) - The security origin of the request.
|
||||
* `mediaTypes` string[] (optional) - The types of media access being requested - elements can be `video`
|
||||
or `audio`.
|
||||
3
docs/api/structures/open-external-permission-request.md
Normal file
3
docs/api/structures/open-external-permission-request.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# OpenExternalPermissionRequest Object extends `PermissionRequest`
|
||||
|
||||
* `externalURL` string (optional) - The url of the `openExternal` request.
|
||||
4
docs/api/structures/permission-request.md
Normal file
4
docs/api/structures/permission-request.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# PermissionRequest Object
|
||||
|
||||
* `requestingUrl` string - The last URL the requesting frame loaded.
|
||||
* `isMainFrame` boolean - Whether the frame making the request is the main frame.
|
||||
@@ -59,7 +59,7 @@ need to be called **before** the app's `ready` event is emitted.
|
||||
With top-level `await` available in Node.js ESM, make sure to `await` every Promise that you need to
|
||||
execute before the `ready` event. Otherwise, your app may be `ready` before your code executes.
|
||||
|
||||
This is particularly important to keep in mind for dynamic ESM import statmements (static imports are unaffected).
|
||||
This is particularly important to keep in mind for dynamic ESM import statements (static imports are unaffected).
|
||||
For example, if `index.mjs` calls `import('./set-up-paths.mjs')` at the top level, the app will
|
||||
likely already be `ready` by the time that dynamic import resolves.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ The cookieEncryption fuse toggles whether the cookie store on disk is encrypted
|
||||
**Default:** Enabled
|
||||
**@electron/fuses:** `FuseV1Options.EnableNodeOptionsEnvironmentVariable`
|
||||
|
||||
The nodeOptions fuse toggles whether the [`NODE_OPTIONS`](https://nodejs.org/api/cli.html#node_optionsoptions) environment variable is respected or not. This environment variable can be used to pass all kinds of custom options to the Node.js runtime and isn't typically used by apps in production. Most apps can safely disable this fuse.
|
||||
The nodeOptions fuse toggles whether the [`NODE_OPTIONS`](https://nodejs.org/api/cli.html#node_optionsoptions) and [`NODE_EXTRA_CA_CERTS`](https://github.com/nodejs/node/blob/main/doc/api/cli.md#node_extra_ca_certsfile) environment variables are respected. The `NODE_OPTIONS` environment variable can be used to pass all kinds of custom options to the Node.js runtime and isn't typically used by apps in production. Most apps can safely disable this fuse.
|
||||
|
||||
### `nodeCliInspect`
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRende
|
||||
<details><summary>Typed import aliases</summary>
|
||||
|
||||
For better type checking when writing TypeScript code, you can choose to import
|
||||
main process modules from <code>electron/main</code>.
|
||||
main process modules from `electron/main`.
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron/main')
|
||||
|
||||
@@ -89,6 +89,7 @@ auto_filenames = {
|
||||
"docs/api/structures/extension.md",
|
||||
"docs/api/structures/file-filter.md",
|
||||
"docs/api/structures/file-path-with-headers.md",
|
||||
"docs/api/structures/filesystem-permission-request.md",
|
||||
"docs/api/structures/gpu-feature-status.md",
|
||||
"docs/api/structures/hid-device.md",
|
||||
"docs/api/structures/input-event.md",
|
||||
@@ -99,6 +100,7 @@ auto_filenames = {
|
||||
"docs/api/structures/jump-list-item.md",
|
||||
"docs/api/structures/keyboard-event.md",
|
||||
"docs/api/structures/keyboard-input-event.md",
|
||||
"docs/api/structures/media-access-permission-request.md",
|
||||
"docs/api/structures/memory-info.md",
|
||||
"docs/api/structures/memory-usage-details.md",
|
||||
"docs/api/structures/mime-typed-buffer.md",
|
||||
@@ -106,7 +108,9 @@ auto_filenames = {
|
||||
"docs/api/structures/mouse-wheel-input-event.md",
|
||||
"docs/api/structures/notification-action.md",
|
||||
"docs/api/structures/notification-response.md",
|
||||
"docs/api/structures/open-external-permission-request.md",
|
||||
"docs/api/structures/payment-discount.md",
|
||||
"docs/api/structures/permission-request.md",
|
||||
"docs/api/structures/point.md",
|
||||
"docs/api/structures/post-body.md",
|
||||
"docs/api/structures/printer-info.md",
|
||||
|
||||
@@ -380,6 +380,10 @@ filenames = {
|
||||
"shell/browser/file_select_helper.cc",
|
||||
"shell/browser/file_select_helper.h",
|
||||
"shell/browser/file_select_helper_mac.mm",
|
||||
"shell/browser/file_system_access/file_system_access_permission_context.cc",
|
||||
"shell/browser/file_system_access/file_system_access_permission_context.h",
|
||||
"shell/browser/file_system_access/file_system_access_permission_context_factory.cc",
|
||||
"shell/browser/file_system_access/file_system_access_permission_context_factory.h",
|
||||
"shell/browser/font_defaults.cc",
|
||||
"shell/browser/font_defaults.h",
|
||||
"shell/browser/hid/electron_hid_delegate.cc",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@electron/docs-parser": "^1.2.0",
|
||||
"@electron/fiddle-core": "^1.0.4",
|
||||
"@electron/github-app-auth": "^2.0.0",
|
||||
"@electron/lint-roller": "^1.9.0",
|
||||
"@electron/lint-roller": "^1.12.1",
|
||||
"@electron/typescript-definitions": "^8.15.2",
|
||||
"@octokit/rest": "^19.0.7",
|
||||
"@primer/octicons": "^10.0.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-unicorn": "^46.0.1",
|
||||
"events": "^3.2.0",
|
||||
"express": "^4.16.4",
|
||||
"express": "^4.19.2",
|
||||
"folder-hash": "^2.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"got": "^11.8.5",
|
||||
|
||||
@@ -129,3 +129,4 @@ build_run_reclient_cfg_generator_after_chrome.patch
|
||||
fix_suppress_clang_-wimplicit-const-int-float-conversion_in.patch
|
||||
fix_getcursorscreenpoint_wrongly_returns_0_0.patch
|
||||
fix_add_support_for_skipping_first_2_no-op_refreshes_in_thumb_cap.patch
|
||||
refactor_expose_file_system_access_blocklist.patch
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Wed, 27 Mar 2024 10:47:48 +0100
|
||||
Subject: refactor: expose file system access blocklist
|
||||
|
||||
This CL exposes the file system access blocklist publicly so that we can leverage
|
||||
it in Electron and prevent drift from Chrome's blocklist. We should look for a way
|
||||
to upstream this change to Chrome.
|
||||
|
||||
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
|
||||
index 9c644d678d6d811ae5679594c0574fc0d8607f62..792cd62da17239ca6933930880af23754e4ab3d3 100644
|
||||
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
|
||||
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
|
||||
@@ -38,7 +38,6 @@
|
||||
#include "chrome/browser/profiles/profile_manager.h"
|
||||
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
|
||||
#include "chrome/browser/ui/file_system_access_dialogs.h"
|
||||
-#include "chrome/common/chrome_paths.h"
|
||||
#include "chrome/common/pdf_util.h"
|
||||
#include "chrome/grit/generated_resources.h"
|
||||
#include "components/content_settings/core/browser/host_content_settings_map.h"
|
||||
@@ -222,121 +221,6 @@ bool MaybeIsLocalUNCPath(const base::FilePath& path) {
|
||||
}
|
||||
#endif
|
||||
|
||||
-// Sentinel used to indicate that no PathService key is specified for a path in
|
||||
-// the struct below.
|
||||
-constexpr const int kNoBasePathKey = -1;
|
||||
-
|
||||
-enum BlockType {
|
||||
- kBlockAllChildren,
|
||||
- kBlockNestedDirectories,
|
||||
- kDontBlockChildren
|
||||
-};
|
||||
-
|
||||
-const struct {
|
||||
- // base::BasePathKey value (or one of the platform specific extensions to it)
|
||||
- // for a path that should be blocked. Specify kNoBasePathKey if |path| should
|
||||
- // be used instead.
|
||||
- int base_path_key;
|
||||
-
|
||||
- // Explicit path to block instead of using |base_path_key|. Set to nullptr to
|
||||
- // use |base_path_key| on its own. If both |base_path_key| and |path| are set,
|
||||
- // |path| is treated relative to the path |base_path_key| resolves to.
|
||||
- const base::FilePath::CharType* path;
|
||||
-
|
||||
- // If this is set to kDontBlockChildren, only the given path and its parents
|
||||
- // are blocked. If this is set to kBlockAllChildren, all children of the given
|
||||
- // path are blocked as well. Finally if this is set to kBlockNestedDirectories
|
||||
- // access is allowed to individual files in the directory, but nested
|
||||
- // directories are still blocked.
|
||||
- // The BlockType of the nearest ancestor of a path to check is what ultimately
|
||||
- // determines if a path is blocked or not. If a blocked path is a descendent
|
||||
- // of another blocked path, then it may override the child-blocking policy of
|
||||
- // its ancestor. For example, if /home blocks all children, but
|
||||
- // /home/downloads does not, then /home/downloads/file.ext will *not* be
|
||||
- // blocked.
|
||||
- BlockType type;
|
||||
-} kBlockedPaths[] = {
|
||||
- // Don't allow users to share their entire home directory, entire desktop or
|
||||
- // entire documents folder, but do allow sharing anything inside those
|
||||
- // directories not otherwise blocked.
|
||||
- {base::DIR_HOME, nullptr, kDontBlockChildren},
|
||||
- {base::DIR_USER_DESKTOP, nullptr, kDontBlockChildren},
|
||||
- {chrome::DIR_USER_DOCUMENTS, nullptr, kDontBlockChildren},
|
||||
- // Similar restrictions for the downloads directory.
|
||||
- {chrome::DIR_DEFAULT_DOWNLOADS, nullptr, kDontBlockChildren},
|
||||
- {chrome::DIR_DEFAULT_DOWNLOADS_SAFE, nullptr, kDontBlockChildren},
|
||||
- // The Chrome installation itself should not be modified by the web.
|
||||
- {base::DIR_EXE, nullptr, kBlockAllChildren},
|
||||
-#if !BUILDFLAG(IS_FUCHSIA)
|
||||
- {base::DIR_MODULE, nullptr, kBlockAllChildren},
|
||||
-#endif
|
||||
- {base::DIR_ASSETS, nullptr, kBlockAllChildren},
|
||||
- // And neither should the configuration of at least the currently running
|
||||
- // Chrome instance (note that this does not take --user-data-dir command
|
||||
- // line overrides into account).
|
||||
- {chrome::DIR_USER_DATA, nullptr, kBlockAllChildren},
|
||||
- // ~/.ssh is pretty sensitive on all platforms, so block access to that.
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL(".ssh"), kBlockAllChildren},
|
||||
- // And limit access to ~/.gnupg as well.
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL(".gnupg"), kBlockAllChildren},
|
||||
-#if BUILDFLAG(IS_WIN)
|
||||
- // Some Windows specific directories to block, basically all apps, the
|
||||
- // operating system itself, as well as configuration data for apps.
|
||||
- {base::DIR_PROGRAM_FILES, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_PROGRAM_FILESX86, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_PROGRAM_FILES6432, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_WINDOWS, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_ROAMING_APP_DATA, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_LOCAL_APP_DATA, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_COMMON_APP_DATA, nullptr, kBlockAllChildren},
|
||||
- // Opening a file from an MTP device, such as a smartphone or a camera, is
|
||||
- // implemented by Windows as opening a file in the temporary internet files
|
||||
- // directory. To support that, allow opening files in that directory, but
|
||||
- // not whole directories.
|
||||
- {base::DIR_IE_INTERNET_CACHE, nullptr, kBlockNestedDirectories},
|
||||
-#endif
|
||||
-#if BUILDFLAG(IS_MAC)
|
||||
- // Similar Mac specific blocks.
|
||||
- {base::DIR_APP_DATA, nullptr, kBlockAllChildren},
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL("Library"), kBlockAllChildren},
|
||||
- // Allow access to other cloud files, such as Google Drive.
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL("Library/CloudStorage"),
|
||||
- kDontBlockChildren},
|
||||
- // Allow the site to interact with data from its corresponding natively
|
||||
- // installed (sandboxed) application. It would be nice to limit a site to
|
||||
- // access only _its_ corresponding natively installed application,
|
||||
- // but unfortunately there's no straightforward way to do that. See
|
||||
- // https://crbug.com/984641#c22.
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL("Library/Containers"),
|
||||
- kDontBlockChildren},
|
||||
- // Allow access to iCloud files...
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL("Library/Mobile Documents"),
|
||||
- kDontBlockChildren},
|
||||
- // ... which may also appear at this directory.
|
||||
- {base::DIR_HOME,
|
||||
- FILE_PATH_LITERAL("Library/Mobile Documents/com~apple~CloudDocs"),
|
||||
- kDontBlockChildren},
|
||||
-#endif
|
||||
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
||||
- // On Linux also block access to devices via /dev.
|
||||
- {kNoBasePathKey, FILE_PATH_LITERAL("/dev"), kBlockAllChildren},
|
||||
- // And security sensitive data in /proc and /sys.
|
||||
- {kNoBasePathKey, FILE_PATH_LITERAL("/proc"), kBlockAllChildren},
|
||||
- {kNoBasePathKey, FILE_PATH_LITERAL("/sys"), kBlockAllChildren},
|
||||
- // And system files in /boot and /etc.
|
||||
- {kNoBasePathKey, FILE_PATH_LITERAL("/boot"), kBlockAllChildren},
|
||||
- {kNoBasePathKey, FILE_PATH_LITERAL("/etc"), kBlockAllChildren},
|
||||
- // And block all of ~/.config, matching the similar restrictions on mac
|
||||
- // and windows.
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL(".config"), kBlockAllChildren},
|
||||
- // Block ~/.dbus as well, just in case, although there probably isn't much a
|
||||
- // website can do with access to that directory and its contents.
|
||||
- {base::DIR_HOME, FILE_PATH_LITERAL(".dbus"), kBlockAllChildren},
|
||||
-#endif
|
||||
- // TODO(https://crbug.com/984641): Refine this list, for example add
|
||||
- // XDG_CONFIG_HOME when it is not set ~/.config?
|
||||
-};
|
||||
-
|
||||
// Describes a rule for blocking a directory, which can be constructed
|
||||
// dynamically (based on state) or statically (from kBlockedPaths).
|
||||
struct BlockPathRule {
|
||||
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h
|
||||
index 8bc8257b603a88e56f77dcf7d72aa9dad45880db..484f98c68b0dc860a6482e923df2379133c57749 100644
|
||||
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h
|
||||
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h
|
||||
@@ -17,12 +17,13 @@
|
||||
#include "base/time/default_clock.h"
|
||||
#include "chrome/browser/file_system_access/file_system_access_features.h"
|
||||
#include "chrome/browser/file_system_access/file_system_access_permission_request_manager.h"
|
||||
-#include "components/enterprise/buildflags/buildflags.h"
|
||||
+#include "chrome/common/chrome_paths.h"
|
||||
#include "components/permissions/features.h"
|
||||
#include "components/permissions/object_permission_context_base.h"
|
||||
#include "content/public/browser/file_system_access_permission_context.h"
|
||||
#include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom-forward.h"
|
||||
|
||||
+
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
#include "chrome/browser/permissions/one_time_permissions_tracker.h"
|
||||
#include "chrome/browser/permissions/one_time_permissions_tracker_observer.h"
|
||||
@@ -30,7 +31,8 @@
|
||||
#include "chrome/browser/web_applications/web_app_install_manager_observer.h"
|
||||
#endif
|
||||
|
||||
-#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
|
||||
+#if 0
|
||||
+#include "components/enterprise/buildflags/buildflags.h"
|
||||
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"
|
||||
#include "components/enterprise/common/files_scan_data.h"
|
||||
#endif
|
||||
@@ -331,6 +333,121 @@ class ChromeFileSystemAccessPermissionContext
|
||||
// chrome://settings/content/filesystem UI.
|
||||
static constexpr char kPermissionPathKey[] = "path";
|
||||
|
||||
+ // Sentinel used to indicate that no PathService key is specified for a path in
|
||||
+ // the struct below.
|
||||
+ static constexpr int kNoBasePathKey = -1;
|
||||
+
|
||||
+ enum BlockType {
|
||||
+ kBlockAllChildren,
|
||||
+ kBlockNestedDirectories,
|
||||
+ kDontBlockChildren
|
||||
+ };
|
||||
+
|
||||
+ static constexpr struct {
|
||||
+ // base::BasePathKey value (or one of the platform specific extensions to it)
|
||||
+ // for a path that should be blocked. Specify kNoBasePathKey if |path| should
|
||||
+ // be used instead.
|
||||
+ int base_path_key;
|
||||
+
|
||||
+ // Explicit path to block instead of using |base_path_key|. Set to nullptr to
|
||||
+ // use |base_path_key| on its own. If both |base_path_key| and |path| are set,
|
||||
+ // |path| is treated relative to the path |base_path_key| resolves to.
|
||||
+ const base::FilePath::CharType* path;
|
||||
+
|
||||
+ // If this is set to kDontBlockChildren, only the given path and its parents
|
||||
+ // are blocked. If this is set to kBlockAllChildren, all children of the given
|
||||
+ // path are blocked as well. Finally if this is set to kBlockNestedDirectories
|
||||
+ // access is allowed to individual files in the directory, but nested
|
||||
+ // directories are still blocked.
|
||||
+ // The BlockType of the nearest ancestor of a path to check is what ultimately
|
||||
+ // determines if a path is blocked or not. If a blocked path is a descendent
|
||||
+ // of another blocked path, then it may override the child-blocking policy of
|
||||
+ // its ancestor. For example, if /home blocks all children, but
|
||||
+ // /home/downloads does not, then /home/downloads/file.ext will *not* be
|
||||
+ // blocked.
|
||||
+ BlockType type;
|
||||
+ } kBlockedPaths[] = {
|
||||
+ // Don't allow users to share their entire home directory, entire desktop or
|
||||
+ // entire documents folder, but do allow sharing anything inside those
|
||||
+ // directories not otherwise blocked.
|
||||
+ {base::DIR_HOME, nullptr, kDontBlockChildren},
|
||||
+ {base::DIR_USER_DESKTOP, nullptr, kDontBlockChildren},
|
||||
+ {chrome::DIR_USER_DOCUMENTS, nullptr, kDontBlockChildren},
|
||||
+ // Similar restrictions for the downloads directory.
|
||||
+ {chrome::DIR_DEFAULT_DOWNLOADS, nullptr, kDontBlockChildren},
|
||||
+ {chrome::DIR_DEFAULT_DOWNLOADS_SAFE, nullptr, kDontBlockChildren},
|
||||
+ // The Chrome installation itself should not be modified by the web.
|
||||
+ {base::DIR_EXE, nullptr, kBlockAllChildren},
|
||||
+ #if !BUILDFLAG(IS_FUCHSIA)
|
||||
+ {base::DIR_MODULE, nullptr, kBlockAllChildren},
|
||||
+ #endif
|
||||
+ {base::DIR_ASSETS, nullptr, kBlockAllChildren},
|
||||
+ // And neither should the configuration of at least the currently running
|
||||
+ // Chrome instance (note that this does not take --user-data-dir command
|
||||
+ // line overrides into account).
|
||||
+ {chrome::DIR_USER_DATA, nullptr, kBlockAllChildren},
|
||||
+ // ~/.ssh is pretty sensitive on all platforms, so block access to that.
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL(".ssh"), kBlockAllChildren},
|
||||
+ // And limit access to ~/.gnupg as well.
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL(".gnupg"), kBlockAllChildren},
|
||||
+ #if BUILDFLAG(IS_WIN)
|
||||
+ // Some Windows specific directories to block, basically all apps, the
|
||||
+ // operating system itself, as well as configuration data for apps.
|
||||
+ {base::DIR_PROGRAM_FILES, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_PROGRAM_FILESX86, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_PROGRAM_FILES6432, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_WINDOWS, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_ROAMING_APP_DATA, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_LOCAL_APP_DATA, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_COMMON_APP_DATA, nullptr, kBlockAllChildren},
|
||||
+ // Opening a file from an MTP device, such as a smartphone or a camera, is
|
||||
+ // implemented by Windows as opening a file in the temporary internet files
|
||||
+ // directory. To support that, allow opening files in that directory, but
|
||||
+ // not whole directories.
|
||||
+ {base::DIR_IE_INTERNET_CACHE, nullptr, kBlockNestedDirectories},
|
||||
+ #endif
|
||||
+ #if BUILDFLAG(IS_MAC)
|
||||
+ // Similar Mac specific blocks.
|
||||
+ {base::DIR_APP_DATA, nullptr, kBlockAllChildren},
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL("Library"), kBlockAllChildren},
|
||||
+ // Allow access to other cloud files, such as Google Drive.
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL("Library/CloudStorage"),
|
||||
+ kDontBlockChildren},
|
||||
+ // Allow the site to interact with data from its corresponding natively
|
||||
+ // installed (sandboxed) application. It would be nice to limit a site to
|
||||
+ // access only _its_ corresponding natively installed application,
|
||||
+ // but unfortunately there's no straightforward way to do that. See
|
||||
+ // https://crbug.com/984641#c22.
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL("Library/Containers"),
|
||||
+ kDontBlockChildren},
|
||||
+ // Allow access to iCloud files...
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL("Library/Mobile Documents"),
|
||||
+ kDontBlockChildren},
|
||||
+ // ... which may also appear at this directory.
|
||||
+ {base::DIR_HOME,
|
||||
+ FILE_PATH_LITERAL("Library/Mobile Documents/com~apple~CloudDocs"),
|
||||
+ kDontBlockChildren},
|
||||
+ #endif
|
||||
+ #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
||||
+ // On Linux also block access to devices via /dev.
|
||||
+ {kNoBasePathKey, FILE_PATH_LITERAL("/dev"), kBlockAllChildren},
|
||||
+ // And security sensitive data in /proc and /sys.
|
||||
+ {kNoBasePathKey, FILE_PATH_LITERAL("/proc"), kBlockAllChildren},
|
||||
+ {kNoBasePathKey, FILE_PATH_LITERAL("/sys"), kBlockAllChildren},
|
||||
+ // And system files in /boot and /etc.
|
||||
+ {kNoBasePathKey, FILE_PATH_LITERAL("/boot"), kBlockAllChildren},
|
||||
+ {kNoBasePathKey, FILE_PATH_LITERAL("/etc"), kBlockAllChildren},
|
||||
+ // And block all of ~/.config, matching the similar restrictions on mac
|
||||
+ // and windows.
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL(".config"), kBlockAllChildren},
|
||||
+ // Block ~/.dbus as well, just in case, although there probably isn't much a
|
||||
+ // website can do with access to that directory and its contents.
|
||||
+ {base::DIR_HOME, FILE_PATH_LITERAL(".dbus"), kBlockAllChildren},
|
||||
+ #endif
|
||||
+ // TODO(https://crbug.com/984641): Refine this list, for example add
|
||||
+ // XDG_CONFIG_HOME when it is not set ~/.config?
|
||||
+ };
|
||||
+
|
||||
protected:
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
@@ -350,7 +467,7 @@ class ChromeFileSystemAccessPermissionContext
|
||||
|
||||
void PermissionGrantDestroyed(PermissionGrantImpl* grant);
|
||||
|
||||
-#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
|
||||
+#if 0
|
||||
void OnContentAnalysisComplete(
|
||||
std::vector<PathInfo> entries,
|
||||
EntriesAllowedByEnterprisePolicyCallback callback,
|
||||
@@ -33,7 +33,6 @@ fix_assert_module_in_the_renderer_process.patch
|
||||
fix_add_trusted_space_and_trusted_lo_space_to_the_v8_heap.patch
|
||||
win_process_avoid_assert_after_spawning_store_app_4152.patch
|
||||
chore_remove_use_of_deprecated_kmaxlength.patch
|
||||
fix_missing_include_for_node_extern.patch
|
||||
feat_optionally_prevent_calling_v8_enablewebassemblytraphandler.patch
|
||||
build_only_create_cppgc_heap_on_non-32_bit_platforms.patch
|
||||
fix_-wshadow_error_in_uvwasi_c.patch
|
||||
|
||||
@@ -87,10 +87,18 @@ index 895ff3a5948add3513700ecc2f32fce4c2fbe4eb..3182a5e4aad2ba0be2b6769edb696b81
|
||||
|
||||
MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback(
|
||||
diff --git a/src/module_wrap.h b/src/module_wrap.h
|
||||
index e17048357feca2419087621ed280de30882a90bc..061117dc3182d63e35d7e99ffd95801f96356322 100644
|
||||
index e17048357feca2419087621ed280de30882a90bc..63682be31ce00a3bf7b9be909cac4b7f9ec02a8e 100644
|
||||
--- a/src/module_wrap.h
|
||||
+++ b/src/module_wrap.h
|
||||
@@ -31,7 +31,14 @@ enum HostDefinedOptions : int {
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "base_object.h"
|
||||
+#include "node.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
@@ -31,7 +32,14 @@ enum HostDefinedOptions : int {
|
||||
kLength = 9,
|
||||
};
|
||||
|
||||
@@ -106,7 +114,7 @@ index e17048357feca2419087621ed280de30882a90bc..061117dc3182d63e35d7e99ffd95801f
|
||||
public:
|
||||
enum InternalFields {
|
||||
kModuleSlot = BaseObject::kInternalFieldCount,
|
||||
@@ -68,6 +75,8 @@ class ModuleWrap : public BaseObject {
|
||||
@@ -68,6 +76,8 @@ class ModuleWrap : public BaseObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -115,7 +123,7 @@ index e17048357feca2419087621ed280de30882a90bc..061117dc3182d63e35d7e99ffd95801f
|
||||
private:
|
||||
ModuleWrap(Realm* realm,
|
||||
v8::Local<v8::Object> object,
|
||||
@@ -102,7 +111,6 @@ class ModuleWrap : public BaseObject {
|
||||
@@ -102,7 +112,6 @@ class ModuleWrap : public BaseObject {
|
||||
v8::Local<v8::String> specifier,
|
||||
v8::Local<v8::FixedArray> import_attributes,
|
||||
v8::Local<v8::Module> referrer);
|
||||
|
||||
@@ -10,7 +10,7 @@ already been called.
|
||||
This should be upstreamed.
|
||||
|
||||
diff --git a/src/node.cc b/src/node.cc
|
||||
index 524f80ee69ee5248e045a2b61faf5610c9ba4285..971668792eabe5be299849b5a3fd8a2790a2210a 100644
|
||||
index 1d77a8b31cb0bfbeeeac594b6e1ac7dd303c902d..dadddf33527beebfcde12214da4084badbd27af1 100644
|
||||
--- a/src/node.cc
|
||||
+++ b/src/node.cc
|
||||
@@ -605,6 +605,7 @@ static void PlatformInit(ProcessInitializationFlags::Flags flags) {
|
||||
|
||||
@@ -373,6 +373,36 @@ index 5734d8fdc5505e1586f571c19b840bd56e9c9f1f..3034b114e081e2b32dd5b71653927a41
|
||||
}
|
||||
} // namespace
|
||||
|
||||
diff --git a/src/node.cc b/src/node.cc
|
||||
index 524f80ee69ee5248e045a2b61faf5610c9ba4285..1d77a8b31cb0bfbeeeac594b6e1ac7dd303c902d 100644
|
||||
--- a/src/node.cc
|
||||
+++ b/src/node.cc
|
||||
@@ -1027,7 +1027,8 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
|
||||
}
|
||||
|
||||
if (!(flags & ProcessInitializationFlags::kNoInitOpenSSL)) {
|
||||
-#if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL)
|
||||
+#if HAVE_OPENSSL
|
||||
+#if !defined(OPENSSL_IS_BORINGSSL)
|
||||
auto GetOpenSSLErrorString = []() -> std::string {
|
||||
std::string ret;
|
||||
ERR_print_errors_cb(
|
||||
@@ -1127,13 +1128,13 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
|
||||
CHECK(crypto::CSPRNG(buffer, length).is_ok());
|
||||
return true;
|
||||
});
|
||||
-
|
||||
+#endif // !defined(OPENSSL_IS_BORINGSSL)
|
||||
{
|
||||
std::string extra_ca_certs;
|
||||
if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs))
|
||||
crypto::UseExtraCaCerts(extra_ca_certs);
|
||||
}
|
||||
-#endif // HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL)
|
||||
+#endif // HAVE_OPENSSL
|
||||
}
|
||||
|
||||
if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) {
|
||||
diff --git a/src/node_metadata.cc b/src/node_metadata.cc
|
||||
index b88cfb98e75aca426224e19376b3ff4c23b92e53..b66f4e2b5cbd8f36af42f82a8921207302360e39 100644
|
||||
--- a/src/node_metadata.cc
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Wed, 15 Nov 2023 12:25:39 +0100
|
||||
Subject: fix: missing include for NODE_EXTERN
|
||||
|
||||
At some point it seems that node.h was removed from the include chain,
|
||||
causing the following error:
|
||||
|
||||
../../third_party/electron_node/src/module_wrap.h:33:1: error: unknown type name 'NODE_EXTERN'
|
||||
33 | NODE_EXTERN v8::MaybeLocal<v8::Promise> ImportModuleDynamically(
|
||||
| ^
|
||||
|
||||
This should be upstreamed.
|
||||
|
||||
diff --git a/src/module_wrap.h b/src/module_wrap.h
|
||||
index 061117dc3182d63e35d7e99ffd95801f96356322..63682be31ce00a3bf7b9be909cac4b7f9ec02a8e 100644
|
||||
--- a/src/module_wrap.h
|
||||
+++ b/src/module_wrap.h
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "base_object.h"
|
||||
+#include "node.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
@@ -62,8 +62,6 @@
|
||||
"parallel/test-snapshot-worker",
|
||||
"parallel/test-strace-openat-openssl",
|
||||
"parallel/test-tls-alpn-server-client",
|
||||
"parallel/test-tls-cert-chains-concat",
|
||||
"parallel/test-tls-cert-chains-in-ca",
|
||||
"parallel/test-tls-cli-max-version-1.2",
|
||||
"parallel/test-tls-cli-max-version-1.3",
|
||||
"parallel/test-tls-cli-min-version-1.1",
|
||||
@@ -77,8 +75,6 @@
|
||||
"parallel/test-tls-cnnic-whitelist",
|
||||
"parallel/test-tls-disable-renegotiation",
|
||||
"parallel/test-tls-empty-sni-context",
|
||||
"parallel/test-tls-env-bad-extra-ca",
|
||||
"parallel/test-tls-env-extra-ca",
|
||||
"parallel/test-tls-finished",
|
||||
"parallel/test-tls-generic-stream",
|
||||
"parallel/test-tls-getcipher",
|
||||
|
||||
@@ -3,9 +3,18 @@ if (!process.env.CI) require('dotenv-safe').load();
|
||||
const assert = require('node:assert');
|
||||
const got = require('got');
|
||||
|
||||
const { Octokit } = require('@octokit/rest');
|
||||
const octokit = new Octokit({
|
||||
auth: process.env.ELECTRON_GITHUB_TOKEN
|
||||
});
|
||||
|
||||
const BUILD_APPVEYOR_URL = 'https://ci.appveyor.com/api/builds';
|
||||
const CIRCLECI_PIPELINE_URL = 'https://circleci.com/api/v2/project/gh/electron/electron/pipeline';
|
||||
const GH_ACTIONS_PIPELINE_URL = 'https://github.com/electron/electron/actions';
|
||||
const GH_ACTIONS_API_URL = '/repos/electron/electron/actions';
|
||||
|
||||
const CIRCLECI_WAIT_TIME = process.env.CIRCLECI_WAIT_TIME || 30000;
|
||||
const GH_ACTIONS_WAIT_TIME = process.env.GH_ACTIONS_WAIT_TIME || 30000;
|
||||
|
||||
const appVeyorJobs = {
|
||||
'electron-x64': 'electron-x64-release',
|
||||
@@ -23,6 +32,14 @@ const circleCIPublishIndividualArches = {
|
||||
'linux-publish': ['arm', 'arm64', 'x64']
|
||||
};
|
||||
|
||||
const ghActionsPublishWorkflows = [
|
||||
'macos-publish'
|
||||
];
|
||||
|
||||
const ghActionsPublishIndividualArches = {
|
||||
'macos-publish': ['osx-x64', 'mas-x64', 'osx-arm64', 'mas-arm64']
|
||||
};
|
||||
|
||||
let jobRequestedCount = 0;
|
||||
|
||||
async function makeRequest ({ auth, username, password, url, headers, body, method }) {
|
||||
@@ -53,6 +70,65 @@ async function makeRequest ({ auth, username, password, url, headers, body, meth
|
||||
return JSON.parse(response.body);
|
||||
}
|
||||
|
||||
async function githubActionsCall (targetBranch, workflowName, options) {
|
||||
console.log(`Triggering GitHub Actions to run build job: ${workflowName} on branch: ${targetBranch} with release flag.`);
|
||||
const buildRequest = {
|
||||
branch: targetBranch,
|
||||
parameters: {}
|
||||
};
|
||||
if (options.ghRelease) {
|
||||
buildRequest.parameters['upload-to-storage'] = '0';
|
||||
} else {
|
||||
buildRequest.parameters['upload-to-storage'] = '1';
|
||||
}
|
||||
buildRequest.parameters[`run-${workflowName}`] = true;
|
||||
if (options.arch) {
|
||||
const validArches = ghActionsPublishIndividualArches[workflowName];
|
||||
assert(validArches.includes(options.arch), `Unknown GitHub Actions architecture "${options.arch}". Valid values are ${JSON.stringify(validArches)}`);
|
||||
buildRequest.parameters['macos-publish-arch-limit'] = options.arch;
|
||||
}
|
||||
|
||||
jobRequestedCount++;
|
||||
try {
|
||||
const commits = await octokit.repos.listCommits({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
sha: targetBranch,
|
||||
per_page: 5
|
||||
});
|
||||
if (!commits.data.length) {
|
||||
console.error('Could not fetch most recent commits for GitHub Actions, returning early');
|
||||
}
|
||||
|
||||
await octokit.request(`POST ${GH_ACTIONS_API_URL}/workflows/${workflowName}.yml/dispatches`, {
|
||||
ref: buildRequest.branch,
|
||||
inputs: {
|
||||
...buildRequest.parameters
|
||||
},
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
}
|
||||
});
|
||||
|
||||
const runNumber = await getGitHubActionsRun(workflowName, commits.data[0].sha);
|
||||
if (runNumber === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`GitHub Actions release build pipeline ${runNumber} for ${workflowName} triggered.`);
|
||||
const runUrl = `${GH_ACTIONS_PIPELINE_URL}/runs/${runNumber}`;
|
||||
|
||||
if (options.runningPublishWorkflows) {
|
||||
console.log(`GitHub Actions release workflow request for ${workflowName} successful. Check ${runUrl} for status.`);
|
||||
} else {
|
||||
console.log(`GitHub Actions release build workflow running at ${GH_ACTIONS_PIPELINE_URL}/runs/${runNumber} for ${workflowName}.`);
|
||||
console.log(`GitHub Actions release build request for ${workflowName} successful. Check ${runUrl} for status.`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error calling GitHub Actions: ', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function circleCIcall (targetBranch, workflowName, options) {
|
||||
console.log(`Triggering CircleCI to run build job: ${workflowName} on branch: ${targetBranch} with release flag.`);
|
||||
const buildRequest = {
|
||||
@@ -167,6 +243,59 @@ async function getCircleCIJobNumber (workflowId) {
|
||||
return jobNumber;
|
||||
}
|
||||
|
||||
async function getGitHubActionsRun (workflowId, headCommit) {
|
||||
let runNumber = 0;
|
||||
let actionRun;
|
||||
while (runNumber === 0) {
|
||||
const actionsRuns = await octokit.request(`GET ${GH_ACTIONS_API_URL}/workflows/${workflowId}.yml/runs`, {
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
}
|
||||
});
|
||||
if (!actionsRuns.data.workflow_runs.length) {
|
||||
console.log(`No current workflow_runs found for ${workflowId}, response was: ${actionsRuns.data.workflow_runs}`);
|
||||
runNumber = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const run of actionsRuns.data.workflow_runs) {
|
||||
if (run.head_sha === headCommit) {
|
||||
console.log(`GitHub Actions run ${run.html_url} found for ${headCommit}, waiting on status.`);
|
||||
actionRun = run;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (actionRun) {
|
||||
switch (actionRun.status) {
|
||||
case 'in_progress':
|
||||
case 'pending':
|
||||
case 'queued':
|
||||
case 'requested':
|
||||
case 'waiting': {
|
||||
if (actionRun.id && !isNaN(actionRun.id)) {
|
||||
console.log(`GitHub Actions run ${actionRun.status} for ${actionRun.html_url}.`);
|
||||
runNumber = actionRun.id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'action_required':
|
||||
case 'cancelled':
|
||||
case 'failure':
|
||||
case 'skipped':
|
||||
case 'timed_out':
|
||||
case 'failed': {
|
||||
console.log(`Error workflow run returned a status of ${actionRun.status} for ${actionRun.html_url}`);
|
||||
runNumber = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, GH_ACTIONS_WAIT_TIME));
|
||||
}
|
||||
}
|
||||
return runNumber;
|
||||
}
|
||||
|
||||
async function circleCIRequest (url, method, requestBody) {
|
||||
const requestOpts = {
|
||||
username: process.env.CIRCLE_TOKEN,
|
||||
@@ -194,18 +323,6 @@ async function circleCIRequest (url, method, requestBody) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildAppVeyor (targetBranch, options) {
|
||||
const validJobs = Object.keys(appVeyorJobs);
|
||||
if (options.job) {
|
||||
assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}. Valid values are: ${validJobs}.`);
|
||||
callAppVeyor(targetBranch, options.job, options);
|
||||
} else {
|
||||
for (const job of validJobs) {
|
||||
callAppVeyor(targetBranch, job, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function callAppVeyor (targetBranch, job, options) {
|
||||
console.log(`Triggering AppVeyor to run build job: ${job} on branch: ${targetBranch} with release flag.`);
|
||||
const environmentVariables = {
|
||||
@@ -252,6 +369,18 @@ async function callAppVeyor (targetBranch, job, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildAppVeyor (targetBranch, options) {
|
||||
const validJobs = Object.keys(appVeyorJobs);
|
||||
if (options.job) {
|
||||
assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}. Valid values are: ${validJobs}.`);
|
||||
callAppVeyor(targetBranch, options.job, options);
|
||||
} else {
|
||||
for (const job of validJobs) {
|
||||
callAppVeyor(targetBranch, job, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildCircleCI (targetBranch, options) {
|
||||
if (options.job) {
|
||||
assert(circleCIPublishWorkflows.includes(options.job), `Unknown CircleCI workflow name: ${options.job}. Valid values are: ${circleCIPublishWorkflows}.`);
|
||||
@@ -265,6 +394,19 @@ function buildCircleCI (targetBranch, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildGHActions (targetBranch, options) {
|
||||
if (options.job) {
|
||||
assert(ghActionsPublishWorkflows.includes(options.job), `Unknown GitHub Actions workflow name: ${options.job}. Valid values are: ${ghActionsPublishWorkflows}.`);
|
||||
githubActionsCall(targetBranch, options.job, options);
|
||||
} else {
|
||||
assert(!options.arch, 'Cannot provide a single architecture while building all workflows, please specify a single workflow via --workflow');
|
||||
options.runningPublishWorkflows = true;
|
||||
for (const job of ghActionsPublishWorkflows) {
|
||||
githubActionsCall(targetBranch, job, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runRelease (targetBranch, options) {
|
||||
if (options.ci) {
|
||||
switch (options.ci) {
|
||||
@@ -272,6 +414,10 @@ function runRelease (targetBranch, options) {
|
||||
buildCircleCI(targetBranch, options);
|
||||
break;
|
||||
}
|
||||
case 'GitHubActions': {
|
||||
buildGHActions(targetBranch, options);
|
||||
break;
|
||||
}
|
||||
case 'AppVeyor': {
|
||||
buildAppVeyor(targetBranch, options);
|
||||
break;
|
||||
@@ -284,6 +430,8 @@ function runRelease (targetBranch, options) {
|
||||
} else {
|
||||
buildCircleCI(targetBranch, options);
|
||||
buildAppVeyor(targetBranch, options);
|
||||
// TODO(vertedinde): Enable GH Actions in defaults when ready
|
||||
// buildGHActions(targetBranch, options);
|
||||
}
|
||||
console.log(`${jobRequestedCount} jobs were requested.`);
|
||||
}
|
||||
@@ -297,7 +445,7 @@ if (require.main === module) {
|
||||
const targetBranch = args._[0];
|
||||
if (args._.length < 1) {
|
||||
console.log(`Trigger CI to build release builds of electron.
|
||||
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=CircleCI|AppVeyor]
|
||||
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=CircleCI|AppVeyor|GitHubActions]
|
||||
[--ghRelease] [--circleBuildNum=xxx] [--appveyorJobId=xxx] [--commit=sha] TARGET_BRANCH
|
||||
`);
|
||||
process.exit(0);
|
||||
|
||||
@@ -127,6 +127,7 @@ int NodeMain(int argc, char* argv[]) {
|
||||
bool node_options_enabled = electron::fuses::IsNodeOptionsEnabled();
|
||||
if (!node_options_enabled) {
|
||||
os_env->UnSetVar("NODE_OPTIONS");
|
||||
os_env->UnSetVar("NODE_EXTRA_CA_CERTS");
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
|
||||
@@ -77,6 +77,7 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
|
||||
WebContentsView::Create(isolate, web_preferences);
|
||||
DCHECK(web_contents_view.get());
|
||||
window_->AddDraggableRegionProvider(web_contents_view.get());
|
||||
web_contents_view_.Reset(isolate, web_contents_view.ToV8());
|
||||
|
||||
// Save a reference of the WebContents.
|
||||
gin::Handle<WebContents> web_contents =
|
||||
@@ -92,7 +93,14 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
|
||||
InitWithArgs(args);
|
||||
|
||||
// Install the content view after BaseWindow's JS code is initialized.
|
||||
SetContentView(gin::CreateHandle<View>(isolate, web_contents_view.get()));
|
||||
// The WebContentsView is added a sibling of BaseWindow's contentView (before
|
||||
// it in the paint order) so that any views added to BrowserWindow's
|
||||
// contentView will be painted on top of the BrowserWindow's WebContentsView.
|
||||
// See https://github.com/electron/electron/pull/41256.
|
||||
// Note that |GetContentsView|, confusingly, does not refer to the same thing
|
||||
// as |BaseWindow::GetContentView|.
|
||||
window()->GetContentsView()->AddChildViewAt(web_contents_view->view(), 0);
|
||||
window()->GetContentsView()->DeprecatedLayoutImmediately();
|
||||
|
||||
// Init window after everything has been setup.
|
||||
window()->InitFromOptions(options);
|
||||
|
||||
@@ -95,6 +95,7 @@ class BrowserWindow : public BaseWindow,
|
||||
base::CancelableRepeatingClosure window_unresponsive_closure_;
|
||||
|
||||
v8::Global<v8::Value> web_contents_;
|
||||
v8::Global<v8::Value> web_contents_view_;
|
||||
base::WeakPtr<api::WebContents> api_web_contents_;
|
||||
|
||||
base::WeakPtrFactory<BrowserWindow> weak_factory_{this};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/fixed_flat_map.h"
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
@@ -33,12 +34,14 @@
|
||||
#include "content/browser/code_cache/generated_code_cache_context.h" // nogncheck
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/browsing_data_filter_builder.h"
|
||||
#include "content/public/browser/browsing_data_remover.h"
|
||||
#include "content/public/browser/download_item_utils.h"
|
||||
#include "content/public/browser/download_manager_delegate.h"
|
||||
#include "content/public/browser/network_service_instance.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "gin/arguments.h"
|
||||
#include "gin/converter.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
|
||||
#include "net/base/completion_repeating_callback.h"
|
||||
@@ -86,6 +89,7 @@
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
@@ -106,6 +110,8 @@
|
||||
#endif
|
||||
|
||||
using content::BrowserThread;
|
||||
using content::BrowsingDataFilterBuilder;
|
||||
using content::BrowsingDataRemover;
|
||||
using content::StoragePartition;
|
||||
|
||||
namespace {
|
||||
@@ -117,25 +123,23 @@ struct ClearStorageDataOptions {
|
||||
};
|
||||
|
||||
uint32_t GetStorageMask(const std::vector<std::string>& storage_types) {
|
||||
static constexpr auto Lookup =
|
||||
base::MakeFixedFlatMap<std::string_view, uint32_t>(
|
||||
{{"cookies", StoragePartition::REMOVE_DATA_MASK_COOKIES},
|
||||
{"filesystem", StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS},
|
||||
{"indexdb", StoragePartition::REMOVE_DATA_MASK_INDEXEDDB},
|
||||
{"localstorage", StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE},
|
||||
{"shadercache", StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE},
|
||||
{"websql", StoragePartition::REMOVE_DATA_MASK_WEBSQL},
|
||||
{"serviceworkers",
|
||||
StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS},
|
||||
{"cachestorage", StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE}});
|
||||
|
||||
uint32_t storage_mask = 0;
|
||||
for (const auto& it : storage_types) {
|
||||
auto type = base::ToLowerASCII(it);
|
||||
if (type == "cookies")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
|
||||
else if (type == "filesystem")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
|
||||
else if (type == "indexdb")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
|
||||
else if (type == "localstorage")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
|
||||
else if (type == "shadercache")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
|
||||
else if (type == "websql")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
|
||||
else if (type == "serviceworkers")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS;
|
||||
else if (type == "cachestorage")
|
||||
storage_mask |= StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE;
|
||||
if (Lookup.contains(type))
|
||||
storage_mask |= Lookup.at(type);
|
||||
}
|
||||
return storage_mask;
|
||||
}
|
||||
@@ -152,38 +156,202 @@ uint32_t GetQuotaMask(const std::vector<std::string>& quota_types) {
|
||||
return quota_mask;
|
||||
}
|
||||
|
||||
constexpr content::BrowsingDataRemover::DataType kClearDataTypeAll = ~0ULL;
|
||||
constexpr content::BrowsingDataRemover::OriginType kClearOriginTypeAll =
|
||||
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
|
||||
content::BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
|
||||
constexpr BrowsingDataRemover::DataType kClearDataTypeAll = ~0ULL;
|
||||
constexpr BrowsingDataRemover::OriginType kClearOriginTypeAll =
|
||||
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
|
||||
BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
|
||||
|
||||
// Observes the BrowsingDataRemover that backs the `clearData` method and
|
||||
// fulfills that API's promise once it's done. This type manages its own
|
||||
// lifetime, deleting itself once it's done.
|
||||
class ClearDataObserver : public content::BrowsingDataRemover::Observer {
|
||||
public:
|
||||
ClearDataObserver(gin_helper::Promise<void> promise,
|
||||
content::BrowsingDataRemover* remover)
|
||||
: promise_(std::move(promise)) {
|
||||
observation_.Observe(remover);
|
||||
}
|
||||
constexpr auto kDataTypeLookup =
|
||||
base::MakeFixedFlatMap<std::string_view, BrowsingDataRemover::DataType>({
|
||||
{"backgroundFetch", BrowsingDataRemover::DATA_TYPE_BACKGROUND_FETCH},
|
||||
{"cache", BrowsingDataRemover::DATA_TYPE_CACHE |
|
||||
BrowsingDataRemover::DATA_TYPE_CACHE_STORAGE},
|
||||
{"cookies", BrowsingDataRemover::DATA_TYPE_COOKIES},
|
||||
{"downloads", BrowsingDataRemover::DATA_TYPE_DOWNLOADS},
|
||||
{"fileSystems", BrowsingDataRemover::DATA_TYPE_FILE_SYSTEMS},
|
||||
{"indexedDB", BrowsingDataRemover::DATA_TYPE_INDEXED_DB},
|
||||
{"localStorage", BrowsingDataRemover::DATA_TYPE_LOCAL_STORAGE},
|
||||
{"serviceWorkers", BrowsingDataRemover::DATA_TYPE_SERVICE_WORKERS},
|
||||
{"webSQL", BrowsingDataRemover::DATA_TYPE_WEB_SQL},
|
||||
});
|
||||
|
||||
void OnBrowsingDataRemoverDone(
|
||||
content::BrowsingDataRemover::DataType failed_data_types) override {
|
||||
if (failed_data_types == 0ULL) {
|
||||
promise_.Resolve();
|
||||
} else {
|
||||
promise_.RejectWithErrorMessage(base::StringPrintf(
|
||||
"Failed to clear browsing data (%" PRIu64 ")", failed_data_types));
|
||||
BrowsingDataRemover::DataType GetDataTypeMask(
|
||||
const std::vector<std::string>& data_types) {
|
||||
BrowsingDataRemover::DataType mask = 0u;
|
||||
for (const auto& type : data_types) {
|
||||
if (kDataTypeLookup.contains(type)) {
|
||||
mask |= kDataTypeLookup.at(type);
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetDataTypesFromMask(
|
||||
BrowsingDataRemover::DataType mask) {
|
||||
std::vector<std::string> results;
|
||||
for (const auto [type, flag] : kDataTypeLookup) {
|
||||
if (mask & flag) {
|
||||
results.emplace_back(type);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// Represents a task to clear browsing data for the `clearData` API method.
|
||||
//
|
||||
// This type manages its own lifetime, deleting itself once the task finishes
|
||||
// completely.
|
||||
class ClearDataTask {
|
||||
public:
|
||||
// Starts running a task. This function will return before the task is
|
||||
// finished, but will resolve or reject the |promise| when it finishes.
|
||||
static void Run(
|
||||
BrowsingDataRemover* remover,
|
||||
gin_helper::Promise<void> promise,
|
||||
BrowsingDataRemover::DataType data_type_mask,
|
||||
std::vector<url::Origin> origins,
|
||||
BrowsingDataFilterBuilder::Mode filter_mode,
|
||||
BrowsingDataFilterBuilder::OriginMatchingMode origin_matching_mode) {
|
||||
std::shared_ptr<ClearDataTask> task(new ClearDataTask(std::move(promise)));
|
||||
|
||||
// This method counts as an operation. This is important so we can call
|
||||
// `OnOperationFinished` at the end of this method as a fallback if all the
|
||||
// other operations finished while this method was still executing
|
||||
task->operations_running_ = 1;
|
||||
|
||||
// Cookies are scoped more broadly than other types of data, so if we are
|
||||
// filtering then we need to do it at the registrable domain level
|
||||
if (!origins.empty() &&
|
||||
data_type_mask & BrowsingDataRemover::DATA_TYPE_COOKIES) {
|
||||
data_type_mask &= ~BrowsingDataRemover::DATA_TYPE_COOKIES;
|
||||
|
||||
auto cookies_filter_builder =
|
||||
BrowsingDataFilterBuilder::Create(filter_mode);
|
||||
|
||||
for (const url::Origin& origin : origins) {
|
||||
std::string domain = GetDomainAndRegistry(
|
||||
origin,
|
||||
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
|
||||
if (domain.empty()) {
|
||||
domain = origin.host();
|
||||
}
|
||||
cookies_filter_builder->AddRegisterableDomain(domain);
|
||||
}
|
||||
|
||||
StartOperation(task, remover, BrowsingDataRemover::DATA_TYPE_COOKIES,
|
||||
std::move(cookies_filter_builder));
|
||||
}
|
||||
|
||||
// If cookies aren't the only data type and weren't handled above, then we
|
||||
// can start an operation that is scoped to origins
|
||||
if (data_type_mask) {
|
||||
auto filter_builder =
|
||||
BrowsingDataFilterBuilder::Create(filter_mode, origin_matching_mode);
|
||||
|
||||
for (auto const& origin : origins) {
|
||||
filter_builder->AddOrigin(origin);
|
||||
}
|
||||
|
||||
StartOperation(task, remover, data_type_mask, std::move(filter_builder));
|
||||
}
|
||||
|
||||
// This static method counts as an operation.
|
||||
task->OnOperationFinished(std::nullopt);
|
||||
}
|
||||
|
||||
private:
|
||||
// An individiual |content::BrowsingDataRemover::Remove...| operation as part
|
||||
// of a full |ClearDataTask|. This class manages its own lifetime, cleaning
|
||||
// itself up after the operation completes and notifies the task of the
|
||||
// result.
|
||||
class ClearDataOperation : public BrowsingDataRemover::Observer {
|
||||
public:
|
||||
static void Run(std::shared_ptr<ClearDataTask> task,
|
||||
BrowsingDataRemover* remover,
|
||||
BrowsingDataRemover::DataType data_type_mask,
|
||||
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
|
||||
auto* operation = new ClearDataOperation(task, remover);
|
||||
|
||||
remover->RemoveWithFilterAndReply(base::Time::Min(), base::Time::Max(),
|
||||
data_type_mask, kClearOriginTypeAll,
|
||||
std::move(filter_builder), operation);
|
||||
}
|
||||
|
||||
// BrowsingDataRemover::Observer:
|
||||
void OnBrowsingDataRemoverDone(
|
||||
BrowsingDataRemover::DataType failed_data_types) override {
|
||||
task_->OnOperationFinished(failed_data_types);
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
ClearDataOperation(std::shared_ptr<ClearDataTask> task,
|
||||
BrowsingDataRemover* remover)
|
||||
: task_(task) {
|
||||
observation_.Observe(remover);
|
||||
}
|
||||
|
||||
std::shared_ptr<ClearDataTask> task_;
|
||||
base::ScopedObservation<BrowsingDataRemover, BrowsingDataRemover::Observer>
|
||||
observation_{this};
|
||||
};
|
||||
|
||||
explicit ClearDataTask(gin_helper::Promise<void> promise)
|
||||
: promise_(std::move(promise)) {}
|
||||
|
||||
static void StartOperation(
|
||||
std::shared_ptr<ClearDataTask> task,
|
||||
BrowsingDataRemover* remover,
|
||||
BrowsingDataRemover::DataType data_type_mask,
|
||||
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
|
||||
// Track this operation
|
||||
task->operations_running_ += 1;
|
||||
|
||||
ClearDataOperation::Run(task, remover, data_type_mask,
|
||||
std::move(filter_builder));
|
||||
}
|
||||
|
||||
void OnOperationFinished(
|
||||
std::optional<BrowsingDataRemover::DataType> failed_data_types) {
|
||||
DCHECK_GT(operations_running_, 0);
|
||||
operations_running_ -= 1;
|
||||
|
||||
if (failed_data_types.has_value()) {
|
||||
failed_data_types_ |= failed_data_types.value();
|
||||
}
|
||||
|
||||
// If this is the last operation, then the task is finished
|
||||
if (operations_running_ == 0) {
|
||||
OnTaskFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void OnTaskFinished() {
|
||||
if (failed_data_types_ == 0ULL) {
|
||||
promise_.Resolve();
|
||||
} else {
|
||||
v8::Isolate* isolate = promise_.isolate();
|
||||
|
||||
v8::Local<v8::Value> failed_data_types_array =
|
||||
gin::ConvertToV8(isolate, GetDataTypesFromMask(failed_data_types_));
|
||||
|
||||
// Create a rich error object with extra detail about what data types
|
||||
// failed
|
||||
auto error = v8::Exception::Error(
|
||||
gin::StringToV8(isolate, "Failed to clear data"));
|
||||
error.As<v8::Object>()
|
||||
->Set(promise_.GetContext(),
|
||||
gin::StringToV8(isolate, "failedDataTypes"),
|
||||
failed_data_types_array)
|
||||
.Check();
|
||||
|
||||
promise_.Reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
int operations_running_ = 0;
|
||||
BrowsingDataRemover::DataType failed_data_types_ = 0ULL;
|
||||
gin_helper::Promise<void> promise_;
|
||||
base::ScopedObservation<content::BrowsingDataRemover,
|
||||
content::BrowsingDataRemover::Observer>
|
||||
observation_{this};
|
||||
};
|
||||
|
||||
base::Value::Dict createProxyConfig(ProxyPrefs::ProxyMode proxy_mode,
|
||||
@@ -1138,17 +1306,85 @@ v8::Local<v8::Promise> Session::ClearCodeCaches(
|
||||
return handle;
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> Session::ClearData(gin::Arguments* args) {
|
||||
v8::Local<v8::Value> Session::ClearData(gin_helper::ErrorThrower thrower,
|
||||
gin::Arguments* args) {
|
||||
auto* isolate = JavascriptEnvironment::GetIsolate();
|
||||
|
||||
BrowsingDataRemover::DataType data_type_mask = kClearDataTypeAll;
|
||||
std::vector<url::Origin> origins;
|
||||
BrowsingDataFilterBuilder::OriginMatchingMode origin_matching_mode =
|
||||
BrowsingDataFilterBuilder::OriginMatchingMode::kThirdPartiesIncluded;
|
||||
BrowsingDataFilterBuilder::Mode filter_mode =
|
||||
BrowsingDataFilterBuilder::Mode::kPreserve;
|
||||
|
||||
if (gin_helper::Dictionary options; args->GetNext(&options)) {
|
||||
if (std::vector<std::string> data_types;
|
||||
options.Get("dataTypes", &data_types)) {
|
||||
data_type_mask = GetDataTypeMask(data_types);
|
||||
}
|
||||
|
||||
if (bool avoid_closing_connections;
|
||||
options.Get("avoidClosingConnections", &avoid_closing_connections) &&
|
||||
avoid_closing_connections) {
|
||||
data_type_mask |=
|
||||
BrowsingDataRemover::DATA_TYPE_AVOID_CLOSING_CONNECTIONS;
|
||||
}
|
||||
|
||||
std::vector<GURL> origin_urls;
|
||||
{
|
||||
bool has_origins_key = options.Get("origins", &origin_urls);
|
||||
std::vector<GURL> exclude_origin_urls;
|
||||
bool has_exclude_origins_key =
|
||||
options.Get("excludeOrigins", &exclude_origin_urls);
|
||||
|
||||
if (has_origins_key && has_exclude_origins_key) {
|
||||
thrower.ThrowError(
|
||||
"Cannot provide both 'origins' and 'excludeOrigins'");
|
||||
return v8::Undefined(isolate);
|
||||
}
|
||||
|
||||
if (has_origins_key) {
|
||||
filter_mode = BrowsingDataFilterBuilder::Mode::kDelete;
|
||||
} else if (has_exclude_origins_key) {
|
||||
origin_urls = std::move(exclude_origin_urls);
|
||||
}
|
||||
}
|
||||
|
||||
if (!origin_urls.empty()) {
|
||||
origins.reserve(origin_urls.size());
|
||||
for (const GURL& origin_url : origin_urls) {
|
||||
auto origin = url::Origin::Create(origin_url);
|
||||
|
||||
// Opaque origins cannot be used with this API
|
||||
if (origin.opaque()) {
|
||||
thrower.ThrowError(
|
||||
base::StringPrintf("Invalid origin: '%s'",
|
||||
origin_url.possibly_invalid_spec().c_str()));
|
||||
return v8::Undefined(isolate);
|
||||
}
|
||||
|
||||
origins.push_back(std::move(origin));
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string origin_matching_mode_string;
|
||||
options.Get("originMatchingMode", &origin_matching_mode_string)) {
|
||||
if (origin_matching_mode_string == "third-parties-included") {
|
||||
origin_matching_mode = BrowsingDataFilterBuilder::OriginMatchingMode::
|
||||
kThirdPartiesIncluded;
|
||||
} else if (origin_matching_mode_string == "origin-in-all-contexts") {
|
||||
origin_matching_mode =
|
||||
BrowsingDataFilterBuilder::OriginMatchingMode::kOriginInAllContexts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gin_helper::Promise<void> promise(isolate);
|
||||
v8::Local<v8::Promise> promise_handle = promise.GetHandle();
|
||||
|
||||
content::BrowsingDataRemover* remover =
|
||||
browser_context_->GetBrowsingDataRemover();
|
||||
|
||||
auto* observer = new ClearDataObserver(std::move(promise), remover);
|
||||
remover->RemoveAndReply(base::Time::Min(), base::Time::Max(),
|
||||
kClearDataTypeAll, kClearOriginTypeAll, observer);
|
||||
BrowsingDataRemover* remover = browser_context_->GetBrowsingDataRemover();
|
||||
ClearDataTask::Run(remover, std::move(promise), data_type_mask,
|
||||
std::move(origins), filter_mode, origin_matching_mode);
|
||||
|
||||
return promise_handle;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,8 @@ class Session : public gin::Wrappable<Session>,
|
||||
v8::Local<v8::Value> GetPath(v8::Isolate* isolate);
|
||||
void SetCodeCachePath(gin::Arguments* args);
|
||||
v8::Local<v8::Promise> ClearCodeCaches(const gin_helper::Dictionary& options);
|
||||
v8::Local<v8::Promise> ClearData(gin::Arguments* args);
|
||||
v8::Local<v8::Value> ClearData(gin_helper::ErrorThrower thrower,
|
||||
gin::Arguments* args);
|
||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||
base::Value GetSpellCheckerLanguages();
|
||||
void SetSpellCheckerLanguages(gin_helper::ErrorThrower thrower,
|
||||
|
||||
@@ -42,6 +42,7 @@ void FrameSubscriber::AttachToHost(content::RenderWidgetHost* host) {
|
||||
|
||||
// Create and configure the video capturer.
|
||||
gfx::Size size = GetRenderViewSize();
|
||||
DCHECK(!size.IsEmpty());
|
||||
video_capturer_ = host_->GetView()->CreateVideoCapturer();
|
||||
video_capturer_->SetResolutionConstraints(size, size, true);
|
||||
video_capturer_->SetAutoThrottlingEnabled(false);
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "shell/browser/electron_browser_main_parts.h"
|
||||
#include "shell/browser/electron_download_manager_delegate.h"
|
||||
#include "shell/browser/electron_permission_manager.h"
|
||||
#include "shell/browser/file_system_access/file_system_access_permission_context_factory.h"
|
||||
#include "shell/browser/net/resolve_proxy_helper.h"
|
||||
#include "shell/browser/protocol_registry.h"
|
||||
#include "shell/browser/special_storage_policy.h"
|
||||
@@ -533,6 +534,11 @@ ElectronBrowserContext::GetReduceAcceptLanguageControllerDelegate() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
content::FileSystemAccessPermissionContext*
|
||||
ElectronBrowserContext::GetFileSystemAccessPermissionContext() {
|
||||
return FileSystemAccessPermissionContextFactory::GetForBrowserContext(this);
|
||||
}
|
||||
|
||||
ResolveProxyHelper* ElectronBrowserContext::GetResolveProxyHelper() {
|
||||
if (!resolve_proxy_helper_) {
|
||||
resolve_proxy_helper_ = base::MakeRefCounted<ResolveProxyHelper>(
|
||||
|
||||
@@ -150,6 +150,8 @@ class ElectronBrowserContext : public content::BrowserContext {
|
||||
content::StorageNotificationService* GetStorageNotificationService() override;
|
||||
content::ReduceAcceptLanguageControllerDelegate*
|
||||
GetReduceAcceptLanguageControllerDelegate() override;
|
||||
content::FileSystemAccessPermissionContext*
|
||||
GetFileSystemAccessPermissionContext() override;
|
||||
|
||||
CookieChangeNotifier* cookie_change_notifier() const {
|
||||
return cookie_change_notifier_.get();
|
||||
|
||||
@@ -154,7 +154,7 @@ void ElectronManagementAPIDelegate::InstallOrLaunchReplacementWebApp(
|
||||
|
||||
void ElectronManagementAPIDelegate::EnableExtension(
|
||||
content::BrowserContext* context,
|
||||
const std::string& extension_id) const {
|
||||
const extensions::ExtensionId& extension_id) const {
|
||||
// const extensions::Extension* extension =
|
||||
// extensions::ExtensionRegistry::Get(context)->GetExtensionById(
|
||||
// extension_id, extensions::ExtensionRegistry::EVERYTHING);
|
||||
@@ -171,7 +171,7 @@ void ElectronManagementAPIDelegate::EnableExtension(
|
||||
void ElectronManagementAPIDelegate::DisableExtension(
|
||||
content::BrowserContext* context,
|
||||
const extensions::Extension* source_extension,
|
||||
const std::string& extension_id,
|
||||
const extensions::ExtensionId& extension_id,
|
||||
extensions::disable_reason::DisableReason disable_reason) const {
|
||||
// TODO(sentialx): we don't have ExtensionService
|
||||
// extensions::ExtensionSystem::Get(context)
|
||||
@@ -182,7 +182,7 @@ void ElectronManagementAPIDelegate::DisableExtension(
|
||||
|
||||
bool ElectronManagementAPIDelegate::UninstallExtension(
|
||||
content::BrowserContext* context,
|
||||
const std::string& transient_extension_id,
|
||||
const extensions::ExtensionId& transient_extension_id,
|
||||
extensions::UninstallReason reason,
|
||||
std::u16string* error) const {
|
||||
// TODO(sentialx): we don't have ExtensionService
|
||||
@@ -194,7 +194,7 @@ bool ElectronManagementAPIDelegate::UninstallExtension(
|
||||
|
||||
void ElectronManagementAPIDelegate::SetLaunchType(
|
||||
content::BrowserContext* context,
|
||||
const std::string& extension_id,
|
||||
const extensions::ExtensionId& extension_id,
|
||||
extensions::LaunchType launch_type) const {
|
||||
// TODO(sentialx)
|
||||
// extensions::SetLaunchType(context, extension_id, launch_type);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "base/task/cancelable_task_tracker.h"
|
||||
#include "extensions/browser/api/management/management_api_delegate.h"
|
||||
#include "extensions/common/extension_id.h"
|
||||
|
||||
class ElectronManagementAPIDelegate : public extensions::ManagementAPIDelegate {
|
||||
public:
|
||||
@@ -51,19 +52,20 @@ class ElectronManagementAPIDelegate : public extensions::ManagementAPIDelegate {
|
||||
const GURL& web_app_url,
|
||||
ManagementAPIDelegate::InstallOrLaunchWebAppCallback callback)
|
||||
const override;
|
||||
void EnableExtension(content::BrowserContext* context,
|
||||
const std::string& extension_id) const override;
|
||||
void EnableExtension(
|
||||
content::BrowserContext* context,
|
||||
const extensions::ExtensionId& extension_id) const override;
|
||||
void DisableExtension(
|
||||
content::BrowserContext* context,
|
||||
const extensions::Extension* source_extension,
|
||||
const std::string& extension_id,
|
||||
const extensions::ExtensionId& extension_id,
|
||||
extensions::disable_reason::DisableReason disable_reason) const override;
|
||||
bool UninstallExtension(content::BrowserContext* context,
|
||||
const std::string& transient_extension_id,
|
||||
const extensions::ExtensionId& transient_extension_id,
|
||||
extensions::UninstallReason reason,
|
||||
std::u16string* error) const override;
|
||||
void SetLaunchType(content::BrowserContext* context,
|
||||
const std::string& extension_id,
|
||||
const extensions::ExtensionId& extension_id,
|
||||
extensions::LaunchType launch_type) const override;
|
||||
GURL GetIconURL(const extensions::Extension* extension,
|
||||
int icon_size,
|
||||
|
||||
@@ -6,10 +6,16 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/pdf/pdf_viewer_stream_manager.h"
|
||||
#include "chrome/common/extensions/api/pdf_viewer_private.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "components/pdf/common/constants.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "url/url_constants.h"
|
||||
|
||||
@@ -22,6 +28,11 @@ namespace IsAllowedLocalFileAccess =
|
||||
|
||||
namespace SetPdfOcrPref = api::pdf_viewer_private::SetPdfOcrPref;
|
||||
|
||||
namespace SetPdfPluginAttributes =
|
||||
api::pdf_viewer_private::SetPdfPluginAttributes;
|
||||
|
||||
namespace SetPdfDocumentTitle = api::pdf_viewer_private::SetPdfDocumentTitle;
|
||||
|
||||
// Check if the current URL is allowed based on a list of allowlisted domains.
|
||||
bool IsUrlAllowedToEmbedLocalFiles(
|
||||
const GURL& current_url,
|
||||
@@ -43,8 +54,46 @@ bool IsUrlAllowedToEmbedLocalFiles(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the `StreamContainer` associated with the `extension_host`.
|
||||
base::WeakPtr<StreamContainer> GetStreamContainer(
|
||||
content::RenderFrameHost* extension_host) {
|
||||
content::RenderFrameHost* embedder_host = extension_host->GetParent();
|
||||
if (!embedder_host) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* pdf_viewer_stream_manager =
|
||||
pdf::PdfViewerStreamManager::FromRenderFrameHost(embedder_host);
|
||||
if (!pdf_viewer_stream_manager) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return pdf_viewer_stream_manager->GetStreamContainer(embedder_host);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PdfViewerPrivateGetStreamInfoFunction::PdfViewerPrivateGetStreamInfoFunction() =
|
||||
default;
|
||||
|
||||
PdfViewerPrivateGetStreamInfoFunction::
|
||||
~PdfViewerPrivateGetStreamInfoFunction() = default;
|
||||
|
||||
ExtensionFunction::ResponseAction PdfViewerPrivateGetStreamInfoFunction::Run() {
|
||||
base::WeakPtr<StreamContainer> stream =
|
||||
GetStreamContainer(render_frame_host());
|
||||
if (!stream) {
|
||||
return RespondNow(Error("Failed to get StreamContainer"));
|
||||
}
|
||||
|
||||
api::pdf_viewer_private::StreamInfo stream_info;
|
||||
stream_info.original_url = stream->original_url().spec();
|
||||
stream_info.stream_url = stream->stream_url().spec();
|
||||
stream_info.tab_id = stream->tab_id();
|
||||
stream_info.embedded = stream->embedded();
|
||||
return RespondNow(WithArguments(stream_info.ToValue()));
|
||||
}
|
||||
|
||||
PdfViewerPrivateIsAllowedLocalFileAccessFunction::
|
||||
PdfViewerPrivateIsAllowedLocalFileAccessFunction() = default;
|
||||
|
||||
@@ -61,6 +110,37 @@ PdfViewerPrivateIsAllowedLocalFileAccessFunction::Run() {
|
||||
IsUrlAllowedToEmbedLocalFiles(GURL(params->url), base::Value::List())));
|
||||
}
|
||||
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction::
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction() = default;
|
||||
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction::
|
||||
~PdfViewerPrivateSetPdfDocumentTitleFunction() = default;
|
||||
|
||||
// This function is only called for full-page PDFs.
|
||||
ExtensionFunction::ResponseAction
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction::Run() {
|
||||
content::WebContents* web_contents = GetSenderWebContents();
|
||||
if (!web_contents) {
|
||||
return RespondNow(Error("Could not find a valid web contents."));
|
||||
}
|
||||
|
||||
// Title should only be set for full-page PDFs.
|
||||
// MIME type associated with sender `WebContents` must be `application/pdf`
|
||||
// for a full-page PDF.
|
||||
EXTENSION_FUNCTION_VALIDATE(web_contents->GetContentsMimeType() ==
|
||||
pdf::kPDFMimeType);
|
||||
|
||||
std::optional<SetPdfDocumentTitle::Params> params =
|
||||
SetPdfDocumentTitle::Params::Create(args());
|
||||
EXTENSION_FUNCTION_VALIDATE(params);
|
||||
|
||||
web_contents->UpdateTitleForEntry(
|
||||
web_contents->GetController().GetLastCommittedEntry(),
|
||||
base::UTF8ToUTF16(params->title));
|
||||
|
||||
return RespondNow(NoArguments());
|
||||
}
|
||||
|
||||
PdfViewerPrivateIsPdfOcrAlwaysActiveFunction::
|
||||
PdfViewerPrivateIsPdfOcrAlwaysActiveFunction() = default;
|
||||
|
||||
@@ -87,4 +167,42 @@ ExtensionFunction::ResponseAction PdfViewerPrivateSetPdfOcrPrefFunction::Run() {
|
||||
return RespondNow(WithArguments(false));
|
||||
}
|
||||
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction::
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction() = default;
|
||||
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction::
|
||||
~PdfViewerPrivateSetPdfPluginAttributesFunction() = default;
|
||||
|
||||
ExtensionFunction::ResponseAction
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction::Run() {
|
||||
std::optional<SetPdfPluginAttributes::Params> params =
|
||||
SetPdfPluginAttributes::Params::Create(args());
|
||||
EXTENSION_FUNCTION_VALIDATE(params);
|
||||
|
||||
base::WeakPtr<StreamContainer> stream =
|
||||
GetStreamContainer(render_frame_host());
|
||||
if (!stream) {
|
||||
return RespondNow(Error("Failed to get StreamContainer"));
|
||||
}
|
||||
|
||||
const api::pdf_viewer_private::PdfPluginAttributes& attributes =
|
||||
params->attributes;
|
||||
// Check the `background_color` is an integer.
|
||||
double whole = 0.0;
|
||||
if (std::modf(attributes.background_color, &whole) != 0.0) {
|
||||
return RespondNow(Error("Background color is not an integer"));
|
||||
}
|
||||
|
||||
// Check the `background_color` is within the range of a uint32_t.
|
||||
if (!base::IsValueInRangeForNumericType<uint32_t>(
|
||||
attributes.background_color)) {
|
||||
return RespondNow(Error("Background color out of bounds"));
|
||||
}
|
||||
|
||||
stream->set_pdf_plugin_attributes(mime_handler::PdfPluginAttributes::New(
|
||||
/*background_color=*/attributes.background_color,
|
||||
/*allow_javascript=*/attributes.allow_javascript));
|
||||
return RespondNow(NoArguments());
|
||||
}
|
||||
|
||||
} // namespace extensions
|
||||
|
||||
@@ -9,6 +9,24 @@
|
||||
|
||||
namespace extensions {
|
||||
|
||||
class PdfViewerPrivateGetStreamInfoFunction : public ExtensionFunction {
|
||||
public:
|
||||
DECLARE_EXTENSION_FUNCTION("pdfViewerPrivate.getStreamInfo",
|
||||
PDFVIEWERPRIVATE_GETSTREAMINFO)
|
||||
|
||||
PdfViewerPrivateGetStreamInfoFunction();
|
||||
PdfViewerPrivateGetStreamInfoFunction(
|
||||
const PdfViewerPrivateGetStreamInfoFunction&) = delete;
|
||||
PdfViewerPrivateGetStreamInfoFunction& operator=(
|
||||
const PdfViewerPrivateGetStreamInfoFunction&) = delete;
|
||||
|
||||
protected:
|
||||
~PdfViewerPrivateGetStreamInfoFunction() override;
|
||||
|
||||
// Override from ExtensionFunction:
|
||||
ResponseAction Run() override;
|
||||
};
|
||||
|
||||
class PdfViewerPrivateIsAllowedLocalFileAccessFunction
|
||||
: public ExtensionFunction {
|
||||
public:
|
||||
@@ -28,6 +46,24 @@ class PdfViewerPrivateIsAllowedLocalFileAccessFunction
|
||||
ResponseAction Run() override;
|
||||
};
|
||||
|
||||
class PdfViewerPrivateSetPdfDocumentTitleFunction : public ExtensionFunction {
|
||||
public:
|
||||
DECLARE_EXTENSION_FUNCTION("pdfViewerPrivate.setPdfDocumentTitle",
|
||||
PDFVIEWERPRIVATE_SETPDFDOCUMENTTITLE)
|
||||
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction();
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction(
|
||||
const PdfViewerPrivateSetPdfDocumentTitleFunction&) = delete;
|
||||
PdfViewerPrivateSetPdfDocumentTitleFunction& operator=(
|
||||
const PdfViewerPrivateSetPdfDocumentTitleFunction&) = delete;
|
||||
|
||||
protected:
|
||||
~PdfViewerPrivateSetPdfDocumentTitleFunction() override;
|
||||
|
||||
// Override from ExtensionFunction:
|
||||
ResponseAction Run() override;
|
||||
};
|
||||
|
||||
class PdfViewerPrivateIsPdfOcrAlwaysActiveFunction : public ExtensionFunction {
|
||||
public:
|
||||
DECLARE_EXTENSION_FUNCTION("pdfViewerPrivate.isPdfOcrAlwaysActive",
|
||||
@@ -64,6 +100,25 @@ class PdfViewerPrivateSetPdfOcrPrefFunction : public ExtensionFunction {
|
||||
ResponseAction Run() override;
|
||||
};
|
||||
|
||||
class PdfViewerPrivateSetPdfPluginAttributesFunction
|
||||
: public ExtensionFunction {
|
||||
public:
|
||||
DECLARE_EXTENSION_FUNCTION("pdfViewerPrivate.setPdfPluginAttributes",
|
||||
PDFVIEWERPRIVATE_SETPDFPLUGINATTRIBUTES)
|
||||
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction();
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction(
|
||||
const PdfViewerPrivateSetPdfPluginAttributesFunction&) = delete;
|
||||
PdfViewerPrivateSetPdfPluginAttributesFunction& operator=(
|
||||
const PdfViewerPrivateSetPdfPluginAttributesFunction&) = delete;
|
||||
|
||||
protected:
|
||||
~PdfViewerPrivateSetPdfPluginAttributesFunction() override;
|
||||
|
||||
// Override from ExtensionFunction:
|
||||
ResponseAction Run() override;
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_API_H_
|
||||
|
||||
@@ -10,12 +10,20 @@
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h"
|
||||
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
|
||||
#include "extensions/common/manifest_handlers/mime_types_handler.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
#include "base/feature_list.h"
|
||||
#include "chrome/browser/pdf/pdf_viewer_stream_manager.h"
|
||||
#include "extensions/common/constants.h"
|
||||
#include "pdf/pdf_features.h"
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
|
||||
namespace extensions {
|
||||
|
||||
void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
|
||||
@@ -51,13 +59,27 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
|
||||
GURL handler_url(
|
||||
extensions::Extension::GetBaseURLFromExtensionId(extension_id).spec() +
|
||||
handler->handler_url());
|
||||
|
||||
int tab_id = -1;
|
||||
auto* api_contents = electron::api::WebContents::From(web_contents);
|
||||
if (api_contents)
|
||||
tab_id = api_contents->ID();
|
||||
|
||||
auto stream_container = std::make_unique<extensions::StreamContainer>(
|
||||
tab_id, embedded, handler_url, extension_id,
|
||||
std::move(transferrable_loader), original_url);
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif) &&
|
||||
extension_id == extension_misc::kPdfExtensionId) {
|
||||
pdf::PdfViewerStreamManager::Create(web_contents);
|
||||
pdf::PdfViewerStreamManager::FromWebContents(web_contents)
|
||||
->AddStreamContainer(frame_tree_node_id, internal_id,
|
||||
std::move(stream_container));
|
||||
return;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
|
||||
extensions::MimeHandlerStreamManager::Get(browser_context)
|
||||
->AddStream(stream_id, std::move(stream_container), frame_tree_node_id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,799 @@
|
||||
// Copyright (c) 2024 Microsoft, GmbH
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/base_paths.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/json/values_util.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h" // nogncheck
|
||||
#include "chrome/browser/file_system_access/file_system_access_features.h"
|
||||
#include "chrome/common/chrome_paths.h"
|
||||
#include "chrome/grit/generated_resources.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/disallow_activation_reason.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "shell/browser/electron_permission_manager.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using BlockType = ChromeFileSystemAccessPermissionContext::BlockType;
|
||||
using HandleType = content::FileSystemAccessPermissionContext::HandleType;
|
||||
using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
|
||||
using blink::mojom::PermissionStatus;
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
[[nodiscard]] constexpr bool ContainsInvalidDNSCharacter(
|
||||
base::FilePath::StringType hostname) {
|
||||
return !base::ranges::all_of(hostname, [](base::FilePath::CharType c) {
|
||||
return (c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z') ||
|
||||
(c >= L'0' && c <= L'9') || (c == L'.') || (c == L'-');
|
||||
});
|
||||
}
|
||||
|
||||
bool MaybeIsLocalUNCPath(const base::FilePath& path) {
|
||||
if (!path.IsNetwork()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<base::FilePath::StringType> components =
|
||||
path.GetComponents();
|
||||
|
||||
// Check for server name that could represent a local system. We only
|
||||
// check for a very short list, as it is impossible to cover all different
|
||||
// variants on Windows.
|
||||
if (components.size() >= 2 &&
|
||||
(base::FilePath::CompareEqualIgnoreCase(components[1],
|
||||
FILE_PATH_LITERAL("localhost")) ||
|
||||
components[1] == FILE_PATH_LITERAL("127.0.0.1") ||
|
||||
components[1] == FILE_PATH_LITERAL(".") ||
|
||||
components[1] == FILE_PATH_LITERAL("?") ||
|
||||
ContainsInvalidDNSCharacter(components[1]))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// In case we missed the server name check above, we also check for shares
|
||||
// ending with '$' as they represent pre-defined shares, including the local
|
||||
// drives.
|
||||
for (size_t i = 2; i < components.size(); ++i) {
|
||||
if (components[i].back() == L'$') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Describes a rule for blocking a directory, which can be constructed
|
||||
// dynamically (based on state) or statically (from kBlockedPaths).
|
||||
struct BlockPathRule {
|
||||
base::FilePath path;
|
||||
BlockType type;
|
||||
};
|
||||
|
||||
bool ShouldBlockAccessToPath(const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
std::vector<BlockPathRule> rules) {
|
||||
DCHECK(!path.empty());
|
||||
DCHECK(path.IsAbsolute());
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// On Windows, local UNC paths are rejected, as UNC path can be written in a
|
||||
// way that can bypass the blocklist.
|
||||
if (base::FeatureList::IsEnabled(
|
||||
features::kFileSystemAccessLocalUNCPathBlock) &&
|
||||
MaybeIsLocalUNCPath(path)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add the hard-coded rules to the dynamic rules.
|
||||
for (auto const& [key, rule_path, type] :
|
||||
ChromeFileSystemAccessPermissionContext::kBlockedPaths) {
|
||||
if (key == ChromeFileSystemAccessPermissionContext::kNoBasePathKey) {
|
||||
rules.emplace_back(base::FilePath{rule_path}, type);
|
||||
} else if (base::FilePath path; base::PathService::Get(key, &path)) {
|
||||
rules.emplace_back(rule_path ? path.Append(rule_path) : path, type);
|
||||
}
|
||||
}
|
||||
|
||||
base::FilePath nearest_ancestor;
|
||||
BlockType nearest_ancestor_block_type = BlockType::kDontBlockChildren;
|
||||
for (const auto& block : rules) {
|
||||
if (path == block.path || path.IsParent(block.path)) {
|
||||
DLOG(INFO) << "Blocking access to " << path
|
||||
<< " because it is a parent of " << block.path;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (block.path.IsParent(path) &&
|
||||
(nearest_ancestor.empty() || nearest_ancestor.IsParent(block.path))) {
|
||||
nearest_ancestor = block.path;
|
||||
nearest_ancestor_block_type = block.type;
|
||||
}
|
||||
}
|
||||
|
||||
// The path we're checking is not in a potentially blocked directory, or the
|
||||
// nearest ancestor does not block access to its children. Grant access.
|
||||
if (nearest_ancestor.empty() ||
|
||||
nearest_ancestor_block_type == BlockType::kDontBlockChildren) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The path we're checking is a file, and the nearest ancestor only blocks
|
||||
// access to directories. Grant access.
|
||||
if (handle_type == HandleType::kFile &&
|
||||
nearest_ancestor_block_type == BlockType::kBlockNestedDirectories) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The nearest ancestor blocks access to its children, so block access.
|
||||
DLOG(INFO) << "Blocking access to " << path << " because it is inside "
|
||||
<< nearest_ancestor;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace electron {
|
||||
|
||||
class FileSystemAccessPermissionContext::PermissionGrantImpl
|
||||
: public content::FileSystemAccessPermissionGrant {
|
||||
public:
|
||||
PermissionGrantImpl(base::WeakPtr<FileSystemAccessPermissionContext> context,
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
GrantType type,
|
||||
UserAction user_action)
|
||||
: context_{std::move(context)},
|
||||
origin_{origin},
|
||||
handle_type_{handle_type},
|
||||
type_{type},
|
||||
path_{path} {}
|
||||
|
||||
// FileSystemAccessPermissionGrant:
|
||||
PermissionStatus GetStatus() override {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return status_;
|
||||
}
|
||||
|
||||
base::FilePath GetPath() override {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return path_;
|
||||
}
|
||||
|
||||
void RequestPermission(
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
UserActivationState user_activation_state,
|
||||
base::OnceCallback<void(PermissionRequestOutcome)> callback) override {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// Check if a permission request has already been processed previously. This
|
||||
// check is done first because we don't want to reset the status of a
|
||||
// permission if it has already been granted.
|
||||
if (GetStatus() != PermissionStatus::ASK || !context_) {
|
||||
if (GetStatus() == PermissionStatus::GRANTED) {
|
||||
SetStatus(PermissionStatus::GRANTED);
|
||||
}
|
||||
std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
|
||||
return;
|
||||
}
|
||||
|
||||
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(frame_id);
|
||||
if (!rfh) {
|
||||
// Requested from a no longer valid RenderFrameHost.
|
||||
std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't request permission for an inactive RenderFrameHost as the
|
||||
// page might not distinguish properly between user denying the permission
|
||||
// and automatic rejection.
|
||||
if (rfh->IsInactiveAndDisallowActivation(
|
||||
content::DisallowActivationReasonId::
|
||||
kFileSystemAccessPermissionRequest)) {
|
||||
std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't allow file system access from fenced frames.
|
||||
if (rfh->IsNestedWithinFencedFrame()) {
|
||||
std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user_activation_state == UserActivationState::kRequired &&
|
||||
!rfh->HasTransientUserActivation()) {
|
||||
// No permission prompts without user activation.
|
||||
std::move(callback).Run(PermissionRequestOutcome::kNoUserActivation);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content::WebContents::FromRenderFrameHost(rfh) == nullptr) {
|
||||
std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
auto origin = rfh->GetLastCommittedOrigin().GetURL();
|
||||
if (url::Origin::Create(origin) != origin_) {
|
||||
// Third party iframes are not allowed to request more permissions.
|
||||
std::move(callback).Run(PermissionRequestOutcome::kThirdPartyContext);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* permission_manager =
|
||||
static_cast<electron::ElectronPermissionManager*>(
|
||||
context_->browser_context()->GetPermissionControllerDelegate());
|
||||
if (!permission_manager) {
|
||||
std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
|
||||
return;
|
||||
}
|
||||
|
||||
blink::PermissionType type = static_cast<blink::PermissionType>(
|
||||
electron::WebContentsPermissionHelper::PermissionType::FILE_SYSTEM);
|
||||
|
||||
base::Value::Dict details;
|
||||
details.Set("filePath", base::FilePathToValue(path_));
|
||||
details.Set("isDirectory", handle_type_ == HandleType::kDirectory);
|
||||
details.Set("fileAccessType",
|
||||
type_ == GrantType::kWrite ? "writable" : "readable");
|
||||
|
||||
permission_manager->RequestPermissionWithDetails(
|
||||
type, rfh, origin, false, std::move(details),
|
||||
base::BindOnce(&PermissionGrantImpl::OnPermissionRequestResult, this,
|
||||
std::move(callback)));
|
||||
}
|
||||
|
||||
const url::Origin& origin() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return origin_;
|
||||
}
|
||||
|
||||
HandleType handle_type() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return handle_type_;
|
||||
}
|
||||
|
||||
GrantType type() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return type_;
|
||||
}
|
||||
|
||||
void SetStatus(PermissionStatus new_status) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto permission_changed = status_ != new_status;
|
||||
status_ = new_status;
|
||||
|
||||
if (permission_changed) {
|
||||
NotifyPermissionStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateGrantPath(
|
||||
std::map<base::FilePath, PermissionGrantImpl*>& grants,
|
||||
const base::FilePath& old_path,
|
||||
const base::FilePath& new_path) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
auto entry_it = base::ranges::find_if(
|
||||
grants,
|
||||
[&old_path](const auto& entry) { return entry.first == old_path; });
|
||||
|
||||
if (entry_it == grants.end()) {
|
||||
// There must be an entry for an ancestor of this entry. Nothing to do
|
||||
// here.
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK_EQ(entry_it->second->GetStatus(), PermissionStatus::GRANTED);
|
||||
|
||||
auto* const grant_impl = entry_it->second;
|
||||
grant_impl->SetPath(new_path);
|
||||
|
||||
// Update the permission grant's key in the map of active permissions.
|
||||
grants.erase(entry_it);
|
||||
grants.emplace(new_path, grant_impl);
|
||||
}
|
||||
|
||||
protected:
|
||||
~PermissionGrantImpl() override {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (context_) {
|
||||
context_->PermissionGrantDestroyed(this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void OnPermissionRequestResult(
|
||||
base::OnceCallback<void(PermissionRequestOutcome)> callback,
|
||||
blink::mojom::PermissionStatus status) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (status == blink::mojom::PermissionStatus::GRANTED) {
|
||||
SetStatus(PermissionStatus::GRANTED);
|
||||
std::move(callback).Run(PermissionRequestOutcome::kUserGranted);
|
||||
} else {
|
||||
SetStatus(PermissionStatus::DENIED);
|
||||
std::move(callback).Run(PermissionRequestOutcome::kUserDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void SetPath(const base::FilePath& new_path) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (path_ == new_path)
|
||||
return;
|
||||
|
||||
path_ = new_path;
|
||||
NotifyPermissionStatusChanged();
|
||||
}
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
base::WeakPtr<FileSystemAccessPermissionContext> const context_;
|
||||
const url::Origin origin_;
|
||||
const HandleType handle_type_;
|
||||
const GrantType type_;
|
||||
base::FilePath path_;
|
||||
|
||||
// This member should only be updated via SetStatus().
|
||||
PermissionStatus status_ = PermissionStatus::ASK;
|
||||
};
|
||||
|
||||
struct FileSystemAccessPermissionContext::OriginState {
|
||||
// Raw pointers, owned collectively by all the handles that reference this
|
||||
// grant. When last reference goes away this state is cleared as well by
|
||||
// PermissionGrantDestroyed().
|
||||
std::map<base::FilePath, PermissionGrantImpl*> read_grants;
|
||||
std::map<base::FilePath, PermissionGrantImpl*> write_grants;
|
||||
};
|
||||
|
||||
FileSystemAccessPermissionContext::FileSystemAccessPermissionContext(
|
||||
content::BrowserContext* browser_context)
|
||||
: browser_context_(browser_context) {
|
||||
DETACH_FROM_SEQUENCE(sequence_checker_);
|
||||
}
|
||||
|
||||
FileSystemAccessPermissionContext::~FileSystemAccessPermissionContext() =
|
||||
default;
|
||||
|
||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
|
||||
FileSystemAccessPermissionContext::GetReadPermissionGrant(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
// operator[] might insert a new OriginState in |active_permissions_map_|,
|
||||
// but that is exactly what we want.
|
||||
auto& origin_state = active_permissions_map_[origin];
|
||||
auto*& existing_grant = origin_state.read_grants[path];
|
||||
scoped_refptr<PermissionGrantImpl> new_grant;
|
||||
|
||||
if (existing_grant && existing_grant->handle_type() != handle_type) {
|
||||
// |path| changed from being a directory to being a file or vice versa,
|
||||
// don't just re-use the existing grant but revoke the old grant before
|
||||
// creating a new grant.
|
||||
existing_grant->SetStatus(PermissionStatus::DENIED);
|
||||
existing_grant = nullptr;
|
||||
}
|
||||
|
||||
if (!existing_grant) {
|
||||
new_grant = base::MakeRefCounted<PermissionGrantImpl>(
|
||||
weak_factory_.GetWeakPtr(), origin, path, handle_type, GrantType::kRead,
|
||||
user_action);
|
||||
existing_grant = new_grant.get();
|
||||
}
|
||||
|
||||
// If a parent directory is already readable this new grant should also be
|
||||
// readable.
|
||||
if (new_grant &&
|
||||
AncestorHasActivePermission(origin, path, GrantType::kRead)) {
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
} else {
|
||||
switch (user_action) {
|
||||
case UserAction::kOpen:
|
||||
case UserAction::kSave:
|
||||
// Open and Save dialog only grant read access for individual files.
|
||||
if (handle_type == HandleType::kDirectory) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case UserAction::kDragAndDrop:
|
||||
// Drag&drop grants read access for all handles.
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
break;
|
||||
case UserAction::kLoadFromStorage:
|
||||
case UserAction::kNone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return existing_grant;
|
||||
}
|
||||
|
||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
|
||||
FileSystemAccessPermissionContext::GetWritePermissionGrant(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
// operator[] might insert a new OriginState in |active_permissions_map_|,
|
||||
// but that is exactly what we want.
|
||||
auto& origin_state = active_permissions_map_[origin];
|
||||
auto*& existing_grant = origin_state.write_grants[path];
|
||||
scoped_refptr<PermissionGrantImpl> new_grant;
|
||||
|
||||
if (existing_grant && existing_grant->handle_type() != handle_type) {
|
||||
// |path| changed from being a directory to being a file or vice versa,
|
||||
// don't just re-use the existing grant but revoke the old grant before
|
||||
// creating a new grant.
|
||||
existing_grant->SetStatus(PermissionStatus::DENIED);
|
||||
existing_grant = nullptr;
|
||||
}
|
||||
|
||||
if (!existing_grant) {
|
||||
new_grant = base::MakeRefCounted<PermissionGrantImpl>(
|
||||
weak_factory_.GetWeakPtr(), origin, path, handle_type,
|
||||
GrantType::kWrite, user_action);
|
||||
existing_grant = new_grant.get();
|
||||
}
|
||||
|
||||
// If a parent directory is already writable this new grant should also be
|
||||
// writable.
|
||||
if (new_grant &&
|
||||
AncestorHasActivePermission(origin, path, GrantType::kWrite)) {
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
} else {
|
||||
switch (user_action) {
|
||||
case UserAction::kSave:
|
||||
// Only automatically grant write access for save dialogs.
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
break;
|
||||
case UserAction::kOpen:
|
||||
case UserAction::kDragAndDrop:
|
||||
case UserAction::kLoadFromStorage:
|
||||
case UserAction::kNone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return existing_grant;
|
||||
}
|
||||
|
||||
bool FileSystemAccessPermissionContext::CanObtainReadPermission(
|
||||
const url::Origin& origin) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystemAccessPermissionContext::CanObtainWritePermission(
|
||||
const url::Origin& origin) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
|
||||
const url::Origin& origin,
|
||||
PathType path_type,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto after_blocklist_check_callback = base::BindOnce(
|
||||
&FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
|
||||
GetWeakPtr(), origin, path, handle_type, user_action, frame_id,
|
||||
std::move(callback));
|
||||
CheckPathAgainstBlocklist(path_type, path, handle_type,
|
||||
std::move(after_blocklist_check_callback));
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::CheckPathAgainstBlocklist(
|
||||
PathType path_type,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
base::OnceCallback<void(bool)> callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
// TODO(https://crbug.com/1009970): Figure out what external paths should be
|
||||
// blocked. We could resolve the external path to a local path, and check for
|
||||
// blocked directories based on that, but that doesn't work well. Instead we
|
||||
// should have a separate Chrome OS only code path to block for example the
|
||||
// root of certain external file systems.
|
||||
if (path_type == PathType::kExternal) {
|
||||
std::move(callback).Run(/*should_block=*/false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<BlockPathRule> extra_rules;
|
||||
extra_rules.emplace_back(browser_context_->GetPath().DirName(),
|
||||
BlockType::kBlockAllChildren);
|
||||
|
||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
||||
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
|
||||
base::BindOnce(&ShouldBlockAccessToPath, path, handle_type, extra_rules),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
|
||||
std::unique_ptr<content::FileSystemAccessWriteItem> item,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(AfterWriteCheckResult)> callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
std::move(callback).Run(AfterWriteCheckResult::kAllow);
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback,
|
||||
bool should_block) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (user_action == UserAction::kNone) {
|
||||
std::move(callback).Run(should_block ? SensitiveEntryResult::kAbort
|
||||
: SensitiveEntryResult::kAllowed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Chromium opens a dialog here, but in Electron's case we log and abort.
|
||||
if (should_block) {
|
||||
LOG(INFO) << path.value()
|
||||
<< " is blocked by the blocklis and cannot be accessed";
|
||||
std::move(callback).Run(SensitiveEntryResult::kAbort);
|
||||
return;
|
||||
}
|
||||
|
||||
std::move(callback).Run(SensitiveEntryResult::kAllowed);
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::SetLastPickedDirectory(
|
||||
const url::Origin& origin,
|
||||
const std::string& id,
|
||||
const base::FilePath& path,
|
||||
const PathType type) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
LOG(INFO) << "NOTIMPLEMENTED SetLastPickedDirectory: " << path.value();
|
||||
}
|
||||
|
||||
FileSystemAccessPermissionContext::PathInfo
|
||||
FileSystemAccessPermissionContext::GetLastPickedDirectory(
|
||||
const url::Origin& origin,
|
||||
const std::string& id) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
LOG(INFO) << "NOTIMPLEMENTED GetLastPickedDirectory";
|
||||
return PathInfo();
|
||||
}
|
||||
|
||||
base::FilePath FileSystemAccessPermissionContext::GetWellKnownDirectoryPath(
|
||||
blink::mojom::WellKnownDirectory directory,
|
||||
const url::Origin& origin) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
int key = base::PATH_START;
|
||||
switch (directory) {
|
||||
case blink::mojom::WellKnownDirectory::kDirDesktop:
|
||||
key = base::DIR_USER_DESKTOP;
|
||||
break;
|
||||
case blink::mojom::WellKnownDirectory::kDirDocuments:
|
||||
key = chrome::DIR_USER_DOCUMENTS;
|
||||
break;
|
||||
case blink::mojom::WellKnownDirectory::kDirDownloads:
|
||||
key = chrome::DIR_DEFAULT_DOWNLOADS;
|
||||
break;
|
||||
case blink::mojom::WellKnownDirectory::kDirMusic:
|
||||
key = chrome::DIR_USER_MUSIC;
|
||||
break;
|
||||
case blink::mojom::WellKnownDirectory::kDirPictures:
|
||||
key = chrome::DIR_USER_PICTURES;
|
||||
break;
|
||||
case blink::mojom::WellKnownDirectory::kDirVideos:
|
||||
key = chrome::DIR_USER_VIDEOS;
|
||||
break;
|
||||
}
|
||||
base::FilePath directory_path;
|
||||
base::PathService::Get(key, &directory_path);
|
||||
return directory_path;
|
||||
}
|
||||
|
||||
std::u16string FileSystemAccessPermissionContext::GetPickerTitle(
|
||||
const blink::mojom::FilePickerOptionsPtr& options) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// TODO(asully): Consider adding custom strings for invocations of the file
|
||||
// picker, as well. Returning the empty string will fall back to the platform
|
||||
// default for the given picker type.
|
||||
std::u16string title;
|
||||
switch (options->type_specific_options->which()) {
|
||||
case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
|
||||
kDirectoryPickerOptions:
|
||||
title = l10n_util::GetStringUTF16(
|
||||
options->type_specific_options->get_directory_picker_options()
|
||||
->request_writable
|
||||
? IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_WRITABLE_DIRECTORY_TITLE
|
||||
: IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_READABLE_DIRECTORY_TITLE);
|
||||
break;
|
||||
case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
|
||||
kSaveFilePickerOptions:
|
||||
title = l10n_util::GetStringUTF16(
|
||||
IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_SAVE_FILE_TITLE);
|
||||
break;
|
||||
case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
|
||||
kOpenFilePickerOptions:
|
||||
break;
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::NotifyEntryMoved(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& old_path,
|
||||
const base::FilePath& new_path) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (old_path == new_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = active_permissions_map_.find(origin);
|
||||
if (it != active_permissions_map_.end()) {
|
||||
PermissionGrantImpl::UpdateGrantPath(it->second.write_grants, old_path,
|
||||
new_path);
|
||||
PermissionGrantImpl::UpdateGrantPath(it->second.read_grants, old_path,
|
||||
new_path);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::OnFileCreatedFromShowSaveFilePicker(
|
||||
const GURL& file_picker_binding_context,
|
||||
const storage::FileSystemURL& url) {}
|
||||
|
||||
void FileSystemAccessPermissionContext::CheckPathsAgainstEnterprisePolicy(
|
||||
std::vector<PathInfo> entries,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
EntriesAllowedByEnterprisePolicyCallback callback) {
|
||||
std::move(callback).Run(std::move(entries));
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::RevokeGrant(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& file_path) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto origin_it = active_permissions_map_.find(origin);
|
||||
if (origin_it != active_permissions_map_.end()) {
|
||||
OriginState& origin_state = origin_it->second;
|
||||
for (auto& grant : origin_state.read_grants) {
|
||||
if (file_path.empty() || grant.first == file_path) {
|
||||
grant.second->SetStatus(PermissionStatus::ASK);
|
||||
}
|
||||
}
|
||||
for (auto& grant : origin_state.write_grants) {
|
||||
if (file_path.empty() || grant.first == file_path) {
|
||||
grant.second->SetStatus(PermissionStatus::ASK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileSystemAccessPermissionContext::OriginHasReadAccess(
|
||||
const url::Origin& origin) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto it = active_permissions_map_.find(origin);
|
||||
if (it != active_permissions_map_.end()) {
|
||||
return base::ranges::any_of(it->second.read_grants, [&](const auto& grant) {
|
||||
return grant.second->GetStatus() == PermissionStatus::GRANTED;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileSystemAccessPermissionContext::OriginHasWriteAccess(
|
||||
const url::Origin& origin) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto it = active_permissions_map_.find(origin);
|
||||
if (it != active_permissions_map_.end()) {
|
||||
return base::ranges::any_of(
|
||||
it->second.write_grants, [&](const auto& grant) {
|
||||
return grant.second->GetStatus() == PermissionStatus::GRANTED;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::CleanupPermissions(
|
||||
const url::Origin& origin) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
RevokeGrant(origin);
|
||||
}
|
||||
|
||||
bool FileSystemAccessPermissionContext::AncestorHasActivePermission(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
GrantType grant_type) const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
auto it = active_permissions_map_.find(origin);
|
||||
if (it == active_permissions_map_.end()) {
|
||||
return false;
|
||||
}
|
||||
const auto& relevant_grants = grant_type == GrantType::kWrite
|
||||
? it->second.write_grants
|
||||
: it->second.read_grants;
|
||||
if (relevant_grants.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Permissions are inherited from the closest ancestor.
|
||||
for (base::FilePath parent = path.DirName(); parent != parent.DirName();
|
||||
parent = parent.DirName()) {
|
||||
auto i = relevant_grants.find(parent);
|
||||
if (i != relevant_grants.end() && i->second &&
|
||||
i->second->GetStatus() == PermissionStatus::GRANTED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::PermissionGrantDestroyed(
|
||||
PermissionGrantImpl* grant) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
auto it = active_permissions_map_.find(grant->origin());
|
||||
if (it == active_permissions_map_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& grants = grant->type() == GrantType::kRead ? it->second.read_grants
|
||||
: it->second.write_grants;
|
||||
auto grant_it = grants.find(grant->GetPath());
|
||||
// Any non-denied permission grants should have still been in our grants
|
||||
// list. If this invariant is violated we would have permissions that might
|
||||
// be granted but won't be visible in any UI because the permission context
|
||||
// isn't tracking them anymore.
|
||||
if (grant_it == grants.end()) {
|
||||
DCHECK_EQ(PermissionStatus::DENIED, grant->GetStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// The grant in |grants| for this path might have been replaced with a
|
||||
// different grant. Only erase if it actually matches the grant that was
|
||||
// destroyed.
|
||||
if (grant_it->second == grant) {
|
||||
grants.erase(grant_it);
|
||||
}
|
||||
}
|
||||
|
||||
base::WeakPtr<FileSystemAccessPermissionContext>
|
||||
FileSystemAccessPermissionContext::GetWeakPtr() {
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2024 Microsoft, GmbH
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_ELECTRON_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
|
||||
#define ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_ELECTRON_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
|
||||
|
||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "content/public/browser/file_system_access_permission_context.h"
|
||||
|
||||
class GURL;
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
} // namespace base
|
||||
|
||||
namespace storage {
|
||||
class FileSystemURL;
|
||||
} // namespace storage
|
||||
|
||||
namespace electron {
|
||||
|
||||
class FileSystemAccessPermissionContext
|
||||
: public KeyedService,
|
||||
public content::FileSystemAccessPermissionContext {
|
||||
public:
|
||||
enum class GrantType { kRead, kWrite };
|
||||
|
||||
explicit FileSystemAccessPermissionContext(
|
||||
content::BrowserContext* browser_context);
|
||||
FileSystemAccessPermissionContext(const FileSystemAccessPermissionContext&) =
|
||||
delete;
|
||||
FileSystemAccessPermissionContext& operator=(
|
||||
const FileSystemAccessPermissionContext&) = delete;
|
||||
~FileSystemAccessPermissionContext() override;
|
||||
|
||||
// content::FileSystemAccessPermissionContext:
|
||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
|
||||
GetReadPermissionGrant(const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action) override;
|
||||
|
||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
|
||||
GetWritePermissionGrant(const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action) override;
|
||||
|
||||
void ConfirmSensitiveEntryAccess(
|
||||
const url::Origin& origin,
|
||||
PathType path_type,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback) override;
|
||||
|
||||
void PerformAfterWriteChecks(
|
||||
std::unique_ptr<content::FileSystemAccessWriteItem> item,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(AfterWriteCheckResult)> callback) override;
|
||||
|
||||
bool CanObtainReadPermission(const url::Origin& origin) override;
|
||||
bool CanObtainWritePermission(const url::Origin& origin) override;
|
||||
|
||||
void SetLastPickedDirectory(const url::Origin& origin,
|
||||
const std::string& id,
|
||||
const base::FilePath& path,
|
||||
const PathType type) override;
|
||||
|
||||
PathInfo GetLastPickedDirectory(const url::Origin& origin,
|
||||
const std::string& id) override;
|
||||
|
||||
base::FilePath GetWellKnownDirectoryPath(
|
||||
blink::mojom::WellKnownDirectory directory,
|
||||
const url::Origin& origin) override;
|
||||
|
||||
std::u16string GetPickerTitle(
|
||||
const blink::mojom::FilePickerOptionsPtr& options) override;
|
||||
|
||||
void NotifyEntryMoved(const url::Origin& origin,
|
||||
const base::FilePath& old_path,
|
||||
const base::FilePath& new_path) override;
|
||||
|
||||
void OnFileCreatedFromShowSaveFilePicker(
|
||||
const GURL& file_picker_binding_context,
|
||||
const storage::FileSystemURL& url) override;
|
||||
|
||||
void CheckPathsAgainstEnterprisePolicy(
|
||||
std::vector<PathInfo> entries,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
EntriesAllowedByEnterprisePolicyCallback callback) override;
|
||||
|
||||
enum class Access { kRead, kWrite, kReadWrite };
|
||||
|
||||
enum class RequestType { kNewPermission, kRestorePermissions };
|
||||
|
||||
void RevokeGrant(const url::Origin& origin,
|
||||
const base::FilePath& file_path = base::FilePath());
|
||||
|
||||
bool OriginHasReadAccess(const url::Origin& origin);
|
||||
bool OriginHasWriteAccess(const url::Origin& origin);
|
||||
|
||||
content::BrowserContext* browser_context() const { return browser_context_; }
|
||||
|
||||
protected:
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
private:
|
||||
class PermissionGrantImpl;
|
||||
|
||||
void PermissionGrantDestroyed(PermissionGrantImpl* grant);
|
||||
|
||||
void CheckPathAgainstBlocklist(PathType path_type,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
base::OnceCallback<void(bool)> callback);
|
||||
void DidCheckPathAgainstBlocklist(
|
||||
const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
HandleType handle_type,
|
||||
UserAction user_action,
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback,
|
||||
bool should_block);
|
||||
|
||||
void CleanupPermissions(const url::Origin& origin);
|
||||
|
||||
bool AncestorHasActivePermission(const url::Origin& origin,
|
||||
const base::FilePath& path,
|
||||
GrantType grant_type) const;
|
||||
|
||||
base::WeakPtr<FileSystemAccessPermissionContext> GetWeakPtr();
|
||||
|
||||
const raw_ptr<content::BrowserContext, DanglingUntriaged> browser_context_;
|
||||
|
||||
struct OriginState;
|
||||
std::map<url::Origin, OriginState> active_permissions_map_;
|
||||
|
||||
base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2024 Microsoft, GmbH
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/file_system_access/file_system_access_permission_context_factory.h"
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "components/keyed_service/content/browser_context_dependency_manager.h"
|
||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
// static
|
||||
electron::FileSystemAccessPermissionContext*
|
||||
FileSystemAccessPermissionContextFactory::GetForBrowserContext(
|
||||
content::BrowserContext* context) {
|
||||
return static_cast<electron::FileSystemAccessPermissionContext*>(
|
||||
GetInstance()->GetServiceForBrowserContext(context, true));
|
||||
}
|
||||
|
||||
// static
|
||||
FileSystemAccessPermissionContextFactory*
|
||||
FileSystemAccessPermissionContextFactory::GetInstance() {
|
||||
static base::NoDestructor<FileSystemAccessPermissionContextFactory> instance;
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
FileSystemAccessPermissionContextFactory::
|
||||
FileSystemAccessPermissionContextFactory()
|
||||
: BrowserContextKeyedServiceFactory(
|
||||
"FileSystemAccessPermissionContext",
|
||||
BrowserContextDependencyManager::GetInstance()) {}
|
||||
|
||||
FileSystemAccessPermissionContextFactory::
|
||||
~FileSystemAccessPermissionContextFactory() = default;
|
||||
|
||||
// static
|
||||
KeyedService* FileSystemAccessPermissionContextFactory::BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const {
|
||||
return BuildInstanceFor(context).release();
|
||||
}
|
||||
|
||||
std::unique_ptr<KeyedService>
|
||||
FileSystemAccessPermissionContextFactory::BuildInstanceFor(
|
||||
content::BrowserContext* context) {
|
||||
return std::make_unique<FileSystemAccessPermissionContext>(context);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2024 Microsoft, GmbH
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_H_
|
||||
#define ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_H_
|
||||
|
||||
#include "base/no_destructor.h"
|
||||
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
|
||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class FileSystemAccessPermissionContextFactory
|
||||
: public BrowserContextKeyedServiceFactory {
|
||||
public:
|
||||
static FileSystemAccessPermissionContext* GetForBrowserContext(
|
||||
content::BrowserContext* context);
|
||||
static FileSystemAccessPermissionContextFactory* GetInstance();
|
||||
|
||||
static std::unique_ptr<KeyedService> BuildInstanceFor(
|
||||
content::BrowserContext* context);
|
||||
|
||||
FileSystemAccessPermissionContextFactory(
|
||||
const FileSystemAccessPermissionContextFactory&) = delete;
|
||||
FileSystemAccessPermissionContextFactory& operator=(
|
||||
const FileSystemAccessPermissionContextFactory&) = delete;
|
||||
|
||||
private:
|
||||
friend class base::NoDestructor<FileSystemAccessPermissionContextFactory>;
|
||||
|
||||
FileSystemAccessPermissionContextFactory();
|
||||
~FileSystemAccessPermissionContextFactory() override;
|
||||
|
||||
// BrowserContextKeyedServiceFactory:
|
||||
KeyedService* BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const override;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_H_
|
||||
@@ -79,10 +79,10 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Process product informations and start the payment.
|
||||
* Process product information and start the payment.
|
||||
*
|
||||
* @param request - The product request.
|
||||
* @param response - The informations about the list of products.
|
||||
* @param response - The information about the list of products.
|
||||
*/
|
||||
- (void)productsRequest:(SKProductsRequest*)request
|
||||
didReceiveResponse:(SKProductsResponse*)response {
|
||||
|
||||
@@ -469,12 +469,12 @@ void NativeWindowViews::SetGTKDarkThemeEnabled(bool use_dark_theme) {
|
||||
|
||||
void NativeWindowViews::SetContentView(views::View* view) {
|
||||
if (content_view()) {
|
||||
root_view_.RemoveChildView(content_view());
|
||||
root_view_.GetMainView()->RemoveChildView(content_view());
|
||||
}
|
||||
set_content_view(view);
|
||||
focused_view_ = view;
|
||||
root_view_.AddChildView(content_view());
|
||||
root_view_.DeprecatedLayoutImmediately();
|
||||
root_view_.GetMainView()->AddChildView(content_view());
|
||||
root_view_.GetMainView()->DeprecatedLayoutImmediately();
|
||||
}
|
||||
|
||||
void NativeWindowViews::Close() {
|
||||
@@ -1664,7 +1664,7 @@ std::u16string NativeWindowViews::GetWindowTitle() const {
|
||||
}
|
||||
|
||||
views::View* NativeWindowViews::GetContentsView() {
|
||||
return &root_view_;
|
||||
return root_view_.GetMainView();
|
||||
}
|
||||
|
||||
bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
|
||||
@@ -1674,7 +1674,7 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
|
||||
}
|
||||
|
||||
views::ClientView* NativeWindowViews::CreateClientView(views::Widget* widget) {
|
||||
return new NativeWindowClientView{widget, GetContentsView(), this};
|
||||
return new NativeWindowClientView{widget, &root_view_, this};
|
||||
}
|
||||
|
||||
std::unique_ptr<views::NonClientFrameView>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "content/browser/renderer_host/render_widget_host_delegate.h" // nogncheck
|
||||
#include "content/browser/renderer_host/render_widget_host_owner_delegate.h" // nogncheck
|
||||
#include "content/common/input/synthetic_gesture.h" // nogncheck
|
||||
#include "content/common/input/synthetic_gesture_target.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/context_factory.h"
|
||||
|
||||
@@ -35,7 +35,9 @@ std::unique_ptr<content::SerialChooser> ElectronSerialDelegate::RunChooser(
|
||||
if (controller) {
|
||||
DeleteControllerForFrame(frame);
|
||||
}
|
||||
AddControllerForFrame(frame, std::move(filters), std::move(callback));
|
||||
AddControllerForFrame(frame, std::move(filters),
|
||||
std::move(allowed_bluetooth_service_class_ids),
|
||||
std::move(callback));
|
||||
|
||||
// Return a nullptr because the return value isn't used for anything, eg
|
||||
// there is no mechanism to cancel navigator.serial.requestPort(). The return
|
||||
@@ -106,12 +108,14 @@ SerialChooserController* ElectronSerialDelegate::ControllerForFrame(
|
||||
SerialChooserController* ElectronSerialDelegate::AddControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids,
|
||||
content::SerialChooser::Callback callback) {
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto controller = std::make_unique<SerialChooserController>(
|
||||
render_frame_host, std::move(filters), std::move(callback), web_contents,
|
||||
weak_factory_.GetWeakPtr());
|
||||
render_frame_host, std::move(filters),
|
||||
std::move(allowed_bluetooth_service_class_ids), 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);
|
||||
|
||||
@@ -67,6 +67,7 @@ class ElectronSerialDelegate : public content::SerialDelegate,
|
||||
SerialChooserController* AddControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids,
|
||||
content::SerialChooser::Callback callback);
|
||||
|
||||
base::ScopedObservation<SerialChooserContext,
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
|
||||
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
|
||||
#include "services/device/public/mojom/serial.mojom.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/serial/serial_chooser_context.h"
|
||||
#include "shell/browser/serial/serial_chooser_context_factory.h"
|
||||
@@ -58,14 +61,57 @@ struct Converter<device::mojom::SerialPortInfoPtr> {
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::device::mojom::SerialPortType;
|
||||
|
||||
bool FilterMatchesPort(const blink::mojom::SerialPortFilter& filter,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
if (filter.bluetooth_service_class_id) {
|
||||
if (!port.bluetooth_service_class_id) {
|
||||
return false;
|
||||
}
|
||||
return device::BluetoothUUID(*port.bluetooth_service_class_id) ==
|
||||
device::BluetoothUUID(*filter.bluetooth_service_class_id);
|
||||
}
|
||||
if (!filter.has_vendor_id) {
|
||||
return true;
|
||||
}
|
||||
if (!port.has_vendor_id || port.vendor_id != filter.vendor_id) {
|
||||
return false;
|
||||
}
|
||||
if (!filter.has_product_id) {
|
||||
return true;
|
||||
}
|
||||
return port.has_product_id && port.product_id == filter.product_id;
|
||||
}
|
||||
|
||||
bool BluetoothPortIsAllowed(
|
||||
const std::vector<::device::BluetoothUUID>& allowed_ids,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
if (!port.bluetooth_service_class_id) {
|
||||
return true;
|
||||
}
|
||||
// Serial Port Profile is allowed by default.
|
||||
if (*port.bluetooth_service_class_id == device::GetSerialPortProfileUUID()) {
|
||||
return true;
|
||||
}
|
||||
return base::Contains(allowed_ids, port.bluetooth_service_class_id.value());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SerialChooserController::SerialChooserController(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids,
|
||||
content::SerialChooser::Callback callback,
|
||||
content::WebContents* web_contents,
|
||||
base::WeakPtr<ElectronSerialDelegate> serial_delegate)
|
||||
: WebContentsObserver(web_contents),
|
||||
filters_(std::move(filters)),
|
||||
allowed_bluetooth_service_class_ids_(
|
||||
std::move(allowed_bluetooth_service_class_ids)),
|
||||
callback_(std::move(callback)),
|
||||
serial_delegate_(serial_delegate),
|
||||
render_frame_host_id_(render_frame_host->GetGlobalId()) {
|
||||
@@ -93,10 +139,11 @@ api::Session* SerialChooserController::GetSession() {
|
||||
|
||||
void SerialChooserController::OnPortAdded(
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
if (!FilterMatchesAny(port))
|
||||
if (!DisplayDevice(port))
|
||||
return;
|
||||
|
||||
ports_.push_back(port.Clone());
|
||||
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
session->Emit("serial-port-added", port.Clone(), web_contents());
|
||||
@@ -105,9 +152,8 @@ void SerialChooserController::OnPortAdded(
|
||||
|
||||
void SerialChooserController::OnPortRemoved(
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
const auto it = std::find_if(
|
||||
ports_.begin(), ports_.end(),
|
||||
[&port](const auto& ptr) { return ptr->token == port.token; });
|
||||
const auto it = base::ranges::find(ports_, port.token,
|
||||
&device::mojom::SerialPortInfo::token);
|
||||
if (it != ports_.end()) {
|
||||
api::Session* session = GetSession();
|
||||
if (session) {
|
||||
@@ -152,7 +198,7 @@ void SerialChooserController::OnGetDevices(
|
||||
});
|
||||
|
||||
for (auto& port : ports) {
|
||||
if (FilterMatchesAny(*port))
|
||||
if (DisplayDevice(*port))
|
||||
ports_.push_back(std::move(port));
|
||||
}
|
||||
|
||||
@@ -169,21 +215,17 @@ void SerialChooserController::OnGetDevices(
|
||||
}
|
||||
}
|
||||
|
||||
bool SerialChooserController::FilterMatchesAny(
|
||||
bool SerialChooserController::DisplayDevice(
|
||||
const device::mojom::SerialPortInfo& port) const {
|
||||
if (filters_.empty())
|
||||
return true;
|
||||
if (filters_.empty()) {
|
||||
return BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port);
|
||||
}
|
||||
|
||||
for (const auto& filter : filters_) {
|
||||
if (filter->has_vendor_id &&
|
||||
(!port.has_vendor_id || filter->vendor_id != port.vendor_id)) {
|
||||
continue;
|
||||
if (FilterMatchesPort(*filter, port) &&
|
||||
BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port)) {
|
||||
return true;
|
||||
}
|
||||
if (filter->has_product_id &&
|
||||
(!port.has_product_id || filter->product_id != port.product_id)) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -34,6 +34,7 @@ class SerialChooserController final : public SerialChooserContext::PortObserver,
|
||||
SerialChooserController(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters,
|
||||
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids,
|
||||
content::SerialChooser::Callback callback,
|
||||
content::WebContents* web_contents,
|
||||
base::WeakPtr<ElectronSerialDelegate> serial_delegate);
|
||||
@@ -55,11 +56,12 @@ class SerialChooserController final : public SerialChooserContext::PortObserver,
|
||||
private:
|
||||
api::Session* GetSession();
|
||||
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
|
||||
bool FilterMatchesAny(const device::mojom::SerialPortInfo& port) const;
|
||||
bool DisplayDevice(const device::mojom::SerialPortInfo& port) const;
|
||||
void RunCallback(device::mojom::SerialPortInfoPtr port);
|
||||
void OnDeviceChosen(const std::string& port_id);
|
||||
|
||||
std::vector<blink::mojom::SerialPortFilterPtr> filters_;
|
||||
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids_;
|
||||
content::SerialChooser::Callback callback_;
|
||||
url::Origin origin_;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "net/socket/stream_socket.h"
|
||||
#include "net/socket/tcp_server_socket.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "third_party/inspector_protocol/crdtp/dispatch.h"
|
||||
#include "ui/base/resource/resource_bundle.h"
|
||||
@@ -139,4 +140,8 @@ bool DevToolsManagerDelegate::HasBundledFrontendResources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
content::BrowserContext* DevToolsManagerDelegate::GetDefaultBrowserContext() {
|
||||
return ElectronBrowserContext::From("", false);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "base/compiler_specific.h"
|
||||
#include "content/public/browser/devtools_manager_delegate.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
|
||||
@@ -33,6 +37,7 @@ class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
|
||||
TargetType target_type) override;
|
||||
std::string GetDiscoveryPageHTML() override;
|
||||
bool HasBundledFrontendResources() override;
|
||||
content::BrowserContext* GetDefaultBrowserContext() override;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -9,18 +9,12 @@
|
||||
#include "content/public/common/input/native_web_keyboard_event.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/ui/views/menu_bar.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
// The menu bar height in pixels.
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const int kMenuBarHeight = 20;
|
||||
#else
|
||||
const int kMenuBarHeight = 25;
|
||||
#endif
|
||||
|
||||
bool IsAltKey(const content::NativeWebKeyboardEvent& event) {
|
||||
return event.windows_key_code == ui::VKEY_MENU;
|
||||
}
|
||||
@@ -41,6 +35,12 @@ RootView::RootView(NativeWindow* window)
|
||||
: window_(window),
|
||||
last_focused_view_tracker_(std::make_unique<views::ViewTracker>()) {
|
||||
set_owned_by_client();
|
||||
views::BoxLayout* layout =
|
||||
SetLayoutManager(std::make_unique<views::BoxLayout>(
|
||||
views::BoxLayout::Orientation::kVertical));
|
||||
main_view_ = AddChildView(std::make_unique<views::View>());
|
||||
main_view_->SetUseDefaultFillLayout(true);
|
||||
layout->SetFlexForView(main_view_, 1);
|
||||
}
|
||||
|
||||
RootView::~RootView() = default;
|
||||
@@ -77,7 +77,7 @@ bool RootView::HasMenu() const {
|
||||
}
|
||||
|
||||
int RootView::GetMenuBarHeight() const {
|
||||
return kMenuBarHeight;
|
||||
return menu_bar_ ? menu_bar_->GetPreferredSize().height() : 0;
|
||||
}
|
||||
|
||||
void RootView::SetAutoHideMenuBar(bool auto_hide) {
|
||||
@@ -90,10 +90,8 @@ void RootView::SetMenuBarVisibility(bool visible) {
|
||||
|
||||
menu_bar_visible_ = visible;
|
||||
if (visible) {
|
||||
DCHECK_EQ(children().size(), 1ul);
|
||||
AddChildView(menu_bar_.get());
|
||||
AddChildViewAt(menu_bar_.get(), 0);
|
||||
} else {
|
||||
DCHECK_EQ(children().size(), 2ul);
|
||||
RemoveChildView(menu_bar_.get());
|
||||
}
|
||||
|
||||
@@ -166,21 +164,6 @@ void RootView::ResetAltState() {
|
||||
menu_bar_alt_pressed_ = false;
|
||||
}
|
||||
|
||||
void RootView::Layout(PassKey) {
|
||||
if (!window_->content_view()) // Not ready yet.
|
||||
return;
|
||||
|
||||
const auto menu_bar_bounds =
|
||||
menu_bar_visible_ ? gfx::Rect(0, 0, size().width(), kMenuBarHeight)
|
||||
: gfx::Rect();
|
||||
if (menu_bar_)
|
||||
menu_bar_->SetBoundsRect(menu_bar_bounds);
|
||||
|
||||
window_->content_view()->SetBoundsRect(
|
||||
gfx::Rect(0, menu_bar_visible_ ? menu_bar_bounds.bottom() : 0,
|
||||
size().width(), size().height() - menu_bar_bounds.height()));
|
||||
}
|
||||
|
||||
gfx::Size RootView::GetMinimumSize() const {
|
||||
return window_->GetMinimumSize();
|
||||
}
|
||||
|
||||
@@ -46,8 +46,9 @@ class RootView : public views::View {
|
||||
void RegisterAcceleratorsWithFocusManager(ElectronMenuModel* menu_model);
|
||||
void UnregisterAcceleratorsWithFocusManager();
|
||||
|
||||
views::View* GetMainView() { return main_view_; }
|
||||
|
||||
// views::View:
|
||||
void Layout(PassKey) override;
|
||||
gfx::Size GetMinimumSize() const override;
|
||||
gfx::Size GetMaximumSize() const override;
|
||||
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
|
||||
@@ -62,6 +63,9 @@ class RootView : public views::View {
|
||||
bool menu_bar_visible_ = false;
|
||||
bool menu_bar_alt_pressed_ = false;
|
||||
|
||||
// Main view area.
|
||||
raw_ptr<views::View> main_view_;
|
||||
|
||||
// Map from accelerator to menu item's command id.
|
||||
accelerator_util::AcceleratorTable accelerator_table_;
|
||||
|
||||
|
||||
@@ -156,36 +156,26 @@ void WinCaptionButtonContainer::UpdateBackground() {
|
||||
}
|
||||
|
||||
void WinCaptionButtonContainer::UpdateButtons() {
|
||||
const bool is_maximized = frame_view_->frame()->IsMaximized();
|
||||
restore_button_->SetVisible(is_maximized);
|
||||
maximize_button_->SetVisible(!is_maximized);
|
||||
|
||||
const bool minimizable = frame_view_->window()->IsMinimizable();
|
||||
minimize_button_->SetEnabled(minimizable);
|
||||
minimize_button_->SetVisible(minimizable);
|
||||
|
||||
// In touch mode, windows cannot be taken out of fullscreen or tiled mode, so
|
||||
// the maximize/restore button should be disabled.
|
||||
const bool is_touch = ui::TouchUiController::Get()->touch_ui();
|
||||
restore_button_->SetEnabled(!is_touch);
|
||||
const bool is_maximized = frame_view_->frame()->IsMaximized();
|
||||
const bool maximizable = frame_view_->window()->IsMaximizable();
|
||||
restore_button_->SetVisible(is_maximized && maximizable);
|
||||
maximize_button_->SetVisible(!is_maximized && maximizable);
|
||||
|
||||
// In touch mode, windows cannot be taken out of fullscreen or tiled mode, so
|
||||
// the maximize/restore button should be disabled, unless the window is not
|
||||
// maximized.
|
||||
const bool maximizable = frame_view_->window()->IsMaximizable();
|
||||
maximize_button_->SetEnabled(!(is_touch && is_maximized) && maximizable);
|
||||
const bool is_touch = ui::TouchUiController::Get()->touch_ui();
|
||||
restore_button_->SetEnabled(!is_touch);
|
||||
maximize_button_->SetEnabled(!is_touch || !is_maximized);
|
||||
|
||||
// If the window isn't closable, the close button should be disabled.
|
||||
const bool closable = frame_view_->window()->IsClosable();
|
||||
close_button_->SetEnabled(closable);
|
||||
|
||||
// If all three of closable, maximizable, and minimizable are disabled,
|
||||
// Windows natively only shows the disabled closable button. Copy that
|
||||
// behavior here.
|
||||
if (!maximizable && !closable && !minimizable) {
|
||||
minimize_button_->SetVisible(false);
|
||||
maximize_button_->SetVisible(false);
|
||||
}
|
||||
|
||||
InvalidateLayout();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ class WebContentsPermissionHelper
|
||||
OPEN_EXTERNAL,
|
||||
SERIAL,
|
||||
HID,
|
||||
USB
|
||||
USB,
|
||||
KEYBOARD_LOCK,
|
||||
FILE_SYSTEM
|
||||
};
|
||||
|
||||
// Asynchronous Requests
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "third_party/skia/include/core/SkImageInfo.h"
|
||||
#include "third_party/skia/include/core/SkPixmap.h"
|
||||
#include "ui/base/clipboard/clipboard_format_type.h"
|
||||
#include "ui/base/clipboard/file_info.h"
|
||||
#include "ui/base/clipboard/scoped_clipboard_writer.h"
|
||||
#include "ui/gfx/codec/png_codec.h"
|
||||
|
||||
@@ -274,6 +275,17 @@ void Clipboard::Clear(gin_helper::Arguments* args) {
|
||||
ui::Clipboard::GetForCurrentThread()->Clear(GetClipboardBuffer(args));
|
||||
}
|
||||
|
||||
// This exists for testing purposes ONLY.
|
||||
void Clipboard::WriteFilesForTesting(const std::vector<base::FilePath>& files) {
|
||||
std::vector<ui::FileInfo> file_infos;
|
||||
for (const auto& file : files) {
|
||||
file_infos.emplace_back(ui::FileInfo(ui::FileInfo(file, file.BaseName())));
|
||||
}
|
||||
|
||||
ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
|
||||
writer.WriteFilenames(ui::FileInfosToURIList(file_infos));
|
||||
}
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
namespace {
|
||||
@@ -302,6 +314,8 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
dict.SetMethod("writeFindText", &electron::api::Clipboard::WriteFindText);
|
||||
dict.SetMethod("readBuffer", &electron::api::Clipboard::ReadBuffer);
|
||||
dict.SetMethod("writeBuffer", &electron::api::Clipboard::WriteBuffer);
|
||||
dict.SetMethod("_writeFilesForTesting",
|
||||
&electron::api::Clipboard::WriteFilesForTesting);
|
||||
dict.SetMethod("clear", &electron::api::Clipboard::Clear);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "ui/base/clipboard/clipboard.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "v8/include/v8.h"
|
||||
@@ -63,6 +64,8 @@ class Clipboard {
|
||||
static void WriteBuffer(const std::string& format_string,
|
||||
const v8::Local<v8::Value> buffer,
|
||||
gin_helper::Arguments* args);
|
||||
|
||||
static void WriteFilesForTesting(const std::vector<base::FilePath>& files);
|
||||
};
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -6,34 +6,85 @@
|
||||
// functionality that the PDF Viewer needs from outside the PDF plugin. This API
|
||||
// is exclusively for the PDF Viewer.
|
||||
namespace pdfViewerPrivate {
|
||||
// Nearly identical to mimeHandlerPrivate.StreamInfo, but without a mime type
|
||||
// nor a response header field. Those fields are unused by the PDF viewer.
|
||||
dictionary StreamInfo {
|
||||
// The original URL that was intercepted.
|
||||
DOMString originalUrl;
|
||||
|
||||
// The URL that the stream can be read from.
|
||||
DOMString streamUrl;
|
||||
|
||||
// The ID of the tab that opened the stream. If the stream is not opened in
|
||||
// a tab, it will be -1.
|
||||
long tabId;
|
||||
|
||||
// Whether the stream is embedded within another document.
|
||||
boolean embedded;
|
||||
};
|
||||
|
||||
// Identical to mimeHandlerPrivate.StreamInfo.
|
||||
dictionary PdfPluginAttributes {
|
||||
// The background color in ARGB format for painting. Since the background
|
||||
// color is an unsigned 32-bit integer which can be outside the range of
|
||||
// "long" type, define it as a "double" type here.
|
||||
double backgroundColor;
|
||||
|
||||
// Indicates whether the plugin allows to execute JavaScript and maybe XFA.
|
||||
// Loading XFA for PDF forms will automatically be disabled if this flag is
|
||||
// false.
|
||||
boolean allowJavascript;
|
||||
};
|
||||
|
||||
callback GetStreamInfoCallback = void(StreamInfo streamInfo);
|
||||
callback IsAllowedLocalFileAccessCallback = void(boolean result);
|
||||
callback IsPdfOcrAlwaysActiveCallback = void(boolean result);
|
||||
callback OnPdfOcrPrefSetCallback = void(boolean result);
|
||||
callback VoidCallback = void();
|
||||
|
||||
interface Functions {
|
||||
// Returns the StreamInfo for the stream for this context if there is one.
|
||||
static void getStreamInfo(
|
||||
GetStreamInfoCallback callback);
|
||||
|
||||
// Determines if the given URL should be allowed to access local files from
|
||||
// the PDF Viewer. |callback|: Called with true if URL should be allowed to
|
||||
// access local files from the PDF Viewer, false otherwise.
|
||||
[supportsPromises] static void isAllowedLocalFileAccess(
|
||||
static void isAllowedLocalFileAccess(
|
||||
DOMString url,
|
||||
IsAllowedLocalFileAccessCallback callback);
|
||||
|
||||
// Determines if the preference for PDF OCR is set to run PDF OCR always.
|
||||
// |callback|: Called with true if PDF OCR is set to be always active;
|
||||
// false otherwise.
|
||||
[supportsPromises] static void isPdfOcrAlwaysActive(
|
||||
static void isPdfOcrAlwaysActive(
|
||||
IsPdfOcrAlwaysActiveCallback callback);
|
||||
|
||||
// Sets the current tab title to `title` for a full-page PDF.
|
||||
static void setPdfDocumentTitle(
|
||||
DOMString title,
|
||||
optional VoidCallback callback);
|
||||
|
||||
// Sets a pref value for PDF OCR.
|
||||
// |value|: The new value of the pref.
|
||||
// |callback|: The callback for whether the pref was set or not.
|
||||
[supportsPromises] static void setPdfOcrPref(
|
||||
static void setPdfOcrPref(
|
||||
boolean value, OnPdfOcrPrefSetCallback callback);
|
||||
|
||||
// Sets PDF plugin attributes in the stream for this context if there is
|
||||
// one.
|
||||
static void setPdfPluginAttributes(
|
||||
PdfPluginAttributes attributes,
|
||||
optional VoidCallback callback);
|
||||
};
|
||||
|
||||
interface Events {
|
||||
// Fired when a pref value for PDF OCR has changed.
|
||||
// |value| The pref value that changed.
|
||||
static void onPdfOcrPrefChanged(boolean value);
|
||||
|
||||
// Fired when the browser wants the listener to perform a save.
|
||||
// `streamUrl`: Unique ID for the instance that should perform the save.
|
||||
static void onSave(DOMString streamUrl);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -229,6 +229,8 @@ v8::Local<v8::Value> Converter<blink::PermissionType>::ToV8(
|
||||
return StringToV8(isolate, "hid");
|
||||
case PermissionType::USB:
|
||||
return StringToV8(isolate, "usb");
|
||||
case PermissionType::FILE_SYSTEM:
|
||||
return StringToV8(isolate, "fileSystem");
|
||||
default:
|
||||
return StringToV8(isolate, "unknown");
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ bool IsAllowedOption(const std::string_view option) {
|
||||
// Initialize NODE_OPTIONS to pass to Node.js
|
||||
// See https://nodejs.org/api/cli.html#cli_node_options_options
|
||||
void SetNodeOptions(base::Environment* env) {
|
||||
// Options that are unilaterally disallowed
|
||||
// Options that are expressly disallowed
|
||||
static constexpr auto disallowed = base::MakeFixedFlatSet<std::string_view>({
|
||||
"--enable-fips",
|
||||
"--experimental-policy",
|
||||
@@ -353,6 +353,13 @@ void SetNodeOptions(base::Environment* env) {
|
||||
"--max-http-header-size",
|
||||
});
|
||||
|
||||
if (env->HasVar("NODE_EXTRA_CA_CERTS")) {
|
||||
if (!electron::fuses::IsNodeOptionsEnabled()) {
|
||||
LOG(WARNING) << "NODE_OPTIONS ignored due to disabled nodeOptions fuse.";
|
||||
env->UnSetVar("NODE_EXTRA_CA_CERTS");
|
||||
}
|
||||
}
|
||||
|
||||
if (env->HasVar("NODE_OPTIONS")) {
|
||||
if (electron::fuses::IsNodeOptionsEnabled()) {
|
||||
std::string options;
|
||||
@@ -383,8 +390,8 @@ void SetNodeOptions(base::Environment* env) {
|
||||
// overwrite new NODE_OPTIONS without unsupported variables
|
||||
env->SetVar("NODE_OPTIONS", options);
|
||||
} else {
|
||||
LOG(ERROR) << "NODE_OPTIONS have been disabled in this app";
|
||||
env->SetVar("NODE_OPTIONS", "");
|
||||
LOG(WARNING) << "NODE_OPTIONS ignored due to disabled nodeOptions fuse.";
|
||||
env->UnSetVar("NODE_OPTIONS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const isScaleFactorRounding = () => {
|
||||
|
||||
const expectBoundsEqual = (actual: any, expected: any) => {
|
||||
if (!isScaleFactorRounding()) {
|
||||
expect(expected).to.deep.equal(actual);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
} else if (Array.isArray(actual)) {
|
||||
expect(actual[0]).to.be.closeTo(expected[0], 1);
|
||||
expect(actual[1]).to.be.closeTo(expected[1], 1);
|
||||
|
||||
@@ -177,6 +177,38 @@ describe('debugger module', () => {
|
||||
await loadingFinished;
|
||||
});
|
||||
|
||||
it('can get and set cookies using the Storage API', async () => {
|
||||
await w.webContents.loadURL('about:blank');
|
||||
w.webContents.debugger.attach('1.1');
|
||||
|
||||
await w.webContents.debugger.sendCommand('Storage.clearCookies', {});
|
||||
await w.webContents.debugger.sendCommand('Storage.setCookies', {
|
||||
cookies: [
|
||||
{
|
||||
name: 'cookieOne',
|
||||
value: 'cookieValueOne',
|
||||
url: 'https://cookieone.com'
|
||||
},
|
||||
{
|
||||
name: 'cookieTwo',
|
||||
value: 'cookieValueTwo',
|
||||
url: 'https://cookietwo.com'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { cookies } = await w.webContents.debugger.sendCommand('Storage.getCookies', {});
|
||||
expect(cookies).to.have.lengthOf(2);
|
||||
|
||||
const cookieOne = cookies.find((cookie: any) => cookie.name === 'cookieOne');
|
||||
expect(cookieOne.domain).to.equal('cookieone.com');
|
||||
expect(cookieOne.value).to.equal('cookieValueOne');
|
||||
|
||||
const cookieTwo = cookies.find((cookie: any) => cookie.name === 'cookieTwo');
|
||||
expect(cookieTwo.domain).to.equal('cookietwo.com');
|
||||
expect(cookieTwo.value).to.equal('cookieValueTwo');
|
||||
});
|
||||
|
||||
it('uses empty sessionId by default', async () => {
|
||||
w.webContents.loadURL('about:blank');
|
||||
w.webContents.debugger.attach();
|
||||
|
||||
@@ -935,12 +935,11 @@ describe('net module', () => {
|
||||
response.end();
|
||||
});
|
||||
const serverUrl = url.parse(serverUrlUnparsed);
|
||||
const options = {
|
||||
const urlRequest = net.request({
|
||||
port: serverUrl.port ? parseInt(serverUrl.port, 10) : undefined,
|
||||
hostname: '127.0.0.1',
|
||||
headers: { [customHeaderName]: customHeaderValue }
|
||||
};
|
||||
const urlRequest = net.request(options);
|
||||
});
|
||||
const response = await getResponse(urlRequest);
|
||||
expect(response.statusCode).to.be.equal(200);
|
||||
await collectStreamBody(response);
|
||||
|
||||
@@ -1613,7 +1613,7 @@ describe('session module', () => {
|
||||
|
||||
// NOTE: This API clears more than localStorage, but localStorage is a
|
||||
// convenient test target for this API
|
||||
it('clears localstorage data', async () => {
|
||||
it('clears all data when no options supplied', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
|
||||
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'));
|
||||
|
||||
@@ -1623,7 +1623,8 @@ describe('session module', () => {
|
||||
|
||||
expect(await w.webContents.executeJavaScript('localStorage.length')).to.equal(0);
|
||||
});
|
||||
it('clears localstorage data when called twice in parallel', async () => {
|
||||
|
||||
it('clears all data when no options supplied, called twice in parallel', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
|
||||
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'));
|
||||
|
||||
@@ -1638,5 +1639,92 @@ describe('session module', () => {
|
||||
// Await the first promise so it doesn't creep into another test
|
||||
await clearDataPromise;
|
||||
});
|
||||
|
||||
it('only clears specified data categories', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: { nodeIntegration: true, contextIsolation: false }
|
||||
});
|
||||
await w.loadFile(
|
||||
path.join(fixtures, 'api', 'localstorage-and-indexeddb.html')
|
||||
);
|
||||
|
||||
const { webContents } = w;
|
||||
const { session } = webContents;
|
||||
|
||||
await once(ipcMain, 'indexeddb-ready');
|
||||
|
||||
async function queryData (channel: string): Promise<string> {
|
||||
const event = once(ipcMain, `result-${channel}`);
|
||||
webContents.send(`get-${channel}`);
|
||||
return (await event)[1];
|
||||
}
|
||||
|
||||
// Data is in localStorage
|
||||
await expect(queryData('localstorage')).to.eventually.equal('hello localstorage');
|
||||
// Data is in indexedDB
|
||||
await expect(queryData('indexeddb')).to.eventually.equal('hello indexeddb');
|
||||
|
||||
// Clear only indexedDB, not localStorage
|
||||
await session.clearData({ dataTypes: ['indexedDB'] });
|
||||
|
||||
// The localStorage data should still be there
|
||||
await expect(queryData('localstorage')).to.eventually.equal('hello localstorage');
|
||||
|
||||
// The indexedDB data should be gone
|
||||
await expect(queryData('indexeddb')).to.eventually.be.undefined();
|
||||
});
|
||||
|
||||
it('only clears the specified origins', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
|
||||
const { session } = w.webContents;
|
||||
const { cookies } = session;
|
||||
|
||||
await Promise.all([
|
||||
cookies.set({
|
||||
url: 'https://example.com/',
|
||||
name: 'testdotcom',
|
||||
value: 'testdotcom'
|
||||
}),
|
||||
cookies.set({
|
||||
url: 'https://example.org/',
|
||||
name: 'testdotorg',
|
||||
value: 'testdotorg'
|
||||
})
|
||||
]);
|
||||
|
||||
await session.clearData({ origins: ['https://example.com'] });
|
||||
|
||||
expect((await cookies.get({ url: 'https://example.com/', name: 'testdotcom' })).length).to.equal(0);
|
||||
expect((await cookies.get({ url: 'https://example.org/', name: 'testdotorg' })).length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('clears all except the specified origins', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
|
||||
const { session } = w.webContents;
|
||||
const { cookies } = session;
|
||||
|
||||
await Promise.all([
|
||||
cookies.set({
|
||||
url: 'https://example.com/',
|
||||
name: 'testdotcom',
|
||||
value: 'testdotcom'
|
||||
}),
|
||||
cookies.set({
|
||||
url: 'https://example.org/',
|
||||
name: 'testdotorg',
|
||||
value: 'testdotorg'
|
||||
})
|
||||
]);
|
||||
|
||||
await session.clearData({ excludeOrigins: ['https://example.com'] });
|
||||
|
||||
expect((await cookies.get({ url: 'https://example.com/', name: 'testdotcom' })).length).to.be.greaterThan(0);
|
||||
expect((await cookies.get({ url: 'https://example.org/', name: 'testdotorg' })).length).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect } from 'chai';
|
||||
import { BrowserWindow, WebContents, webFrameMain, session, ipcMain, app, protocol, webContents, dialog, MessageBoxOptions } from 'electron/main';
|
||||
import { clipboard } from 'electron/common';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import * as https from 'node:https';
|
||||
import * as http from 'node:http';
|
||||
@@ -13,6 +14,7 @@ import { PipeTransport } from './pipe-transport';
|
||||
import * as ws from 'ws';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { AddressInfo } from 'node:net';
|
||||
import { MediaAccessPermissionRequest } from 'electron';
|
||||
|
||||
const features = process._linkedBinding('electron_common_features');
|
||||
|
||||
@@ -846,6 +848,129 @@ describe('chromium features', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('File System API,', () => {
|
||||
afterEach(closeAllWindows);
|
||||
afterEach(() => {
|
||||
session.defaultSession.setPermissionRequestHandler(null);
|
||||
});
|
||||
|
||||
it('allows access by default to reading an OPFS file', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
partition: 'file-system-spec',
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
|
||||
const result = await w.webContents.executeJavaScript(`
|
||||
new Promise(async (resolve, reject) => {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const fileHandle = await root.getFileHandle('test', { create: true });
|
||||
const { name, size } = await fileHandle.getFile();
|
||||
resolve({ name, size });
|
||||
}
|
||||
)`, true);
|
||||
expect(result).to.deep.equal({ name: 'test', size: 0 });
|
||||
});
|
||||
|
||||
it('fileHandle.queryPermission by default has permission to read and write to OPFS files', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
partition: 'file-system-spec',
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
|
||||
const status = await w.webContents.executeJavaScript(`
|
||||
new Promise(async (resolve, reject) => {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const fileHandle = await root.getFileHandle('test', { create: true });
|
||||
const status = await fileHandle.queryPermission({ mode: 'readwrite' });
|
||||
resolve(status);
|
||||
}
|
||||
)`, true);
|
||||
expect(status).to.equal('granted');
|
||||
});
|
||||
|
||||
it('fileHandle.requestPermission automatically grants permission to read and write to OPFS files', async () => {
|
||||
const w = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
partition: 'file-system-spec',
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
|
||||
const status = await w.webContents.executeJavaScript(`
|
||||
new Promise(async (resolve, reject) => {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const fileHandle = await root.getFileHandle('test', { create: true });
|
||||
const status = await fileHandle.requestPermission({ mode: 'readwrite' });
|
||||
resolve(status);
|
||||
}
|
||||
)`, true);
|
||||
expect(status).to.equal('granted');
|
||||
});
|
||||
|
||||
it('requests permission when trying to create a writable file handle', (done) => {
|
||||
const writablePath = path.join(fixturesPath, 'file-system', 'test-writable.html');
|
||||
const testFile = path.join(fixturesPath, 'file-system', 'test.txt');
|
||||
|
||||
const w = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
sandbox: false
|
||||
}
|
||||
});
|
||||
|
||||
w.webContents.session.setPermissionRequestHandler((wc, permission, callback, details) => {
|
||||
expect(permission).to.equal('fileSystem');
|
||||
|
||||
const { href } = url.pathToFileURL(writablePath);
|
||||
expect(details).to.deep.equal({
|
||||
fileAccessType: 'writable',
|
||||
isDirectory: false,
|
||||
isMainFrame: true,
|
||||
filePath: testFile,
|
||||
requestingUrl: href
|
||||
});
|
||||
|
||||
callback(true);
|
||||
});
|
||||
|
||||
ipcMain.once('did-create-file-handle', async () => {
|
||||
const result = await w.webContents.executeJavaScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const writable = fileHandle.createWritable();
|
||||
resolve(true);
|
||||
} catch {
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
`, true);
|
||||
expect(result).to.be.true();
|
||||
done();
|
||||
});
|
||||
|
||||
w.loadFile(writablePath);
|
||||
|
||||
w.webContents.once('did-finish-load', () => {
|
||||
// @ts-expect-error Undocumented testing method.
|
||||
clipboard._writeFilesForTesting([testFile]);
|
||||
w.webContents.paste();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('web workers', () => {
|
||||
let appProcess: ChildProcess.ChildProcessWithoutNullStreams | undefined;
|
||||
|
||||
@@ -1663,7 +1788,7 @@ describe('chromium features', () => {
|
||||
it('provides a securityOrigin to the request handler', async () => {
|
||||
session.defaultSession.setPermissionRequestHandler(
|
||||
(wc, permission, callback, details) => {
|
||||
if (details.securityOrigin !== undefined) {
|
||||
if ((details as MediaAccessPermissionRequest).securityOrigin !== undefined) {
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false);
|
||||
|
||||
50
spec/fixtures/api/localstorage-and-indexeddb.html
vendored
Normal file
50
spec/fixtures/api/localstorage-and-indexeddb.html
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<html>
|
||||
<body>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
window.localStorage.setItem('test', 'hello localstorage');
|
||||
ipcRenderer.on('get-localstorage', () => {
|
||||
const result = window.localStorage.getItem('test');
|
||||
ipcRenderer.send('result-localstorage', result);
|
||||
});
|
||||
|
||||
const deleteRequest = window.indexedDB.deleteDatabase('testdb');
|
||||
deleteRequest.onerror = deleteRequest.onsuccess = (event) => {
|
||||
const openRequest = window.indexedDB.open('testdb');
|
||||
openRequest.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
db.onerror = (event) => {
|
||||
console.error(event);
|
||||
};
|
||||
const objectStore = db.createObjectStore('testdata');
|
||||
objectStore.createIndex('test', '');
|
||||
};
|
||||
openRequest.onsuccess = (event) => {
|
||||
const db = event.target.result;
|
||||
const addRequest = db.transaction("testdata", "readwrite").objectStore("testdata").add("hello indexeddb", 'test');
|
||||
addRequest.onsuccess = () => {
|
||||
ipcRenderer.send('indexeddb-ready');
|
||||
};
|
||||
};
|
||||
};
|
||||
ipcRenderer.on('get-indexeddb', () => {
|
||||
const openRequest = window.indexedDB.open('testdb');
|
||||
openRequest.onerror = (event) => {
|
||||
console.error(event);
|
||||
};
|
||||
openRequest.onsuccess = (event) => {
|
||||
const db = event.target.result;
|
||||
if (!db.objectStoreNames.contains('testdata')) {
|
||||
ipcRenderer.send('result-indexeddb', undefined);
|
||||
return;
|
||||
}
|
||||
const getRequest = db.transaction('testdata', 'readonly').objectStore('testdata').get('test');
|
||||
getRequest.onsuccess = (event) => {
|
||||
ipcRenderer.send('result-indexeddb', event.target.result);
|
||||
};
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
spec/fixtures/file-system/test-writable.html
vendored
Normal file
26
spec/fixtures/file-system/test-writable.html
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
let fileHandle = null;
|
||||
let sent = false;
|
||||
window.document.onpaste = async (event) => {
|
||||
const fileItem = event.clipboardData.items[0];
|
||||
fileHandle = await fileItem.getAsFileSystemHandle();
|
||||
if (!sent) {
|
||||
ipcRenderer.send('did-create-file-handle');
|
||||
sent = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
spec/fixtures/file-system/test.txt
vendored
Normal file
1
spec/fixtures/file-system/test.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello world
|
||||
@@ -14,8 +14,6 @@ import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
||||
declare let WebView: any;
|
||||
const features = process._linkedBinding('electron_common_features');
|
||||
|
||||
const isMacArm64 = (process.platform === 'darwin' && process.arch === 'arm64');
|
||||
|
||||
async function loadWebView (w: WebContents, attributes: Record<string, string>, opts?: {openDevTools?: boolean}): Promise<void> {
|
||||
const { openDevTools } = {
|
||||
openDevTools: false,
|
||||
@@ -2107,9 +2105,8 @@ describe('<webview> tag', function () {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(miniak): figure out why this is failing on windows
|
||||
// TODO(vertedinde): figure out why this is failing on mac arm64
|
||||
ifdescribe(process.platform !== 'win32' && !isMacArm64)('<webview>.capturePage()', () => {
|
||||
// FIXME: This test is flaking constantly on Linux and macOS.
|
||||
xdescribe('<webview>.capturePage()', () => {
|
||||
it('returns a Promise with a NativeImage', async function () {
|
||||
this.retries(5);
|
||||
|
||||
|
||||
65
yarn.lock
65
yarn.lock
@@ -199,10 +199,10 @@
|
||||
"@octokit/auth-app" "^4.0.13"
|
||||
"@octokit/rest" "^19.0.11"
|
||||
|
||||
"@electron/lint-roller@^1.9.0":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.11.1.tgz#bf4ab114e8cb3a77e2c634807d4af3d40c3ba863"
|
||||
integrity sha512-LfErOK5MnSmoSyVN5OnHWsQ8p5It8JBE0AmRYCx65WS4BZudnYeRcohlRSOSi+GGqldujdKHyEo+fdY5lREL/Q==
|
||||
"@electron/lint-roller@^1.12.1":
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.12.1.tgz#3152b9a68815b2ab51cc5a4d462ae6769d5052ce"
|
||||
integrity sha512-TGgVcHUAooM9/dV3iJTxhmKl35x/gzStsClz2/LWtBQZ59cRK+0YwWF5HWhtydGFIpOLEQGzCvUrty5zZLyd4w==
|
||||
dependencies:
|
||||
"@dsanders11/vscode-markdown-languageservice" "^0.3.0"
|
||||
balanced-match "^2.0.0"
|
||||
@@ -1599,13 +1599,13 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
|
||||
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
|
||||
|
||||
body-parser@1.20.1:
|
||||
version "1.20.1"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
|
||||
integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
|
||||
body-parser@1.20.2:
|
||||
version "1.20.2"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
|
||||
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
|
||||
dependencies:
|
||||
bytes "3.1.2"
|
||||
content-type "~1.0.4"
|
||||
content-type "~1.0.5"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
destroy "1.2.0"
|
||||
@@ -1613,7 +1613,7 @@ body-parser@1.20.1:
|
||||
iconv-lite "0.4.24"
|
||||
on-finished "2.4.1"
|
||||
qs "6.11.0"
|
||||
raw-body "2.5.1"
|
||||
raw-body "2.5.2"
|
||||
type-is "~1.6.18"
|
||||
unpipe "1.0.0"
|
||||
|
||||
@@ -2017,20 +2017,20 @@ content-disposition@0.5.4:
|
||||
dependencies:
|
||||
safe-buffer "5.2.1"
|
||||
|
||||
content-type@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
content-type@~1.0.4, content-type@~1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||
cookie@0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
@@ -2071,14 +2071,7 @@ debug@^3.1.0, debug@^3.2.7:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.0.0:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
|
||||
debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@@ -2808,17 +2801,17 @@ execa@^4.0.1:
|
||||
signal-exit "^3.0.2"
|
||||
strip-final-newline "^2.0.0"
|
||||
|
||||
express@^4.16.4:
|
||||
version "4.18.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
|
||||
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
|
||||
express@^4.19.2:
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
|
||||
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
|
||||
dependencies:
|
||||
accepts "~1.3.8"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.20.1"
|
||||
body-parser "1.20.2"
|
||||
content-disposition "0.5.4"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.5.0"
|
||||
cookie "0.6.0"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
@@ -5243,10 +5236,10 @@ range-parser@~1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
raw-body@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
|
||||
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
|
||||
raw-body@2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
|
||||
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
|
||||
dependencies:
|
||||
bytes "3.1.2"
|
||||
http-errors "2.0.0"
|
||||
|
||||
Reference in New Issue
Block a user