mirror of
https://github.com/electron/electron.git
synced 2026-03-19 03:02:02 -04:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d32b8a64d0 | ||
|
|
425fe98c14 | ||
|
|
6b4b7df937 | ||
|
|
cc81658f40 | ||
|
|
9be5389e77 | ||
|
|
8264495aff | ||
|
|
ed9ec1a535 | ||
|
|
b8362fe96f | ||
|
|
4480c3545d | ||
|
|
de5d94bc49 | ||
|
|
4fe62718b9 | ||
|
|
1c9e1cd141 | ||
|
|
04e39e24e6 | ||
|
|
e0c8b9b168 | ||
|
|
77f3f5f2b2 | ||
|
|
a349e616d4 | ||
|
|
8c1b38d443 | ||
|
|
06278ba3b3 | ||
|
|
15b95fcd52 | ||
|
|
d574f99c9e | ||
|
|
cbc6959269 | ||
|
|
f4c7a3ff66 | ||
|
|
66ce2439cc |
@@ -209,6 +209,7 @@ jobs:
|
||||
|
||||
- name: Run Electron Tests
|
||||
shell: bash
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
MOCHA_REPORTER: mocha-multi-reporters
|
||||
MOCHA_MULTI_REPORTERS: mocha-junit-reporter, tap
|
||||
@@ -259,6 +260,19 @@ jobs:
|
||||
|
||||
fi
|
||||
fi
|
||||
- name: Take screenshot on timeout or cancellation
|
||||
if: ${{ inputs.target-platform != 'linux' && (cancelled() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
screenshot_dir="src/electron/spec/artifacts"
|
||||
mkdir -p "$screenshot_dir"
|
||||
screenshot_file="$screenshot_dir/screenshot-timeout-$(date +%Y%m%d%H%M%S).png"
|
||||
if [ "${{ inputs.target-platform }}" = "macos" ]; then
|
||||
screencapture -x "$screenshot_file" || true
|
||||
elif [ "${{ inputs.target-platform }}" = "win" ]; then
|
||||
powershell -command "Add-Type -AssemblyName System.Windows.Forms; \$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; \$bitmap = New-Object System.Drawing.Bitmap(\$screen.Width, \$screen.Height); \$graphics = [System.Drawing.Graphics]::FromImage(\$bitmap); \$graphics.CopyFromScreen(\$screen.Location, [System.Drawing.Point]::Empty, \$screen.Size); \$bitmap.Save('$screenshot_file')" || true
|
||||
fi
|
||||
|
||||
- name: Upload Test results to Datadog
|
||||
env:
|
||||
DD_ENV: ci
|
||||
@@ -274,7 +288,7 @@ jobs:
|
||||
fi
|
||||
if: always() && !cancelled()
|
||||
- name: Upload Test Artifacts
|
||||
if: always() && !cancelled()
|
||||
if: always()
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: test_artifacts_${{ env.ARTIFACT_KEY }}_${{ matrix.shard }}
|
||||
|
||||
@@ -255,7 +255,7 @@ async function startRepl () {
|
||||
if (option.file && !option.webdriver) {
|
||||
const file = option.file;
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const protocol = url.parse(file).protocol;
|
||||
const protocol = URL.canParse(file) ? new URL(file).protocol : null;
|
||||
const extension = path.extname(file);
|
||||
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
|
||||
await loadApplicationByURL(file);
|
||||
|
||||
@@ -94,7 +94,7 @@ The `desktopCapturer` module has the following methods:
|
||||
Returns `Promise<DesktopCapturerSource[]>` - Resolves with an array of [`DesktopCapturerSource`](structures/desktop-capturer-source.md) objects, each `DesktopCapturerSource` represents a screen or an individual window that can be captured.
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
<!-- markdownlint-disable-next-line MD032 -->
|
||||
> * Capturing audio requires `NSAudioCaptureUsageDescription` Info.plist key on macOS 14.2 Sonoma and higher - [read more](#macos-versions-142-or-higher).
|
||||
> * Capturing the screen contents requires user consent on macOS 10.15 Catalina or higher, which can detected by [`systemPreferences.getMediaAccessStatus`][].
|
||||
|
||||
@@ -109,30 +109,41 @@ Returns `Promise<DesktopCapturerSource[]>` - Resolves with an array of [`Desktop
|
||||
|
||||
PipeWire supports a single capture for both screens and windows. If you request the window and screen type, the selected source will be returned as a window capture.
|
||||
|
||||
---
|
||||
### macOS versions 14.2 or higher
|
||||
|
||||
### MacOS versions 14.2 or higher
|
||||
|
||||
`NSAudioCaptureUsageDescription` Info.plist key must be added in-order for audio to be captured by `desktopCapturer`. If instead you are running electron from another program like a terminal or IDE then that parent program must contain the Info.plist key.
|
||||
`NSAudioCaptureUsageDescription` Info.plist key must be added in order for audio to be captured by
|
||||
`desktopCapturer`. If instead you are running Electron from another program like a terminal or IDE
|
||||
then that parent program must contain the Info.plist key.
|
||||
|
||||
This is in order to facillitate use of Apple's new [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps#Configure-the-sample-code-project) by Chromium.
|
||||
|
||||
> [!WARNING]
|
||||
> Failure of `desktopCapturer` to start an audio stream due to `NSAudioCaptureUsageDescription` permission not present will still create a dead audio stream however no warnings or errors are displayed.
|
||||
> Failure of `desktopCapturer` to start an audio stream due to `NSAudioCaptureUsageDescription`
|
||||
> permission not present will still create a dead audio stream however no warnings or errors are
|
||||
> displayed.
|
||||
|
||||
As of electron `v39.0.0-beta.4` Chromium [made Apple's new `CoreAudio Tap API` the default](https://source.chromium.org/chromium/chromium/src/+/ad17e8f8b93d5f34891b06085d373a668918255e) for desktop audio capture. There is no fallback to the older `Screen & System Audio Recording` permissions system even if [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps) stream creation fails.
|
||||
As of Electron `v39.0.0-beta.4`, Chromium [made Apple's new `CoreAudio Tap API` the default](https://source.chromium.org/chromium/chromium/src/+/ad17e8f8b93d5f34891b06085d373a668918255e)
|
||||
for desktop audio capture. There is no fallback to the older `Screen & System Audio Recording`
|
||||
permissions system even if [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps) stream creation fails.
|
||||
|
||||
If you need to continue using `Screen & System Audio Recording` permissions for `desktopCapturer` on macOS versions 14.2 and later, you can apply a chromium feature flag to force use of that older permissions system:
|
||||
If you need to continue using `Screen & System Audio Recording` permissions for `desktopCapturer`
|
||||
on macOS versions 14.2 and later, you can apply a Chromium feature flag to force use of that older
|
||||
permissions system:
|
||||
|
||||
```js
|
||||
// main.js (right beneath your require/import statments)
|
||||
app.commandLine.appendSwitch('disable-features', 'MacCatapLoopbackAudioForScreenShare')
|
||||
```
|
||||
|
||||
---
|
||||
### macOS versions 12.7.6 or lower
|
||||
|
||||
### MacOS versions 12.7.6 or lower
|
||||
`navigator.mediaDevices.getUserMedia` does not work on macOS versions 12.7.6 and prior for audio
|
||||
capture due to a fundamental limitation whereby apps that want to access the system's audio require
|
||||
a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html).
|
||||
Chromium, and by extension Electron, does not provide this. Only in macOS 13 and onwards does Apple
|
||||
provide APIs to capture desktop audio without the need for a signed kernel extension.
|
||||
|
||||
`navigator.mediaDevices.getUserMedia` does not work on macOS versions 12.7.6 and prior for audio capture due to a fundamental limitation whereby apps that want to access the system's audio require a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html). Chromium, and by extension Electron, does not provide this. Only in macOS 13 and onwards does Apple provide APIs to capture desktop audio without the need for a signed kernel extension.
|
||||
|
||||
It is possible to circumvent this limitation by capturing system audio with another macOS app like [BlackHole](https://existential.audio/blackhole/) or [Soundflower](https://rogueamoeba.com/freebies/soundflower/) and passing it through a virtual audio input device. This virtual device can then be queried with `navigator.mediaDevices.getUserMedia`.
|
||||
It is possible to circumvent this limitation by capturing system audio with another macOS app like
|
||||
[BlackHole](https://existential.audio/blackhole/) or [Soundflower](https://rogueamoeba.com/freebies/soundflower/)
|
||||
and passing it through a virtual audio input device. This virtual device can then be queried
|
||||
with `navigator.mediaDevices.getUserMedia`.
|
||||
|
||||
@@ -110,6 +110,8 @@ Returns [`Point`](structures/point.md)
|
||||
|
||||
The current absolute position of the mouse pointer.
|
||||
|
||||
Not supported on Wayland (Linux).
|
||||
|
||||
> [!NOTE]
|
||||
> The return value is a DIP point, not a screen physical point.
|
||||
|
||||
|
||||
@@ -7,47 +7,7 @@ check out our [Electron Versioning](./electron-versioning.md) doc.
|
||||
|
||||
## Timeline
|
||||
|
||||
| Electron | Alpha | Beta | Stable | EOL | Chrome | Node | Supported |
|
||||
| ------- | ----- | ------- | ------ | ------ | ---- | ---- | ---- |
|
||||
| 40.0.0 | 2025-Oct-30 | 2025-Dec-03 | 2026-Jan-13 | 2026-Jun-30 | M144 | TBD | ✅ |
|
||||
| 39.0.0 | 2025-Sep-04 | 2025-Oct-01 | 2025-Oct-28 | 2026-May-05 | M142 | v22.20 | ✅ |
|
||||
| 38.0.0 | 2025-Jun-26 | 2025-Aug-06 | 2025-Sep-02 | 2026-Mar-10 | M140 | v22.18 | ✅ |
|
||||
| 37.0.0 | 2025-May-01 | 2025-May-28 | 2025-Jun-24 | 2026-Jan-13 | M138 | v22.16 | ✅ |
|
||||
| 36.0.0 | 2025-Mar-06 | 2025-Apr-02 | 2025-Apr-29 | 2025-Oct-28 | M136 | v22.14 | 🚫 |
|
||||
| 35.0.0 | 2025-Jan-16 | 2025-Feb-05 | 2025-Mar-04 | 2025-Sep-02 | M134 | v22.14 | 🚫 |
|
||||
| 34.0.0 | 2024-Oct-17 | 2024-Nov-13 | 2025-Jan-14 | 2025-Jun-24 | M132 | v20.18 | 🚫 |
|
||||
| 33.0.0 | 2024-Aug-22 | 2024-Sep-18 | 2024-Oct-15 | 2025-Apr-29 | M130 | v20.18 | 🚫 |
|
||||
| 32.0.0 | 2024-Jun-14 | 2024-Jul-24 | 2024-Aug-20 | 2025-Mar-04 | M128 | v20.16 | 🚫 |
|
||||
| 31.0.0 | 2024-Apr-18 | 2024-May-15 | 2024-Jun-11 | 2025-Jan-14 | M126 | v20.14 | 🚫 |
|
||||
| 30.0.0 | 2024-Feb-22 | 2024-Mar-20 | 2024-Apr-16 | 2024-Oct-15 | M124 | v20.11 | 🚫 |
|
||||
| 29.0.0 | 2023-Dec-07 | 2024-Jan-24 | 2024-Feb-20 | 2024-Aug-20 | M122 | v20.9 | 🚫 |
|
||||
| 28.0.0 | 2023-Oct-11 | 2023-Nov-06 | 2023-Dec-05 | 2024-Jun-11 | M120 | v18.18 | 🚫 |
|
||||
| 27.0.0 | 2023-Aug-17 | 2023-Sep-13 | 2023-Oct-10 | 2024-Apr-16 | M118 | v18.17 | 🚫 |
|
||||
| 26.0.0 | 2023-Jun-01 | 2023-Jun-27 | 2023-Aug-15 | 2024-Feb-20 | M116 | v18.16 | 🚫 |
|
||||
| 25.0.0 | 2023-Apr-10 | 2023-May-02 | 2023-May-30 | 2023-Dec-05 | M114 | v18.15 | 🚫 |
|
||||
| 24.0.0 | 2023-Feb-09 | 2023-Mar-07 | 2023-Apr-04 | 2023-Oct-10 | M112 | v18.14 | 🚫 |
|
||||
| 23.0.0 | 2022-Dec-01 | 2023-Jan-10 | 2023-Feb-07 | 2023-Aug-15 | M110 | v18.12 | 🚫 |
|
||||
| 22.0.0 | 2022-Sep-29 | 2022-Oct-25 | 2022-Nov-29 | 2023-Oct-10 | M108 | v16.17 | 🚫 |
|
||||
| 21.0.0 | 2022-Aug-04 | 2022-Aug-30 | 2022-Sep-27 | 2023-Apr-04 | M106 | v16.16 | 🚫 |
|
||||
| 20.0.0 | 2022-May-26 | 2022-Jun-21 | 2022-Aug-02 | 2023-Feb-07 | M104 | v16.15 | 🚫 |
|
||||
| 19.0.0 | 2022-Mar-31 | 2022-Apr-26 | 2022-May-24 | 2022-Nov-29 | M102 | v16.14 | 🚫 |
|
||||
| 18.0.0 | 2022-Feb-03 | 2022-Mar-03 | 2022-Mar-29 | 2022-Sep-27 | M100 | v16.13 | 🚫 |
|
||||
| 17.0.0 | 2021-Nov-18 | 2022-Jan-06 | 2022-Feb-01 | 2022-Aug-02 | M98 | v16.13 | 🚫 |
|
||||
| 16.0.0 | 2021-Sep-23 | 2021-Oct-20 | 2021-Nov-16 | 2022-May-24 | M96 | v16.9 | 🚫 |
|
||||
| 15.0.0 | 2021-Jul-20 | 2021-Sep-01 | 2021-Sep-21 | 2022-May-24 | M94 | v16.5 | 🚫 |
|
||||
| 14.0.0 | -- | 2021-May-27 | 2021-Aug-31 | 2022-Mar-29 | M93 | v14.17 | 🚫 |
|
||||
| 13.0.0 | -- | 2021-Mar-04 | 2021-May-25 | 2022-Feb-01 | M91 | v14.16 | 🚫 |
|
||||
| 12.0.0 | -- | 2020-Nov-19 | 2021-Mar-02 | 2021-Nov-16 | M89 | v14.16 | 🚫 |
|
||||
| 11.0.0 | -- | 2020-Aug-27 | 2020-Nov-17 | 2021-Aug-31 | M87 | v12.18 | 🚫 |
|
||||
| 10.0.0 | -- | 2020-May-21 | 2020-Aug-25 | 2021-May-25 | M85 | v12.16 | 🚫 |
|
||||
| 9.0.0 | -- | 2020-Feb-06 | 2020-May-19 | 2021-Mar-02 | M83 | v12.14 | 🚫 |
|
||||
| 8.0.0 | -- | 2019-Oct-24 | 2020-Feb-04 | 2020-Nov-17 | M80 | v12.13 | 🚫 |
|
||||
| 7.0.0 | -- | 2019-Aug-01 | 2019-Oct-22 | 2020-Aug-25 | M78 | v12.8 | 🚫 |
|
||||
| 6.0.0 | -- | 2019-Apr-25 | 2019-Jul-30 | 2020-May-19 | M76 | v12.14.0 | 🚫 |
|
||||
| 5.0.0 | -- | 2019-Jan-22 | 2019-Apr-23 | 2020-Feb-04 | M73 | v12.0 | 🚫 |
|
||||
| 4.0.0 | -- | 2018-Oct-11 | 2018-Dec-20 | 2019-Oct-22 | M69 | v10.11 | 🚫 |
|
||||
| 3.0.0 | -- | 2018-Jun-21 | 2018-Sep-18 | 2019-Jul-30 | M66 | v10.2 | 🚫 |
|
||||
| 2.0.0 | -- | 2018-Feb-21 | 2018-May-01 | 2019-Apr-23 | M61 | v8.9 | 🚫 |
|
||||
[Electron's Release Schedule](https://releases.electronjs.org/schedule) lists a schedule of Electron major releases showing key milestones including alpha, beta, and stable release dates, as well as end-of-life dates and dependency versions.
|
||||
|
||||
:::info Official support dates may change
|
||||
|
||||
|
||||
@@ -8,13 +8,19 @@ const {
|
||||
isOnBatteryPower
|
||||
} = process._linkedBinding('electron_browser_power_monitor');
|
||||
|
||||
// Hold the native PowerMonitor at module level so it is never garbage-collected
|
||||
// while this module is alive. The C++ side registers OS-level callbacks (HWND
|
||||
// user-data on Windows, shutdown handler on macOS, notification observers) that
|
||||
// prevent safe collection of the C++ wrapper while those registrations exist.
|
||||
let pm: any;
|
||||
|
||||
class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
|
||||
constructor () {
|
||||
super();
|
||||
// Don't start the event source until both a) the app is ready and b)
|
||||
// there's a listener registered for a powerMonitor event.
|
||||
this.once('newListener', () => {
|
||||
const pm = createPowerMonitor();
|
||||
pm = createPowerMonitor();
|
||||
pm.emit = this.emit.bind(this);
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
|
||||
@@ -19,8 +19,8 @@ export function invokeInWebContents<T> (sender: Electron.WebContents, command: s
|
||||
const requestId = ++nextId;
|
||||
const channel = `${command}_RESPONSE_${requestId}`;
|
||||
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
|
||||
if (event.type === 'frame' && event.sender !== sender) {
|
||||
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`);
|
||||
if (event.type !== 'frame' || event.sender !== sender) {
|
||||
console.error(`Reply to ${command} sent by unexpected sender`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ export function invokeInWebFrameMain<T> (sender: Electron.WebFrameMain, command:
|
||||
const channel = `${command}_RESPONSE_${requestId}`;
|
||||
const frameTreeNodeId = sender.frameTreeNodeId;
|
||||
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
|
||||
if (event.type === 'frame' && event.frameTreeNodeId !== frameTreeNodeId) {
|
||||
console.error(`Reply to ${command} sent by unexpected WebFrameMain (${event.frameTreeNodeId})`);
|
||||
if (event.type !== 'frame' || event.frameTreeNodeId !== frameTreeNodeId) {
|
||||
console.error(`Reply to ${command} sent by unexpected sender`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -227,10 +227,9 @@ function validateHeader (name: any, value: any): void {
|
||||
}
|
||||
|
||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
|
||||
const options: any = typeof optionsIn === 'string' ? new URL(optionsIn) : { ...optionsIn };
|
||||
|
||||
let urlStr: string = options.url;
|
||||
let urlStr: string = options.url || options.href;
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj: url.UrlObject = {};
|
||||
@@ -260,8 +259,8 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
|
||||
// an invalid request.
|
||||
throw new TypeError('Request path contains unescaped characters');
|
||||
}
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const pathObj = url.parse(options.path || '/');
|
||||
|
||||
const pathObj = new URL(options.path || '/', 'http://localhost');
|
||||
urlObj.pathname = pathObj.pathname;
|
||||
urlObj.search = pathObj.search;
|
||||
urlObj.hash = pathObj.hash;
|
||||
|
||||
@@ -23,11 +23,14 @@ export default contextBridge;
|
||||
|
||||
export const internalContextBridge = {
|
||||
contextIsolationEnabled: process.contextIsolated,
|
||||
tryOverrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
|
||||
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true, true);
|
||||
},
|
||||
overrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
|
||||
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false);
|
||||
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false, false);
|
||||
},
|
||||
overrideGlobalValueWithDynamicPropsFromIsolatedWorld: (keys: string[], value: any) => {
|
||||
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true);
|
||||
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true, false);
|
||||
},
|
||||
overrideGlobalPropertyFromIsolatedWorld: (keys: string[], getter: Function, setter?: Function) => {
|
||||
return binding._overrideGlobalPropertyFromIsolatedWorld(keys, getter, setter || null);
|
||||
|
||||
@@ -11,14 +11,12 @@ const { contextIsolationEnabled } = internalContextBridge;
|
||||
* 1) Use menu API to show context menu.
|
||||
*/
|
||||
window.onload = function () {
|
||||
if (window.InspectorFrontendHost) {
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
|
||||
'InspectorFrontendHost', 'showContextMenuAtPoint'
|
||||
], createMenu);
|
||||
} else {
|
||||
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu;
|
||||
}
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld([
|
||||
'InspectorFrontendHost', 'showContextMenuAtPoint'
|
||||
], createMenu);
|
||||
} else {
|
||||
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
cherry-pick-a08731cf6d70.patch
|
||||
|
||||
240
patches/angle/cherry-pick-a08731cf6d70.patch
Normal file
240
patches/angle/cherry-pick-a08731cf6d70.patch
Normal file
@@ -0,0 +1,240 @@
|
||||
From a08731cf6d70c60fd74b1d75f2e8b94c52e18140 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Thu, 19 Feb 2026 14:42:08 -0500
|
||||
Subject: [PATCH] Vulkan: Avoid overflow in texture size calculation
|
||||
|
||||
Bug: chromium:485622239
|
||||
Change-Id: Idf9847afa0aa2e72b6433ac8348ae2820c1ad8c5
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7595734
|
||||
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
|
||||
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
---
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
index 9e208f9..e2185a4 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
@@ -3164,8 +3164,17 @@
|
||||
// invalidate must be called after wait for finish.
|
||||
ANGLE_TRY(srcBuffer->invalidate(renderer));
|
||||
|
||||
- size_t dstBufferSize = sourceBox.width * sourceBox.height * sourceBox.depth *
|
||||
- dstFormat.pixelBytes * layerCount;
|
||||
+ // Use size_t calculations to avoid 32-bit overflows. Note that the dimensions are bound by
|
||||
+ // the maximums specified in Constants.h, and that gl::Box members are signed 32-bit
|
||||
+ // integers.
|
||||
+ static_assert(gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE *
|
||||
+ gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE <
|
||||
+ std::numeric_limits<int32_t>::max());
|
||||
+ size_t dstBufferSize = sourceBox.width * sourceBox.height;
|
||||
+ static_assert(gl::IMPLEMENTATION_MAX_3D_TEXTURE_SIZE *
|
||||
+ gl::IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS * 16 <
|
||||
+ std::numeric_limits<int32_t>::max());
|
||||
+ dstBufferSize *= sourceBox.depth * dstFormat.pixelBytes * layerCount;
|
||||
|
||||
// Allocate memory in the destination texture for the copy/conversion.
|
||||
uint8_t *dstData = nullptr;
|
||||
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
index 020a041..f72f1a7 100644
|
||||
--- a/src/tests/gl_tests/FramebufferTest.cpp
|
||||
+++ b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
@@ -8894,6 +8894,62 @@
|
||||
ASSERT_GL_NO_ERROR();
|
||||
}
|
||||
|
||||
+// Test that 2D array texture size calculation doesn't overflow internally when rendering to it. An
|
||||
+// RGB format is used which is often emualted with RGBA.
|
||||
+//
|
||||
+// Practically we cannot run this test. On most configurations, allocating a 4GB texture fails due
|
||||
+// to internal driver limitations. On the few configs that the test actually runs, allocating such
|
||||
+// large memory leads to instability.
|
||||
+TEST_P(FramebufferTest_ES3, DISABLED_MaxSize2DArrayNoOverflow)
|
||||
+{
|
||||
+ GLint maxTexture2DSize;
|
||||
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexture2DSize);
|
||||
+
|
||||
+ maxTexture2DSize = std::min(maxTexture2DSize, 16384);
|
||||
+
|
||||
+ // Create a 2D array texture with RGB format. Every layer is going to take 1GB of memory (if
|
||||
+ // emulated with RGBA), so only create 4 layers of it (for a total of 4GB of memory). If 32-bit
|
||||
+ // math is involved when calculating sizes related to this texture, they will overflow.
|
||||
+ constexpr uint32_t kLayers = 4;
|
||||
+ GLTexture tex;
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
|
||||
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGB8, maxTexture2DSize, maxTexture2DSize, kLayers);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
+
|
||||
+ // Initialize the texture so its content is considered valid and worth preserving.
|
||||
+ constexpr int kValidSubsectionWidth = 16;
|
||||
+ constexpr int kValidSubsectionHeight = 20;
|
||||
+ std::vector<GLColorRGB> data(kValidSubsectionWidth * kValidSubsectionHeight,
|
||||
+ GLColorRGB(0, 255, 0));
|
||||
+ for (uint32_t layer = 0; layer < kLayers; ++layer)
|
||||
+ {
|
||||
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kValidSubsectionWidth,
|
||||
+ kValidSubsectionHeight, 1, GL_RGB, GL_UNSIGNED_BYTE, data.data());
|
||||
+ }
|
||||
+
|
||||
+ // Draw with the texture, making sure it's initialized and data is flushed.
|
||||
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
|
||||
+ essl3_shaders::fs::Texture2DArray());
|
||||
+ drawQuad(drawTex2DArray, essl3_shaders::PositionAttrib(), 0.5f);
|
||||
+
|
||||
+ // Bind a framebuffer to the texture and render into it. In some backends, the texture is
|
||||
+ // recreated to RGBA to be renderable.
|
||||
+ GLFramebuffer fbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, 1);
|
||||
+
|
||||
+ ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
|
||||
+ glViewport(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight);
|
||||
+ drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);
|
||||
+
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight, GLColor::red);
|
||||
+ EXPECT_PIXEL_RECT_EQ(kValidSubsectionWidth / 2, 0,
|
||||
+ kValidSubsectionWidth - kValidSubsectionWidth / 2, kValidSubsectionHeight,
|
||||
+ GLColor::green);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
ANGLE_INSTANTIATE_TEST_ES2_AND(AddMockTextureNoRenderTargetTest,
|
||||
ES2_D3D9().enable(Feature::AddMockTextureNoRenderTarget),
|
||||
ES2_D3D11().enable(Feature::AddMockTextureNoRenderTarget));
|
||||
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
|
||||
index 2f06e2d..87e7482 100644
|
||||
--- a/src/tests/gl_tests/VulkanImageTest.cpp
|
||||
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
|
||||
@@ -677,8 +677,8 @@
|
||||
kTextureHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, textureColor.data());
|
||||
}
|
||||
|
||||
- ANGLE_GL_PROGRAM(drawTex2DArray, essl1_shaders::vs::Texture2DArray(),
|
||||
- essl1_shaders::fs::Texture2DArray());
|
||||
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
|
||||
+ essl3_shaders::fs::Texture2DArray());
|
||||
drawQuad(drawTex2DArray, essl1_shaders::PositionAttrib(), 0.5f);
|
||||
|
||||
// Fill up the device memory until we start allocating on the system memory.
|
||||
diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp
|
||||
index 275e261..8994612 100644
|
||||
--- a/util/shader_utils.cpp
|
||||
+++ b/util/shader_utils.cpp
|
||||
@@ -580,18 +580,6 @@
|
||||
})";
|
||||
}
|
||||
|
||||
-const char *Texture2DArray()
|
||||
-{
|
||||
- return R"(#version 300 es
|
||||
-out vec2 v_texCoord;
|
||||
-in vec4 a_position;
|
||||
-void main()
|
||||
-{
|
||||
- gl_Position = vec4(a_position.xy, 0.0, 1.0);
|
||||
- v_texCoord = (a_position.xy * 0.5) + 0.5;
|
||||
-})";
|
||||
-}
|
||||
-
|
||||
} // namespace vs
|
||||
|
||||
namespace fs
|
||||
@@ -689,20 +677,6 @@
|
||||
})";
|
||||
}
|
||||
|
||||
-const char *Texture2DArray()
|
||||
-{
|
||||
- return R"(#version 300 es
|
||||
-precision highp float;
|
||||
-uniform highp sampler2DArray tex2DArray;
|
||||
-uniform int slice;
|
||||
-in vec2 v_texCoord;
|
||||
-out vec4 fragColor;
|
||||
-void main()
|
||||
-{
|
||||
- fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
|
||||
-})";
|
||||
-}
|
||||
-
|
||||
} // namespace fs
|
||||
} // namespace essl1_shaders
|
||||
|
||||
@@ -787,6 +761,18 @@
|
||||
})";
|
||||
}
|
||||
|
||||
+const char *Texture2DArray()
|
||||
+{
|
||||
+ return R"(#version 300 es
|
||||
+out vec2 v_texCoord;
|
||||
+in vec4 a_position;
|
||||
+void main()
|
||||
+{
|
||||
+ gl_Position = vec4(a_position.xy, 0.0, 1.0);
|
||||
+ v_texCoord = (a_position.xy * 0.5) + 0.5;
|
||||
+})";
|
||||
+}
|
||||
+
|
||||
} // namespace vs
|
||||
|
||||
namespace fs
|
||||
@@ -844,6 +830,20 @@
|
||||
})";
|
||||
}
|
||||
|
||||
+const char *Texture2DArray()
|
||||
+{
|
||||
+ return R"(#version 300 es
|
||||
+precision highp float;
|
||||
+uniform highp sampler2DArray tex2DArray;
|
||||
+uniform int slice;
|
||||
+in vec2 v_texCoord;
|
||||
+out vec4 fragColor;
|
||||
+void main()
|
||||
+{
|
||||
+ fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
|
||||
+})";
|
||||
+}
|
||||
+
|
||||
} // namespace fs
|
||||
} // namespace essl3_shaders
|
||||
|
||||
diff --git a/util/shader_utils.h b/util/shader_utils.h
|
||||
index 676341e..cf211cf 100644
|
||||
--- a/util/shader_utils.h
|
||||
+++ b/util/shader_utils.h
|
||||
@@ -90,7 +90,6 @@
|
||||
// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
|
||||
// texcoord.
|
||||
ANGLE_UTIL_EXPORT const char *Texture2D();
|
||||
-ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
|
||||
} // namespace vs
|
||||
|
||||
@@ -120,7 +119,6 @@
|
||||
|
||||
// A shader that samples the texture
|
||||
ANGLE_UTIL_EXPORT const char *Texture2D();
|
||||
-ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
|
||||
} // namespace fs
|
||||
} // namespace essl1_shaders
|
||||
@@ -151,6 +149,7 @@
|
||||
// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
|
||||
// texcoord.
|
||||
ANGLE_UTIL_EXPORT const char *Texture2DLod();
|
||||
+ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
|
||||
} // namespace vs
|
||||
|
||||
@@ -169,6 +168,9 @@
|
||||
// A shader that samples the texture at a given lod.
|
||||
ANGLE_UTIL_EXPORT const char *Texture2DLod();
|
||||
|
||||
+// A shader that samples the texture at a given slice.
|
||||
+ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
+
|
||||
} // namespace fs
|
||||
} // namespace essl3_shaders
|
||||
|
||||
@@ -149,3 +149,5 @@ move_wayland_pointer_lock_overrides_to_common_code.patch
|
||||
loaf_add_feature_to_enable_sourceurl_for_all_protocols.patch
|
||||
fix_update_dbus_signal_signature_for_xdg_globalshortcuts_portal.patch
|
||||
patch_osr_control_screen_info.patch
|
||||
cherry-pick-12f932985275.patch
|
||||
fix_mac_high_res_icons.patch
|
||||
|
||||
762
patches/chromium/cherry-pick-12f932985275.patch
Normal file
762
patches/chromium/cherry-pick-12f932985275.patch
Normal file
@@ -0,0 +1,762 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Geoff Lang <geofflang@chromium.org>
|
||||
Date: Wed, 11 Feb 2026 08:05:52 -0800
|
||||
Subject: Ensure the previous complete fbo is not deleted on IMG.
|
||||
|
||||
Change-Id: I7d84833312749fc58ecb511b276ff6bd783af1ba
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7533383
|
||||
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Commit-Queue: Geoff Lang <geofflang@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1583241}
|
||||
|
||||
diff --git a/gpu/command_buffer/service/context_group.cc b/gpu/command_buffer/service/context_group.cc
|
||||
index 9a913cdefd0681d9972a83d8f940a3c48af0f46d..4a3a68b4415b6cc05eab01f98b1a0afffe73afc9 100644
|
||||
--- a/gpu/command_buffer/service/context_group.cc
|
||||
+++ b/gpu/command_buffer/service/context_group.cc
|
||||
@@ -120,9 +120,17 @@ ContextGroup::ContextGroup(
|
||||
|
||||
use_passthrough_cmd_decoder_ = gpu_preferences_.use_passthrough_cmd_decoder;
|
||||
}
|
||||
-
|
||||
gpu::ContextResult ContextGroup::Initialize(DecoderContext* decoder,
|
||||
ContextType context_type) {
|
||||
+ return InitializeWithCompleteFramebufferForWorkarounds(decoder, context_type,
|
||||
+ 0);
|
||||
+}
|
||||
+
|
||||
+gpu::ContextResult
|
||||
+ContextGroup::InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ DecoderContext* decoder,
|
||||
+ ContextType context_type,
|
||||
+ uint32_t complete_fbo_for_workarounds) {
|
||||
switch (context_type) {
|
||||
case CONTEXT_TYPE_WEBGL1:
|
||||
if (kGpuFeatureStatusBlocklisted ==
|
||||
@@ -156,8 +164,9 @@ gpu::ContextResult ContextGroup::Initialize(DecoderContext* decoder,
|
||||
DisallowedFeatures adjusted_disallowed_features =
|
||||
GetDisallowedFeatures(context_type);
|
||||
|
||||
- feature_info_->Initialize(context_type, use_passthrough_cmd_decoder_,
|
||||
- adjusted_disallowed_features);
|
||||
+ feature_info_->InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ context_type, use_passthrough_cmd_decoder_, adjusted_disallowed_features,
|
||||
+ complete_fbo_for_workarounds);
|
||||
|
||||
// Fail early if ES3 is requested and driver does not support it.
|
||||
if ((context_type == CONTEXT_TYPE_WEBGL2 ||
|
||||
diff --git a/gpu/command_buffer/service/context_group.h b/gpu/command_buffer/service/context_group.h
|
||||
index 78ea1ccd987cfc6f8ec0e378f906fb19fdccbc19..051d8126c128c47af71d0862cc0ec9476414825e 100644
|
||||
--- a/gpu/command_buffer/service/context_group.h
|
||||
+++ b/gpu/command_buffer/service/context_group.h
|
||||
@@ -72,7 +72,10 @@ class GPU_GLES2_EXPORT ContextGroup : public base::RefCounted<ContextGroup> {
|
||||
// call to destroy if it succeeds.
|
||||
gpu::ContextResult Initialize(DecoderContext* decoder,
|
||||
ContextType context_type);
|
||||
-
|
||||
+ gpu::ContextResult InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ DecoderContext* decoder,
|
||||
+ ContextType context_type,
|
||||
+ uint32_t complete_fbo_for_workarounds);
|
||||
// Destroys all the resources when called for the last context in the group.
|
||||
// It should only be called by DecoderContext.
|
||||
void Destroy(DecoderContext* decoder, bool have_context);
|
||||
diff --git a/gpu/command_buffer/service/decoder_context.h b/gpu/command_buffer/service/decoder_context.h
|
||||
index f00ad2459ff2b4993af37ac6fe0a5aa661fee692..96edc25e2d1ce364470b75ec1a3658ed0e217e54 100644
|
||||
--- a/gpu/command_buffer/service/decoder_context.h
|
||||
+++ b/gpu/command_buffer/service/decoder_context.h
|
||||
@@ -139,6 +139,12 @@ class GPU_GLES2_EXPORT DecoderContext : public AsyncAPIInterface,
|
||||
virtual gles2::ContextGroup* GetContextGroup() = 0;
|
||||
virtual gles2::ErrorState* GetErrorState() = 0;
|
||||
|
||||
+ //
|
||||
+ // Methods required by GLES2 Decoder helpers
|
||||
+ //
|
||||
+ // Bind the framebuffer `service_id` and perform any workarounds needed.
|
||||
+ virtual void BindFramebuffer(unsigned target, uint32_t service_id) const = 0;
|
||||
+
|
||||
//
|
||||
// Methods required by Texture.
|
||||
//
|
||||
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
|
||||
index 14bf6c1a220f7b37fa81ddbc7ac1f2a16fb08e4c..6f1cb7f75f8bc4b80d1bd95be721285bd0bab1a8 100644
|
||||
--- a/gpu/command_buffer/service/feature_info.cc
|
||||
+++ b/gpu/command_buffer/service/feature_info.cc
|
||||
@@ -64,7 +64,8 @@ class ScopedPixelUnpackBufferOverride {
|
||||
|
||||
bool IsWebGLDrawBuffersSupported(bool webglCompatibilityContext,
|
||||
GLenum depth_texture_internal_format,
|
||||
- GLenum depth_stencil_texture_internal_format) {
|
||||
+ GLenum depth_stencil_texture_internal_format,
|
||||
+ GLuint complete_fbo_for_workarounds) {
|
||||
// This is called after we make sure GL_EXT_draw_buffers is supported.
|
||||
GLint max_draw_buffers = 0;
|
||||
GLint max_color_attachments = 0;
|
||||
@@ -81,6 +82,9 @@ bool IsWebGLDrawBuffersSupported(bool webglCompatibilityContext,
|
||||
|
||||
GLuint fbo;
|
||||
glGenFramebuffersEXT(1, &fbo);
|
||||
+ if (complete_fbo_for_workarounds) {
|
||||
+ glBindFramebufferEXT(GL_FRAMEBUFFER, complete_fbo_for_workarounds);
|
||||
+ }
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER, fbo);
|
||||
|
||||
GLuint depth_stencil_texture = 0;
|
||||
@@ -157,6 +161,9 @@ bool IsWebGLDrawBuffersSupported(bool webglCompatibilityContext,
|
||||
}
|
||||
}
|
||||
|
||||
+ if (complete_fbo_for_workarounds) {
|
||||
+ glBindFramebufferEXT(GL_FRAMEBUFFER, complete_fbo_for_workarounds);
|
||||
+ }
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER, static_cast<GLuint>(fb_binding));
|
||||
glDeleteFramebuffersEXT(1, &fbo);
|
||||
|
||||
@@ -238,6 +245,15 @@ void FeatureInfo::InitializeBasicState(const base::CommandLine* command_line) {
|
||||
void FeatureInfo::Initialize(ContextType context_type,
|
||||
bool is_passthrough_cmd_decoder,
|
||||
const DisallowedFeatures& disallowed_features) {
|
||||
+ InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ context_type, is_passthrough_cmd_decoder, disallowed_features, 0);
|
||||
+}
|
||||
+
|
||||
+void FeatureInfo::InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ ContextType context_type,
|
||||
+ bool is_passthrough_cmd_decoder,
|
||||
+ const DisallowedFeatures& disallowed_features,
|
||||
+ unsigned complete_fbo_for_workarounds) {
|
||||
if (initialized_) {
|
||||
DCHECK_EQ(context_type, context_type_);
|
||||
DCHECK_EQ(is_passthrough_cmd_decoder, is_passthrough_cmd_decoder_);
|
||||
@@ -248,14 +264,14 @@ void FeatureInfo::Initialize(ContextType context_type,
|
||||
disallowed_features_ = disallowed_features;
|
||||
context_type_ = context_type;
|
||||
is_passthrough_cmd_decoder_ = is_passthrough_cmd_decoder;
|
||||
- InitializeFeatures();
|
||||
+ InitializeFeatures(complete_fbo_for_workarounds);
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void FeatureInfo::ForceReinitialize() {
|
||||
CHECK(initialized_);
|
||||
CHECK(is_passthrough_cmd_decoder_);
|
||||
- InitializeFeatures();
|
||||
+ InitializeFeatures(0);
|
||||
}
|
||||
|
||||
void FeatureInfo::InitializeForTesting(
|
||||
@@ -277,7 +293,7 @@ void FeatureInfo::InitializeForTesting(ContextType context_type) {
|
||||
DisallowedFeatures());
|
||||
}
|
||||
|
||||
-bool IsGL_REDSupportedOnFBOs() {
|
||||
+bool IsGL_REDSupportedOnFBOs(uint32_t complete_fbo_for_workarounds) {
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
// The glTexImage2D call below can hang on Mac so skip this since it's only
|
||||
// really needed to workaround a Mesa issue. See https://crbug.com/1158744.
|
||||
@@ -311,6 +327,9 @@ bool IsGL_REDSupportedOnFBOs() {
|
||||
GL_UNSIGNED_BYTE, nullptr);
|
||||
GLuint textureFBOID = 0;
|
||||
glGenFramebuffersEXT(1, &textureFBOID);
|
||||
+ if (complete_fbo_for_workarounds) {
|
||||
+ glBindFramebufferEXT(GL_FRAMEBUFFER, complete_fbo_for_workarounds);
|
||||
+ }
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER, textureFBOID);
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
textureId, 0);
|
||||
@@ -319,6 +338,9 @@ bool IsGL_REDSupportedOnFBOs() {
|
||||
glDeleteFramebuffersEXT(1, &textureFBOID);
|
||||
glDeleteTextures(1, &textureId);
|
||||
|
||||
+ if (complete_fbo_for_workarounds) {
|
||||
+ glBindFramebufferEXT(GL_FRAMEBUFFER, complete_fbo_for_workarounds);
|
||||
+ }
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER, static_cast<GLuint>(fb_binding));
|
||||
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(tex_binding));
|
||||
|
||||
@@ -476,7 +498,7 @@ void FeatureInfo::EnableWEBGLMultiDrawIfPossible(
|
||||
}
|
||||
}
|
||||
|
||||
-void FeatureInfo::InitializeFeatures() {
|
||||
+void FeatureInfo::InitializeFeatures(uint32_t complete_fbo_for_workarounds) {
|
||||
// Figure out what extensions to turn on.
|
||||
std::string extensions_string(gl::GetGLExtensionsFromCurrentContext());
|
||||
gfx::ExtensionSet extensions(gfx::MakeExtensionSet(extensions_string));
|
||||
@@ -1264,9 +1286,9 @@ void FeatureInfo::InitializeFeatures() {
|
||||
can_emulate_es2_draw_buffers_on_es3_nv) &&
|
||||
(context_type_ == CONTEXT_TYPE_OPENGLES2 ||
|
||||
(context_type_ == CONTEXT_TYPE_WEBGL1 &&
|
||||
- IsWebGLDrawBuffersSupported(is_webgl_compatibility_context,
|
||||
- depth_texture_format,
|
||||
- depth_stencil_texture_format)));
|
||||
+ IsWebGLDrawBuffersSupported(
|
||||
+ is_webgl_compatibility_context, depth_texture_format,
|
||||
+ depth_stencil_texture_format, complete_fbo_for_workarounds)));
|
||||
if (have_es2_draw_buffers) {
|
||||
AddExtensionString("GL_EXT_draw_buffers");
|
||||
feature_flags_.ext_draw_buffers = true;
|
||||
@@ -1387,7 +1409,7 @@ void FeatureInfo::InitializeFeatures() {
|
||||
|
||||
if ((gl_version_info_->is_es3 ||
|
||||
gfx::HasExtension(extensions, "GL_EXT_texture_rg")) &&
|
||||
- IsGL_REDSupportedOnFBOs()) {
|
||||
+ IsGL_REDSupportedOnFBOs(complete_fbo_for_workarounds)) {
|
||||
feature_flags_.ext_texture_rg = true;
|
||||
AddExtensionString("GL_EXT_texture_rg");
|
||||
|
||||
diff --git a/gpu/command_buffer/service/feature_info.h b/gpu/command_buffer/service/feature_info.h
|
||||
index 2db3588ca3ed729799b113350ea8a7c449712587..83c683e900a3267061ced97ba971bf9dc0b88f4f 100644
|
||||
--- a/gpu/command_buffer/service/feature_info.h
|
||||
+++ b/gpu/command_buffer/service/feature_info.h
|
||||
@@ -163,6 +163,14 @@ class GPU_GLES2_EXPORT FeatureInfo : public base::RefCounted<FeatureInfo> {
|
||||
bool is_passthrough_cmd_decoder,
|
||||
const DisallowedFeatures& disallowed_features);
|
||||
|
||||
+ // Same as initialize but with a provided `complete_fbo_for_workarounds` to
|
||||
+ // use with the ensure_previous_framebuffer_not_deleted driver bug workaround.
|
||||
+ void InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ ContextType context_type,
|
||||
+ bool is_passthrough_cmd_decoder,
|
||||
+ const DisallowedFeatures& disallowed_features,
|
||||
+ uint32_t complete_fbo_for_workarounds);
|
||||
+
|
||||
// Same as above, but allows reinitialization.
|
||||
void ForceReinitialize();
|
||||
|
||||
@@ -250,7 +258,7 @@ class GPU_GLES2_EXPORT FeatureInfo : public base::RefCounted<FeatureInfo> {
|
||||
|
||||
void AddExtensionString(std::string_view s);
|
||||
void InitializeBasicState(const base::CommandLine* command_line);
|
||||
- void InitializeFeatures();
|
||||
+ void InitializeFeatures(uint32_t complete_fbo_for_workarounds);
|
||||
void InitializeFloatAndHalfFloatFeatures(const gfx::ExtensionSet& extensions);
|
||||
|
||||
void EnableANGLEInstancedArrayIfPossible(const gfx::ExtensionSet& extensions);
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc b/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
|
||||
index b500acb4fe7cbff0e84a4e52e66459d5b85fdf75..a08bb3ac4ab625f220865803352e206713763d77 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
|
||||
@@ -185,7 +185,7 @@ void CopyTexImageResourceManager::DoCopyTexSubImageToLUMACompatibilityTexture(
|
||||
// framebuffer is copying from a texture and sample directly from that texture
|
||||
// instead of doing an extra copy
|
||||
|
||||
- glBindFramebufferEXT(GL_FRAMEBUFFER, source_framebuffer);
|
||||
+ decoder->BindFramebuffer(GL_FRAMEBUFFER, source_framebuffer);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, scratch_textures_[0]);
|
||||
glCopyTexImage2D(GL_TEXTURE_2D, 0, source_framebuffer_internal_format, x, y,
|
||||
@@ -217,7 +217,7 @@ void CopyTexImageResourceManager::DoCopyTexSubImageToLUMACompatibilityTexture(
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, compatability_format, width, height, 0,
|
||||
compatability_format, luma_type, nullptr);
|
||||
|
||||
- glBindFramebufferEXT(GL_FRAMEBUFFER, scratch_fbo_);
|
||||
+ decoder->BindFramebuffer(GL_FRAMEBUFFER, scratch_fbo_);
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
scratch_textures_[1], 0);
|
||||
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc b/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
|
||||
index 469642028b839d490199379254a5a44c2fcd7f02..98f476de9e4d423b6ec86f2a69638e43bd0fb423 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
|
||||
@@ -491,7 +491,8 @@ void DeleteShader(GLuint shader) {
|
||||
glDeleteShader(shader);
|
||||
}
|
||||
|
||||
-bool BindFramebufferTexture2D(GLenum target,
|
||||
+bool BindFramebufferTexture2D(DecoderContext* decoder,
|
||||
+ GLenum target,
|
||||
GLuint texture_id,
|
||||
GLint level,
|
||||
GLuint framebuffer) {
|
||||
@@ -511,7 +512,7 @@ bool BindFramebufferTexture2D(GLenum target,
|
||||
glTexParameterf(binding_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(binding_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(binding_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
- glBindFramebufferEXT(GL_FRAMEBUFFER, framebuffer);
|
||||
+ decoder->BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target,
|
||||
texture_id, level);
|
||||
|
||||
@@ -545,7 +546,7 @@ void DoCopyTexImage2D(
|
||||
DCHECK(dest_binding_target == GL_TEXTURE_2D ||
|
||||
dest_binding_target == GL_TEXTURE_CUBE_MAP);
|
||||
DCHECK(source_level == 0 || decoder->GetFeatureInfo()->IsES3Capable());
|
||||
- if (BindFramebufferTexture2D(source_target, source_id, source_level,
|
||||
+ if (BindFramebufferTexture2D(decoder, source_target, source_id, source_level,
|
||||
framebuffer)) {
|
||||
glBindTexture(dest_binding_target, dest_id);
|
||||
glTexParameterf(dest_binding_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@@ -603,7 +604,7 @@ void DoCopyTexSubImage2D(
|
||||
DCHECK(dest_binding_target == GL_TEXTURE_2D ||
|
||||
dest_binding_target == GL_TEXTURE_CUBE_MAP);
|
||||
DCHECK(source_level == 0 || decoder->GetFeatureInfo()->IsES3Capable());
|
||||
- if (BindFramebufferTexture2D(source_target, source_id, source_level,
|
||||
+ if (BindFramebufferTexture2D(decoder, source_target, source_id, source_level,
|
||||
framebuffer)) {
|
||||
glBindTexture(dest_binding_target, dest_id);
|
||||
glTexParameterf(dest_binding_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@@ -767,7 +768,7 @@ void DoReadbackAndTexImage(TexImageCommandType command_type,
|
||||
DCHECK(dest_binding_target == GL_TEXTURE_2D ||
|
||||
dest_binding_target == GL_TEXTURE_CUBE_MAP);
|
||||
DCHECK(source_level == 0 || decoder->GetFeatureInfo()->IsES3Capable());
|
||||
- if (BindFramebufferTexture2D(source_target, source_id, source_level,
|
||||
+ if (BindFramebufferTexture2D(decoder, source_target, source_id, source_level,
|
||||
framebuffer)) {
|
||||
glBindTexture(dest_binding_target, dest_id);
|
||||
glTexParameterf(dest_binding_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@@ -1341,7 +1342,7 @@ void CopyTextureResourceManagerImpl::DoCopyTextureInternal(
|
||||
(y + height / 2.f) * m_y / source_height);
|
||||
|
||||
DCHECK(dest_level == 0 || decoder->GetFeatureInfo()->IsES3Capable());
|
||||
- if (BindFramebufferTexture2D(dest_target, dest_id, dest_level,
|
||||
+ if (BindFramebufferTexture2D(decoder, dest_target, dest_id, dest_level,
|
||||
framebuffer_)) {
|
||||
#ifndef NDEBUG
|
||||
// glValidateProgram of MACOSX validates FBO unlike other platforms, so
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
|
||||
index 0bb2581fa602217ab77aa429ed51ce0ce5a06f00..895266f5988e2446eadf3d3e1d5c4919416cba76 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
|
||||
@@ -675,6 +675,9 @@ class GLES2DecoderImpl : public GLES2Decoder,
|
||||
// Implements GpuSwitchingObserver.
|
||||
void OnGpuSwitched() override;
|
||||
|
||||
+ // Bind the framebuffer `fbo` and perform any workarounds needed.
|
||||
+ void BindFramebuffer(unsigned target, uint32_t service_id) const override;
|
||||
+
|
||||
// Restores the current state to the user's settings.
|
||||
void RestoreCurrentFramebufferBindings();
|
||||
|
||||
@@ -2418,6 +2421,10 @@ class GLES2DecoderImpl : public GLES2Decoder,
|
||||
// Backbuffer attachments that are currently undefined.
|
||||
uint32_t backbuffer_needs_clear_bits_;
|
||||
|
||||
+ // An always-complete FBO to use for workarounds
|
||||
+ GLuint complete_fbo_ = 0;
|
||||
+ GLuint complete_fbo_color_texture_ = 0;
|
||||
+
|
||||
// The current decoder error communicates the decoder error through command
|
||||
// processing functions that do not return the error value. Should be set only
|
||||
// if not returning an error.
|
||||
@@ -2594,7 +2601,7 @@ ScopedFramebufferBinder::ScopedFramebufferBinder(GLES2DecoderImpl* decoder,
|
||||
: decoder_(decoder) {
|
||||
ScopedGLErrorSuppressor suppressor("ScopedFramebufferBinder::ctor",
|
||||
decoder_->error_state_.get());
|
||||
- decoder->api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, id);
|
||||
+ decoder->BindFramebuffer(GL_FRAMEBUFFER, id);
|
||||
decoder->OnFboChanged();
|
||||
}
|
||||
|
||||
@@ -2980,7 +2987,27 @@ gpu::ContextResult GLES2DecoderImpl::Initialize(
|
||||
return gpu::ContextResult::kFatalFailure;
|
||||
}
|
||||
|
||||
- auto result = group_->Initialize(this, context_type);
|
||||
+ if (workarounds().ensure_previous_framebuffer_not_deleted) {
|
||||
+ // Use a 1x1 RGBA8 framebuffer as the "always complete" framebuffer to bind
|
||||
+ // before binding other framebuffers
|
||||
+ api()->glGenTexturesFn(1, &complete_fbo_color_texture_);
|
||||
+ api()->glBindTextureFn(GL_TEXTURE_2D, complete_fbo_color_texture_);
|
||||
+ api()->glTexImage2DFn(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA,
|
||||
+ GL_UNSIGNED_BYTE, nullptr);
|
||||
+
|
||||
+ api()->glGenFramebuffersEXTFn(1, &complete_fbo_);
|
||||
+ api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, complete_fbo_);
|
||||
+ api()->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
+ GL_TEXTURE_2D,
|
||||
+ complete_fbo_color_texture_, 0);
|
||||
+ CHECK_EQ(api()->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
|
||||
+ static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));
|
||||
+ }
|
||||
+ CHECK_GL_ERROR();
|
||||
+
|
||||
+ auto result = group_->InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ this, context_type, complete_fbo_);
|
||||
+
|
||||
if (result != gpu::ContextResult::kSuccess) {
|
||||
// Must not destroy ContextGroup if it is not initialized.
|
||||
group_ = nullptr;
|
||||
@@ -3116,7 +3143,7 @@ gpu::ContextResult GLES2DecoderImpl::Initialize(
|
||||
state_.viewport_width = initial_size.width();
|
||||
state_.viewport_height = initial_size.height();
|
||||
} else {
|
||||
- api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, GetBackbufferServiceId());
|
||||
+ BindFramebuffer(GL_FRAMEBUFFER, GetBackbufferServiceId());
|
||||
// These are NOT if the back buffer has these proprorties. They are
|
||||
// if we want the command buffer to enforce them regardless of what
|
||||
// the real backbuffer is assuming the real back buffer gives us more than
|
||||
@@ -3809,7 +3836,7 @@ void GLES2DecoderImpl::DeleteFramebuffersHelper(
|
||||
if (workarounds().unbind_attachments_on_bound_render_fbo_delete)
|
||||
framebuffer->DoUnbindGLAttachmentsForWorkaround(target);
|
||||
|
||||
- api()->glBindFramebufferEXTFn(target, GetBackbufferServiceId());
|
||||
+ BindFramebuffer(target, GetBackbufferServiceId());
|
||||
state_.UpdateWindowRectanglesForBoundDrawFramebufferClientID(0);
|
||||
framebuffer_state_.bound_draw_framebuffer = nullptr;
|
||||
framebuffer_state_.clear_state_dirty = true;
|
||||
@@ -3817,7 +3844,7 @@ void GLES2DecoderImpl::DeleteFramebuffersHelper(
|
||||
if (framebuffer == framebuffer_state_.bound_read_framebuffer.get()) {
|
||||
framebuffer_state_.bound_read_framebuffer = nullptr;
|
||||
GLenum target = GetReadFramebufferTarget();
|
||||
- api()->glBindFramebufferEXTFn(target, GetBackbufferServiceId());
|
||||
+ BindFramebuffer(target, GetBackbufferServiceId());
|
||||
}
|
||||
OnFboChanged();
|
||||
RemoveFramebuffer(client_id);
|
||||
@@ -3965,33 +3992,32 @@ void GLES2DecoderImpl::ProcessFinishedAsyncTransfers() {
|
||||
ProcessPendingReadPixels(false);
|
||||
}
|
||||
|
||||
-static void RebindCurrentFramebuffer(gl::GLApi* api,
|
||||
- GLenum target,
|
||||
- Framebuffer* framebuffer,
|
||||
- GLuint back_buffer_service_id) {
|
||||
- GLuint framebuffer_id = framebuffer ? framebuffer->service_id() : 0;
|
||||
+void GLES2DecoderImpl::RestoreCurrentFramebufferBindings() {
|
||||
+ framebuffer_state_.clear_state_dirty = true;
|
||||
|
||||
- if (framebuffer_id == 0) {
|
||||
- framebuffer_id = back_buffer_service_id;
|
||||
- }
|
||||
+ auto rebind_current_framebuffer = [this](GLenum target,
|
||||
+ Framebuffer* framebuffer,
|
||||
+ GLuint back_buffer_service_id) {
|
||||
+ GLuint framebuffer_id = framebuffer ? framebuffer->service_id() : 0;
|
||||
|
||||
- api->glBindFramebufferEXTFn(target, framebuffer_id);
|
||||
-}
|
||||
+ if (framebuffer_id == 0) {
|
||||
+ framebuffer_id = back_buffer_service_id;
|
||||
+ }
|
||||
|
||||
-void GLES2DecoderImpl::RestoreCurrentFramebufferBindings() {
|
||||
- framebuffer_state_.clear_state_dirty = true;
|
||||
+ BindFramebuffer(target, framebuffer_id);
|
||||
+ };
|
||||
|
||||
if (!SupportsSeparateFramebufferBinds()) {
|
||||
- RebindCurrentFramebuffer(api(), GL_FRAMEBUFFER,
|
||||
- framebuffer_state_.bound_draw_framebuffer.get(),
|
||||
- GetBackbufferServiceId());
|
||||
+ rebind_current_framebuffer(GL_FRAMEBUFFER,
|
||||
+ framebuffer_state_.bound_draw_framebuffer.get(),
|
||||
+ GetBackbufferServiceId());
|
||||
} else {
|
||||
- RebindCurrentFramebuffer(api(), GL_READ_FRAMEBUFFER,
|
||||
- framebuffer_state_.bound_read_framebuffer.get(),
|
||||
- GetBackbufferServiceId());
|
||||
- RebindCurrentFramebuffer(api(), GL_DRAW_FRAMEBUFFER,
|
||||
- framebuffer_state_.bound_draw_framebuffer.get(),
|
||||
- GetBackbufferServiceId());
|
||||
+ rebind_current_framebuffer(GL_READ_FRAMEBUFFER,
|
||||
+ framebuffer_state_.bound_read_framebuffer.get(),
|
||||
+ GetBackbufferServiceId());
|
||||
+ rebind_current_framebuffer(GL_DRAW_FRAMEBUFFER,
|
||||
+ framebuffer_state_.bound_draw_framebuffer.get(),
|
||||
+ GetBackbufferServiceId());
|
||||
}
|
||||
OnFboChanged();
|
||||
}
|
||||
@@ -4380,6 +4406,16 @@ void GLES2DecoderImpl::OnGpuSwitched() {
|
||||
client()->OnGpuSwitched();
|
||||
}
|
||||
|
||||
+void GLES2DecoderImpl::BindFramebuffer(unsigned target,
|
||||
+ uint32_t service_id) const {
|
||||
+ if (workarounds().ensure_previous_framebuffer_not_deleted) {
|
||||
+ DCHECK(complete_fbo_);
|
||||
+ api()->glBindFramebufferEXTFn(target, complete_fbo_);
|
||||
+ }
|
||||
+
|
||||
+ api()->glBindFramebufferEXTFn(target, service_id);
|
||||
+}
|
||||
+
|
||||
void GLES2DecoderImpl::Destroy(bool have_context) {
|
||||
if (!initialized())
|
||||
return;
|
||||
@@ -4429,6 +4465,13 @@ void GLES2DecoderImpl::Destroy(bool have_context) {
|
||||
offscreen_target_frame_buffer_->Destroy();
|
||||
if (offscreen_target_color_texture_.get())
|
||||
offscreen_target_color_texture_->Destroy();
|
||||
+
|
||||
+ if (complete_fbo_color_texture_) {
|
||||
+ api()->glDeleteTexturesFn(1, &complete_fbo_color_texture_);
|
||||
+ }
|
||||
+ if (complete_fbo_) {
|
||||
+ api()->glDeleteFramebuffersEXTFn(1, &complete_fbo_);
|
||||
+ }
|
||||
} else {
|
||||
if (offscreen_target_frame_buffer_.get())
|
||||
offscreen_target_frame_buffer_->Invalidate();
|
||||
@@ -5058,13 +5101,13 @@ void GLES2DecoderImpl::RestoreFramebufferBindings() const {
|
||||
? framebuffer_state_.bound_draw_framebuffer->service_id()
|
||||
: GetBackbufferServiceId();
|
||||
if (!SupportsSeparateFramebufferBinds()) {
|
||||
- api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, service_id);
|
||||
+ BindFramebuffer(GL_FRAMEBUFFER, service_id);
|
||||
} else {
|
||||
- api()->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, service_id);
|
||||
+ BindFramebuffer(GL_DRAW_FRAMEBUFFER, service_id);
|
||||
service_id = framebuffer_state_.bound_read_framebuffer.get()
|
||||
? framebuffer_state_.bound_read_framebuffer->service_id()
|
||||
: GetBackbufferServiceId();
|
||||
- api()->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, service_id);
|
||||
+ BindFramebuffer(GL_READ_FRAMEBUFFER, service_id);
|
||||
}
|
||||
OnFboChanged();
|
||||
}
|
||||
@@ -5205,7 +5248,7 @@ void GLES2DecoderImpl::DoBindFramebuffer(GLenum target, GLuint client_id) {
|
||||
service_id = GetBackbufferServiceId();
|
||||
}
|
||||
|
||||
- api()->glBindFramebufferEXTFn(target, service_id);
|
||||
+ BindFramebuffer(target, service_id);
|
||||
OnFboChanged();
|
||||
}
|
||||
|
||||
@@ -6976,8 +7019,7 @@ void GLES2DecoderImpl::ClearUnclearedAttachments(
|
||||
if (target == GL_READ_FRAMEBUFFER && draw_framebuffer != framebuffer) {
|
||||
// TODO(zmo): There is no guarantee that an FBO that is complete on the
|
||||
// READ attachment will be complete as a DRAW attachment.
|
||||
- api()->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER,
|
||||
- framebuffer->service_id());
|
||||
+ BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer->service_id());
|
||||
}
|
||||
state_.SetDeviceColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, false);
|
||||
@@ -7024,8 +7066,7 @@ void GLES2DecoderImpl::ClearUnclearedAttachments(
|
||||
target == GL_READ_FRAMEBUFFER && draw_framebuffer != framebuffer) {
|
||||
// TODO(zmo): There is no guarantee that an FBO that is complete on the
|
||||
// READ attachment will be complete as a DRAW attachment.
|
||||
- api()->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER,
|
||||
- framebuffer->service_id());
|
||||
+ BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer->service_id());
|
||||
}
|
||||
state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, false);
|
||||
ClearDeviceWindowRectangles();
|
||||
@@ -7043,7 +7084,7 @@ void GLES2DecoderImpl::ClearUnclearedAttachments(
|
||||
if (target == GL_READ_FRAMEBUFFER && draw_framebuffer != framebuffer) {
|
||||
GLuint service_id = draw_framebuffer ? draw_framebuffer->service_id() :
|
||||
GetBackbufferServiceId();
|
||||
- api()->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, service_id);
|
||||
+ BindFramebuffer(GL_DRAW_FRAMEBUFFER, service_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7900,7 +7941,8 @@ void GLES2DecoderImpl::RenderbufferStorageMultisampleHelperAMD(
|
||||
|
||||
bool GLES2DecoderImpl::RegenerateRenderbufferIfNeeded(
|
||||
Renderbuffer* renderbuffer) {
|
||||
- if (!renderbuffer->RegenerateAndBindBackingObjectIfNeeded(workarounds())) {
|
||||
+ if (!renderbuffer->RegenerateAndBindBackingObjectIfNeeded(this,
|
||||
+ workarounds())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12059,7 +12101,7 @@ bool GLES2DecoderImpl::ClearLevelUsingGL(Texture* texture,
|
||||
GLenum fb_target = GetDrawFramebufferTarget();
|
||||
GLuint fb = 0;
|
||||
api()->glGenFramebuffersEXTFn(1, &fb);
|
||||
- api()->glBindFramebufferEXTFn(fb_target, fb);
|
||||
+ BindFramebuffer(fb_target, fb);
|
||||
|
||||
bool have_color = (channels & GLES2Util::kRGBA) != 0;
|
||||
if (have_color) {
|
||||
@@ -12102,7 +12144,7 @@ bool GLES2DecoderImpl::ClearLevelUsingGL(Texture* texture,
|
||||
Framebuffer* framebuffer = GetFramebufferInfoForTarget(fb_target);
|
||||
GLuint fb_service_id =
|
||||
framebuffer ? framebuffer->service_id() : GetBackbufferServiceId();
|
||||
- api()->glBindFramebufferEXTFn(fb_target, fb_service_id);
|
||||
+ BindFramebuffer(fb_target, fb_service_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -14581,8 +14623,9 @@ error::Error GLES2DecoderImpl::HandleGetRequestableExtensionsCHROMIUM(
|
||||
new FeatureInfo(workarounds(), group_->gpu_feature_info()));
|
||||
DisallowedFeatures disallowed_features = feature_info_->disallowed_features();
|
||||
disallowed_features.AllowExtensions();
|
||||
- info->Initialize(feature_info_->context_type(),
|
||||
- false /* is_passthrough_cmd_decoder */, disallowed_features);
|
||||
+ info->InitializeWithCompleteFramebufferForWorkarounds(
|
||||
+ feature_info_->context_type(), false /* is_passthrough_cmd_decoder */,
|
||||
+ disallowed_features, complete_fbo_);
|
||||
bucket->SetFromString(gfx::MakeExtensionString(info->extensions()).c_str());
|
||||
return error::kNoError;
|
||||
}
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_mock.h b/gpu/command_buffer/service/gles2_cmd_decoder_mock.h
|
||||
index bd3158e174f0881f99c45a0c8b8d5640e3be2b8f..cc160b5771954b485652f4b0cdc6695bfdea9954 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_decoder_mock.h
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_mock.h
|
||||
@@ -147,6 +147,8 @@ class MockGLES2Decoder : public GLES2Decoder {
|
||||
int height,
|
||||
int depth));
|
||||
MOCK_METHOD0(GetErrorState, ErrorState *());
|
||||
+ MOCK_CONST_METHOD2(BindFramebuffer,
|
||||
+ void(unsigned target, uint32_t service_id));
|
||||
|
||||
MOCK_METHOD0(GetLogger, Logger*());
|
||||
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
|
||||
index e8808f50df6d179879bc44fcacfb4154a4ac0454..de9f2f5cfb247a914606d3ba8b24f0924ac728e0 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
|
||||
@@ -1541,6 +1541,11 @@ gpu::gles2::ErrorState* GLES2DecoderPassthroughImpl::GetErrorState() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+void GLES2DecoderPassthroughImpl::BindFramebuffer(unsigned target,
|
||||
+ uint32_t service_id) const {
|
||||
+ NOTREACHED();
|
||||
+}
|
||||
+
|
||||
void GLES2DecoderPassthroughImpl::WaitForReadPixels(
|
||||
base::OnceClosure callback) {}
|
||||
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
|
||||
index 30dc611c8f0a84ec3493e3f865b6640cab157e2a..50938ca4a0086d14493ec7e0d85b99615c0c7fb7 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
|
||||
@@ -315,6 +315,8 @@ class GPU_GLES2_EXPORT GLES2DecoderPassthroughImpl
|
||||
|
||||
ErrorState* GetErrorState() override;
|
||||
|
||||
+ void BindFramebuffer(unsigned target, uint32_t service_id) const override;
|
||||
+
|
||||
void WaitForReadPixels(base::OnceClosure callback) override;
|
||||
|
||||
// Returns true if the context was lost either by GL_ARB_robustness, forced
|
||||
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
|
||||
index 5f61aac4fc217a97ccfcdbd6634056e5f305a425..767fee8cace6dec4cfaf1ccdd46f1764a7ae5318 100644
|
||||
--- a/gpu/command_buffer/service/raster_decoder.cc
|
||||
+++ b/gpu/command_buffer/service/raster_decoder.cc
|
||||
@@ -560,6 +560,7 @@ class RasterDecoderImpl final : public RasterDecoder,
|
||||
|
||||
gles2::ContextGroup* GetContextGroup() override;
|
||||
gles2::ErrorState* GetErrorState() override;
|
||||
+ void BindFramebuffer(unsigned target, uint32_t service_id) const override;
|
||||
|
||||
bool IsCompressedTextureFormat(unsigned format) override;
|
||||
bool ClearLevel(gles2::Texture* texture,
|
||||
@@ -1598,6 +1599,11 @@ gles2::ErrorState* RasterDecoderImpl::GetErrorState() {
|
||||
return error_state_.get();
|
||||
}
|
||||
|
||||
+void RasterDecoderImpl::BindFramebuffer(unsigned target,
|
||||
+ uint32_t service_id) const {
|
||||
+ NOTREACHED();
|
||||
+}
|
||||
+
|
||||
bool RasterDecoderImpl::IsCompressedTextureFormat(unsigned format) {
|
||||
return feature_info()->validators()->compressed_texture_format.IsValid(
|
||||
format);
|
||||
diff --git a/gpu/command_buffer/service/renderbuffer_manager.cc b/gpu/command_buffer/service/renderbuffer_manager.cc
|
||||
index 8075cb3acf7204a661a6c094edc1c1a783d46dfb..6cdb9186c4e3bfe40dd437db6b92343f60830171 100644
|
||||
--- a/gpu/command_buffer/service/renderbuffer_manager.cc
|
||||
+++ b/gpu/command_buffer/service/renderbuffer_manager.cc
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "base/trace_event/memory_dump_manager.h"
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
|
||||
+#include "gpu/command_buffer/service/decoder_context.h"
|
||||
#include "gpu/command_buffer/service/feature_info.h"
|
||||
#include "gpu/command_buffer/service/framebuffer_manager.h"
|
||||
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
|
||||
@@ -141,6 +142,7 @@ Renderbuffer::Renderbuffer(RenderbufferManager* manager,
|
||||
}
|
||||
|
||||
bool Renderbuffer::RegenerateAndBindBackingObjectIfNeeded(
|
||||
+ const DecoderContext* decoder,
|
||||
const GpuDriverBugWorkarounds& workarounds) {
|
||||
bool multisample_workaround =
|
||||
workarounds.multisample_renderbuffer_resize_emulation;
|
||||
@@ -167,7 +169,7 @@ bool Renderbuffer::RegenerateAndBindBackingObjectIfNeeded(
|
||||
|
||||
// Attach new renderbuffer to all framebuffers
|
||||
for (auto& point : framebuffer_attachment_points_) {
|
||||
- glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, point.first->service_id());
|
||||
+ decoder->BindFramebuffer(GL_DRAW_FRAMEBUFFER, point.first->service_id());
|
||||
glFramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, point.second,
|
||||
GL_RENDERBUFFER, service_id_);
|
||||
}
|
||||
diff --git a/gpu/command_buffer/service/renderbuffer_manager.h b/gpu/command_buffer/service/renderbuffer_manager.h
|
||||
index 7d575387ca8f6a7cb1b4bb020c52f7b53bed5d10..5eabeb9dd160122366507b7dfba53fd1c3285115 100644
|
||||
--- a/gpu/command_buffer/service/renderbuffer_manager.h
|
||||
+++ b/gpu/command_buffer/service/renderbuffer_manager.h
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
namespace gpu {
|
||||
class GpuDriverBugWorkarounds;
|
||||
+class DecoderContext;
|
||||
|
||||
namespace gles2 {
|
||||
|
||||
@@ -79,6 +80,7 @@ class GPU_GLES2_EXPORT Renderbuffer : public base::RefCounted<Renderbuffer> {
|
||||
// Regenerates the object backing this client_id, creating a new service_id.
|
||||
// Also reattaches any framebuffers using this renderbuffer.
|
||||
bool RegenerateAndBindBackingObjectIfNeeded(
|
||||
+ const DecoderContext* decoder,
|
||||
const GpuDriverBugWorkarounds& workarounds);
|
||||
|
||||
void AddFramebufferAttachmentPoint(Framebuffer* framebuffer,
|
||||
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
|
||||
index 172834911e75edb3ed1c4dceb3ec23755bf3b5f6..fb873fee0e4b1d33edd4210fad8ad74c6d563355 100644
|
||||
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
|
||||
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
|
||||
@@ -305,6 +305,9 @@ class WebGPUDecoderImpl final : public WebGPUDecoder {
|
||||
std::string_view GetLogPrefix() override { return "WebGPUDecoderImpl"; }
|
||||
gles2::ContextGroup* GetContextGroup() override { return nullptr; }
|
||||
gles2::ErrorState* GetErrorState() override { NOTREACHED(); }
|
||||
+ void BindFramebuffer(unsigned target, uint32_t service_id) const override {
|
||||
+ NOTREACHED();
|
||||
+ }
|
||||
bool IsCompressedTextureFormat(unsigned format) override { NOTREACHED(); }
|
||||
bool ClearLevel(gles2::Texture* texture,
|
||||
unsigned target,
|
||||
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
|
||||
index ebea892a523322b38a22ba6b0442262edcd6166b..2af5b0460beed7b78c00c9f2a70e14e5f7696ac0 100644
|
||||
--- a/gpu/config/gpu_driver_bug_list.json
|
||||
+++ b/gpu/config/gpu_driver_bug_list.json
|
||||
@@ -3818,6 +3818,31 @@
|
||||
"features": [
|
||||
"disable_d3d12_video_encoder"
|
||||
]
|
||||
+ },
|
||||
+ {
|
||||
+ "id": 470,
|
||||
+ "description": "Disable D3D12 video encoder on Windows versions older 11 24H2",
|
||||
+ "os": {
|
||||
+ "type": "win",
|
||||
+ "version": {
|
||||
+ "op": "<",
|
||||
+ "value": "10.0.26100.2033"
|
||||
+ }
|
||||
+ },
|
||||
+ "features": [
|
||||
+ "disable_d3d12_video_encoder"
|
||||
+ ]
|
||||
+ },
|
||||
+ {
|
||||
+ "id": 471,
|
||||
+ "description": "IMG drivers can sometimes reference previously bound complete framebuffers.",
|
||||
+ "os": {
|
||||
+ "type": "android"
|
||||
+ },
|
||||
+ "gl_vendor": "Imagination.*",
|
||||
+ "features": [
|
||||
+ "ensure_previous_framebuffer_not_deleted"
|
||||
+ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
|
||||
index 7f8b6e019f9b1986411b17c4ef1a2e863eb689f0..30ee7799cdd0a344e433e95cd74e4630a7c87aff 100644
|
||||
--- a/gpu/config/gpu_workaround_list.txt
|
||||
+++ b/gpu/config/gpu_workaround_list.txt
|
||||
@@ -78,6 +78,7 @@ dont_delete_source_texture_for_egl_image
|
||||
dont_use_loops_to_initialize_variables
|
||||
enable_bgra8_overlays_with_yuv_overlay_support
|
||||
enable_webgl_timer_query_extensions
|
||||
+ensure_previous_framebuffer_not_deleted
|
||||
etc1_power_of_two_only
|
||||
exit_on_context_lost
|
||||
flush_before_create_fence
|
||||
@@ -65,7 +65,7 @@ index 2748dd196fe1f56357348a204e24f0b8a28b97dd..5800dd00b47c657d9e6766f3fc5a3065
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
bool EscapeVirtualization(const base::FilePath& user_data_dir);
|
||||
diff --git a/chrome/browser/process_singleton_posix.cc b/chrome/browser/process_singleton_posix.cc
|
||||
index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0aa3183a0b 100644
|
||||
index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..144788ceadea85c9d1fae12d1ba4dbc1fc7cd699 100644
|
||||
--- a/chrome/browser/process_singleton_posix.cc
|
||||
+++ b/chrome/browser/process_singleton_posix.cc
|
||||
@@ -619,6 +619,7 @@ class ProcessSingleton::LinuxWatcher
|
||||
@@ -106,22 +106,41 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
const size_t kMinMessageLength = kStartToken.length() + 4;
|
||||
if (bytes_read_ < kMinMessageLength) {
|
||||
buf_[bytes_read_] = 0;
|
||||
@@ -745,10 +751,26 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
||||
@@ -745,10 +751,45 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
||||
tokens.erase(tokens.begin());
|
||||
tokens.erase(tokens.begin());
|
||||
|
||||
+ size_t num_args;
|
||||
+ base::StringToSizeT(tokens[0], &num_args);
|
||||
+ std::vector<std::string> command_line(tokens.begin() + 1, tokens.begin() + 1 + num_args);
|
||||
+ if (!base::StringToSizeT(tokens[0], &num_args) ||
|
||||
+ num_args > tokens.size() - 1) {
|
||||
+ LOG(ERROR) << "Invalid num_args in socket message";
|
||||
+ CleanupAndDeleteSelf();
|
||||
+ return;
|
||||
+ }
|
||||
+ std::vector<std::string> command_line(tokens.begin() + 1,
|
||||
+ tokens.begin() + 1 + num_args);
|
||||
+
|
||||
+ std::vector<uint8_t> additional_data;
|
||||
+ if (tokens.size() >= 3 + num_args) {
|
||||
+ // After consuming [num_args, argv...], two more tokens are needed for
|
||||
+ // additional data: [size, payload]. Subtract to avoid overflow when
|
||||
+ // num_args is large.
|
||||
+ if (tokens.size() - 1 - num_args >= 2) {
|
||||
+ size_t additional_data_size;
|
||||
+ base::StringToSizeT(tokens[1 + num_args], &additional_data_size);
|
||||
+ if (!base::StringToSizeT(tokens[1 + num_args], &additional_data_size)) {
|
||||
+ LOG(ERROR) << "Invalid additional_data_size in socket message";
|
||||
+ CleanupAndDeleteSelf();
|
||||
+ return;
|
||||
+ }
|
||||
+ std::string remaining_args = base::JoinString(
|
||||
+ base::span(tokens).subspan(2 + num_args),
|
||||
+ std::string(1, kTokenDelimiter));
|
||||
+ const auto adspan = base::as_byte_span(remaining_args).first(additional_data_size);
|
||||
+ if (additional_data_size > remaining_args.size()) {
|
||||
+ LOG(ERROR) << "additional_data_size exceeds payload length";
|
||||
+ CleanupAndDeleteSelf();
|
||||
+ return;
|
||||
+ }
|
||||
+ const auto adspan =
|
||||
+ base::as_byte_span(remaining_args).first(additional_data_size);
|
||||
+ additional_data.assign(adspan.begin(), adspan.end());
|
||||
+ }
|
||||
+
|
||||
@@ -134,7 +153,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
fd_watch_controller_.reset();
|
||||
|
||||
// LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
|
||||
@@ -777,8 +799,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
|
||||
@@ -777,8 +818,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
|
||||
//
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const base::FilePath& user_data_dir,
|
||||
@@ -145,7 +164,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
current_pid_(base::GetCurrentProcId()) {
|
||||
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
|
||||
lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
|
||||
@@ -899,7 +923,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
@@ -899,7 +942,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
sizeof(socket_timeout));
|
||||
|
||||
// Found another process, prepare our command line
|
||||
@@ -155,7 +174,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
std::string to_send(kStartToken);
|
||||
to_send.push_back(kTokenDelimiter);
|
||||
|
||||
@@ -909,11 +934,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
@@ -909,11 +953,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
to_send.append(current_dir.value());
|
||||
|
||||
const std::vector<std::string>& argv = cmd_line.argv();
|
||||
@@ -178,10 +197,18 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
if (!WriteToSocket(socket.fd(), to_send)) {
|
||||
// Try to kill the other process, because it might have been dead.
|
||||
diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc
|
||||
index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f947e6bfc 100644
|
||||
index ae659d84a5ae2f2e87ce288477506575f8d86839..274887d62ff8d008bb86815a11205fcaa5f2c2ff 100644
|
||||
--- a/chrome/browser/process_singleton_win.cc
|
||||
+++ b/chrome/browser/process_singleton_win.cc
|
||||
@@ -81,10 +81,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <shellapi.h>
|
||||
#include <stddef.h>
|
||||
|
||||
+#include "base/base64.h"
|
||||
#include "base/base_paths.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
@@ -81,10 +82,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
|
||||
bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
base::CommandLine* parsed_command_line,
|
||||
@@ -196,7 +223,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
static const int min_message_size = 7;
|
||||
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
|
||||
cds->cbData % sizeof(wchar_t) != 0) {
|
||||
@@ -134,6 +136,23 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
@@ -134,6 +137,25 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
const std::wstring cmd_line =
|
||||
msg.substr(second_null + 1, third_null - second_null);
|
||||
*parsed_command_line = base::CommandLine::FromString(cmd_line);
|
||||
@@ -209,18 +236,20 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ // Get the actual additional data.
|
||||
+ const std::wstring additional_data =
|
||||
+ msg.substr(third_null + 1, fourth_null - third_null);
|
||||
+ base::span<const uint8_t> additional_data_bytes =
|
||||
+ base::as_byte_span(additional_data);
|
||||
+ *parsed_additional_data = std::vector<uint8_t>(
|
||||
+ additional_data_bytes.begin(), additional_data_bytes.end());
|
||||
+ // Get the actual additional data. It is base64-encoded so it can
|
||||
+ // safely traverse the null-delimited wchar_t buffer.
|
||||
+ const std::wstring encoded_w =
|
||||
+ msg.substr(third_null + 1, fourth_null - third_null - 1);
|
||||
+ std::string encoded = base::WideToASCII(encoded_w);
|
||||
+ std::optional<std::vector<uint8_t>> decoded = base::Base64Decode(encoded);
|
||||
+ if (decoded) {
|
||||
+ *parsed_additional_data = std::move(*decoded);
|
||||
+ }
|
||||
+
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -155,13 +174,14 @@ bool ProcessLaunchNotification(
|
||||
@@ -155,13 +177,14 @@ bool ProcessLaunchNotification(
|
||||
|
||||
base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
|
||||
base::FilePath current_directory;
|
||||
@@ -238,7 +267,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -265,9 +285,11 @@ bool ProcessSingleton::EscapeVirtualization(
|
||||
@@ -265,9 +288,11 @@ bool ProcessSingleton::EscapeVirtualization(
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const std::string& program_name,
|
||||
const base::FilePath& user_data_dir,
|
||||
@@ -250,7 +279,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
program_name_(program_name),
|
||||
is_app_sandboxed_(is_app_sandboxed),
|
||||
is_virtualized_(false),
|
||||
@@ -294,7 +316,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
@@ -294,7 +319,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
@@ -260,10 +289,18 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
return PROCESS_NOTIFIED;
|
||||
case NotifyChromeResult::kFailed:
|
||||
diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc
|
||||
index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3e5b73942 100644
|
||||
index 594f3bc08a4385c177fb488123cef79448e94850..28e5a18a19718b2e748ada6882341413a1ab0705 100644
|
||||
--- a/chrome/browser/win/chrome_process_finder.cc
|
||||
+++ b/chrome/browser/win/chrome_process_finder.cc
|
||||
@@ -39,7 +39,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
+#include "base/base64.h"
|
||||
#include "base/check.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
@@ -39,7 +40,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
|
||||
return base::win::MessageWindow::FindWindow(user_data_dir.value());
|
||||
}
|
||||
|
||||
@@ -274,7 +311,7 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
|
||||
TRACE_EVENT0("startup", "AttemptToNotifyRunningChrome");
|
||||
|
||||
DCHECK(remote_window);
|
||||
@@ -70,12 +72,24 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
@@ -70,12 +73,22 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
new_command_line.AppendSwitch(switches::kSourceAppId);
|
||||
}
|
||||
// Send the command line to the remote chrome window.
|
||||
@@ -286,14 +323,12 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
|
||||
std::wstring_view{L"\0", 1}, new_command_line.GetCommandLineString(),
|
||||
std::wstring_view{L"\0", 1}});
|
||||
|
||||
+ size_t additional_data_size = additional_data.size_bytes();
|
||||
+ if (additional_data_size) {
|
||||
+ size_t padded_size = additional_data_size / sizeof(wchar_t);
|
||||
+ if (additional_data_size % sizeof(wchar_t) != 0) {
|
||||
+ padded_size++;
|
||||
+ }
|
||||
+ to_send.append(reinterpret_cast<const wchar_t*>(additional_data.data()),
|
||||
+ padded_size);
|
||||
+ if (!additional_data.empty()) {
|
||||
+ // Base64-encode so the payload survives the null-delimited wchar_t
|
||||
+ // framing; raw serialized bytes can contain 0x0000 sequences which
|
||||
+ // would otherwise terminate the field early.
|
||||
+ std::string encoded = base::Base64Encode(additional_data);
|
||||
+ to_send.append(base::ASCIIToWide(encoded));
|
||||
+ to_send.append(L"\0", 1); // Null separator.
|
||||
+ }
|
||||
+
|
||||
|
||||
80
patches/chromium/fix_mac_high_res_icons.patch
Normal file
80
patches/chromium/fix_mac_high_res_icons.patch
Normal file
@@ -0,0 +1,80 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Kanishk Ranjan <kanishkranjan17@gmail.com>
|
||||
Date: Thu, 11 Dec 2025 10:03:47 -0800
|
||||
Subject: Mac: Fix WebRTC window icon conversion via gfx::Image
|
||||
|
||||
The current WebRTC window picker implementation tries to manually convert
|
||||
NSImages to ImageSkia, but it does this incorrectly. As a result, the
|
||||
icons can appear corrupted or blank.
|
||||
|
||||
This CL resolves the issue by using gfx::Image for the conversion. This
|
||||
method offers a reliable and standard way to change an NSImage into an
|
||||
ImageSkia.
|
||||
|
||||
Feature-Flag: kUseGfxImageForMacWindowIcons
|
||||
Bug: 465028835
|
||||
Change-Id: Ib69bc151e9542d2402c1cd7d282e5f3298581862
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7239386
|
||||
Reviewed-by: Elad Alon <eladalon@chromium.org>
|
||||
Commit-Queue: Avi Drissman <avi@chromium.org>
|
||||
Reviewed-by: Avi Drissman <avi@chromium.org>
|
||||
Reviewed-by: Tove Petersson <tovep@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1557501}
|
||||
|
||||
diff --git a/AUTHORS b/AUTHORS
|
||||
index 7eb8f26120a23539b0780eb3f7e1d6a7ac52b102..fb0796cabdec4419e953306608a5b816ea1f2662 100644
|
||||
--- a/AUTHORS
|
||||
+++ b/AUTHORS
|
||||
@@ -824,6 +824,7 @@ Kamil Rytarowski <krytarowski@gmail.com>
|
||||
Kanaru Sato <i.am.kanaru.sato@gmail.com>
|
||||
Kangil Han <kangil.han@samsung.com>
|
||||
Kangyuan Shu <kangyuan.shu@intel.com>
|
||||
+Kanishk Ranjan <kanishkranjan17@gmail.com>
|
||||
Karan Thakkar <karanjthakkar@gmail.com>
|
||||
Karel Král <kralkareliv@gmail.com>
|
||||
Karl <karlpolicechromium@gmail.com>
|
||||
diff --git a/chrome/browser/media/webrtc/window_icon_util_mac.mm b/chrome/browser/media/webrtc/window_icon_util_mac.mm
|
||||
index 8bd216b9da864c9a8b82231ce6613cc120b32de7..c37b753c6aaf3b5036aacc74b310343fc7379188 100644
|
||||
--- a/chrome/browser/media/webrtc/window_icon_util_mac.mm
|
||||
+++ b/chrome/browser/media/webrtc/window_icon_util_mac.mm
|
||||
@@ -8,9 +8,19 @@
|
||||
|
||||
#include "base/apple/foundation_util.h"
|
||||
#include "base/apple/scoped_cftyperef.h"
|
||||
+#include "base/feature_list.h"
|
||||
+#include "ui/gfx/image/image.h"
|
||||
+#include "ui/gfx/image/image_skia.h"
|
||||
+
|
||||
+// TODO(crbug.com/465028835): Remove these includes and the fallback code once
|
||||
+// kUseGfxImageForMacWindowIcons is stable and the feature flag is removed
|
||||
#include "third_party/libyuv/include/libyuv/convert_argb.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
|
||||
+BASE_FEATURE(kUseGfxImageForMacWindowIcons,
|
||||
+ "UseGfxImageForMacWindowIcons",
|
||||
+ base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
+
|
||||
gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) {
|
||||
DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
|
||||
|
||||
@@ -35,6 +45,20 @@
|
||||
NSImage* icon_image =
|
||||
[[NSRunningApplication runningApplicationWithProcessIdentifier:pid] icon];
|
||||
|
||||
+ // TODO(crbug.com/465028835): Remove this feature check and the fallback
|
||||
+ // path once kUseGfxImageForMacWindowIcons is stable and the flag is removed
|
||||
+ if (base::FeatureList::IsEnabled(kUseGfxImageForMacWindowIcons)) {
|
||||
+ // The app may have terminated, resulting in a nil icon.
|
||||
+ if (!icon_image) {
|
||||
+ return gfx::ImageSkia();
|
||||
+ }
|
||||
+
|
||||
+ return gfx::Image(icon_image).AsImageSkia();
|
||||
+ }
|
||||
+
|
||||
+ // TODO(crbug.com/465028835): Remove the code below this line once
|
||||
+ // kUseGfxImageForMacWindowIcons is stable and the flag is removed.
|
||||
+
|
||||
// Icon's NSImage defaults to the smallest which can be only 32x32.
|
||||
NSRect proposed_rect = NSMakeRect(0, 0, 128, 128);
|
||||
CGImageRef cg_icon_image =
|
||||
@@ -1 +1,2 @@
|
||||
graphite_add_insertstatus_koutoforderrecording.patch
|
||||
cherry-pick-7911bee5d90e.patch
|
||||
|
||||
539
patches/skia/cherry-pick-7911bee5d90e.patch
Normal file
539
patches/skia/cherry-pick-7911bee5d90e.patch
Normal file
@@ -0,0 +1,539 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Greg Daniel <egdaniel@google.com>
|
||||
Date: Wed, 11 Mar 2026 15:29:58 -0400
|
||||
Subject: Make sure we are getting the correct atlas for glyph mask format.
|
||||
|
||||
Bug: b/491421267
|
||||
Change-Id: I4eacd46599eca2df8c10a3fc894b9ce890fae1e2
|
||||
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1184076
|
||||
Commit-Queue: Greg Daniel <egdaniel@google.com>
|
||||
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
|
||||
(cherry picked from commit 0cab3e4ee34b3bca6ba7df676639d73ffe4b2135)
|
||||
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1184917
|
||||
|
||||
diff --git a/bench/GlyphQuadFillBench.cpp b/bench/GlyphQuadFillBench.cpp
|
||||
index 6793512e216b00e1f8112f8e681eecf5beee8fe8..4fd0965185f8bab5a55ec63329bf6aa36ad56ed0 100644
|
||||
--- a/bench/GlyphQuadFillBench.cpp
|
||||
+++ b/bench/GlyphQuadFillBench.cpp
|
||||
@@ -68,7 +68,7 @@ class DirectMaskGlyphVertexFillBenchmark : public Benchmark {
|
||||
const sktext::gpu::AtlasSubRun* subRun =
|
||||
sktext::gpu::TextBlobTools::FirstSubRun(fBlob.get());
|
||||
SkASSERT_RELEASE(subRun);
|
||||
- subRun->testingOnly_packedGlyphIDToGlyph(&fCache);
|
||||
+ subRun->testingOnly_packedGlyphIDToGlyph(&fCache, subRun->maskFormat());
|
||||
fVertices.reset(new char[subRun->vertexStride(drawMatrix) * subRun->glyphCount() * 4]);
|
||||
}
|
||||
|
||||
diff --git a/gn/tests.gni b/gn/tests.gni
|
||||
index 8ae89364ce33f62ced5e8ff5b417a0cf69a3afb1..6286969c91fa9dff8d1e83413ab5b9fd514c5ae9 100644
|
||||
--- a/gn/tests.gni
|
||||
+++ b/gn/tests.gni
|
||||
@@ -424,6 +424,7 @@ pathops_tests_sources = [
|
||||
ganesh_tests_sources = [
|
||||
"$_tests/AdvancedBlendTest.cpp",
|
||||
"$_tests/ApplyGammaTest.cpp",
|
||||
+ "$_tests/AtlasOobTest.cpp",
|
||||
"$_tests/BackendAllocationTest.cpp",
|
||||
"$_tests/BackendSurfaceMutableStateTest.cpp",
|
||||
"$_tests/BlendTest.cpp",
|
||||
diff --git a/src/gpu/ganesh/text/GrAtlasManager.cpp b/src/gpu/ganesh/text/GrAtlasManager.cpp
|
||||
index 403bfe274e56293bfe2382b02525ae742ba541a7..1e7d9aa0ce14f19e09d79544730c6aa922ae37d6 100644
|
||||
--- a/src/gpu/ganesh/text/GrAtlasManager.cpp
|
||||
+++ b/src/gpu/ganesh/text/GrAtlasManager.cpp
|
||||
@@ -178,8 +178,7 @@ GrDrawOpAtlas::ErrorCode GrAtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph,
|
||||
}
|
||||
SkASSERT(glyph != nullptr);
|
||||
|
||||
- MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat());
|
||||
- MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat);
|
||||
+ MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyph->fGlyphEntryKey.fFormat);
|
||||
int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat);
|
||||
|
||||
int padding;
|
||||
@@ -299,7 +298,7 @@ std::tuple<bool, int> GlyphVector::regenerateAtlasForGanesh(
|
||||
|
||||
uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat);
|
||||
|
||||
- this->packedGlyphIDToGlyph(target->strikeCache());
|
||||
+ this->packedGlyphIDToGlyph(target->strikeCache(), maskFormat);
|
||||
|
||||
if (fAtlasGeneration != currentAtlasGen) {
|
||||
// Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
|
||||
@@ -316,9 +315,10 @@ std::tuple<bool, int> GlyphVector::regenerateAtlasForGanesh(
|
||||
for (const Variant& variant : glyphs) {
|
||||
Glyph* gpuGlyph = variant.glyph;
|
||||
SkASSERT(gpuGlyph != nullptr);
|
||||
-
|
||||
+ SkASSERT(gpuGlyph->fGlyphEntryKey.fFormat == maskFormat);
|
||||
if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) {
|
||||
- const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID);
|
||||
+ const SkGlyph& skGlyph =
|
||||
+ *metricsAndImages.glyph(gpuGlyph->fGlyphEntryKey.fPackedID);
|
||||
auto code = atlasManager->addGlyphToAtlas(
|
||||
skGlyph, gpuGlyph, srcPadding, target->resourceProvider(), uploadTarget);
|
||||
if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
|
||||
diff --git a/src/gpu/graphite/Device.cpp b/src/gpu/graphite/Device.cpp
|
||||
index 1163eacd741d059b5a782112d9dbeed7080e3207..b069ba5e84bf113f3e1bcff1cd7c8e9ef570722d 100644
|
||||
--- a/src/gpu/graphite/Device.cpp
|
||||
+++ b/src/gpu/graphite/Device.cpp
|
||||
@@ -1427,6 +1427,7 @@ void Device::drawAtlasSubRun(const sktext::gpu::AtlasSubRun* subRun,
|
||||
int padding) {
|
||||
return glyphs->regenerateAtlasForGraphite(begin, end, maskFormat, padding, fRecorder);
|
||||
};
|
||||
+
|
||||
for (int subRunCursor = 0; subRunCursor < subRunEnd;) {
|
||||
// For the remainder of the run, add any atlas uploads to the Recorder's TextAtlasManager
|
||||
auto[ok, glyphsRegenerated] = subRun->regenerateAtlas(subRunCursor, subRunEnd,
|
||||
diff --git a/src/gpu/graphite/text/TextAtlasManager.cpp b/src/gpu/graphite/text/TextAtlasManager.cpp
|
||||
index 6602a76c150bff077666fb91b990d3e45d528ce2..cbb51a66846922995912c3159afba879a2487313 100644
|
||||
--- a/src/gpu/graphite/text/TextAtlasManager.cpp
|
||||
+++ b/src/gpu/graphite/text/TextAtlasManager.cpp
|
||||
@@ -207,8 +207,7 @@ DrawAtlas::ErrorCode TextAtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph,
|
||||
}
|
||||
SkASSERT(glyph != nullptr);
|
||||
|
||||
- MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat());
|
||||
- MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat);
|
||||
+ MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyph->fGlyphEntryKey.fFormat);
|
||||
int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat);
|
||||
|
||||
int padding;
|
||||
@@ -359,7 +358,7 @@ std::tuple<bool, int> GlyphVector::regenerateAtlasForGraphite(int begin,
|
||||
|
||||
uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat);
|
||||
|
||||
- this->packedGlyphIDToGlyph(recorder->priv().strikeCache());
|
||||
+ this->packedGlyphIDToGlyph(recorder->priv().strikeCache(), maskFormat);
|
||||
|
||||
if (fAtlasGeneration != currentAtlasGen) {
|
||||
// Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
|
||||
@@ -375,9 +374,10 @@ std::tuple<bool, int> GlyphVector::regenerateAtlasForGraphite(int begin,
|
||||
for (const Variant& variant : glyphs) {
|
||||
Glyph* gpuGlyph = variant.glyph;
|
||||
SkASSERT(gpuGlyph != nullptr);
|
||||
-
|
||||
+ SkASSERT(gpuGlyph->fGlyphEntryKey.fFormat == maskFormat);
|
||||
if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) {
|
||||
- const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID);
|
||||
+ const SkGlyph& skGlyph =
|
||||
+ *metricsAndImages.glyph(gpuGlyph->fGlyphEntryKey.fPackedID);
|
||||
auto code = atlasManager->addGlyphToAtlas(skGlyph, gpuGlyph, srcPadding);
|
||||
if (code != DrawAtlas::ErrorCode::kSucceeded) {
|
||||
success = code != DrawAtlas::ErrorCode::kError;
|
||||
diff --git a/src/text/gpu/Glyph.h b/src/text/gpu/Glyph.h
|
||||
index 821612d68cecfe9dae9518e376e09fdf233395ad..7942006a563bcab925ea2129ab6f6beea438a4c8 100644
|
||||
--- a/src/text/gpu/Glyph.h
|
||||
+++ b/src/text/gpu/Glyph.h
|
||||
@@ -14,6 +14,25 @@
|
||||
|
||||
namespace sktext::gpu {
|
||||
|
||||
+struct GlyphEntryKey {
|
||||
+ explicit GlyphEntryKey(SkPackedGlyphID id, skgpu::MaskFormat format)
|
||||
+ : fPackedID(id), fFormat(format) {}
|
||||
+
|
||||
+ const SkPackedGlyphID fPackedID;
|
||||
+ skgpu::MaskFormat fFormat;
|
||||
+
|
||||
+ bool operator==(const GlyphEntryKey& that) const {
|
||||
+ return fPackedID == that.fPackedID && fFormat == that.fFormat;
|
||||
+ }
|
||||
+ bool operator!=(const GlyphEntryKey& that) const {
|
||||
+ return !(*this == that);
|
||||
+ }
|
||||
+
|
||||
+ uint32_t hash() const {
|
||||
+ return fPackedID.hash();
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
class Glyph {
|
||||
public:
|
||||
static skgpu::MaskFormat FormatFromSkGlyph(SkMask::Format format) {
|
||||
@@ -34,10 +53,11 @@ public:
|
||||
SkUNREACHABLE;
|
||||
}
|
||||
|
||||
- explicit Glyph(SkPackedGlyphID packedGlyphID) : fPackedID(packedGlyphID) {}
|
||||
+ explicit Glyph(SkPackedGlyphID packedGlyphID, skgpu::MaskFormat format)
|
||||
+ : fGlyphEntryKey(packedGlyphID, format) {}
|
||||
|
||||
- const SkPackedGlyphID fPackedID;
|
||||
- skgpu::AtlasLocator fAtlasLocator;
|
||||
+ const GlyphEntryKey fGlyphEntryKey;
|
||||
+ skgpu::AtlasLocator fAtlasLocator;
|
||||
};
|
||||
|
||||
} // namespace sktext::gpu
|
||||
diff --git a/src/text/gpu/GlyphVector.cpp b/src/text/gpu/GlyphVector.cpp
|
||||
index 2a8e85f926aa547169f4b85372e9d3fb99816956..7bec7a0b77d8560d5ef978281edd7df6c45cb56f 100644
|
||||
--- a/src/text/gpu/GlyphVector.cpp
|
||||
+++ b/src/text/gpu/GlyphVector.cpp
|
||||
@@ -99,14 +99,14 @@ SkSpan<const Glyph*> GlyphVector::glyphs() const {
|
||||
|
||||
// packedGlyphIDToGlyph must be run in single-threaded mode.
|
||||
// If fSkStrike is not sk_sp<SkStrike> then the conversion to Glyph* has not happened.
|
||||
-void GlyphVector::packedGlyphIDToGlyph(StrikeCache* cache) {
|
||||
+void GlyphVector::packedGlyphIDToGlyph(StrikeCache* cache, MaskFormat maskFormat) {
|
||||
if (fTextStrike == nullptr) {
|
||||
SkStrike* strike = fStrikePromise.strike();
|
||||
fTextStrike = cache->findOrCreateStrike(strike->strikeSpec());
|
||||
|
||||
// Get all the atlas locations for each glyph.
|
||||
for (Variant& variant : fGlyphs) {
|
||||
- variant.glyph = fTextStrike->getGlyph(variant.packedGlyphID);
|
||||
+ variant.glyph = fTextStrike->getGlyph(variant.packedGlyphID, maskFormat);
|
||||
}
|
||||
|
||||
// This must be pinned for the Atlas filling to work.
|
||||
diff --git a/src/text/gpu/GlyphVector.h b/src/text/gpu/GlyphVector.h
|
||||
index 42b92a93f70cc6d86d0a87dd07c2244e0da1281c..1eec6327d38fb4472b027faae68eecb9ad7509d7 100644
|
||||
--- a/src/text/gpu/GlyphVector.h
|
||||
+++ b/src/text/gpu/GlyphVector.h
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
// the sub runs.
|
||||
int unflattenSize() const { return GlyphVectorSize(fGlyphs.size()); }
|
||||
|
||||
- void packedGlyphIDToGlyph(StrikeCache* cache);
|
||||
+ void packedGlyphIDToGlyph(StrikeCache* cache, skgpu::MaskFormat);
|
||||
|
||||
static size_t GlyphVectorSize(size_t count) {
|
||||
return sizeof(Variant) * count;
|
||||
diff --git a/src/text/gpu/StrikeCache.cpp b/src/text/gpu/StrikeCache.cpp
|
||||
index add3127c92fdbfe56d6b56209a2235ce5a9f5acb..19df48329fd500f8682669ec96eb883b58243fdd 100644
|
||||
--- a/src/text/gpu/StrikeCache.cpp
|
||||
+++ b/src/text/gpu/StrikeCache.cpp
|
||||
@@ -207,10 +207,11 @@ TextStrike::TextStrike(StrikeCache* strikeCache, const SkStrikeSpec& strikeSpec)
|
||||
: fStrikeCache(strikeCache)
|
||||
, fStrikeSpec{strikeSpec} {}
|
||||
|
||||
-Glyph* TextStrike::getGlyph(SkPackedGlyphID packedGlyphID) {
|
||||
- Glyph* glyph = fCache.findOrNull(packedGlyphID);
|
||||
+Glyph* TextStrike::getGlyph(SkPackedGlyphID packedGlyphID, skgpu::MaskFormat format) {
|
||||
+ GlyphEntryKey localKey(packedGlyphID, format);
|
||||
+ Glyph* glyph = fCache.findOrNull(localKey);
|
||||
if (glyph == nullptr) {
|
||||
- glyph = fAlloc.make<Glyph>(packedGlyphID);
|
||||
+ glyph = fAlloc.make<Glyph>(packedGlyphID, format);
|
||||
fCache.set(glyph);
|
||||
fMemoryUsed += sizeof(Glyph);
|
||||
if (!fRemoved) {
|
||||
@@ -220,11 +221,11 @@ Glyph* TextStrike::getGlyph(SkPackedGlyphID packedGlyphID) {
|
||||
return glyph;
|
||||
}
|
||||
|
||||
-const SkPackedGlyphID& TextStrike::HashTraits::GetKey(const Glyph* glyph) {
|
||||
- return glyph->fPackedID;
|
||||
+const GlyphEntryKey& TextStrike::HashTraits::GetKey(const Glyph* glyph) {
|
||||
+ return glyph->fGlyphEntryKey;
|
||||
}
|
||||
|
||||
-uint32_t TextStrike::HashTraits::Hash(SkPackedGlyphID key) {
|
||||
+uint32_t TextStrike::HashTraits::Hash(GlyphEntryKey key) {
|
||||
return key.hash();
|
||||
}
|
||||
|
||||
diff --git a/src/text/gpu/StrikeCache.h b/src/text/gpu/StrikeCache.h
|
||||
index 007c45c6c6feecba3ff031ba3939ad2402e082b9..014afd5286602e3e049d8e48ae328273e599dc41 100644
|
||||
--- a/src/text/gpu/StrikeCache.h
|
||||
+++ b/src/text/gpu/StrikeCache.h
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "src/core/SkDescriptor.h"
|
||||
#include "src/core/SkStrikeSpec.h"
|
||||
#include "src/core/SkTHash.h"
|
||||
+#include "src/gpu/AtlasTypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -32,6 +33,7 @@ struct SkPackedGlyphID;
|
||||
namespace sktext::gpu {
|
||||
|
||||
class Glyph;
|
||||
+struct GlyphEntryKey;
|
||||
class StrikeCache;
|
||||
|
||||
// The TextStrike manages an SkArenaAlloc for Glyphs. The SkStrike is what actually creates
|
||||
@@ -43,7 +45,7 @@ public:
|
||||
TextStrike(StrikeCache* strikeCache,
|
||||
const SkStrikeSpec& strikeSpec);
|
||||
|
||||
- Glyph* getGlyph(SkPackedGlyphID);
|
||||
+ Glyph* getGlyph(SkPackedGlyphID, skgpu::MaskFormat format);
|
||||
const SkStrikeSpec& strikeSpec() const { return fStrikeSpec; }
|
||||
const SkDescriptor& getDescriptor() const { return fStrikeSpec.descriptor(); }
|
||||
|
||||
@@ -54,11 +56,11 @@ private:
|
||||
const SkStrikeSpec fStrikeSpec;
|
||||
|
||||
struct HashTraits {
|
||||
- static const SkPackedGlyphID& GetKey(const Glyph* glyph);
|
||||
- static uint32_t Hash(SkPackedGlyphID key);
|
||||
+ static const GlyphEntryKey& GetKey(const Glyph* glyph);
|
||||
+ static uint32_t Hash(GlyphEntryKey key);
|
||||
};
|
||||
// Map SkPackedGlyphID -> Glyph*.
|
||||
- skia_private::THashTable<Glyph*, SkPackedGlyphID, HashTraits> fCache;
|
||||
+ skia_private::THashTable<Glyph*, GlyphEntryKey, HashTraits> fCache;
|
||||
|
||||
// Store for the glyph information.
|
||||
SkArenaAlloc fAlloc{512};
|
||||
diff --git a/src/text/gpu/SubRunContainer.cpp b/src/text/gpu/SubRunContainer.cpp
|
||||
index 3a061a2012cd99de9ee4b3674f78ae99e0385d6c..a19460c82593c6713c047ab19e71caa27e375a6d 100644
|
||||
--- a/src/text/gpu/SubRunContainer.cpp
|
||||
+++ b/src/text/gpu/SubRunContainer.cpp
|
||||
@@ -651,8 +651,9 @@ public:
|
||||
|
||||
int glyphSrcPadding() const override { return 0; }
|
||||
|
||||
- void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache) const override {
|
||||
- fGlyphs.packedGlyphIDToGlyph(cache);
|
||||
+ void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache,
|
||||
+ skgpu::MaskFormat maskFormat) const override {
|
||||
+ fGlyphs.packedGlyphIDToGlyph(cache, maskFormat);
|
||||
}
|
||||
|
||||
std::tuple<bool, SkRect> deviceRectAndNeedsTransform(
|
||||
@@ -755,8 +756,9 @@ public:
|
||||
|
||||
const AtlasSubRun* testingOnly_atlasSubRun() const override { return this; }
|
||||
|
||||
- void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const override {
|
||||
- fGlyphs.packedGlyphIDToGlyph(cache);
|
||||
+ void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache,
|
||||
+ skgpu::MaskFormat maskFormat) const override {
|
||||
+ fGlyphs.packedGlyphIDToGlyph(cache, maskFormat);
|
||||
}
|
||||
|
||||
int glyphSrcPadding() const override { return 1; }
|
||||
@@ -884,8 +886,9 @@ public:
|
||||
|
||||
const AtlasSubRun* testingOnly_atlasSubRun() const override { return this; }
|
||||
|
||||
- void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const override {
|
||||
- fGlyphs.packedGlyphIDToGlyph(cache);
|
||||
+ void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache,
|
||||
+ skgpu::MaskFormat maskFormat) const override {
|
||||
+ fGlyphs.packedGlyphIDToGlyph(cache, maskFormat);
|
||||
}
|
||||
|
||||
int glyphSrcPadding() const override { return SK_DistanceFieldInset; }
|
||||
diff --git a/src/text/gpu/SubRunContainer.h b/src/text/gpu/SubRunContainer.h
|
||||
index 2573dbb3964e9ab2cc0e276b60d4ab4f9804f0d9..4d1a3c8c2d55015d3d351d322ef039c45be2a398 100644
|
||||
--- a/src/text/gpu/SubRunContainer.h
|
||||
+++ b/src/text/gpu/SubRunContainer.h
|
||||
@@ -167,7 +167,7 @@ public:
|
||||
|
||||
const VertexFiller& vertexFiller() const { return fVertexFiller; }
|
||||
|
||||
- virtual void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache) const = 0;
|
||||
+ virtual void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache, skgpu::MaskFormat) const = 0;
|
||||
|
||||
protected:
|
||||
const VertexFiller fVertexFiller;
|
||||
diff --git a/tests/AtlasOobTest.cpp b/tests/AtlasOobTest.cpp
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..4e6fb02ee6af6543df285d8112f1a2ced5bd9ac9
|
||||
--- /dev/null
|
||||
+++ b/tests/AtlasOobTest.cpp
|
||||
@@ -0,0 +1,201 @@
|
||||
+/*
|
||||
+ * Copyright 2026 Google LLC
|
||||
+ *
|
||||
+ * Use of this source code is governed by a BSD-style license that can be
|
||||
+ * found in the LICENSE file.
|
||||
+ */
|
||||
+#include "include/core/SkCanvas.h"
|
||||
+#include "include/core/SkGraphics.h"
|
||||
+#include "include/core/SkSerialProcs.h"
|
||||
+#include "include/core/SkSurface.h"
|
||||
+#include "include/private/chromium/SkChromeRemoteGlyphCache.h"
|
||||
+#include "include/private/chromium/Slug.h"
|
||||
+#include "src/core/SkDescriptor.h"
|
||||
+#include "src/core/SkReadBuffer.h"
|
||||
+#include "src/core/SkTypeface_remote.h"
|
||||
+#include "src/core/SkWriteBuffer.h"
|
||||
+#include "src/gpu/AtlasTypes.h"
|
||||
+#include "tests/CtsEnforcement.h"
|
||||
+#include "tests/Test.h"
|
||||
+#include "tools/ToolUtils.h"
|
||||
+
|
||||
+#if defined(SK_GANESH)
|
||||
+#include "include/gpu/ganesh/GrDirectContext.h"
|
||||
+#include "include/gpu/ganesh/SkSurfaceGanesh.h"
|
||||
+#endif
|
||||
+
|
||||
+#if defined(SK_GRAPHITE)
|
||||
+#include "include/gpu/graphite/Context.h"
|
||||
+#include "include/gpu/graphite/Surface.h"
|
||||
+#include "tools/graphite/GraphiteTestContext.h"
|
||||
+#endif // defined(SK_GRAPHITE)
|
||||
+
|
||||
+#include <vector>
|
||||
+#include <cstring>
|
||||
+
|
||||
+namespace {
|
||||
+class FakeDiscardableManager : public SkStrikeClient::DiscardableHandleManager {
|
||||
+public:
|
||||
+ bool deleteHandle(SkDiscardableHandleId) override { return false; }
|
||||
+ void notifyCacheMiss(SkStrikeClient::CacheMissType, int) override {}
|
||||
+ void notifyReadFailure(const ReadFailureData&) override {}
|
||||
+ void assertHandleValid(SkDiscardableHandleId) override {}
|
||||
+};
|
||||
+
|
||||
+unsigned char kStrikeData[] = {
|
||||
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x07, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65,
|
||||
+ 0x00, 0x00, 0x00, 0x65, 0xd8, 0x50, 0xda, 0x99, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
+ 0x63, 0x65, 0x72, 0x73, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x80, 0x41,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
+ 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x64, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
+ 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x66, 0x86, 0x07, 0xc2, 0x42,
|
||||
+ 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x65, 0x72, 0x73, 0x38, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
+ 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00
|
||||
+};
|
||||
+
|
||||
+unsigned char kDrawSlugOp[] = {
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x41,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f,
|
||||
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41,
|
||||
+ 0x00, 0x00, 0x00, 0x41, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x86, 0x07, 0xc2, 0x42, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x65, 0x72, 0x73,
|
||||
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
+ 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
+};
|
||||
+
|
||||
+} // namespace
|
||||
+
|
||||
+// TODO: We expect this test to correctly hit an SkUnreachable and then crash. That does not work
|
||||
+// with our current testing framework because we have no to "expect" a crash. So for now we will
|
||||
+// land this test with only the valid loop enabled, but to test this is working locally, you should
|
||||
+// change the loop to have both iterations.
|
||||
+static void run_atlas_oob_test(skiatest::Reporter* reporter, SkCanvas* canvas) {
|
||||
+ auto discardableManager = sk_make_sp<FakeDiscardableManager>();
|
||||
+ SkStrikeClient client(discardableManager, false);
|
||||
+
|
||||
+ // 1. Prepare Strike Data
|
||||
+ if (!client.readStrikeData(kStrikeData, sizeof(kStrikeData))) {
|
||||
+ REPORTER_ASSERT(reporter, false, "Failed to read initial strike data");
|
||||
+ }
|
||||
+
|
||||
+ // 2. Prepare and Execute DrawSlug ops
|
||||
+ SkPaint paint;
|
||||
+ for (int idx = 0; idx < 1; ++idx) {
|
||||
+// for (int idx = 0; idx < 2; ++idx) {
|
||||
+ if (idx == 0) {
|
||||
+ kDrawSlugOp[0x48] = (unsigned char)skgpu::MaskFormat::kARGB;
|
||||
+ } else if (idx == 1) {
|
||||
+ kDrawSlugOp[0x48] = (unsigned char)skgpu::MaskFormat::kA8;
|
||||
+ }
|
||||
+ kDrawSlugOp[0xd8] = SkMask::kARGB32_Format;
|
||||
+ kDrawSlugOp[0xe0] = 0x99;
|
||||
+
|
||||
+ auto slug = client.deserializeSlugForTest(kDrawSlugOp, sizeof(kDrawSlugOp));
|
||||
+ if (slug) {
|
||||
+ slug->draw(canvas, paint);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
+
|
||||
+#if defined(SK_GANESH)
|
||||
+DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(Atlas_Oob_ganesh, reporter, ctxInfo, CtsEnforcement::kNextRelease) {
|
||||
+ auto dContext = ctxInfo.directContext();
|
||||
+ SkImageInfo info = SkImageInfo::MakeN32Premul(1024, 1024);
|
||||
+ auto surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info);
|
||||
+ if (!surface) return;
|
||||
+ auto canvas = surface->getCanvas();
|
||||
+
|
||||
+ run_atlas_oob_test(reporter, canvas);
|
||||
+
|
||||
+ dContext->flushAndSubmit();
|
||||
+}
|
||||
+#endif // defined(SK_GANESH)
|
||||
+
|
||||
+#if defined(SK_GRAPHITE)
|
||||
+DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(Atlas_Oob_graphite, reporter, context, CtsEnforcement::kNextRelease) {
|
||||
+ using namespace skgpu::graphite;
|
||||
+ std::unique_ptr<Recorder> recorder = context->makeRecorder();
|
||||
+ SkImageInfo info = SkImageInfo::MakeN32Premul(1024, 1024);
|
||||
+ auto surface = SkSurfaces::RenderTarget(recorder.get(), info);
|
||||
+ if (!surface) return;
|
||||
+ auto canvas = surface->getCanvas();
|
||||
+
|
||||
+ run_atlas_oob_test(reporter, canvas);
|
||||
+
|
||||
+ std::unique_ptr<Recording> recording = recorder->snap();
|
||||
+ InsertRecordingInfo recordingInfo;
|
||||
+ recordingInfo.fRecording = recording.get();
|
||||
+ context->insertRecording(recordingInfo);
|
||||
+ context->submit();
|
||||
+}
|
||||
+#endif // defined(SK_GRAPHITE)
|
||||
@@ -9,4 +9,4 @@ refactor_use_non-deprecated_nskeyedarchiver_apis.patch
|
||||
chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch
|
||||
fix_crash_when_process_to_extract_zip_cannot_be_launched.patch
|
||||
use_uttype_class_instead_of_deprecated_uttypeconformsto.patch
|
||||
fix_clean_up_old_staged_updates_before_downloading_new_update.patch
|
||||
fix_clean_up_orphaned_staged_updates_before_downloading_new_update.patch
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andy Locascio <loc@anthropic.com>
|
||||
Date: Tue, 6 Jan 2026 08:23:03 -0800
|
||||
Subject: fix: clean up old staged updates before downloading new update
|
||||
|
||||
When checkForUpdates() is called while an update is already staged,
|
||||
Squirrel creates a new temporary directory for the download without
|
||||
cleaning up the old one. This can lead to significant disk usage if
|
||||
the app keeps checking for updates without restarting.
|
||||
|
||||
This change adds a force parameter to pruneUpdateDirectories that
|
||||
bypasses the AwaitingRelaunch state check. This is called before
|
||||
creating a new temp directory, ensuring old staged updates are
|
||||
cleaned up when a new download starts.
|
||||
|
||||
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
|
||||
index d156616e81e6f25a3bded30e6216b8fc311f31bc..6cd4346bf43b191147aff819cb93387e71275a46 100644
|
||||
--- a/Squirrel/SQRLUpdater.m
|
||||
+++ b/Squirrel/SQRLUpdater.m
|
||||
@@ -543,11 +543,17 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
|
||||
#pragma mark File Management
|
||||
|
||||
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
|
||||
- return [[[RACSignal
|
||||
+ // Clean up any old staged update directories before creating a new one.
|
||||
+ // This prevents disk usage from growing when checkForUpdates() is called
|
||||
+ // multiple times without the app restarting.
|
||||
+ return [[[[[self
|
||||
+ pruneUpdateDirectoriesWithForce:YES]
|
||||
+ ignoreValues]
|
||||
+ concat:[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
- }]
|
||||
+ }]]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
|
||||
@@ -643,7 +649,7 @@ - (BOOL)isRunningOnReadOnlyVolume {
|
||||
|
||||
- (RACSignal *)performHousekeeping {
|
||||
return [[RACSignal
|
||||
- merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]
|
||||
+ merge:@[ [self pruneUpdateDirectoriesWithForce:NO], [self truncateLogs] ]]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error doing housekeeping: %@", error);
|
||||
return [RACSignal empty];
|
||||
@@ -658,11 +664,12 @@ - (RACSignal *)performHousekeeping {
|
||||
///
|
||||
/// Sends each removed directory then completes, or errors, on an unspecified
|
||||
/// thread.
|
||||
-- (RACSignal *)pruneUpdateDirectories {
|
||||
+- (RACSignal *)pruneUpdateDirectoriesWithForce:(BOOL)force {
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
- // If we already have updates downloaded we don't wanna prune them.
|
||||
- if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
+ // If we already have updates downloaded we don't wanna prune them,
|
||||
+ // unless force is YES (used when starting a new download).
|
||||
+ if (!force && self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
@@ -0,0 +1,130 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andy Locascio <loc@anthropic.com>
|
||||
Date: Tue, 6 Jan 2026 08:23:03 -0800
|
||||
Subject: fix: clean up orphaned staged updates before downloading new update
|
||||
|
||||
When checkForUpdates() is called while an update is already staged,
|
||||
Squirrel creates a new temporary directory for the download without
|
||||
cleaning up the old one. This can lead to significant disk usage if
|
||||
the app keeps checking for updates without restarting.
|
||||
|
||||
This change adds a pruneOrphanedUpdateDirectories step before creating
|
||||
a new temp directory. Unlike a blanket prune, this reads the current
|
||||
ShipItState.plist and preserves the directory it references, deleting
|
||||
only truly orphaned update directories. This keeps the on-disk
|
||||
footprint bounded (at most 2 dirs) while ensuring quitAndInstall
|
||||
remains safe to call even when a new check is in progress.
|
||||
|
||||
Refs https://github.com/electron/electron/issues/50200
|
||||
|
||||
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
|
||||
index d156616e81e6f25a3bded30e6216b8fc311f31bc..41856e5754228d33982db72f97f2ff241615a357 100644
|
||||
--- a/Squirrel/SQRLUpdater.m
|
||||
+++ b/Squirrel/SQRLUpdater.m
|
||||
@@ -543,11 +543,19 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
|
||||
#pragma mark File Management
|
||||
|
||||
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
|
||||
- return [[[RACSignal
|
||||
+ // Clean up any orphaned update directories before creating a new one.
|
||||
+ // This prevents disk usage from growing when checkForUpdates() is called
|
||||
+ // multiple times without the app restarting. The currently staged update
|
||||
+ // (referenced by ShipItState.plist) is always preserved so quitAndInstall
|
||||
+ // remains safe to call while a new check is in progress.
|
||||
+ return [[[[[self
|
||||
+ pruneOrphanedUpdateDirectories]
|
||||
+ ignoreValues]
|
||||
+ concat:[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
- }]
|
||||
+ }]]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
|
||||
@@ -668,25 +676,68 @@ - (RACSignal *)pruneUpdateDirectories {
|
||||
return [directoryManager storageURL];
|
||||
}]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
- NSFileManager *manager = [[NSFileManager alloc] init];
|
||||
- NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
|
||||
- NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
|
||||
- return YES;
|
||||
- }];
|
||||
+ return [self removeUpdateDirectoriesInStorageURL:storageURL excludingURL:nil];
|
||||
+ }]
|
||||
+ setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
+}
|
||||
|
||||
- return [[enumerator.rac_sequence.signal
|
||||
- filter:^(NSURL *enumeratedURL) {
|
||||
- NSString *name = enumeratedURL.lastPathComponent;
|
||||
- return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
|
||||
- }]
|
||||
- doNext:^(NSURL *directoryURL) {
|
||||
- NSError *error = nil;
|
||||
- if (![manager removeItemAtURL:directoryURL error:&error]) {
|
||||
- NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
|
||||
- }
|
||||
+/// Lazily removes orphaned temporary directories upon subscription, always
|
||||
+/// preserving the directory currently referenced by ShipItState.plist so that
|
||||
+/// quitAndInstall remains safe to call mid-check.
|
||||
+///
|
||||
+/// Safe to call in any state. Sends each removed directory then completes on
|
||||
+/// an unspecified thread. Errors reading the staged request are swallowed
|
||||
+/// (treated as "nothing staged").
|
||||
+- (RACSignal *)pruneOrphanedUpdateDirectories {
|
||||
+ return [[[[[SQRLShipItRequest
|
||||
+ readUsingURL:self.shipItStateURL]
|
||||
+ map:^(SQRLShipItRequest *request) {
|
||||
+ // The request holds the URL to the staged .app bundle; its parent
|
||||
+ // is the update.XXXXXXX directory we must preserve.
|
||||
+ return [request.updateBundleURL URLByDeletingLastPathComponent];
|
||||
+ }]
|
||||
+ catch:^(NSError *error) {
|
||||
+ // No staged request (or unreadable) — nothing to preserve.
|
||||
+ return [RACSignal return:nil];
|
||||
+ }]
|
||||
+ flattenMap:^(NSURL *stagedDirectoryURL) {
|
||||
+ SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
+ return [[directoryManager storageURL]
|
||||
+ flattenMap:^(NSURL *storageURL) {
|
||||
+ return [self removeUpdateDirectoriesInStorageURL:storageURL excludingURL:stagedDirectoryURL];
|
||||
}];
|
||||
}]
|
||||
- setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
+ setNameWithFormat:@"%@ -pruneOrphanedUpdateDirectories", self];
|
||||
+}
|
||||
+
|
||||
+/// Shared enumerate-and-delete logic for update temp directories.
|
||||
+///
|
||||
+/// storageURL - The Squirrel storage root to enumerate. Must not be nil.
|
||||
+/// excludedURL - Directory to skip (compared by standardized path). May be nil.
|
||||
+- (RACSignal *)removeUpdateDirectoriesInStorageURL:(NSURL *)storageURL excludingURL:(NSURL *)excludedURL {
|
||||
+ NSParameterAssert(storageURL != nil);
|
||||
+
|
||||
+ NSFileManager *manager = [[NSFileManager alloc] init];
|
||||
+ NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
|
||||
+ NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
|
||||
+ return YES;
|
||||
+ }];
|
||||
+
|
||||
+ NSString *excludedPath = excludedURL.URLByStandardizingPath.path;
|
||||
+
|
||||
+ return [[enumerator.rac_sequence.signal
|
||||
+ filter:^(NSURL *enumeratedURL) {
|
||||
+ NSString *name = enumeratedURL.lastPathComponent;
|
||||
+ if (![name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix]) return NO;
|
||||
+ if (excludedPath != nil && [enumeratedURL.URLByStandardizingPath.path isEqualToString:excludedPath]) return NO;
|
||||
+ return YES;
|
||||
+ }]
|
||||
+ doNext:^(NSURL *directoryURL) {
|
||||
+ NSError *error = nil;
|
||||
+ if (![manager removeItemAtURL:directoryURL error:&error]) {
|
||||
+ NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
|
||||
+ }
|
||||
+ }];
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
chore_allow_customizing_microtask_policy_per_context.patch
|
||||
cherry-pick-d5b0cb2acffe.patch
|
||||
|
||||
50
patches/v8/cherry-pick-d5b0cb2acffe.patch
Normal file
50
patches/v8/cherry-pick-d5b0cb2acffe.patch
Normal file
@@ -0,0 +1,50 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Darius Mercadier <dmercadier@chromium.org>
|
||||
Date: Wed, 25 Feb 2026 12:56:18 +0100
|
||||
Subject: [M144 Merge] [maglev] fix CanElideWriteBarrier Smi recording for phis
|
||||
|
||||
Recording a Tagged use is not enough for 2 reasons:
|
||||
|
||||
* Tagged uses are sometimes ignored, in particular for loop phis
|
||||
where we distinguish in-loop and out-of-loop uses.
|
||||
|
||||
* This Tagged use could only prevent untagging of this specific phi,
|
||||
but none of its inputs. So we could have a Smi phi as input to the
|
||||
current phi which gets untagged and retagged to a non-Smi, all
|
||||
while the current phi doesn't get untagged.
|
||||
|
||||
(cherry picked from commit a54bf5cd45e5b119e2afe6019428e81c3d626fb3)
|
||||
|
||||
Change-Id: I9b3a2ea339f2c9d81dbb74a44425ba55d8c73871
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7604255
|
||||
Auto-Submit: Darius Mercadier <dmercadier@chromium.org>
|
||||
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
|
||||
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
|
||||
Cr-Original-Commit-Position: refs/heads/main@{#105444}
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7659106
|
||||
Auto-Submit: Srinivas Sista <srinivassista@chromium.org>
|
||||
Reviewed-by: Rezvan Mahdavi Hezaveh <rezvan@chromium.org>
|
||||
Commit-Queue: Srinivas Sista <srinivassista@chromium.org>
|
||||
Reviewed-by: Deepti Gandluri <gdeepti@chromium.org>
|
||||
Owners-Override: Srinivas Sista <srinivassista@chromium.org>
|
||||
Cr-Commit-Position: refs/branch-heads/14.4@{#64}
|
||||
Cr-Branched-From: 80acc26727d5a34e77dabeebe7c9213ec1bd4768-refs/heads/14.4.258@{#1}
|
||||
Cr-Branched-From: ce7e597e90f6df3fa4b6df224bc613b80c635450-refs/heads/main@{#104020}
|
||||
|
||||
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
|
||||
index bf6a5ab1b41d684c37dd96f7720474c6bb71a4db..c21a41d7da3394bb8f35857e0dcf49a78b218d31 100644
|
||||
--- a/src/maglev/maglev-graph-builder.cc
|
||||
+++ b/src/maglev/maglev-graph-builder.cc
|
||||
@@ -4439,7 +4439,11 @@ bool MaglevGraphBuilder::CanElideWriteBarrier(ValueNode* object,
|
||||
ValueNode* value) {
|
||||
if (value->Is<RootConstant>() || value->Is<ConsStringMap>()) return true;
|
||||
if (!IsEmptyNodeType(GetType(value)) && CheckType(value, NodeType::kSmi)) {
|
||||
- value->MaybeRecordUseReprHint(UseRepresentation::kTagged);
|
||||
+ if constexpr (SmiValuesAre31Bits()) {
|
||||
+ if (Phi* value_as_phi = value->TryCast<Phi>()) {
|
||||
+ value_as_phi->SetUseRequires31BitValue();
|
||||
+ }
|
||||
+ }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,11 @@ def format_patch(repo, since):
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'electron.gitattributes',
|
||||
),
|
||||
# Pin rename/copy detection to git's default so that patch output is
|
||||
# deterministic regardless of local or system-level diff.renames config
|
||||
# (e.g. 'copies', which would encode similar new files as copies).
|
||||
'-c',
|
||||
'diff.renames=true',
|
||||
# Ensure it is not possible to match anything
|
||||
# Disabled for now as we have consistent chunk headers
|
||||
# '-c',
|
||||
|
||||
@@ -80,6 +80,14 @@ PowerMonitor::PowerMonitor(v8::Isolate* isolate) {
|
||||
}
|
||||
|
||||
PowerMonitor::~PowerMonitor() {
|
||||
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
||||
DestroyPlatformSpecificMonitors();
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
Browser::Get()->SetShutdownHandler(base::RepeatingCallback<bool()>());
|
||||
#endif
|
||||
|
||||
auto* power_monitor = base::PowerMonitor::GetInstance();
|
||||
power_monitor->RemovePowerStateObserver(this);
|
||||
power_monitor->RemovePowerSuspendObserver(this);
|
||||
|
||||
@@ -49,6 +49,7 @@ class PowerMonitor final : public gin_helper::DeprecatedWrappable<PowerMonitor>,
|
||||
|
||||
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
||||
void InitPlatformSpecificMonitors();
|
||||
void DestroyPlatformSpecificMonitors();
|
||||
#endif
|
||||
|
||||
// base::PowerStateObserver implementations:
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
}
|
||||
|
||||
- (void)addEmitter:(electron::api::PowerMonitor*)monitor_;
|
||||
- (void)removeEmitter:(electron::api::PowerMonitor*)monitor_;
|
||||
|
||||
@end
|
||||
|
||||
@@ -62,6 +63,10 @@
|
||||
self->emitters.push_back(monitor_);
|
||||
}
|
||||
|
||||
- (void)removeEmitter:(electron::api::PowerMonitor*)monitor_ {
|
||||
std::erase(self->emitters, monitor_);
|
||||
}
|
||||
|
||||
- (void)onScreenLocked:(NSNotification*)notification {
|
||||
for (auto* emitter : self->emitters) {
|
||||
emitter->Emit("lock-screen");
|
||||
@@ -98,4 +103,9 @@ void PowerMonitor::InitPlatformSpecificMonitors() {
|
||||
[g_lock_monitor addEmitter:this];
|
||||
}
|
||||
|
||||
void PowerMonitor::DestroyPlatformSpecificMonitors() {
|
||||
if (g_lock_monitor)
|
||||
[g_lock_monitor removeEmitter:this];
|
||||
}
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -49,6 +49,20 @@ void PowerMonitor::InitPlatformSpecificMonitors() {
|
||||
DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
}
|
||||
|
||||
void PowerMonitor::DestroyPlatformSpecificMonitors() {
|
||||
if (window_) {
|
||||
WTSUnRegisterSessionNotification(window_);
|
||||
UnregisterSuspendResumeNotification(static_cast<HANDLE>(window_));
|
||||
gfx::SetWindowUserData(window_, nullptr);
|
||||
DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
if (atom_) {
|
||||
UnregisterClass(MAKEINTATOM(atom_), instance_);
|
||||
atom_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK PowerMonitor::WndProcStatic(HWND hwnd,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
@@ -76,7 +90,7 @@ LRESULT CALLBACK PowerMonitor::WndProc(HWND hwnd,
|
||||
}
|
||||
if (should_treat_as_current_session) {
|
||||
if (wparam == WTS_SESSION_LOCK) {
|
||||
// Unretained is OK because this object is eternally pinned.
|
||||
// SelfKeepAlive prevents GC of this object, so Unretained is safe.
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce([](PowerMonitor* pm) { pm->Emit("lock-screen"); },
|
||||
|
||||
@@ -32,10 +32,6 @@
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
#endif
|
||||
|
||||
#if defined(USE_OZONE)
|
||||
#include "ui/ozone/public/ozone_platform.h"
|
||||
#endif
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
gin::DeprecatedWrapperInfo Screen::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
@@ -81,16 +77,9 @@ Screen::~Screen() {
|
||||
}
|
||||
|
||||
gfx::Point Screen::GetCursorScreenPoint(v8::Isolate* isolate) {
|
||||
#if defined(USE_OZONE)
|
||||
// Wayland will crash unless a window is created prior to calling
|
||||
// GetCursorScreenPoint.
|
||||
if (!ui::OzonePlatform::IsInitialized()) {
|
||||
gin_helper::ErrorThrower thrower(isolate);
|
||||
thrower.ThrowError(
|
||||
"screen.getCursorScreenPoint() cannot be called before a window has "
|
||||
"been created.");
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
if (x11_util::IsWayland())
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
return screen_->GetCursorScreenPoint();
|
||||
}
|
||||
|
||||
@@ -2201,6 +2201,11 @@ void WebContents::DidUpdateFaviconURL(
|
||||
iter->icon_url.is_valid())
|
||||
unique_urls.insert(iter->icon_url);
|
||||
}
|
||||
// Only emit if favicon URLs actually changed
|
||||
if (unique_urls == last_favicon_urls_)
|
||||
return;
|
||||
last_favicon_urls_ = unique_urls;
|
||||
|
||||
Emit("page-favicon-updated", unique_urls);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/raw_ptr_exclusion.h"
|
||||
@@ -462,6 +463,9 @@ class WebContents final : public ExclusiveAccessContext,
|
||||
WebContents& operator=(const WebContents&) = delete;
|
||||
|
||||
private:
|
||||
// Store last emitted favicon URLs to avoid duplicate page-favicon-updated
|
||||
// events
|
||||
base::flat_set<GURL> last_favicon_urls_;
|
||||
// Does not manage lifetime of |web_contents|.
|
||||
WebContents(v8::Isolate* isolate, content::WebContents* web_contents);
|
||||
// Takes over ownership of |web_contents|.
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "chrome/common/chrome_paths.h"
|
||||
@@ -71,6 +73,29 @@ Browser* Browser::Get() {
|
||||
return ElectronBrowserMainParts::Get()->browser();
|
||||
}
|
||||
|
||||
// static
|
||||
bool Browser::IsValidProtocolScheme(const std::string& scheme) {
|
||||
// RFC 3986 Section 3.1:
|
||||
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
if (scheme.empty()) {
|
||||
LOG(ERROR) << "Protocol scheme must not be empty";
|
||||
return false;
|
||||
}
|
||||
if (!base::IsAsciiAlpha(scheme[0])) {
|
||||
LOG(ERROR) << "Protocol scheme must start with an ASCII letter";
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 1; i < scheme.size(); ++i) {
|
||||
const char c = scheme[i];
|
||||
if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '+' &&
|
||||
c != '-' && c != '.') {
|
||||
LOG(ERROR) << "Protocol scheme contains invalid character: '" << c << "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||
void Browser::Focus(gin::Arguments* args) {
|
||||
// Focus on the first visible window.
|
||||
|
||||
@@ -134,6 +134,10 @@ class Browser : private WindowListObserver {
|
||||
void SetAppUserModelID(const std::wstring& name);
|
||||
#endif
|
||||
|
||||
// Validate that a protocol scheme conforms to RFC 3986:
|
||||
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
static bool IsValidProtocolScheme(const std::string& scheme);
|
||||
|
||||
// Remove the default protocol handler registry key
|
||||
bool RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args);
|
||||
|
||||
@@ -103,16 +103,19 @@ void Browser::ClearRecentDocuments() {}
|
||||
|
||||
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
return SetDefaultWebClient(protocol);
|
||||
}
|
||||
|
||||
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
auto env = base::Environment::Create();
|
||||
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
auto env = base::Environment::Create();
|
||||
|
||||
std::vector<std::string> argv = {kXdgSettings, "check",
|
||||
kXdgSettingsDefaultSchemeHandler, protocol};
|
||||
if (std::optional<std::string> desktop_name = env->GetVar("CHROME_DESKTOP")) {
|
||||
|
||||
@@ -233,7 +233,7 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||
|
||||
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
|
||||
@@ -249,7 +249,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
|
||||
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
|
||||
|
||||
@@ -429,7 +429,7 @@ bool Browser::SetUserTasks(const std::vector<UserTask>& tasks) {
|
||||
|
||||
bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
// Main Registry Key
|
||||
@@ -508,7 +508,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
// Software\Classes", which is inherited by "HKEY_CLASSES_ROOT"
|
||||
// anyway, and can be written by unprivileged users.
|
||||
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
std::wstring exe;
|
||||
@@ -538,7 +538,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
|
||||
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
std::wstring exe;
|
||||
|
||||
@@ -88,13 +88,9 @@ HidChooserController::HidChooserController(
|
||||
exclusion_filters_(std::move(exclusion_filters)),
|
||||
callback_(std::move(callback)),
|
||||
initiator_document_(render_frame_host->GetWeakDocumentPtr()),
|
||||
origin_(content::WebContents::FromRenderFrameHost(render_frame_host)
|
||||
->GetPrimaryMainFrame()
|
||||
->GetLastCommittedOrigin()),
|
||||
origin_(render_frame_host->GetLastCommittedOrigin()),
|
||||
hid_delegate_(hid_delegate),
|
||||
render_frame_host_id_(render_frame_host->GetGlobalId()) {
|
||||
// The use above of GetMainFrame is safe as content::HidService instances are
|
||||
// not created for fenced frames.
|
||||
DCHECK(!render_frame_host->IsNestedWithinFencedFrame());
|
||||
|
||||
chooser_context_ = HidChooserContextFactory::GetForBrowserContext(
|
||||
|
||||
@@ -4,13 +4,27 @@
|
||||
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "ui/ozone/platform_selection.h" // nogncheck
|
||||
|
||||
namespace x11_util {
|
||||
|
||||
bool IsX11() {
|
||||
static const bool is_x11 = ui::GetOzonePlatformId() == ui::kPlatformX11;
|
||||
return is_x11;
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
static const bool is = ui::GetOzonePlatformId() == ui::kPlatformX11;
|
||||
return is;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsWayland() {
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
static const bool is = ui::GetOzonePlatformId() == ui::kPlatformWayland;
|
||||
return is;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace x11_util
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
namespace x11_util {
|
||||
|
||||
bool IsX11();
|
||||
[[nodiscard]] bool IsX11();
|
||||
[[nodiscard]] bool IsWayland();
|
||||
|
||||
} // namespace x11_util
|
||||
|
||||
|
||||
@@ -170,6 +170,12 @@ class NativeWindowMac : public NativeWindow,
|
||||
void NotifyWindowDidFailToEnterFullScreen();
|
||||
void NotifyWindowWillLeaveFullScreen();
|
||||
|
||||
// Hide/show traffic light buttons around miniaturize/deminiaturize to
|
||||
// prevent them from flashing at the default position during the restore
|
||||
// animation when a custom trafficLightPosition is configured.
|
||||
void HideTrafficLights();
|
||||
void RestoreTrafficLights();
|
||||
|
||||
// Cleanup observers when window is getting closed. Note that the destructor
|
||||
// can be called much later after window gets closed, so we should not do
|
||||
// cleanup in destructor.
|
||||
|
||||
@@ -1498,6 +1498,18 @@ void NativeWindowMac::RedrawTrafficLights() {
|
||||
[buttons_proxy_ redraw];
|
||||
}
|
||||
|
||||
void NativeWindowMac::HideTrafficLights() {
|
||||
if (buttons_proxy_)
|
||||
[buttons_proxy_ setVisible:NO];
|
||||
}
|
||||
|
||||
void NativeWindowMac::RestoreTrafficLights() {
|
||||
if (buttons_proxy_ && window_button_visibility_.value_or(true)) {
|
||||
[buttons_proxy_ redraw];
|
||||
[buttons_proxy_ setVisible:YES];
|
||||
}
|
||||
}
|
||||
|
||||
// In simpleFullScreen mode, update the frame for new bounds.
|
||||
void NativeWindowMac::UpdateFrame() {
|
||||
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "net/base/filename_util.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/http/http_status_code.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "net/url_request/redirect_util.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/shared_url_loader_factory.h"
|
||||
@@ -138,13 +139,17 @@ network::mojom::URLResponseHeadPtr ToResponseHead(
|
||||
base::Value::Dict headers;
|
||||
if (dict.Get("headers", &headers)) {
|
||||
for (const auto iter : headers) {
|
||||
if (!net::HttpUtil::IsValidHeaderName(iter.first))
|
||||
continue;
|
||||
if (iter.second.is_string()) {
|
||||
// key, value
|
||||
head->headers->AddHeader(iter.first, iter.second.GetString());
|
||||
if (net::HttpUtil::IsValidHeaderValue(iter.second.GetString()))
|
||||
head->headers->AddHeader(iter.first, iter.second.GetString());
|
||||
} else if (iter.second.is_list()) {
|
||||
// key: [values...]
|
||||
for (const auto& item : iter.second.GetList()) {
|
||||
if (item.is_string())
|
||||
if (item.is_string() &&
|
||||
net::HttpUtil::IsValidHeaderValue(item.GetString()))
|
||||
head->headers->AddHeader(iter.first, item.GetString());
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -52,25 +52,21 @@ bool ElectronSerialDelegate::CanRequestPortPermission(
|
||||
auto* permission_helper =
|
||||
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||
return permission_helper->CheckSerialAccessPermission(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());
|
||||
frame->GetLastCommittedOrigin());
|
||||
}
|
||||
|
||||
bool ElectronSerialDelegate::HasPortPermission(
|
||||
content::RenderFrameHost* frame,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
return GetChooserContext(frame)->HasPortPermission(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), port,
|
||||
frame);
|
||||
frame->GetLastCommittedOrigin(), port, frame);
|
||||
}
|
||||
|
||||
void ElectronSerialDelegate::RevokePortPermissionWebInitiated(
|
||||
content::RenderFrameHost* frame,
|
||||
const base::UnguessableToken& token) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
return GetChooserContext(frame)->RevokePortPermissionWebInitiated(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), token,
|
||||
frame);
|
||||
frame->GetLastCommittedOrigin(), token, frame);
|
||||
}
|
||||
|
||||
const device::mojom::SerialPortInfo* ElectronSerialDelegate::GetPortInfo(
|
||||
|
||||
@@ -125,7 +125,7 @@ SerialChooserController::SerialChooserController(
|
||||
std::move(allowed_bluetooth_service_class_ids)),
|
||||
callback_(std::move(callback)),
|
||||
initiator_document_(render_frame_host->GetWeakDocumentPtr()) {
|
||||
origin_ = web_contents_->GetPrimaryMainFrame()->GetLastCommittedOrigin();
|
||||
origin_ = render_frame_host->GetLastCommittedOrigin();
|
||||
|
||||
chooser_context_ = SerialChooserContextFactory::GetForBrowserContext(
|
||||
web_contents_->GetBrowserContext())
|
||||
|
||||
@@ -255,6 +255,10 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
shell_->SetWindowLevel(NSNormalWindowLevel);
|
||||
shell_->UpdateWindowOriginalFrame();
|
||||
shell_->DetachChildren();
|
||||
// Hide the traffic light buttons container before miniaturize so that
|
||||
// when the window is restored, macOS does not render the buttons at
|
||||
// their default position during the deminiaturize animation.
|
||||
shell_->HideTrafficLights();
|
||||
}
|
||||
|
||||
- (void)windowDidMiniaturize:(NSNotification*)notification {
|
||||
@@ -272,6 +276,10 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
shell_->set_wants_to_be_visible(true);
|
||||
shell_->AttachChildren();
|
||||
shell_->SetWindowLevel(level_);
|
||||
// Reposition traffic light buttons and make them visible again.
|
||||
// They were hidden in windowWillMiniaturize to prevent a flash at
|
||||
// the default (0,0) position during the restore animation.
|
||||
shell_->RestoreTrafficLights();
|
||||
shell_->NotifyWindowRestore();
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ DialogResult ShowTaskDialogWstr(gfx::AcceleratedWidget parent,
|
||||
config.hInstance = GetModuleHandle(nullptr);
|
||||
config.dwFlags = flags;
|
||||
|
||||
if (parent) {
|
||||
if (parent && ::IsWindowEnabled(parent)) {
|
||||
config.hwndParent = parent;
|
||||
config.dwFlags |= TDF_POSITION_RELATIVE_TO_WINDOW;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "components/input/native_web_keyboard_event.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/ui/views/menu_bar.h"
|
||||
#include "ui/events/keycodes/dom/keycode_converter.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
|
||||
namespace electron {
|
||||
@@ -22,21 +21,9 @@ bool IsAltKey(const input::NativeWebKeyboardEvent& event) {
|
||||
|
||||
bool IsAltModifier(const input::NativeWebKeyboardEvent& event) {
|
||||
using Mods = input::NativeWebKeyboardEvent::Modifiers;
|
||||
|
||||
// AltGraph (AltGr) should not be treated as a single Alt keypress for
|
||||
// menu-bar toggling.
|
||||
if (event.windows_key_code == ui::VKEY_ALTGR ||
|
||||
ui::KeycodeConverter::DomKeyToKeyString(event.dom_key) == "AltGraph") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (event.GetModifiers() & Mods::kKeyModifiers) == Mods::kAltKey;
|
||||
}
|
||||
|
||||
bool IsSingleAltKey(const input::NativeWebKeyboardEvent& event) {
|
||||
return IsAltKey(event) && IsAltModifier(event);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RootView::RootView(NativeWindow* window)
|
||||
@@ -111,7 +98,7 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
|
||||
return;
|
||||
|
||||
// Show accelerator when "Alt" is pressed.
|
||||
if (menu_bar_visible_ && IsSingleAltKey(event))
|
||||
if (menu_bar_visible_ && IsAltKey(event))
|
||||
menu_bar_->SetAcceleratorVisibility(
|
||||
event.GetType() == blink::WebInputEvent::Type::kRawKeyDown);
|
||||
|
||||
@@ -134,11 +121,11 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
|
||||
|
||||
// Toggle the menu bar only when a single Alt is released.
|
||||
if (event.GetType() == blink::WebInputEvent::Type::kRawKeyDown &&
|
||||
IsSingleAltKey(event)) {
|
||||
IsAltKey(event)) {
|
||||
// When a single Alt is pressed:
|
||||
menu_bar_alt_pressed_ = true;
|
||||
} else if (event.GetType() == blink::WebInputEvent::Type::kKeyUp &&
|
||||
IsSingleAltKey(event) && menu_bar_alt_pressed_) {
|
||||
IsAltKey(event) && menu_bar_alt_pressed_) {
|
||||
// When a single Alt is released right after a Alt is pressed:
|
||||
menu_bar_alt_pressed_ = false;
|
||||
if (menu_bar_autohide_)
|
||||
|
||||
@@ -43,7 +43,7 @@ UsbChooserController::UsbChooserController(
|
||||
: WebContentsObserver(web_contents),
|
||||
options_(std::move(options)),
|
||||
callback_(std::move(callback)),
|
||||
origin_(render_frame_host->GetMainFrame()->GetLastCommittedOrigin()),
|
||||
origin_(render_frame_host->GetLastCommittedOrigin()),
|
||||
usb_delegate_(usb_delegate),
|
||||
render_frame_host_id_(render_frame_host->GetGlobalId()) {
|
||||
chooser_context_ = UsbChooserContextFactory::GetForBrowserContext(
|
||||
|
||||
@@ -219,7 +219,7 @@ void WebContentsPermissionHelper::RequestPermission(
|
||||
base::Value::Dict details) {
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
web_contents_->GetBrowserContext()->GetPermissionControllerDelegate());
|
||||
auto origin = web_contents_->GetLastCommittedURL();
|
||||
auto origin = requesting_frame->GetLastCommittedOrigin().GetURL();
|
||||
permission_manager->RequestPermissionWithDetails(
|
||||
content::PermissionDescriptorUtil::
|
||||
CreatePermissionDescriptorForPermissionType(permission),
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "net/cert/x509_certificate.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "net/http/http_version.h"
|
||||
#include "net/url_request/redirect_info.h"
|
||||
#include "services/network/public/cpp/data_element.h"
|
||||
@@ -197,6 +198,10 @@ bool Converter<net::HttpResponseHeaders*>::FromV8(
|
||||
}
|
||||
std::string value;
|
||||
gin::ConvertFromV8(isolate, localStrVal, &value);
|
||||
if (!net::HttpUtil::IsValidHeaderName(key) ||
|
||||
!net::HttpUtil::IsValidHeaderValue(value)) {
|
||||
return false;
|
||||
}
|
||||
out->AddHeader(key, value);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "shell/renderer/api/electron_api_context_bridge.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -827,13 +828,18 @@ void ExposeAPIInWorld(v8::Isolate* isolate,
|
||||
ExposeAPI(isolate, source_context, target_isolate, target_context, key, api);
|
||||
}
|
||||
|
||||
gin_helper::Dictionary TraceKeyPath(const gin_helper::Dictionary& start,
|
||||
const std::vector<std::string>& key_path) {
|
||||
std::optional<gin_helper::Dictionary> TraceKeyPath(
|
||||
const gin_helper::Dictionary& start,
|
||||
const std::vector<std::string>& key_path,
|
||||
bool allow_silent_failure) {
|
||||
gin_helper::Dictionary current = start;
|
||||
for (size_t i = 0; i < key_path.size() - 1; i++) {
|
||||
CHECK(current.Get(key_path[i], ¤t))
|
||||
<< "Failed to get property '" << key_path[i] << "' at index " << i
|
||||
<< " in key path";
|
||||
if (!current.Get(key_path[i], ¤t)) {
|
||||
if (allow_silent_failure)
|
||||
return std::nullopt;
|
||||
CHECK(false) << "Failed to get property '" << key_path[i] << "' at index "
|
||||
<< i << " in key path";
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
@@ -842,7 +848,8 @@ void OverrideGlobalValueFromIsolatedWorld(
|
||||
v8::Isolate* isolate,
|
||||
const std::vector<std::string>& key_path,
|
||||
v8::Local<v8::Object> value,
|
||||
bool support_dynamic_properties) {
|
||||
bool support_dynamic_properties,
|
||||
bool allow_silent_failure) {
|
||||
if (key_path.empty())
|
||||
return;
|
||||
|
||||
@@ -854,7 +861,11 @@ void OverrideGlobalValueFromIsolatedWorld(
|
||||
gin_helper::Dictionary global(isolate, main_context->Global());
|
||||
|
||||
const std::string final_key = key_path[key_path.size() - 1];
|
||||
gin_helper::Dictionary target_object = TraceKeyPath(global, key_path);
|
||||
auto maybe_target_object =
|
||||
TraceKeyPath(global, key_path, allow_silent_failure);
|
||||
if (!maybe_target_object.has_value())
|
||||
return;
|
||||
gin_helper::Dictionary target_object = maybe_target_object.value();
|
||||
|
||||
{
|
||||
v8::Context::Scope main_context_scope(main_context);
|
||||
@@ -887,8 +898,8 @@ bool OverrideGlobalPropertyFromIsolatedWorld(
|
||||
gin_helper::Dictionary global(isolate, main_context->Global());
|
||||
|
||||
const std::string final_key = key_path[key_path.size() - 1];
|
||||
v8::Local<v8::Object> target_object =
|
||||
TraceKeyPath(global, key_path).GetHandle();
|
||||
auto target_dict = TraceKeyPath(global, key_path, false);
|
||||
v8::Local<v8::Object> target_object = target_dict.value().GetHandle();
|
||||
|
||||
{
|
||||
v8::Context::Scope main_context_scope(main_context);
|
||||
|
||||
@@ -1478,6 +1478,29 @@ describe('app module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('protocol scheme validation', () => {
|
||||
it('rejects empty protocol names', () => {
|
||||
expect(app.setAsDefaultProtocolClient('')).to.equal(false);
|
||||
expect(app.isDefaultProtocolClient('')).to.equal(false);
|
||||
expect(app.removeAsDefaultProtocolClient('')).to.equal(false);
|
||||
});
|
||||
|
||||
it('rejects non-conformant protocol names ', () => {
|
||||
// Starting with a digit.
|
||||
expect(app.setAsDefaultProtocolClient('0badscheme')).to.equal(false);
|
||||
// Starting with a hyphen.
|
||||
expect(app.setAsDefaultProtocolClient('-badscheme')).to.equal(false);
|
||||
// Containing backslashes.
|
||||
expect(app.setAsDefaultProtocolClient('http\\shell\\open\\command')).to.equal(false);
|
||||
// Containing forward slashes.
|
||||
expect(app.setAsDefaultProtocolClient('bad/protocol')).to.equal(false);
|
||||
// Containing spaces.
|
||||
expect(app.setAsDefaultProtocolClient('bad protocol')).to.equal(false);
|
||||
// Containing colons.
|
||||
expect(app.setAsDefaultProtocolClient('bad:protocol')).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'win32')('app launch through uri', () => {
|
||||
it('does not launch for argument following a URL', async () => {
|
||||
const appPath = path.join(fixturesPath, 'api', 'quit-app');
|
||||
|
||||
@@ -403,7 +403,7 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should clean up old staged update directories when a new update is downloaded', async () => {
|
||||
it('should preserve the staged update directory and prune orphaned ones when a new update is downloaded', async () => {
|
||||
// Clean up any existing update directories before the test
|
||||
await cleanSquirrelCache();
|
||||
|
||||
@@ -419,16 +419,23 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
}, async (_, updateZipPath3) => {
|
||||
let updateCount = 0;
|
||||
let downloadCount = 0;
|
||||
let directoriesDuringSecondDownload: string[] = [];
|
||||
let dirsDuringFirstDownload: string[] = [];
|
||||
let dirsDuringSecondDownload: string[] = [];
|
||||
|
||||
server.get('/update-file', async (req, res) => {
|
||||
downloadCount++;
|
||||
// When the second download request arrives, Squirrel has already
|
||||
// called uniqueTemporaryDirectoryForUpdate which (with our patch)
|
||||
// cleans up old directories before creating the new one.
|
||||
// Without the patch, both directories would exist at this point.
|
||||
if (downloadCount === 2) {
|
||||
directoriesDuringSecondDownload = await getUpdateDirectoriesInCache();
|
||||
// Snapshot update directories at the moment each download begins.
|
||||
// By this point uniqueTemporaryDirectoryForUpdate has already run
|
||||
// (prune + mkdtemp). We want to verify:
|
||||
// 1st download: 1 dir (nothing to preserve, nothing to prune)
|
||||
// 2nd download: 2 dirs (staged dir from 1st check is preserved
|
||||
// so quitAndInstall stays safe, + new temp dir)
|
||||
// The count never exceeds 2 across repeated checks — orphaned dirs
|
||||
// (no longer referenced by ShipItState.plist) get pruned.
|
||||
if (downloadCount === 1) {
|
||||
dirsDuringFirstDownload = await getUpdateDirectoriesInCache();
|
||||
} else if (downloadCount === 2) {
|
||||
dirsDuringSecondDownload = await getUpdateDirectoriesInCache();
|
||||
}
|
||||
res.download(updateCount > 1 ? updateZipPath3 : updateZipPath2);
|
||||
});
|
||||
@@ -455,15 +462,181 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
|
||||
await relaunchPromise;
|
||||
|
||||
// During the second download, the old staged update directory should
|
||||
// have been cleaned up. With our patch, there should be exactly 1
|
||||
// directory (the new one). Without the patch, there would be 2.
|
||||
expect(directoriesDuringSecondDownload).to.have.lengthOf(1,
|
||||
`Expected 1 update directory during second download but found ${directoriesDuringSecondDownload.length}: ${directoriesDuringSecondDownload.join(', ')}`);
|
||||
// First download: exactly one temp dir (the first update).
|
||||
expect(dirsDuringFirstDownload).to.have.lengthOf(1,
|
||||
`Expected 1 update directory during first download but found ${dirsDuringFirstDownload.length}: ${dirsDuringFirstDownload.join(', ')}`);
|
||||
|
||||
// Second download: exactly two — the staged one preserved + the new
|
||||
// one. Crucially the first download's directory must still be present,
|
||||
// otherwise a mid-download quitAndInstall would find a dangling
|
||||
// ShipItState.plist.
|
||||
expect(dirsDuringSecondDownload).to.have.lengthOf(2,
|
||||
`Expected 2 update directories during second download (staged + new) but found ${dirsDuringSecondDownload.length}: ${dirsDuringSecondDownload.join(', ')}`);
|
||||
expect(dirsDuringSecondDownload).to.include(dirsDuringFirstDownload[0],
|
||||
'The staged update directory from the first download must be preserved during the second download');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep the update directory count bounded across repeated checks', async () => {
|
||||
// Verifies the orphan prune actually fires: after a second download
|
||||
// completes and rewrites ShipItState.plist, the first directory is no
|
||||
// longer referenced and must be removed when a third check begins.
|
||||
// Without this, directories would accumulate forever.
|
||||
await cleanSquirrelCache();
|
||||
|
||||
await withUpdatableApp({
|
||||
nextVersion: '2.0.0',
|
||||
startFixture: 'update-triple-stack',
|
||||
endFixture: 'update-triple-stack'
|
||||
}, async (appPath, updateZipPath2) => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '3.0.0',
|
||||
startFixture: 'update-triple-stack',
|
||||
endFixture: 'update-triple-stack'
|
||||
}, async (_, updateZipPath3) => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '4.0.0',
|
||||
startFixture: 'update-triple-stack',
|
||||
endFixture: 'update-triple-stack'
|
||||
}, async (__, updateZipPath4) => {
|
||||
let downloadCount = 0;
|
||||
const dirsPerDownload: string[][] = [];
|
||||
|
||||
server.get('/update-file', async (req, res) => {
|
||||
downloadCount++;
|
||||
// Snapshot after prune+mkdtemp but before the payload transfers.
|
||||
dirsPerDownload.push(await getUpdateDirectoriesInCache());
|
||||
const zips = [updateZipPath2, updateZipPath3, updateZipPath4];
|
||||
res.download(zips[Math.min(downloadCount, zips.length) - 1]);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const relaunchPromise = new Promise<void>((resolve) => {
|
||||
server.get('/update-check/updated/:version', (req, res) => {
|
||||
res.status(204).send();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 0);
|
||||
expect(launchResult.out).to.include('Update Downloaded');
|
||||
});
|
||||
|
||||
await relaunchPromise;
|
||||
expect(requests[requests.length - 1].url).to.equal('/update-check/updated/4.0.0');
|
||||
|
||||
expect(dirsPerDownload).to.have.lengthOf(3);
|
||||
|
||||
// 1st: fresh cache, 1 dir.
|
||||
expect(dirsPerDownload[0]).to.have.lengthOf(1,
|
||||
`1st download: ${dirsPerDownload[0].join(', ')}`);
|
||||
|
||||
// 2nd: staged (1st) preserved + new = 2 dirs.
|
||||
expect(dirsPerDownload[1]).to.have.lengthOf(2,
|
||||
`2nd download: ${dirsPerDownload[1].join(', ')}`);
|
||||
expect(dirsPerDownload[1]).to.include(dirsPerDownload[0][0]);
|
||||
|
||||
// 3rd: 1st is now orphaned (plist points to 2nd) — must be pruned.
|
||||
// Staged (2nd) preserved + new = still 2 dirs. Bounded.
|
||||
expect(dirsPerDownload[2]).to.have.lengthOf(2,
|
||||
`3rd download: ${dirsPerDownload[2].join(', ')}`);
|
||||
expect(dirsPerDownload[2]).to.not.include(dirsPerDownload[0][0],
|
||||
'The first (now orphaned) update directory must be pruned on the third check');
|
||||
const secondDir = dirsPerDownload[1].find(d => d !== dirsPerDownload[0][0]);
|
||||
expect(dirsPerDownload[2]).to.include(secondDir,
|
||||
'The second (currently staged) update directory must be preserved on the third check');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/electron/electron/issues/50200
|
||||
//
|
||||
// When checkForUpdates() is called again after an update has been staged,
|
||||
// Squirrel creates a new temporary directory and prunes old ones. If the
|
||||
// prune removes the directory that ShipItState.plist references while the
|
||||
// second download is still in flight, a subsequent quitAndInstall() will
|
||||
// fail with ENOENT and the app will never relaunch.
|
||||
it('should install the staged update when quitAndInstall is called while a second check is in flight', async () => {
|
||||
await cleanSquirrelCache();
|
||||
|
||||
await withUpdatableApp({
|
||||
nextVersion: '2.0.0',
|
||||
startFixture: 'update-race',
|
||||
endFixture: 'update-race'
|
||||
}, async (appPath, updateZipPath) => {
|
||||
let downloadCount = 0;
|
||||
let stalledResponse: express.Response | null = null;
|
||||
|
||||
server.get('/update-file', (req, res) => {
|
||||
downloadCount++;
|
||||
if (downloadCount === 1) {
|
||||
// First download completes normally and stages the update.
|
||||
res.download(updateZipPath);
|
||||
} else {
|
||||
// Second download: stall indefinitely to simulate a slow
|
||||
// network. This keeps the second check "in progress" when
|
||||
// quitAndInstall() fires. Hold onto the response so we can
|
||||
// clean it up later.
|
||||
stalledResponse = res;
|
||||
}
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const relaunchPromise = new Promise<void>((resolve) => {
|
||||
server.get('/update-check/updated/:version', (req, res) => {
|
||||
res.status(204).send();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 0);
|
||||
expect(launchResult.out).to.include('Update Downloaded');
|
||||
expect(launchResult.out).to.include('Calling quitAndInstall mid-download');
|
||||
// First check + first download + second check + stalled second download.
|
||||
expect(requests).to.have.lengthOf(4);
|
||||
expect(requests[0]).to.have.property('url', '/update-check');
|
||||
expect(requests[1]).to.have.property('url', '/update-file');
|
||||
expect(requests[2]).to.have.property('url', '/update-check');
|
||||
expect(requests[3]).to.have.property('url', '/update-file');
|
||||
// The second download must have been in flight (never completed)
|
||||
// when quitAndInstall was called.
|
||||
expect(launchResult.out).to.not.include('Unexpected second download completion');
|
||||
});
|
||||
|
||||
// Unblock the stalled response now that the initial app has exited
|
||||
// so the express server can shut down cleanly.
|
||||
if (stalledResponse) {
|
||||
(stalledResponse as express.Response).status(500).end();
|
||||
}
|
||||
|
||||
// The originally staged update (2.0.0) must have been applied and
|
||||
// the app must relaunch, proving the staged update directory was
|
||||
// not pruned out from under ShipItState.plist.
|
||||
await relaunchPromise;
|
||||
expect(requests).to.have.lengthOf(5);
|
||||
expect(requests[4].url).to.equal('/update-check/updated/2.0.0');
|
||||
expect(requests[4].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update to lower version numbers', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '0.0.1',
|
||||
|
||||
@@ -5931,23 +5931,6 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'linux')('menu bar AltGr behavior', () => {
|
||||
it('does not toggle auto-hide menu bar visibility', async () => {
|
||||
const w = new BrowserWindow({ show: false, autoHideMenuBar: true });
|
||||
w.setMenuBarVisibility(false);
|
||||
expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible');
|
||||
|
||||
w.show();
|
||||
await once(w, 'show');
|
||||
w.webContents.focus();
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'AltGr' });
|
||||
w.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'AltGr' });
|
||||
await setTimeout();
|
||||
|
||||
expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible');
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform !== 'darwin')('when fullscreen state is changed', () => {
|
||||
it('correctly remembers state prior to fullscreen change', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
|
||||
82
spec/fixtures/auto-update/update-race/index.js
vendored
Normal file
82
spec/fixtures/auto-update/update-race/index.js
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
const { app, autoUpdater } = require('electron');
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
let installInvoked = false;
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
// Once quitAndInstall() has been invoked the second in-flight check may
|
||||
// surface a cancellation/network error as the process tears down; ignore
|
||||
// errors after that point so we test the actual install race, not teardown.
|
||||
if (installInvoked) {
|
||||
console.log('Ignoring post-install error:', err && err.message);
|
||||
return;
|
||||
}
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const urlPath = path.resolve(__dirname, '../../../../url.txt');
|
||||
let feedUrl = process.argv[1];
|
||||
|
||||
if (feedUrl === 'remain-open') {
|
||||
// Hold the event loop
|
||||
setInterval(() => {});
|
||||
} else {
|
||||
if (!feedUrl || !feedUrl.startsWith('http')) {
|
||||
feedUrl = `${fs.readFileSync(urlPath, 'utf8')}/${app.getVersion()}`;
|
||||
} else {
|
||||
fs.writeFileSync(urlPath, `${feedUrl}/updated`);
|
||||
}
|
||||
|
||||
autoUpdater.setFeedURL({
|
||||
url: feedUrl
|
||||
});
|
||||
|
||||
autoUpdater.checkForUpdates();
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
console.log('Update Available');
|
||||
});
|
||||
|
||||
let downloadedOnce = false;
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
console.log('Update Downloaded');
|
||||
if (!downloadedOnce) {
|
||||
downloadedOnce = true;
|
||||
// Simulate a periodic update check firing after an update was already
|
||||
// staged. The test server is expected to stall this second download so
|
||||
// that it remains in flight while we call quitAndInstall().
|
||||
// The short delay lets checkForUpdatesCommand's RACCommand executing
|
||||
// state settle; calling immediately would hit the command's "disabled"
|
||||
// guard since RACCommand disallows concurrent execution.
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdates();
|
||||
// Give Squirrel enough time to enter the second check (creating a new
|
||||
// temporary directory, which with the regression prunes the directory
|
||||
// that the staged update lives in) before invoking the install.
|
||||
setTimeout(() => {
|
||||
console.log('Calling quitAndInstall mid-download');
|
||||
installInvoked = true;
|
||||
autoUpdater.quitAndInstall();
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
} else {
|
||||
// Should not reach here — the second download is stalled on purpose.
|
||||
console.log('Unexpected second download completion');
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
console.error('No update available');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
5
spec/fixtures/auto-update/update-race/package.json
vendored
Normal file
5
spec/fixtures/auto-update/update-race/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "electron-test-update-race",
|
||||
"version": "1.0.0",
|
||||
"main": "./index.js"
|
||||
}
|
||||
57
spec/fixtures/auto-update/update-triple-stack/index.js
vendored
Normal file
57
spec/fixtures/auto-update/update-triple-stack/index.js
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
const { app, autoUpdater } = require('electron');
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const urlPath = path.resolve(__dirname, '../../../../url.txt');
|
||||
let feedUrl = process.argv[1];
|
||||
|
||||
if (feedUrl === 'remain-open') {
|
||||
// Hold the event loop
|
||||
setInterval(() => {});
|
||||
} else {
|
||||
if (!feedUrl || !feedUrl.startsWith('http')) {
|
||||
feedUrl = `${fs.readFileSync(urlPath, 'utf8')}/${app.getVersion()}`;
|
||||
} else {
|
||||
fs.writeFileSync(urlPath, `${feedUrl}/updated`);
|
||||
}
|
||||
|
||||
autoUpdater.setFeedURL({
|
||||
url: feedUrl
|
||||
});
|
||||
|
||||
autoUpdater.checkForUpdates();
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
console.log('Update Available');
|
||||
});
|
||||
|
||||
let updateStackCount = 0;
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
updateStackCount++;
|
||||
console.log('Update Downloaded');
|
||||
if (updateStackCount > 2) {
|
||||
autoUpdater.quitAndInstall();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdates();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
console.error('No update available');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
5
spec/fixtures/auto-update/update-triple-stack/package.json
vendored
Normal file
5
spec/fixtures/auto-update/update-triple-stack/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "electron-test-update-triple-stack",
|
||||
"version": "1.0.0",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
}
|
||||
const parsedURL = new URL(window.location.href);
|
||||
if (parsedURL.searchParams.get('opened') != null) {
|
||||
if (parsedURL.searchParams.has('opened')) {
|
||||
// Ensure origins are properly checked by removing a single character from the end
|
||||
tryPostMessage('do not deliver substring origin', window.location.origin.substring(0, window.location.origin.length - 1))
|
||||
tryPostMessage('do not deliver file://', 'file://')
|
||||
|
||||
Reference in New Issue
Block a user