mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
1 Commits
v33.0.0-ni
...
clavin/smo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33e37ce702 |
1
.circleci/.gitignore
vendored
Normal file
1
.circleci/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
config-staging
|
||||
56
.circleci/config.yml
Normal file
56
.circleci/config.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
version: 2.1
|
||||
|
||||
# Required for dynamic configuration
|
||||
setup: true
|
||||
|
||||
# Orbs
|
||||
orbs:
|
||||
path-filtering: circleci/path-filtering@0.1.0
|
||||
continuation: circleci/continuation@0.2.0
|
||||
|
||||
# All input parameters to pass to build config
|
||||
parameters:
|
||||
run-docs-only:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
upload-to-storage:
|
||||
type: string
|
||||
default: '1'
|
||||
|
||||
run-build-linux:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
generate-config:
|
||||
docker:
|
||||
- image: cimg/node:16.14
|
||||
steps:
|
||||
- checkout
|
||||
- path-filtering/set-parameters:
|
||||
base-revision: main
|
||||
mapping: |
|
||||
^((?!docs/).)*$ run-build-linux true
|
||||
docs/.* run-docs-only true
|
||||
^((?!docs/).)*$ run-docs-only false
|
||||
- run:
|
||||
command: |
|
||||
cd .circleci/config
|
||||
yarn
|
||||
export CIRCLECI_BINARY="$HOME/circleci"
|
||||
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/main/install.sh | DESTDIR=$CIRCLECI_BINARY bash
|
||||
node build.js
|
||||
name: Pack config.yml
|
||||
- run:
|
||||
name: Set params
|
||||
command: node .circleci/config/params.js
|
||||
- continuation/continue:
|
||||
configuration_path: .circleci/config-staging/built.yml
|
||||
parameters: /tmp/pipeline-parameters.json
|
||||
|
||||
# Initial setup workflow
|
||||
workflows:
|
||||
setup:
|
||||
jobs:
|
||||
- generate-config
|
||||
1642
.circleci/config/base.yml
Normal file
1642
.circleci/config/base.yml
Normal file
File diff suppressed because it is too large
Load Diff
34
.circleci/config/build.js
Normal file
34
.circleci/config/build.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const STAGING_DIR = path.resolve(__dirname, '..', 'config-staging');
|
||||
|
||||
function copyAndExpand(dir = './') {
|
||||
const absDir = path.resolve(__dirname, dir);
|
||||
const targetDir = path.resolve(STAGING_DIR, dir);
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir);
|
||||
}
|
||||
|
||||
for (const file of fs.readdirSync(absDir)) {
|
||||
if (!file.endsWith('.yml')) {
|
||||
if (fs.statSync(path.resolve(absDir, file)).isDirectory()) {
|
||||
copyAndExpand(path.join(dir, file));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(targetDir, file), yaml.dump(yaml.load(fs.readFileSync(path.resolve(absDir, file), 'utf8')), {
|
||||
noRefs: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.pathExists(STAGING_DIR)) fs.removeSync(STAGING_DIR);
|
||||
copyAndExpand();
|
||||
|
||||
const output = cp.spawnSync(process.env.CIRCLECI_BINARY || 'circleci', ['config', 'pack', STAGING_DIR]);
|
||||
fs.writeFileSync(path.resolve(STAGING_DIR, 'built.yml'), output.stdout.toString());
|
||||
51
.circleci/config/jobs/lint.yml
Normal file
51
.circleci/config/jobs/lint.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
executor:
|
||||
name: linux-docker
|
||||
size: medium
|
||||
steps:
|
||||
- checkout:
|
||||
path: src/electron
|
||||
- run:
|
||||
name: Setup third_party Depot Tools
|
||||
command: |
|
||||
# "depot_tools" has to be checkout into "//third_party/depot_tools" so pylint.py can a "pylintrc" file.
|
||||
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git src/third_party/depot_tools
|
||||
echo 'export PATH="$PATH:'"$PWD"'/src/third_party/depot_tools"' >> $BASH_ENV
|
||||
- run:
|
||||
name: Download GN Binary
|
||||
command: |
|
||||
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
|
||||
gn_version="$(curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/DEPS?format=TEXT" | base64 -d | grep gn_version | head -n1 | cut -d\' -f4)"
|
||||
|
||||
cipd ensure -ensure-file - -root . \<<-CIPD
|
||||
\$ServiceURL https://chrome-infra-packages.appspot.com/
|
||||
@Subdir src/buildtools/linux64
|
||||
gn/gn/linux-amd64 $gn_version
|
||||
CIPD
|
||||
|
||||
echo 'export CHROMIUM_BUILDTOOLS_PATH="'"$PWD"'/src/buildtools"' >> $BASH_ENV
|
||||
- run:
|
||||
name: Download clang-format Binary
|
||||
command: |
|
||||
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
|
||||
|
||||
mkdir -p src/buildtools
|
||||
curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/buildtools/DEPS?format=TEXT" | base64 -d > src/buildtools/DEPS
|
||||
|
||||
gclient sync --spec="solutions=[{'name':'src/buildtools','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':True},'managed':False}]"
|
||||
- run:
|
||||
name: Run Lint
|
||||
command: |
|
||||
# gn.py tries to find a gclient root folder starting from the current dir.
|
||||
# When it fails and returns "None" path, the whole script fails. Let's "fix" it.
|
||||
touch .gclient
|
||||
# Another option would be to checkout "buildtools" inside the Electron checkout,
|
||||
# but then we would lint its contents (at least gn format), and it doesn't pass it.
|
||||
|
||||
cd src/electron
|
||||
node script/yarn install --frozen-lockfile
|
||||
node script/yarn lint
|
||||
- run:
|
||||
name: Run Script Typechecker
|
||||
command: |
|
||||
cd src/electron
|
||||
node script/yarn tsc -p tsconfig.script.json
|
||||
10
.circleci/config/package.json
Normal file
10
.circleci/config/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@electron/circleci-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fs-extra": "^10.1.0",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
}
|
||||
12
.circleci/config/params.js
Normal file
12
.circleci/config/params.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const PARAMS_PATH = '/tmp/pipeline-parameters.json';
|
||||
|
||||
const content = JSON.parse(fs.readFileSync(PARAMS_PATH, 'utf-8'));
|
||||
|
||||
// Choose resource class for linux hosts
|
||||
const currentBranch = process.env.CIRCLE_BRANCH || '';
|
||||
content['large-linux-executor'] = /^pull\/[0-9-]+$/.test(currentBranch) ? '2xlarge' : 'electronjs/aks-linux-large';
|
||||
content['medium-linux-executor'] = /^pull\/[0-9-]+$/.test(currentBranch) ? 'medium' : 'electronjs/aks-linux-medium';
|
||||
|
||||
fs.writeFileSync(PARAMS_PATH, JSON.stringify(content));
|
||||
43
.circleci/config/yarn.lock
Normal file
43
.circleci/config/yarn.lock
Normal file
@@ -0,0 +1,43 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
fs-extra@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
||||
dependencies:
|
||||
universalify "^2.0.0"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
||||
8
.circleci/fix-known-hosts.sh
Executable file
8
.circleci/fix-known-hosts.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
|
||||
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
|
||||
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=" >> ~/.ssh/known_hosts
|
||||
@@ -36,6 +36,7 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"shakram02.bash-beautify",
|
||||
"marshallofsound.gnls-electron",
|
||||
"CircleCI.circleci"
|
||||
],
|
||||
"settings": {
|
||||
"editor.tabSize": 2,
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
# See docs/development/releasing.md
|
||||
|
||||
APPVEYOR_CLOUD_TOKEN=
|
||||
CIRCLE_TOKEN=
|
||||
ELECTRON_GITHUB_TOKEN=
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -22,7 +22,6 @@ patches/**/.patches merge=union
|
||||
*.md text eol=lf
|
||||
*.mm text eol=lf
|
||||
*.mojom text eol=lf
|
||||
*.patches text eol=lf
|
||||
*.proto text eol=lf
|
||||
*.py text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -26,8 +26,7 @@ body:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What operating system(s) are you using?
|
||||
multiple: true
|
||||
label: What operating system are you using?
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
|
||||
@@ -6,6 +6,6 @@ runs:
|
||||
- name: Install Build Tools
|
||||
shell: bash
|
||||
run: |
|
||||
export BUILD_TOOLS_SHA=d5b87591842be19058e8d75d2c5b7f1fabe9f450
|
||||
export BUILD_TOOLS_SHA=ff3e40a9a2ebb735c18b6450ecd5ddaa8bb364a9
|
||||
npm i -g @electron/build-tools
|
||||
e auto-update disable
|
||||
|
||||
@@ -23,7 +23,7 @@ runs:
|
||||
# or it was uploaded in the checkout job for a previous commit.
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
timeout_minutes: 20
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
@@ -32,11 +32,8 @@ runs:
|
||||
echo "SAS Token not found; exiting src cache download early..."
|
||||
exit 1
|
||||
fi
|
||||
azcopy copy --log-level=ERROR \
|
||||
azcopy copy \
|
||||
"https://${{ env.AZURE_AKS_CACHE_STORAGE_ACCOUNT }}.file.core.windows.net/${{ env.AZURE_AKS_CACHE_SHARE_NAME }}/${{ env.CACHE_PATH }}?$sas_token" $DEPSHASH.tar
|
||||
env:
|
||||
AZURE_AKS_CACHE_STORAGE_ACCOUNT: f723719aa87a34622b5f7f3
|
||||
AZURE_AKS_CACHE_SHARE_NAME: pvc-f6a4089f-b082-4bee-a3f9-c3e1c0c02d8f
|
||||
- name: Clean SAS Key
|
||||
shell: bash
|
||||
run: rm -f sas-token
|
||||
|
||||
24
.github/workflows/auto-close-pull-request.yml
vendored
Normal file
24
.github/workflows/auto-close-pull-request.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Auto Close Pull Request
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'yarn.lock'
|
||||
- 'spec/yarn.lock'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
auto-close-dependency-pull-request:
|
||||
name: Auto close non-maintainer dependency pull request
|
||||
if: ${{ !contains(fromJSON('["MEMBER", "OWNER"]'), github.event.pull_request.author_association) && github.event.pull_request.user.type != 'Bot' && !github.event.pull_request.draft }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close pull request
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr close $PR_URL --comment 'Hello @${{ github.event.pull_request.user.login }}! It looks like this pull request touches one of our dependency files, and per [our contribution policy](https://github.com/electron/electron/blob/main/CONTRIBUTING.md#dependencies-upgrades-policy) we do not accept these types of PRs, so this PR will be closed.'
|
||||
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
||||
checkout-macos:
|
||||
needs: setup
|
||||
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-macos}}
|
||||
runs-on: electron-arc-linux-amd64-32core
|
||||
runs-on: aks-linux-large
|
||||
container:
|
||||
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||
options: --user root
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
checkout-linux:
|
||||
needs: setup
|
||||
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-linux}}
|
||||
runs-on: electron-arc-linux-amd64-32core
|
||||
runs-on: aks-linux-large
|
||||
container:
|
||||
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||
options: --user root
|
||||
@@ -171,8 +171,8 @@ jobs:
|
||||
uses: ./.github/workflows/pipeline-electron-build-and-test-and-nan.yml
|
||||
needs: checkout-linux
|
||||
with:
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
test-runs-on: electron-arc-linux-amd64-4core
|
||||
build-runs-on: aks-linux-large
|
||||
test-runs-on: aks-linux-medium
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
test-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init"}'
|
||||
target-platform: linux
|
||||
@@ -191,8 +191,8 @@ jobs:
|
||||
uses: ./.github/workflows/pipeline-electron-build-and-test.yml
|
||||
needs: checkout-linux
|
||||
with:
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
test-runs-on: electron-arc-linux-amd64-4core
|
||||
build-runs-on: aks-linux-large
|
||||
test-runs-on: aks-linux-medium
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
test-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init"}'
|
||||
target-platform: linux
|
||||
@@ -212,8 +212,8 @@ jobs:
|
||||
uses: ./.github/workflows/pipeline-electron-build-and-test.yml
|
||||
needs: checkout-linux
|
||||
with:
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
test-runs-on: electron-arc-linux-arm64-4core
|
||||
build-runs-on: aks-linux-large
|
||||
test-runs-on: aks-linux-arm-medium
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
|
||||
target-platform: linux
|
||||
@@ -232,8 +232,8 @@ jobs:
|
||||
uses: ./.github/workflows/pipeline-electron-build-and-test.yml
|
||||
needs: checkout-linux
|
||||
with:
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
test-runs-on: electron-arc-linux-arm64-4core
|
||||
build-runs-on: aks-linux-large
|
||||
test-runs-on: aks-linux-arm-medium
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
test-container: '{"image":"ghcr.io/electron/test:arm64v8-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init"}'
|
||||
target-platform: linux
|
||||
|
||||
26
.github/workflows/issue-opened.yml
vendored
26
.github/workflows/issue-opened.yml
vendored
@@ -35,10 +35,9 @@ jobs:
|
||||
with:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- run: npm install @electron/fiddle-core@1.3.3 mdast-util-from-markdown@2.0.0 unist-util-select@5.1.0 semver@7.6.0
|
||||
- run: npm install mdast-util-from-markdown@2.0.0 unist-util-select@5.1.0 semver@7.6.0
|
||||
- name: Add labels
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
id: add-labels
|
||||
env:
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
with:
|
||||
@@ -72,15 +71,6 @@ jobs:
|
||||
} catch {}
|
||||
|
||||
if (labelExists) {
|
||||
// Check if it's an unsupported major
|
||||
const { ElectronVersions } = await import('${{ github.workspace }}/node_modules/@electron/fiddle-core/dist/index.js');
|
||||
const versions = await ElectronVersions.create(undefined, { ignoreCache: true });
|
||||
|
||||
if (!versions.supportedMajors.includes(major)) {
|
||||
core.setOutput('unsupportedMajor', true);
|
||||
labels.push('blocked/need-info ❌');
|
||||
}
|
||||
|
||||
labels.push(versionLabel);
|
||||
}
|
||||
}
|
||||
@@ -99,17 +89,3 @@ jobs:
|
||||
labels,
|
||||
});
|
||||
}
|
||||
- name: Create unsupported major comment
|
||||
if: ${{ steps.add-labels.outputs.unsupportedMajor }}
|
||||
uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3.6.0
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
body: |
|
||||
<!-- end-of-life -->
|
||||
|
||||
Hello @${{ github.event.issue.user.login }}. Thanks for reporting this and helping to make Electron better!
|
||||
|
||||
The version of Electron reported in this issue has reached end-of-life and is [no longer supported](https://www.electronjs.org/docs/latest/tutorial/electron-timelines#timeline). If you're still experiencing this issue on a [supported version](https://www.electronjs.org/releases/stable) of Electron, please update this issue to reflect that version of Electron.
|
||||
|
||||
Now adding the https://github.com/electron/electron/labels/blocked%2Fneed-info%20%E2%9D%8C label for this reason. This issue will be closed in 10 days if the above is not addressed.
|
||||
|
||||
8
.github/workflows/linux-publish.yml
vendored
8
.github/workflows/linux-publish.yml
vendored
@@ -19,7 +19,7 @@ on:
|
||||
|
||||
jobs:
|
||||
checkout-linux:
|
||||
runs-on: electron-arc-linux-amd64-32core
|
||||
runs-on: aks-linux-large
|
||||
container:
|
||||
image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
|
||||
options: --user root
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
needs: checkout-linux
|
||||
with:
|
||||
environment: production-release
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
build-runs-on: aks-linux-large
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
target-platform: linux
|
||||
target-arch: x64
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
needs: checkout-linux
|
||||
with:
|
||||
environment: production-release
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
build-runs-on: aks-linux-large
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
target-platform: linux
|
||||
target-arch: arm
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
needs: checkout-linux
|
||||
with:
|
||||
environment: production-release
|
||||
build-runs-on: electron-arc-linux-amd64-32core
|
||||
build-runs-on: aks-linux-large
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ inputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
target-platform: linux
|
||||
target-arch: arm64
|
||||
|
||||
2
.github/workflows/macos-publish.yml
vendored
2
.github/workflows/macos-publish.yml
vendored
@@ -20,7 +20,7 @@ on:
|
||||
|
||||
jobs:
|
||||
checkout-macos:
|
||||
runs-on: electron-arc-linux-amd64-32core
|
||||
runs-on: aks-linux-large
|
||||
container:
|
||||
image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
|
||||
options: --user root
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Check for Non-Maintainer Dependency Change
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'yarn.lock'
|
||||
- 'spec/yarn.lock'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-for-non-maintainer-dependency-change:
|
||||
name: Check for non-maintainer dependency change
|
||||
if: ${{ !contains(fromJSON('["MEMBER", "OWNER"]'), github.event.pull_request.author_association) && github.event.pull_request.user.type != 'Bot' && !github.event.pull_request.draft }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for existing review
|
||||
id: check-for-review
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
set -eo pipefail
|
||||
REVIEW_COUNT=$(gh pr view $PR_URL --json reviews | jq '[ .reviews[] | select(.author.login == "github-actions") | select(.body | startswith("<!-- no-dependency-change -->")) ] | length')
|
||||
if [[ $REVIEW_COUNT -eq 0 ]]; then
|
||||
echo "SHOULD_REVIEW=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Request changes
|
||||
if: ${{ steps.check-for-review.outputs.SHOULD_REVIEW }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
printf "<!-- no-dependency-change -->\n\nHello @${{ github.event.pull_request.user.login }}! It looks like this pull request touches one of our dependency files, and per [our contribution policy](https://github.com/electron/electron/blob/main/CONTRIBUTING.md#dependencies-upgrades-policy) we do not accept these types of changes in PRs." | gh pr review $PR_URL -r --body-file=-
|
||||
@@ -15,7 +15,7 @@ concurrency:
|
||||
jobs:
|
||||
docs-only:
|
||||
name: Docs Only Compile
|
||||
runs-on: electron-arc-linux-amd64-4core
|
||||
runs-on: aks-linux-medium
|
||||
timeout-minutes: 20
|
||||
container: ${{ fromJSON(inputs.container) }}
|
||||
steps:
|
||||
|
||||
2
.github/workflows/pipeline-electron-lint.yml
vendored
2
.github/workflows/pipeline-electron-lint.yml
vendored
@@ -15,7 +15,7 @@ concurrency:
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: electron-arc-linux-amd64-4core
|
||||
runs-on: aks-linux-medium
|
||||
timeout-minutes: 20
|
||||
container: ${{ fromJSON(inputs.container) }}
|
||||
steps:
|
||||
|
||||
@@ -61,6 +61,8 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && !endsWith(github.ref, '-x-y') }}
|
||||
|
||||
env:
|
||||
AZURE_AKS_CACHE_STORAGE_ACCOUNT: ${{ secrets.AZURE_AKS_CACHE_STORAGE_ACCOUNT }}
|
||||
AZURE_AKS_CACHE_SHARE_NAME: ${{ secrets.AZURE_AKS_CACHE_SHARE_NAME }}
|
||||
ELECTRON_ARTIFACTS_BLOB_STORAGE: ${{ secrets.ELECTRON_ARTIFACTS_BLOB_STORAGE }}
|
||||
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
|
||||
ELECTRON_GITHUB_TOKEN: ${{ secrets.ELECTRON_GITHUB_TOKEN }}
|
||||
@@ -84,7 +86,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js/npm
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
|
||||
with:
|
||||
node-version: 20.11.x
|
||||
cache: yarn
|
||||
|
||||
@@ -36,6 +36,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
AZURE_AKS_CACHE_STORAGE_ACCOUNT: ${{ secrets.AZURE_AKS_CACHE_STORAGE_ACCOUNT }}
|
||||
AZURE_AKS_CACHE_SHARE_NAME: ${{ secrets.AZURE_AKS_CACHE_SHARE_NAME }}
|
||||
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
|
||||
GCLIENT_EXTRA_ARGS: ${{ inputs.target-platform == 'macos' && '--custom-var=checkout_mac=True --custom-var=host_os=mac' || '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True' }}
|
||||
ELECTRON_OUT_DIR: Default
|
||||
|
||||
@@ -125,12 +125,12 @@ jobs:
|
||||
echo "DISABLE_CRASH_REPORTER_TESTS=true" >> $GITHUB_ENV
|
||||
echo "IS_ASAN=true" >> $GITHUB_ENV
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
|
||||
with:
|
||||
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
|
||||
with:
|
||||
name: src_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||
@@ -187,17 +187,10 @@ jobs:
|
||||
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files
|
||||
fi
|
||||
fi
|
||||
- name: Upload Test Artifacts
|
||||
if: always() && !cancelled()
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
|
||||
with:
|
||||
name: test_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: src/electron/spec/artifacts
|
||||
if-no-files-found: ignore
|
||||
- name: Wait for active SSH sessions
|
||||
if: always() && !cancelled()
|
||||
run: |
|
||||
while [ -f /var/.ssh-lock ]
|
||||
do
|
||||
sleep 60
|
||||
done
|
||||
done
|
||||
@@ -37,7 +37,7 @@ env:
|
||||
jobs:
|
||||
node-tests:
|
||||
name: Run Node.js Tests
|
||||
runs-on: electron-arc-linux-amd64-8core
|
||||
runs-on: aks-linux-medium-plus
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
TARGET_ARCH: ${{ inputs.target-arch }}
|
||||
@@ -71,12 +71,12 @@ jobs:
|
||||
- name: Add Depot Tools to PATH
|
||||
run: echo "$(pwd)/depot_tools" >> $GITHUB_PATH
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
done
|
||||
nan-tests:
|
||||
name: Run Nan Tests
|
||||
runs-on: electron-arc-linux-amd64-4core
|
||||
runs-on: aks-linux-medium
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
TARGET_ARCH: ${{ inputs.target-arch }}
|
||||
@@ -135,12 +135,12 @@ jobs:
|
||||
- name: Add Depot Tools to PATH
|
||||
run: echo "$(pwd)/depot_tools" >> $GITHUB_PATH
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
unzip -:o dist.zip
|
||||
- name: Setup Linux for Headless Testing
|
||||
run: sh -e /etc/init.d/xvfb start
|
||||
- name: Run Nan Tests
|
||||
- name: Run Node.js Tests
|
||||
run: |
|
||||
cd src
|
||||
node electron/script/nan-spec-runner.js
|
||||
|
||||
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@@ -50,6 +50,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12
|
||||
uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
2
.github/workflows/semantic.yml
vendored
2
.github/workflows/semantic.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: semantic-pull-request
|
||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||
uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
2
DEPS
2
DEPS
@@ -4,7 +4,7 @@ vars = {
|
||||
'chromium_version':
|
||||
'128.0.6573.0',
|
||||
'node_version':
|
||||
'v20.15.1',
|
||||
'v20.15.0',
|
||||
'nan_version':
|
||||
'e14bdcd1f72d62bca1d541b66da43130384ec213',
|
||||
'squirrel.mac_version':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[](https://electronjs.org)
|
||||
|
||||
[](https://github.com/electron/electron/actions/workflows/build.yml)
|
||||
[](https://circleci.com/gh/electron/electron/tree/main)
|
||||
[](https://ci.appveyor.com/project/electron-bot/electron-ljo26/branch/main)
|
||||
[](https://discord.gg/electronjs)
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ import("//third_party/widevine/cdm/widevine.gni")
|
||||
static_library("chrome") {
|
||||
visibility = [ "//electron:electron_lib" ]
|
||||
sources = [
|
||||
"//ash/style/rounded_rect_cutout_path_builder.cc",
|
||||
"//ash/style/rounded_rect_cutout_path_builder.h",
|
||||
"//chrome/browser/app_mode/app_mode_utils.cc",
|
||||
"//chrome/browser/app_mode/app_mode_utils.h",
|
||||
"//chrome/browser/browser_features.cc",
|
||||
|
||||
@@ -1489,38 +1489,6 @@ This method can only be called after app is ready.
|
||||
|
||||
Returns `Promise<string>` - Resolves with the proxy information for `url` that will be used when attempting to make requests using [Net](net.md) in the [utility process](../glossary.md#utility-process).
|
||||
|
||||
### `app.setClientCertRequestPasswordHandler(handler)` _Linux_
|
||||
|
||||
* `handler` Function\<Promise\<string\>\>
|
||||
* `clientCertRequestParams` Object
|
||||
* `hostname` string - the hostname of the site requiring a client certificate
|
||||
* `tokenName` string - the token (or slot) name of the cryptographic device
|
||||
* `isRetry` boolean - whether there have been previous failed attempts at prompting the password
|
||||
|
||||
Returns `Promise<string>` - Resolves with the password
|
||||
|
||||
The handler is called when a password is needed to unlock a client certificate for
|
||||
`hostname`.
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
async function passwordPromptUI (text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// display UI to prompt user for password
|
||||
// ...
|
||||
// ...
|
||||
resolve('the password')
|
||||
})
|
||||
}
|
||||
|
||||
app.setClientCertRequestPasswordHandler(async ({ hostname, tokenName, isRetry }) => {
|
||||
const text = `Please sign in to ${tokenName} to authenticate to ${hostname} with your certificate`
|
||||
const password = await passwordPromptUI(text)
|
||||
return password
|
||||
})
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
### `app.accessibilitySupportEnabled` _macOS_ _Windows_
|
||||
|
||||
@@ -145,10 +145,6 @@ Returns `string` - The file name of the download item.
|
||||
disk. If user changes the file name in a prompted download saving dialog, the
|
||||
actual name of saved file will be different.
|
||||
|
||||
#### `downloadItem.getCurrentBytesPerSecond()`
|
||||
|
||||
Returns `Integer` - The current download speed in bytes per second.
|
||||
|
||||
#### `downloadItem.getTotalBytes()`
|
||||
|
||||
Returns `Integer` - The total size in bytes of the download item.
|
||||
@@ -159,10 +155,6 @@ If the size is unknown, it returns 0.
|
||||
|
||||
Returns `Integer` - The received bytes of the download item.
|
||||
|
||||
#### `downloadItem.getPercentComplete()`
|
||||
|
||||
Returns `Integer` - The download completion in percent.
|
||||
|
||||
#### `downloadItem.getContentDisposition()`
|
||||
|
||||
Returns `string` - The Content-Disposition field from the response
|
||||
@@ -192,10 +184,6 @@ Returns `string` - ETag header value.
|
||||
Returns `Double` - Number of seconds since the UNIX epoch when the download was
|
||||
started.
|
||||
|
||||
#### `downloadItem.getEndTime()`
|
||||
|
||||
Returns `Double` - Number of seconds since the UNIX epoch when the download ended.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `downloadItem.savePath`
|
||||
|
||||
@@ -94,12 +94,6 @@ Examples of valid `color` values:
|
||||
|
||||
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBAA` or `RGB`.
|
||||
|
||||
#### `view.setBorderRadius(radius)`
|
||||
|
||||
* `radius` Integer - Border radius size in pixels.
|
||||
|
||||
**Note:** The area cutout of the view's border still captures clicks.
|
||||
|
||||
#### `view.setVisible(visible)`
|
||||
|
||||
* `visible` boolean - If false, the view will be hidden from display.
|
||||
|
||||
@@ -78,7 +78,8 @@ Electron
|
||||
|
||||
## Structure of Other Directories
|
||||
|
||||
* **.github** - GitHub-specific config files including issues templates, CI with GitHub Actions and CODEOWNERS.
|
||||
* **.circleci** - Config file for CI with CircleCI.
|
||||
* **.github** - GitHub-specific config files including issues templates and CODEOWNERS.
|
||||
* **dist** - Temporary directory created by `script/create-dist.py` script
|
||||
when creating a distribution.
|
||||
* **node_modules** - Third party node modules used for building.
|
||||
|
||||
@@ -362,8 +362,6 @@ filenames = {
|
||||
"shell/browser/electron_browser_context.h",
|
||||
"shell/browser/electron_browser_main_parts.cc",
|
||||
"shell/browser/electron_browser_main_parts.h",
|
||||
"shell/browser/electron_crypto_module_delegate_nss.cc",
|
||||
"shell/browser/electron_crypto_module_delegate_nss.h",
|
||||
"shell/browser/electron_download_manager_delegate.cc",
|
||||
"shell/browser/electron_download_manager_delegate.h",
|
||||
"shell/browser/electron_gpu_client.cc",
|
||||
|
||||
@@ -111,19 +111,3 @@ for (const name of events) {
|
||||
webContents.emit(name, event, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
app._clientCertRequestPasswordHandler = null;
|
||||
app.setClientCertRequestPasswordHandler = function (handler: (params: Electron.ClientCertRequestParams) => Promise<string>) {
|
||||
app._clientCertRequestPasswordHandler = handler;
|
||||
};
|
||||
|
||||
app.on('-client-certificate-request-password', async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
|
||||
event.preventDefault();
|
||||
const { hostname, tokenName, isRetry } = event;
|
||||
if (!app._clientCertRequestPasswordHandler) {
|
||||
callback('');
|
||||
return;
|
||||
}
|
||||
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
|
||||
callback(password);
|
||||
});
|
||||
|
||||
@@ -130,3 +130,4 @@ fix_font_face_resolution_when_renderer_is_blocked.patch
|
||||
feat_enable_passing_exit_code_on_service_process_crash.patch
|
||||
chore_remove_reference_to_chrome_browser_themes.patch
|
||||
feat_enable_customizing_symbol_color_in_framecaptionbutton.patch
|
||||
smooth_corner_rounding_wip_demo.patch
|
||||
|
||||
339
patches/chromium/smooth_corner_rounding_wip_demo.patch
Normal file
339
patches/chromium/smooth_corner_rounding_wip_demo.patch
Normal file
@@ -0,0 +1,339 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Calvin Watford <watfordcalvin@gmail.com>
|
||||
Date: Thu, 11 Jul 2024 14:14:28 -0600
|
||||
Subject: smooth corner rounding (WIP DEMO)
|
||||
|
||||
this is not meant to be committed in its current state. i had to write
|
||||
this description to pass pre-commit checks.
|
||||
|
||||
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
|
||||
index a7a4cac473c95d7c16b801809ab3eb9c060ebf1d..fe577b9ff0742c266a9a7d4c8f87669160b69c46 100644
|
||||
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
|
||||
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
|
||||
@@ -27,8 +27,13 @@
|
||||
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
|
||||
|
||||
#include <memory>
|
||||
+#include <numbers>
|
||||
#include <optional>
|
||||
|
||||
+#include "base/ranges/functional.h"
|
||||
+#include "base/ranges/algorithm.h"
|
||||
+#include "base/types/fixed_array.h"
|
||||
+#include "base/containers/fixed_flat_map.h"
|
||||
#include "base/logging.h"
|
||||
#include "build/build_config.h"
|
||||
#include "cc/paint/color_filter.h"
|
||||
@@ -39,6 +44,7 @@
|
||||
#include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
|
||||
#include "third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h"
|
||||
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
|
||||
+#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
|
||||
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
|
||||
#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
|
||||
#include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
|
||||
@@ -68,6 +74,216 @@ namespace blink {
|
||||
|
||||
namespace {
|
||||
|
||||
+namespace {
|
||||
+
|
||||
+float ToRadians(float degrees) {
|
||||
+ return (degrees * std::numbers::pi) / 180.0f;
|
||||
+}
|
||||
+
|
||||
+struct CornerPathParams {
|
||||
+ float a;
|
||||
+ float b;
|
||||
+ float c;
|
||||
+ float d;
|
||||
+ float p;
|
||||
+ float cornerRadius;
|
||||
+ float arcSectionLength;
|
||||
+};
|
||||
+
|
||||
+CornerPathParams GetPathParamsForCorner(float cornerRadius, float cornerSmoothing, bool preserveSmoothing, float roundingAndSmoothingBudget) {
|
||||
+ float p = (1 + cornerSmoothing) * cornerRadius;
|
||||
+
|
||||
+ if (!preserveSmoothing) {
|
||||
+ const float maxCornerSmoothing = roundingAndSmoothingBudget / cornerRadius - 1.0f;
|
||||
+ cornerSmoothing = std::min(cornerSmoothing, maxCornerSmoothing);
|
||||
+ p = std::min(p, roundingAndSmoothingBudget);
|
||||
+ }
|
||||
+
|
||||
+ const float arcMeasure = 90.0f * (1.0f - cornerSmoothing);
|
||||
+ const float arcSectionLength = sin(ToRadians(arcMeasure / 2.0f)) * cornerRadius * sqrt(2.0f);
|
||||
+
|
||||
+ const float angleAlpha = (90.0f - arcMeasure) / 2.0f;
|
||||
+ const float p3ToP4Distance = cornerRadius * tan(ToRadians(angleAlpha / 2.0f));
|
||||
+
|
||||
+ const float angleBeta = 45.0f * cornerSmoothing;
|
||||
+ const float c = p3ToP4Distance * cos(ToRadians(angleBeta));
|
||||
+ const float d = c * tan(ToRadians(angleBeta));
|
||||
+
|
||||
+ float b = (p - arcSectionLength - c - d) / 3.0f;
|
||||
+ float a = 2.0f * b;
|
||||
+
|
||||
+ if (preserveSmoothing && p > roundingAndSmoothingBudget) {
|
||||
+ const float p1ToP3MaxDistance = roundingAndSmoothingBudget - d - arcSectionLength - c;
|
||||
+
|
||||
+ const float minA = p1ToP3MaxDistance / 6.0f;
|
||||
+ const float maxB = p1ToP3MaxDistance - minA;
|
||||
+
|
||||
+ b = std::min(b, maxB);
|
||||
+ a = p1ToP3MaxDistance - b;
|
||||
+ p = std::min(p, roundingAndSmoothingBudget);
|
||||
+ }
|
||||
+
|
||||
+ return {
|
||||
+ .a = a,
|
||||
+ .b = b,
|
||||
+ .c = c,
|
||||
+ .d = d,
|
||||
+ .p = p,
|
||||
+ .cornerRadius = cornerRadius,
|
||||
+ .arcSectionLength = arcSectionLength
|
||||
+ };
|
||||
+}
|
||||
+
|
||||
+template <typename T>
|
||||
+struct Corners {
|
||||
+ T topLeft;
|
||||
+ T topRight;
|
||||
+ T bottomLeft;
|
||||
+ T bottomRight;
|
||||
+};
|
||||
+
|
||||
+template <typename T>
|
||||
+using Corner = T Corners<T>::*;
|
||||
+
|
||||
+Corners<float> MinRadii(const FloatRoundedRect::Radii& radii) {
|
||||
+ return {
|
||||
+ .topLeft = std::min(radii.TopLeft().width(), radii.TopLeft().height()),
|
||||
+ .topRight = std::min(radii.TopRight().width(), radii.TopRight().height()),
|
||||
+ .bottomLeft = std::min(radii.BottomLeft().width(), radii.BottomLeft().height()),
|
||||
+ .bottomRight = std::min(radii.BottomRight().width(), radii.BottomRight().height()),
|
||||
+ };
|
||||
+}
|
||||
+
|
||||
+struct NormalizedCorner {
|
||||
+ float radius;
|
||||
+ float roundingAndSmoothingBudget;
|
||||
+};
|
||||
+
|
||||
+float CalculateBudget(const NormalizedCorner& corner, const NormalizedCorner& adjacent, float sideLength) {
|
||||
+ if (corner.radius == 0.0f && adjacent.radius == 0.0f) {
|
||||
+ return 0.0f;
|
||||
+ }
|
||||
+
|
||||
+ const float& adjacentCornerBudget = adjacent.roundingAndSmoothingBudget;
|
||||
+ if (adjacentCornerBudget >= 0) {
|
||||
+ return sideLength - adjacentCornerBudget;
|
||||
+ } else {
|
||||
+ return (corner.radius / (corner.radius + adjacent.radius)) * sideLength;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+Corners<NormalizedCorner> DistributeAndNormalize(const FloatRoundedRect& rrect) {
|
||||
+ constexpr auto TopLeftCorner = &Corners<NormalizedCorner>::topLeft;
|
||||
+ constexpr auto TopRightCorner = &Corners<NormalizedCorner>::topRight;
|
||||
+ constexpr auto BottomLeftCorner = &Corners<NormalizedCorner>::bottomLeft;
|
||||
+ constexpr auto BottomRightCorner = &Corners<NormalizedCorner>::bottomRight;
|
||||
+
|
||||
+ Corners<float> minRadii = MinRadii(rrect.GetRadii());
|
||||
+ Corners<NormalizedCorner> result = {
|
||||
+ .topLeft = {
|
||||
+ .radius = minRadii.topLeft,
|
||||
+ .roundingAndSmoothingBudget = -1.0f
|
||||
+ },
|
||||
+ .topRight = {
|
||||
+ .radius = minRadii.topRight,
|
||||
+ .roundingAndSmoothingBudget = -1.0f
|
||||
+ },
|
||||
+ .bottomLeft = {
|
||||
+ .radius = minRadii.bottomLeft,
|
||||
+ .roundingAndSmoothingBudget = -1.0f
|
||||
+ },
|
||||
+ .bottomRight = {
|
||||
+ .radius = minRadii.bottomRight,
|
||||
+ .roundingAndSmoothingBudget = -1.0f
|
||||
+ },
|
||||
+ };
|
||||
+
|
||||
+ base::FixedArray<Corner<NormalizedCorner>, 4> corners({TopLeftCorner, TopRightCorner, BottomLeftCorner, BottomRightCorner});
|
||||
+ base::ranges::sort(corners, base::ranges::greater(), [&result](NormalizedCorner Corners<NormalizedCorner>::* corner) { return (result.*corner).radius; });
|
||||
+
|
||||
+ for (Corner<NormalizedCorner> corner : corners) {
|
||||
+ const Corner<NormalizedCorner> adjacentCornerX = corner == TopLeftCorner ? TopRightCorner : corner == TopRightCorner ? TopLeftCorner : corner == BottomLeftCorner ? BottomRightCorner : BottomLeftCorner;
|
||||
+ const Corner<NormalizedCorner> adjacentCornerY = corner == TopLeftCorner ? BottomLeftCorner : corner == TopRightCorner ? BottomRightCorner : corner == BottomLeftCorner ? TopLeftCorner : TopRightCorner;
|
||||
+
|
||||
+ const float budget = std::min(
|
||||
+ CalculateBudget(result.*corner, result.*adjacentCornerX, rrect.Rect().width()),
|
||||
+ CalculateBudget(result.*corner, result.*adjacentCornerY, rrect.Rect().height())
|
||||
+ );
|
||||
+
|
||||
+ (result.*corner).roundingAndSmoothingBudget = budget;
|
||||
+ (result.*corner).radius = std::min((result.*corner).radius, budget);
|
||||
+ }
|
||||
+
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
+const float kCornerSmoothing = 1.0f;
|
||||
+
|
||||
+}
|
||||
+
|
||||
+SkPath GetRoundedPath(const FloatRoundedRect& rrect, float cornerSmoothing = kCornerSmoothing, bool preserveSmoothing = false) {
|
||||
+ const gfx::PointF& origin = rrect.Rect().origin();
|
||||
+ const gfx::SizeF& size = rrect.Rect().size();
|
||||
+ const FloatRoundedRect::Radii& radii = rrect.GetRadii();
|
||||
+
|
||||
+ CornerPathParams topLeftParams;
|
||||
+ CornerPathParams topRightParams;
|
||||
+ CornerPathParams bottomLeftParams;
|
||||
+ CornerPathParams bottomRightParams;
|
||||
+
|
||||
+ if (radii.TopLeft() == radii.TopRight() && radii.TopRight() == radii.BottomRight() && radii.BottomRight() == radii.BottomLeft()) {
|
||||
+ const float roundingAndSmoothingBudget = std::min(size.width(), size.height()) / 2.0f;
|
||||
+ CornerPathParams params = GetPathParamsForCorner(std::min(radii.TopLeft().width(), radii.TopRight().height()), cornerSmoothing, preserveSmoothing, roundingAndSmoothingBudget);
|
||||
+
|
||||
+ topLeftParams = params;
|
||||
+ topRightParams = params;
|
||||
+ bottomLeftParams = params;
|
||||
+ bottomRightParams = params;
|
||||
+ } else {
|
||||
+ Corners<NormalizedCorner> distributedCorners = DistributeAndNormalize(rrect);
|
||||
+
|
||||
+ topLeftParams = GetPathParamsForCorner(distributedCorners.topLeft.radius, cornerSmoothing, preserveSmoothing, distributedCorners.topLeft.roundingAndSmoothingBudget);
|
||||
+ topRightParams = GetPathParamsForCorner(distributedCorners.topRight.radius, cornerSmoothing, preserveSmoothing, distributedCorners.topRight.roundingAndSmoothingBudget);
|
||||
+ bottomLeftParams = GetPathParamsForCorner(distributedCorners.bottomLeft.radius, cornerSmoothing, preserveSmoothing, distributedCorners.bottomLeft.roundingAndSmoothingBudget);
|
||||
+ bottomRightParams = GetPathParamsForCorner(distributedCorners.bottomRight.radius, cornerSmoothing, preserveSmoothing, distributedCorners.bottomRight.roundingAndSmoothingBudget);
|
||||
+ }
|
||||
+
|
||||
+ SkPath path;
|
||||
+
|
||||
+ // Top right
|
||||
+ path.moveTo(origin.x() + size.width() - topRightParams.p, origin.y());
|
||||
+ path.rCubicTo(topRightParams.a, 0.0f, topRightParams.a + topRightParams.b, 0.0f, topRightParams.a + topRightParams.b + topRightParams.c, topRightParams.d);
|
||||
+ path.rArcTo(topRightParams.cornerRadius, topRightParams.cornerRadius, 0.0f, SkPath::kSmall_ArcSize, SkPathDirection::kCW, topRightParams.arcSectionLength, topRightParams.arcSectionLength);
|
||||
+ path.rCubicTo(topRightParams.d, topRightParams.c, topRightParams.d, topRightParams.b + topRightParams.c, topRightParams.d, topRightParams.a + topLeftParams.b + topRightParams.c);
|
||||
+
|
||||
+ // Bottom right
|
||||
+ path.lineTo(origin.x() + size.width(), origin.y() + size.height() - bottomRightParams.p);
|
||||
+ path.rCubicTo(0.0f, bottomRightParams.a, 0.0f, bottomRightParams.a + bottomRightParams.b, -bottomRightParams.d, bottomRightParams.a + bottomRightParams.b + bottomRightParams.c);
|
||||
+ path.rArcTo(bottomRightParams.cornerRadius, bottomRightParams.cornerRadius, 0.0f, SkPath::kSmall_ArcSize, SkPathDirection::kCW, -bottomRightParams.arcSectionLength, bottomRightParams.arcSectionLength);
|
||||
+ path.rCubicTo(-bottomRightParams.c, bottomRightParams.d, -(bottomRightParams.b + bottomRightParams.c), bottomRightParams.d, -(bottomRightParams.a + bottomRightParams.b + bottomRightParams.c), bottomRightParams.d);
|
||||
+
|
||||
+ // Bottom left
|
||||
+ path.lineTo(origin.x() + bottomLeftParams.p, origin.y() + size.height());
|
||||
+ path.rCubicTo(-bottomLeftParams.a, 0.0f, -(bottomLeftParams.a + bottomLeftParams.b), 0.0f, -(bottomLeftParams.a + bottomLeftParams.b + bottomLeftParams.c), -bottomLeftParams.d);
|
||||
+ path.rArcTo(bottomLeftParams.cornerRadius, bottomLeftParams.cornerRadius, 0.0f,SkPath::kSmall_ArcSize, SkPathDirection::kCW, -bottomLeftParams.arcSectionLength, -bottomLeftParams.arcSectionLength);
|
||||
+ path.rCubicTo(-bottomLeftParams.d, -bottomLeftParams.c, -bottomLeftParams.d, -(bottomLeftParams.b + bottomLeftParams.c), -bottomLeftParams.d, -(bottomLeftParams.a + bottomLeftParams.b + bottomLeftParams.c));
|
||||
+
|
||||
+ // Top left
|
||||
+ path.lineTo(origin.x(), origin.y() + topLeftParams.p);
|
||||
+ path.rCubicTo(0.0f, -topLeftParams.a, 0.0f, -(topLeftParams.a + topLeftParams.b), topLeftParams.d, -(topLeftParams.a + topLeftParams.b + topLeftParams.c));
|
||||
+ path.rArcTo(topLeftParams.cornerRadius, topLeftParams.cornerRadius, 0.0f, SkPath::kSmall_ArcSize, SkPathDirection::kCW, topLeftParams.arcSectionLength, -topLeftParams.arcSectionLength);
|
||||
+ path.rCubicTo(topLeftParams.c, -topLeftParams.d, topLeftParams.b + topLeftParams.c, -topLeftParams.d, topLeftParams.a + topLeftParams.b + topLeftParams.c, -topLeftParams.d);
|
||||
+
|
||||
+ // Close
|
||||
+ path.close();
|
||||
+
|
||||
+ return path;
|
||||
+}
|
||||
+
|
||||
+}
|
||||
+
|
||||
+namespace {
|
||||
+
|
||||
SkColor4f DarkModeColor(GraphicsContext& context,
|
||||
const SkColor4f& color,
|
||||
const AutoDarkMode& auto_dark_mode) {
|
||||
@@ -668,6 +884,8 @@ void GraphicsContext::DrawImageRRect(
|
||||
image.ApplyShader(image_flags, local_matrix, src_rect, draw_options);
|
||||
}
|
||||
|
||||
+ SkPath path = GetRoundedPath(dest);
|
||||
+
|
||||
if (use_shader) {
|
||||
// Temporarily set filter-quality for the shader. <reed>
|
||||
// Should be replaced with explicit sampling parameter passed to
|
||||
@@ -675,11 +893,13 @@ void GraphicsContext::DrawImageRRect(
|
||||
image_flags.setFilterQuality(
|
||||
ComputeFilterQuality(image, dest.Rect(), src_rect));
|
||||
// Shader-based fast path.
|
||||
- canvas_->drawRRect(SkRRect(dest), image_flags);
|
||||
+ // canvas_->drawRRect(SkRRect(dest), image_flags);
|
||||
+ canvas_->drawPath(path, image_flags);
|
||||
} else {
|
||||
// Clip-based fallback.
|
||||
PaintCanvasAutoRestore auto_restore(canvas_, true);
|
||||
- canvas_->clipRRect(SkRRect(dest), image_flags.isAntiAlias());
|
||||
+ // canvas_->clipRRect(SkRRect(dest), image_flags.isAntiAlias());
|
||||
+ canvas_->clipPath(path, image_flags.isAntiAlias());
|
||||
image.Draw(canvas_, image_flags, dest.Rect(), src_rect, draw_options);
|
||||
}
|
||||
|
||||
@@ -775,7 +995,8 @@ void GraphicsContext::DrawRRect(const SkRRect& rrect,
|
||||
const cc::PaintFlags& flags,
|
||||
const AutoDarkMode& auto_dark_mode) {
|
||||
DCHECK(canvas_);
|
||||
- canvas_->drawRRect(rrect, DarkModeFlags(this, auto_dark_mode, flags));
|
||||
+ // canvas_->drawRRect(rrect, DarkModeFlags(this, auto_dark_mode, flags));
|
||||
+ canvas_->drawPath(GetRoundedPath(FloatRoundedRect(rrect)), DarkModeFlags(this, auto_dark_mode, flags));
|
||||
}
|
||||
|
||||
void GraphicsContext::FillPath(const Path& path_to_fill,
|
||||
@@ -834,17 +1055,21 @@ void GraphicsContext::FillRoundedRect(const FloatRoundedRect& rrect,
|
||||
return;
|
||||
}
|
||||
|
||||
+ const SkPath path = GetRoundedPath(rrect);
|
||||
+
|
||||
const cc::PaintFlags& fill_flags = ImmutableState()->FillFlags();
|
||||
const SkColor4f sk_color = color.toSkColor4f();
|
||||
if (sk_color == fill_flags.getColor4f()) {
|
||||
- DrawRRect(SkRRect(rrect), fill_flags, auto_dark_mode);
|
||||
+ // DrawRRect(SkRRect(rrect), fill_flags, auto_dark_mode);
|
||||
+ DrawPath(path, fill_flags, auto_dark_mode);
|
||||
return;
|
||||
}
|
||||
|
||||
cc::PaintFlags flags = fill_flags;
|
||||
flags.setColor(sk_color);
|
||||
|
||||
- DrawRRect(SkRRect(rrect), flags, auto_dark_mode);
|
||||
+ // DrawRRect(SkRRect(rrect), flags, auto_dark_mode);
|
||||
+ DrawPath(path, flags, auto_dark_mode);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -896,6 +1121,8 @@ void GraphicsContext::FillDRRect(const FloatRoundedRect& outer,
|
||||
const AutoDarkMode& auto_dark_mode) {
|
||||
DCHECK(canvas_);
|
||||
|
||||
+ // TODO: smooth rounded rect
|
||||
+
|
||||
const cc::PaintFlags& fill_flags = ImmutableState()->FillFlags();
|
||||
const SkColor4f sk_color = color.toSkColor4f();
|
||||
if (!IsSimpleDRRect(outer, inner)) {
|
||||
@@ -979,7 +1206,8 @@ void GraphicsContext::ClipRoundedRect(const FloatRoundedRect& rrect,
|
||||
return;
|
||||
}
|
||||
|
||||
- ClipRRect(SkRRect(rrect), should_antialias, clip_op);
|
||||
+ ClipPath(GetRoundedPath(rrect), should_antialias, clip_op);
|
||||
+ // ClipRRect(SkRRect(rrect), should_antialias, clip_op);
|
||||
}
|
||||
|
||||
void GraphicsContext::ClipOut(const Path& path_to_clip) {
|
||||
@@ -1013,7 +1241,8 @@ void GraphicsContext::ClipRRect(const SkRRect& rect,
|
||||
AntiAliasingMode aa,
|
||||
SkClipOp op) {
|
||||
DCHECK(canvas_);
|
||||
- canvas_->clipRRect(rect, op, aa == kAntiAliased);
|
||||
+ // canvas_->clipRRect(rect, op, aa == kAntiAliased);
|
||||
+ canvas_->clipPath(GetRoundedPath(FloatRoundedRect(rect)), op, aa == kAntiAliased);
|
||||
}
|
||||
|
||||
void GraphicsContext::Rotate(float angle_in_radians) {
|
||||
@@ -48,7 +48,7 @@ index 5239bc8ed883a54df206d73c5dc0b70942c4f3df..6a15fcae677b3bda58fc85f705862bbc
|
||||
ArrayPrototypePush(schemes, 'https', 'http');
|
||||
}
|
||||
diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js
|
||||
index e73a8ad60a13925d6773c32cead8d04ec9d96ee7..52cdb7d5e14a18ed7b1b65e429729cf47dce3f98 100644
|
||||
index b56ad6cc833f00f58bf3925e9fd82a8f5b7b9bd7..8c9e8bfb4e828d044b236a11c0890cb4f0161050 100644
|
||||
--- a/lib/internal/modules/esm/resolve.js
|
||||
+++ b/lib/internal/modules/esm/resolve.js
|
||||
@@ -741,6 +741,8 @@ function packageImportsResolve(name, base, conditions) {
|
||||
|
||||
@@ -38,7 +38,7 @@ index 6a15fcae677b3bda58fc85f705862bbcd9feec9d..449131b9af99744c08d62d73f8d124ce
|
||||
const match = RegExpPrototypeExec(DATA_URL_PATTERN, url.pathname);
|
||||
if (!match) {
|
||||
diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js
|
||||
index 52cdb7d5e14a18ed7b1b65e429729cf47dce3f98..69f73f829706deddc4f328b78af9d58434af647d 100644
|
||||
index 8c9e8bfb4e828d044b236a11c0890cb4f0161050..30f6abd5d2d3fb7aceaa6191ebd99642e34d9ee4 100644
|
||||
--- a/lib/internal/modules/esm/resolve.js
|
||||
+++ b/lib/internal/modules/esm/resolve.js
|
||||
@@ -24,7 +24,7 @@ const {
|
||||
|
||||
@@ -15,7 +15,7 @@ to recognize asar files.
|
||||
This reverts commit 9cf2e1f55b8446a7cde23699d00a3be73aa0c8f1.
|
||||
|
||||
diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js
|
||||
index 69f73f829706deddc4f328b78af9d58434af647d..1d53a2a47423150e822bb917b2725d3a6a794814 100644
|
||||
index 30f6abd5d2d3fb7aceaa6191ebd99642e34d9ee4..cd5f59a5c27afd74f68c920a6ab5b8bc7992458c 100644
|
||||
--- a/lib/internal/modules/esm/resolve.js
|
||||
+++ b/lib/internal/modules/esm/resolve.js
|
||||
@@ -36,10 +36,9 @@ const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
|
||||
|
||||
@@ -7,7 +7,8 @@ async function checkIfDocOnlyChange () {
|
||||
|
||||
if (prNumber || prURL) {
|
||||
try {
|
||||
// extract the PR number from the PR URL.
|
||||
// CircleCI doesn't provide the PR number except on forked PRs,
|
||||
// so to cover all cases we just extract it from the PR URL.
|
||||
if (!prNumber || isNaN(prNumber)) {
|
||||
if (args.prURL) {
|
||||
prNumber = prURL.split('/').pop();
|
||||
|
||||
72
script/download-circleci-artifacts.js
Normal file
72
script/download-circleci-artifacts.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
const fs = require('node:fs');
|
||||
const got = require('got');
|
||||
const stream = require('node:stream');
|
||||
const { promisify } = require('node:util');
|
||||
|
||||
const pipeline = promisify(stream.pipeline);
|
||||
|
||||
async function downloadArtifact (name, buildNum, dest) {
|
||||
const circleArtifactUrl = `https://circleci.com/api/v1.1/project/github/electron/electron/${args.buildNum}/artifacts?circle-token=${process.env.CIRCLE_TOKEN}`;
|
||||
const responsePromise = got(circleArtifactUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
const [response, artifacts] = await Promise.all([responsePromise, responsePromise.json()]);
|
||||
if (response.statusCode !== 200) {
|
||||
console.error('Could not fetch circleci artifact list, got status code:', response.statusCode);
|
||||
}
|
||||
const artifactToDownload = artifacts.find(artifact => {
|
||||
return (artifact.path === name);
|
||||
});
|
||||
if (!artifactToDownload) {
|
||||
console.log(`Could not find artifact called ${name} to download for build #${buildNum}.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`Downloading ${artifactToDownload.url}.`);
|
||||
let downloadError = false;
|
||||
await downloadWithRetry(artifactToDownload.url, dest).catch(err => {
|
||||
if (args.verbose) {
|
||||
console.log(`${artifactToDownload.url} could not be successfully downloaded. Error was:`, err);
|
||||
} else {
|
||||
console.log(`${artifactToDownload.url} could not be successfully downloaded.`);
|
||||
}
|
||||
downloadError = true;
|
||||
});
|
||||
if (!downloadError) {
|
||||
console.log(`Successfully downloaded ${name}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadWithRetry (url, directory) {
|
||||
let lastError;
|
||||
const downloadURL = `${url}?circle-token=${process.env.CIRCLE_TOKEN}`;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
console.log(`Attempting to download ${url} - attempt #${(i + 1)}`);
|
||||
try {
|
||||
return await downloadFile(downloadURL, directory);
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
await new Promise(resolve => setTimeout(resolve, 30000));
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
function downloadFile (url, directory) {
|
||||
return pipeline(
|
||||
got.stream(url),
|
||||
fs.createWriteStream(directory)
|
||||
);
|
||||
}
|
||||
|
||||
if (!args.name || !args.buildNum || !args.dest) {
|
||||
console.log(`Download CircleCI artifacts.
|
||||
Usage: download-circleci-artifacts.js [--buildNum=CIRCLE_BUILD_NUMBER] [--name=artifactName] [--dest] [--verbose]`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
downloadArtifact(args.name, args.buildNum, args.dest);
|
||||
}
|
||||
@@ -194,9 +194,6 @@ def get_buildtools_executable(name):
|
||||
chromium_platform = 'linux64'
|
||||
else:
|
||||
raise Exception(f"Unsupported platform: {sys.platform}")
|
||||
|
||||
if name == 'clang-format':
|
||||
chromium_platform += '-format'
|
||||
|
||||
path = os.path.join(buildtools, chromium_platform, name)
|
||||
if sys.platform == 'win32':
|
||||
|
||||
@@ -55,7 +55,7 @@ const CPPLINT_FILTERS = [
|
||||
];
|
||||
|
||||
function spawnAndCheckExitCode (cmd, args, opts) {
|
||||
opts = { stdio: 'inherit', shell: IS_WINDOWS, ...opts };
|
||||
opts = { stdio: 'inherit', ...opts };
|
||||
const { error, status, signal } = childProcess.spawnSync(cmd, args, opts);
|
||||
if (error) {
|
||||
// the subprocess failed or timed out
|
||||
@@ -100,12 +100,9 @@ const LINTERS = [{
|
||||
roots: ['shell'],
|
||||
test: filename => filename.endsWith('.cc') || (filename.endsWith('.h') && !isObjCHeader(filename)),
|
||||
run: (opts, filenames) => {
|
||||
const env = {
|
||||
CHROMIUM_BUILDTOOLS_PATH: path.resolve(ELECTRON_ROOT, '..', 'buildtools')
|
||||
};
|
||||
const clangFormatFlags = opts.fix ? ['--fix'] : [];
|
||||
for (const chunk of chunkFilenames(filenames)) {
|
||||
spawnAndCheckExitCode('python3', ['script/run-clang-format.py', ...clangFormatFlags, ...chunk], { env });
|
||||
spawnAndCheckExitCode('python3', ['script/run-clang-format.py', ...clangFormatFlags, ...chunk]);
|
||||
cpplint([`--filter=${CPPLINT_FILTERS.join(',')}`, ...chunk]);
|
||||
}
|
||||
}
|
||||
@@ -114,11 +111,8 @@ const LINTERS = [{
|
||||
roots: ['shell'],
|
||||
test: filename => filename.endsWith('.mm') || (filename.endsWith('.h') && isObjCHeader(filename)),
|
||||
run: (opts, filenames) => {
|
||||
const env = {
|
||||
CHROMIUM_BUILDTOOLS_PATH: path.resolve(ELECTRON_ROOT, '..', 'buildtools')
|
||||
};
|
||||
const clangFormatFlags = opts.fix ? ['--fix'] : [];
|
||||
spawnAndCheckExitCode('python3', ['script/run-clang-format.py', '-r', ...clangFormatFlags, ...filenames], { env });
|
||||
spawnAndCheckExitCode('python3', ['script/run-clang-format.py', '-r', ...clangFormatFlags, ...filenames]);
|
||||
const filter = [...CPPLINT_FILTERS, '-readability/braces'];
|
||||
cpplint(['--extensions=mm,h', `--filter=${filter.join(',')}`, ...filenames]);
|
||||
}
|
||||
@@ -130,7 +124,7 @@ const LINTERS = [{
|
||||
const rcfile = path.join(DEPOT_TOOLS, 'pylintrc-2.17');
|
||||
const args = ['--rcfile=' + rcfile, ...filenames];
|
||||
const env = { PYTHONPATH: path.join(ELECTRON_ROOT, 'script'), ...process.env };
|
||||
spawnAndCheckExitCode(IS_WINDOWS ? 'pylint-2.17.bat' : 'pylint-2.17', args, { env });
|
||||
spawnAndCheckExitCode('pylint-2.17', args, { env });
|
||||
}
|
||||
}, {
|
||||
key: 'javascript',
|
||||
@@ -176,6 +170,8 @@ const LINTERS = [{
|
||||
DEPOT_TOOLS_WIN_TOOLCHAIN: '0',
|
||||
...process.env
|
||||
};
|
||||
// Users may not have depot_tools in PATH.
|
||||
env.PATH = `${env.PATH}${path.delimiter}${DEPOT_TOOLS}`;
|
||||
const args = ['format', filename];
|
||||
if (!opts.fix) args.push('--dry-run');
|
||||
const result = childProcess.spawnSync('gn', args, { env, stdio: 'inherit', shell: true });
|
||||
|
||||
@@ -17,14 +17,6 @@ const args = require('minimist')(process.argv.slice(2), {
|
||||
string: ['only']
|
||||
});
|
||||
|
||||
const getNodeGypVersion = () => {
|
||||
const nanPackageJSONPath = path.join(NAN_DIR, 'package.json');
|
||||
const nanPackageJSON = JSON.parse(fs.readFileSync(nanPackageJSONPath, 'utf8'));
|
||||
const { devDependencies } = nanPackageJSON;
|
||||
const nodeGypVersion = devDependencies['node-gyp'];
|
||||
return nodeGypVersion || 'latest';
|
||||
};
|
||||
|
||||
async function main () {
|
||||
const outDir = utils.getOutDir({ shouldLog: true });
|
||||
const nodeDir = path.resolve(BASE, 'out', outDir, 'gen', 'node_headers');
|
||||
@@ -98,8 +90,7 @@ async function main () {
|
||||
env.LDFLAGS = ldflags;
|
||||
}
|
||||
|
||||
const nodeGypVersion = getNodeGypVersion();
|
||||
const { status: buildStatus, signal } = cp.spawnSync(NPX_CMD, [`node-gyp@${nodeGypVersion}`, 'rebuild', '--verbose', '--directory', 'test', '-j', 'max'], {
|
||||
const { status: buildStatus, signal } = cp.spawnSync(NPX_CMD, ['node-gyp', 'rebuild', '--verbose', '--directory', 'test', '-j', 'max'], {
|
||||
env,
|
||||
cwd: NAN_DIR,
|
||||
stdio: 'inherit',
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
const { appCredentialsFromString, getTokenForRepo } = require('@electron/github-app-auth');
|
||||
const cp = require('node:child_process');
|
||||
|
||||
if (!process.env.CIRCLE_BRANCH) {
|
||||
console.error('Not building for a specific branch, can\'t autopush a patch');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.env.CIRCLE_PR_NUMBER) {
|
||||
console.error('Building for a forked PR, can\'t autopush a patch');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const token = await getTokenForRepo(
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ import traceback
|
||||
import tempfile
|
||||
|
||||
from functools import partial
|
||||
from lib.util import get_buildtools_executable
|
||||
from lib.util import get_depot_tools_executable
|
||||
|
||||
DEFAULT_EXTENSIONS = 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx,mm'
|
||||
DEFAULT_CLANG_FORMAT_IGNORE = '.clang-format-ignore'
|
||||
@@ -134,7 +134,6 @@ def run_clang_format_diff(args, file_name):
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
encoding='utf-8',
|
||||
shell=True) as proc:
|
||||
outs = list(proc.stdout.readlines())
|
||||
errs = list(proc.stderr.readlines())
|
||||
@@ -187,7 +186,10 @@ def colorize(diff_lines):
|
||||
def print_diff(diff_lines, use_color):
|
||||
if use_color:
|
||||
diff_lines = colorize(diff_lines)
|
||||
sys.stdout.writelines(diff_lines)
|
||||
if sys.version_info[0] < 3:
|
||||
sys.stdout.writelines((l.encode('utf-8') for l in diff_lines))
|
||||
else:
|
||||
sys.stdout.writelines(diff_lines)
|
||||
|
||||
|
||||
def print_trouble(prog, message, use_colors):
|
||||
@@ -203,7 +205,7 @@ def main():
|
||||
'--clang-format-executable',
|
||||
metavar='EXECUTABLE',
|
||||
help='path to the clang-format executable',
|
||||
default=get_buildtools_executable('clang-format'))
|
||||
default=get_depot_tools_executable('clang-format'))
|
||||
parser.add_argument(
|
||||
'--extensions',
|
||||
help='comma-separated list of file extensions'
|
||||
|
||||
@@ -167,16 +167,6 @@ std::unique_ptr<ThumbnailCapturer> MakeScreenCapturer() {
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR monitor,
|
||||
HDC hdc,
|
||||
LPRECT rect,
|
||||
LPARAM data) {
|
||||
reinterpret_cast<std::vector<HMONITOR>*>(data)->push_back(monitor);
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gin {
|
||||
@@ -399,38 +389,18 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
|
||||
if (using_directx_capturer_) {
|
||||
std::vector<std::string> device_names;
|
||||
// Crucially, this list of device names will be in the same order as
|
||||
// |screen_sources|.
|
||||
// |media_list_sources|.
|
||||
if (!webrtc::DxgiDuplicatorController::Instance()->GetDeviceNames(
|
||||
&device_names)) {
|
||||
HandleFailure();
|
||||
return;
|
||||
}
|
||||
DCHECK_EQ(device_names.size(), screen_sources.size());
|
||||
|
||||
std::vector<HMONITOR> monitors;
|
||||
EnumDisplayMonitors(nullptr, nullptr, EnumDisplayMonitorsCallback,
|
||||
reinterpret_cast<LPARAM>(&monitors));
|
||||
|
||||
base::flat_map<std::string, int64_t> device_name_to_id;
|
||||
device_name_to_id.reserve(monitors.size());
|
||||
for (auto* monitor : monitors) {
|
||||
MONITORINFOEX monitorInfo{{sizeof(MONITORINFOEX)}};
|
||||
if (!GetMonitorInfo(monitor, &monitorInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
device_name_to_id[base::WideToUTF8(monitorInfo.szDevice)] =
|
||||
display::win::internal::DisplayInfo::DisplayIdFromMonitorInfo(
|
||||
monitorInfo);
|
||||
}
|
||||
|
||||
int device_name_index = 0;
|
||||
for (auto& source : screen_sources) {
|
||||
const auto& device_name = device_names[device_name_index++];
|
||||
if (auto id_iter = device_name_to_id.find(device_name);
|
||||
id_iter != device_name_to_id.end()) {
|
||||
source.display_id = base::NumberToString(id_iter->second);
|
||||
}
|
||||
const int64_t device_id = base::PersistentHash(device_name);
|
||||
source.display_id = base::NumberToString(device_id);
|
||||
}
|
||||
}
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
|
||||
@@ -149,12 +149,6 @@ void DownloadItem::Cancel() {
|
||||
download_item_->Cancel(true);
|
||||
}
|
||||
|
||||
int64_t DownloadItem::GetCurrentBytesPerSecond() const {
|
||||
if (!CheckAlive())
|
||||
return 0;
|
||||
return download_item_->CurrentSpeed();
|
||||
}
|
||||
|
||||
int64_t DownloadItem::GetReceivedBytes() const {
|
||||
if (!CheckAlive())
|
||||
return 0;
|
||||
@@ -167,12 +161,6 @@ int64_t DownloadItem::GetTotalBytes() const {
|
||||
return download_item_->GetTotalBytes();
|
||||
}
|
||||
|
||||
int DownloadItem::GetPercentComplete() const {
|
||||
if (!CheckAlive())
|
||||
return 0;
|
||||
return download_item_->PercentComplete();
|
||||
}
|
||||
|
||||
std::string DownloadItem::GetMimeType() const {
|
||||
if (!CheckAlive())
|
||||
return "";
|
||||
@@ -260,12 +248,6 @@ double DownloadItem::GetStartTime() const {
|
||||
return download_item_->GetStartTime().InSecondsFSinceUnixEpoch();
|
||||
}
|
||||
|
||||
double DownloadItem::GetEndTime() const {
|
||||
if (!CheckAlive())
|
||||
return 0;
|
||||
return download_item_->GetEndTime().InSecondsFSinceUnixEpoch();
|
||||
}
|
||||
|
||||
// static
|
||||
gin::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
@@ -276,11 +258,8 @@ gin::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder(
|
||||
.SetMethod("resume", &DownloadItem::Resume)
|
||||
.SetMethod("canResume", &DownloadItem::CanResume)
|
||||
.SetMethod("cancel", &DownloadItem::Cancel)
|
||||
.SetMethod("getCurrentBytesPerSecond",
|
||||
&DownloadItem::GetCurrentBytesPerSecond)
|
||||
.SetMethod("getReceivedBytes", &DownloadItem::GetReceivedBytes)
|
||||
.SetMethod("getTotalBytes", &DownloadItem::GetTotalBytes)
|
||||
.SetMethod("getPercentComplete", &DownloadItem::GetPercentComplete)
|
||||
.SetMethod("getMimeType", &DownloadItem::GetMimeType)
|
||||
.SetMethod("hasUserGesture", &DownloadItem::HasUserGesture)
|
||||
.SetMethod("getFilename", &DownloadItem::GetFilename)
|
||||
@@ -297,8 +276,7 @@ gin::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder(
|
||||
.SetMethod("getSaveDialogOptions", &DownloadItem::GetSaveDialogOptions)
|
||||
.SetMethod("getLastModifiedTime", &DownloadItem::GetLastModifiedTime)
|
||||
.SetMethod("getETag", &DownloadItem::GetETag)
|
||||
.SetMethod("getStartTime", &DownloadItem::GetStartTime)
|
||||
.SetMethod("getEndTime", &DownloadItem::GetEndTime);
|
||||
.SetMethod("getStartTime", &DownloadItem::GetStartTime);
|
||||
}
|
||||
|
||||
const char* DownloadItem::GetTypeName() {
|
||||
|
||||
@@ -62,10 +62,8 @@ class DownloadItem : public gin::Wrappable<DownloadItem>,
|
||||
void Resume();
|
||||
bool CanResume() const;
|
||||
void Cancel();
|
||||
int64_t GetCurrentBytesPerSecond() const;
|
||||
int64_t GetReceivedBytes() const;
|
||||
int64_t GetTotalBytes() const;
|
||||
int GetPercentComplete() const;
|
||||
std::string GetMimeType() const;
|
||||
bool HasUserGesture() const;
|
||||
std::string GetFilename() const;
|
||||
@@ -78,7 +76,6 @@ class DownloadItem : public gin::Wrappable<DownloadItem>,
|
||||
std::string GetLastModifiedTime() const;
|
||||
std::string GetETag() const;
|
||||
double GetStartTime() const;
|
||||
double GetEndTime() const;
|
||||
|
||||
base::FilePath save_path_;
|
||||
file_dialog::DialogSettings dialog_options_;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "ash/style/rounded_rect_cutout_path_builder.h"
|
||||
#include "gin/data_object_builder.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
@@ -180,7 +179,7 @@ View::~View() {
|
||||
return;
|
||||
view_->RemoveObserver(this);
|
||||
if (delete_view_)
|
||||
view_.ClearAndDelete();
|
||||
delete view_;
|
||||
}
|
||||
|
||||
void View::ReorderChildView(gin::Handle<View> child, size_t index) {
|
||||
@@ -339,38 +338,6 @@ void View::SetBackgroundColor(std::optional<WrappedSkColor> color) {
|
||||
view_->SetBackground(color ? views::CreateSolidBackground(*color) : nullptr);
|
||||
}
|
||||
|
||||
void View::SetBorderRadius(int radius) {
|
||||
border_radius_ = radius;
|
||||
ApplyBorderRadius();
|
||||
}
|
||||
|
||||
void View::ApplyBorderRadius() {
|
||||
if (!border_radius_.has_value() || !view_)
|
||||
return;
|
||||
|
||||
auto size = view_->bounds().size();
|
||||
|
||||
// Restrict border radius to the constraints set in the path builder class.
|
||||
// If the constraints are exceeded, the builder will crash.
|
||||
int radius;
|
||||
{
|
||||
float r = border_radius_.value() * 1.f;
|
||||
r = std::min(r, size.width() / 2.f);
|
||||
r = std::min(r, size.height() / 2.f);
|
||||
r = std::max(r, 0.f);
|
||||
radius = std::floor(r);
|
||||
}
|
||||
|
||||
// RoundedRectCutoutPathBuilder has a minimum size of 32 x 32.
|
||||
if (radius > 0 && size.width() >= 32 && size.height() >= 32) {
|
||||
auto builder = ash::RoundedRectCutoutPathBuilder(gfx::SizeF(size));
|
||||
builder.CornerRadius(radius);
|
||||
view_->SetClipPath(builder.Build());
|
||||
} else {
|
||||
view_->SetClipPath(SkPath());
|
||||
}
|
||||
}
|
||||
|
||||
void View::SetVisible(bool visible) {
|
||||
if (!view_)
|
||||
return;
|
||||
@@ -378,7 +345,6 @@ void View::SetVisible(bool visible) {
|
||||
}
|
||||
|
||||
void View::OnViewBoundsChanged(views::View* observed_view) {
|
||||
ApplyBorderRadius();
|
||||
Emit("bounds-changed");
|
||||
}
|
||||
|
||||
@@ -427,7 +393,6 @@ void View::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("setBounds", &View::SetBounds)
|
||||
.SetMethod("getBounds", &View::GetBounds)
|
||||
.SetMethod("setBackgroundColor", &View::SetBackgroundColor)
|
||||
.SetMethod("setBorderRadius", &View::SetBorderRadius)
|
||||
.SetMethod("setLayout", &View::SetLayout)
|
||||
.SetMethod("setVisible", &View::SetVisible);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ class View : public gin_helper::EventEmitter<View>,
|
||||
void SetLayout(v8::Isolate* isolate, v8::Local<v8::Object> value);
|
||||
std::vector<v8::Local<v8::Value>> GetChildren();
|
||||
void SetBackgroundColor(std::optional<WrappedSkColor> color);
|
||||
void SetBorderRadius(int radius);
|
||||
void SetVisible(bool visible);
|
||||
|
||||
// views::ViewObserver
|
||||
@@ -45,7 +44,6 @@ class View : public gin_helper::EventEmitter<View>,
|
||||
void OnViewIsDeleting(views::View* observed_view) override;
|
||||
|
||||
views::View* view() const { return view_; }
|
||||
std::optional<int> border_radius() const { return border_radius_; }
|
||||
|
||||
// disable copy
|
||||
View(const View&) = delete;
|
||||
@@ -60,11 +58,9 @@ class View : public gin_helper::EventEmitter<View>,
|
||||
void set_delete_view(bool should) { delete_view_ = should; }
|
||||
|
||||
private:
|
||||
void ApplyBorderRadius();
|
||||
void ReorderChildView(gin::Handle<View> child, size_t index);
|
||||
|
||||
std::vector<v8::Global<v8::Object>> child_views_;
|
||||
std::optional<int> border_radius_;
|
||||
|
||||
bool delete_view_ = true;
|
||||
raw_ptr<views::View> view_ = nullptr;
|
||||
|
||||
@@ -3729,17 +3729,14 @@ void WebContents::SetBackgroundColor(std::optional<SkColor> maybe_color) {
|
||||
type_ == Type::kBrowserView
|
||||
? SK_ColorTRANSPARENT
|
||||
: SK_ColorWHITE);
|
||||
bool is_opaque = SkColorGetA(color) == SK_AlphaOPAQUE;
|
||||
web_contents()->SetPageBaseBackgroundColor(color);
|
||||
|
||||
content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame();
|
||||
if (!rfh)
|
||||
return;
|
||||
|
||||
content::RenderWidgetHostView* rwhv = rfh->GetView();
|
||||
if (rwhv) {
|
||||
// RenderWidgetHostView doesn't allow setting an alpha that's not 0 or 255.
|
||||
rwhv->SetBackgroundColor(is_opaque ? color : SK_ColorTRANSPARENT);
|
||||
rwhv->SetBackgroundColor(color);
|
||||
static_cast<content::RenderWidgetHostViewBase*>(rwhv)
|
||||
->SetContentBackgroundColor(color);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
#include "shell/common/options_switches.h"
|
||||
#include "third_party/skia/include/core/SkRegion.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
#include "ui/gfx/geometry/rounded_corners_f.h"
|
||||
#include "ui/views/controls/webview/webview.h"
|
||||
#include "ui/views/layout/flex_layout_types.h"
|
||||
#include "ui/views/view_class_properties.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
@@ -67,25 +65,6 @@ void WebContentsView::SetBackgroundColor(std::optional<WrappedSkColor> color) {
|
||||
}
|
||||
}
|
||||
|
||||
void WebContentsView::SetBorderRadius(int radius) {
|
||||
View::SetBorderRadius(radius);
|
||||
ApplyBorderRadius();
|
||||
}
|
||||
|
||||
void WebContentsView::ApplyBorderRadius() {
|
||||
if (border_radius().has_value() && api_web_contents_ && view()->GetWidget()) {
|
||||
auto* web_view = api_web_contents_->inspectable_web_contents()
|
||||
->GetView()
|
||||
->contents_web_view();
|
||||
|
||||
// WebView won't exist for offscreen rendering.
|
||||
if (web_view) {
|
||||
web_view->holder()->SetCornerRadii(
|
||||
gfx::RoundedCornersF(border_radius().value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
|
||||
if (api_web_contents_) {
|
||||
gfx::Point local_point(point);
|
||||
@@ -114,7 +93,6 @@ void WebContentsView::OnViewAddedToWidget(views::View* observed_view) {
|
||||
// because that's handled in the WebContents dtor called prior.
|
||||
api_web_contents_->SetOwnerWindow(native_window);
|
||||
native_window->AddDraggableRegionProvider(this);
|
||||
ApplyBorderRadius();
|
||||
}
|
||||
|
||||
void WebContentsView::OnViewRemovedFromWidget(views::View* observed_view) {
|
||||
@@ -220,7 +198,6 @@ void WebContentsView::BuildPrototype(
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "WebContentsView"));
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("setBackgroundColor", &WebContentsView::SetBackgroundColor)
|
||||
.SetMethod("setBorderRadius", &WebContentsView::SetBorderRadius)
|
||||
.SetProperty("webContents", &WebContentsView::GetWebContents);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ class WebContentsView : public View,
|
||||
// Public APIs.
|
||||
gin::Handle<WebContents> GetWebContents(v8::Isolate* isolate);
|
||||
void SetBackgroundColor(std::optional<WrappedSkColor> color);
|
||||
void SetBorderRadius(int radius);
|
||||
|
||||
int NonClientHitTest(const gfx::Point& point) override;
|
||||
|
||||
@@ -58,8 +57,6 @@ class WebContentsView : public View,
|
||||
private:
|
||||
static gin_helper::WrappableBase* New(gin_helper::Arguments* args);
|
||||
|
||||
void ApplyBorderRadius();
|
||||
|
||||
// Keep a reference to v8 wrapper.
|
||||
v8::Global<v8::Value> web_contents_;
|
||||
raw_ptr<api::WebContents> api_web_contents_;
|
||||
|
||||
@@ -329,7 +329,7 @@ const std::string& BrowserProcessImpl::GetSystemLocale() const {
|
||||
electron::ResolveProxyHelper* BrowserProcessImpl::GetResolveProxyHelper() {
|
||||
if (!resolve_proxy_helper_) {
|
||||
resolve_proxy_helper_ = base::MakeRefCounted<electron::ResolveProxyHelper>(
|
||||
nullptr /* browser_context */);
|
||||
system_network_context_manager()->GetContext());
|
||||
}
|
||||
return resolve_proxy_helper_.get();
|
||||
}
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
|
||||
#if BUILDFLAG(USE_NSS_CERTS)
|
||||
#include "net/ssl/client_cert_store_nss.h"
|
||||
#include "shell/browser/electron_crypto_module_delegate_nss.h"
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
#include "net/ssl/client_cert_store_win.h"
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
@@ -425,7 +424,6 @@ void ElectronBrowserClient::OverrideWebkitPrefs(
|
||||
renderer_prefs->can_accept_load_drops = false;
|
||||
|
||||
ui::NativeTheme* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
|
||||
prefs->in_forced_colors = native_theme->InForcedColorsMode();
|
||||
prefs->preferred_color_scheme =
|
||||
native_theme->ShouldUseDarkColors()
|
||||
? blink::mojom::PreferredColorScheme::kDark
|
||||
@@ -784,11 +782,7 @@ ElectronBrowserClient::CreateClientCertStore(
|
||||
content::BrowserContext* browser_context) {
|
||||
#if BUILDFLAG(USE_NSS_CERTS)
|
||||
return std::make_unique<net::ClientCertStoreNSS>(
|
||||
base::BindRepeating([](const net::HostPortPair& server) {
|
||||
crypto::CryptoModuleBlockingPasswordDelegate* delegate =
|
||||
new ElectronNSSCryptoModuleDelegate(server);
|
||||
return delegate;
|
||||
}));
|
||||
net::ClientCertStoreNSS::PasswordDelegateFactory());
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
return std::make_unique<net::ClientCertStoreWin>();
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
|
||||
@@ -609,7 +609,8 @@ ElectronBrowserContext::GetFileSystemAccessPermissionContext() {
|
||||
|
||||
ResolveProxyHelper* ElectronBrowserContext::GetResolveProxyHelper() {
|
||||
if (!resolve_proxy_helper_) {
|
||||
resolve_proxy_helper_ = base::MakeRefCounted<ResolveProxyHelper>(this);
|
||||
resolve_proxy_helper_ = base::MakeRefCounted<ResolveProxyHelper>(
|
||||
GetDefaultStoragePartition()->GetNetworkContext());
|
||||
}
|
||||
return resolve_proxy_helper_.get();
|
||||
}
|
||||
|
||||
@@ -604,7 +604,6 @@ void ElectronBrowserMainParts::PostMainMessageLoopRun() {
|
||||
node_env_->set_trace_sync_io(false);
|
||||
js_env_->DestroyMicrotasksRunner();
|
||||
node::Stop(node_env_.get(), node::StopFlags::kDoNotTerminateIsolate);
|
||||
node_bindings_->set_uv_env(nullptr);
|
||||
node_env_.reset();
|
||||
|
||||
auto default_context_key = ElectronBrowserContext::PartitionKey("", false);
|
||||
|
||||
@@ -155,10 +155,10 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
|
||||
// Before then, we just exit() without any intermediate steps.
|
||||
std::optional<int> exit_code_;
|
||||
|
||||
const std::unique_ptr<NodeBindings> node_bindings_;
|
||||
std::unique_ptr<NodeBindings> node_bindings_;
|
||||
|
||||
// depends-on: node_bindings_
|
||||
const std::unique_ptr<ElectronBindings> electron_bindings_;
|
||||
std::unique_ptr<ElectronBindings> electron_bindings_;
|
||||
|
||||
// depends-on: node_bindings_
|
||||
std::unique_ptr<JavascriptEnvironment> js_env_;
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2024 Switchboard
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/electron_crypto_module_delegate_nss.h"
|
||||
|
||||
#include "crypto/nss_crypto_module_delegate.h"
|
||||
#include "shell/browser/api/electron_api_app.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_helper/callback.h"
|
||||
#include "shell/common/v8_value_serializer.h"
|
||||
|
||||
ElectronNSSCryptoModuleDelegate::ElectronNSSCryptoModuleDelegate(
|
||||
const net::HostPortPair& server)
|
||||
: server_(server),
|
||||
event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
|
||||
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
|
||||
|
||||
ElectronNSSCryptoModuleDelegate::~ElectronNSSCryptoModuleDelegate() = default;
|
||||
|
||||
std::string ElectronNSSCryptoModuleDelegate::RequestPassword(
|
||||
const std::string& token_name,
|
||||
bool retry,
|
||||
bool* cancelled) {
|
||||
DCHECK(!event_.IsSignaled());
|
||||
event_.Reset();
|
||||
|
||||
if (content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
&ElectronNSSCryptoModuleDelegate::RequestPasswordOnUIThread, this,
|
||||
token_name, retry))) {
|
||||
base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
|
||||
event_.Wait();
|
||||
}
|
||||
*cancelled = cancelled_;
|
||||
return password_;
|
||||
}
|
||||
|
||||
void ElectronNSSCryptoModuleDelegate::RequestPasswordOnUIThread(
|
||||
const std::string& token_name,
|
||||
bool retry) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
gin_helper::internal::Event::New(isolate);
|
||||
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();
|
||||
gin_helper::Dictionary dict(isolate, event_object);
|
||||
dict.Set("hostname", server_.host());
|
||||
dict.Set("tokenName", token_name);
|
||||
dict.Set("isRetry", retry);
|
||||
|
||||
electron::api::App::Get()->EmitWithoutEvent(
|
||||
"-client-certificate-request-password", event_object,
|
||||
base::BindOnce(&ElectronNSSCryptoModuleDelegate::OnPassword, this));
|
||||
|
||||
if (!event->GetDefaultPrevented()) {
|
||||
password_ = "";
|
||||
cancelled_ = true;
|
||||
event_.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronNSSCryptoModuleDelegate::OnPassword(gin::Arguments* args) {
|
||||
args->GetNext(&password_);
|
||||
cancelled_ = password_.empty();
|
||||
event_.Signal();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2024 Switchboard
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_CRYPTO_MODULE_DELEGATE_NSS_H_
|
||||
#define ELECTRON_SHELL_CRYPTO_MODULE_DELEGATE_NSS_H_
|
||||
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "crypto/nss_crypto_module_delegate.h"
|
||||
#include "net/base/host_port_pair.h"
|
||||
|
||||
namespace gin {
|
||||
class Arguments;
|
||||
}
|
||||
|
||||
class ElectronNSSCryptoModuleDelegate
|
||||
: public crypto::CryptoModuleBlockingPasswordDelegate {
|
||||
public:
|
||||
explicit ElectronNSSCryptoModuleDelegate(const net::HostPortPair& server);
|
||||
ElectronNSSCryptoModuleDelegate(const ElectronNSSCryptoModuleDelegate&) =
|
||||
delete;
|
||||
ElectronNSSCryptoModuleDelegate& operator=(
|
||||
const ElectronNSSCryptoModuleDelegate&) = delete;
|
||||
|
||||
std::string RequestPassword(const std::string& token_name,
|
||||
bool retry,
|
||||
bool* cancelled) override;
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<ElectronNSSCryptoModuleDelegate>;
|
||||
~ElectronNSSCryptoModuleDelegate() override;
|
||||
|
||||
void RequestPasswordOnUIThread(const std::string& token_name, bool retry);
|
||||
void OnPassword(gin::Arguments* args);
|
||||
|
||||
net::HostPortPair server_;
|
||||
base::WaitableEvent event_;
|
||||
std::string password_;
|
||||
bool cancelled_ = false;
|
||||
};
|
||||
|
||||
#endif // ELECTRON_SHELL_CRYPTO_MODULE_DELEGATE_NSS_H_
|
||||
@@ -16,6 +16,33 @@ ElectronSpeechRecognitionManagerDelegate::
|
||||
ElectronSpeechRecognitionManagerDelegate::
|
||||
~ElectronSpeechRecognitionManagerDelegate() = default;
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnRecognitionStart(
|
||||
int session_id) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnRecognitionEnd(
|
||||
int session_id) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnRecognitionResults(
|
||||
int session_id,
|
||||
const std::vector<media::mojom::WebSpeechRecognitionResultPtr>& results) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnRecognitionError(
|
||||
int session_id,
|
||||
const media::mojom::SpeechRecognitionError& error) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::OnAudioLevelsChange(
|
||||
int session_id,
|
||||
float volume,
|
||||
float noise_volume) {}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
|
||||
int session_id,
|
||||
base::OnceCallback<void(bool ask_user, bool is_allowed)> callback) {
|
||||
@@ -24,7 +51,7 @@ void ElectronSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
|
||||
|
||||
content::SpeechRecognitionEventListener*
|
||||
ElectronSpeechRecognitionManagerDelegate::GetEventListener() {
|
||||
return nullptr;
|
||||
return this;
|
||||
}
|
||||
|
||||
void ElectronSpeechRecognitionManagerDelegate::BindSpeechRecognitionContext(
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
#ifndef ELECTRON_SHELL_BROWSER_ELECTRON_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_
|
||||
#define ELECTRON_SHELL_BROWSER_ELECTRON_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "content/public/browser/speech_recognition_event_listener.h"
|
||||
#include "content/public/browser/speech_recognition_manager_delegate.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ElectronSpeechRecognitionManagerDelegate
|
||||
: public content::SpeechRecognitionManagerDelegate {
|
||||
: public content::SpeechRecognitionManagerDelegate,
|
||||
public content::SpeechRecognitionEventListener {
|
||||
public:
|
||||
ElectronSpeechRecognitionManagerDelegate();
|
||||
~ElectronSpeechRecognitionManagerDelegate() override;
|
||||
@@ -22,6 +25,23 @@ class ElectronSpeechRecognitionManagerDelegate
|
||||
ElectronSpeechRecognitionManagerDelegate& operator=(
|
||||
const ElectronSpeechRecognitionManagerDelegate&) = delete;
|
||||
|
||||
// content::SpeechRecognitionEventListener:
|
||||
void OnRecognitionStart(int session_id) override;
|
||||
void OnAudioStart(int session_id) override;
|
||||
void OnSoundStart(int session_id) override;
|
||||
void OnSoundEnd(int session_id) override;
|
||||
void OnAudioEnd(int session_id) override;
|
||||
void OnRecognitionEnd(int session_id) override;
|
||||
void OnRecognitionResults(
|
||||
int session_id,
|
||||
const std::vector<media::mojom::WebSpeechRecognitionResultPtr>&) override;
|
||||
void OnRecognitionError(
|
||||
int session_id,
|
||||
const media::mojom::SpeechRecognitionError& error) override;
|
||||
void OnAudioLevelsChange(int session_id,
|
||||
float volume,
|
||||
float noise_volume) override;
|
||||
|
||||
// content::SpeechRecognitionManagerDelegate:
|
||||
void CheckRecognitionIsAllowed(
|
||||
int session_id,
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include "base/json/values_util.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h" // nogncheck
|
||||
@@ -40,24 +38,6 @@ using HandleType = content::FileSystemAccessPermissionContext::HandleType;
|
||||
using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
|
||||
using blink::mojom::PermissionStatus;
|
||||
|
||||
// Dictionary keys for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting.
|
||||
// Schema (per origin):
|
||||
// {
|
||||
// ...
|
||||
// {
|
||||
// "default-id" : { "path" : <path> , "path-type" : <type>}
|
||||
// "custom-id-fruit" : { "path" : <path> , "path-type" : <type> }
|
||||
// "custom-id-flower" : { "path" : <path> , "path-type" : <type> }
|
||||
// ...
|
||||
// }
|
||||
// ...
|
||||
// }
|
||||
const char kDefaultLastPickedDirectoryKey[] = "default-id";
|
||||
const char kCustomLastPickedDirectoryKey[] = "custom-id";
|
||||
const char kPathKey[] = "path";
|
||||
const char kPathTypeKey[] = "path-type";
|
||||
const char kTimestampKey[] = "timestamp";
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
[[nodiscard]] constexpr bool ContainsInvalidDNSCharacter(
|
||||
base::FilePath::StringType hostname) {
|
||||
@@ -99,7 +79,7 @@ bool MaybeIsLocalUNCPath(const base::FilePath& path) {
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#endif
|
||||
|
||||
// Describes a rule for blocking a directory, which can be constructed
|
||||
// dynamically (based on state) or statically (from kBlockedPaths).
|
||||
@@ -122,7 +102,7 @@ bool ShouldBlockAccessToPath(const base::FilePath& path,
|
||||
MaybeIsLocalUNCPath(path)) {
|
||||
return true;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#endif
|
||||
|
||||
// Add the hard-coded rules to the dynamic rules.
|
||||
for (auto const& [key, rule_path, type] :
|
||||
@@ -170,11 +150,6 @@ bool ShouldBlockAccessToPath(const base::FilePath& path,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GenerateLastPickedDirectoryKey(const std::string& id) {
|
||||
return id.empty() ? kDefaultLastPickedDirectoryKey
|
||||
: base::StrCat({kCustomLastPickedDirectoryKey, "-", id});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace electron {
|
||||
@@ -392,9 +367,8 @@ struct FileSystemAccessPermissionContext::OriginState {
|
||||
};
|
||||
|
||||
FileSystemAccessPermissionContext::FileSystemAccessPermissionContext(
|
||||
content::BrowserContext* browser_context,
|
||||
const base::Clock* clock)
|
||||
: browser_context_(browser_context), clock_(clock) {
|
||||
content::BrowserContext* browser_context)
|
||||
: browser_context_(browser_context) {
|
||||
DETACH_FROM_SEQUENCE(sequence_checker_);
|
||||
}
|
||||
|
||||
@@ -597,63 +571,13 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
|
||||
std::move(callback).Run(SensitiveEntryResult::kAllowed);
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::MaybeEvictEntries(
|
||||
base::Value::Dict& dict) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
std::vector<std::pair<base::Time, std::string>> entries;
|
||||
entries.reserve(dict.size());
|
||||
for (auto entry : dict) {
|
||||
// Don't evict the default ID.
|
||||
if (entry.first == kDefaultLastPickedDirectoryKey) {
|
||||
continue;
|
||||
}
|
||||
// If the data is corrupted and `entry.second` is for some reason not a
|
||||
// dict, it should be first in line for eviction.
|
||||
auto timestamp = base::Time::Min();
|
||||
if (entry.second.is_dict()) {
|
||||
timestamp = base::ValueToTime(entry.second.GetDict().Find(kTimestampKey))
|
||||
.value_or(base::Time::Min());
|
||||
}
|
||||
entries.emplace_back(timestamp, entry.first);
|
||||
}
|
||||
|
||||
if (entries.size() <= max_ids_per_origin_) {
|
||||
return;
|
||||
}
|
||||
|
||||
base::ranges::sort(entries);
|
||||
size_t entries_to_remove = entries.size() - max_ids_per_origin_;
|
||||
for (size_t i = 0; i < entries_to_remove; ++i) {
|
||||
bool did_remove_entry = dict.Remove(entries[i].second);
|
||||
DCHECK(did_remove_entry);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::SetLastPickedDirectory(
|
||||
const url::Origin& origin,
|
||||
const std::string& id,
|
||||
const base::FilePath& path,
|
||||
const PathType type) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// Create an entry into the nested dictionary.
|
||||
base::Value::Dict entry;
|
||||
entry.Set(kPathKey, base::FilePathToValue(path));
|
||||
entry.Set(kPathTypeKey, static_cast<int>(type));
|
||||
entry.Set(kTimestampKey, base::TimeToValue(clock_->Now()));
|
||||
|
||||
auto it = id_pathinfo_map_.find(origin);
|
||||
if (it != id_pathinfo_map_.end()) {
|
||||
base::Value::Dict& dict = it->second;
|
||||
dict.Set(GenerateLastPickedDirectoryKey(id), std::move(entry));
|
||||
MaybeEvictEntries(dict);
|
||||
} else {
|
||||
base::Value::Dict dict;
|
||||
dict.Set(GenerateLastPickedDirectoryKey(id), std::move(entry));
|
||||
MaybeEvictEntries(dict);
|
||||
id_pathinfo_map_.insert(std::make_pair(origin, std::move(dict)));
|
||||
}
|
||||
LOG(INFO) << "NOTIMPLEMENTED SetLastPickedDirectory: " << path.value();
|
||||
}
|
||||
|
||||
FileSystemAccessPermissionContext::PathInfo
|
||||
@@ -661,27 +585,8 @@ FileSystemAccessPermissionContext::GetLastPickedDirectory(
|
||||
const url::Origin& origin,
|
||||
const std::string& id) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto it = id_pathinfo_map_.find(origin);
|
||||
|
||||
PathInfo path_info;
|
||||
if (it == id_pathinfo_map_.end()) {
|
||||
return path_info;
|
||||
}
|
||||
|
||||
auto* entry = it->second.FindDict(GenerateLastPickedDirectoryKey(id));
|
||||
if (!entry) {
|
||||
return path_info;
|
||||
}
|
||||
|
||||
auto type_int =
|
||||
entry->FindInt(kPathTypeKey).value_or(static_cast<int>(PathType::kLocal));
|
||||
path_info.type = type_int == static_cast<int>(PathType::kExternal)
|
||||
? PathType::kExternal
|
||||
: PathType::kLocal;
|
||||
path_info.path =
|
||||
base::ValueToFilePath(entry->Find(kPathKey)).value_or(base::FilePath());
|
||||
return path_info;
|
||||
LOG(INFO) << "NOTIMPLEMENTED GetLastPickedDirectory";
|
||||
return PathInfo();
|
||||
}
|
||||
|
||||
base::FilePath FileSystemAccessPermissionContext::GetWellKnownDirectoryPath(
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/time/clock.h"
|
||||
#include "base/time/default_clock.h"
|
||||
#include "base/values.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "content/public/browser/file_system_access_permission_context.h"
|
||||
|
||||
@@ -38,8 +35,7 @@ class FileSystemAccessPermissionContext
|
||||
enum class GrantType { kRead, kWrite };
|
||||
|
||||
explicit FileSystemAccessPermissionContext(
|
||||
content::BrowserContext* browser_context,
|
||||
const base::Clock* clock = base::DefaultClock::GetInstance());
|
||||
content::BrowserContext* browser_context);
|
||||
FileSystemAccessPermissionContext(const FileSystemAccessPermissionContext&) =
|
||||
delete;
|
||||
FileSystemAccessPermissionContext& operator=(
|
||||
@@ -137,8 +133,6 @@ class FileSystemAccessPermissionContext
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback,
|
||||
bool should_block);
|
||||
|
||||
void MaybeEvictEntries(base::Value::Dict& dict);
|
||||
|
||||
void CleanupPermissions(const url::Origin& origin);
|
||||
|
||||
bool AncestorHasActivePermission(const url::Origin& origin,
|
||||
@@ -152,13 +146,6 @@ class FileSystemAccessPermissionContext
|
||||
struct OriginState;
|
||||
std::map<url::Origin, OriginState> active_permissions_map_;
|
||||
|
||||
// Number of custom IDs an origin can specify.
|
||||
size_t max_ids_per_origin_ = 32u;
|
||||
|
||||
const raw_ptr<const base::Clock> clock_;
|
||||
|
||||
std::map<url::Origin, base::Value::Dict> id_pathinfo_map_;
|
||||
|
||||
base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/containers/map_util.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
@@ -150,18 +149,21 @@ void HidChooserContext::RevokeEphemeralDevicePermission(
|
||||
const url::Origin& origin,
|
||||
const device::mojom::HidDeviceInfo& device) {
|
||||
auto it = ephemeral_devices_.find(origin);
|
||||
if (it == ephemeral_devices_.end())
|
||||
return;
|
||||
if (it != ephemeral_devices_.end()) {
|
||||
std::set<std::string>& devices = it->second;
|
||||
for (auto guid = devices.begin(); guid != devices.end();) {
|
||||
DCHECK(base::Contains(devices_, *guid));
|
||||
|
||||
std::set<std::string>& device_guids = it->second;
|
||||
std::erase_if(device_guids, [&](const auto& guid) {
|
||||
auto* device_ptr = base::FindPtrOrNull(devices_, guid);
|
||||
return device_ptr &&
|
||||
device_ptr->physical_device_id == device.physical_device_id;
|
||||
});
|
||||
if (devices_[*guid]->physical_device_id != device.physical_device_id) {
|
||||
++guid;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (device_guids.empty())
|
||||
ephemeral_devices_.erase(it);
|
||||
guid = devices.erase(guid);
|
||||
if (devices.empty())
|
||||
ephemeral_devices_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HidChooserContext::HasDevicePermission(
|
||||
|
||||
@@ -8,20 +8,17 @@
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
#include "net/base/network_anonymization_key.h"
|
||||
#include "net/proxy_resolution/proxy_info.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/net/system_network_context_manager.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace electron {
|
||||
|
||||
ResolveProxyHelper::ResolveProxyHelper(ElectronBrowserContext* browser_context)
|
||||
: browser_context_(browser_context) {}
|
||||
ResolveProxyHelper::ResolveProxyHelper(
|
||||
network::mojom::NetworkContext* network_context)
|
||||
: network_context_(network_context) {}
|
||||
|
||||
ResolveProxyHelper::~ResolveProxyHelper() {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
@@ -55,18 +52,9 @@ void ResolveProxyHelper::StartPendingRequest() {
|
||||
receiver_.set_disconnect_handler(
|
||||
base::BindOnce(&ResolveProxyHelper::OnProxyLookupComplete,
|
||||
base::Unretained(this), net::ERR_ABORTED, std::nullopt));
|
||||
network::mojom::NetworkContext* network_context = nullptr;
|
||||
if (browser_context_) {
|
||||
network_context =
|
||||
browser_context_->GetDefaultStoragePartition()->GetNetworkContext();
|
||||
} else {
|
||||
DCHECK(SystemNetworkContextManager::GetInstance());
|
||||
network_context = SystemNetworkContextManager::GetInstance()->GetContext();
|
||||
}
|
||||
CHECK(network_context);
|
||||
network_context->LookUpProxyForURL(pending_requests_.front().url,
|
||||
net::NetworkAnonymizationKey(),
|
||||
std::move(proxy_lookup_client));
|
||||
network_context_->LookUpProxyForURL(pending_requests_.front().url,
|
||||
net::NetworkAnonymizationKey(),
|
||||
std::move(proxy_lookup_client));
|
||||
}
|
||||
|
||||
void ResolveProxyHelper::OnProxyLookupComplete(
|
||||
|
||||
@@ -12,20 +12,19 @@
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "mojo/public/cpp/bindings/receiver.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ElectronBrowserContext;
|
||||
|
||||
class ResolveProxyHelper
|
||||
: public base::RefCountedThreadSafe<ResolveProxyHelper>,
|
||||
network::mojom::ProxyLookupClient {
|
||||
public:
|
||||
using ResolveProxyCallback = base::OnceCallback<void(std::string)>;
|
||||
|
||||
explicit ResolveProxyHelper(ElectronBrowserContext* browser_context);
|
||||
explicit ResolveProxyHelper(network::mojom::NetworkContext* network_context);
|
||||
|
||||
void ResolveProxy(const GURL& url, ResolveProxyCallback callback);
|
||||
|
||||
@@ -71,7 +70,7 @@ class ResolveProxyHelper
|
||||
mojo::Receiver<network::mojom::ProxyLookupClient> receiver_{this};
|
||||
|
||||
// Weak Ref
|
||||
raw_ptr<ElectronBrowserContext> browser_context_;
|
||||
raw_ptr<network::mojom::NetworkContext> network_context_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -109,7 +109,7 @@ class OffScreenWebContentsView : public content::WebContentsView,
|
||||
raw_ptr<content::WebContents> web_contents_ = nullptr;
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
RAW_PTR_EXCLUSION OffScreenView* offScreenView_ = nullptr;
|
||||
RAW_PTR_EXCLUSION OffScreenView* offScreenView_;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -84,14 +84,14 @@ InspectableWebContentsView::InspectableWebContentsView(
|
||||
auto* contents_web_view = new views::WebView(nullptr);
|
||||
contents_web_view->SetWebContents(
|
||||
inspectable_web_contents_->GetWebContents());
|
||||
contents_view_ = contents_web_view_ = contents_web_view;
|
||||
contents_web_view_ = contents_web_view;
|
||||
} else {
|
||||
contents_view_ = new views::Label(u"No content under offscreen mode");
|
||||
contents_web_view_ = new views::Label(u"No content under offscreen mode");
|
||||
}
|
||||
|
||||
devtools_web_view_->SetVisible(false);
|
||||
AddChildView(devtools_web_view_.get());
|
||||
AddChildView(contents_view_.get());
|
||||
AddChildView(contents_web_view_.get());
|
||||
}
|
||||
|
||||
InspectableWebContentsView::~InspectableWebContentsView() {
|
||||
@@ -209,7 +209,7 @@ const std::u16string InspectableWebContentsView::GetTitle() {
|
||||
|
||||
void InspectableWebContentsView::Layout(PassKey) {
|
||||
if (!devtools_web_view_->GetVisible()) {
|
||||
contents_view_->SetBoundsRect(GetContentsBounds());
|
||||
contents_web_view_->SetBoundsRect(GetContentsBounds());
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
LayoutSuperclass<View>(this);
|
||||
return;
|
||||
@@ -227,7 +227,7 @@ void InspectableWebContentsView::Layout(PassKey) {
|
||||
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
|
||||
|
||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||
contents_view_->SetBoundsRect(new_contents_bounds);
|
||||
contents_web_view_->SetBoundsRect(new_contents_bounds);
|
||||
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
LayoutSuperclass<View>(this);
|
||||
|
||||
@@ -36,8 +36,6 @@ class InspectableWebContentsView : public views::View {
|
||||
return inspectable_web_contents_;
|
||||
}
|
||||
|
||||
views::WebView* contents_web_view() const { return contents_web_view_; }
|
||||
|
||||
// The delegate manages its own life.
|
||||
void SetDelegate(InspectableWebContentsViewDelegate* delegate) {
|
||||
delegate_ = delegate;
|
||||
@@ -69,9 +67,8 @@ class InspectableWebContentsView : public views::View {
|
||||
|
||||
std::unique_ptr<views::Widget> devtools_window_;
|
||||
raw_ptr<views::WebView> devtools_window_web_view_ = nullptr;
|
||||
raw_ptr<views::View> contents_web_view_ = nullptr;
|
||||
raw_ptr<views::WebView> devtools_web_view_ = nullptr;
|
||||
raw_ptr<views::WebView> contents_web_view_ = nullptr;
|
||||
raw_ptr<views::View> contents_view_ = nullptr;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
bool devtools_visible_ = false;
|
||||
|
||||
@@ -7,29 +7,23 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "components/content_settings/core/common/content_settings.h"
|
||||
#include "components/webrtc/media_stream_devices_controller.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/web_contents_user_data.h"
|
||||
#include "shell/browser/electron_permission_manager.h"
|
||||
// #include "shell/browser/media/media_stream_devices_controller.h"
|
||||
#include "components/content_settings/core/common/content_settings.h"
|
||||
#include "components/webrtc/media_stream_devices_controller.h"
|
||||
#include "shell/browser/media/media_capture_devices_dispatcher.h"
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
|
||||
#endif
|
||||
|
||||
using blink::mojom::MediaStreamRequestResult;
|
||||
using blink::mojom::MediaStreamType;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view MediaStreamTypeToString(
|
||||
blink::mojom::MediaStreamType type) {
|
||||
switch (type) {
|
||||
case MediaStreamType::DEVICE_AUDIO_CAPTURE:
|
||||
case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
|
||||
return "audio";
|
||||
case MediaStreamType::DEVICE_VIDEO_CAPTURE:
|
||||
case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
|
||||
return "video";
|
||||
default:
|
||||
return "unknown";
|
||||
@@ -54,74 +48,53 @@ namespace {
|
||||
-1 /* kFullDesktopScreenId */);
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
bool SystemMediaPermissionDenied(const content::MediaStreamRequest& request) {
|
||||
if (request.audio_type != MediaStreamType::NO_SERVICE) {
|
||||
const auto system_audio_permission =
|
||||
system_media_permissions::CheckSystemAudioCapturePermission();
|
||||
return system_audio_permission ==
|
||||
system_media_permissions::SystemPermission::kRestricted ||
|
||||
system_audio_permission ==
|
||||
system_media_permissions::SystemPermission::kDenied;
|
||||
}
|
||||
if (request.video_type != MediaStreamType::NO_SERVICE) {
|
||||
const auto system_video_permission =
|
||||
system_media_permissions::CheckSystemVideoCapturePermission();
|
||||
return system_video_permission ==
|
||||
system_media_permissions::SystemPermission::kRestricted ||
|
||||
system_video_permission ==
|
||||
system_media_permissions::SystemPermission::kDenied;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handles requests for legacy-style `navigator.getUserMedia(...)` calls.
|
||||
// This includes desktop capture through the chromeMediaSource /
|
||||
// chromeMediaSourceId constraints.
|
||||
void HandleUserMediaRequest(const content::MediaStreamRequest& request,
|
||||
content::MediaResponseCallback callback) {
|
||||
auto stream_devices_set = blink::mojom::StreamDevicesSet::New();
|
||||
auto devices = blink::mojom::StreamDevices::New();
|
||||
stream_devices_set->stream_devices.emplace_back(std::move(devices));
|
||||
auto& devices_ref = *stream_devices_set->stream_devices[0];
|
||||
blink::mojom::StreamDevicesSetPtr stream_devices_set =
|
||||
blink::mojom::StreamDevicesSet::New();
|
||||
stream_devices_set->stream_devices.emplace_back(
|
||||
blink::mojom::StreamDevices::New());
|
||||
blink::mojom::StreamDevices& devices = *stream_devices_set->stream_devices[0];
|
||||
|
||||
if (request.audio_type == MediaStreamType::GUM_TAB_AUDIO_CAPTURE ||
|
||||
request.audio_type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE) {
|
||||
devices_ref.audio_device = blink::MediaStreamDevice(
|
||||
request.audio_type,
|
||||
request.audio_type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE
|
||||
? "loopback"
|
||||
: "",
|
||||
request.audio_type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE
|
||||
? "System Audio"
|
||||
: "");
|
||||
if (request.audio_type ==
|
||||
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
|
||||
devices.audio_device = blink::MediaStreamDevice(
|
||||
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE, "", "");
|
||||
}
|
||||
if (request.video_type ==
|
||||
blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE) {
|
||||
devices.video_device = blink::MediaStreamDevice(
|
||||
blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE, "", "");
|
||||
}
|
||||
if (request.audio_type ==
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE) {
|
||||
devices.audio_device = blink::MediaStreamDevice(
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, "loopback",
|
||||
"System Audio");
|
||||
}
|
||||
if (request.video_type ==
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
|
||||
devices.video_device = blink::MediaStreamDevice(
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
|
||||
GetScreenId(request.requested_video_device_ids).ToString(), "Screen");
|
||||
}
|
||||
|
||||
if (request.video_type == MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
|
||||
request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
|
||||
devices_ref.video_device = blink::MediaStreamDevice(
|
||||
request.video_type,
|
||||
request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE
|
||||
? GetScreenId(request.requested_video_device_ids).ToString()
|
||||
: "",
|
||||
request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE
|
||||
? "Screen"
|
||||
: "");
|
||||
}
|
||||
|
||||
bool empty = !devices_ref.audio_device.has_value() &&
|
||||
!devices_ref.video_device.has_value();
|
||||
std::move(callback).Run(*stream_devices_set,
|
||||
empty ? MediaStreamRequestResult::NO_HARDWARE
|
||||
: MediaStreamRequestResult::OK,
|
||||
nullptr);
|
||||
bool empty =
|
||||
!devices.audio_device.has_value() && !devices.video_device.has_value();
|
||||
std::move(callback).Run(
|
||||
*stream_devices_set,
|
||||
empty ? blink::mojom::MediaStreamRequestResult::NO_HARDWARE
|
||||
: blink::mojom::MediaStreamRequestResult::OK,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void OnMediaStreamRequestResponse(
|
||||
content::MediaResponseCallback callback,
|
||||
const blink::mojom::StreamDevicesSet& stream_devices_set,
|
||||
MediaStreamRequestResult result,
|
||||
blink::mojom::MediaStreamRequestResult result,
|
||||
bool blocked_by_permissions_policy,
|
||||
ContentSetting audio_setting,
|
||||
ContentSetting video_setting) {
|
||||
@@ -132,34 +105,31 @@ void MediaAccessAllowed(const content::MediaStreamRequest& request,
|
||||
content::MediaResponseCallback callback,
|
||||
bool allowed) {
|
||||
if (allowed) {
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
// If the request was approved, ask for system permissions if needed.
|
||||
// See
|
||||
// chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc.
|
||||
if (SystemMediaPermissionDenied(request)) {
|
||||
std::move(callback).Run(blink::mojom::StreamDevicesSet(),
|
||||
MediaStreamRequestResult::PERMISSION_DENIED,
|
||||
nullptr);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
|
||||
request.audio_type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE ||
|
||||
request.video_type == MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
|
||||
request.audio_type == MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
|
||||
if (request.video_type ==
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
|
||||
request.audio_type ==
|
||||
blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE ||
|
||||
request.video_type ==
|
||||
blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
|
||||
request.audio_type ==
|
||||
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
|
||||
HandleUserMediaRequest(request, std::move(callback));
|
||||
} else if (request.video_type == MediaStreamType::DEVICE_VIDEO_CAPTURE ||
|
||||
request.audio_type == MediaStreamType::DEVICE_AUDIO_CAPTURE) {
|
||||
} else if (request.video_type ==
|
||||
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
|
||||
request.audio_type ==
|
||||
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
|
||||
webrtc::MediaStreamDevicesController::RequestPermissions(
|
||||
request, MediaCaptureDevicesDispatcher::GetInstance(),
|
||||
base::BindOnce(&OnMediaStreamRequestResponse, std::move(callback)),
|
||||
allowed);
|
||||
} else if (request.video_type == MediaStreamType::DISPLAY_VIDEO_CAPTURE ||
|
||||
} else if (request.video_type ==
|
||||
blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE ||
|
||||
request.video_type == blink::mojom::MediaStreamType::
|
||||
DISPLAY_VIDEO_CAPTURE_THIS_TAB ||
|
||||
request.video_type ==
|
||||
MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB ||
|
||||
request.video_type ==
|
||||
MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET ||
|
||||
request.audio_type == MediaStreamType::DISPLAY_AUDIO_CAPTURE) {
|
||||
blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET ||
|
||||
request.audio_type ==
|
||||
blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE) {
|
||||
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
|
||||
request.render_process_id, request.render_frame_id);
|
||||
if (!rfh)
|
||||
@@ -174,15 +144,16 @@ void MediaAccessAllowed(const content::MediaStreamRequest& request,
|
||||
return;
|
||||
std::move(split_callback.first)
|
||||
.Run(blink::mojom::StreamDevicesSet(),
|
||||
MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
|
||||
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
|
||||
} else {
|
||||
std::move(callback).Run(blink::mojom::StreamDevicesSet(),
|
||||
MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
|
||||
std::move(callback).Run(
|
||||
blink::mojom::StreamDevicesSet(),
|
||||
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
|
||||
}
|
||||
} else {
|
||||
std::move(callback).Run(blink::mojom::StreamDevicesSet(),
|
||||
MediaStreamRequestResult::PERMISSION_DENIED,
|
||||
nullptr);
|
||||
std::move(callback).Run(
|
||||
blink::mojom::StreamDevicesSet(),
|
||||
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
spec/.gitignore
vendored
3
spec/.gitignore
vendored
@@ -1,2 +1 @@
|
||||
node_modules
|
||||
artifacts
|
||||
node_modules
|
||||
@@ -1559,6 +1559,19 @@ describe('app module', () => {
|
||||
});
|
||||
|
||||
ifdescribe(!(process.platform === 'linux' && (process.arch === 'arm64' || process.arch === 'arm')))('sandbox options', () => {
|
||||
// Our ARM tests are run on VSTS rather than CircleCI, and the Docker
|
||||
// setup on VSTS disallows syscalls that Chrome requires for setting up
|
||||
// sandboxing.
|
||||
// See:
|
||||
// - https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile
|
||||
// - https://chromium.googlesource.com/chromium/src/+/70.0.3538.124/sandbox/linux/services/credentials.cc#292
|
||||
// - https://github.com/docker/docker-ce/blob/ba7dfc59ccfe97c79ee0d1379894b35417b40bca/components/engine/profiles/seccomp/seccomp_default.go#L497
|
||||
// - https://blog.jessfraz.com/post/how-to-use-new-docker-seccomp-profiles/
|
||||
//
|
||||
// Adding `--cap-add SYS_ADMIN` or `--security-opt seccomp=unconfined`
|
||||
// to the Docker invocation allows the syscalls that Chrome needs, but
|
||||
// are probably more permissive than we'd like.
|
||||
|
||||
let appProcess: cp.ChildProcess = null as any;
|
||||
let server: net.Server = null as any;
|
||||
const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-mixed-sandbox' : '/tmp/electron-mixed-sandbox';
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from 'node:path';
|
||||
import { BrowserView, BrowserWindow, screen, webContents } from 'electron/main';
|
||||
import { closeWindow } from './lib/window-helpers';
|
||||
import { defer, ifit, startRemoteControlApp } from './lib/spec-helpers';
|
||||
import { ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
|
||||
import { ScreenCapture } from './lib/screen-helpers';
|
||||
import { once } from 'node:events';
|
||||
|
||||
describe('BrowserView module', () => {
|
||||
@@ -75,7 +75,8 @@ describe('BrowserView module', () => {
|
||||
}).not.to.throw();
|
||||
});
|
||||
|
||||
ifit(hasCapturableScreen())('sets the background color to transparent if none is set', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('sets the background color to transparent if none is set', async () => {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
|
||||
|
||||
@@ -89,11 +90,12 @@ describe('BrowserView module', () => {
|
||||
w.setBrowserView(view);
|
||||
await view.webContents.loadURL('data:text/html,hello there');
|
||||
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
||||
});
|
||||
|
||||
ifit(hasCapturableScreen())('successfully applies the background color', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('successfully applies the background color', async () => {
|
||||
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
|
||||
const VIEW_BACKGROUND_COLOR = '#ff00ff';
|
||||
const display = screen.getPrimaryDisplay();
|
||||
@@ -109,7 +111,7 @@ describe('BrowserView module', () => {
|
||||
w.setBackgroundColor(VIEW_BACKGROUND_COLOR);
|
||||
await view.webContents.loadURL('data:text/html,hello there');
|
||||
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
await screenCapture.expectColorAtCenterMatches(VIEW_BACKGROUND_COLOR);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1660,7 +1660,6 @@ describe('BrowserWindow module', () => {
|
||||
w = new BrowserWindow({});
|
||||
expect(w.getBackgroundColor()).to.equal('#FFFFFF');
|
||||
});
|
||||
|
||||
it('returns correct value if backgroundColor is set', () => {
|
||||
const backgroundColor = '#BBAAFF';
|
||||
w.destroy();
|
||||
@@ -1669,7 +1668,6 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
expect(w.getBackgroundColor()).to.equal(backgroundColor);
|
||||
});
|
||||
|
||||
it('returns correct value from setBackgroundColor()', () => {
|
||||
const backgroundColor = '#AABBFF';
|
||||
w.destroy();
|
||||
@@ -1677,42 +1675,24 @@ describe('BrowserWindow module', () => {
|
||||
w.setBackgroundColor(backgroundColor);
|
||||
expect(w.getBackgroundColor()).to.equal(backgroundColor);
|
||||
});
|
||||
|
||||
it('returns correct color with multiple passed formats', async () => {
|
||||
it('returns correct color with multiple passed formats', () => {
|
||||
w.destroy();
|
||||
w = new BrowserWindow({});
|
||||
|
||||
await w.loadURL('about:blank');
|
||||
w.setBackgroundColor('#AABBFF');
|
||||
expect(w.getBackgroundColor()).to.equal('#AABBFF');
|
||||
|
||||
const colors = new Map([
|
||||
['blueviolet', '#8A2BE2'],
|
||||
['rgb(255, 0, 185)', '#FF00B9'],
|
||||
['hsl(155, 100%, 50%)', '#00FF95'],
|
||||
['#355E3B', '#355E3B']
|
||||
]);
|
||||
w.setBackgroundColor('blueviolet');
|
||||
expect(w.getBackgroundColor()).to.equal('#8A2BE2');
|
||||
|
||||
for (const [color, hex] of colors) {
|
||||
w.setBackgroundColor(color);
|
||||
expect(w.getBackgroundColor()).to.equal(hex);
|
||||
}
|
||||
});
|
||||
w.setBackgroundColor('rgb(255, 0, 185)');
|
||||
expect(w.getBackgroundColor()).to.equal('#FF00B9');
|
||||
|
||||
it('can set the background color with transparency', async () => {
|
||||
w.destroy();
|
||||
w = new BrowserWindow({});
|
||||
w.setBackgroundColor('rgba(245, 40, 145, 0.8)');
|
||||
expect(w.getBackgroundColor()).to.equal('#F52891');
|
||||
|
||||
await w.loadURL('about:blank');
|
||||
|
||||
const colors = new Map([
|
||||
['hsl(155, 100%, 50%)', '#00FF95'],
|
||||
['rgba(245, 40, 145, 0.8)', '#F52891'],
|
||||
['#1D1F21d9', '#1F21D9']
|
||||
]);
|
||||
|
||||
for (const [color, hex] of colors) {
|
||||
w.setBackgroundColor(color);
|
||||
expect(w.getBackgroundColor()).to.equal(hex);
|
||||
}
|
||||
w.setBackgroundColor('hsl(155, 100%, 50%)');
|
||||
expect(w.getBackgroundColor()).to.equal('#00FF95');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6510,8 +6490,8 @@ describe('BrowserWindow module', () => {
|
||||
expect(w.getBounds()).to.deep.equal(newBounds);
|
||||
});
|
||||
|
||||
// FIXME(codebytere): figure out why these are failing on MAS arm64.
|
||||
ifit(hasCapturableScreen() && !(process.mas && process.arch === 'arm64'))('should not display a visible background', async () => {
|
||||
// FIXME(codebytere): figure out why these are failing on macOS arm64.
|
||||
ifit(process.platform === 'darwin' && process.arch !== 'arm64')('should not display a visible background', async () => {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
|
||||
const backgroundWindow = new BrowserWindow({
|
||||
@@ -6534,7 +6514,9 @@ describe('BrowserWindow module', () => {
|
||||
const colorFile = path.join(__dirname, 'fixtures', 'pages', 'half-background-color.html');
|
||||
await foregroundWindow.loadFile(colorFile);
|
||||
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await setTimeout(1000);
|
||||
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
await screenCapture.expectColorAtPointOnDisplayMatches(
|
||||
HexColors.GREEN,
|
||||
(size) => ({
|
||||
@@ -6551,8 +6533,8 @@ describe('BrowserWindow module', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// FIXME(codebytere): figure out why these are failing on MAS arm64.
|
||||
ifit(hasCapturableScreen() && !(process.mas && process.arch === 'arm64'))('Allows setting a transparent window via CSS', async () => {
|
||||
// FIXME(codebytere): figure out why these are failing on macOS arm64.
|
||||
ifit(process.platform === 'darwin' && process.arch !== 'arm64')('Allows setting a transparent window via CSS', async () => {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
|
||||
const backgroundWindow = new BrowserWindow({
|
||||
@@ -6578,11 +6560,14 @@ describe('BrowserWindow module', () => {
|
||||
foregroundWindow.loadFile(path.join(__dirname, 'fixtures', 'pages', 'css-transparent.html'));
|
||||
await once(ipcMain, 'set-transparent');
|
||||
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await setTimeout(1000);
|
||||
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
await screenCapture.expectColorAtCenterMatches(HexColors.PURPLE);
|
||||
});
|
||||
|
||||
ifit(hasCapturableScreen())('should not make background transparent if falsy', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('should not make background transparent if falsy', async () => {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
|
||||
for (const transparent of [false, undefined]) {
|
||||
@@ -6594,7 +6579,8 @@ describe('BrowserWindow module', () => {
|
||||
await once(window, 'show');
|
||||
await window.webContents.loadURL('data:text/html,<head><meta name="color-scheme" content="dark"></head>');
|
||||
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await setTimeout(1000);
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
// color-scheme is set to dark so background should not be white
|
||||
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
||||
|
||||
@@ -6606,7 +6592,8 @@ describe('BrowserWindow module', () => {
|
||||
describe('"backgroundColor" option', () => {
|
||||
afterEach(closeAllWindows);
|
||||
|
||||
ifit(hasCapturableScreen())('should display the set color', async () => {
|
||||
// Linux/WOA doesn't return any capture sources.
|
||||
ifit(process.platform === 'darwin')('should display the set color', async () => {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
|
||||
const w = new BrowserWindow({
|
||||
@@ -6618,7 +6605,9 @@ describe('BrowserWindow module', () => {
|
||||
w.loadURL('about:blank');
|
||||
await once(w, 'ready-to-show');
|
||||
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await setTimeout(1000);
|
||||
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
await screenCapture.expectColorAtCenterMatches(HexColors.BLUE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,10 @@ describe('setDisplayMediaRequestHandler', () => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
// FIXME(nornagon): this test fails on our macOS CircleCI runners with the
|
||||
// error message:
|
||||
// [ERROR:video_capture_device_client.cc(659)] error@ OnStart@content/browser/media/capture/desktop_capture_device_mac.cc:98, CGDisplayStreamCreate failed, OS message: Value too large to be stored in data type (84)
|
||||
// This is possibly related to the OS/VM setup that CircleCI uses for macOS.
|
||||
ifit(process.platform !== 'darwin')('works when calling getDisplayMedia', async function () {
|
||||
if ((await desktopCapturer.getSources({ types: ['screen'] })).length === 0) {
|
||||
return this.skip();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai';
|
||||
import { Display, screen, desktopCapturer } from 'electron/main';
|
||||
import { Display, screen } from 'electron/main';
|
||||
|
||||
describe('screen module', () => {
|
||||
describe('methods reassignment', () => {
|
||||
@@ -14,26 +14,6 @@ describe('screen module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('screen.getAllDisplays', () => {
|
||||
it('returns an array of displays', () => {
|
||||
const displays = screen.getAllDisplays();
|
||||
expect(displays).to.be.an('array').with.lengthOf.at.least(1);
|
||||
for (const display of displays) {
|
||||
expect(display).to.be.an('object');
|
||||
}
|
||||
});
|
||||
|
||||
it('returns displays with IDs matching desktopCapturer source display IDs', async () => {
|
||||
const displayIds = screen.getAllDisplays().map(d => `${d.id}`);
|
||||
|
||||
const sources = await desktopCapturer.getSources({ types: ['screen'] });
|
||||
const sourceIds = sources.map(s => s.display_id);
|
||||
|
||||
expect(displayIds).to.have.length(sources.length);
|
||||
expect(displayIds).to.have.same.members(sourceIds);
|
||||
});
|
||||
});
|
||||
|
||||
describe('screen.getCursorScreenPoint()', () => {
|
||||
it('returns a point object', () => {
|
||||
const point = screen.getCursorScreenPoint();
|
||||
|
||||
@@ -889,21 +889,13 @@ describe('session module', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const today = Math.floor(Date.now() / 1000);
|
||||
const item = await downloadDone;
|
||||
expect(item.getState()).to.equal('completed');
|
||||
expect(item.getFilename()).to.equal('mock.pdf');
|
||||
expect(item.getMimeType()).to.equal('application/pdf');
|
||||
expect(item.getReceivedBytes()).to.equal(mockPDF.length);
|
||||
expect(item.getTotalBytes()).to.equal(mockPDF.length);
|
||||
expect(item.getPercentComplete()).to.equal(100);
|
||||
expect(item.getCurrentBytesPerSecond()).to.equal(0);
|
||||
expect(item.getContentDisposition()).to.equal(contentDisposition);
|
||||
|
||||
const start = item.getStartTime();
|
||||
const end = item.getEndTime();
|
||||
expect(start).to.be.greaterThan(today);
|
||||
expect(end).to.be.greaterThan(start);
|
||||
});
|
||||
|
||||
it('throws when called with invalid headers', () => {
|
||||
|
||||
@@ -54,15 +54,4 @@ describe('View', () => {
|
||||
w.contentView.addChildView(v2);
|
||||
expect(w.contentView.children).to.deep.equal([v3, v1, v2]);
|
||||
});
|
||||
|
||||
it('allows setting various border radius values', () => {
|
||||
w = new BaseWindow({ show: false });
|
||||
const v = new View();
|
||||
w.setContentView(v);
|
||||
v.setBorderRadius(10);
|
||||
v.setBorderRadius(0);
|
||||
v.setBorderRadius(-10);
|
||||
v.setBorderRadius(9999999);
|
||||
v.setBorderRadius(-9999999);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { expect } from 'chai';
|
||||
import { BaseWindow, BrowserWindow, View, WebContentsView, webContents, screen } from 'electron/main';
|
||||
import { once } from 'node:events';
|
||||
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { defer, ifdescribe } from './lib/spec-helpers';
|
||||
import { HexColors, ScreenCapture, hasCapturableScreen, nextFrameTime } from './lib/screen-helpers';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { BaseWindow, View, WebContentsView, webContents } from 'electron/main';
|
||||
import { once } from 'node:events';
|
||||
import { defer } from './lib/spec-helpers';
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
describe('WebContentsView', () => {
|
||||
afterEach(closeAllWindows);
|
||||
@@ -224,92 +224,4 @@ describe('WebContentsView', () => {
|
||||
expect(visibilityState).to.equal('visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setBorderRadius', () => {
|
||||
ifdescribe(hasCapturableScreen())('capture', () => {
|
||||
let w: Electron.BaseWindow;
|
||||
let v: Electron.WebContentsView;
|
||||
let display: Electron.Display;
|
||||
let corners: Electron.Point[];
|
||||
|
||||
const backgroundUrl = `data:text/html,<style>html{background:${encodeURIComponent(HexColors.GREEN)}}</style>`;
|
||||
|
||||
beforeEach(async () => {
|
||||
display = screen.getPrimaryDisplay();
|
||||
|
||||
w = new BaseWindow({
|
||||
...display.workArea,
|
||||
show: true,
|
||||
frame: false,
|
||||
hasShadow: false,
|
||||
backgroundColor: HexColors.BLUE,
|
||||
roundedCorners: false
|
||||
});
|
||||
|
||||
v = new WebContentsView();
|
||||
w.setContentView(v);
|
||||
v.setBorderRadius(100);
|
||||
|
||||
const readyForCapture = once(v.webContents, 'ready-to-show');
|
||||
v.webContents.loadURL(backgroundUrl);
|
||||
|
||||
const inset = 10;
|
||||
corners = [
|
||||
{ x: display.workArea.x + inset, y: display.workArea.y + inset }, // top-left
|
||||
{ x: display.workArea.x + display.workArea.width - inset, y: display.workArea.y + inset }, // top-right
|
||||
{ x: display.workArea.x + display.workArea.width - inset, y: display.workArea.y + display.workArea.height - inset }, // bottom-right
|
||||
{ x: display.workArea.x + inset, y: display.workArea.y + display.workArea.height - inset } // bottom-left
|
||||
];
|
||||
|
||||
await readyForCapture;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
w.destroy();
|
||||
w = v = null!;
|
||||
});
|
||||
|
||||
it('should render with cutout corners', async () => {
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
|
||||
for (const corner of corners) {
|
||||
await screenCapture.expectColorAtPointOnDisplayMatches(HexColors.BLUE, () => corner);
|
||||
}
|
||||
|
||||
// Center should be WebContents page background color
|
||||
await screenCapture.expectColorAtCenterMatches(HexColors.GREEN);
|
||||
});
|
||||
|
||||
it('should allow resetting corners', async () => {
|
||||
const corner = corners[0];
|
||||
v.setBorderRadius(0);
|
||||
|
||||
await nextFrameTime();
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await screenCapture.expectColorAtPointOnDisplayMatches(HexColors.GREEN, () => corner);
|
||||
await screenCapture.expectColorAtCenterMatches(HexColors.GREEN);
|
||||
});
|
||||
|
||||
it('should render when set before attached', async () => {
|
||||
v = new WebContentsView();
|
||||
v.setBorderRadius(100); // must set before
|
||||
|
||||
w.setContentView(v);
|
||||
|
||||
const readyForCapture = once(v.webContents, 'ready-to-show');
|
||||
v.webContents.loadURL(backgroundUrl);
|
||||
await readyForCapture;
|
||||
|
||||
const corner = corners[0];
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await screenCapture.expectColorAtPointOnDisplayMatches(HexColors.BLUE, () => corner);
|
||||
await screenCapture.expectColorAtCenterMatches(HexColors.GREEN);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow setting when not attached', async () => {
|
||||
const v = new WebContentsView();
|
||||
v.setBorderRadius(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { BrowserWindow, screen } from 'electron';
|
||||
import { expect, assert } from 'chai';
|
||||
import { HexColors, ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
|
||||
import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
||||
import { ifit, listen } from './lib/spec-helpers';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { once } from 'node:events';
|
||||
import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
|
||||
import * as http from 'node:http';
|
||||
|
||||
describe('webContents.setWindowOpenHandler', () => {
|
||||
@@ -200,7 +201,8 @@ describe('webContents.setWindowOpenHandler', () => {
|
||||
expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
|
||||
});
|
||||
|
||||
ifit(hasCapturableScreen())('should not make child window background transparent', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('should not make child window background transparent', async () => {
|
||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
|
||||
const didCreateWindow = once(browserWindow.webContents, 'did-create-window');
|
||||
browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
|
||||
@@ -208,7 +210,8 @@ describe('webContents.setWindowOpenHandler', () => {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
childWindow.setBounds(display.bounds);
|
||||
await childWindow.webContents.executeJavaScript("const meta = document.createElement('meta'); meta.name = 'color-scheme'; meta.content = 'dark'; document.head.appendChild(meta); true;");
|
||||
const screenCapture = new ScreenCapture(display);
|
||||
await setTimeoutAsync(1000);
|
||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||
// color-scheme is set to dark so background should not be white
|
||||
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
||||
});
|
||||
|
||||
@@ -18,6 +18,11 @@ export function getCodesignIdentity () {
|
||||
if (identity === undefined) {
|
||||
const result = cp.spawnSync(path.resolve(__dirname, '../../script/codesign/get-trusted-identity.sh'));
|
||||
if (result.status !== 0 || result.stdout.toString().trim().length === 0) {
|
||||
// Per https://circleci.com/docs/2.0/env-vars:
|
||||
// CIRCLE_PR_NUMBER is only present on forked PRs
|
||||
if (process.env.CI && !process.env.CIRCLE_PR_NUMBER) {
|
||||
throw new Error('No valid signing identity available to run autoUpdater specs');
|
||||
}
|
||||
identity = null;
|
||||
} else {
|
||||
identity = result.stdout.toString().trim();
|
||||
|
||||
@@ -75,72 +75,67 @@ function areColorsSimilar (
|
||||
return distance <= distanceThreshold;
|
||||
}
|
||||
|
||||
function displayCenter (display: Electron.Display): Electron.Point {
|
||||
function imageCenter (image: NativeImage): Electron.Point {
|
||||
const size = image.getSize();
|
||||
return {
|
||||
x: display.size.width / 2,
|
||||
y: display.size.height / 2
|
||||
x: size.width / 2,
|
||||
y: size.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
/** Resolve when approx. one frame has passed (30FPS) */
|
||||
export async function nextFrameTime (): Promise<void> {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000 / 30);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities for creating and inspecting a screen capture.
|
||||
*
|
||||
* Set `PAUSE_CAPTURE_TESTS` env var to briefly pause during screen
|
||||
* capture for easier inspection.
|
||||
*
|
||||
* NOTE: Not yet supported on Linux in CI due to empty sources list.
|
||||
*/
|
||||
export class ScreenCapture {
|
||||
/** Timeout to wait for expected color to match. */
|
||||
static TIMEOUT = 3000;
|
||||
/** Use the async constructor `ScreenCapture.create()` instead. */
|
||||
private constructor (image: NativeImage) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
constructor (display?: Electron.Display) {
|
||||
this.display = display || screen.getPrimaryDisplay();
|
||||
public static async create (): Promise<ScreenCapture> {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
return ScreenCapture._createImpl(display);
|
||||
}
|
||||
|
||||
public static async createForDisplay (
|
||||
display: Electron.Display
|
||||
): Promise<ScreenCapture> {
|
||||
return ScreenCapture._createImpl(display);
|
||||
}
|
||||
|
||||
public async expectColorAtCenterMatches (hexColor: string) {
|
||||
return this._expectImpl(displayCenter(this.display), hexColor, true);
|
||||
return this._expectImpl(imageCenter(this.image), hexColor, true);
|
||||
}
|
||||
|
||||
public async expectColorAtCenterDoesNotMatch (hexColor: string) {
|
||||
return this._expectImpl(displayCenter(this.display), hexColor, false);
|
||||
return this._expectImpl(imageCenter(this.image), hexColor, false);
|
||||
}
|
||||
|
||||
public async expectColorAtPointOnDisplayMatches (
|
||||
hexColor: string,
|
||||
findPoint: (displaySize: Electron.Size) => Electron.Point
|
||||
) {
|
||||
return this._expectImpl(findPoint(this.display.size), hexColor, true);
|
||||
return this._expectImpl(findPoint(this.image.getSize()), hexColor, true);
|
||||
}
|
||||
|
||||
private async captureFrame (): Promise<NativeImage> {
|
||||
private static async _createImpl (display: Electron.Display) {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen'],
|
||||
thumbnailSize: this.display.size
|
||||
thumbnailSize: display.size
|
||||
});
|
||||
|
||||
const captureSource = sources.find(
|
||||
(source) => source.display_id === this.display.id.toString()
|
||||
(source) => source.display_id === display.id.toString()
|
||||
);
|
||||
if (captureSource === undefined) {
|
||||
const displayIds = sources.map((source) => source.display_id).join(', ');
|
||||
throw new Error(
|
||||
`Unable to find screen capture for display '${this.display.id}'\n\tAvailable displays: ${displayIds}`
|
||||
`Unable to find screen capture for display '${display.id}'\n\tAvailable displays: ${displayIds}`
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.PAUSE_CAPTURE_TESTS) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
||||
}
|
||||
|
||||
return captureSource.thumbnail;
|
||||
return new ScreenCapture(captureSource.thumbnail);
|
||||
}
|
||||
|
||||
private async _expectImpl (
|
||||
@@ -148,37 +143,16 @@ export class ScreenCapture {
|
||||
expectedColor: string,
|
||||
matchIsExpected: boolean
|
||||
) {
|
||||
let frame: Electron.NativeImage;
|
||||
let actualColor: string;
|
||||
let gotExpectedResult: boolean = false;
|
||||
const expiration = Date.now() + ScreenCapture.TIMEOUT;
|
||||
|
||||
// Continuously capture frames until we either see the expected result or
|
||||
// reach a timeout. This helps avoid flaky tests in which a short waiting
|
||||
// period is required for the expected result.
|
||||
do {
|
||||
frame = await this.captureFrame();
|
||||
actualColor = getPixelColor(frame, point);
|
||||
const colorsMatch = areColorsSimilar(expectedColor, actualColor);
|
||||
gotExpectedResult = matchIsExpected ? colorsMatch : !colorsMatch;
|
||||
if (gotExpectedResult) break;
|
||||
|
||||
await nextFrameTime(); // limit framerate
|
||||
} while (Date.now() < expiration);
|
||||
const actualColor = getPixelColor(this.image, point);
|
||||
const colorsMatch = areColorsSimilar(expectedColor, actualColor);
|
||||
const gotExpectedResult = matchIsExpected ? colorsMatch : !colorsMatch;
|
||||
|
||||
if (!gotExpectedResult) {
|
||||
// Limit image to 720p to save on storage space
|
||||
if (process.env.CI) {
|
||||
const width = Math.floor(Math.min(frame.getSize().width, 720));
|
||||
frame = frame.resize({ width });
|
||||
}
|
||||
|
||||
// Save the image as an artifact for better debugging
|
||||
const artifactName = await createArtifactWithRandomId(
|
||||
(id) => `color-mismatch-${id}.png`,
|
||||
frame.toPNG()
|
||||
this.image.toPNG()
|
||||
);
|
||||
|
||||
throw new AssertionError(
|
||||
`Expected color at (${point.x}, ${point.y}) to ${
|
||||
matchIsExpected ? 'match' : '*not* match'
|
||||
@@ -187,7 +161,7 @@ export class ScreenCapture {
|
||||
}
|
||||
}
|
||||
|
||||
private display: Electron.Display;
|
||||
private image: NativeImage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,5 +174,5 @@ export class ScreenCapture {
|
||||
* - Win32 x64: virtual screen display is 0x0
|
||||
*/
|
||||
export const hasCapturableScreen = () => {
|
||||
return process.env.CI ? process.platform === 'darwin' : true;
|
||||
return process.platform === 'darwin';
|
||||
};
|
||||
|
||||
@@ -90,6 +90,8 @@ describe('version-bumper', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// On macOS Circle CI we don't have a real git environment due to running
|
||||
// gclient sync on a linux machine. These tests therefore don't run as expected.
|
||||
ifdescribe(!(process.platform === 'linux' && process.arch.indexOf('arm') === 0) && process.platform !== 'darwin')('nextVersion', () => {
|
||||
describe('bump versions', () => {
|
||||
const nightlyPattern = /[0-9.]*(-nightly.(\d{4})(\d{2})(\d{2}))$/g;
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as http from 'node:http';
|
||||
import * as auth from 'basic-auth';
|
||||
import { once } from 'node:events';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { HexColors, ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
|
||||
import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
||||
|
||||
declare let WebView: any;
|
||||
const features = process._linkedBinding('electron_common_features');
|
||||
@@ -796,32 +796,41 @@ describe('<webview> tag', function () {
|
||||
});
|
||||
after(() => w.close());
|
||||
|
||||
ifit(hasCapturableScreen())('is transparent by default', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('is transparent by default', async () => {
|
||||
await loadWebView(w.webContents, {
|
||||
src: 'data:text/html,foo'
|
||||
});
|
||||
|
||||
const screenCapture = new ScreenCapture();
|
||||
await setTimeout(1000);
|
||||
|
||||
const screenCapture = await ScreenCapture.create();
|
||||
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
||||
});
|
||||
|
||||
ifit(hasCapturableScreen())('remains transparent when set', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('remains transparent when set', async () => {
|
||||
await loadWebView(w.webContents, {
|
||||
src: 'data:text/html,foo',
|
||||
webpreferences: 'transparent=yes'
|
||||
});
|
||||
|
||||
const screenCapture = new ScreenCapture();
|
||||
await setTimeout(1000);
|
||||
|
||||
const screenCapture = await ScreenCapture.create();
|
||||
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
||||
});
|
||||
|
||||
ifit(hasCapturableScreen())('can disable transparency', async () => {
|
||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('can disable transparency', async () => {
|
||||
await loadWebView(w.webContents, {
|
||||
src: 'data:text/html,foo',
|
||||
webpreferences: 'transparent=no'
|
||||
});
|
||||
|
||||
const screenCapture = new ScreenCapture();
|
||||
await setTimeout(1000);
|
||||
|
||||
const screenCapture = await ScreenCapture.create();
|
||||
await screenCapture.expectColorAtCenterMatches(HexColors.WHITE);
|
||||
});
|
||||
});
|
||||
|
||||
2
typings/internal-electron.d.ts
vendored
2
typings/internal-electron.d.ts
vendored
@@ -17,8 +17,6 @@ declare namespace Electron {
|
||||
setVersion(version: string): void;
|
||||
setDesktopName(name: string): void;
|
||||
setAppPath(path: string | null): void;
|
||||
_clientCertRequestPasswordHandler: ((params: ClientCertRequestParams) => Promise<string>) | null;
|
||||
on(event: '-client-certificate-request-password', listener: (event: Event<ClientCertRequestParams>, callback: (password: string) => void) => Promise<void>): this;
|
||||
}
|
||||
|
||||
interface AutoUpdater {
|
||||
|
||||
Reference in New Issue
Block a user