mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
26 Commits
miniak/lis
...
no-onwindo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15bd6948d1 | ||
|
|
ba3b647fd7 | ||
|
|
76f7bbb0a8 | ||
|
|
22c149812c | ||
|
|
42164d7081 | ||
|
|
3eb94b72ba | ||
|
|
752f2eb124 | ||
|
|
beafbfd511 | ||
|
|
d54645e554 | ||
|
|
0bf53a3876 | ||
|
|
62d4b21819 | ||
|
|
61457c9498 | ||
|
|
c6102b9278 | ||
|
|
72c2b9e862 | ||
|
|
08241669bc | ||
|
|
6e36153799 | ||
|
|
4f76fff978 | ||
|
|
c82ec0c72b | ||
|
|
c57ce31e84 | ||
|
|
32b44aa5c8 | ||
|
|
7032c0d03c | ||
|
|
d3d9e919a8 | ||
|
|
9a51f74bd8 | ||
|
|
4f6712c8e8 | ||
|
|
ebdedf6c02 | ||
|
|
e2bf1aa5fd |
4
.github/workflows/issue-opened.yml
vendored
4
.github/workflows/issue-opened.yml
vendored
@@ -38,6 +38,8 @@ jobs:
|
||||
- run: npm install mdast-util-from-markdown@2.0.0 unist-util-select@5.1.0
|
||||
- name: Add labels
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
env:
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
@@ -47,7 +49,7 @@ jobs:
|
||||
const [ owner, repo ] = '${{ github.repository }}'.split('/');
|
||||
const issue_number = ${{ github.event.issue.number }};
|
||||
|
||||
const tree = fromMarkdown(`${{ github.event.issue.body }}`);
|
||||
const tree = fromMarkdown(process.env.ISSUE_BODY);
|
||||
|
||||
const labels = [];
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -814,6 +814,8 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
* `keyboardLock` - Request capture of keypresses for any or all of the keys on the physical keyboard via the [Keyboard Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/lock). These requests always appear to originate from the main frame.
|
||||
* `openExternal` - Request to open links in external applications.
|
||||
* `speaker-selection` - Request to enumerate and select audio output devices via the [speaker-selection permissions policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection).
|
||||
* `storage-access` - Allows content loaded in a third-party context to request access to third-party cookies using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
|
||||
* `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.
|
||||
* `callback` Function
|
||||
@@ -862,6 +864,8 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||
* `openExternal` - Open links in external applications.
|
||||
* `pointerLock` - Directly interpret mouse movements as an input method via the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API). These requests always appear to originate from the main frame.
|
||||
* `serial` - Read from and write to serial devices with the [Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API).
|
||||
* `storage-access` - Allows content loaded in a third-party context to request access to third-party cookies using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
|
||||
* `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).
|
||||
* `usb` - Expose non-standard Universal Serial Bus (USB) compatible devices services to the web with the [WebUSB API](https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API).
|
||||
* `requestingOrigin` string - The origin URL of the permission check
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
@@ -1424,23 +1428,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).
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -209,6 +209,22 @@ type ExtraURLLoaderOptions = {
|
||||
headers: Record<string, { name: string, value: string | string[] }>;
|
||||
allowNonHttpProtocols: boolean;
|
||||
}
|
||||
|
||||
function validateHeader (name: any, value: any): void {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)');
|
||||
}
|
||||
if (value == null) {
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)');
|
||||
}
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`);
|
||||
}
|
||||
if (!isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
|
||||
@@ -275,12 +291,7 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
|
||||
};
|
||||
const headers: Record<string, string | string[]> = options.headers || {};
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`);
|
||||
}
|
||||
if (!isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`);
|
||||
}
|
||||
validateHeader(name, value);
|
||||
const key = name.toLowerCase();
|
||||
urlLoaderOptions.headers[key] = { name, value };
|
||||
}
|
||||
@@ -351,21 +362,10 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
}
|
||||
|
||||
setHeader (name: string, value: string) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)');
|
||||
}
|
||||
if (value == null) {
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)');
|
||||
}
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t set headers after they are sent');
|
||||
}
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`);
|
||||
}
|
||||
if (!isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`);
|
||||
}
|
||||
validateHeader(name, value);
|
||||
|
||||
const key = name.toLowerCase();
|
||||
this._urlLoaderOptions.headers[key] = { name, value };
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -372,16 +380,6 @@ void BrowserWindow::NotifyWindowUnresponsive() {
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserWindow::OnWindowShow() {
|
||||
web_contents()->WasShown();
|
||||
BaseWindow::OnWindowShow();
|
||||
}
|
||||
|
||||
void BrowserWindow::OnWindowHide() {
|
||||
web_contents()->WasOccluded();
|
||||
BaseWindow::OnWindowHide();
|
||||
}
|
||||
|
||||
// static
|
||||
gin_helper::WrappableBase* BrowserWindow::New(gin_helper::ErrorThrower thrower,
|
||||
gin::Arguments* args) {
|
||||
|
||||
@@ -68,8 +68,6 @@ class BrowserWindow : public BaseWindow,
|
||||
void Focus() override;
|
||||
void Blur() override;
|
||||
void SetBackgroundColor(const std::string& color_name) override;
|
||||
void OnWindowShow() override;
|
||||
void OnWindowHide() override;
|
||||
|
||||
// BrowserWindow APIs.
|
||||
void FocusOnWebView();
|
||||
@@ -95,6 +93,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);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "extensions/browser/api/scripting/scripting_utils.h"
|
||||
#include "extensions/browser/extension_api_frame_id_map.h"
|
||||
#include "extensions/browser/extension_file_task_runner.h"
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
#include "extensions/browser/extension_system.h"
|
||||
#include "extensions/browser/extension_user_script_loader.h"
|
||||
#include "extensions/browser/extension_util.h"
|
||||
@@ -1061,6 +1062,17 @@ void ScriptingRegisterContentScriptsFunction::OnContentScriptFilesValidated(
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot proceed if the extension is uninstalled or unloaded in the middle
|
||||
// of validating its script files.
|
||||
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
|
||||
if (!extension() ||
|
||||
!registry->enabled_extensions().Contains(extension_id())) {
|
||||
// Note: a Respond() is not needed if the system is shutting down or if the
|
||||
// extension is no longer enabled.
|
||||
Release(); // Matches the `AddRef()` in `Run()`.
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = std::move(result.second);
|
||||
auto scripts = std::move(result.first);
|
||||
ExtensionUserScriptLoader* loader =
|
||||
@@ -1306,6 +1318,17 @@ void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot proceed if the extension is uninstalled or unloaded in the middle
|
||||
// of validating its script files.
|
||||
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
|
||||
if (!extension() ||
|
||||
!registry->enabled_extensions().Contains(extension_id())) {
|
||||
// Note: a Respond() is not needed if the system is shutting down or if the
|
||||
// extension is no longer enabled.
|
||||
Release(); // Matches the `AddRef()` in `Run()`.
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = std::move(result.second);
|
||||
auto scripts = std::move(result.first);
|
||||
ExtensionUserScriptLoader* loader =
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -159,21 +159,21 @@ void LibnotifyNotification::Show(const NotificationOptions& options) {
|
||||
|
||||
void LibnotifyNotification::Dismiss() {
|
||||
if (!notification_) {
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
on_dismissing_ = true;
|
||||
libnotify_loader_.notify_notification_close(notification_, &error);
|
||||
if (error) {
|
||||
log_and_clear_error(error, "notify_notification_close");
|
||||
Destroy();
|
||||
}
|
||||
on_dismissing_ = false;
|
||||
}
|
||||
|
||||
void LibnotifyNotification::OnNotificationClosed(
|
||||
NotifyNotification* notification) {
|
||||
NotificationDismissed();
|
||||
NotificationDismissed(!on_dismissing_);
|
||||
}
|
||||
|
||||
void LibnotifyNotification::OnNotificationView(NotifyNotification* notification,
|
||||
|
||||
@@ -33,6 +33,7 @@ class LibnotifyNotification : public Notification {
|
||||
RAW_PTR_EXCLUSION NotifyNotification* notification_ = nullptr;
|
||||
|
||||
ScopedGSignal signal_;
|
||||
bool on_dismissing_ = false;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,14 @@
|
||||
"contexts": ["blessed_extension", "unblessed_extension", "content_script"],
|
||||
"max_manifest_version": 2
|
||||
},
|
||||
"extension.lastError": {
|
||||
"contexts": [
|
||||
"blessed_extension",
|
||||
"unblessed_extension",
|
||||
"content_script"
|
||||
],
|
||||
"max_manifest_version": 2
|
||||
},
|
||||
"i18n": {
|
||||
"channel": "stable",
|
||||
"extension_types": ["extension"],
|
||||
@@ -68,5 +76,10 @@
|
||||
"scripting": {
|
||||
"dependencies": ["permission:scripting"],
|
||||
"contexts": ["blessed_extension"]
|
||||
},
|
||||
"scripting.globalParams": {
|
||||
"channel": "trunk",
|
||||
"dependencies": ["permission:scripting"],
|
||||
"contexts": ["content_script"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -616,6 +616,25 @@ describe('net module (session)', () => {
|
||||
});
|
||||
}).to.throw('`partition` should be a string');
|
||||
});
|
||||
|
||||
it('should throw if given a header value that is empty(null/undefined)', () => {
|
||||
const emptyHeaderValues = [null, undefined];
|
||||
const errorMsg = '`value` required in setHeader("foo", value)';
|
||||
|
||||
for (const emptyValue of emptyHeaderValues) {
|
||||
expect(() => {
|
||||
net.request({
|
||||
url: 'https://foo',
|
||||
headers: { foo: emptyValue as any }
|
||||
} as any);
|
||||
}).to.throw(errorMsg);
|
||||
|
||||
const request = net.request({ url: 'https://foo' });
|
||||
expect(() => {
|
||||
request.setHeader('foo', emptyValue as any);
|
||||
}).to.throw(errorMsg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('net.fetch', () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -548,6 +548,9 @@ describe('webContents module', () => {
|
||||
|
||||
describe('navigationHistory', () => {
|
||||
let w: BrowserWindow;
|
||||
const urlPage1 = 'data:text/html,<html><head><script>document.title = "Page 1";</script></head><body></body></html>';
|
||||
const urlPage2 = 'data:text/html,<html><head><script>document.title = "Page 2";</script></head><body></body></html>';
|
||||
const urlPage3 = 'data:text/html,<html><head><script>document.title = "Page 3";</script></head><body></body></html>';
|
||||
|
||||
beforeEach(async () => {
|
||||
w = new BrowserWindow({ show: false });
|
||||
@@ -559,44 +562,32 @@ describe('webContents module', () => {
|
||||
expect(result).to.deep.equal({ url: '', title: '' });
|
||||
});
|
||||
it('should fetch navigation entry given a valid index', async () => {
|
||||
await w.loadURL('https://www.google.com');
|
||||
w.webContents.on('did-finish-load', async () => {
|
||||
const result = w.webContents.navigationHistory.getEntryAtIndex(0);
|
||||
expect(result).to.deep.equal({ url: 'https://www.google.com/', title: 'Google' });
|
||||
});
|
||||
await w.loadURL(urlPage1);
|
||||
const result = w.webContents.navigationHistory.getEntryAtIndex(0);
|
||||
expect(result).to.deep.equal({ url: urlPage1, title: 'Page 1' });
|
||||
});
|
||||
it('should return null given an invalid index larger than history length', async () => {
|
||||
await w.loadURL('https://www.google.com');
|
||||
w.webContents.on('did-finish-load', async () => {
|
||||
const result = w.webContents.navigationHistory.getEntryAtIndex(5);
|
||||
expect(result).to.be.null();
|
||||
});
|
||||
await w.loadURL(urlPage1);
|
||||
const result = w.webContents.navigationHistory.getEntryAtIndex(5);
|
||||
expect(result).to.be.null();
|
||||
});
|
||||
it('should return null given an invalid negative index', async () => {
|
||||
await w.loadURL('https://www.google.com');
|
||||
w.webContents.on('did-finish-load', async () => {
|
||||
const result = w.webContents.navigationHistory.getEntryAtIndex(-1);
|
||||
expect(result).to.be.null();
|
||||
});
|
||||
await w.loadURL(urlPage1);
|
||||
const result = w.webContents.navigationHistory.getEntryAtIndex(-1);
|
||||
expect(result).to.be.null();
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigationHistory.getActiveIndex() API', () => {
|
||||
it('should return valid active index after a single page visit', async () => {
|
||||
await w.loadURL('https://www.google.com');
|
||||
w.webContents.on('did-finish-load', async () => {
|
||||
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(0);
|
||||
});
|
||||
await w.loadURL(urlPage1);
|
||||
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(0);
|
||||
});
|
||||
|
||||
it('should return valid active index after a multiple page visits', async () => {
|
||||
const loadPromise = once(w.webContents, 'did-finish-load');
|
||||
await w.loadURL('https://www.github.com');
|
||||
await loadPromise;
|
||||
await w.loadURL('https://www.google.com');
|
||||
await loadPromise;
|
||||
await w.loadURL('about:blank');
|
||||
await loadPromise;
|
||||
await w.loadURL(urlPage1);
|
||||
await w.loadURL(urlPage2);
|
||||
await w.loadURL(urlPage3);
|
||||
|
||||
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(2);
|
||||
});
|
||||
@@ -608,20 +599,14 @@ describe('webContents module', () => {
|
||||
|
||||
describe('navigationHistory.length() API', () => {
|
||||
it('should return valid history length after a single page visit', async () => {
|
||||
await w.loadURL('https://www.google.com');
|
||||
w.webContents.on('did-finish-load', async () => {
|
||||
expect(w.webContents.navigationHistory.length()).to.equal(1);
|
||||
});
|
||||
await w.loadURL(urlPage1);
|
||||
expect(w.webContents.navigationHistory.length()).to.equal(1);
|
||||
});
|
||||
|
||||
it('should return valid history length after a multiple page visits', async () => {
|
||||
const loadPromise = once(w.webContents, 'did-finish-load');
|
||||
await w.loadURL('https://www.github.com');
|
||||
await loadPromise;
|
||||
await w.loadURL('https://www.google.com');
|
||||
await loadPromise;
|
||||
await w.loadURL('about:blank');
|
||||
await loadPromise;
|
||||
await w.loadURL(urlPage1);
|
||||
await w.loadURL(urlPage2);
|
||||
await w.loadURL(urlPage3);
|
||||
|
||||
expect(w.webContents.navigationHistory.length()).to.equal(3);
|
||||
});
|
||||
|
||||
@@ -1383,6 +1383,138 @@ describe('chromium features', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Storage Access API', () => {
|
||||
afterEach(closeAllWindows);
|
||||
afterEach(() => {
|
||||
session.defaultSession.setPermissionCheckHandler(null);
|
||||
session.defaultSession.setPermissionRequestHandler(null);
|
||||
});
|
||||
|
||||
it('can determine if a permission is granted for "storage-access"', async () => {
|
||||
session.defaultSession.setPermissionCheckHandler(
|
||||
(_wc, permission) => permission === 'storage-access'
|
||||
);
|
||||
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
|
||||
|
||||
const permission = await w.webContents.executeJavaScript(`
|
||||
navigator.permissions.query({ name: 'storage-access' })
|
||||
.then(permission => permission.state).catch(err => err.message);
|
||||
`, true);
|
||||
|
||||
expect(permission).to.eq('granted');
|
||||
});
|
||||
|
||||
it('can determine if a permission is denied for "storage-access"', async () => {
|
||||
session.defaultSession.setPermissionCheckHandler(
|
||||
(_wc, permission) => permission !== 'storage-access'
|
||||
);
|
||||
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
|
||||
|
||||
const permission = await w.webContents.executeJavaScript(`
|
||||
navigator.permissions.query({ name: 'storage-access' })
|
||||
.then(permission => permission.state).catch(err => err.message);
|
||||
`, true);
|
||||
|
||||
expect(permission).to.eq('denied');
|
||||
});
|
||||
|
||||
it('can determine if a permission is granted for "top-level-storage-access"', async () => {
|
||||
session.defaultSession.setPermissionCheckHandler(
|
||||
(_wc, permission) => permission === 'top-level-storage-access'
|
||||
);
|
||||
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
|
||||
|
||||
const permission = await w.webContents.executeJavaScript(`
|
||||
navigator.permissions.query({
|
||||
name: 'top-level-storage-access',
|
||||
requestedOrigin: "https://www.example.com",
|
||||
}).then(permission => permission.state).catch(err => err.message);
|
||||
`, true);
|
||||
|
||||
expect(permission).to.eq('granted');
|
||||
});
|
||||
|
||||
it('can determine if a permission is denied for "top-level-storage-access"', async () => {
|
||||
session.defaultSession.setPermissionCheckHandler(
|
||||
(_wc, permission) => permission !== 'top-level-storage-access'
|
||||
);
|
||||
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
|
||||
|
||||
const permission = await w.webContents.executeJavaScript(`
|
||||
navigator.permissions.query({
|
||||
name: 'top-level-storage-access',
|
||||
requestedOrigin: "https://www.example.com",
|
||||
}).then(permission => permission.state).catch(err => err.message);
|
||||
`, true);
|
||||
|
||||
expect(permission).to.eq('denied');
|
||||
});
|
||||
|
||||
it('can grant a permission request for "top-level-storage-access"', async () => {
|
||||
session.defaultSession.setPermissionRequestHandler(
|
||||
(_wc, permission, callback) => {
|
||||
callback(permission === 'top-level-storage-access');
|
||||
}
|
||||
);
|
||||
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'button.html'));
|
||||
|
||||
// requestStorageAccessFor returns a Promise that fulfills with undefined
|
||||
// if the access to third-party cookies was granted and rejects if access was denied.
|
||||
const permission = await w.webContents.executeJavaScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
const button = document.getElementById('button');
|
||||
button.addEventListener("click", () => {
|
||||
document.requestStorageAccessFor('https://myfakesite').then(
|
||||
(res) => { resolve('granted') },
|
||||
(err) => { resolve('denied') },
|
||||
);
|
||||
});
|
||||
button.click();
|
||||
});
|
||||
`, true);
|
||||
|
||||
expect(permission).to.eq('granted');
|
||||
});
|
||||
|
||||
it('can deny a permission request for "top-level-storage-access"', async () => {
|
||||
session.defaultSession.setPermissionRequestHandler(
|
||||
(_wc, permission, callback) => {
|
||||
callback(permission !== 'top-level-storage-access');
|
||||
}
|
||||
);
|
||||
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'button.html'));
|
||||
|
||||
// requestStorageAccessFor returns a Promise that fulfills with undefined
|
||||
// if the access to third-party cookies was granted and rejects if access was denied.
|
||||
const permission = await w.webContents.executeJavaScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
const button = document.getElementById('button');
|
||||
button.addEventListener("click", () => {
|
||||
document.requestStorageAccessFor('https://myfakesite').then(
|
||||
(res) => { resolve('granted') },
|
||||
(err) => { resolve('denied') },
|
||||
);
|
||||
});
|
||||
button.click();
|
||||
});
|
||||
`, true);
|
||||
|
||||
expect(permission).to.eq('denied');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IdleDetection', () => {
|
||||
afterEach(closeAllWindows);
|
||||
afterEach(() => {
|
||||
|
||||
@@ -1285,6 +1285,16 @@ describe('chrome extensions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('globalParams', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'globalParams' };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||
const response = JSON.parse(responseString);
|
||||
expect(response).to.deep.equal({ changed: true });
|
||||
});
|
||||
|
||||
it('insertCSS', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
|
||||
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>
|
||||
@@ -20,6 +20,27 @@ const handleRequest = async (request, sender, sendResponse) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'globalParams' : {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
func: () => {
|
||||
chrome.scripting.globalParams.changed = true;
|
||||
},
|
||||
world: 'ISOLATED'
|
||||
});
|
||||
|
||||
const results = await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
func: () => JSON.stringify(chrome.scripting.globalParams),
|
||||
world: 'ISOLATED'
|
||||
});
|
||||
|
||||
const result = JSON.parse(results[0].result);
|
||||
|
||||
sendResponse(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'registerContentScripts': {
|
||||
await chrome.scripting.registerContentScripts([{
|
||||
id: 'session-script',
|
||||
|
||||
@@ -19,6 +19,11 @@ const map = {
|
||||
chrome.runtime.sendMessage({ method: 'insertCSS' }, response => {
|
||||
console.log(JSON.stringify(response));
|
||||
});
|
||||
},
|
||||
globalParams () {
|
||||
chrome.runtime.sendMessage({ method: 'globalParams' }, response => {
|
||||
console.log(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
|
||||
show: true
|
||||
}, async () => {
|
||||
// TODO(MarshallOfSound): Figure out if we can work around this 1 tick issue for users
|
||||
if (process.platform === 'darwin') {
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
// Wait for a tick, the window being "shown" takes 1 tick on macOS
|
||||
await setTimeout(10000);
|
||||
}
|
||||
|
||||
@@ -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