From 8169ba0de74ca525f37f38175641c8efa08629c8 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:06:47 +0100 Subject: [PATCH 001/222] feat(webkit): roll to r2182 (#36183) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 5944a13af7512..a64ea919aaf71 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2181", + "revision": "2182", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From a0479cb24b965cb99acd6564dcef516428107c31 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 4 Jun 2025 13:25:43 +0100 Subject: [PATCH 002/222] fix(test runner): prevent esm loader deadlock (#36187) --- .../playwright/src/transform/esmLoader.ts | 10 +- .../playwright/src/transform/portTransport.ts | 13 ++- .../playwright/src/transform/transform.ts | 12 ++- tests/playwright-test/esm.spec.ts | 92 +++++++++++++++++++ 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/packages/playwright/src/transform/esmLoader.ts b/packages/playwright/src/transform/esmLoader.ts index a74b20110d7d6..d32537be5eb12 100644 --- a/packages/playwright/src/transform/esmLoader.ts +++ b/packages/playwright/src/transform/esmLoader.ts @@ -72,8 +72,12 @@ async function load(moduleUrl: string, context: { format?: string }, defaultLoad const transformed = transformHook(code, filename, moduleUrl); // Flush the source maps to the main thread, so that errors during import() are source-mapped. - if (transformed.serializedCache) - await transport?.send('pushToCompilationCache', { cache: transformed.serializedCache }); + if (transformed.serializedCache) { + if (legacyWaitForSourceMaps) + await transport?.send('pushToCompilationCache', { cache: transformed.serializedCache }); + else + transport?.post('pushToCompilationCache', { cache: transformed.serializedCache }); + } // Output format is required, so we determine it manually when unknown. // shortCircuit is required by Node >= 18.6 to designate no more loaders should be called. @@ -85,9 +89,11 @@ async function load(moduleUrl: string, context: { format?: string }, defaultLoad } let transport: PortTransport | undefined; +let legacyWaitForSourceMaps = false; function initialize(data: { port: MessagePort }) { transport = createTransport(data?.port); + legacyWaitForSourceMaps = !!process.env.PLAYWRIGHT_WAIT_FOR_SOURCE_MAPS; } function createTransport(port: MessagePort) { diff --git a/packages/playwright/src/transform/portTransport.ts b/packages/playwright/src/transform/portTransport.ts index f99e39f6ceb5a..a0f4819131b33 100644 --- a/packages/playwright/src/transform/portTransport.ts +++ b/packages/playwright/src/transform/portTransport.ts @@ -24,11 +24,6 @@ export class PortTransport { port.addEventListener('message', async event => { const message = event.data; const { id, ackId, method, params, result } = message; - if (id) { - const result = await handler(method, params); - this._port.postMessage({ ackId: id, result }); - return; - } if (ackId) { const callback = this._callbacks.get(ackId); @@ -37,12 +32,20 @@ export class PortTransport { callback?.(result); return; } + + const handlerResult = await handler(method, params); + if (id) + this._port.postMessage({ ackId: id, result: handlerResult }); }); // Make sure to unref **after** adding a 'message' event listener. // https://nodejs.org/api/worker_threads.html#portref this._resetRef(); } + post(method: string, params: any) { + this._port.postMessage({ method, params }); + } + async send(method: string, params: any) { return await new Promise(f => { const id = ++this._lastId; diff --git a/packages/playwright/src/transform/transform.ts b/packages/playwright/src/transform/transform.ts index 1719fd504c233..383180afba2cb 100644 --- a/packages/playwright/src/transform/transform.ts +++ b/packages/playwright/src/transform/transform.ts @@ -260,8 +260,16 @@ export async function requireOrImport(file: string) { installTransformIfNeeded(); const isModule = fileIsModule(file); const esmImport = () => eval(`import(${JSON.stringify(url.pathToFileURL(file))})`); - if (isModule) - return await esmImport(); + if (isModule) { + return await esmImport().finally(async () => { + // Compilation cache, which includes source maps, is populated in a post task. + // When importing a module results in an error, the very next access to `error.stack` + // will need source maps. To make sure source maps have arrived, we insert a task + // that will be processed after compilation cache and guarantee that + // source maps are available, before `error.stack` is accessed. + await new Promise(resolve => setTimeout(resolve, 0)); + }); + } const result = require(file); const depsCollector = currentFileDepsCollector(); if (depsCollector) { diff --git a/tests/playwright-test/esm.spec.ts b/tests/playwright-test/esm.spec.ts index 9f567b0f44b6a..0e1deb9585917 100644 --- a/tests/playwright-test/esm.spec.ts +++ b/tests/playwright-test/esm.spec.ts @@ -195,6 +195,32 @@ test('should use source maps when importing a file throws an error', async ({ ru `); }); +test('should use source maps when importing a file throws an error in legacy mode', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35706' }, +}, async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'package.json': `{ "type": "module" }`, + 'playwright.config.ts': ` + export default {}; + `, + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + + throw new Error('Oh my!'); + ` + }, {}, { 'PLAYWRIGHT_WAIT_FOR_SOURCE_MAPS': '1' }); + expect(result.exitCode).toBe(1); + expect(result.output).toContain(`Error: Oh my! + + at a.test.ts:4 + + 2 | import { test, expect } from '@playwright/test'; + 3 | +> 4 | throw new Error('Oh my!'); + | ^ + `); +}); + test('should show the codeframe in errors', async ({ runInlineTest }) => { const result = await runInlineTest({ 'package.json': `{ "type": "module" }`, @@ -764,3 +790,69 @@ test('should exit after merge-reports', async ({ runInlineTest, mergeReports }) const { exitCode } = await mergeReports(test.info().outputPath('blob-report'), undefined, { additionalArgs: ['-c', 'merge.config.ts'] }); expect(exitCode).toBe(0); }); + +test('should load esm -> cjs -> cjs require tree without stalling', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35706' }, +}, async ({ runInlineTest }) => { + test.setTimeout(10000); + const result = await runInlineTest({ + 'playwright.config.js': ` + export default { projects: [{name: 'foo'}] }; + `, + 'package.json': JSON.stringify({ type: 'module' }), + 'a.esm.test.js': ` + import { test, expect } from '@playwright/test'; + import * as root from './nested/root_require.js'; + test('check project name', ({}, testInfo) => { + console.log('root', root); + expect(testInfo.project.name).toBe('foo'); + }); + `, + 'nested/package.json': JSON.stringify({ type: 'commonjs' }), + 'nested/root_require.js': ` + const nested = require('./nested_require.js'); + console.log(nested); + `, + 'nested/nested_require.js': ` + console.log('nested require'); + `, + }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +}); + +test('should have source maps when throwing during nested module import', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35706' }, +}, async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'package.json': JSON.stringify({ type: 'module' }), + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + import { foo } from './import1.ts'; + test('dummy', () => { + expect(foo).toBe('foo'); + }); + `, + 'import1.ts': ` + export { foo } from './import2.ts'; + `, + 'import2.ts': ` + type Foo = { bar: string }; + const fs = require('fs'); + throw new Error('Oh no!'); + export const foo: Foo = { bar: 'foo' }; + console.log(fs.existsSync('does-not-exist.txt')); + `, + }); + + expect(result.exitCode).toBe(1); + expect(result.output).toContain(`require is not defined in ES module scope`); + expect(result.output).toContain([ + ` 2 | type Foo = { bar: string };`, + `> 3 | const fs = require('fs');`, + ` | ^`, + ].join('\n')); + + expect(result.output).toContain(`at import2.ts:3`); +}); From 85519de71bf76f2fb78ba8f42945c0a471d5a5ab Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 4 Jun 2025 13:50:57 +0100 Subject: [PATCH 003/222] chore: update browser_patches to 9768715bd (#36191) --- browser_patches/firefox/UPSTREAM_CONFIG.sh | 2 +- browser_patches/firefox/juggler/Helper.js | 10 +- .../firefox/juggler/JugglerFrameParent.jsm | 8 +- .../firefox/juggler/NetworkObserver.js | 76 +- .../firefox/juggler/SimpleChannel.js | 5 +- .../firefox/juggler/TargetRegistry.js | 129 +- .../firefox/juggler/components/Juggler.js | 29 +- .../juggler/components/components.conf | 2 +- .../firefox/juggler/content/FrameTree.js | 9 +- .../juggler/content/JugglerFrameChild.jsm | 7 +- .../firefox/juggler/content/PageAgent.js | 13 +- .../firefox/juggler/content/Runtime.js | 8 +- .../firefox/juggler/content/main.js | 19 +- .../juggler/protocol/BrowserHandler.js | 15 +- .../firefox/juggler/protocol/Dispatcher.js | 11 +- .../firefox/juggler/protocol/PageHandler.js | 15 +- .../juggler/protocol/PrimitiveTypes.js | 7 +- .../firefox/juggler/protocol/Protocol.js | 6 +- .../firefox/patches/bootstrap.diff | 375 ++-- .../firefox/preferences/playwright.cfg | 8 + browser_patches/webkit/UPSTREAM_CONFIG.sh | 2 +- .../embedder/Playwright/win/MainWindow.cpp | 4 +- .../embedder/Playwright/win/Playwright.ico | Bin 122845 -> 113314 bytes browser_patches/webkit/patches/bootstrap.diff | 1989 ++++++++--------- 24 files changed, 1378 insertions(+), 1371 deletions(-) diff --git a/browser_patches/firefox/UPSTREAM_CONFIG.sh b/browser_patches/firefox/UPSTREAM_CONFIG.sh index 5ae9758e38d0e..db9424fcf3779 100644 --- a/browser_patches/firefox/UPSTREAM_CONFIG.sh +++ b/browser_patches/firefox/UPSTREAM_CONFIG.sh @@ -1,3 +1,3 @@ REMOTE_URL="https://github.com/mozilla/gecko-dev" BASE_BRANCH="release" -BASE_REVISION="5e1efb776a56e399f6810204a2eca13f18a3eba6" +BASE_REVISION="9cbfae27052e4aaeb064d2d08e7e869f31ee4288" diff --git a/browser_patches/firefox/juggler/Helper.js b/browser_patches/firefox/juggler/Helper.js index f5a64d6c8bc33..875dd5ac2e8b3 100644 --- a/browser_patches/firefox/juggler/Helper.js +++ b/browser_patches/firefox/juggler/Helper.js @@ -4,9 +4,9 @@ const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); -class Helper { +export class Helper { decorateAsEventEmitter(objectToDecorate) { - const { EventEmitter } = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); + const { EventEmitter } = ChromeUtils.importESModule('resource://gre/modules/EventEmitter.sys.mjs'); const emitter = new EventEmitter(); objectToDecorate.on = emitter.on.bind(emitter); objectToDecorate.addEventListener = emitter.on.bind(emitter); @@ -172,7 +172,7 @@ class Helper { const helper = new Helper(); -class EventWatcher { +export class EventWatcher { constructor(receiver, eventNames, pendingEventWatchers = new Set()) { this._pendingEventWatchers = pendingEventWatchers; this._pendingEventWatchers.add(this); @@ -233,7 +233,3 @@ class EventWatcher { } } -var EXPORTED_SYMBOLS = [ "Helper", "EventWatcher" ]; -this.Helper = Helper; -this.EventWatcher = EventWatcher; - diff --git a/browser_patches/firefox/juggler/JugglerFrameParent.jsm b/browser_patches/firefox/juggler/JugglerFrameParent.jsm index e621fab240680..9c3b58898955e 100644 --- a/browser_patches/firefox/juggler/JugglerFrameParent.jsm +++ b/browser_patches/firefox/juggler/JugglerFrameParent.jsm @@ -1,13 +1,11 @@ "use strict"; -const { TargetRegistry } = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js'); -const { Helper } = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const { TargetRegistry } = ChromeUtils.importESModule('chrome://juggler/content/TargetRegistry.js'); +const { Helper } = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); const helper = new Helper(); -var EXPORTED_SYMBOLS = ['JugglerFrameParent']; - -class JugglerFrameParent extends JSWindowActorParent { +export class JugglerFrameParent extends JSWindowActorParent { constructor() { super(); } diff --git a/browser_patches/firefox/juggler/NetworkObserver.js b/browser_patches/firefox/juggler/NetworkObserver.js index 3fc9368d81c68..e3df3b15b7bb0 100644 --- a/browser_patches/firefox/juggler/NetworkObserver.js +++ b/browser_patches/firefox/juggler/NetworkObserver.js @@ -4,9 +4,9 @@ "use strict"; -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); -const { ChannelEventSinkFactory } = ChromeUtils.import("chrome://remote/content/cdp/observers/ChannelEventSink.jsm"); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs'); +const { ChannelEventSinkFactory } = ChromeUtils.importESModule("chrome://remote/content/cdp/observers/ChannelEventSink.sys.mjs"); const Cc = Components.classes; @@ -28,7 +28,7 @@ const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024; const pageNetworkSymbol = Symbol('PageNetwork'); -class PageNetwork { +export class PageNetwork { static forPageTarget(target) { if (!target) return undefined; @@ -143,9 +143,10 @@ class NetworkRequest { const target = this._networkObserver._targetRegistry.targetForBrowserId(browsingContext.browserId); this._pageNetwork = PageNetwork.forPageTarget(target); } - this._expectingInterception = false; + this._shouldYieldInterceptionToServiceWorker = false; this._expectingResumedRequest = undefined; // { method, headers, postData } this._overriddenHeadersForRedirect = redirectedFrom?._overriddenHeadersForRedirect; + this._sentOnRequest = false; this._sentOnResponse = false; this._fulfilled = false; @@ -318,9 +319,8 @@ class NetworkRequest { const interceptController = this._fallThroughInterceptController(); if (interceptController && interceptController.shouldPrepareForIntercept(aURI, channel)) { // We assume that interceptController is a service worker if there is one, - // and yield interception to it. We are not going to intercept ourselves, - // so we send onRequest now. - this._sendOnRequest(false); + // and yield interception to it. + this._shouldYieldInterceptionToServiceWorker = true; return true; } @@ -329,12 +329,6 @@ class NetworkRequest { return false; } - // We do not want to intercept any redirects, because we are not able - // to intercept subresource redirects, and it's unreliable for main requests. - // We do not sendOnRequest here, because redirects do that in constructor. - if (this.redirectedFromId) - return false; - const shouldIntercept = this._shouldIntercept(); if (!shouldIntercept) { // We are not intercepting - ready to issue onRequest. @@ -342,21 +336,24 @@ class NetworkRequest { return false; } - this._expectingInterception = true; return true; } // nsINetworkInterceptController channelIntercepted(intercepted) { - if (!this._expectingInterception) { - // We are not intercepting, fall-through. - const interceptController = this._fallThroughInterceptController(); - if (interceptController) - interceptController.channelIntercepted(intercepted); + // Yield to a service worker if determined so in shouldPrepareForIntercept(). + const serviceWorker = this._shouldYieldInterceptionToServiceWorker ? this._fallThroughInterceptController() : undefined; + // Clear the flag to avoid an infinite loop. After service worker, we should intercept ourselves. + this._shouldYieldInterceptionToServiceWorker = false; + + if (serviceWorker) { + const interceptedChannel = intercepted.QueryInterface(Ci.nsIInterceptedChannel); + // If service worker will not actually intercept the request, we want to be called again. + interceptedChannel.interceptAfterServiceWorkerResets(); + serviceWorker.channelIntercepted(intercepted); return; } - this._expectingInterception = false; this._interceptedChannel = intercepted.QueryInterface(Ci.nsIInterceptedChannel); const pageNetwork = this._pageNetwork; @@ -410,6 +407,7 @@ class NetworkRequest { // See https://github.com/microsoft/playwright/issues/9418#issuecomment-944836244 if (aRequest !== this.httpChannel) return; + this._sendOnRequest(false); try { this._originalListener.onStartRequest(aRequest); } catch (e) { @@ -447,6 +445,10 @@ class NetworkRequest { } _shouldIntercept() { + // We do not want to intercept any redirects, because we are not able + // to intercept subresource redirects, and it's unreliable for main requests. + if (this.redirectedFromId) + return false; const pageNetwork = this._pageNetwork; if (!pageNetwork) return false; @@ -467,8 +469,15 @@ class NetworkRequest { } _sendOnRequest(isIntercepted) { - // Note: we call _sendOnRequest either after we intercepted the request, - // or at the first moment we know that we are not going to intercept. + if (this._sentOnRequest) { + // We can come here twice because: + // - Redirects call _sendOnRequest in the constructor and from inside interception. + // - All other requests might call _sendOnRequest from onStartRequest and from inside interception. + // - All requests call _sendOnRequest from _sendOnResponse to avoid responses without requests. + return; + } + this._sentOnRequest = true; + const pageNetwork = this._pageNetwork; if (!pageNetwork) return; @@ -491,11 +500,21 @@ class NetworkRequest { } _sendOnResponse(fromCache, opt_statusCode, opt_statusText) { + // For internal redirects, and perhaps something else that we lack test coverage for, + // we can arrive here before onStartRequest has fired. Make sure we + // notify about the request first. + this._sendOnRequest(false); + if (this._sentOnResponse) { - // We can come here twice because of internal redirects, e.g. service workers. + // We can come here twice because of an internal redirect, for example: + // - request was intercepted by a service worker; + // - HSTS redirect; + // - CORS preflight; + // - who knows what else? return; } this._sentOnResponse = true; + const pageNetwork = this._pageNetwork; if (!pageNetwork) return; @@ -576,7 +595,7 @@ class NetworkRequest { } } -class NetworkObserver { +export class NetworkObserver { static instance() { return NetworkObserver._instance || null; } @@ -776,6 +795,10 @@ function clearRequestHeaders(httpChannel) { // We cannot remove the "host" header. if (header.name.toLowerCase() === 'host') continue; + // Keep the "cookie" header. If there is an override, it will be set anyway. + // Otherwise, we may delete a cookie that was set for a redirect. + if (header.name.toLowerCase() === 'cookie') + continue; httpChannel.setRequestHeader(header.name, '', false /* merge */); } } @@ -964,6 +987,3 @@ PageNetwork.Events = { RequestFailed: Symbol('PageNetwork.Events.RequestFailed'), }; -var EXPORTED_SYMBOLS = ['NetworkObserver', 'PageNetwork']; -this.NetworkObserver = NetworkObserver; -this.PageNetwork = PageNetwork; diff --git a/browser_patches/firefox/juggler/SimpleChannel.js b/browser_patches/firefox/juggler/SimpleChannel.js index a4e1b19df4c6d..cc555451cdd49 100644 --- a/browser_patches/firefox/juggler/SimpleChannel.js +++ b/browser_patches/firefox/juggler/SimpleChannel.js @@ -79,7 +79,7 @@ class SimpleChannel { _setTimeout(cb, timeout) { // Lazy load on first call. - this._setTimeout = ChromeUtils.import('resource://gre/modules/Timer.jsm').setTimeout; + this._setTimeout = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs').setTimeout; this._setTimeout(cb, timeout); } @@ -251,6 +251,3 @@ class SimpleChannel { } } } - -var EXPORTED_SYMBOLS = ['SimpleChannel']; -this.SimpleChannel = SimpleChannel; diff --git a/browser_patches/firefox/juggler/TargetRegistry.js b/browser_patches/firefox/juggler/TargetRegistry.js index 2ffb53390d5a7..4bdf162ab64f7 100644 --- a/browser_patches/firefox/juggler/TargetRegistry.js +++ b/browser_patches/firefox/juggler/TargetRegistry.js @@ -2,12 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); -const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm"); -const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm"); -const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); -const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {Preferences} = ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs"); +const {ContextualIdentityService} = ChromeUtils.importESModule("resource://gre/modules/ContextualIdentityService.sys.mjs"); +const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs'); +const {AppConstants} = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); const Cr = Components.results; @@ -23,6 +22,7 @@ const ALL_PERMISSIONS = [ let globalTabAndWindowActivationChain = Promise.resolve(); // This is a workaround for https://github.com/microsoft/playwright/issues/34586 +let didCreateFirstPage = false; let globalNewPageChain = Promise.resolve(); class DownloadInterceptor { @@ -104,7 +104,7 @@ class DownloadInterceptor { const screencastService = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService); -class TargetRegistry { +export class TargetRegistry { static instance() { return TargetRegistry._instance || null; } @@ -310,61 +310,68 @@ class TargetRegistry { } async newPage({browserContextId}) { - const result = globalNewPageChain.then(async () => { - const browserContext = this.browserContextForId(browserContextId); - const features = "chrome,dialog=no,all"; - // See _callWithURIToLoad in browser.js for the structure of window.arguments - // window.arguments[1]: unused (bug 871161) - // [2]: referrerInfo (nsIReferrerInfo) - // [3]: postData (nsIInputStream) - // [4]: allowThirdPartyFixup (bool) - // [5]: userContextId (int) - // [6]: originPrincipal (nsIPrincipal) - // [7]: originStoragePrincipal (nsIPrincipal) - // [8]: triggeringPrincipal (nsIPrincipal) - // [9]: allowInheritPrincipal (bool) - // [10]: csp (nsIContentSecurityPolicy) - // [11]: nsOpenWindowInfo - const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); - const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance( - Ci.nsISupportsString - ); - urlSupports.data = 'about:blank'; - args.appendElement(urlSupports); // 0 - args.appendElement(undefined); // 1 - args.appendElement(undefined); // 2 - args.appendElement(undefined); // 3 - args.appendElement(undefined); // 4 - const userContextIdSupports = Cc[ - "@mozilla.org/supports-PRUint32;1" - ].createInstance(Ci.nsISupportsPRUint32); - userContextIdSupports.data = browserContext.userContextId; - args.appendElement(userContextIdSupports); // 5 - args.appendElement(undefined); // 6 - args.appendElement(undefined); // 7 - args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8 - - const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args); - await waitForWindowReady(window); - if (window.gBrowser.browsers.length !== 1) - throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`); - const browser = window.gBrowser.browsers[0]; - let target = this._browserToTarget.get(browser); - while (!target) { - await helper.awaitEvent(this, TargetRegistry.Events.TargetCreated); - target = this._browserToTarget.get(browser); - } - browser.focus(); - if (browserContext.crossProcessCookie.settings.timezoneId) { - if (await target.hasFailedToOverrideTimezone()) - throw new Error('Failed to override timezone'); - } - return target.id(); - }); + // When creating the very first page, we cannot create multiple in parallel. + // See https://github.com/microsoft/playwright/issues/34586. + if (didCreateFirstPage) + return this._newPageInternal({browserContextId}); + const result = globalNewPageChain.then(() => this._newPageInternal({browserContextId})); globalNewPageChain = result.catch(error => { /* swallow errors to keep chain running */ }); return result; } + async _newPageInternal({browserContextId}) { + const browserContext = this.browserContextForId(browserContextId); + const features = "chrome,dialog=no,all"; + // See _callWithURIToLoad in browser.js for the structure of window.arguments + // window.arguments[1]: unused (bug 871161) + // [2]: referrerInfo (nsIReferrerInfo) + // [3]: postData (nsIInputStream) + // [4]: allowThirdPartyFixup (bool) + // [5]: userContextId (int) + // [6]: originPrincipal (nsIPrincipal) + // [7]: originStoragePrincipal (nsIPrincipal) + // [8]: triggeringPrincipal (nsIPrincipal) + // [9]: allowInheritPrincipal (bool) + // [10]: csp (nsIContentSecurityPolicy) + // [11]: nsOpenWindowInfo + const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + urlSupports.data = 'about:blank'; + args.appendElement(urlSupports); // 0 + args.appendElement(undefined); // 1 + args.appendElement(undefined); // 2 + args.appendElement(undefined); // 3 + args.appendElement(undefined); // 4 + const userContextIdSupports = Cc[ + "@mozilla.org/supports-PRUint32;1" + ].createInstance(Ci.nsISupportsPRUint32); + userContextIdSupports.data = browserContext.userContextId; + args.appendElement(userContextIdSupports); // 5 + args.appendElement(undefined); // 6 + args.appendElement(undefined); // 7 + args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8 + + const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args); + await waitForWindowReady(window); + if (window.gBrowser.browsers.length !== 1) + throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`); + const browser = window.gBrowser.browsers[0]; + let target = this._browserToTarget.get(browser); + while (!target) { + await helper.awaitEvent(this, TargetRegistry.Events.TargetCreated); + target = this._browserToTarget.get(browser); + } + browser.focus(); + if (browserContext.crossProcessCookie.settings.timezoneId) { + if (await target.hasFailedToOverrideTimezone()) + throw new Error('Failed to override timezone'); + } + didCreateFirstPage = true; + return target.id(); + } + targets() { return Array.from(this._browserToTarget.values()); } @@ -378,7 +385,7 @@ class TargetRegistry { } } -class PageTarget { +export class PageTarget { constructor(registry, win, tab, browserContext, opener) { helper.decorateAsEventEmitter(this); @@ -1286,7 +1293,3 @@ TargetRegistry.Events = { DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'), ScreencastStopped: Symbol('TargetRegistry.ScreencastStopped'), }; - -var EXPORTED_SYMBOLS = ['TargetRegistry', 'PageTarget']; -this.TargetRegistry = TargetRegistry; -this.PageTarget = PageTarget; diff --git a/browser_patches/firefox/juggler/components/Juggler.js b/browser_patches/firefox/juggler/components/Juggler.js index aa51a8887929f..6121c4d3928bc 100644 --- a/browser_patches/firefox/juggler/components/Juggler.js +++ b/browser_patches/firefox/juggler/components/Juggler.js @@ -2,16 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -var EXPORTED_SYMBOLS = ["Juggler", "JugglerFactory"]; - -const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -const {ComponentUtils} = ChromeUtils.import("resource://gre/modules/ComponentUtils.jsm"); -const {Dispatcher} = ChromeUtils.import("chrome://juggler/content/protocol/Dispatcher.js"); -const {BrowserHandler} = ChromeUtils.import("chrome://juggler/content/protocol/BrowserHandler.js"); -const {NetworkObserver} = ChromeUtils.import("chrome://juggler/content/NetworkObserver.js"); -const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js"); -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {ActorManagerParent} = ChromeUtils.import('resource://gre/modules/ActorManagerParent.jsm'); +// Load SimpleChannel in browser-process global. +Services.scriptloader.loadSubScript('chrome://juggler/content/SimpleChannel.js'); + +const {XPCOMUtils} = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); +const {ComponentUtils} = ChromeUtils.importESModule("resource://gre/modules/ComponentUtils.sys.mjs"); +const {Dispatcher} = ChromeUtils.importESModule("chrome://juggler/content/protocol/Dispatcher.js"); +const {BrowserHandler} = ChromeUtils.importESModule("chrome://juggler/content/protocol/BrowserHandler.js"); +const {NetworkObserver} = ChromeUtils.importESModule("chrome://juggler/content/NetworkObserver.js"); +const {TargetRegistry} = ChromeUtils.importESModule("chrome://juggler/content/TargetRegistry.js"); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {ActorManagerParent} = ChromeUtils.importESModule('resource://gre/modules/ActorManagerParent.sys.mjs'); const helper = new Helper(); const Cc = Components.classes; @@ -21,10 +22,10 @@ const Ci = Components.interfaces; ActorManagerParent.addJSWindowActors({ JugglerFrame: { parent: { - moduleURI: 'chrome://juggler/content/JugglerFrameParent.jsm', + esModuleURI: 'chrome://juggler/content/JugglerFrameParent.jsm', }, child: { - moduleURI: 'chrome://juggler/content/content/JugglerFrameChild.jsm', + esModuleURI: 'chrome://juggler/content/content/JugglerFrameChild.jsm', events: { // Normally, we instantiate an actor when a new window is created. DOMWindowCreated: {}, @@ -45,7 +46,7 @@ ActorManagerParent.addJSWindowActors({ let browserStartupFinishedCallback; let browserStartupFinishedPromise = new Promise(x => browserStartupFinishedCallback = x); -class Juggler { +export class Juggler { get classDescription() { return "Sample command-line handler"; } get classID() { return Components.ID('{f7a74a33-e2ab-422d-b022-4fb213dd2639}'); } get contractID() { return "@mozilla.org/remote/juggler;1" } @@ -154,7 +155,7 @@ class Juggler { const jugglerInstance = new Juggler(); // This is used by the XPCOM codepath which expects a constructor -var JugglerFactory = function() { +export var JugglerFactory = function() { return jugglerInstance; }; diff --git a/browser_patches/firefox/juggler/components/components.conf b/browser_patches/firefox/juggler/components/components.conf index e5bc6523b10cd..955b38ab9ccec 100644 --- a/browser_patches/firefox/juggler/components/components.conf +++ b/browser_patches/firefox/juggler/components/components.conf @@ -11,7 +11,7 @@ Classes = [ "command-line-handler": "m-remote", "profile-after-change": "Juggler", }, - "jsm": "chrome://juggler/content/components/Juggler.js", + "esModule": "chrome://juggler/content/components/Juggler.js", "constructor": "JugglerFactory", }, ] diff --git a/browser_patches/firefox/juggler/content/FrameTree.js b/browser_patches/firefox/juggler/content/FrameTree.js index 721f392b9bccb..09a95b3548df9 100644 --- a/browser_patches/firefox/juggler/content/FrameTree.js +++ b/browser_patches/firefox/juggler/content/FrameTree.js @@ -7,13 +7,11 @@ const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); -const {Runtime} = ChromeUtils.import('chrome://juggler/content/content/Runtime.js'); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); const helper = new Helper(); -class FrameTree { +export class FrameTree { constructor(rootBrowsingContext) { helper.decorateAsEventEmitter(this); @@ -690,6 +688,3 @@ function channelId(channel) { } -var EXPORTED_SYMBOLS = ['FrameTree']; -this.FrameTree = FrameTree; - diff --git a/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm b/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm index 05d91473d3a8d..af060c57aac90 100644 --- a/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm +++ b/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm @@ -1,7 +1,7 @@ "use strict"; -const { Helper } = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const { initialize } = ChromeUtils.import('chrome://juggler/content/content/main.js'); +const { Helper } = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const { initialize } = ChromeUtils.importESModule('chrome://juggler/content/content/main.js'); const Ci = Components.interfaces; const helper = new Helper(); @@ -10,7 +10,7 @@ let sameProcessInstanceNumber = 0; const topBrowingContextToAgents = new Map(); -class JugglerFrameChild extends JSWindowActorChild { +export class JugglerFrameChild extends JSWindowActorChild { constructor() { super(); @@ -83,4 +83,3 @@ class JugglerFrameChild extends JSWindowActorChild { receiveMessage() { } } -var EXPORTED_SYMBOLS = ['JugglerFrameChild']; diff --git a/browser_patches/firefox/juggler/content/PageAgent.js b/browser_patches/firefox/juggler/content/PageAgent.js index 255b84f726662..2ff5e6b922b55 100644 --- a/browser_patches/firefox/juggler/content/PageAgent.js +++ b/browser_patches/firefox/juggler/content/PageAgent.js @@ -8,9 +8,9 @@ const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); -const {setTimeout} = ChromeUtils.import('resource://gre/modules/Timer.jsm'); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs'); +const {setTimeout} = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs'); const dragService = Cc["@mozilla.org/widget/dragservice;1"].getService( Ci.nsIDragService @@ -51,7 +51,7 @@ class WorkerData { } } -class PageAgent { +export class PageAgent { constructor(browserChannel, frameTree) { this._browserChannel = browserChannel; this._browserPage = browserChannel.connect('page'); @@ -575,7 +575,7 @@ class PageAgent { // We crash by using js-ctypes and dereferencing // a bad pointer. The crash should happen immediately // upon loading this frame script. - const { ctypes } = ChromeUtils.import('resource://gre/modules/ctypes.jsm'); + const { ctypes } = ChromeUtils.importESModule('resource://gre/modules/ctypes.sys.mjs'); ChromeUtils.privateNoteIntentionalCrash(); const zero = new ctypes.intptr_t(8); const badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); @@ -709,6 +709,3 @@ class PageAgent { } } -var EXPORTED_SYMBOLS = ['PageAgent']; -this.PageAgent = PageAgent; - diff --git a/browser_patches/firefox/juggler/content/Runtime.js b/browser_patches/firefox/juggler/content/Runtime.js index a5fa7a03dd343..ad8720a1d3e36 100644 --- a/browser_patches/firefox/juggler/content/Runtime.js +++ b/browser_patches/firefox/juggler/content/Runtime.js @@ -8,8 +8,8 @@ if (!this.Debugger) { // Worker has a Debugger defined already. - const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {}); - addDebuggerToGlobal(Components.utils.getGlobalForObject(this)); + const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); + addDebuggerToGlobal(Components.utils.getGlobalForObject(globalThis)); } let lastId = 0; @@ -596,5 +596,5 @@ function emitEvent(event, ...args) { listener.call(null, ...args); } -var EXPORTED_SYMBOLS = ['Runtime']; -this.Runtime = Runtime; +// Export Runtime to global. +globalThis.Runtime = Runtime; diff --git a/browser_patches/firefox/juggler/content/main.js b/browser_patches/firefox/juggler/content/main.js index 7eaa704059711..68fb364eadb4e 100644 --- a/browser_patches/firefox/juggler/content/main.js +++ b/browser_patches/firefox/juggler/content/main.js @@ -2,14 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js'); -const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); -const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js'); +// Load SimpleChannel and Runtime in content process's global. +// NOTE: since these have to exist in both Worker and main threads, and we do +// not know a way to load ES Modules in worker threads, we have to use the loadSubScript +// utility instead. +Services.scriptloader.loadSubScript('chrome://juggler/content/SimpleChannel.js'); +Services.scriptloader.loadSubScript('chrome://juggler/content/content/Runtime.js'); + +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {FrameTree} = ChromeUtils.importESModule('chrome://juggler/content/content/FrameTree.js'); +const {PageAgent} = ChromeUtils.importESModule('chrome://juggler/content/content/PageAgent.js'); const helper = new Helper(); -function initialize(browsingContext, docShell) { +export function initialize(browsingContext, docShell) { const data = { channel: undefined, pageAgent: undefined, frameTree: undefined, failedToOverrideTimezone: false }; const applySetting = { @@ -114,6 +120,3 @@ function initialize(browsingContext, docShell) { return data; } - -var EXPORTED_SYMBOLS = ['initialize']; -this.initialize = initialize; diff --git a/browser_patches/firefox/juggler/protocol/BrowserHandler.js b/browser_patches/firefox/juggler/protocol/BrowserHandler.js index e92b6dfefeb5b..035a53bb688c9 100644 --- a/browser_patches/firefox/juggler/protocol/BrowserHandler.js +++ b/browser_patches/firefox/juggler/protocol/BrowserHandler.js @@ -4,15 +4,15 @@ "use strict"; -const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); -const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js"); -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {PageHandler} = ChromeUtils.import("chrome://juggler/content/protocol/PageHandler.js"); -const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); +const {AddonManager} = ChromeUtils.importESModule("resource://gre/modules/AddonManager.sys.mjs"); +const {TargetRegistry} = ChromeUtils.importESModule("chrome://juggler/content/TargetRegistry.js"); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {PageHandler} = ChromeUtils.importESModule("chrome://juggler/content/protocol/PageHandler.js"); +const {AppConstants} = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); const helper = new Helper(); -class BrowserHandler { +export class BrowserHandler { constructor(session, dispatcher, targetRegistry, startCompletePromise, onclose) { this._session = session; this._dispatcher = dispatcher; @@ -313,6 +313,3 @@ async function waitForWindowClosed(browserWindow) { function nullToUndefined(value) { return value === null ? undefined : value; } - -var EXPORTED_SYMBOLS = ['BrowserHandler']; -this.BrowserHandler = BrowserHandler; diff --git a/browser_patches/firefox/juggler/protocol/Dispatcher.js b/browser_patches/firefox/juggler/protocol/Dispatcher.js index 8542461d529eb..7a65d0a1170c1 100644 --- a/browser_patches/firefox/juggler/protocol/Dispatcher.js +++ b/browser_patches/firefox/juggler/protocol/Dispatcher.js @@ -2,12 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/protocol/Protocol.js"); -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {protocol} = ChromeUtils.importESModule("chrome://juggler/content/protocol/Protocol.js"); +const {checkScheme} = ChromeUtils.importESModule("chrome://juggler/content/protocol/PrimitiveTypes.js"); +const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); const helper = new Helper(); -class Dispatcher { +export class Dispatcher { /** * @param {Connection} connection */ @@ -132,7 +133,3 @@ class ProtocolSession { return await this._handler[method](params); } } - -this.EXPORTED_SYMBOLS = ['Dispatcher']; -this.Dispatcher = Dispatcher; - diff --git a/browser_patches/firefox/juggler/protocol/PageHandler.js b/browser_patches/firefox/juggler/protocol/PageHandler.js index 5892c8860331f..1dab7d8803782 100644 --- a/browser_patches/firefox/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox/juggler/protocol/PageHandler.js @@ -4,11 +4,11 @@ "use strict"; -const {Helper, EventWatcher} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); -const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js'); -const {PageTarget} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js'); -const {setTimeout} = ChromeUtils.import('resource://gre/modules/Timer.jsm'); +const {Helper, EventWatcher} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js'); +const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs'); +const {NetworkObserver, PageNetwork} = ChromeUtils.importESModule('chrome://juggler/content/NetworkObserver.js'); +const {PageTarget} = ChromeUtils.importESModule('chrome://juggler/content/TargetRegistry.js'); +const {setTimeout} = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs'); const Cc = Components.classes; const Ci = Components.interfaces; @@ -65,7 +65,7 @@ class WorkerHandler { } } -class PageHandler { +export class PageHandler { constructor(target, session, contentChannel) { this._session = session; this._contentChannel = contentChannel; @@ -691,6 +691,3 @@ class PageHandler { return await worker.sendMessage(JSON.parse(message)); } } - -var EXPORTED_SYMBOLS = ['PageHandler']; -this.PageHandler = PageHandler; diff --git a/browser_patches/firefox/juggler/protocol/PrimitiveTypes.js b/browser_patches/firefox/juggler/protocol/PrimitiveTypes.js index 5799038f19cbc..ca6d1ec4f60f2 100644 --- a/browser_patches/firefox/juggler/protocol/PrimitiveTypes.js +++ b/browser_patches/firefox/juggler/protocol/PrimitiveTypes.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const t = {}; +export const t = {}; t.String = function(x, details = {}, path = ['']) { if (typeof x === 'string' || typeof x === 'String') @@ -96,7 +96,7 @@ function beauty(path, obj) { return `property "${path.join('.')}" - ${JSON.stringify(obj, null, 2)}`; } -function checkScheme(scheme, x, details = {}, path = ['']) { +export function checkScheme(scheme, x, details = {}, path = ['']) { if (!scheme) throw new Error(`ILLDEFINED SCHEME: ${path.join('.')}`); if (typeof scheme === 'object') { @@ -142,6 +142,3 @@ test(t.Either(t.String, t.Number), {}); */ -this.t = t; -this.checkScheme = checkScheme; -this.EXPORTED_SYMBOLS = ['t', 'checkScheme']; diff --git a/browser_patches/firefox/juggler/protocol/Protocol.js b/browser_patches/firefox/juggler/protocol/Protocol.js index f18dff61bf17f..4cb44d679ce0b 100644 --- a/browser_patches/firefox/juggler/protocol/Protocol.js +++ b/browser_patches/firefox/juggler/protocol/Protocol.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js'); +const {t} = ChromeUtils.importESModule('chrome://juggler/content/protocol/PrimitiveTypes.js'); // Protocol-specific types. const browserTypes = {}; @@ -1018,8 +1018,6 @@ const Accessibility = { } } -this.protocol = { +export const protocol = { domains: {Browser, Heap, Page, Runtime, Network, Accessibility}, }; -this.checkScheme = checkScheme; -this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme']; diff --git a/browser_patches/firefox/patches/bootstrap.diff b/browser_patches/firefox/patches/bootstrap.diff index 094a734019258..8cb0e71d92a20 100644 --- a/browser_patches/firefox/patches/bootstrap.diff +++ b/browser_patches/firefox/patches/bootstrap.diff @@ -89,7 +89,7 @@ index 8167d2b81c918e02ce757f7f448f22e07c29d140..3ae798880acfd8aa965ae08051f2f818 DWORD creationFlags = CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT; diff --git a/browser/installer/allowed-dupes.mn b/browser/installer/allowed-dupes.mn -index 213a99ed433d5219c2b9a64baad82d14cdbcd432..ee4f6484cdfe80899c28a1d9607494e520bfc93d 100644 +index a097c2c56a665204ff7b5593c7faf836366801cf..235a4e224e08c22870c6913e335f0b6020b3e7da 100644 --- a/browser/installer/allowed-dupes.mn +++ b/browser/installer/allowed-dupes.mn @@ -67,6 +67,12 @@ browser/features/webcompat@mozilla.org/shims/empty-shim.txt @@ -106,7 +106,7 @@ index 213a99ed433d5219c2b9a64baad82d14cdbcd432..ee4f6484cdfe80899c28a1d9607494e5 browser/chrome/browser/content/activity-stream/data/content/tippytop/favicons/allegro-pl.ico browser/defaults/settings/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in -index beae3018bb533529555496433b90403827ba07fc..3f3c750f4cef768b5429492c0077616505262cb9 100644 +index 89cff6b562a82bd3a9a5d268abd4373199b31fac..e58dbf0ab0b06e84f6dac64698d11c6268091204 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -196,6 +196,9 @@ @@ -167,10 +167,10 @@ index d49c6fbf1bf83b832795fa674f6b41f223eef812..7ea3540947ff5f61b15f27fbf4b95564 const transportProvider = { setListener(upgradeListener) { diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp -index 28e9d14bd7979798025f9fc30d9a45527488c34c..d6abf1c3e7e2b2cdfd51351ced4aa6fb0d51327b 100644 +index 2425779a7767e9350ee2afc4aea111a090c7f909..393eb86bf2d9a8f778bfce560a9fb3bf528ba558 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp -@@ -106,8 +106,15 @@ struct ParamTraits +@@ -108,8 +108,15 @@ struct ParamTraits template <> struct ParamTraits @@ -188,7 +188,7 @@ index 28e9d14bd7979798025f9fc30d9a45527488c34c..d6abf1c3e7e2b2cdfd51351ced4aa6fb template <> struct ParamTraits -@@ -2887,6 +2894,32 @@ void BrowsingContext::DidSet(FieldIndex, +@@ -2891,6 +2898,32 @@ void BrowsingContext::DidSet(FieldIndex, PresContextAffectingFieldChanged(); } @@ -222,7 +222,7 @@ index 28e9d14bd7979798025f9fc30d9a45527488c34c..d6abf1c3e7e2b2cdfd51351ced4aa6fb nsString&& aOldValue) { MOZ_ASSERT(IsTop()); diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h -index 13815ddf7e13825970cafda19ca24412a5150b53..71c7ae2fce1d92614a60b2aba85bbd70629f682f 100644 +index eb183cb5c0751e43ea674f9e52441a5a82f186e0..79c5d8110faa89779dd0c16ba00620e7e65d06f5 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -203,10 +203,10 @@ struct EmbedderColorSchemes { @@ -248,7 +248,7 @@ index 13815ddf7e13825970cafda19ca24412a5150b53..71c7ae2fce1d92614a60b2aba85bbd70 /* The number of entries added to the session history because of this \ * browsing context. */ \ FIELD(HistoryEntryCount, uint32_t) \ -@@ -948,6 +951,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -950,6 +953,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { return GetForcedColorsOverride(); } @@ -263,7 +263,7 @@ index 13815ddf7e13825970cafda19ca24412a5150b53..71c7ae2fce1d92614a60b2aba85bbd70 bool IsInBFCache() const; bool AllowJavascript() const { return GetAllowJavascript(); } -@@ -1107,6 +1118,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -1112,6 +1123,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { return IsTop(); } @@ -275,7 +275,7 @@ index 13815ddf7e13825970cafda19ca24412a5150b53..71c7ae2fce1d92614a60b2aba85bbd70 bool CanSet(FieldIndex, dom::ForcedColorsOverride, ContentParent*) { return IsTop(); -@@ -1125,10 +1141,22 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -1130,10 +1146,22 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { void DidSet(FieldIndex, dom::ForcedColorsOverride aOldValue); @@ -299,10 +299,10 @@ index 13815ddf7e13825970cafda19ca24412a5150b53..71c7ae2fce1d92614a60b2aba85bbd70 bool CanSet(FieldIndex, bool, ContentParent*) { diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp -index 671a12de84d3b01c4331dbbb2fac050ce061cda1..afcd95e77185e6d606503b7c01d6ab48cc236cc8 100644 +index bef42c91d6f88922c8c101f3675325d828872aaf..8eb68c441fbef8ecbe5e90c118ccc00813564577 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp -@@ -323,6 +323,8 @@ void CanonicalBrowsingContext::ReplacedBy( +@@ -324,6 +324,8 @@ void CanonicalBrowsingContext::ReplacedBy( txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart()); txn.SetForceOffline(GetForceOffline()); txn.SetTopInnerSizeForRFP(GetTopInnerSizeForRFP()); @@ -311,7 +311,7 @@ index 671a12de84d3b01c4331dbbb2fac050ce061cda1..afcd95e77185e6d606503b7c01d6ab48 // Propagate some settings on BrowsingContext replacement so they're not lost // on bfcached navigations. These are important for GeckoView (see bug -@@ -1600,6 +1602,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, +@@ -1635,6 +1637,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, return; } @@ -325,7 +325,7 @@ index 671a12de84d3b01c4331dbbb2fac050ce061cda1..afcd95e77185e6d606503b7c01d6ab48 } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp -index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af15704738700b0e14f 100644 +index 32c537d6be90247af8d778048c6d27f3800d4b02..b72196b0694828489f8ad27c209f49f0d41c43cb 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -15,6 +15,12 @@ @@ -348,16 +348,16 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 +#include "mozilla/dom/Geolocation.h" #include "mozilla/dom/HTMLAnchorElement.h" #include "mozilla/dom/HTMLIFrameElement.h" - #include "mozilla/dom/PerformanceNavigation.h" -@@ -90,6 +97,7 @@ - #include "mozilla/dom/JSWindowActorChild.h" + #include "mozilla/dom/Navigation.h" +@@ -94,6 +101,7 @@ #include "mozilla/dom/DocumentBinding.h" + #include "mozilla/glean/DocshellMetrics.h" #include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/dom/WorkerCommon.h" #include "mozilla/net/DocumentChannel.h" #include "mozilla/net/DocumentChannelChild.h" #include "mozilla/net/ParentChannelWrapper.h" -@@ -113,6 +121,7 @@ +@@ -117,6 +125,7 @@ #include "nsIDocumentViewer.h" #include "mozilla/dom/Document.h" #include "nsHTMLDocument.h" @@ -365,7 +365,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 #include "nsIDocumentLoaderFactory.h" #include "nsIDOMWindow.h" #include "nsIEditingSession.h" -@@ -207,6 +216,7 @@ +@@ -211,6 +220,7 @@ #include "nsGlobalWindowInner.h" #include "nsGlobalWindowOuter.h" #include "nsJSEnvironment.h" @@ -373,7 +373,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsObjectLoadingContent.h" -@@ -347,6 +357,14 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, +@@ -352,6 +362,14 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, mAllowDNSPrefetch(true), mAllowWindowControl(true), mCSSErrorReportingEnabled(false), @@ -388,7 +388,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 mAllowAuth(mItemType == typeContent), mAllowKeywordFixup(false), mDisableMetaRefreshWhenInactive(false), -@@ -3019,6 +3037,232 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { +@@ -3024,6 +3042,232 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { return NS_OK; } @@ -621,7 +621,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 NS_IMETHODIMP nsDocShell::GetIsNavigating(bool* aOut) { *aOut = mIsNavigating; -@@ -4715,7 +4959,7 @@ nsDocShell::GetVisibility(bool* aVisibility) { +@@ -4731,7 +4975,7 @@ nsDocShell::GetVisibility(bool* aVisibility) { } void nsDocShell::ActivenessMaybeChanged() { @@ -630,7 +630,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 if (RefPtr presShell = GetPresShell()) { presShell->ActivenessMaybeChanged(); } -@@ -6639,6 +6883,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, +@@ -6658,6 +6902,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, return false; // no entry to save into } @@ -641,7 +641,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 MOZ_ASSERT(!mozilla::SessionHistoryInParent(), "mOSHE cannot be non-null with SHIP"); nsCOMPtr viewer = mOSHE->GetDocumentViewer(); -@@ -8377,6 +8625,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { +@@ -8399,6 +8647,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { true, // aForceNoOpener getter_AddRefs(newBC)); MOZ_ASSERT(!newBC); @@ -654,7 +654,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 return rv; } -@@ -9531,6 +9785,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, +@@ -9572,6 +9826,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr); nsCOMPtr req; @@ -671,7 +671,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req)); if (NS_SUCCEEDED(rv)) { -@@ -12733,6 +12997,9 @@ class OnLinkClickEvent : public Runnable { +@@ -12791,6 +13055,9 @@ class OnLinkClickEvent : public Runnable { mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied, mTriggeringPrincipal); } @@ -681,7 +681,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 return NS_OK; } -@@ -12819,6 +13086,8 @@ nsresult nsDocShell::OnLinkClick( +@@ -12877,6 +13144,8 @@ nsresult nsDocShell::OnLinkClick( nsCOMPtr ev = new OnLinkClickEvent( this, aContent, loadState, noOpenerImplied, aTriggeringPrincipal); @@ -691,7 +691,7 @@ index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af157047387 } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h -index 888741f8490d6f0e885ed0ce73115c16e7bbe821..9cb32a1ec61bc4cb67f5fc5bb1fa723055bb1eaa 100644 +index f22a333733322ad17f097d7edd46af21a687906c..6bcf8ca9f9cd64dc9f5695d00e0a3e6a97978f02 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -15,6 +15,7 @@ @@ -702,7 +702,7 @@ index 888741f8490d6f0e885ed0ce73115c16e7bbe821..9cb32a1ec61bc4cb67f5fc5bb1fa7230 #include "mozilla/dom/WindowProxyHolder.h" #include "nsCOMPtr.h" #include "nsCharsetSource.h" -@@ -76,6 +77,7 @@ class nsCommandManager; +@@ -77,6 +78,7 @@ class nsCommandManager; class nsDocShellEditorData; class nsDOMNavigationTiming; class nsDSURIContentListener; @@ -710,7 +710,7 @@ index 888741f8490d6f0e885ed0ce73115c16e7bbe821..9cb32a1ec61bc4cb67f5fc5bb1fa7230 class nsGlobalWindowOuter; class FramingChecker; -@@ -402,6 +404,15 @@ class nsDocShell final : public nsDocLoader, +@@ -403,6 +405,15 @@ class nsDocShell final : public nsDocLoader, void SetWillChangeProcess() { mWillChangeProcess = true; } bool WillChangeProcess() { return mWillChangeProcess; } @@ -726,7 +726,7 @@ index 888741f8490d6f0e885ed0ce73115c16e7bbe821..9cb32a1ec61bc4cb67f5fc5bb1fa7230 // Create a content viewer within this nsDocShell for the given // `WindowGlobalChild` actor. nsresult CreateDocumentViewerForActor( -@@ -1005,6 +1016,8 @@ class nsDocShell final : public nsDocLoader, +@@ -1006,6 +1017,8 @@ class nsDocShell final : public nsDocLoader, bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; } @@ -735,7 +735,7 @@ index 888741f8490d6f0e885ed0ce73115c16e7bbe821..9cb32a1ec61bc4cb67f5fc5bb1fa7230 // Handles retrieval of subframe session history for nsDocShell::LoadURI. If a // load is requested in a subframe of the current DocShell, the subframe // loadType may need to reflect the loadType of the parent document, or in -@@ -1282,6 +1295,17 @@ class nsDocShell final : public nsDocLoader, +@@ -1285,6 +1298,17 @@ class nsDocShell final : public nsDocLoader, bool mAllowDNSPrefetch : 1; bool mAllowWindowControl : 1; bool mCSSErrorReportingEnabled : 1; @@ -812,10 +812,10 @@ index 84e821e33e8164829dfee4f05340784e189b90ee..aa690eb747cb73bc6bff40a62546037c * This attempts to save any applicable layout history state (like * scroll position) in the nsISHEntry. This is normally done diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp -index 46de7024c0e148c5bc9b90553f0ab7c961acdaae..4ce29d24b940940ba7eefab60ac2be55bb888f78 100644 +index fd2d0be5f7755e64fc134515ea138c4ed0d28daf..ae48159ddbfb98d03e538d077a33260c639a74ac 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp -@@ -3765,6 +3765,9 @@ void Document::SendToConsole(nsCOMArray& aMessages) { +@@ -3752,6 +3752,9 @@ void Document::SendToConsole(nsCOMArray& aMessages) { } void Document::ApplySettingsFromCSP(bool aSpeculative) { @@ -825,7 +825,7 @@ index 46de7024c0e148c5bc9b90553f0ab7c961acdaae..4ce29d24b940940ba7eefab60ac2be55 nsresult rv = NS_OK; if (!aSpeculative) { // 1) apply settings from regular CSP -@@ -3822,6 +3825,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { +@@ -3809,6 +3812,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { MOZ_ASSERT(!mScriptGlobalObject, "CSP must be initialized before mScriptGlobalObject is set!"); @@ -837,7 +837,7 @@ index 46de7024c0e148c5bc9b90553f0ab7c961acdaae..4ce29d24b940940ba7eefab60ac2be55 // If this is a data document - no need to set CSP. if (mLoadedAsData) { return NS_OK; -@@ -4629,6 +4637,10 @@ bool Document::HasFocus(ErrorResult& rv) const { +@@ -4617,6 +4625,10 @@ bool Document::HasFocus(ErrorResult& rv) const { return false; } @@ -848,7 +848,7 @@ index 46de7024c0e148c5bc9b90553f0ab7c961acdaae..4ce29d24b940940ba7eefab60ac2be55 if (!fm->IsInActiveWindow(bc)) { return false; } -@@ -19580,6 +19592,35 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { +@@ -19688,6 +19700,35 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { return PreferenceSheet::PrefsFor(*this).mColorScheme; } @@ -885,10 +885,10 @@ index 46de7024c0e148c5bc9b90553f0ab7c961acdaae..4ce29d24b940940ba7eefab60ac2be55 if (!sLoadingForegroundTopLevelContentDocument) { return false; diff --git a/dom/base/Document.h b/dom/base/Document.h -index 7eb244d65eb55322fb16ed185be83cb731fc268c..3cfb3c17c1a769e4d0fc3fa76288c570822dc48f 100644 +index 622f54e369d324a4cc2800dd4b331bd628339bed..2ef6ed20cf35febeff75b22dfa5bb2fbb4e295fe 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h -@@ -4134,6 +4134,8 @@ class Document : public nsINode, +@@ -4140,6 +4140,8 @@ class Document : public nsINode, // color-scheme meta tag. ColorScheme DefaultColorScheme() const; @@ -898,7 +898,7 @@ index 7eb244d65eb55322fb16ed185be83cb731fc268c..3cfb3c17c1a769e4d0fc3fa76288c570 static bool AutomaticStorageAccessPermissionCanBeGranted( diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp -index 0f159ad09a2a4b8962307b9f20abf30323377a36..e0cbb3f1f8af42825696d7152eb9993ab3802f90 100644 +index a13cae5b990fb2f750e62f5117ad63aa981d787f..bc0f2d674aadd8eba867f56e873595a8885d1798 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -344,14 +344,18 @@ void Navigator::GetAppName(nsAString& aAppName) const { @@ -961,20 +961,17 @@ index 6abf6cef230c97815f17f6b7abf9f1b1de274a6f..46ead1f32e0d710b5b32e61dff72a4f7 dom::MediaCapabilities* MediaCapabilities(); dom::MediaSession* MediaSession(); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp -index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc388a692b 100644 +index f362a444a0f5ed247582646754dffd54d0b4540a..bbd72dab7ff4fbac8c247961e530764cb5c68d11 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp -@@ -8740,7 +8740,8 @@ nsresult nsContentUtils::SendMouseEvent( - bool aIgnoreRootScrollFrame, float aPressure, - unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, - PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized, +@@ -9151,11 +9151,13 @@ nsresult nsContentUtils::SendMouseEvent( + int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame, + float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, + bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized, - bool aIsWidgetEventSynthesized) { + bool aIsWidgetEventSynthesized, + bool convertToPointer, uint32_t aJugglerEventId) { - nsPoint offset; - nsCOMPtr widget = GetWidget(aPresShell, &offset); - if (!widget) return NS_ERROR_FAILURE; -@@ -8748,6 +8749,7 @@ nsresult nsContentUtils::SendMouseEvent( + MOZ_ASSERT(aWidget); EventMessage msg; Maybe exitFrom; bool contextMenuKey = false; @@ -982,7 +979,7 @@ index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc if (aType.EqualsLiteral("mousedown")) { msg = eMouseDown; } else if (aType.EqualsLiteral("mouseup")) { -@@ -8773,6 +8775,12 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -9181,6 +9183,12 @@ nsresult nsContentUtils::SendMouseEvent( msg = eMouseHitTest; } else if (aType.EqualsLiteral("MozMouseExploreByTouch")) { msg = eMouseExploreByTouch; @@ -995,7 +992,7 @@ index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc } else { return NS_ERROR_FAILURE; } -@@ -8783,7 +8791,14 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -9191,7 +9199,14 @@ nsresult nsContentUtils::SendMouseEvent( Maybe pointerEvent; Maybe mouseEvent; @@ -1003,7 +1000,7 @@ index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc + Maybe pwDragEvent; + + if (isPWDragEventMessage) { -+ pwDragEvent.emplace(true, msg, widget); ++ pwDragEvent.emplace(true, msg, aWidget); + pwDragEvent->mReason = aIsWidgetEventSynthesized + ? WidgetMouseEvent::eSynthesized + : WidgetMouseEvent::eReal; @@ -1011,7 +1008,7 @@ index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc MOZ_ASSERT(!aIsWidgetEventSynthesized, "The event shouldn't be dispatched as a synthesized event"); if (MOZ_UNLIKELY(aIsWidgetEventSynthesized)) { -@@ -8802,8 +8817,11 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -9210,8 +9225,11 @@ nsresult nsContentUtils::SendMouseEvent( contextMenuKey ? WidgetMouseEvent::eContextMenuKey : WidgetMouseEvent::eNormal); } @@ -1023,7 +1020,7 @@ index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc mouseOrPointerEvent.pointerId = aIdentifier; mouseOrPointerEvent.mModifiers = GetWidgetModifiers(aModifiers); mouseOrPointerEvent.mButton = aButton; -@@ -8816,6 +8834,8 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -9224,6 +9242,8 @@ nsresult nsContentUtils::SendMouseEvent( mouseOrPointerEvent.mClickCount = aClickCount; mouseOrPointerEvent.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized; mouseOrPointerEvent.mExitFrom = exitFrom; @@ -1033,21 +1030,23 @@ index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc nsPresContext* presContext = aPresShell->GetPresContext(); if (!presContext) return NS_ERROR_FAILURE; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h -index ed822f10425272124dd33a74d1cdac5011b1ba6a..0f29ce787f9cebc068d2e5faa9907d52b177283a 100644 +index 779cd9e544bfb2d135f12c3558c0ca8164b37029..041eba4bcbf40f51fc40ce7609ea81408e6a788d 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h -@@ -3019,7 +3019,8 @@ class nsContentUtils { +@@ -3015,8 +3015,9 @@ class nsContentUtils { + int32_t aButton, int32_t aButtons, int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, - mozilla::PreventDefaultResult* aPreventDefault, -- bool aIsDOMEventSynthesized, bool aIsWidgetEventSynthesized); +- bool* aPreventDefault, bool aIsDOMEventSynthesized, +- bool aIsWidgetEventSynthesized); ++ bool* aPreventDefault, + bool aIsDOMEventSynthesized, bool aIsWidgetEventSynthesized, + bool convertToPointer = true, uint32_t aJugglerEventId = 0); static void FirePageShowEventForFrameLoaderSwap( nsIDocShellTreeItem* aItem, diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp -index fe23028691d0d06f6b036fd9da2c466730f58cb7..40cfc256510cbddea50574b531ce4369462fd956 100644 +index 15fe1db8a28ed2592b61aaf2006ddaa656f12389..2642c18bebcdfdd467be557171ba4ee204fcabde 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -710,6 +710,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) { @@ -1095,27 +1094,29 @@ index fe23028691d0d06f6b036fd9da2c466730f58cb7..40cfc256510cbddea50574b531ce4369 } NS_IMETHODIMP -@@ -751,13 +771,13 @@ nsDOMWindowUtils::SendMouseEventCommon( +@@ -751,7 +771,7 @@ nsDOMWindowUtils::SendMouseEventCommon( int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aPointerId, bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized, - bool aIsWidgetEventSynthesized, int32_t aButtons) { + bool aIsWidgetEventSynthesized, int32_t aButtons, bool aConvertToPointer, uint32_t aJugglerEventId) { RefPtr presShell = GetPresShell(); - PreventDefaultResult preventDefaultResult; - nsresult rv = nsContentUtils::SendMouseEvent( - presShell, aType, aX, aY, aButton, aButtons, aClickCount, aModifiers, - aIgnoreRootScrollFrame, aPressure, aInputSourceArg, aPointerId, aToWindow, -- &preventDefaultResult, aIsDOMEventSynthesized, aIsWidgetEventSynthesized); -+ &preventDefaultResult, aIsDOMEventSynthesized, aIsWidgetEventSynthesized, aConvertToPointer, aJugglerEventId); - - if (aPreventDefault) { - *aPreventDefault = preventDefaultResult != PreventDefaultResult::No; + if (!presShell) { + return NS_ERROR_FAILURE; +@@ -768,7 +788,7 @@ nsDOMWindowUtils::SendMouseEventCommon( + presShell, widget, aType, refPoint, aButton, aButtons, aClickCount, + aModifiers, aIgnoreRootScrollFrame, aPressure, aInputSourceArg, + aPointerId, aToWindow, aPreventDefault, aIsDOMEventSynthesized, +- aIsWidgetEventSynthesized); ++ aIsWidgetEventSynthesized, aConvertToPointer, aJugglerEventId); + } + + NS_IMETHODIMP diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h -index 47ff326b202266b1d7d6af8bdfb72776df8a6a93..b8e084b0c788c46345b1455b8257f1719c851404 100644 +index a8a48d28fc4ef612f8234bc2490a41672f1704f5..f702b0c9a0783ec547a41bbefd68e18a27a239fe 100644 --- a/dom/base/nsDOMWindowUtils.h +++ b/dom/base/nsDOMWindowUtils.h -@@ -93,7 +93,7 @@ class nsDOMWindowUtils final : public nsIDOMWindowUtils, +@@ -94,7 +94,7 @@ class nsDOMWindowUtils final : public nsIDOMWindowUtils, int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized, @@ -1125,10 +1126,10 @@ index 47ff326b202266b1d7d6af8bdfb72776df8a6a93..b8e084b0c788c46345b1455b8257f171 MOZ_CAN_RUN_SCRIPT nsresult SendTouchEventCommon( diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp -index a1d3ae3f3cb8d916a9a8bcca4cb515a2ab496ccb..72326b41064f6a86d76aba7b1902851e966365a8 100644 +index 555a08b4b3fcd0d0c7986014d2e3516c6e5991c0..74a39e000b0e68042f1f51f6fafbc39ac9b42e51 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp -@@ -1719,6 +1719,10 @@ Maybe nsFocusManager::SetFocusInner(Element* aNewContent, +@@ -1720,6 +1720,10 @@ Maybe nsFocusManager::SetFocusInner(Element* aNewContent, (GetActiveBrowsingContext() == newRootBrowsingContext); } @@ -1139,7 +1140,7 @@ index a1d3ae3f3cb8d916a9a8bcca4cb515a2ab496ccb..72326b41064f6a86d76aba7b1902851e // Exit fullscreen if a website focuses another window if (StaticPrefs::full_screen_api_exit_on_windowRaise() && !isElementInActiveWindow && (aFlags & FLAG_RAISE)) { -@@ -2304,6 +2308,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, +@@ -2306,6 +2310,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, bool aIsLeavingDocument, bool aAdjustWidget, bool aRemainActive, Element* aElementToFocus, uint64_t aActionId) { @@ -1147,7 +1148,7 @@ index a1d3ae3f3cb8d916a9a8bcca4cb515a2ab496ccb..72326b41064f6a86d76aba7b1902851e LOGFOCUS(("<>", aActionId)); // hold a reference to the focused content, which may be null -@@ -2350,6 +2355,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, +@@ -2352,6 +2357,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, return true; } @@ -1159,7 +1160,7 @@ index a1d3ae3f3cb8d916a9a8bcca4cb515a2ab496ccb..72326b41064f6a86d76aba7b1902851e // Keep a ref to presShell since dispatching the DOM event may cause // the document to be destroyed. RefPtr presShell = docShell->GetPresShell(); -@@ -3064,7 +3074,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, +@@ -3066,7 +3076,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, } } @@ -1171,10 +1172,10 @@ index a1d3ae3f3cb8d916a9a8bcca4cb515a2ab496ccb..72326b41064f6a86d76aba7b1902851e // care of lowering the present active window. This happens in // a separate runnable to avoid touching multiple windows in diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp -index 4d0483fb6cc4b7ed6cc9633c72413017e7b49249..9eb60397c94f567cc76fcbeec4585bf92b2fd0f7 100644 +index 99a5049a3aff2efb208895d60622fd9c8d7f337a..9a9b039a46f1294a8b4af0613fb4f4173ac6a8a0 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp -@@ -2516,10 +2516,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, +@@ -2512,10 +2512,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, }(); if (!isContentAboutBlankInChromeDocshell) { @@ -1195,7 +1196,7 @@ index 4d0483fb6cc4b7ed6cc9633c72413017e7b49249..9eb60397c94f567cc76fcbeec4585bf9 } } -@@ -2639,6 +2645,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() { +@@ -2635,6 +2641,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() { } } @@ -1216,10 +1217,10 @@ index 4d0483fb6cc4b7ed6cc9633c72413017e7b49249..9eb60397c94f567cc76fcbeec4585bf9 void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) { diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h -index d4347e7a508742986f634e97394a9e3dd435d170..5088520537c8c5c6cc79c1dffd91a68de09f27eb 100644 +index 0453a18ec10c1434d1692f10b1b4acee754e6b7e..ee7bad691515bb51f6b4345e88944b02ad173180 100644 --- a/dom/base/nsGlobalWindowOuter.h +++ b/dom/base/nsGlobalWindowOuter.h -@@ -317,6 +317,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget, +@@ -320,6 +320,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget, // Outer windows only. void DispatchDOMWindowCreated(); @@ -1228,10 +1229,10 @@ index d4347e7a508742986f634e97394a9e3dd435d170..5088520537c8c5c6cc79c1dffd91a68d // Outer windows only. virtual void EnsureSizeAndPositionUpToDate() override; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp -index 4c1efbb1edf0e7d34a333e2724baf055eefa7e6e..c5d64fba742187c8ebaad8f45e868ab65722d07a 100644 +index 8e2bbed21c13f23745e2eaad4ded831106ebd930..a17b0c7b9730737f178c05703b08d0f5f6d9ecd1 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp -@@ -1437,6 +1437,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions, +@@ -1449,6 +1449,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions, mozilla::GetBoxQuadsFromWindowOrigin(this, aOptions, aResult, aRv); } @@ -1294,10 +1295,10 @@ index 4c1efbb1edf0e7d34a333e2724baf055eefa7e6e..c5d64fba742187c8ebaad8f45e868ab6 DOMQuad& aQuad, const GeometryNode& aFrom, const ConvertCoordinateOptions& aOptions, CallerType aCallerType, diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h -index e1fb6c2aeb67ab600b668b342bee0c716427d116..55e573dce8ad4d34542f89b9179a1da14f45d1bc 100644 +index eb75f281630f8ca1b901686207c9fc97336d675f..e607f0ae454d52fc2bfe19046b492352a84b4ebd 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h -@@ -2356,6 +2356,10 @@ class nsINode : public mozilla::dom::EventTarget { +@@ -2397,6 +2397,10 @@ class nsINode : public mozilla::dom::EventTarget { nsTArray>& aResult, ErrorResult& aRv); @@ -1337,7 +1338,7 @@ index f32e21752d5013bf143eb45391ab9218debab08e..83763d2354dade2f8d2b7930ba18ae91 static bool DumpEnabled(); diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl -index 28e8d8cb9c61ff8362b2d191d47c3630d2cb0b34..58c12d597978100507dbc21e88c49c5642a3ba1f 100644 +index 6ec88536141126c97c9b599e3237bb5670d42ce8..41c6cc56738bdaf711adf2cf5b00c7fad5d71ba8 100644 --- a/dom/chrome-webidl/BrowsingContext.webidl +++ b/dom/chrome-webidl/BrowsingContext.webidl @@ -61,6 +61,26 @@ enum ForcedColorsOverride { @@ -1381,10 +1382,10 @@ index 28e8d8cb9c61ff8362b2d191d47c3630d2cb0b34..58c12d597978100507dbc21e88c49c56 * A unique identifier for the browser element that is hosting this * BrowsingContext tree. Every BrowsingContext in the element's tree will diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp -index a030f3f07b9f4a676f87ac482507056bc911edf5..68bd39affdbbe2e35fdac62b1e159ebc7ffd031c 100644 +index 0bd219694282347309680fc9b53b945e1fd0ad92..c5c2e2d32a380ec72379b05a8b84f187431f7309 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp -@@ -689,6 +689,12 @@ already_AddRefed FetchRequest(nsIGlobalObject* aGlobal, +@@ -701,6 +701,12 @@ already_AddRefed FetchRequest(nsIGlobalObject* aGlobal, ipcArgs.hasCSPEventListener() = false; ipcArgs.isWorkerRequest() = false; @@ -1398,10 +1399,10 @@ index a030f3f07b9f4a676f87ac482507056bc911edf5..68bd39affdbbe2e35fdac62b1e159ebc mozilla::glean::networking::fetch_keepalive_request_count.Get("main"_ns) diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp -index 6fc05bf6cc8341e7cfac63789b79f610bdbe9641..112db3c2e6d2f854103ddacde896904018f5ea90 100644 +index b5e60bbd27fbb2f033d233e9ae2ebc728f442512..0adc568ece34d2c0f35eeacd81e2db9125b7c327 100644 --- a/dom/fetch/FetchService.cpp +++ b/dom/fetch/FetchService.cpp -@@ -265,6 +265,14 @@ RefPtr FetchService::FetchInstance::Fetch() { +@@ -268,6 +268,14 @@ RefPtr FetchService::FetchInstance::Fetch() { false // IsTrackingFetch ); @@ -1417,10 +1418,10 @@ index 6fc05bf6cc8341e7cfac63789b79f610bdbe9641..112db3c2e6d2f854103ddacde8969040 auto& args = mArgs.as(); mFetchDriver->SetWorkerScript(args.mWorkerScript); diff --git a/dom/geolocation/Geolocation.cpp b/dom/geolocation/Geolocation.cpp -index e67cb4efce43b42fa876c906f7f1927c65d34ea7..a42da20dc6aaa1915c611d4bc339a8db974c75eb 100644 +index 7c653fe131dc34d35ffdc030950071adb31a9fc9..b23442a42ba8ee270e8e38930e59ae15c2a29039 100644 --- a/dom/geolocation/Geolocation.cpp +++ b/dom/geolocation/Geolocation.cpp -@@ -29,6 +29,7 @@ +@@ -28,6 +28,7 @@ #include "nsComponentManagerUtils.h" #include "nsContentPermissionHelper.h" #include "nsContentUtils.h" @@ -1428,7 +1429,7 @@ index e67cb4efce43b42fa876c906f7f1927c65d34ea7..a42da20dc6aaa1915c611d4bc339a8db #include "nsGlobalWindowInner.h" #include "mozilla/dom/Document.h" #include "nsINamed.h" -@@ -429,10 +430,8 @@ nsGeolocationRequest::Allow(JS::Handle aChoices) { +@@ -428,10 +429,8 @@ nsGeolocationRequest::Allow(JS::Handle aChoices) { return NS_OK; } @@ -1441,7 +1442,7 @@ index e67cb4efce43b42fa876c906f7f1927c65d34ea7..a42da20dc6aaa1915c611d4bc339a8db CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition(); if (lastPosition.position) { EpochTimeStamp cachedPositionTime_ms; -@@ -640,8 +639,7 @@ void nsGeolocationRequest::Shutdown() { +@@ -639,8 +638,7 @@ void nsGeolocationRequest::Shutdown() { // If there are no other high accuracy requests, the geolocation service will // notify the provider to switch to the default accuracy. if (mOptions && mOptions->mEnableHighAccuracy) { @@ -1451,7 +1452,7 @@ index e67cb4efce43b42fa876c906f7f1927c65d34ea7..a42da20dc6aaa1915c611d4bc339a8db if (gs) { gs->UpdateAccuracy(); } -@@ -958,8 +956,14 @@ void nsGeolocationService::StopDevice() { +@@ -957,8 +955,14 @@ void nsGeolocationService::StopDevice() { StaticRefPtr nsGeolocationService::sService; already_AddRefed @@ -1467,7 +1468,7 @@ index e67cb4efce43b42fa876c906f7f1927c65d34ea7..a42da20dc6aaa1915c611d4bc339a8db if (nsGeolocationService::sService) { result = nsGeolocationService::sService; -@@ -1051,7 +1055,9 @@ nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) { +@@ -1050,7 +1054,9 @@ nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) { // If no aContentDom was passed into us, we are being used // by chrome/c++ and have no mOwner, no mPrincipal, and no need // to prompt. @@ -1516,7 +1517,7 @@ index 992de29b5d2d09c19e55ebb2502215ec9d05a171..cdc20567b693283b0fd5a5923f7ea542 ~Geolocation(); diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp -index 2e6ef116be73d0794683189c07afc8a629859154..33f4add0cb7736edd416d31d0feca4fd5afc4526 100644 +index c3e7de8f41e06e11155620b75c4d8a830d908b37..39eb9d31258693dce3a26c3227f28d92bccdb019 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -64,6 +64,7 @@ @@ -1527,7 +1528,7 @@ index 2e6ef116be73d0794683189c07afc8a629859154..33f4add0cb7736edd416d31d0feca4fd #include "nsIFrame.h" #include "nsRangeFrame.h" #include "nsError.h" -@@ -789,6 +790,13 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { +@@ -790,6 +791,13 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { return NS_ERROR_FAILURE; } @@ -1542,7 +1543,7 @@ index 2e6ef116be73d0794683189c07afc8a629859154..33f4add0cb7736edd416d31d0feca4fd return NS_OK; } diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl -index 8ddde6e5de319142ce0898dc3667c08f1f24cce9..9e530a727f06b924e3d0bcf4ba52507231778257 100644 +index 5e417145c4f21d8f2aa65088611477b681c9c327..bc84c509659c7556077e69c652e5b19639eb88bb 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -374,6 +374,26 @@ interface nsIDOMWindowUtils : nsISupports { @@ -1846,7 +1847,7 @@ index 3b39538e51840cd9b1685b2efd2ff2e9ec83608a..c7bf4f2d53b58bbacb22b3ebebf6f3fc return aGlobalOrNull; diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp -index 32a8d9496e674e752dd3ac41afc7f22ed534dce3..e57690654be4ae18f14d3171fa4eab9ec8aa991f 100644 +index 5ec21c1c7f975a372399748e8bab2b21ce347f20..ed16831e549afa3d6623398d35eb61e26ab5f2b0 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -23,6 +23,7 @@ @@ -1857,7 +1858,7 @@ index 32a8d9496e674e752dd3ac41afc7f22ed534dce3..e57690654be4ae18f14d3171fa4eab9e #include "mozilla/Assertions.h" #include "mozilla/Components.h" -@@ -134,6 +135,11 @@ void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc, +@@ -135,6 +136,11 @@ void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc, return; } @@ -1893,10 +1894,10 @@ index aee376e971ae01ac1e512c3920b115bfaf06afa8..1701311534bf77e6cd9bafc0e3a28361 * returned quads are further translated relative to the window * origin -- which is not the layout origin. Further translation diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp -index f528bec20afef533e4c6b99c5e9d1680fd0b636e..a0d22d38657f672d865f35c02975e7b611571353 100644 +index a23637c4a887b66a1b4c709a648762b84151bf01..d8da9063261482f1da3257e3f95a8a49d94325f8 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp -@@ -1027,7 +1027,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { +@@ -1026,7 +1026,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsTArray languages; @@ -1905,7 +1906,7 @@ index f528bec20afef533e4c6b99c5e9d1680fd0b636e..a0d22d38657f672d865f35c02975e7b6 RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { -@@ -1215,8 +1215,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { +@@ -1214,8 +1214,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { } // The navigator overridden properties should have already been read. @@ -1915,7 +1916,7 @@ index f528bec20afef533e4c6b99c5e9d1680fd0b636e..a0d22d38657f672d865f35c02975e7b6 mNavigatorPropertiesLoaded = true; } -@@ -1837,6 +1836,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted( +@@ -1836,6 +1835,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted( } } @@ -1929,7 +1930,7 @@ index f528bec20afef533e4c6b99c5e9d1680fd0b636e..a0d22d38657f672d865f35c02975e7b6 template void RuntimeService::BroadcastAllWorkers(const Func& aFunc) { AssertIsOnMainThread(); -@@ -2362,6 +2368,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers( +@@ -2361,6 +2367,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers( } } @@ -1971,7 +1972,7 @@ index 58894a8361c7ef1dddd481ca5877a209a8b8ff5c..c481d40d79b6397b7f1d571bd9f6ae5c bool IsWorkerGlobal(JSObject* global); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp -index 0076a8463fa9ff05b32edfe21462987610432b5d..9f810c9728b7d7caf9fbeb3e24346c219326637d 100644 +index 5d918a82708a26125f7322e43f6436d7eafaa812..b230baead02e05d87a211c276066ec7939ea8251 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -736,6 +736,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable { @@ -1993,7 +1994,7 @@ index 0076a8463fa9ff05b32edfe21462987610432b5d..9f810c9728b7d7caf9fbeb3e24346c21 class UpdateLanguagesRunnable final : public WorkerThreadRunnable { nsTArray mLanguages; -@@ -2149,6 +2161,16 @@ void WorkerPrivate::UpdateContextOptions( +@@ -2159,6 +2171,16 @@ void WorkerPrivate::UpdateContextOptions( } } @@ -2010,7 +2011,7 @@ index 0076a8463fa9ff05b32edfe21462987610432b5d..9f810c9728b7d7caf9fbeb3e24346c21 void WorkerPrivate::UpdateLanguages(const nsTArray& aLanguages) { AssertIsOnParentThread(); -@@ -5833,6 +5855,15 @@ void WorkerPrivate::UpdateContextOptionsInternal( +@@ -5946,6 +5968,15 @@ void WorkerPrivate::UpdateContextOptionsInternal( } } @@ -2027,7 +2028,7 @@ index 0076a8463fa9ff05b32edfe21462987610432b5d..9f810c9728b7d7caf9fbeb3e24346c21 const nsTArray& aLanguages) { WorkerGlobalScope* globalScope = GlobalScope(); diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h -index c2ade467934d08587d4e3589b310c6302534d699..414814c03180b50d218ad84a0db262fff19f78a7 100644 +index 7bccdc8c0c2cd53f7aa7a6d9a74435344dd27980..86bba2128a7c0f4e5efa4bfbc939937aec146695 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -443,6 +443,8 @@ class WorkerPrivate final @@ -2039,7 +2040,7 @@ index c2ade467934d08587d4e3589b310c6302534d699..414814c03180b50d218ad84a0db262ff void UpdateLanguagesInternal(const nsTArray& aLanguages); void UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, -@@ -1086,6 +1088,8 @@ class WorkerPrivate final +@@ -1091,6 +1093,8 @@ class WorkerPrivate final void UpdateContextOptions(const JS::ContextOptions& aContextOptions); @@ -2101,10 +2102,10 @@ index 523e84c8c93f4221701f90f2e8ee146ec8e1adbd..98d5b1176e5378431b859a2dbd4d4e77 inline ClippedTime TimeClip(double time); diff --git a/js/src/debugger/Object.cpp b/js/src/debugger/Object.cpp -index 880e716c24464c93283410417f8e69d6d233d105..6e046fbd2e643dace5ad7796740253df3ddf2cbe 100644 +index b7ea4b6f66d14db0324397cdc1b0ed8c5ea167e2..1c59e328079e7e43b65f7cb7bc31636a48a93263 100644 --- a/js/src/debugger/Object.cpp +++ b/js/src/debugger/Object.cpp -@@ -2474,7 +2474,11 @@ Maybe DebuggerObject::call(JSContext* cx, +@@ -2484,7 +2484,11 @@ Maybe DebuggerObject::call(JSContext* cx, invokeArgs[i].set(args2[i]); } @@ -2117,7 +2118,7 @@ index 880e716c24464c93283410417f8e69d6d233d105..6e046fbd2e643dace5ad7796740253df } diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp -index cf63b124f39223a1a42c322dcc0ad108947d54f5..9e3a7b757cbad57ee6cfe2254d0355f62f7c8662 100644 +index a57b8fefa104f966393a99f5a81876b9a95f9743..adc42dd227e52643b06fb101170aeafb490c0acc 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -185,6 +185,11 @@ void js::DateTimeInfo::internalResetTimeZone(ResetTimeZoneMode mode) { @@ -2272,10 +2273,10 @@ index 4bfd336ddcbee8004ac538ca7b7d8216d04a61c3..cd22351c4aeacea8afc9828972222aca // No boxes to return return; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp -index ede24b9c7ac3569d6467ac88bc491d2987ac0bca..a45ebcdf3a3caaad15a8dff0b8ebbec971aac8a6 100644 +index e8fb3a8304b27814e6e84355f24410c820667f9d..211c86fe55b8b650e40275a427b30a1ee8a9a3d7 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp -@@ -11265,7 +11265,9 @@ bool PresShell::ComputeActiveness() const { +@@ -11512,7 +11512,9 @@ bool PresShell::ComputeActiveness() const { if (!browserChild->IsVisible()) { MOZ_LOG(gLog, LogLevel::Debug, (" > BrowserChild %p is not visible", browserChild)); @@ -2287,7 +2288,7 @@ index ede24b9c7ac3569d6467ac88bc491d2987ac0bca..a45ebcdf3a3caaad15a8dff0b8ebbec9 // If the browser is visible but just due to be preserving layers diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp -index b9f06daa19e5aecb976ad198990a315bc39f736d..1600435c406e2e652abdab72a7947add7266c75a 100644 +index 315d532eab56dab13b6c8bc2380a5cda2a17ffc1..7c4552137149e8c7fc9ac08a09bd4242952b53b6 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -708,6 +708,10 @@ bool nsLayoutUtils::AllowZoomingForDocument( @@ -2301,7 +2302,7 @@ index b9f06daa19e5aecb976ad198990a315bc39f736d..1600435c406e2e652abdab72a7947add // True if we allow zooming for all documents on this platform, or if we are // in RDM. BrowsingContext* bc = aDocument->GetBrowsingContext(); -@@ -9748,6 +9752,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont, +@@ -9770,6 +9774,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont, /* static */ bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) { @@ -2312,10 +2313,10 @@ index b9f06daa19e5aecb976ad198990a315bc39f736d..1600435c406e2e652abdab72a7947add return StaticPrefs::dom_meta_viewport_enabled() || (bc && bc->InRDMPane()); } diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h -index 3f97c46ee5721c9f5bb9b86e2c0ece552ed00568..52f6c4d600baccc846503373af3476816f4c9fdc 100644 +index c7cf59c2661c7e203384c9b82789879f756b44b7..21e32dab4e60112c073bdd5070a308da2b4e0373 100644 --- a/layout/style/GeckoBindings.h +++ b/layout/style/GeckoBindings.h -@@ -595,6 +595,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*); +@@ -596,6 +596,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*); bool Gecko_MediaFeatures_PrefersReducedMotion(const mozilla::dom::Document*); bool Gecko_MediaFeatures_PrefersReducedTransparency( const mozilla::dom::Document*); @@ -2362,7 +2363,7 @@ index ca382a3cfba8ce5839890d6e4cb3cf9789287e3b..5800fc23dc77ee5764beddd6fa48a7fd return StylePrefersContrast::NoPreference; } diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp -index 06acdc629c2b6ee0e29c50d8edc5a96d343b1ef2..6c263edf54117fd9cbf4a77abc396f1238730880 100644 +index 1ec2c64193206d31702e22e5c4783f084b1cff31..fb463eb12ee39cd1e448369f3b47fbcfbb2473b9 100644 --- a/netwerk/base/LoadInfo.cpp +++ b/netwerk/base/LoadInfo.cpp @@ -696,7 +696,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) @@ -2375,7 +2376,7 @@ index 06acdc629c2b6ee0e29c50d8edc5a96d343b1ef2..6c263edf54117fd9cbf4a77abc396f12 } LoadInfo::LoadInfo( -@@ -2515,4 +2516,16 @@ LoadInfo::SetSkipHTTPSUpgrade(bool aSkipHTTPSUpgrade) { +@@ -2534,4 +2535,16 @@ LoadInfo::SetSkipHTTPSUpgrade(bool aSkipHTTPSUpgrade) { return NS_OK; } @@ -2393,10 +2394,10 @@ index 06acdc629c2b6ee0e29c50d8edc5a96d343b1ef2..6c263edf54117fd9cbf4a77abc396f12 + } // namespace mozilla::net diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h -index c78602f6b46c983aa4d96c5727ebbaf7e2c7d984..e292766a0f34306ea1101be4ecd8848764726bad 100644 +index 93cc8d3630f7029303240555ae72d41b68047375..8a09863af399e25ba3f01caff2f6b3af1260e8b8 100644 --- a/netwerk/base/LoadInfo.h +++ b/netwerk/base/LoadInfo.h -@@ -423,6 +423,8 @@ class LoadInfo final : public nsILoadInfo { +@@ -426,6 +426,8 @@ class LoadInfo final : public nsILoadInfo { bool mIsNewWindowTarget = false; bool mSkipHTTPSUpgrade = false; @@ -2406,10 +2407,10 @@ index c78602f6b46c983aa4d96c5727ebbaf7e2c7d984..e292766a0f34306ea1101be4ecd88487 // This is exposed solely for testing purposes and should not be used outside of diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp -index 5984a0a196615cca5544de052874cbb163a8233b..3617816a06651ae65c214ebd5f0affedc4d11390 100644 +index d1650595f8cf28a704f94a99c1f6bfe1deb9cc77..2072a3990ff6f4496626dcebb277291ad845cac3 100644 --- a/netwerk/base/TRRLoadInfo.cpp +++ b/netwerk/base/TRRLoadInfo.cpp -@@ -936,5 +936,15 @@ TRRLoadInfo::GetFetchDestination(nsACString& aDestination) { +@@ -950,5 +950,15 @@ TRRLoadInfo::GetFetchDestination(nsACString& aDestination) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -2426,33 +2427,42 @@ index 5984a0a196615cca5544de052874cbb163a8233b..3617816a06651ae65c214ebd5f0affed } // namespace net } // namespace mozilla diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl -index 2d77b8aa8799ec6bb7f38722e837d070f9057ea6..0261e58afd17c78a1484ec55e45bf34442929200 100644 +index 774ec045c0b18310e8cb86e8a9d6b1788d028435..cbf303a0ed872c27d580b4b6615f3dd9c76a8a19 100644 --- a/netwerk/base/nsILoadInfo.idl +++ b/netwerk/base/nsILoadInfo.idl -@@ -1609,4 +1609,6 @@ interface nsILoadInfo : nsISupports - * When true, this load will never be upgraded to HTTPS. - */ - [infallible] attribute boolean skipHTTPSUpgrade; +@@ -1626,4 +1626,6 @@ interface nsILoadInfo : nsISupports + return static_cast(userNavigationInvolvement); + } + %} + + [infallible] attribute unsigned long long jugglerLoadIdentifier; }; diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl -index 7f91d2df6f8bb4020c75c132dc8f6bf26625fa1e..ba6569f4be8fc54ec96ee44d5de45a0904c077ba 100644 +index 7f91d2df6f8bb4020c75c132dc8f6bf26625fa1e..aaa5541a17039d6b13ad83ab176fdaaf79edb2a0 100644 --- a/netwerk/base/nsINetworkInterceptController.idl +++ b/netwerk/base/nsINetworkInterceptController.idl -@@ -59,6 +59,7 @@ interface nsIInterceptedChannel : nsISupports - * results in the resulting client not being controlled. +@@ -60,6 +60,16 @@ interface nsIInterceptedChannel : nsISupports */ void resetInterception(in boolean bypass); -+ void resetInterceptionWithURI(in nsIURI aURI); ++ // ----- Playwright begin ----- ++ ++ // Same as resetInterception, but updates the URI. ++ void resetInterceptionWithURI(in nsIURI aURI); ++ ++ // After resetInterception is called, this request will be intercepted again. ++ void interceptAfterServiceWorkerResets(); ++ ++ // ----- Playwright end ------- ++ /** * Set the status and reason for the forthcoming synthesized response. + * Multiple calls overwrite existing values. diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp -index cdf9ad443ebf49eabc362fd555ae54c09502395c..8bca3d81fef7b59334b2ab0cdccdd80ef19c675d 100644 +index 771ae1fbe3d54aa25443eea675cf3abd26a21ce9..a916cb49c16dc6c7809ccbb7c8d4172446a5ac07 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp -@@ -175,6 +175,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, +@@ -177,6 +177,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, loadInfo->SetTextDirectiveUserActivation( aLoadState->GetTextDirectiveUserActivation()); loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); @@ -2461,13 +2471,19 @@ index cdf9ad443ebf49eabc362fd555ae54c09502395c..8bca3d81fef7b59334b2ab0cdccdd80e return loadInfo.forget(); } diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp -index 6d8d65c9335583d28aa3be8c05c065fa5aede908..6ab815111954b53b4dc722a1babe6b49a78bbedc 100644 +index fbf4bdf1e24d1102df113984be6c8dc3a7d0d810..787bf014d3bf0b8537f99bf5eb4074e100c78c18 100644 --- a/netwerk/protocol/http/InterceptedHttpChannel.cpp +++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp -@@ -727,6 +727,14 @@ NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor) +@@ -728,10 +728,33 @@ NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor) } // anonymous namespace ++NS_IMETHODIMP ++InterceptedHttpChannel::InterceptAfterServiceWorkerResets() { ++ mInterceptAfterServiceWorkerResets = true; ++ return NS_OK; ++} ++ +NS_IMETHODIMP +InterceptedHttpChannel::ResetInterceptionWithURI(nsIURI* aURI) { + if (aURI) { @@ -2479,7 +2495,20 @@ index 6d8d65c9335583d28aa3be8c05c065fa5aede908..6ab815111954b53b4dc722a1babe6b49 NS_IMETHODIMP InterceptedHttpChannel::ResetInterception(bool aBypass) { INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s", -@@ -1140,11 +1148,18 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { + this, aBypass ? "true" : "false")); ++ if (mInterceptAfterServiceWorkerResets) { ++ mInterceptAfterServiceWorkerResets = false; ++ nsCOMPtr controller; ++ GetCallback(controller); ++ if (!controller) ++ return NS_ERROR_DOM_INVALID_STATE_ERR; ++ return controller->ChannelIntercepted(this); ++ } ++ + if (mCanceled) { + return mStatus; + } +@@ -1146,11 +1169,18 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { GetCallback(mProgressSink); } @@ -2498,11 +2527,27 @@ index 6d8d65c9335583d28aa3be8c05c065fa5aede908..6ab815111954b53b4dc722a1babe6b49 if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { mPump->PeekStream(CallTypeSniffers, static_cast(this)); +diff --git a/netwerk/protocol/http/InterceptedHttpChannel.h b/netwerk/protocol/http/InterceptedHttpChannel.h +index 704404c9f094640ad63b685d64bd5a396e733e4b..92bdc21b4d6a015cc2f2bb22781ec6750c7789ec 100644 +--- a/netwerk/protocol/http/InterceptedHttpChannel.h ++++ b/netwerk/protocol/http/InterceptedHttpChannel.h +@@ -90,6 +90,11 @@ class InterceptedHttpChannel final + Atomic mCallingStatusAndProgress; + bool mInterceptionReset{false}; + ++ // ----- Playwright begin ----- ++ // After resetInterception is called, this request will call into interceptors again. ++ bool mInterceptAfterServiceWorkerResets{false}; ++ // ----- Playwright end ------- ++ + /** + * InterceptionTimeStamps is used to record the time stamps of the + * interception. diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp -index d05b06c3f9ddba3b40d5969730474eaf0d843cb1..9b2cc35c504e1044ac681c62c107f8feb6c16938 100644 +index d3b44cc62d3df49bbf842356cbdb153c82c3163c..23cf9bc83fb1faaf1c7406331b78e522b307cbf0 100644 --- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp -@@ -1334,6 +1334,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( +@@ -1349,6 +1349,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); @@ -2514,7 +2559,7 @@ index d05b06c3f9ddba3b40d5969730474eaf0d843cb1..9b2cc35c504e1044ac681c62c107f8fe nsCOMPtr preloadCsp = mDocument->GetPreloadCsp(); if (!preloadCsp) { diff --git a/security/manager/ssl/nsCertOverrideService.cpp b/security/manager/ssl/nsCertOverrideService.cpp -index 1b9f32fc97bf3c5000db95567eaab85b518fe03a..3a39859d58b05b373e9db7ebb2b7ae37166d74e7 100644 +index 8413eb5916f1f857e18972a14292d14f32684aee..66a3c7b01fdc56c29d789ff786aa91d8b0f02cd6 100644 --- a/security/manager/ssl/nsCertOverrideService.cpp +++ b/security/manager/ssl/nsCertOverrideService.cpp @@ -433,7 +433,12 @@ nsCertOverrideService::HasMatchingOverride( @@ -2624,7 +2669,7 @@ index 75555352b8a15a50e4a21e34fc8ede4e9246c7cc..72855a404effa42b6c55cd0c2fcb8bdd // ignored for Linux. const unsigned long CHROME_SUPPRESS_ANIMATION = 1 << 24; diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs -index 8b975a8b11bcf2eabbb7fa51a431ff99ff69a5bc..0eeb5924c43a21b8561dd4b68fa89228ddcbc708 100644 +index 76fb919603e8d2b7864d351eb47be2a38e40e31e..cdfef96e20bea13799751154f4076bbcc2f827d4 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -108,6 +108,12 @@ EnterprisePoliciesManager.prototype = { @@ -2641,10 +2686,10 @@ index 8b975a8b11bcf2eabbb7fa51a431ff99ff69a5bc..0eeb5924c43a21b8561dd4b68fa89228 if (provider.failed) { diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp -index cb235ed5b39fe5092a17b12976121ee3952d2062..649bba4c0faab1745f3040eba5925ef48b8f34f9 100644 +index 253171bed4dea54fc28bb4ddc9920823dbd9351c..6dc0e620b399ed9ee6b53f97bc080ec17ee4e1b5 100644 --- a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp +++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp -@@ -489,7 +489,7 @@ void PopulateLanguages() { +@@ -490,7 +490,7 @@ void PopulateLanguages() { // sufficient to only collect this information as the other properties are // just reformats of Navigator::GetAcceptLanguages. nsTArray languages; @@ -2682,10 +2727,10 @@ index 654903fadb709be976b72f36f155e23bc0622152..815b3dc24c9fda6b1db6c4666ac68904 int32_t aMaxSelfProgress, int32_t aCurTotalProgress, diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp -index 585a957fd8a1467dc262bd1ca2058584fd8762c9..16ad38c3b7d753c386e091af700d1bebd4c59e3e 100644 +index 811fb16410e8cf900ad873797269e5fe715579a5..821f5b0c2af8e1dc8754cd023571d1d0ff09eeb6 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp -@@ -1875,7 +1875,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( +@@ -1880,7 +1880,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( // Open a minimal popup. *aIsPopupRequested = true; @@ -2699,10 +2744,10 @@ index 585a957fd8a1467dc262bd1ca2058584fd8762c9..16ad38c3b7d753c386e091af700d1beb /** diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs -index b050236a46af8dc5f1b72559065c813db9343088..85873915d92d49fddff3b7c4330cbe2d39719f39 100644 +index 40f04aeace0efd701e9454bb8dc6260dec90807e..5b70f65f3e78fc0889b15651ff203bb82e79d202 100644 --- a/toolkit/mozapps/update/UpdateService.sys.mjs +++ b/toolkit/mozapps/update/UpdateService.sys.mjs -@@ -3752,6 +3752,8 @@ export class UpdateService { +@@ -3814,6 +3814,8 @@ export class UpdateService { } get disabledForTesting() { @@ -2723,22 +2768,6 @@ index c50b7f3932e18da9fad4b673e353974a001e78c4..708e0d75594ddcd62276d4e08c4bd5c6 "/toolkit/components/telemetry/tests/marionette", ] -diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp -index 7a16ea20770dd945e743871e15e5b02da4842c60..e608a81c3b069b4a020da2d7807556975f603d33 100644 ---- a/toolkit/xre/nsAppRunner.cpp -+++ b/toolkit/xre/nsAppRunner.cpp -@@ -5669,7 +5669,10 @@ nsresult XREMain::XRE_mainRun() { - - if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { - #ifdef XP_MACOSX -- if (!BackgroundTasks::IsBackgroundTaskMode()) { -+# if defined(MOZ_BACKGROUNDTASKS) -+ if (!BackgroundTasks::IsBackgroundTaskMode()) -+# endif // defined(MOZ_BACKGROUNDTASKS) -+ { - rv = appStartup->CreateHiddenWindow(); - NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); - } diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp index 7eb9e1104682d4eb47060654f43a1efa8b2a6bb2..a8315d6decf654b5302bea5beeea34140c300ded 100644 --- a/toolkit/xre/nsWindowsWMain.cpp @@ -2792,7 +2821,7 @@ index e5cc386651e192710b61858ab5625c97a02b92da..e560ad4fef232a26ce1e1b244f4ccea0 // nsDocumentViewer::LoadComplete that doesn't do various things // that are not relevant here because this wasn't an actual diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp -index 418902fc1d00a2f0bc06bd68402e8bb342485c0a..101a7b63128743862d404c3fcadaa2aa886a7f8a 100644 +index e23df8e6f982ea71eb1f07dd677ed13109d2831b..d98f49d34a346113fd0ed5c242d5ef228ea0e0cd 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -112,6 +112,7 @@ @@ -3027,10 +3056,10 @@ index 5ca1a6fa13233b1bd00ee0467732c5875c51d343..0d3b8ebe127e59516802e8819f4bbed9 mIgnoreCapturingContent = aEvent.mIgnoreCapturingContent; mClickEventPrevented = aEvent.mClickEventPrevented; diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.mm -index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce8050630a1aa 100644 +index 24b70173c2e8bb9be9fd6255984a70efe3b14099..75ac367a1c4bb44d4b68b5f4ecc6adf56dbd408e 100644 --- a/widget/cocoa/NativeKeyBindings.mm +++ b/widget/cocoa/NativeKeyBindings.mm -@@ -528,6 +528,13 @@ +@@ -549,6 +549,13 @@ break; case KEY_NAME_INDEX_ArrowLeft: if (aEvent.IsAlt()) { @@ -3044,7 +3073,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 break; } if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) { -@@ -550,6 +557,13 @@ +@@ -571,6 +578,13 @@ break; case KEY_NAME_INDEX_ArrowRight: if (aEvent.IsAlt()) { @@ -3058,7 +3087,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 break; } if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) { -@@ -572,6 +586,10 @@ +@@ -593,6 +607,10 @@ break; case KEY_NAME_INDEX_ArrowUp: if (aEvent.IsControl()) { @@ -3069,7 +3098,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 break; } if (aEvent.IsMeta()) { -@@ -582,7 +600,7 @@ +@@ -603,7 +621,7 @@ !aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveToBeginningOfDocument:)) : ToObjcSelectorPtr( @@ -3078,7 +3107,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 aCommands); break; } -@@ -609,6 +627,10 @@ +@@ -630,6 +648,10 @@ break; case KEY_NAME_INDEX_ArrowDown: if (aEvent.IsControl()) { @@ -3293,7 +3322,7 @@ index 39833c28e40c61e354119cde429b8389056bafac..a638fb7520b857219ce58fcbf9ca0ed9 ~HeadlessWidget(); bool mEnabled; diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h -index a1f48167403f5bfb30a66809ec3e64bea468fa05..eac7fccf3493e162629918462294456e6ee6b6e1 100644 +index f7262978239665cbe20470da0790d4d177d4c501..70d11aca3d5b509cf5b37d626299a23fede73ba3 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -244,6 +244,7 @@ struct ParamTraits { diff --git a/browser_patches/firefox/preferences/playwright.cfg b/browser_patches/firefox/preferences/playwright.cfg index b5c5e2254c182..8c0404cbe73ac 100644 --- a/browser_patches/firefox/preferences/playwright.cfg +++ b/browser_patches/firefox/preferences/playwright.cfg @@ -96,6 +96,14 @@ pref("geo.provider.testing", true); pref("media.getdisplaymedia.screencapturekit.enabled", false); pref("media.getdisplaymedia.screencapturekit.picker.enabled", false); +// Allow proxying loopback URLs; see https://phabricator.services.mozilla.com/D237187 +pref("network.proxy.allow_hijacking_localhost", true); +pref("network.proxy.testing_localhost_is_secure_when_hijacked", true); + +// Disable double-dispatching of "input" event for the text composition. +// See https://phabricator.services.mozilla.com/D234620 for details. +pref("dom.input_events.dispatch_before_compositionend", false); + // Enable software-backed webgl. See https://phabricator.services.mozilla.com/D164016 pref("webgl.forbid-software", false); diff --git a/browser_patches/webkit/UPSTREAM_CONFIG.sh b/browser_patches/webkit/UPSTREAM_CONFIG.sh index 2cb527289333b..a5ab8feea5500 100644 --- a/browser_patches/webkit/UPSTREAM_CONFIG.sh +++ b/browser_patches/webkit/UPSTREAM_CONFIG.sh @@ -1,3 +1,3 @@ REMOTE_URL="https://github.com/WebKit/WebKit.git" BASE_BRANCH="main" -BASE_REVISION="ba8bcf39b0a89706b998447abba82590ad50fc36" +BASE_REVISION="4dd862e5ce1c10d77f72be94260164c5f13aafbd" diff --git a/browser_patches/webkit/embedder/Playwright/win/MainWindow.cpp b/browser_patches/webkit/embedder/Playwright/win/MainWindow.cpp index e74590c491860..f8430a601d6ef 100644 --- a/browser_patches/webkit/embedder/Playwright/win/MainWindow.cpp +++ b/browser_patches/webkit/embedder/Playwright/win/MainWindow.cpp @@ -140,7 +140,7 @@ void MainWindow::createToolbar(HINSTANCE hInstance) SendMessage(m_hToolbarWnd, TB_ADDBUTTONS, _countof(tbButtons), reinterpret_cast(&tbButtons)); ShowWindow(m_hToolbarWnd, true); - m_hURLBarWnd = CreateWindow(L"EDIT", 0, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOVSCROLL, 0, 0, 0, 0, m_hToolbarWnd, 0, hInstance, 0); + m_hURLBarWnd = CreateWindow(L"EDIT", 0, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL, 0, 0, 0, 0, m_hToolbarWnd, 0, hInstance, 0); DefEditProc = reinterpret_cast(GetWindowLongPtr(m_hURLBarWnd, GWLP_WNDPROC)); SetWindowLongPtr(m_hURLBarWnd, GWLP_WNDPROC, reinterpret_cast(EditProc)); @@ -210,7 +210,9 @@ bool MainWindow::init(HINSTANCE hInstance, WKPageConfigurationRef conf) resizeSubViews(); if (s_headless) { + auto menu = GetMenu(m_hMainWnd); SetMenu(m_hMainWnd, NULL); + DestroyMenu(menu); } else { SetFocus(m_hURLBarWnd); ShowWindow(m_hMainWnd, SW_SHOW); diff --git a/browser_patches/webkit/embedder/Playwright/win/Playwright.ico b/browser_patches/webkit/embedder/Playwright/win/Playwright.ico index 0137fe83a54ddf068a6c9885dca0397a2c812a0c..cea0bdebc38b20b92f39948ee5df0ea6c9254a2c 100644 GIT binary patch literal 113314 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 literal 122845 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", + "parameters": [ + { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "description": "Input element handle." }, -+ { "name": "files", "type": "array", "items": { "$ref": "FilePayload" }, "optional": true, "description": "Files to set" }, -+ { "name": "paths", "type": "array", "items": { "type": "string" }, "optional": true, "description": "File paths to set" } ++ { "name": "paths", "type": "array", "items": { "type": "string" }, "description": "File paths to set" } + ], + "async": true } @@ -1697,7 +1679,7 @@ index 24891ad836086fd23024fcb4d08ca63f6974c812..29f4b6b1923383fec7a99d28a4e815dc private: enum ArgumentRequirement { ArgumentRequired, ArgumentNotRequired }; diff --git a/Source/ThirdParty/libwebrtc/CMakeLists.txt b/Source/ThirdParty/libwebrtc/CMakeLists.txt -index 2ccb951b0cf57707b68ce5e971c5e191b91f2bd0..6ff141b41f4b6c279c367ad66d5c2b6adede8646 100644 +index ca4f3508a44e3c6677a72fbe3d7c853714b4f2c6..ae117f5f402a7eb259e376ca9440e00062e22d9f 100644 --- a/Source/ThirdParty/libwebrtc/CMakeLists.txt +++ b/Source/ThirdParty/libwebrtc/CMakeLists.txt @@ -532,6 +532,11 @@ set(webrtc_SOURCES @@ -1712,15 +1694,7 @@ index 2ccb951b0cf57707b68ce5e971c5e191b91f2bd0..6ff141b41f4b6c279c367ad66d5c2b6a Source/third_party/libyuv/source/compare.cc Source/third_party/libyuv/source/compare_common.cc Source/third_party/libyuv/source/compare_gcc.cc -@@ -695,7 +700,6 @@ set(webrtc_SOURCES - Source/webrtc/api/video_codecs/builtin_video_encoder_factory.cc - Source/webrtc/api/video_codecs/h264_profile_level_id.cc - Source/webrtc/api/video_codecs/h265_profile_tier_level.cc -- Source/webrtc/api/video_codecs/libaom_av1_encoder_factory.cc - Source/webrtc/api/video_codecs/scalability_mode.cc - Source/webrtc/api/video_codecs/scalability_mode_helper.cc - Source/webrtc/api/video_codecs/sdp_video_format.cc -@@ -2350,6 +2354,11 @@ set(webrtc_INCLUDE_DIRECTORIES PRIVATE +@@ -2348,6 +2353,11 @@ set(webrtc_INCLUDE_DIRECTORIES PRIVATE Source/third_party/libsrtp/config Source/third_party/libsrtp/crypto/include Source/third_party/libsrtp/include @@ -1746,13 +1720,13 @@ index 0c5c8e689bdddec766f9de5bffd4444a5e068d77..330dd1f585e530722178c65c883641a2 // FIXME: Set WEBRTC_USE_BUILTIN_ISAC_FIX and WEBRTC_USE_BUILTIN_ISAC_FLOAT for iOS and Mac diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp -index 6c754858a3ea883a65ad022ce16b6c45ae65ee35..15f968472712e1a2a88672ed290b103cf9d620ec 100644 +index f7995f8f8b7af9fb6b28fe81b371a16700549cdf..c372447fbe21912803997876f95b5ca263cb47b5 100644 --- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp +++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp -@@ -421,3 +421,16 @@ __ZNK8mkvmuxer7Segment16GetTrackByNumberEy - __ZN8mkvmuxer6Tracks11kAv1CodecIdE - __ZN8mkvmuxer6Tracks11kVp8CodecIdE - __ZN8mkvmuxer6Tracks11kVp9CodecIdE +@@ -425,3 +425,16 @@ __ZN6webrtc21SdpSerializeCandidateERKN7cricket9CandidateE + __ZNK6webrtc18VideoFrameMetadata8GetCsrcsEv + __ZN6webrtc15CloneAudioFrameEPNS_32TransformableAudioFrameInterfaceE + __ZN6webrtc15CloneVideoFrameEPNS_32TransformableVideoFrameInterfaceE +__ZN8mkvmuxer11SegmentInfo4InitEv +__ZN8mkvmuxer9MkvWriterC1EP7__sFILE +_ARGBToI420 @@ -1767,10 +1741,10 @@ index 6c754858a3ea883a65ad022ce16b6c45ae65ee35..15f968472712e1a2a88672ed290b103c +_vpx_codec_version_str +_vpx_codec_vp8_cx diff --git a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj -index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150c43a4b3c 100644 +index 7585b2e5e9bffdc8cabd888dd822313d53b30141..5909d33b9726cdc7d2d53b538f7da18bc221e30b 100644 --- a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj +++ b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj -@@ -55,6 +55,20 @@ +@@ -56,6 +56,20 @@ }; /* End PBXAggregateTarget section */ @@ -1791,7 +1765,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 /* Begin PBXBuildFile section */ 2D6BFF60280A93DF00A1A74F /* video_coding.h in Headers */ = {isa = PBXBuildFile; fileRef = 4131C45B234C81710028A615 /* video_coding.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2D6BFF61280A93EC00A1A74F /* video_codec_initializer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4131C45E234C81720028A615 /* video_codec_initializer.h */; settings = {ATTRIBUTES = (Public, ); }; }; -@@ -5772,6 +5786,13 @@ +@@ -5786,6 +5800,13 @@ remoteGlobalIDString = DDF30D0527C5C003006A526F; remoteInfo = absl; }; @@ -1805,7 +1779,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ -@@ -24241,6 +24262,7 @@ +@@ -24271,6 +24292,7 @@ ); dependencies = ( 410B3827292B73E90003E515 /* PBXTargetDependency */, @@ -1813,7 +1787,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 DD2E76E827C6B69A00F2A74C /* PBXTargetDependency */, CDEBB4CC24C01AB400ADBD44 /* PBXTargetDependency */, 411ED040212E0811004320BA /* PBXTargetDependency */, -@@ -24334,6 +24356,7 @@ +@@ -24364,6 +24386,7 @@ 4460B8B92B155B6A00392062 /* vp9_qp_parser_fuzzer */, 444A6EF02AEADFC9005FE121 /* vp9_replay_fuzzer */, 44945C512B9BA1C300447FFD /* webm_fuzzer */, @@ -1821,7 +1795,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 ); }; /* End PBXProject section */ -@@ -24437,6 +24460,23 @@ +@@ -24467,6 +24490,23 @@ shellPath = /bin/sh; shellScript = "[ -z \"${WK_DERIVED_SDK_HEADERS_DIR}\" -o -d \"${WK_DERIVED_SDK_HEADERS_DIR}\" ] && touch \"${SCRIPT_OUTPUT_FILE_0}\"\n"; }; @@ -1845,7 +1819,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ -@@ -27400,6 +27440,11 @@ +@@ -27437,6 +27477,11 @@ target = DDF30D0527C5C003006A526F /* absl */; targetProxy = DD2E76E727C6B69A00F2A74C /* PBXContainerItemProxy */; }; @@ -1857,7 +1831,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ -@@ -28167,6 +28212,27 @@ +@@ -28204,6 +28249,27 @@ }; name = Production; }; @@ -1885,7 +1859,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 FB39D0711200ED9200088E69 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D7C59C71208C68B001C873E /* DebugRelease.xcconfig */; -@@ -28549,6 +28615,16 @@ +@@ -28586,6 +28652,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Production; }; @@ -1903,7 +1877,7 @@ index 2c30b5b08d589c82def812fb881a63cb6e49e7b4..c76a8a2963923bf54c3d134aa2594150 isa = XCConfigurationList; buildConfigurations = ( diff --git a/Source/ThirdParty/skia/CMakeLists.txt b/Source/ThirdParty/skia/CMakeLists.txt -index 74b12c3ccae8b65ad3a35063bece92a9ecb27cbe..1656508a1c5a2103b2bab3068305c6bdba7f0208 100644 +index 6bfc5cba986488f3d808ebd0583c476cd93da70e..f4c4222d17bce640355ba52e00152069d5b14432 100644 --- a/Source/ThirdParty/skia/CMakeLists.txt +++ b/Source/ThirdParty/skia/CMakeLists.txt @@ -10,6 +10,8 @@ if (USE_SKIA_ENCODERS) @@ -1915,7 +1889,7 @@ index 74b12c3ccae8b65ad3a35063bece92a9ecb27cbe..1656508a1c5a2103b2bab3068305c6bd if (ANDROID) find_package(EXPAT REQUIRED) endif () -@@ -947,6 +949,7 @@ endif () +@@ -948,6 +950,7 @@ endif () target_link_libraries(Skia PRIVATE JPEG::JPEG PNG::PNG @@ -1923,8 +1897,20 @@ index 74b12c3ccae8b65ad3a35063bece92a9ecb27cbe..1656508a1c5a2103b2bab3068305c6bd ) WEBKIT_ADD_TARGET_CXX_FLAGS(Skia +diff --git a/Source/ThirdParty/skia/src/opts/SkOpts_SetTarget.h b/Source/ThirdParty/skia/src/opts/SkOpts_SetTarget.h +index 525cfcb862ae96bf8573d00b67dc9e5e23c10d22..f2debc0444cb8f5b80a0e99a2214bceaab3960c1 100644 +--- a/Source/ThirdParty/skia/src/opts/SkOpts_SetTarget.h ++++ b/Source/ThirdParty/skia/src/opts/SkOpts_SetTarget.h +@@ -65,6 +65,7 @@ + // Each of the specific intrinsic headers also checks to ensure that immintrin.h has been + // included, so do that here, first. + #if defined(__clang__) && defined(_MSC_VER) ++ #define __RTMINTRIN_H // Workaround for https://github.com/llvm/llvm-project/issues/95133 + #include + #endif + diff --git a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml -index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0ddd2ca52c 100644 +index e6e1af5d1973c991229948e0b1eb0d5c456f846f..7fb6e2455bd489c5b5f9f2169fea5a2f9865bf91 100644 --- a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml +++ b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml @@ -602,6 +602,7 @@ ApplePayEnabled: @@ -1958,7 +1944,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false BlobRegistryTopOriginPartitioningEnabled: -@@ -2270,6 +2268,7 @@ CrossOriginEmbedderPolicyEnabled: +@@ -2312,6 +2310,7 @@ CrossOriginEmbedderPolicyEnabled: WebCore: default: false @@ -1966,7 +1952,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d CrossOriginOpenerPolicyEnabled: type: bool status: stable -@@ -2310,7 +2309,7 @@ CustomPasteboardDataEnabled: +@@ -2352,7 +2351,7 @@ CustomPasteboardDataEnabled: WebKitLegacy: default: false WebKit: @@ -1975,7 +1961,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false DOMAudioSessionEnabled: -@@ -2343,6 +2342,7 @@ DOMAudioSessionFullEnabled: +@@ -2385,6 +2384,7 @@ DOMAudioSessionFullEnabled: WebCore: default: false @@ -1983,7 +1969,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d DOMPasteAccessRequestsEnabled: type: bool status: internal -@@ -2354,7 +2354,7 @@ DOMPasteAccessRequestsEnabled: +@@ -2396,7 +2396,7 @@ DOMPasteAccessRequestsEnabled: default: false WebKit: "PLATFORM(IOS) || PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(VISION)": true @@ -1992,7 +1978,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d WebCore: default: false -@@ -2420,10 +2420,10 @@ DataListElementEnabled: +@@ -2462,10 +2462,10 @@ DataListElementEnabled: WebKitLegacy: default: false WebKit: @@ -2005,7 +1991,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false sharedPreferenceForWebProcess: true -@@ -2436,7 +2436,7 @@ DataTransferItemsEnabled: +@@ -2478,7 +2478,7 @@ DataTransferItemsEnabled: WebKitLegacy: default: true WebKit: @@ -2014,7 +2000,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -2691,7 +2691,7 @@ DirectoryUploadEnabled: +@@ -2734,7 +2734,7 @@ DirectoryUploadEnabled: WebKitLegacy: default: false WebKit: @@ -2023,7 +2009,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -3143,10 +3143,10 @@ FullScreenEnabled: +@@ -3202,10 +3202,10 @@ FullScreenEnabled: WebKitLegacy: default: false WebKit: @@ -2036,7 +2022,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false sharedPreferenceForWebProcess: true -@@ -3707,7 +3707,7 @@ InputTypeColorEnabled: +@@ -3811,7 +3811,7 @@ InputTypeColorEnabled: WebKitLegacy: default: false WebKit: @@ -2045,7 +2031,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -3740,7 +3740,7 @@ InputTypeDateEnabled: +@@ -3844,7 +3844,7 @@ InputTypeDateEnabled: "PLATFORM(IOS_FAMILY)": true default: false WebKit: @@ -2054,7 +2040,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -3756,7 +3756,7 @@ InputTypeDateTimeLocalEnabled: +@@ -3860,7 +3860,7 @@ InputTypeDateTimeLocalEnabled: "PLATFORM(IOS_FAMILY)": true default: false WebKit: @@ -2063,7 +2049,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -3788,7 +3788,7 @@ InputTypeTimeEnabled: +@@ -3892,7 +3892,7 @@ InputTypeTimeEnabled: "PLATFORM(IOS_FAMILY)": true default: false WebKit: @@ -2072,24 +2058,24 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -3836,6 +3836,7 @@ InspectorAttachmentSide: - WebKit: - default: 0 +@@ -3953,6 +3953,7 @@ InspectorMaximumResourcesContentSize: + "PLATFORM(WPE)": 50 + default: 200 +# Playwright: disable setting. InspectorStartsAttached: type: bool status: embedder -@@ -3843,7 +3844,7 @@ InspectorStartsAttached: +@@ -3960,7 +3961,7 @@ InspectorStartsAttached: exposed: [ WebKit ] defaultValue: WebKit: - default: true + default: false - InspectorWindowFrame: - type: String -@@ -5778,7 +5779,7 @@ PermissionsAPIEnabled: + InspectorSupportsShowingCertificate: + type: bool +@@ -5911,7 +5912,7 @@ PermissionsAPIEnabled: WebKitLegacy: default: false WebKit: @@ -2098,7 +2084,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false WebCore: default: false -@@ -5841,6 +5842,19 @@ PitchCorrectionAlgorithm: +@@ -5990,6 +5991,19 @@ PitchCorrectionAlgorithm: WebCore: default: MediaPlayerEnums::PitchCorrectionAlgorithm::BestAllAround @@ -2118,7 +2104,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d PointerLockOptionsEnabled: type: bool status: stable -@@ -6420,7 +6434,7 @@ ScreenOrientationAPIEnabled: +@@ -6586,7 +6600,7 @@ ScreenOrientationAPIEnabled: WebKitLegacy: default: false WebKit: @@ -2127,7 +2113,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d WebCore: default: false sharedPreferenceForWebProcess: true -@@ -7879,6 +7893,7 @@ UseCGDisplayListsForDOMRendering: +@@ -8063,6 +8077,7 @@ UseCGDisplayListsForDOMRendering: default: true sharedPreferenceForWebProcess: true @@ -2135,7 +2121,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d UseGPUProcessForCanvasRenderingEnabled: type: bool status: stable -@@ -7891,7 +7906,7 @@ UseGPUProcessForCanvasRenderingEnabled: +@@ -8075,7 +8090,7 @@ UseGPUProcessForCanvasRenderingEnabled: defaultValue: WebKit: "ENABLE(GPU_PROCESS_BY_DEFAULT)": true @@ -2144,7 +2130,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d default: false UseGPUProcessForDOMRenderingEnabled: -@@ -7936,6 +7951,7 @@ UseGPUProcessForMediaEnabled: +@@ -8120,6 +8135,7 @@ UseGPUProcessForMediaEnabled: sharedPreferenceForWebProcess: true mediaPlaybackRelated: true @@ -2152,7 +2138,7 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d UseGPUProcessForWebGLEnabled: type: bool status: internal -@@ -7947,7 +7963,7 @@ UseGPUProcessForWebGLEnabled: +@@ -8131,7 +8147,7 @@ UseGPUProcessForWebGLEnabled: default: false WebKit: "ENABLE(GPU_PROCESS_BY_DEFAULT) && ENABLE(GPU_PROCESS_WEBGL_BY_DEFAULT)": true @@ -2162,10 +2148,10 @@ index 02d962ba310f7e231c84657823766c834ae5b6a6..44587b824e07c1d8a9fa99b32a02da0d WebCore: "ENABLE(GPU_PROCESS_BY_DEFAULT) && ENABLE(GPU_PROCESS_WEBGL_BY_DEFAULT)": true diff --git a/Source/WTF/wtf/PlatformEnable.h b/Source/WTF/wtf/PlatformEnable.h -index d86540738b2e5e66811b67dd07889c6188126dd7..128f697e7a46b2c6af7716933ee8845228eaba0e 100644 +index 0652dc379f7322cd7af4078cf1b6c54588565aea..51ce8260d8889c6e3dbef09bfff48177b9cf9f0a 100644 --- a/Source/WTF/wtf/PlatformEnable.h +++ b/Source/WTF/wtf/PlatformEnable.h -@@ -385,7 +385,7 @@ +@@ -387,7 +387,7 @@ // ORIENTATION_EVENTS should never get enabled on Desktop, only Mobile. #if !defined(ENABLE_ORIENTATION_EVENTS) @@ -2174,7 +2160,7 @@ index d86540738b2e5e66811b67dd07889c6188126dd7..128f697e7a46b2c6af7716933ee88452 #endif #if !defined(ENABLE_OVERFLOW_SCROLLING_TOUCH) -@@ -502,7 +502,7 @@ +@@ -504,7 +504,7 @@ #endif #if !defined(ENABLE_TOUCH_EVENTS) @@ -2184,10 +2170,10 @@ index d86540738b2e5e66811b67dd07889c6188126dd7..128f697e7a46b2c6af7716933ee88452 #if !defined(ENABLE_TOUCH_ACTION_REGIONS) diff --git a/Source/WTF/wtf/PlatformEnableCocoa.h b/Source/WTF/wtf/PlatformEnableCocoa.h -index bacd225fea9dc695e021649fc6d74012e300be27..9e5497d6f0ecca35bbb5fc2a53f36ee8a3b39435 100644 +index e416fc014aa5731d77f7a3a440b762a9814f6ef5..00a67ad2516627e982763f4684fb951741517817 100644 --- a/Source/WTF/wtf/PlatformEnableCocoa.h +++ b/Source/WTF/wtf/PlatformEnableCocoa.h -@@ -813,7 +813,7 @@ +@@ -817,7 +817,7 @@ #endif #if !defined(ENABLE_SEC_ITEM_SHIM) @@ -2197,10 +2183,10 @@ index bacd225fea9dc695e021649fc6d74012e300be27..9e5497d6f0ecca35bbb5fc2a53f36ee8 #if !defined(ENABLE_SERVER_PRECONNECT) diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h -index 93e12dd54312a4c8479687b9f175c948250339f8..b39779453f1d51c302e5c5a39d58594a63eb22a2 100644 +index e4a8591c4a4e0d682cdf3d4232e25e6af963ba16..96a65e06d788995714bafc95bde3ad8bf21bc2e8 100644 --- a/Source/WTF/wtf/PlatformHave.h +++ b/Source/WTF/wtf/PlatformHave.h -@@ -1220,7 +1220,8 @@ +@@ -1198,7 +1198,8 @@ #endif #if PLATFORM(MAC) @@ -2227,10 +2213,10 @@ index 007b8fe3292f326504013be8198ae020f7aacf35..1c722c473732ffe05fdb61010fa4417e namespace Unicode { diff --git a/Source/WebCore/DerivedSources.make b/Source/WebCore/DerivedSources.make -index 6b62ff89947f5a393ee50381af9f6ddfecc91037..202376ddb4a475a23e0379e26f866451c6e184ba 100644 +index d0e3444bd9699dfe2bc1c85dde2ad5543d71b0df..b624441883bfa4ea04ee22fef2c38ad0daabe479 100644 --- a/Source/WebCore/DerivedSources.make +++ b/Source/WebCore/DerivedSources.make -@@ -1221,6 +1221,10 @@ JS_BINDING_IDLS := \ +@@ -1229,6 +1229,10 @@ JS_BINDING_IDLS := \ $(WebCore)/dom/SubscriberCallback.idl \ $(WebCore)/dom/SubscriptionObserver.idl \ $(WebCore)/dom/SubscriptionObserverCallback.idl \ @@ -2241,7 +2227,7 @@ index 6b62ff89947f5a393ee50381af9f6ddfecc91037..202376ddb4a475a23e0379e26f866451 $(WebCore)/dom/Text.idl \ $(WebCore)/dom/TextDecoder.idl \ $(WebCore)/dom/TextDecoderStream.idl \ -@@ -1821,9 +1825,6 @@ JS_BINDING_IDLS := \ +@@ -1829,9 +1833,6 @@ JS_BINDING_IDLS := \ ADDITIONAL_BINDING_IDLS = \ DocumentTouch.idl \ GestureEvent.idl \ @@ -2252,7 +2238,7 @@ index 6b62ff89947f5a393ee50381af9f6ddfecc91037..202376ddb4a475a23e0379e26f866451 vpath %.in $(WEBKITADDITIONS_HEADER_SEARCH_PATHS) diff --git a/Source/WebCore/Modules/geolocation/Geolocation.cpp b/Source/WebCore/Modules/geolocation/Geolocation.cpp -index d66eb4a0c175ab495ef10bd393115165b5545fc2..cd89bd44bd2a8b3036c3446c01475bf726a3a18a 100644 +index 05091a46212f00333ed97939a6ec18d8656657e9..3fb779a3eba806cf416d0dd7586be871d4782651 100644 --- a/Source/WebCore/Modules/geolocation/Geolocation.cpp +++ b/Source/WebCore/Modules/geolocation/Geolocation.cpp @@ -361,8 +361,9 @@ bool Geolocation::shouldBlockGeolocationRequests() @@ -2316,10 +2302,10 @@ index 7bbcd501126a7b83986f5d1f5a077779441dc1fe..13e68ac853603d8e7da1b42f5c8073eb set(WebCore_USER_AGENT_SCRIPTS_DEPENDENCIES ${WEBCORE_DIR}/platform/wpe/RenderThemeWPE.cpp) diff --git a/Source/WebCore/SourcesCocoa.txt b/Source/WebCore/SourcesCocoa.txt -index 7cd931c48dd3c7c4cc6136c91692054ca98e6ddf..e32cfb28521033f1713bd963ba9825a9500f4fc9 100644 +index 06a9accfc8e6c46493733663b5d76b07fc80db22..4946d012d166c84b25d4d954266c4dc528f7d8ad 100644 --- a/Source/WebCore/SourcesCocoa.txt +++ b/Source/WebCore/SourcesCocoa.txt -@@ -733,3 +733,9 @@ testing/cocoa/WebViewVisualIdentificationOverlay.mm +@@ -734,3 +734,9 @@ testing/cocoa/WebViewVisualIdentificationOverlay.mm platform/graphics/angle/GraphicsContextGLANGLE.cpp @no-unify platform/graphics/cocoa/GraphicsContextGLCocoa.mm @no-unify platform/graphics/cv/GraphicsContextGLCVCocoa.mm @no-unify @@ -2345,7 +2331,7 @@ index 9cc9f51e48b3058ea29da389629c021bcf05e202..978ac405073534ba6fa79f483d885643 +JSSpeechSynthesisEventInit.cpp +// Playwright: end. diff --git a/Source/WebCore/SourcesWPE.txt b/Source/WebCore/SourcesWPE.txt -index f785335022a20f31c8941c0aabd8895a17f71a3e..a3516d2db0cef38b3257b4b8653711f90f24c257 100644 +index 5fd26e859ef215fccd597ca475a320abcad83b98..f422c4023eb1c9f101d989ce56e4b4b42e203c83 100644 --- a/Source/WebCore/SourcesWPE.txt +++ b/Source/WebCore/SourcesWPE.txt @@ -46,6 +46,8 @@ editing/libwpe/EditorLibWPE.cpp @@ -2357,7 +2343,7 @@ index f785335022a20f31c8941c0aabd8895a17f71a3e..a3516d2db0cef38b3257b4b8653711f9 page/linux/ResourceUsageOverlayLinux.cpp page/linux/ResourceUsageThreadLinux.cpp -@@ -89,6 +91,17 @@ platform/text/LocaleICU.cpp +@@ -91,6 +93,17 @@ platform/android/SharedMemoryAndroid.cpp platform/unix/LoggingUnix.cpp platform/unix/SharedMemoryUnix.cpp @@ -2376,10 +2362,10 @@ index f785335022a20f31c8941c0aabd8895a17f71a3e..a3516d2db0cef38b3257b4b8653711f9 +JSSpeechSynthesisEventInit.cpp +// Playwright: end. diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj -index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17daa926e7 100644 +index 656e474f969c44c344c87d53aca6981071b39ef5..c8cf5ab6da2a5d2a2c2a723e52a32e2bee261846 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj -@@ -6404,6 +6404,13 @@ +@@ -6432,6 +6432,13 @@ EE0C7E042CE845CB0043DAF8 /* CSSPositionTryRule.h in Headers */ = {isa = PBXBuildFile; fileRef = EE0C7E002CE845CB0043DAF8 /* CSSPositionTryRule.h */; }; EE0D3C492D2F4B4C00072978 /* StageModeOperations.h in Headers */ = {isa = PBXBuildFile; fileRef = EE0D3C482D2F4AE600072978 /* StageModeOperations.h */; settings = {ATTRIBUTES = (Private, ); }; }; EFCC6C8F20FE914400A2321B /* CanvasActivityRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = EFCC6C8D20FE914000A2321B /* CanvasActivityRecord.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -2393,7 +2379,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 F12171F616A8CF0B000053CA /* WebVTTElement.h in Headers */ = {isa = PBXBuildFile; fileRef = F12171F416A8BC63000053CA /* WebVTTElement.h */; }; F32BDCD92363AACA0073B6AE /* UserGestureEmulationScope.h in Headers */ = {isa = PBXBuildFile; fileRef = F32BDCD72363AACA0073B6AE /* UserGestureEmulationScope.h */; }; F344C7141125B82C00F26EEE /* InspectorFrontendClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F344C7121125B82C00F26EEE /* InspectorFrontendClient.h */; settings = {ATTRIBUTES = (Private, ); }; }; -@@ -21038,6 +21045,14 @@ +@@ -21095,6 +21102,14 @@ EE7A169F2C607BFA0057B563 /* StartViewTransitionOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartViewTransitionOptions.h; sourceTree = ""; }; EFB7287B2124C73D005C2558 /* CanvasActivityRecord.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CanvasActivityRecord.cpp; sourceTree = ""; }; EFCC6C8D20FE914000A2321B /* CanvasActivityRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CanvasActivityRecord.h; sourceTree = ""; }; @@ -2408,7 +2394,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 F12171F316A8BC63000053CA /* WebVTTElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebVTTElement.cpp; sourceTree = ""; }; F12171F416A8BC63000053CA /* WebVTTElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebVTTElement.h; sourceTree = ""; }; F32BDCD52363AAC90073B6AE /* UserGestureEmulationScope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserGestureEmulationScope.cpp; sourceTree = ""; }; -@@ -28800,6 +28815,11 @@ +@@ -28876,6 +28891,11 @@ BC4A5324256055590028C592 /* TextDirectionSubmenuInclusionBehavior.h */, 2D4F96F11A1ECC240098BF88 /* TextIndicator.cpp */, 2D4F96F21A1ECC240098BF88 /* TextIndicator.h */, @@ -2420,7 +2406,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 F48570A42644C76D00C05F71 /* TranslationContextMenuInfo.h */, F4E1965F21F26E4E00285078 /* UndoItem.cpp */, 2ECDBAD521D8906300F00ECD /* UndoItem.h */, -@@ -35771,6 +35791,8 @@ +@@ -35855,6 +35875,8 @@ 29E4D8DF16B0940F00C84704 /* PlatformSpeechSynthesizer.h */, 1AD8F81A11CAB9E900E93E54 /* PlatformStrategies.cpp */, 1AD8F81911CAB9E900E93E54 /* PlatformStrategies.h */, @@ -2429,7 +2415,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 FE3DC9932D0C063C0021B6FC /* PlatformTZoneImpls.cpp */, 0FD7C21D23CE41E30096D102 /* PlatformWheelEvent.cpp */, 935C476A09AC4D4F00A6AAB4 /* PlatformWheelEvent.h */, -@@ -38569,6 +38591,7 @@ +@@ -38674,6 +38696,7 @@ AD6E71AB1668899D00320C13 /* DocumentSharedObjectPool.h */, 6BDB5DC1227BD3B800919770 /* DocumentStorageAccess.cpp */, 6BDB5DC0227BD3B800919770 /* DocumentStorageAccess.h */, @@ -2437,7 +2423,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 7CE7FA5B1EF882300060C9D6 /* DocumentTouch.cpp */, 7CE7FA591EF882300060C9D6 /* DocumentTouch.h */, A8185F3209765765005826D9 /* DocumentType.cpp */, -@@ -43482,6 +43505,8 @@ +@@ -43613,6 +43636,8 @@ F4E90A3C2B52038E002DA469 /* PlatformTextAlternatives.h in Headers */, 0F7D07331884C56C00B4AF86 /* PlatformTextTrack.h in Headers */, 074E82BB18A69F0E007EF54C /* PlatformTimeRanges.h in Headers */, @@ -2446,7 +2432,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 CDD08ABD277E542600EA3755 /* PlatformTrackConfiguration.h in Headers */, CD1F9B022700323D00617EB6 /* PlatformVideoColorPrimaries.h in Headers */, CD1F9B01270020B700617EB6 /* PlatformVideoColorSpace.h in Headers */, -@@ -44827,6 +44852,7 @@ +@@ -44969,6 +44994,7 @@ 0F54DD081881D5F5003EEDBB /* Touch.h in Headers */, 71B7EE0D21B5C6870031C1EF /* TouchAction.h in Headers */, 0F54DD091881D5F5003EEDBB /* TouchEvent.h in Headers */, @@ -2454,7 +2440,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 0F54DD0A1881D5F5003EEDBB /* TouchList.h in Headers */, 070334D71459FFD5008D8D45 /* TrackBase.h in Headers */, BE88E0C21715CE2600658D98 /* TrackListBase.h in Headers */, -@@ -46007,6 +46033,8 @@ +@@ -46169,6 +46195,8 @@ 2D22830323A8470700364B7E /* CursorMac.mm in Sources */, 5CBD59592280E926002B22AA /* CustomHeaderFields.cpp in Sources */, 07E4BDBF2A3A5FAB000D5509 /* DictationCaretAnimator.cpp in Sources */, @@ -2463,7 +2449,7 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 7CE6CBFD187F394900D46BF5 /* FormatConverter.cpp in Sources */, 4667EA3E2968D9DA00BAB1E2 /* GameControllerHapticEffect.mm in Sources */, 46FE73D32968E52000B8064C /* GameControllerHapticEngines.mm in Sources */, -@@ -46098,6 +46126,9 @@ +@@ -46260,6 +46288,9 @@ CE88EE262414467B007F29C2 /* TextAlternativeWithRange.mm in Sources */, BE39137129B267F500FA5D4F /* TextTransformCocoa.cpp in Sources */, 51DF6D800B92A18E00C2DC85 /* ThreadCheck.mm in Sources */, @@ -2474,10 +2460,10 @@ index d58402bdd7423c3274088b4e35a22922a107062a..4dc6b7adcaf5a34ffbe0b4aad5552a17 538EC8021F96AF81004D22A8 /* UnifiedSource1.cpp in Sources */, 538EC8051F96AF81004D22A8 /* UnifiedSource2-mm.mm in Sources */, diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp -index c6efab7897b328d46bfbca84f84fca8ad39bff48..bc5826d14817244396ddae91e57dde1312a4407e 100644 +index a38a54553f430b96fa7744a442676d6136f5eda9..4ff2eaeab053a186e5e6bacab94a6b3b129eb7ab 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp -@@ -70,6 +70,7 @@ +@@ -71,6 +71,7 @@ #include "HTMLTableSectionElement.h" #include "HTMLTextAreaElement.h" #include "HitTestResult.h" @@ -2485,7 +2471,7 @@ index c6efab7897b328d46bfbca84f84fca8ad39bff48..bc5826d14817244396ddae91e57dde13 #include "LocalFrame.h" #include "LocalizedStrings.h" #include "MathMLNames.h" -@@ -3987,7 +3988,12 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const +@@ -3910,7 +3911,12 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const if (roleValue() == AccessibilityRole::ApplicationDialog) return AccessibilityObjectInclusion::IncludeObject; @@ -2500,10 +2486,10 @@ index c6efab7897b328d46bfbca84f84fca8ad39bff48..bc5826d14817244396ddae91e57dde13 bool AccessibilityObject::isWithinHiddenWebArea() const diff --git a/Source/WebCore/bindings/js/WebCoreBuiltinNames.h b/Source/WebCore/bindings/js/WebCoreBuiltinNames.h -index 0a0523ff37b7a51dc2152f074fb57b17883bd80f..715881ff75557f4e9165b521061e4b7a35286eba 100644 +index a9d9ee8f0cb26fd47f9fea85e0856d2849362e6c..e2c58e571f41da4f65388fd36a9de56c92a404a8 100644 --- a/Source/WebCore/bindings/js/WebCoreBuiltinNames.h +++ b/Source/WebCore/bindings/js/WebCoreBuiltinNames.h -@@ -187,6 +187,8 @@ namespace WebCore { +@@ -189,6 +189,8 @@ namespace WebCore { macro(DelayNode) \ macro(DeprecationReportBody) \ macro(DigitalCredential) \ @@ -2513,10 +2499,10 @@ index 0a0523ff37b7a51dc2152f074fb57b17883bd80f..715881ff75557f4e9165b521061e4b7a macro(DynamicsCompressorNode) \ macro(ElementInternals) \ diff --git a/Source/WebCore/css/query/MediaQueryFeatures.cpp b/Source/WebCore/css/query/MediaQueryFeatures.cpp -index 83e2a42d3f09289d61217f63a121611a92aae85d..f4dfda8e9dc1dd9ad221272f09e1bd7f49afa1e0 100644 +index aec43490053f95341ef979385b5e6c1daf03a090..4e44b7e341c8247f916f8597092248d8ec884518 100644 --- a/Source/WebCore/css/query/MediaQueryFeatures.cpp +++ b/Source/WebCore/css/query/MediaQueryFeatures.cpp -@@ -496,7 +496,11 @@ static const IdentifierSchema& forcedColorsFeatureSchema() +@@ -498,7 +498,11 @@ static const IdentifierSchema& forcedColorsFeatureSchema() "forced-colors"_s, FixedVector { CSSValueNone, CSSValueActive }, OptionSet(), @@ -2529,7 +2515,7 @@ index 83e2a42d3f09289d61217f63a121611a92aae85d..f4dfda8e9dc1dd9ad221272f09e1bd7f return MatchingIdentifiers { CSSValueNone }; } }; -@@ -682,6 +686,9 @@ static const IdentifierSchema& prefersReducedMotionFeatureSchema() +@@ -686,6 +690,9 @@ static const IdentifierSchema& prefersReducedMotionFeatureSchema() [](auto& context) { bool userPrefersReducedMotion = [&] { Ref frame = *context.document->frame(); @@ -2768,7 +2754,7 @@ index 7813532cc52d582c42aebc979a1ecd1137765f08..c01cbd53ad2430a6ffab9a80fc73e74a #endif // USE(LIBWPE) diff --git a/Source/WebCore/html/FileInputType.cpp b/Source/WebCore/html/FileInputType.cpp -index 28d2a4af93da12474d1b699f6f0c46522514e3bb..0f20c78e509cce10e4179a1be91173b3de16db10 100644 +index 75de5d5611953b830cf07d940ca1eedb789d2a9c..6adabe7a8250e02bc6b1e9fa28b0575d6a5e4dd7 100644 --- a/Source/WebCore/html/FileInputType.cpp +++ b/Source/WebCore/html/FileInputType.cpp @@ -37,6 +37,7 @@ @@ -2780,30 +2766,19 @@ index 28d2a4af93da12474d1b699f6f0c46522514e3bb..0f20c78e509cce10e4179a1be91173b3 #include "LocalizedStrings.h" #include "MIMETypeRegistry.h" @@ -160,6 +161,11 @@ void FileInputType::handleDOMActivateEvent(Event& event) - if (input.isDisabledFormControl()) + if (protectedElement()->isDisabledFormControl()) return; + bool intercept = false; -+ InspectorInstrumentation::runOpenPanel(input.document().frame(), element(), &intercept); ++ InspectorInstrumentation::runOpenPanel(element()->document().frame(), element(), &intercept); + if (intercept) + return; + if (!UserGestureIndicator::processingUserGesture()) return; -@@ -345,7 +351,9 @@ void FileInputType::setFiles(RefPtr&& files, RequestIcon shouldRequest - pathsChanged = true; - else { - for (unsigned i = 0; i < length; ++i) { -- if (files->file(i).path() != m_fileList->file(i).path() || !FileSystem::fileIDsAreEqual(files->file(i).fileID(), m_fileList->file(i).fileID())) { -+ if (files->file(i).path() != m_fileList->file(i).path() || !FileSystem::fileIDsAreEqual(files->file(i).fileID(), m_fileList->file(i).fileID()) || -+ // Files created from Blob have empty path. -+ (files->file(i).path().isEmpty() && files->file(i).name() != m_fileList->file(i).name())) { - pathsChanged = true; - break; - } diff --git a/Source/WebCore/inspector/InspectorController.cpp b/Source/WebCore/inspector/InspectorController.cpp -index 79d7f4b39f4bc4ec2535bb355e567e39bc1f9ffa..6d1c4513780476767b4c51c8dcd3afd30088176d 100644 +index dac15af852ff335ffba6b4bff717682a99b52b48..29bceeebb5a3e1c6c5d9d782dd62530d75b57d9a 100644 --- a/Source/WebCore/inspector/InspectorController.cpp +++ b/Source/WebCore/inspector/InspectorController.cpp @@ -295,6 +295,8 @@ void InspectorController::disconnectFrontend(FrontendChannel& frontendChannel) @@ -2904,10 +2879,10 @@ index 4f5c1e836876710a554455ec53733f72db63de58..774c4a66af84664a7a83ba0790d147f7 } // namespace WebCore diff --git a/Source/WebCore/inspector/InspectorInstrumentation.cpp b/Source/WebCore/inspector/InspectorInstrumentation.cpp -index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8ea2728999 100644 +index 3d97cbaf887e52fe5c269cd4bc8cf750de41ddcb..46dc1872991560e41cff9c8b1ede2b73708c3736 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.cpp +++ b/Source/WebCore/inspector/InspectorInstrumentation.cpp -@@ -583,6 +583,12 @@ void InspectorInstrumentation::applyUserAgentOverrideImpl(InstrumentingAgents& i +@@ -595,6 +595,12 @@ void InspectorInstrumentation::applyUserAgentOverrideImpl(InstrumentingAgents& i pageAgent->applyUserAgentOverride(userAgent); } @@ -2920,7 +2895,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e void InspectorInstrumentation::applyEmulatedMediaImpl(InstrumentingAgents& instrumentingAgents, AtomString& media) { if (auto* pageAgent = instrumentingAgents.enabledPageAgent()) -@@ -666,6 +672,12 @@ void InspectorInstrumentation::didFailLoadingImpl(InstrumentingAgents& instrumen +@@ -678,6 +684,12 @@ void InspectorInstrumentation::didFailLoadingImpl(InstrumentingAgents& instrumen consoleAgent->didFailLoading(identifier, error); // This should come AFTER resource notification, front-end relies on this. } @@ -2933,7 +2908,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e void InspectorInstrumentation::willLoadXHRSynchronouslyImpl(InstrumentingAgents& instrumentingAgents) { if (auto* networkAgent = instrumentingAgents.enabledNetworkAgent()) -@@ -698,20 +710,17 @@ void InspectorInstrumentation::didReceiveScriptResponseImpl(InstrumentingAgents& +@@ -710,20 +722,17 @@ void InspectorInstrumentation::didReceiveScriptResponseImpl(InstrumentingAgents& void InspectorInstrumentation::domContentLoadedEventFiredImpl(InstrumentingAgents& instrumentingAgents, LocalFrame& frame) { @@ -2957,7 +2932,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e } void InspectorInstrumentation::frameDetachedFromParentImpl(InstrumentingAgents& instrumentingAgents, LocalFrame& frame) -@@ -791,12 +800,6 @@ void InspectorInstrumentation::frameDocumentUpdatedImpl(InstrumentingAgents& ins +@@ -803,12 +812,6 @@ void InspectorInstrumentation::frameDocumentUpdatedImpl(InstrumentingAgents& ins pageDOMDebuggerAgent->frameDocumentUpdated(frame); } @@ -2970,7 +2945,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e void InspectorInstrumentation::frameStartedLoadingImpl(InstrumentingAgents& instrumentingAgents, LocalFrame& frame) { if (frame.isMainFrame()) { -@@ -827,10 +830,10 @@ void InspectorInstrumentation::frameStoppedLoadingImpl(InstrumentingAgents& inst +@@ -839,10 +842,10 @@ void InspectorInstrumentation::frameStoppedLoadingImpl(InstrumentingAgents& inst inspectorPageAgent->frameStoppedLoading(frame); } @@ -2983,7 +2958,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e } void InspectorInstrumentation::frameClearedScheduledNavigationImpl(InstrumentingAgents& instrumentingAgents, Frame& frame) -@@ -845,6 +848,12 @@ void InspectorInstrumentation::accessibilitySettingsDidChangeImpl(InstrumentingA +@@ -857,6 +860,12 @@ void InspectorInstrumentation::accessibilitySettingsDidChangeImpl(InstrumentingA inspectorPageAgent->accessibilitySettingsDidChange(); } @@ -2996,7 +2971,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e #if ENABLE(DARK_MODE_CSS) void InspectorInstrumentation::defaultAppearanceDidChangeImpl(InstrumentingAgents& instrumentingAgents) { -@@ -897,6 +906,12 @@ void InspectorInstrumentation::interceptResponseImpl(InstrumentingAgents& instru +@@ -909,6 +918,12 @@ void InspectorInstrumentation::interceptResponseImpl(InstrumentingAgents& instru networkAgent->interceptResponse(response, identifier, WTFMove(handler)); } @@ -3009,7 +2984,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e // JavaScriptCore InspectorDebuggerAgent should know Console MessageTypes. static bool isConsoleAssertMessage(MessageSource source, MessageType type) { -@@ -1015,6 +1030,12 @@ void InspectorInstrumentation::consoleStopRecordingCanvasImpl(InstrumentingAgent +@@ -1027,6 +1042,12 @@ void InspectorInstrumentation::consoleStopRecordingCanvasImpl(InstrumentingAgent canvasAgent->consoleStopRecordingCanvas(context); } @@ -3022,7 +2997,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e void InspectorInstrumentation::didDispatchDOMStorageEventImpl(InstrumentingAgents& instrumentingAgents, const String& key, const String& oldValue, const String& newValue, StorageType storageType, const SecurityOrigin& securityOrigin) { if (auto* domStorageAgent = instrumentingAgents.enabledDOMStorageAgent()) -@@ -1305,6 +1326,36 @@ void InspectorInstrumentation::renderLayerDestroyedImpl(InstrumentingAgents& ins +@@ -1317,6 +1338,36 @@ void InspectorInstrumentation::renderLayerDestroyedImpl(InstrumentingAgents& ins layerTreeAgent->renderLayerDestroyed(renderLayer); } @@ -3059,7 +3034,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(WorkerOrWorkletGlobalScope& globalScope) { return globalScope.inspectorController().m_instrumentingAgents; -@@ -1321,6 +1372,13 @@ InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(Page& page) +@@ -1333,6 +1384,13 @@ InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(Page& page) return page.inspectorController().m_instrumentingAgents.get(); } @@ -3074,7 +3049,7 @@ index 7e789f4b0704d574b3bf180b2417822d9ee33994..80e6b4bc7810af19265d24e0de861c8e { // Using RefPtr makes us hit the m_inRemovedLastRefFunction assert. diff --git a/Source/WebCore/inspector/InspectorInstrumentation.h b/Source/WebCore/inspector/InspectorInstrumentation.h -index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f3005f541c3 100644 +index 0fb5a57841daca73bbc452b6bbe97d8bbb25500e..7aac51c58d0fc3640fcaf7f9cdf0776367020f9e 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.h +++ b/Source/WebCore/inspector/InspectorInstrumentation.h @@ -31,6 +31,7 @@ @@ -3101,7 +3076,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 class HTTPHeaderMap; class InspectorTimelineAgent; class InstrumentingAgents; -@@ -194,6 +197,7 @@ public: +@@ -196,6 +199,7 @@ public: static void didRecalculateStyle(Document&); static void didScheduleStyleRecalculation(Document&); static void applyUserAgentOverride(LocalFrame&, String&); @@ -3109,7 +3084,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void applyEmulatedMedia(LocalFrame&, AtomString&); static void flexibleBoxRendererBeganLayout(const RenderObject&); -@@ -206,6 +210,7 @@ public: +@@ -208,6 +212,7 @@ public: static void didReceiveData(LocalFrame*, ResourceLoaderIdentifier, const SharedBuffer*, int encodedDataLength); static void didFinishLoading(LocalFrame*, DocumentLoader*, ResourceLoaderIdentifier, const NetworkLoadMetrics&, ResourceLoader*); static void didFailLoading(LocalFrame*, DocumentLoader*, ResourceLoaderIdentifier, const ResourceError&); @@ -3117,7 +3092,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void willSendRequest(ServiceWorkerGlobalScope&, ResourceLoaderIdentifier, ResourceRequest&); static void didReceiveResourceResponse(ServiceWorkerGlobalScope&, ResourceLoaderIdentifier, const ResourceResponse&); -@@ -232,13 +237,13 @@ public: +@@ -234,13 +239,13 @@ public: static void frameDetachedFromParent(LocalFrame&); static void didCommitLoad(LocalFrame&, DocumentLoader*); static void frameDocumentUpdated(LocalFrame&); @@ -3133,7 +3108,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 #if ENABLE(DARK_MODE_CSS) static void defaultAppearanceDidChange(Page&); #endif -@@ -249,6 +254,7 @@ public: +@@ -251,6 +256,7 @@ public: static bool shouldInterceptResponse(const LocalFrame&, const ResourceResponse&); static void interceptRequest(ResourceLoader&, Function&&); static void interceptResponse(const LocalFrame&, const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); @@ -3141,7 +3116,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void addMessageToConsole(Page&, std::unique_ptr); static void addMessageToConsole(WorkerOrWorkletGlobalScope&, std::unique_ptr); -@@ -270,6 +276,7 @@ public: +@@ -272,6 +278,7 @@ public: static void stopProfiling(Page&, const String& title); static void consoleStartRecordingCanvas(CanvasRenderingContext&, JSC::JSGlobalObject&, JSC::JSObject* options); static void consoleStopRecordingCanvas(CanvasRenderingContext&); @@ -3149,7 +3124,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void performanceMark(ScriptExecutionContext&, const String&, std::optional); -@@ -323,6 +330,12 @@ public: +@@ -325,6 +332,12 @@ public: static void layerTreeDidChange(Page*); static void renderLayerDestroyed(Page*, const RenderLayer&); @@ -3162,7 +3137,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void frontendCreated(); static void frontendDeleted(); static bool hasFrontends() { return InspectorInstrumentationPublic::hasFrontends(); } -@@ -339,6 +352,8 @@ public: +@@ -341,6 +354,8 @@ public: static void registerInstrumentingAgents(InstrumentingAgents&); static void unregisterInstrumentingAgents(InstrumentingAgents&); @@ -3171,7 +3146,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 private: static void didClearWindowObjectInWorldImpl(InstrumentingAgents&, LocalFrame&, DOMWrapperWorld&); static bool isDebuggerPausedImpl(InstrumentingAgents&); -@@ -418,6 +433,7 @@ private: +@@ -422,6 +437,7 @@ private: static void didRecalculateStyleImpl(InstrumentingAgents&); static void didScheduleStyleRecalculationImpl(InstrumentingAgents&, Document&); static void applyUserAgentOverrideImpl(InstrumentingAgents&, String&); @@ -3179,7 +3154,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void applyEmulatedMediaImpl(InstrumentingAgents&, AtomString&); static void flexibleBoxRendererBeganLayoutImpl(InstrumentingAgents&, const RenderObject&); -@@ -432,6 +448,7 @@ private: +@@ -436,6 +452,7 @@ private: static void didReceiveDataImpl(InstrumentingAgents&, ResourceLoaderIdentifier, const SharedBuffer*, int encodedDataLength); static void didFinishLoadingImpl(InstrumentingAgents&, ResourceLoaderIdentifier, DocumentLoader*, const NetworkLoadMetrics&, ResourceLoader*); static void didFailLoadingImpl(InstrumentingAgents&, ResourceLoaderIdentifier, DocumentLoader*, const ResourceError&); @@ -3187,7 +3162,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void willLoadXHRSynchronouslyImpl(InstrumentingAgents&); static void didLoadXHRSynchronouslyImpl(InstrumentingAgents&); static void scriptImportedImpl(InstrumentingAgents&, ResourceLoaderIdentifier, const String& sourceString); -@@ -442,13 +459,13 @@ private: +@@ -446,13 +463,13 @@ private: static void frameDetachedFromParentImpl(InstrumentingAgents&, LocalFrame&); static void didCommitLoadImpl(InstrumentingAgents&, LocalFrame&, DocumentLoader*); static void frameDocumentUpdatedImpl(InstrumentingAgents&, LocalFrame&); @@ -3203,7 +3178,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 #if ENABLE(DARK_MODE_CSS) static void defaultAppearanceDidChangeImpl(InstrumentingAgents&); #endif -@@ -459,6 +476,7 @@ private: +@@ -463,6 +480,7 @@ private: static bool shouldInterceptResponseImpl(InstrumentingAgents&, const ResourceResponse&); static void interceptRequestImpl(InstrumentingAgents&, ResourceLoader&, Function&&); static void interceptResponseImpl(InstrumentingAgents&, const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); @@ -3211,7 +3186,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void addMessageToConsoleImpl(InstrumentingAgents&, std::unique_ptr); -@@ -473,6 +491,7 @@ private: +@@ -477,6 +495,7 @@ private: static void stopProfilingImpl(InstrumentingAgents&, const String& title); static void consoleStartRecordingCanvasImpl(InstrumentingAgents&, CanvasRenderingContext&, JSC::JSGlobalObject&, JSC::JSObject* options); static void consoleStopRecordingCanvasImpl(InstrumentingAgents&, CanvasRenderingContext&); @@ -3219,7 +3194,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static void performanceMarkImpl(InstrumentingAgents&, const String& label, std::optional); -@@ -526,6 +545,12 @@ private: +@@ -530,6 +549,12 @@ private: static void layerTreeDidChangeImpl(InstrumentingAgents&); static void renderLayerDestroyedImpl(InstrumentingAgents&, const RenderLayer&); @@ -3232,7 +3207,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 static InstrumentingAgents& instrumentingAgents(Page&); static InstrumentingAgents& instrumentingAgents(WorkerOrWorkletGlobalScope&); static InstrumentingAgents& instrumentingAgents(ServiceWorkerGlobalScope&); -@@ -1071,6 +1096,13 @@ inline void InspectorInstrumentation::applyUserAgentOverride(LocalFrame& frame, +@@ -1089,6 +1114,13 @@ inline void InspectorInstrumentation::applyUserAgentOverride(LocalFrame& frame, applyUserAgentOverrideImpl(*agents, userAgent); } @@ -3246,7 +3221,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 inline void InspectorInstrumentation::applyEmulatedMedia(LocalFrame& frame, AtomString& media) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1173,6 +1205,13 @@ inline void InspectorInstrumentation::didFailLoading(ServiceWorkerGlobalScope& g +@@ -1191,6 +1223,13 @@ inline void InspectorInstrumentation::didFailLoading(ServiceWorkerGlobalScope& g didFailLoadingImpl(instrumentingAgents(globalScope), identifier, nullptr, error); } @@ -3260,7 +3235,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 inline void InspectorInstrumentation::continueAfterXFrameOptionsDenied(LocalFrame& frame, ResourceLoaderIdentifier identifier, DocumentLoader& loader, const ResourceResponse& response) { // Treat the same as didReceiveResponse. -@@ -1263,13 +1302,6 @@ inline void InspectorInstrumentation::frameDocumentUpdated(LocalFrame& frame) +@@ -1281,13 +1320,6 @@ inline void InspectorInstrumentation::frameDocumentUpdated(LocalFrame& frame) frameDocumentUpdatedImpl(*agents, frame); } @@ -3274,7 +3249,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 inline void InspectorInstrumentation::frameStartedLoading(LocalFrame& frame) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1291,11 +1323,11 @@ inline void InspectorInstrumentation::frameStoppedLoading(LocalFrame& frame) +@@ -1309,11 +1341,11 @@ inline void InspectorInstrumentation::frameStoppedLoading(LocalFrame& frame) frameStoppedLoadingImpl(*agents, frame); } @@ -3288,7 +3263,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 } inline void InspectorInstrumentation::frameClearedScheduledNavigation(Frame& frame) -@@ -1311,6 +1343,13 @@ inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page) +@@ -1329,6 +1361,13 @@ inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page) accessibilitySettingsDidChangeImpl(instrumentingAgents(page)); } @@ -3302,7 +3277,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 #if ENABLE(DARK_MODE_CSS) inline void InspectorInstrumentation::defaultAppearanceDidChange(Page& page) { -@@ -1363,6 +1402,13 @@ inline void InspectorInstrumentation::interceptResponse(const LocalFrame& frame, +@@ -1381,6 +1420,13 @@ inline void InspectorInstrumentation::interceptResponse(const LocalFrame& frame, interceptResponseImpl(*agents, response, identifier, WTFMove(handler)); } @@ -3316,7 +3291,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 inline void InspectorInstrumentation::didDispatchDOMStorageEvent(Page& page, const String& key, const String& oldValue, const String& newValue, StorageType storageType, const SecurityOrigin& securityOrigin) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1679,6 +1725,11 @@ inline void InspectorInstrumentation::performanceMark(ScriptExecutionContext& co +@@ -1697,6 +1743,11 @@ inline void InspectorInstrumentation::performanceMark(ScriptExecutionContext& co performanceMarkImpl(*agents, label, WTFMove(startTime)); } @@ -3328,7 +3303,7 @@ index c338ad466f69c4b03121e0b17fbc9a4729654aff..6139fb2b5e0364bce1a5720200943f30 inline void InspectorInstrumentation::didRequestAnimationFrame(ScriptExecutionContext& scriptExecutionContext, int callbackId) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1735,6 +1786,42 @@ inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const Ren +@@ -1753,6 +1804,42 @@ inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const Ren renderLayerDestroyedImpl(*agents, renderLayer); } @@ -3424,7 +3399,7 @@ index c028341e84e59a6b1b16107fd74feb21f70b12ab..d385418ac34e8f315f201801a2c65226 + } diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp -index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0ca049ae40 100644 +index 5bd7a42c19fd11234eea0520ebf61f7f820f765e..a70339c639682820de7ff09d62e8cfd42a7d7060 100644 --- a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp @@ -56,6 +56,7 @@ @@ -3450,7 +3425,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c #include "HTMLMediaElement.h" #include "HTMLNames.h" #include "HTMLScriptElement.h" -@@ -103,12 +109,14 @@ +@@ -104,12 +110,14 @@ #include "Pasteboard.h" #include "PseudoElement.h" #include "RenderGrid.h" @@ -3465,7 +3440,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c #include "StaticNodeList.h" #include "StyleProperties.h" #include "StyleResolver.h" -@@ -150,7 +158,8 @@ using namespace HTMLNames; +@@ -151,7 +159,8 @@ using namespace HTMLNames; static const size_t maxTextSize = 10000; static const UChar horizontalEllipsisUChar[] = { horizontalEllipsis, 0 }; @@ -3475,7 +3450,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c { if (!colorObject) return std::nullopt; -@@ -169,7 +178,7 @@ static std::optional parseColor(RefPtr&& colorObject) +@@ -170,7 +179,7 @@ static std::optional parseColor(RefPtr&& colorObject) static std::optional parseRequiredConfigColor(const String& fieldName, JSON::Object& configObject) { @@ -3484,7 +3459,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c } static Color parseOptionalConfigColor(const String& fieldName, JSON::Object& configObject) -@@ -196,6 +205,20 @@ static bool parseQuad(Ref&& quadArray, FloatQuad* quad) +@@ -197,6 +206,20 @@ static bool parseQuad(Ref&& quadArray, FloatQuad* quad) return true; } @@ -3505,7 +3480,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c class RevalidateStyleAttributeTask final : public CanMakeCheckedPtr { WTF_MAKE_TZONE_ALLOCATED(RevalidateStyleAttributeTask); WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(RevalidateStyleAttributeTask); -@@ -476,6 +499,20 @@ Node* InspectorDOMAgent::assertNode(Inspector::Protocol::ErrorString& errorStrin +@@ -477,6 +500,20 @@ Node* InspectorDOMAgent::assertNode(Inspector::Protocol::ErrorString& errorStrin return node.get(); } @@ -3526,7 +3501,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c Document* InspectorDOMAgent::assertDocument(Inspector::Protocol::ErrorString& errorString, Inspector::Protocol::DOM::NodeId nodeId) { RefPtr node = assertNode(errorString, nodeId); -@@ -1550,16 +1587,7 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::highlightNode(std::o +@@ -1595,16 +1632,7 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::highlightNode(std::o Inspector::Protocol::ErrorStringOr InspectorDOMAgent::highlightNode(std::optional&& nodeId, const Inspector::Protocol::Runtime::RemoteObjectId& objectId, Ref&& highlightInspectorObject, RefPtr&& gridOverlayInspectorObject, RefPtr&& flexOverlayInspectorObject, std::optional&& showRulers) { Inspector::Protocol::ErrorString errorString; @@ -3544,7 +3519,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c if (!node) return makeUnexpected(errorString); -@@ -1814,15 +1842,159 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::setInspectedNode(Ins +@@ -1859,15 +1887,159 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::setInspectedNode(Ins return { }; } @@ -3707,7 +3682,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c if (!object) return makeUnexpected("Missing injected script for given nodeId"_s); -@@ -3088,7 +3260,7 @@ Inspector::Protocol::ErrorStringOr InspectorDO +@@ -3133,7 +3305,7 @@ Inspector::Protocol::ErrorStringOr InspectorDO return makeUnexpected("Missing node for given path"_s); } @@ -3716,7 +3691,7 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c { Document* document = &node->document(); if (auto* templateHost = document->templateDocumentHost()) -@@ -3097,12 +3269,18 @@ RefPtr InspectorDOMAgent::resolveNod +@@ -3142,12 +3314,18 @@ RefPtr InspectorDOMAgent::resolveNod if (!frame) return nullptr; @@ -3738,11 +3713,11 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c } Node* InspectorDOMAgent::scriptValueAsNode(JSC::JSValue value) -@@ -3210,4 +3388,89 @@ Inspector::Protocol::ErrorStringOr> In +@@ -3255,4 +3433,53 @@ Inspector::Protocol::ErrorStringOr> In #endif } -+void InspectorDOMAgent::setInputFiles(const String& objectId, RefPtr&& files, RefPtr&& paths, Ref&& callback) { ++void InspectorDOMAgent::setInputFiles(const String& objectId, Ref&& paths, Ref&& callback) { + InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId); + if (injectedScript.hasNoValue()) { + callback->sendFailure("Can not find element's context for given id"_s); @@ -3760,76 +3735,40 @@ index a477bad0c14c9304b9c2f03aa4e690e8b27a7756..fcb712a41223ec4698337a8b17999e0c + return; + } + -+ if (!(bool(files) ^ bool(paths))) { -+ callback->sendFailure("Exactly one of files and paths should be specified"_s); -+ return; -+ } -+ + HTMLInputElement* element = static_cast(node); + Vector> fileObjects; -+ if (files) { -+ for (unsigned i = 0; i < files->length(); ++i) { -+ RefPtr item = files->get(i); -+ RefPtr obj = item->asObject(); -+ if (!obj) { -+ callback->sendFailure("Invalid file payload format"_s); -+ return; -+ } -+ -+ String name; -+ String type; -+ String data; -+ if (!obj->getString("name"_s, name) || !obj->getString("type"_s, type) || !obj->getString("data"_s, data)) { -+ callback->sendFailure("Invalid file payload format"_s); -+ return; -+ } -+ -+ std::optional> buffer = base64Decode(data); -+ if (!buffer) { -+ callback->sendFailure("Unable to decode given content"_s); ++ if (element->hasAttributeWithoutSynchronization(webkitdirectoryAttr)) { ++ auto directoryFileListCreator = DirectoryFileListCreator::create([element = RefPtr { element }, callback = WTFMove(callback)](Ref&& fileList) mutable { ++ ASSERT(isMainThread()); ++ element->setFiles(WTFMove(fileList)); ++ callback->sendSuccess(); ++ }); ++ Vector fileChooserFiles; ++ for (size_t i = 0; i < paths->length(); ++i) { ++ fileChooserFiles.append(FileChooserFileInfo { paths->get(i)->asString(), nullString(), { } }); ++ } ++ directoryFileListCreator->start(m_document.get(), fileChooserFiles); ++ } else { ++ for (unsigned i = 0; i < paths->length(); ++i) { ++ RefPtr item = paths->get(i); ++ String path = item->asString(); ++ if (path.isEmpty()) { ++ callback->sendFailure("Invalid file path"_s); + return; + } + + ScriptExecutionContext* context = element->scriptExecutionContext(); -+ fileObjects.append(File::create(context, Blob::create(context, WTFMove(*buffer), type), name)); ++ fileObjects.append(File::create(context, path)); + } + RefPtr fileList = FileList::create(WTFMove(fileObjects)); + element->setFiles(WTFMove(fileList)); + callback->sendSuccess(); -+ } else { -+ if (element->hasAttributeWithoutSynchronization(webkitdirectoryAttr)) { -+ auto directoryFileListCreator = DirectoryFileListCreator::create([element = RefPtr { element }, callback = WTFMove(callback)](Ref&& fileList) mutable { -+ ASSERT(isMainThread()); -+ element->setFiles(WTFMove(fileList)); -+ callback->sendSuccess(); -+ }); -+ Vector fileChooserFiles; -+ for (size_t i = 0; i < paths->length(); ++i) { -+ fileChooserFiles.append(FileChooserFileInfo { paths->get(i)->asString(), nullString(), { } }); -+ } -+ directoryFileListCreator->start(m_document.get(), fileChooserFiles); -+ } else { -+ for (unsigned i = 0; i < paths->length(); ++i) { -+ RefPtr item = paths->get(i); -+ String path = item->asString(); -+ if (path.isEmpty()) { -+ callback->sendFailure("Invalid file path"_s); -+ return; -+ } -+ -+ ScriptExecutionContext* context = element->scriptExecutionContext(); -+ fileObjects.append(File::create(context, path)); -+ } -+ RefPtr fileList = FileList::create(WTFMove(fileObjects)); -+ element->setFiles(WTFMove(fileList)); -+ callback->sendSuccess(); -+ } + } +} + } // namespace WebCore diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.h b/Source/WebCore/inspector/agents/InspectorDOMAgent.h -index 0d1406e0a1d061e2336bbd49c56328ce93351f14..4187fb453adebfd98f91ebbf99e38a072ef8942b 100644 +index c8602dd6236d299df04b28b456b4d4ab6348c6eb..15128978fa8d64b6413b82ce9991dd708d50271b 100644 --- a/Source/WebCore/inspector/agents/InspectorDOMAgent.h +++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.h @@ -59,6 +59,7 @@ namespace WebCore { @@ -3848,7 +3787,7 @@ index 0d1406e0a1d061e2336bbd49c56328ce93351f14..4187fb453adebfd98f91ebbf99e38a07 // We represent embedded doms as a part of the same hierarchy. Hence we treat children of frame owners differently. // We also skip whitespace text nodes conditionally. Following methods encapsulate these specifics. -@@ -137,7 +139,7 @@ public: +@@ -139,7 +141,7 @@ public: Inspector::Protocol::ErrorStringOr> performSearch(const String& query, RefPtr&& nodeIds, std::optional&& caseSensitive); Inspector::Protocol::ErrorStringOr>> getSearchResults(const String& searchId, int fromIndex, int toIndex); Inspector::Protocol::ErrorStringOr discardSearchResults(const String& searchId); @@ -3857,18 +3796,18 @@ index 0d1406e0a1d061e2336bbd49c56328ce93351f14..4187fb453adebfd98f91ebbf99e38a07 Inspector::Protocol::ErrorStringOr>> getAttributes(Inspector::Protocol::DOM::NodeId); #if PLATFORM(IOS_FAMILY) Inspector::Protocol::ErrorStringOr setInspectModeEnabled(bool, RefPtr&& highlightConfig, RefPtr&& gridOverlayConfig, RefPtr&& flexOverlayConfig); -@@ -174,6 +176,10 @@ public: +@@ -176,6 +178,10 @@ public: Inspector::Protocol::ErrorStringOr setInspectedNode(Inspector::Protocol::DOM::NodeId); Inspector::Protocol::ErrorStringOr setAllowEditingUserAgentShadowTrees(bool); Inspector::Protocol::ErrorStringOr> getMediaStats(Inspector::Protocol::DOM::NodeId); + Inspector::Protocol::ErrorStringOr> describeNode(const String& objectId); + Inspector::Protocol::ErrorStringOr scrollIntoViewIfNeeded(const String& objectId, RefPtr&& rect); + Inspector::Protocol::ErrorStringOr>> getContentQuads(const String& objectId); -+ void setInputFiles(const String& objectId, RefPtr&& files, RefPtr&& paths, Ref&& callback); ++ void setInputFiles(const String& objectId, Ref&& paths, Ref&& callback); // InspectorInstrumentation Inspector::Protocol::DOM::NodeId identifierForNode(Node&); -@@ -215,7 +221,7 @@ public: +@@ -217,7 +223,7 @@ public: Node* nodeForId(Inspector::Protocol::DOM::NodeId); Inspector::Protocol::DOM::NodeId boundNodeId(const Node*); @@ -3877,7 +3816,7 @@ index 0d1406e0a1d061e2336bbd49c56328ce93351f14..4187fb453adebfd98f91ebbf99e38a07 bool handleMousePress(); void mouseDidMoveOverElement(const HitTestResult&, OptionSet); void inspect(Node*); -@@ -227,12 +233,15 @@ public: +@@ -229,12 +235,15 @@ public: void reset(); Node* assertNode(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId); @@ -3893,7 +3832,7 @@ index 0d1406e0a1d061e2336bbd49c56328ce93351f14..4187fb453adebfd98f91ebbf99e38a07 private: #if ENABLE(VIDEO) void mediaMetricsTimerFired(); -@@ -262,7 +271,6 @@ private: +@@ -264,7 +273,6 @@ private: void processAccessibilityChildren(AXCoreObject&, JSON::ArrayOf&); Node* nodeForPath(const String& path); @@ -3902,18 +3841,18 @@ index 0d1406e0a1d061e2336bbd49c56328ce93351f14..4187fb453adebfd98f91ebbf99e38a07 void discardBindings(); diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp -index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d3a0e29bc 100644 +index 263890779b1a012b1ae1fe9145f4a7af494a581c..dc83cf514b03a80ceff036248147203898a4e113 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp -@@ -59,6 +59,7 @@ +@@ -58,6 +58,7 @@ + #include "LocalFrame.h" #include "MIMETypeRegistry.h" #include "MemoryCache.h" - #include "NetworkResourcesData.h" +#include "NetworkStateNotifier.h" #include "Page.h" #include "PlatformStrategies.h" #include "ProgressTracker.h" -@@ -345,8 +346,8 @@ static Ref buildObjectForResourceRequest( +@@ -343,8 +344,8 @@ static Ref buildObjectForResourceRequest( .release(); if (request.httpBody() && !request.httpBody()->isEmpty()) { @@ -3924,7 +3863,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d } if (resourceLoader) { -@@ -399,6 +400,8 @@ RefPtr InspectorNetworkAgent::buildObjec +@@ -397,6 +398,8 @@ RefPtr InspectorNetworkAgent::buildObjec .setSource(responseSource(response.source())) .release(); @@ -3933,7 +3872,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d if (resourceLoader) { auto* metrics = response.deprecatedNetworkLoadMetricsOrNull(); responseObject->setTiming(buildObjectForTiming(metrics ? *metrics : NetworkLoadMetrics::emptyMetrics(), *resourceLoader)); -@@ -685,6 +688,9 @@ void InspectorNetworkAgent::didFailLoading(ResourceLoaderIdentifier identifier, +@@ -683,6 +686,9 @@ void InspectorNetworkAgent::didFailLoading(ResourceLoaderIdentifier identifier, String requestId = IdentifiersFactory::requestId(identifier.toUInt64()); if (loader && m_resourcesData->resourceType(requestId) == InspectorPageAgent::DocumentResource) { @@ -3943,7 +3882,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d auto* frame = loader->frame(); if (frame && frame->loader().documentLoader() && frame->document()) { m_resourcesData->addResourceSharedBuffer(requestId, -@@ -914,6 +920,7 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::disable() +@@ -912,6 +918,7 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::disable() m_instrumentingAgents.setEnabledNetworkAgent(nullptr); m_resourcesData->clear(); m_extraRequestHeaders.clear(); @@ -3951,7 +3890,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d continuePendingRequests(); continuePendingResponses(); -@@ -966,6 +973,7 @@ void InspectorNetworkAgent::continuePendingResponses() +@@ -957,6 +964,7 @@ void InspectorNetworkAgent::continuePendingResponses() Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::setExtraHTTPHeaders(Ref&& headers) { @@ -3959,7 +3898,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d for (auto& entry : headers.get()) { auto stringValue = entry.value->asString(); if (!!stringValue) -@@ -1215,6 +1223,11 @@ void InspectorNetworkAgent::interceptResponse(const ResourceResponse& response, +@@ -1206,6 +1214,11 @@ void InspectorNetworkAgent::interceptResponse(const ResourceResponse& response, m_frontendDispatcher->responseIntercepted(requestId, resourceResponse.releaseNonNull()); } @@ -3971,7 +3910,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptContinue(const Inspector::Protocol::Network::RequestId& requestId, Inspector::Protocol::Network::NetworkStage networkStage) { switch (networkStage) { -@@ -1244,6 +1257,9 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptWithReq +@@ -1235,6 +1248,9 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptWithReq return makeUnexpected("Missing pending intercept request for given requestId"_s); auto& loader = *pendingRequest->m_loader; @@ -3981,7 +3920,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d ResourceRequest request = loader.request(); if (!!url) request.setURL(URL({ }, url)); -@@ -1339,14 +1355,23 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptRequest +@@ -1330,14 +1346,23 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptRequest response.setHTTPStatusCode(status); response.setHTTPStatusText(String { statusText }); HTTPHeaderMap explicitHeaders; @@ -4007,7 +3946,7 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d if (loader->reachedTerminalState()) return; -@@ -1409,6 +1434,12 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::setEmulatedCondi +@@ -1400,6 +1425,12 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::setEmulatedCondi #endif // ENABLE(INSPECTOR_NETWORK_THROTTLING) @@ -4021,19 +3960,19 @@ index 6db0a3c6a46a4be5cb6f9f0e6fcbc61ac552e438..cae87102e8228cb944cec4a6ebe83b7d { return startsWithLettersIgnoringASCIICase(mimeType, "text/"_s) diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.h b/Source/WebCore/inspector/agents/InspectorNetworkAgent.h -index eda400879afb10b687fcbb317c9fdbb3be9c94cd..f3a382c44b53e6b1507fc046e22bff684abd55f9 100644 +index de6b2dd844943074c5a383c7b9b8ccba1c96419a..7a3404f2380b5e62f1c0523a70f8ef442014759d 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.h +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.h -@@ -34,6 +34,8 @@ - #include "InspectorInstrumentation.h" +@@ -35,6 +35,8 @@ #include "InspectorPageAgent.h" #include "InspectorWebAgentBase.h" + #include "NetworkResourcesData.h" +#include "ResourceError.h" +#include "SharedBuffer.h" #include "WebSocket.h" + #include #include - #include -@@ -102,6 +104,7 @@ public: +@@ -104,6 +106,7 @@ public: #if ENABLE(INSPECTOR_NETWORK_THROTTLING) Inspector::Protocol::ErrorStringOr setEmulatedConditions(std::optional&& bytesPerSecondLimit) final; #endif @@ -4041,7 +3980,7 @@ index eda400879afb10b687fcbb317c9fdbb3be9c94cd..f3a382c44b53e6b1507fc046e22bff68 // InspectorInstrumentation void willRecalculateStyle(); -@@ -133,6 +136,7 @@ public: +@@ -135,6 +138,7 @@ public: bool shouldInterceptResponse(const ResourceResponse&); void interceptResponse(const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); void interceptRequest(ResourceLoader&, Function&&); @@ -4049,7 +3988,7 @@ index eda400879afb10b687fcbb317c9fdbb3be9c94cd..f3a382c44b53e6b1507fc046e22bff68 void searchOtherRequests(const JSC::Yarr::RegularExpression&, Ref>&); void searchInRequest(Inspector::Protocol::ErrorString&, const Inspector::Protocol::Network::RequestId&, const String& query, bool caseSensitive, bool isRegex, RefPtr>&); -@@ -259,6 +263,7 @@ private: +@@ -275,6 +279,7 @@ private: bool m_enabled { false }; bool m_loadingXHRSynchronously { false }; bool m_interceptionEnabled { false }; @@ -4058,7 +3997,7 @@ index eda400879afb10b687fcbb317c9fdbb3be9c94cd..f3a382c44b53e6b1507fc046e22bff68 } // namespace WebCore diff --git a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp -index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f9bb132cc 100644 +index c7c930db458419ffdf3d10b9a7db0d3c2b9615a1..6253804039ea38105bb7647082752cafdd08d895 100644 --- a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp @@ -32,19 +32,27 @@ @@ -4272,7 +4211,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f case Inspector::Protocol::Page::Setting::WebSecurityEnabled: inspectedPageSettings.setWebSecurityEnabledInspectorOverride(value); return { }; -@@ -919,15 +1023,16 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::setShowPaintRects(b +@@ -920,15 +1024,16 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::setShowPaintRects(b return { }; } @@ -4294,33 +4233,24 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f } void InspectorPageAgent::frameNavigated(LocalFrame& frame) -@@ -935,13 +1040,38 @@ void InspectorPageAgent::frameNavigated(LocalFrame& frame) +@@ -936,13 +1041,29 @@ void InspectorPageAgent::frameNavigated(LocalFrame& frame) m_frontendDispatcher->frameNavigated(buildObjectForFrame(&frame)); } +String InspectorPageAgent::serializeFrameID(FrameIdentifier frameID) +{ -+ return makeString(frameID.processIdentifier().toUInt64(), '.', frameID.object().toUInt64()); ++ return makeString(frameID.toUInt64()); +} + +std::optional InspectorPageAgent::parseFrameID(String frameID) +{ -+ size_t dotPos = frameID.find("."_s); -+ if (dotPos == notFound) -+ return std::nullopt; -+ + if (!frameID.containsOnlyASCII()) + return std::nullopt; + -+ String processIDString = frameID.left(dotPos); -+ uint64_t pid = strtoull(processIDString.ascii().data(), 0, 10); -+ auto processID = ObjectIdentifier(pid); -+ String frameIDString = frameID.substring(dotPos + 1); -+ uint64_t frameIDNumber = strtoull(frameIDString.ascii().data(), 0, 10); -+ return WebCore::FrameIdentifier { -+ ObjectIdentifier(frameIDNumber), -+ processID -+ }; ++WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN ++ uint64_t frameIDNumber = strtoull(frameID.ascii().data(), 0, 10); ++WTF_ALLOW_UNSAFE_BUFFER_USAGE_END ++ return WebCore::FrameIdentifier(frameIDNumber); +} + void InspectorPageAgent::frameDetached(LocalFrame& frame) @@ -4336,7 +4266,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f } Frame* InspectorPageAgent::frameForId(const Inspector::Protocol::Network::FrameId& frameId) -@@ -953,20 +1083,21 @@ String InspectorPageAgent::frameId(Frame* frame) +@@ -954,20 +1075,21 @@ String InspectorPageAgent::frameId(Frame* frame) { if (!frame) return emptyString(); @@ -4366,7 +4296,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f } LocalFrame* InspectorPageAgent::assertFrame(Inspector::Protocol::ErrorString& errorString, const Inspector::Protocol::Network::FrameId& frameId) -@@ -977,11 +1108,6 @@ LocalFrame* InspectorPageAgent::assertFrame(Inspector::Protocol::ErrorString& er +@@ -978,11 +1100,6 @@ LocalFrame* InspectorPageAgent::assertFrame(Inspector::Protocol::ErrorString& er return frame; } @@ -4378,7 +4308,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f void InspectorPageAgent::frameStartedLoading(LocalFrame& frame) { m_frontendDispatcher->frameStartedLoading(frameId(&frame)); -@@ -992,9 +1118,9 @@ void InspectorPageAgent::frameStoppedLoading(LocalFrame& frame) +@@ -993,9 +1110,9 @@ void InspectorPageAgent::frameStoppedLoading(LocalFrame& frame) m_frontendDispatcher->frameStoppedLoading(frameId(&frame)); } @@ -4390,7 +4320,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f } void InspectorPageAgent::frameClearedScheduledNavigation(Frame& frame) -@@ -1041,6 +1167,12 @@ void InspectorPageAgent::defaultUserPreferencesDidChange() +@@ -1042,6 +1159,12 @@ void InspectorPageAgent::defaultUserPreferencesDidChange() m_frontendDispatcher->defaultUserPreferencesDidChange(WTFMove(defaultUserPreferences)); } @@ -4403,7 +4333,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f #if ENABLE(DARK_MODE_CSS) void InspectorPageAgent::defaultAppearanceDidChange() { -@@ -1054,6 +1186,9 @@ void InspectorPageAgent::didClearWindowObjectInWorld(LocalFrame& frame, DOMWrapp +@@ -1055,6 +1178,9 @@ void InspectorPageAgent::didClearWindowObjectInWorld(LocalFrame& frame, DOMWrapp return; if (m_bootstrapScript.isEmpty()) @@ -4413,7 +4343,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f return; frame.script().evaluateIgnoringException(ScriptSourceCode(m_bootstrapScript, JSC::SourceTaintedOrigin::Untainted, URL { "web-inspector://bootstrap.js"_str })); -@@ -1101,6 +1236,51 @@ void InspectorPageAgent::didRecalculateStyle() +@@ -1102,6 +1228,51 @@ void InspectorPageAgent::didRecalculateStyle() protectedOverlay()->update(); } @@ -4465,7 +4395,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f Ref InspectorPageAgent::buildObjectForFrame(LocalFrame* frame) { ASSERT_ARG(frame, frame); -@@ -1194,6 +1374,12 @@ void InspectorPageAgent::applyUserAgentOverride(String& userAgent) +@@ -1195,6 +1366,12 @@ void InspectorPageAgent::applyUserAgentOverride(String& userAgent) userAgent = m_userAgentOverride; } @@ -4478,7 +4408,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f void InspectorPageAgent::applyEmulatedMedia(AtomString& media) { if (!m_emulatedMedia.isEmpty()) -@@ -1221,11 +1407,13 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::snapshotNode(Insp +@@ -1222,11 +1399,13 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::snapshotNode(Insp return snapshot->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes); } @@ -4493,7 +4423,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f IntRect rectangle(x, y, width, height); RefPtr localMainFrame = m_inspectedPage->localMainFrame(); -@@ -1239,6 +1427,43 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::snapshotRect(int +@@ -1240,6 +1419,43 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::snapshotRect(int return snapshot->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes); } @@ -4537,7 +4467,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f #if ENABLE(WEB_ARCHIVE) && USE(CF) Inspector::Protocol::ErrorStringOr InspectorPageAgent::archive() { -@@ -1255,7 +1480,6 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::archive() +@@ -1256,7 +1472,6 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::archive() } #endif @@ -4545,7 +4475,7 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f Inspector::Protocol::ErrorStringOr InspectorPageAgent::setScreenSizeOverride(std::optional&& width, std::optional&& height) { if (width.has_value() != height.has_value()) -@@ -1273,6 +1497,498 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::setScreenSizeOverri +@@ -1274,6 +1489,496 @@ Inspector::Protocol::ErrorStringOr InspectorPageAgent::setScreenSizeOverri localMainFrame->setOverrideScreenSize(FloatSize(width.value_or(0), height.value_or(0))); return { }; } @@ -4676,8 +4606,6 @@ index 322ae350279461c5c213a760444580421cb89682..6fc187ab25a6c76c41880fdf63acda1f + return "Image"_s; + case AccessibilityRole::ImageMap: + return "ImageMap"_s; -+ case AccessibilityRole::ImageMapLink: -+ return "ImageMapLink"_s; + case AccessibilityRole::Insertion: + return "Insertion"_s; + case AccessibilityRole::Label: @@ -5384,10 +5312,10 @@ index 8fb27c1045b8073d1487d5b61ccdec23a395bfd1..5008052f587ca4ba90da973c539188de protected: static SameSiteInfo sameSiteInfo(const Document&, IsForDOMCookieAccess = IsForDOMCookieAccess::No); diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp -index 858e5866d005b40ce61b79c607c4e5530f23f582..ec3d856897d821aeb4ba8616598932b19b1bcf3f 100644 +index 0ed08d14f259f4c9ca92447222bb7bc5344f335b..8f04196baa58d2c9b627f59d5e2c0126bd2b4e01 100644 --- a/Source/WebCore/loader/DocumentLoader.cpp +++ b/Source/WebCore/loader/DocumentLoader.cpp -@@ -773,8 +773,10 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc +@@ -774,8 +774,10 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc if (!didReceiveRedirectResponse) return completionHandler(WTFMove(newRequest)); @@ -5398,7 +5326,7 @@ index 858e5866d005b40ce61b79c607c4e5530f23f582..ec3d856897d821aeb4ba8616598932b1 switch (navigationPolicyDecision) { case NavigationPolicyDecision::IgnoreLoad: case NavigationPolicyDecision::LoadWillContinueInAnotherProcess: -@@ -1573,11 +1575,17 @@ void DocumentLoader::detachFromFrame(LoadWillContinueInAnotherProcess loadWillCo +@@ -1574,11 +1576,17 @@ void DocumentLoader::detachFromFrame(LoadWillContinueInAnotherProcess loadWillCo if (auto navigationID = std::exchange(m_navigationID, { })) frame->loader().client().documentLoaderDetached(*navigationID, loadWillContinueInAnotherProcess); @@ -5419,7 +5347,7 @@ index 858e5866d005b40ce61b79c607c4e5530f23f582..ec3d856897d821aeb4ba8616598932b1 { m_navigationID = navigationID; diff --git a/Source/WebCore/loader/DocumentLoader.h b/Source/WebCore/loader/DocumentLoader.h -index 564f944a63868ced85f3c412a4df6dce8d257929..b2228742433ef7f478c20328e62e159d6a051b10 100644 +index 3b8896bcc5661929a7f96fb7a01de99b36109dbf..8c3421c5e6a0c36f82c0d7a82f796ca90ba61be7 100644 --- a/Source/WebCore/loader/DocumentLoader.h +++ b/Source/WebCore/loader/DocumentLoader.h @@ -224,6 +224,8 @@ public: @@ -5432,10 +5360,10 @@ index 564f944a63868ced85f3c412a4df6dce8d257929..b2228742433ef7f478c20328e62e159d WEBCORE_EXPORT RefPtr protectedFrameLoader() const; WEBCORE_EXPORT SubresourceLoader* mainResourceLoader() const; diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp -index 7073bb8cdc33fcffd7864505cc9dbeb96202e140..d7bf4bb0695d20eb8e479a703a331ba345fb213b 100644 +index 0a408b9d3b2c4daa271691041eb1f4fd897d3e93..650180483ace8b8d031d1d022e847409e948a9a4 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp -@@ -1319,6 +1319,7 @@ void FrameLoader::loadInSameDocument(URL url, RefPtr stat +@@ -1323,6 +1323,7 @@ void FrameLoader::loadInSameDocument(URL url, RefPtr stat } m_client->dispatchDidNavigateWithinPage(); @@ -5443,7 +5371,7 @@ index 7073bb8cdc33fcffd7864505cc9dbeb96202e140..d7bf4bb0695d20eb8e479a703a331ba3 document->statePopped(stateObject ? stateObject.releaseNonNull() : SerializedScriptValue::nullValue()); m_client->dispatchDidPopStateWithinPage(); -@@ -1850,6 +1851,7 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t +@@ -1861,6 +1862,7 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t const String& httpMethod = loader->request().httpMethod(); if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker().loadType(), newURL)) { @@ -5451,23 +5379,23 @@ index 7073bb8cdc33fcffd7864505cc9dbeb96202e140..d7bf4bb0695d20eb8e479a703a331ba3 RefPtr oldDocumentLoader = m_documentLoader; NavigationAction action { frame->protectedDocument().releaseNonNull(), loader->request(), InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), policyChecker().loadType(), isFormSubmission }; -@@ -1887,7 +1889,9 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t +@@ -1898,7 +1900,9 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t auto policyDecisionMode = loader->triggeringAction().isFromNavigationAPI() ? PolicyDecisionMode::Synchronous : PolicyDecisionMode::Asynchronous; RELEASE_ASSERT(!isBackForwardLoadType(policyChecker().loadType()) || history().provisionalItem()); + InspectorInstrumentation::willCheckNavigationPolicy(m_frame); - policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), ResourceResponse { } /* redirectResponse */, loader, WTFMove(formState), [this, frame, allowNavigationToInvalidURL, completionHandler = completionHandlerCaller.release()] (const ResourceRequest& request, WeakPtr&& weakFormState, NavigationPolicyDecision navigationPolicyDecision) mutable { + policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), ResourceResponse { } /* redirectResponse */, loader, WTFMove(formState), [this, protectedThis = Ref { *this }, allowNavigationToInvalidURL, completionHandler = completionHandlerCaller.release()] (const ResourceRequest& request, WeakPtr&& weakFormState, NavigationPolicyDecision navigationPolicyDecision) mutable { + InspectorInstrumentation::didCheckNavigationPolicy(m_frame, navigationPolicyDecision != NavigationPolicyDecision::ContinueLoad); continueLoadAfterNavigationPolicy(request, RefPtr { weakFormState.get() }.get(), navigationPolicyDecision, allowNavigationToInvalidURL); completionHandler(); }, policyDecisionMode); -@@ -3178,10 +3182,15 @@ String FrameLoader::userAgent(const URL& url) const +@@ -3210,10 +3214,15 @@ String FrameLoader::userAgent(const URL& url) const String FrameLoader::navigatorPlatform() const { + String platform; + - auto customNavigatorPlatform = m_frame->mainFrame().customNavigatorPlatform(); + auto customNavigatorPlatform = m_frame->protectedMainFrame()->customNavigatorPlatform(); if (!customNavigatorPlatform.isEmpty()) - return customNavigatorPlatform; - return String(); @@ -5479,7 +5407,7 @@ index 7073bb8cdc33fcffd7864505cc9dbeb96202e140..d7bf4bb0695d20eb8e479a703a331ba3 } void FrameLoader::dispatchOnloadEvents() -@@ -3638,6 +3647,8 @@ void FrameLoader::receivedMainResourceError(const ResourceError& error, LoadWill +@@ -3672,6 +3681,8 @@ void FrameLoader::receivedMainResourceError(const ResourceError& error, LoadWill checkCompleted(); if (frame->page()) checkLoadComplete(loadWillContinueInAnotherProcess); @@ -5488,7 +5416,7 @@ index 7073bb8cdc33fcffd7864505cc9dbeb96202e140..d7bf4bb0695d20eb8e479a703a331ba3 } void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequest& request, const SecurityOrigin* requesterOrigin, bool shouldContinue, NavigationHistoryBehavior historyHandling) -@@ -4522,9 +4533,6 @@ String FrameLoader::referrer() const +@@ -4566,9 +4577,6 @@ String FrameLoader::referrer() const void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() { @@ -5498,11 +5426,11 @@ index 7073bb8cdc33fcffd7864505cc9dbeb96202e140..d7bf4bb0695d20eb8e479a703a331ba3 Vector> worlds; ScriptController::getAllWorlds(worlds); for (auto& world : worlds) -@@ -4534,13 +4542,12 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() +@@ -4578,13 +4586,12 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld& world) { Ref frame = m_frame.get(); -- if (!frame->checkedScript()->canExecuteScripts(ReasonForCallingCanExecuteScripts::NotAboutToExecuteScript) || !frame->windowProxy().existingJSWindowProxy(world)) +- if (!frame->checkedScript()->canExecuteScripts(ReasonForCallingCanExecuteScripts::NotAboutToExecuteScript) || !frame->protectedWindowProxy()->existingJSWindowProxy(world)) - return; - - m_client->dispatchDidClearWindowObjectInWorld(world); @@ -5544,7 +5472,7 @@ index 5f70f737d06ced029e3b86a4dc93fd49e8ff4cae..15e6c42cbd4733b526493c3c7dfbea62 } diff --git a/Source/WebCore/loader/ProgressTracker.cpp b/Source/WebCore/loader/ProgressTracker.cpp -index 171c80cd90bdc8cc16c5e037b25f07faef0dec4e..0869f1be1699dd1548a2e4df6612d7fff42e3093 100644 +index a1554c16a0b836bcf87fea0f4e7831d9e0d7baf0..354e4fef6de9c77b7cad96cc428056ae4e2ff067 100644 --- a/Source/WebCore/loader/ProgressTracker.cpp +++ b/Source/WebCore/loader/ProgressTracker.cpp @@ -163,6 +163,8 @@ void ProgressTracker::progressCompleted(LocalFrame& frame) @@ -5566,10 +5494,10 @@ index 171c80cd90bdc8cc16c5e037b25f07faef0dec4e..0869f1be1699dd1548a2e4df6612d7ff } diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp -index 6fc201b47f8bb970faba58fa195cc2964b42e45c..f77592b2664d435e00bcf83820e4e8ccb110aeb6 100644 +index fb3e38627c8c3491f4994a74c6785cc9767fea2c..000de0c2f725d894cbdfa000d79ed48290eba00a 100644 --- a/Source/WebCore/loader/cache/CachedResourceLoader.cpp +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp -@@ -1166,8 +1166,11 @@ ResourceErrorOr> CachedResourceLoader::requ +@@ -1173,8 +1173,11 @@ ResourceErrorOr> CachedResourceLoader::requ request.updateReferrerPolicy(document ? document->referrerPolicy() : ReferrerPolicy::Default); @@ -5583,7 +5511,7 @@ index 6fc201b47f8bb970faba58fa195cc2964b42e45c..f77592b2664d435e00bcf83820e4e8cc if (RefPtr documentLoader = m_documentLoader.get()) { bool madeHTTPS { request.resourceRequest().wasSchemeOptimisticallyUpgraded() }; -@@ -1801,8 +1804,9 @@ Vector> CachedResourceLoader::allCachedSVGImages() const +@@ -1811,8 +1814,9 @@ Vector> CachedResourceLoader::allCachedSVGImages() const ResourceErrorOr> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request) { @@ -5596,10 +5524,10 @@ index 6fc201b47f8bb970faba58fa195cc2964b42e45c..f77592b2664d435e00bcf83820e4e8cc RefPtr document = m_document.get(); ASSERT(document); diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h -index 5352cc44f68f9038f7bd64214853fae160f9ba9d..76a9c273cd758af99c2ed4645876b594c55b0ea1 100644 +index 51ac3768b20683f26e3a568f390bfe8eba7e1fb5..f6f285ddbe261ccb807e1a57f93f202296f100d2 100644 --- a/Source/WebCore/page/ChromeClient.h +++ b/Source/WebCore/page/ChromeClient.h -@@ -362,7 +362,7 @@ public: +@@ -364,7 +364,7 @@ public: #endif #if ENABLE(ORIENTATION_EVENTS) @@ -5609,10 +5537,10 @@ index 5352cc44f68f9038f7bd64214853fae160f9ba9d..76a9c273cd758af99c2ed4645876b594 virtual RefPtr createColorChooser(ColorChooserClient&, const Color&) = 0; diff --git a/Source/WebCore/page/EventHandler.cpp b/Source/WebCore/page/EventHandler.cpp -index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b7657d86a04f 100644 +index 47392ed5c5888954373a1916f757ee9ccf1edbbd..8fb82ad84ea589efaf6074e79ca5a0c2a4df14ca 100644 --- a/Source/WebCore/page/EventHandler.cpp +++ b/Source/WebCore/page/EventHandler.cpp -@@ -4462,6 +4462,12 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr +@@ -4460,6 +4460,12 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr if (!document) return false; @@ -5625,7 +5553,7 @@ index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b765 dragState().dataTransfer = DataTransfer::createForDrag(*document); auto hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::No; -@@ -5053,6 +5059,7 @@ static HitTestResult hitTestResultInFrame(LocalFrame* frame, const LayoutPoint& +@@ -5054,6 +5060,7 @@ static HitTestResult hitTestResultInFrame(LocalFrame* frame, const LayoutPoint& return result; } @@ -5633,7 +5561,7 @@ index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b765 HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEvent& event) { Ref frame = m_frame.get(); -@@ -5126,7 +5133,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -5127,7 +5134,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve // Increment the platform touch id by 1 to avoid storing a key of 0 in the hashmap. unsigned touchPointTargetKey = point.id() + 1; @@ -5642,7 +5570,7 @@ index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b765 bool pointerCancelled = false; #endif RefPtr touchTarget; -@@ -5173,7 +5180,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -5174,7 +5181,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve // we also remove it from the map. touchTarget = m_originatingTouchPointTargets.take(touchPointTargetKey); @@ -5651,7 +5579,7 @@ index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b765 HitTestResult result = hitTestResultAtPoint(pagePoint, hitType | HitTestRequest::Type::AllowChildFrameContent); pointerTarget = result.targetElement(); pointerCancelled = (pointerTarget != touchTarget); -@@ -5196,7 +5203,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -5197,7 +5204,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve if (!targetFrame) continue; @@ -5660,7 +5588,7 @@ index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b765 // FIXME: WPE currently does not send touch stationary events, so create a naive TouchReleased PlatformTouchPoint // on release if the hit test result changed since the previous TouchPressed or TouchMoved if (pointState == PlatformTouchPoint::TouchReleased && pointerCancelled) { -@@ -5286,6 +5293,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -5287,6 +5294,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve return swallowedEvent; } @@ -5669,7 +5597,7 @@ index 408ddc7c6541d80568d126ea5ecdb17caf7b10d3..d588b0c5003f8e39cb03cbf28258b765 #if ENABLE(TOUCH_EVENTS) diff --git a/Source/WebCore/page/FocusController.cpp b/Source/WebCore/page/FocusController.cpp -index 676ca3ed5df66159852331a1373cdef66295fd3f..ed0dbb2445822df19e3d013e1ebe2cbaaa8d20eb 100644 +index f4a1e3408623e2b338149d1a129dc8075c764f59..571c4c6e9a873167bbc78b0708ffac44b7a8e641 100644 --- a/Source/WebCore/page/FocusController.cpp +++ b/Source/WebCore/page/FocusController.cpp @@ -586,13 +586,14 @@ bool FocusController::relinquishFocusToChrome(FocusDirection direction) @@ -5690,10 +5618,10 @@ index 676ca3ed5df66159852331a1373cdef66295fd3f..ed0dbb2445822df19e3d013e1ebe2cba } diff --git a/Source/WebCore/page/FrameSnapshotting.cpp b/Source/WebCore/page/FrameSnapshotting.cpp -index 0c099f95827d6f9fe307704ef9d7754fbb3d24f7..8e36dd7dca078f2de0c1733c8d462d17b2a3d5c4 100644 +index 3474800864049dcbe6c84746c4216e72a68fa48f..8f5d536921eeb41c77fe689c036dcb77e625e431 100644 --- a/Source/WebCore/page/FrameSnapshotting.cpp +++ b/Source/WebCore/page/FrameSnapshotting.cpp -@@ -111,7 +111,7 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& +@@ -115,7 +115,7 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& // Other paint behaviors are set by paintContentsForSnapshot. frame.view()->setPaintBehavior(paintBehavior); @@ -5702,7 +5630,7 @@ index 0c099f95827d6f9fe307704ef9d7754fbb3d24f7..8e36dd7dca078f2de0c1733c8d462d17 if (options.flags.contains(SnapshotFlags::PaintWith3xBaseScale)) scaleFactor = 3; -@@ -130,6 +130,8 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& +@@ -134,6 +134,8 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& return nullptr; buffer->context().translate(-imageRect.location()); @@ -5711,7 +5639,7 @@ index 0c099f95827d6f9fe307704ef9d7754fbb3d24f7..8e36dd7dca078f2de0c1733c8d462d17 if (!clipRects.isEmpty()) { Path clipPath; -@@ -138,7 +140,10 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& +@@ -142,7 +144,10 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& buffer->context().clipPath(clipPath); } @@ -5724,14 +5652,14 @@ index 0c099f95827d6f9fe307704ef9d7754fbb3d24f7..8e36dd7dca078f2de0c1733c8d462d17 } diff --git a/Source/WebCore/page/FrameSnapshotting.h b/Source/WebCore/page/FrameSnapshotting.h -index 3ab70c27fef52f5d933cd2338a7c2f96e2debc88..39fc13df5ce132f8d028cd5039c607fe8963ddcf 100644 +index 713cb9c3c59bbad23ef0b821ab27b3d669a7dea8..12077ebf0978b6b52e6af4602767c09d9f3cd9de 100644 --- a/Source/WebCore/page/FrameSnapshotting.h +++ b/Source/WebCore/page/FrameSnapshotting.h -@@ -56,6 +56,7 @@ enum class SnapshotFlags : uint16_t { - Accelerated = 1 << 8, - ExcludeReplacedContent = 1 << 9, - PaintWith3xBaseScale = 1 << 10, -+ OmitDeviceScaleFactor = 1 << 11, +@@ -58,6 +58,7 @@ enum class SnapshotFlags : uint16_t { + PaintWith3xBaseScale = 1 << 10, + ExcludeText = 1 << 11, + FixedAndStickyLayersOnly = 1 << 12, ++ OmitDeviceScaleFactor = 1 << 13, }; struct SnapshotOptions { @@ -5757,7 +5685,7 @@ index adc6185687b6fcee3a49819d0149cde24a4485bf..a33e5db46933c514abce86e3f264de9b } diff --git a/Source/WebCore/page/LocalFrame.cpp b/Source/WebCore/page/LocalFrame.cpp -index 090657b2602a09708b6b859952b466fcca07f174..ce5df33671b65494c0d222ed9086d7fe828f3702 100644 +index 54cab8376adfb6dd1e5ca32e170219290e4f0508..d212eece82066ba788d049e9aff212ff3493a6c3 100644 --- a/Source/WebCore/page/LocalFrame.cpp +++ b/Source/WebCore/page/LocalFrame.cpp @@ -41,6 +41,7 @@ @@ -5765,10 +5693,10 @@ index 090657b2602a09708b6b859952b466fcca07f174..ce5df33671b65494c0d222ed9086d7fe #include "Chrome.h" #include "ChromeClient.h" +#include "ComposedTreeIterator.h" + #include "DiagnosticLoggingClient.h" + #include "DiagnosticLoggingKeys.h" #include "DocumentLoader.h" - #include "DocumentType.h" - #include "Editing.h" -@@ -57,6 +58,7 @@ +@@ -59,6 +60,7 @@ #include "FrameSelection.h" #include "GraphicsContext.h" #include "GraphicsLayer.h" @@ -5776,15 +5704,15 @@ index 090657b2602a09708b6b859952b466fcca07f174..ce5df33671b65494c0d222ed9086d7fe #include "HTMLAttachmentElement.h" #include "HTMLFormControlElement.h" #include "HTMLFormElement.h" -@@ -79,6 +81,7 @@ +@@ -81,6 +83,7 @@ #include "Logging.h" #include "Navigator.h" #include "NodeList.h" +#include "NodeRenderStyle.h" #include "NodeTraversal.h" #include "Page.h" - #include "ProcessSyncClient.h" -@@ -202,6 +205,7 @@ LocalFrame::LocalFrame(Page& page, ClientCreator&& clientCreator, FrameIdentifie + #include "PaymentSession.h" +@@ -205,6 +208,7 @@ LocalFrame::LocalFrame(Page& page, ClientCreator&& clientCreator, FrameIdentifie void LocalFrame::init() { @@ -5792,7 +5720,7 @@ index 090657b2602a09708b6b859952b466fcca07f174..ce5df33671b65494c0d222ed9086d7fe protectedLoader()->init(); } -@@ -431,7 +435,7 @@ void LocalFrame::orientationChanged() +@@ -442,7 +446,7 @@ void LocalFrame::orientationChanged() IntDegrees LocalFrame::orientation() const { if (RefPtr page = this->page()) @@ -5801,9 +5729,9 @@ index 090657b2602a09708b6b859952b466fcca07f174..ce5df33671b65494c0d222ed9086d7fe return 0; } #endif // ENABLE(ORIENTATION_EVENTS) -@@ -1501,6 +1505,364 @@ void LocalFrame::reportResourceMonitoringWarning() - - #endif +@@ -1549,6 +1553,364 @@ RefPtr LocalFrame::frameDocumentSecurityOrigin() const + return nullptr; + } +#if !PLATFORM(IOS_FAMILY) + @@ -6167,7 +6095,7 @@ index 090657b2602a09708b6b859952b466fcca07f174..ce5df33671b65494c0d222ed9086d7fe #undef FRAME_RELEASE_LOG_ERROR diff --git a/Source/WebCore/page/LocalFrame.h b/Source/WebCore/page/LocalFrame.h -index 82f67e2e21e64b95800696a6bb22f15298866979..26a1bc0736e00964a5beb151bb53c430c36e029d 100644 +index b46fd5f993569302c23b7302a493a37fd8c1f440..2d9bbfc639deba681ecb7465d87981a787901dac 100644 --- a/Source/WebCore/page/LocalFrame.h +++ b/Source/WebCore/page/LocalFrame.h @@ -28,8 +28,10 @@ @@ -6238,7 +6166,7 @@ index 82f67e2e21e64b95800696a6bb22f15298866979..26a1bc0736e00964a5beb151bb53c430 ViewportArguments m_viewportArguments; diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp -index 916d2cc402fb668d19143022b5e3c58838a8920d..0f389d033511bdc7ef92fb4bd8a7525f92a23daa 100644 +index a9369054928a0c9c1f3aef0252c2da3d33d4ce7c..2b151666e718fb037471ab7005c62623c4ee332d 100644 --- a/Source/WebCore/page/Page.cpp +++ b/Source/WebCore/page/Page.cpp @@ -655,6 +655,45 @@ void Page::setOverrideViewportArguments(const std::optional& @@ -6287,7 +6215,7 @@ index 916d2cc402fb668d19143022b5e3c58838a8920d..0f389d033511bdc7ef92fb4bd8a7525f ScrollingCoordinator* Page::scrollingCoordinator() { if (!m_scrollingCoordinator && m_settings->scrollingCoordinatorEnabled()) { -@@ -4211,6 +4250,26 @@ void Page::setUseDarkAppearanceOverride(std::optional valueOverride) +@@ -4214,6 +4253,26 @@ void Page::setUseDarkAppearanceOverride(std::optional valueOverride) appearanceDidChange(); } @@ -6315,10 +6243,10 @@ index 916d2cc402fb668d19143022b5e3c58838a8920d..0f389d033511bdc7ef92fb4bd8a7525f { if (insets == m_fullscreenInsets) diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h -index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770dbf81d163e 100644 +index 4e33885a4771eb8260c8cebb14991d780f95be3d..e413dde6db076f587cbf520f17bfa5078320f8cc 100644 --- a/Source/WebCore/page/Page.h +++ b/Source/WebCore/page/Page.h -@@ -365,6 +365,9 @@ public: +@@ -366,6 +366,9 @@ public: const ViewportArguments* overrideViewportArguments() const { return m_overrideViewportArguments.get(); } WEBCORE_EXPORT void setOverrideViewportArguments(const std::optional&); @@ -6328,7 +6256,7 @@ index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770db static void refreshPlugins(bool reload); WEBCORE_EXPORT PluginData& pluginData(); WEBCORE_EXPORT Ref protectedPluginData(); -@@ -462,6 +465,10 @@ public: +@@ -463,6 +466,10 @@ public: #if ENABLE(DRAG_SUPPORT) DragController& dragController() { return m_dragController.get(); } const DragController& dragController() const { return m_dragController.get(); } @@ -6339,7 +6267,7 @@ index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770db #endif FocusController& focusController() const { return *m_focusController; } WEBCORE_EXPORT CheckedRef checkedFocusController() const; -@@ -647,6 +654,10 @@ public: +@@ -648,6 +655,10 @@ public: WEBCORE_EXPORT void setUseColorAppearance(bool useDarkAppearance, bool useElevatedUserInterfaceLevel); bool defaultUseDarkAppearance() const { return m_useDarkAppearance; } void setUseDarkAppearanceOverride(std::optional); @@ -6362,7 +6290,7 @@ index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770db #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY) DeviceOrientationUpdateProvider* deviceOrientationUpdateProvider() const { return m_deviceOrientationUpdateProvider.get(); } #endif -@@ -1392,6 +1408,9 @@ private: +@@ -1389,6 +1405,9 @@ private: #if ENABLE(DRAG_SUPPORT) const UniqueRef m_dragController; @@ -6372,7 +6300,7 @@ index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770db #endif std::unique_ptr m_focusController; #if ENABLE(CONTEXT_MENUS) -@@ -1469,6 +1488,8 @@ private: +@@ -1467,6 +1486,8 @@ private: bool m_useElevatedUserInterfaceLevel { false }; bool m_useDarkAppearance { false }; std::optional m_useDarkAppearanceOverride; @@ -6381,7 +6309,7 @@ index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770db #if ENABLE(TEXT_AUTOSIZING) float m_textAutosizingWidth { 0 }; -@@ -1651,6 +1672,11 @@ private: +@@ -1649,6 +1670,11 @@ private: #endif std::unique_ptr m_overrideViewportArguments; @@ -6394,7 +6322,7 @@ index 11c3b26d33342999041fb464ad487cd7dc0e6891..41bfb065fe96f08c21f296e2565770db #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY) RefPtr m_deviceOrientationUpdateProvider; diff --git a/Source/WebCore/page/PageConsoleClient.cpp b/Source/WebCore/page/PageConsoleClient.cpp -index 33ef23f456d389c876f7d66732a1ae9bb2ed7662..f0d38ba2208975e9a31f634096caf20ecb7340be 100644 +index ad70f3c37261ab24afc23f7136ae3feb1fcbe154..ecf31d4aaf2080e705e9caec75596104bdfce786 100644 --- a/Source/WebCore/page/PageConsoleClient.cpp +++ b/Source/WebCore/page/PageConsoleClient.cpp @@ -456,4 +456,9 @@ Ref PageConsoleClient::protectedPage() const @@ -6514,10 +6442,10 @@ index 24ed7c019bea4df52f2883db0e40bdbc2dc74ebd..a788f534d9e0e8124153c7f380b4fdb2 } diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp -index 51933cf69017936dfa08823d4a1a778d3d64db71..2101a8ab75fc7adf5ebcbf245e2083a2b750271b 100644 +index 4861e2276c172ea347cd31f68029b49df9350fe1..91c6eb5e3031f863d7b2483d9ba8be75c8718ea0 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp -@@ -348,6 +348,8 @@ bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtoc +@@ -372,6 +372,8 @@ bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtoc template typename std::enable_if::value, bool>::type ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, Predicate&& predicate, Args&&... args) const { @@ -6526,7 +6454,7 @@ index 51933cf69017936dfa08823d4a1a778d3d64db71..2101a8ab75fc7adf5ebcbf245e2083a2 bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly; for (auto& policy : m_policies) { if (policy->isReportOnly() != isReportOnly) -@@ -361,6 +363,8 @@ typename std::enable_if bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const { @@ -6535,7 +6463,7 @@ index 51933cf69017936dfa08823d4a1a778d3d64db71..2101a8ab75fc7adf5ebcbf245e2083a2 bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly; bool isAllowed = true; for (auto& policy : m_policies) { -@@ -377,6 +381,8 @@ bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposit +@@ -401,6 +405,8 @@ bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposit template bool ContentSecurityPolicy::allPoliciesAllow(NOESCAPE const ViolatedDirectiveCallback& callback, Predicate&& predicate, Args&&... args) const { @@ -6631,10 +6559,10 @@ index 0000000000000000000000000000000000000000..803239911006cfb3b03ea911c003f2d2 + +} diff --git a/Source/WebCore/platform/Cairo.cmake b/Source/WebCore/platform/Cairo.cmake -index 29492dd39b08db28aad2bf2439eb3e2bbcf25ad7..2b603cb8440b1b5057c87fcbd6909c61bae4ceb8 100644 +index 96baa501801c5952d4b5b203dff278adb54aaa54..34c498f6050388d30e4190604e7af6f68b33fab9 100644 --- a/Source/WebCore/platform/Cairo.cmake +++ b/Source/WebCore/platform/Cairo.cmake -@@ -14,6 +14,7 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS +@@ -15,6 +15,7 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS platform/graphics/cairo/ImageBufferCairoBackend.h platform/graphics/cairo/ImageBufferCairoImageSurfaceBackend.h platform/graphics/cairo/ImageBufferCairoSurfaceBackend.h @@ -6688,6 +6616,19 @@ index 2f4e02e783ac512aeb76e0abe7e253b57bfda937..94e0c9335720324a746f300a26b17a28 IntSize dragImageSize(DragImageRef) { +diff --git a/Source/WebCore/platform/DragImage.h b/Source/WebCore/platform/DragImage.h +index 77286d8e715825c9c7c0d329480fc0fc497fe561..5ea53eb8b7e53dd5a5950c65a38c1d23bfe46681 100644 +--- a/Source/WebCore/platform/DragImage.h ++++ b/Source/WebCore/platform/DragImage.h +@@ -60,7 +60,7 @@ class Node; + typedef RetainPtr DragImageRef; + #elif PLATFORM(MAC) + typedef RetainPtr DragImageRef; +-#elif PLATFORM(WIN) ++#elif PLATFORM(WIN) && USE(CAIRO) + typedef HBITMAP DragImageRef; + #elif USE(CAIRO) + typedef RefPtr DragImageRef; diff --git a/Source/WebCore/platform/Pasteboard.h b/Source/WebCore/platform/Pasteboard.h index 1b1a1147d4948e9281a114281e95d57d3b0ccf27..e21ccb98542cc582ad2489d301cb29c2b0c03f4b 100644 --- a/Source/WebCore/platform/Pasteboard.h @@ -6783,10 +6724,10 @@ index 63ffd6ca32c3baee03db2a9419c4f7e9de45388a..c60c7a8d1f110472117c8c4e969fd05f #endif diff --git a/Source/WebCore/platform/PlatformScreen.cpp b/Source/WebCore/platform/PlatformScreen.cpp -index ef0abc9a93e878897ffc9d2497a3da0fca5b37b7..8868e20e3720ce33d3148cea5c3bcdaf2ff2c4e5 100644 +index ef0abc9a93e878897ffc9d2497a3da0fca5b37b7..abd96c6d1a6c3ab9e0121c1e78f2f75a5f805b32 100644 --- a/Source/WebCore/platform/PlatformScreen.cpp +++ b/Source/WebCore/platform/PlatformScreen.cpp -@@ -85,3 +85,25 @@ OptionSet screenContentsFormatsForTesting() +@@ -85,3 +85,24 @@ OptionSet screenContentsFormatsForTesting() } // namespace WebCore #endif // PLATFORM(COCOA) || PLATFORM(GTK) || (PLATFORM(WPE) && ENABLE(WPE_PLATFORM)) @@ -6794,45 +6735,41 @@ index ef0abc9a93e878897ffc9d2497a3da0fca5b37b7..8868e20e3720ce33d3148cea5c3bcdaf +#if ENABLE(TOUCH_EVENTS) +namespace WebCore { + -+static std::optional screenHasTouchDeviceOverride = std::nullopt; ++static std::optional _screenHasTouchDeviceOverride = std::nullopt; ++ +void setScreenHasTouchDeviceOverride(bool value) { -+ screenHasTouchDeviceOverride = value; ++ _screenHasTouchDeviceOverride = value; ++} ++std::optional screenHasTouchDeviceOverride() { ++ return _screenHasTouchDeviceOverride; +} + +bool screenHasTouchDevice() { -+ if (screenHasTouchDeviceOverride) -+ return screenHasTouchDeviceOverride.value(); ++ if (screenHasTouchDeviceOverride()) ++ return screenHasTouchDeviceOverride().value(); + return platformScreenHasTouchDevice(); +} -+bool screenIsTouchPrimaryInputDevice() { -+ if (screenHasTouchDeviceOverride) -+ return screenHasTouchDeviceOverride.value(); -+ return platformScreenIsTouchPrimaryInputDevice(); -+} + +} // namespace WebCore +#endif diff --git a/Source/WebCore/platform/PlatformScreen.h b/Source/WebCore/platform/PlatformScreen.h -index dfef4ecbfb44cc985d1d26eaceae855cb427c002..665da262aeb6524f48b2de2418a474a65d0ef754 100644 +index 82a54ac2de2ddf4650e4b48db129fe25f6562264..d144ba15d18d2e892c25988dab27636135353a99 100644 --- a/Source/WebCore/platform/PlatformScreen.h +++ b/Source/WebCore/platform/PlatformScreen.h -@@ -160,12 +160,16 @@ WEBCORE_EXPORT float screenScaleFactor(UIScreen * = nullptr); +@@ -160,10 +160,14 @@ WEBCORE_EXPORT float screenScaleFactor(UIScreen * = nullptr); #endif #if ENABLE(TOUCH_EVENTS) --#if PLATFORM(GTK) || PLATFORM(WPE) +-#if PLATFORM(GTK) +WEBCORE_EXPORT void setScreenHasTouchDeviceOverride(bool); ++WEBCORE_EXPORT std::optional screenHasTouchDeviceOverride(); + WEBCORE_EXPORT bool screenHasTouchDevice(); - WEBCORE_EXPORT bool screenIsTouchPrimaryInputDevice(); -+#if PLATFORM(GTK) || PLATFORM(WPE) ++#if PLATFORM(GTK) +bool platformScreenHasTouchDevice(); -+bool platformScreenIsTouchPrimaryInputDevice(); #else -constexpr bool screenHasTouchDevice() { return true; } --constexpr bool screenIsTouchPrimaryInputDevice() { return true; } +constexpr bool platformScreenHasTouchDevice() { return true; } -+constexpr bool platformScreenIsTouchPrimaryInputDevice() { return true; } #endif #endif @@ -6863,11 +6800,11 @@ index 34715d27b529750fc866db87cd330b5184286771..3eefa218af075f76d98012cdeae7e4b3 // create a PlatformTouchPoint of type TouchCancelled artificially PlatformTouchPoint(unsigned id, State state, IntPoint screenPos, IntPoint pos) diff --git a/Source/WebCore/platform/Skia.cmake b/Source/WebCore/platform/Skia.cmake -index 31460a79014b0a8b21fdfe71759af14ff2da46d7..f2594d701502102fed89d711694b64e05a863aaa 100644 +index c39b57e01190b833be46452c3d964fe243c216d3..6e4af1509037697421cf0c80e2459da0bad79ba8 100644 --- a/Source/WebCore/platform/Skia.cmake +++ b/Source/WebCore/platform/Skia.cmake -@@ -13,6 +13,7 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS - +@@ -14,6 +14,7 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS + platform/graphics/skia/FontCascadeSkiaInlines.h platform/graphics/skia/GraphicsContextSkia.h platform/graphics/skia/ImageBufferSkiaBackend.h + platform/graphics/skia/ImageBufferUtilitiesSkia.h @@ -7008,10 +6945,10 @@ index 3d0ab7eceaf2a6321685bc362eb9b25600fd98fd..2d7e9a399bf2e9dc3f373d5fa3db99fa namespace WebCore { diff --git a/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp b/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp -index 3f0b75a0702db1ed10334c80e4813094571f588c..ea5ff6b7e7bd4c3b8babebc294d7d7c94fc6afb6 100644 +index 78ea08023ebd5f1b41b06cd843b6dce4ee80dd50..55fed94b774f033b4f75e2dee85dc27cf2e2689e 100644 --- a/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp +++ b/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp -@@ -169,6 +169,33 @@ static Vector stringIndicesFromClusters(const Vector& clusters, +@@ -168,6 +168,33 @@ static Vector stringIndicesFromClusters(const Vector& clusters, return stringIndices; } @@ -7045,7 +6982,7 @@ index 3f0b75a0702db1ed10334c80e4813094571f588c..ea5ff6b7e7bd4c3b8babebc294d7d7c9 void ComplexTextController::collectComplexTextRunsForCharacters(std::span cp, unsigned stringLocation, const Font* font) { if (!font) { -@@ -198,6 +225,8 @@ void ComplexTextController::collectComplexTextRunsForCharacters(std::span(typeString.length), std::numeric_limits::max()); -- types.append(String({ typeString.data, length })); +- types.append(String(unsafeMakeSpan(typeString.data, length))); - } - - wpe_pasteboard_string_vector_free(&pasteboardTypes); @@ -7855,7 +7783,7 @@ index 065c1b362537547bc732bd9ded7e801ddf938ef8..448321a8388b7075173061caf8c9b272 - return String(); - - const auto length = std::min(static_cast(string.length), std::numeric_limits::max()); -- String returnValue({ string.data, length }); +- String returnValue(unsafeMakeSpan(string.data, length)); - - wpe_pasteboard_string_free(&string); - return returnValue; @@ -7930,10 +7858,10 @@ index 1178c8fb001994bc9e6166376a367d9bc148913c..fcc6534568cad6b42a819a435f84ba2b m_commonHeaders.append(CommonHeader { name, value }); } diff --git a/Source/WebCore/platform/network/NetworkStorageSession.h b/Source/WebCore/platform/network/NetworkStorageSession.h -index 8c57734966ccfaf38dbb7fd956e71f3d3f65a477..594e5031caba89142535a16c2fb132bb878aab21 100644 +index 18e1d04c6f984c1c3502542399ab1b8eeba1f239..2ec3ed7e28cdf802c017ca705bab3bf769f69a21 100644 --- a/Source/WebCore/platform/network/NetworkStorageSession.h +++ b/Source/WebCore/platform/network/NetworkStorageSession.h -@@ -200,6 +200,7 @@ public: +@@ -202,6 +202,7 @@ public: NetworkingContext* context() const; #endif @@ -8023,7 +7951,7 @@ index be9d60dc24eec83f9b7fad5c9bbcdffc2cbd3bb4..9fe86dc133adb69e21a1e9067ea0b2a1 ResourceResponseBase::Source source; ResourceResponseBase::Type type; diff --git a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm -index ab7a9d832f0573bd402c5643d3eee3aa3e3b33c6..7dcb122411c4b6bf45c05e3a6edf7b133c5d9e9e 100644 +index d938386a2a7bb9a338b781706434fbee753a5ad4..96b8e6d3b7fc65fb6f04162e4975467b3cbd3cf4 100644 --- a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm +++ b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm @@ -552,6 +552,22 @@ bool NetworkStorageSession::setCookieFromDOM(const URL& firstParty, const SameSi @@ -8036,14 +7964,14 @@ index ab7a9d832f0573bd402c5643d3eee3aa3e3b33c6..7dcb122411c4b6bf45c05e3a6edf7b13 + size_t count = cookieValues.size(); + auto* cookies = [NSMutableArray arrayWithCapacity:count]; + for (const auto& cookieValue : cookieValues) { -+ NSString* cookieString = (NSString *)cookieValue; ++ NSString* cookieString = cookieValue.createNSString().autorelease(); + NSString* cookieKey = @"Set-Cookie"; + NSDictionary* headers = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObject:cookieString] forKeys:[NSArray arrayWithObject:cookieKey]]; -+ NSArray* parsedCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:(NSURL *)url]; ++ NSArray* parsedCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:url.createNSURL().get()]; + [cookies addObject:parsedCookies[0]]; + } -+ NSURL *cookieURL = url; -+ setHTTPCookiesForURL(cookieStorage().get(), cookies, cookieURL, firstParty, sameSiteInfo); ++ NSURL *cookieURL = url.createNSURL().get(); ++ setHTTPCookiesForURL(cookieStorage().get(), cookies, cookieURL, firstParty.createNSURL().get(), sameSiteInfo); +} + static NSHTTPCookieAcceptPolicy httpCookieAcceptPolicy(CFHTTPCookieStorageRef cookieStorage) @@ -8143,10 +8071,10 @@ index 2d7a77d759aaea9a541030af5e6015a8ed9c97a4..a0c947d325c984045dbbdf2580d19a32 WEBCORE_EXPORT void send(CurlStreamID, UniqueArray&&, size_t); diff --git a/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp b/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp -index b422dee5926a14aab7a20ea4de9d51080b2b3fea..d5735a2097e18f5c94fa0695fde6ff7a4ffda823 100644 +index 96289d8ae2e4feb60a91fab3f5cf1fc27b9e7c87..8c0b62c44a18571d1f3ea1ed81d59a0aae28d3f1 100644 --- a/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp +++ b/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp -@@ -135,6 +135,12 @@ void NetworkStorageSession::setCookieAcceptPolicy(CookieAcceptPolicy policy) con +@@ -136,6 +136,12 @@ void NetworkStorageSession::setCookieAcceptPolicy(CookieAcceptPolicy policy) con cookieDatabase().setAcceptPolicy(policy); } @@ -8160,10 +8088,10 @@ index b422dee5926a14aab7a20ea4de9d51080b2b3fea..d5735a2097e18f5c94fa0695fde6ff7a { switch (cookieDatabase().acceptPolicy()) { diff --git a/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp b/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp -index c191cdc193019db8efc2c597bb3c87b3a318bcd6..35ad719201c4505c43c20701dffce0adcfb241f6 100644 +index 8750fc6d3ad81889da3d60685fc9a107bf4760c1..7a35cf7516b837084fafbfceae84147ab17bd161 100644 --- a/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp +++ b/Source/WebCore/platform/network/soup/NetworkStorageSessionSoup.cpp -@@ -528,6 +528,26 @@ void NetworkStorageSession::replaceCookies(const Vector& cookies) +@@ -551,6 +551,26 @@ void NetworkStorageSession::replaceCookies(const Vector& cookies) g_signal_emit(jar, signalId, 0, nullptr, nullptr); } @@ -8254,6 +8182,69 @@ index 0379437d84807e4a8d3846afac5ec8a70e743e70..5b0461bf12535d4900ffaddc2a878262 { if (!m_dragDataMap.isEmpty() || !m_platformDragData) return m_dragDataMap; +diff --git a/Source/WebCore/platform/win/DragImageWin.cpp b/Source/WebCore/platform/win/DragImageWin.cpp +index dd24e15115aeff41f0f4452a9cac292d75bc0d5d..8df467c008bdb3de59d301b14c1c20b8bb0b6a41 100644 +--- a/Source/WebCore/platform/win/DragImageWin.cpp ++++ b/Source/WebCore/platform/win/DragImageWin.cpp +@@ -62,16 +62,22 @@ IntSize dragImageSize(DragImageRef image) + { + if (!image) + return IntSize(); +- BITMAP b; +- GetObject(image, sizeof(BITMAP), &b); +- return IntSize(b.bmWidth, b.bmHeight); ++ return { image->width(), image->height() }; + } + ++#if USE(CAIRO) + void deleteDragImage(DragImageRef image) + { + if (image) + ::DeleteObject(image); + } ++#else ++void deleteDragImage(DragImageRef) ++{ ++ // Since this is a RefPtr, there's nothing additional we need to do to ++ // delete it. It will be released when it falls out of scope. ++} ++#endif + + DragImageRef dissolveDragImageToFraction(DragImageRef image, float) + { +@@ -79,8 +85,9 @@ DragImageRef dissolveDragImageToFraction(DragImageRef image, float) + return image; + } + +-DragImageRef createDragImageIconForCachedImageFilename(const String& filename) ++DragImageRef createDragImageIconForCachedImageFilename(const String&) + { ++#if USE(CAIRO) + SHFILEINFO shfi { }; + auto fname = filename.wideCharacters(); + if (FAILED(SHGetFileInfo(fname.data(), FILE_ATTRIBUTE_NORMAL, &shfi, sizeof(shfi), SHGFI_ICON | SHGFI_USEFILEATTRIBUTES))) +@@ -96,6 +103,9 @@ DragImageRef createDragImageIconForCachedImageFilename(const String& filename) + DeleteObject(iconInfo.hbmMask); + + return iconInfo.hbmColor; ++#else ++ return nullptr; ++#endif + } + + #if USE(CAIRO) +@@ -221,9 +231,9 @@ DragImageRef createDragImageForColor(const Color&, const FloatRect&, float, Path + } + + #if USE(SKIA) +-DragImageRef createDragImageFromImage(Image*, ImageOrientation, GraphicsClient*, float) ++DragImageRef createDragImageFromImage(Image* image, ImageOrientation, GraphicsClient*, float) + { +- return nullptr; ++ return image->currentNativeImage()->platformImage(); + } + + DragImageRef scaleDragImage(DragImageRef, FloatSize) diff --git a/Source/WebCore/platform/win/KeyEventWin.cpp b/Source/WebCore/platform/win/KeyEventWin.cpp index d450bf9d0fd1f0bf8f28db483ac9d3d60fa9d114..72a59403a0b5493aea4a8e28eb15eac24b652b09 100644 --- a/Source/WebCore/platform/win/KeyEventWin.cpp @@ -8507,25 +8498,6 @@ index 0000000000000000000000000000000000000000..5a20bfaacd42d27bee5f843483d6b60d +} + +} -diff --git a/Source/WebCore/platform/wpe/PlatformScreenWPE.cpp b/Source/WebCore/platform/wpe/PlatformScreenWPE.cpp -index 77bdff686770e56f5445fa12216c6bff93bb5cfb..e16583ea6298864df9c8b82cb0686b2afb18ce95 100644 ---- a/Source/WebCore/platform/wpe/PlatformScreenWPE.cpp -+++ b/Source/WebCore/platform/wpe/PlatformScreenWPE.cpp -@@ -151,12 +151,12 @@ bool screenSupportsExtendedColor(Widget*) - } - - #if ENABLE(TOUCH_EVENTS) --bool screenHasTouchDevice() -+bool platformScreenHasTouchDevice() - { - return true; - } - --bool screenIsTouchPrimaryInputDevice() -+bool platformScreenIsTouchPrimaryInputDevice() - { - return true; - } diff --git a/Source/WebCore/platform/wpe/SelectionData.cpp b/Source/WebCore/platform/wpe/SelectionData.cpp new file mode 100644 index 0000000000000000000000000000000000000000..947bfe6576780038ecb87ea9bda116adb19dfd71 @@ -8832,10 +8804,10 @@ index db95c8273bd0deb3f903a45d02fc07bbbd8ab305..bf88228b4c838b90d11d430cc9429d51 WorkerOrWorkletGlobalScope& m_globalScope; }; diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp -index e185fc809df730737ff85c349ad17dce10d246f0..b1ccbc16b185ac76ecffb753b6aed2ae1eb8e39a 100644 +index eb1ee28ea9a6bee86f68b4d0d4e16ae6dea32772..1968c70c80c1529ada0e76f1a5f45981924fa629 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp -@@ -96,6 +96,8 @@ +@@ -97,6 +97,8 @@ #if PLATFORM(COCOA) #include @@ -8844,7 +8816,7 @@ index e185fc809df730737ff85c349ad17dce10d246f0..b1ccbc16b185ac76ecffb753b6aed2ae #endif #if ENABLE(APPLE_PAY_REMOTE_UI) -@@ -1232,6 +1234,14 @@ void NetworkConnectionToWebProcess::clearPageSpecificData(PageIdentifier pageID) +@@ -1231,6 +1233,14 @@ void NetworkConnectionToWebProcess::clearPageSpecificData(PageIdentifier pageID) storageSession->clearPageSpecificDataForResourceLoadStatistics(pageID); } @@ -8860,10 +8832,10 @@ index e185fc809df730737ff85c349ad17dce10d246f0..b1ccbc16b185ac76ecffb753b6aed2ae { if (auto* storageSession = protectedNetworkProcess()->storageSession(m_sessionID)) diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h -index 706ceb9edc7de45d731db75c656a7a9ad5780cbe..49a6735a5936a8b6daab556a7df89b254157e14f 100644 +index 3f1539237c6c8d1cd832cd3ece2ba20939e01a41..1259c36e38d46c0ebfdf98be20f217433f831ca5 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h -@@ -380,6 +380,8 @@ private: +@@ -388,6 +388,8 @@ private: void clearPageSpecificData(WebCore::PageIdentifier); @@ -8873,7 +8845,7 @@ index 706ceb9edc7de45d731db75c656a7a9ad5780cbe..49a6735a5936a8b6daab556a7df89b25 void logUserInteraction(RegistrableDomain&&); diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in -index a8f2e3cd5cef58c0e390a9cc8c3d18c9d24ffbda..fdbc166682cd3ec9fdbba737757331e46fd93364 100644 +index b10706aafd037a2b92a68b0d2c474ec2e42cd2fe..f80792c02d880dbd61849233fdbc348f1eeffb33 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in @@ -80,6 +80,8 @@ messages -> NetworkConnectionToWebProcess WantsDispatchMessage { @@ -8886,10 +8858,10 @@ index a8f2e3cd5cef58c0e390a9cc8c3d18c9d24ffbda..fdbc166682cd3ec9fdbba737757331e4 LogUserInteraction(WebCore::RegistrableDomain domain) ResourceLoadStatisticsUpdated(Vector statistics) -> () diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.cpp b/Source/WebKit/NetworkProcess/NetworkProcess.cpp -index 40718b455299eb28978d999d2088579564b06fd4..9ada150a661beda8b4411c5b6e669c72e0895a75 100644 +index be181729d818871958a51ad54b95424be796cbd1..759be2137d58cfbc088e1e459fdfca5d329c3ff4 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkProcess.cpp -@@ -653,6 +653,12 @@ void NetworkProcess::registrableDomainsExemptFromWebsiteDataDeletion(PAL::Sessio +@@ -670,6 +670,12 @@ void NetworkProcess::registrableDomainsExemptFromWebsiteDataDeletion(PAL::Sessio completionHandler({ }); } @@ -8903,7 +8875,7 @@ index 40718b455299eb28978d999d2088579564b06fd4..9ada150a661beda8b4411c5b6e669c72 { if (auto* session = networkSession(sessionID)) { diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.h b/Source/WebKit/NetworkProcess/NetworkProcess.h -index c6b478b2564a0fb550118e43a9749101c2aa7361..2a45c0dc4e3947b84ed1da124c02ccfff7834cbb 100644 +index aad6d1ad7d9735935f7d55d56ae8f3e8d0c5b852..a7421b51a4e8bbf9c695d2aca3ec6cfc98d14bf5 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkProcess.h @@ -84,6 +84,7 @@ class SessionID; @@ -8914,7 +8886,7 @@ index c6b478b2564a0fb550118e43a9749101c2aa7361..2a45c0dc4e3947b84ed1da124c02ccff class CurlProxySettings; class ProtectionSpace; class NetworkStorageSession; -@@ -234,6 +235,9 @@ public: +@@ -235,6 +236,9 @@ public: void registrableDomainsWithLastAccessedTime(PAL::SessionID, CompletionHandler>)>&&); void registrableDomainsExemptFromWebsiteDataDeletion(PAL::SessionID, CompletionHandler)>&&); @@ -8925,10 +8897,10 @@ index c6b478b2564a0fb550118e43a9749101c2aa7361..2a45c0dc4e3947b84ed1da124c02ccff void clearUserInteraction(PAL::SessionID, RegistrableDomain&&, CompletionHandler&&); void deleteAndRestrictWebsiteDataForRegistrableDomains(PAL::SessionID, OptionSet, RegistrableDomainsToDeleteOrRestrictWebsiteDataFor&&, CompletionHandler&&)>&&); diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -index 24124fdb6ad7f5447345764a8481342557ceba8e..204e00738921f21e70af8398a9350cd49a4640ce 100644 +index 64312176007bfc3c06ae2d624dbbabc4007e0fdf..8dfd79c77f27f13aa23927c9b2c6610cd40885d5 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -@@ -90,6 +90,8 @@ messages -> NetworkProcess : AuxiliaryProcess WantsAsyncDispatchMessage { +@@ -91,6 +91,8 @@ messages -> NetworkProcess : AuxiliaryProcess WantsAsyncDispatchMessage { SetInspectionForServiceWorkersAllowed(PAL::SessionID sessionID, bool inspectable) @@ -8938,7 +8910,7 @@ index 24124fdb6ad7f5447345764a8481342557ceba8e..204e00738921f21e70af8398a9350cd4 ClearUserInteraction(PAL::SessionID sessionID, WebCore::RegistrableDomain resourceDomain) -> () DumpResourceLoadStatistics(PAL::SessionID sessionID) -> (String dumpedStatistics) diff --git a/Source/WebKit/NetworkProcess/NetworkSession.h b/Source/WebKit/NetworkProcess/NetworkSession.h -index 679cf1fc291f262f90d42f5d597361c67eb0f4c0..527015014b15f9efca45dd054f541b4be416eafe 100644 +index 2a48fae7bd8cce5ee360df66e5bc49cb4d2b9c54..5fdec3ff3fb706a426717c95831ece36c43543ce 100644 --- a/Source/WebKit/NetworkProcess/NetworkSession.h +++ b/Source/WebKit/NetworkProcess/NetworkSession.h @@ -207,6 +207,9 @@ public: @@ -8951,7 +8923,7 @@ index 679cf1fc291f262f90d42f5d597361c67eb0f4c0..527015014b15f9efca45dd054f541b4b void removeSoftUpdateLoader(ServiceWorkerSoftUpdateLoader* loader) { m_softUpdateLoaders.remove(loader); } void addNavigationPreloaderTask(ServiceWorkerFetchTask&); ServiceWorkerFetchTask* navigationPreloaderTaskFromFetchIdentifier(WebCore::FetchIdentifier); -@@ -343,6 +346,7 @@ protected: +@@ -345,6 +348,7 @@ protected: bool m_privateClickMeasurementDebugModeEnabled { false }; std::optional m_ephemeralMeasurement; bool m_isRunningEphemeralMeasurementTest { false }; @@ -8960,7 +8932,7 @@ index 679cf1fc291f262f90d42f5d597361c67eb0f4c0..527015014b15f9efca45dd054f541b4b HashSet> m_keptAliveLoads; diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm -index 0d2fcd76b91d7f9afa8c2378ab1109f12386a6c2..ff01afba3686fd8f81e1ad914f0171d3fd40e0fb 100644 +index 846603d498fa5f0cfe8829340ed810558df54b41..6edf57edc6d0c2d7ba79319ddb2765b856a8cddc 100644 --- a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm +++ b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm @@ -805,6 +805,8 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didRece @@ -8972,9 +8944,9 @@ index 0d2fcd76b91d7f9afa8c2378ab1109f12386a6c2..ff01afba3686fd8f81e1ad914f0171d3 negotiatedLegacyTLS = checkForLegacyTLS(task._incompleteTaskMetrics.transactionMetrics.lastObject); if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes && task._preconnect) -@@ -1146,6 +1148,14 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data +@@ -1148,6 +1150,14 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data - resourceResponse.setDeprecatedNetworkLoadMetrics(WebCore::copyTimingData(taskMetrics, networkDataTask->networkLoadMetrics())); + resourceResponse.setDeprecatedNetworkLoadMetrics(WebCore::copyTimingData(taskMetrics.get(), networkDataTask->networkLoadMetrics())); resourceResponse.setProxyName(WTFMove(proxyName)); + + __block WebCore::HTTPHeaderMap requestHeaders; @@ -8986,9 +8958,9 @@ index 0d2fcd76b91d7f9afa8c2378ab1109f12386a6c2..ff01afba3686fd8f81e1ad914f0171d3 + networkDataTask->didReceiveResponse(WTFMove(resourceResponse), negotiatedLegacyTLS, privateRelayed, [completionHandler = makeBlockPtr(completionHandler), taskIdentifier](WebCore::PolicyAction policyAction) { #if !LOG_DISABLED - LOG(NetworkSession, "%llu didReceiveResponse completionHandler (%d)", taskIdentifier, policyAction); + LOG(NetworkSession, "%llu didReceiveResponse completionHandler (%s)", taskIdentifier, toString(policyAction).characters()); diff --git a/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp b/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp -index 50cd0b45a2fa5be6f217b686b2ff703b74c06078..c2d6b10762def97a222197d1e6f45d31d3b7a49b 100644 +index 1aa46419c05ba688a2ca79bfa48f4c7a9164375e..9b90036b16c587cd509bb35f067c9a9fddd8cfc2 100644 --- a/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp +++ b/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp @@ -80,10 +80,18 @@ NetworkDataTaskCurl::NetworkDataTaskCurl(NetworkSession& session, NetworkDataTas @@ -9022,7 +8994,7 @@ index 50cd0b45a2fa5be6f217b686b2ff703b74c06078..c2d6b10762def97a222197d1e6f45d31 handleCookieHeaders(request.resourceRequest(), receivedResponse); -@@ -294,6 +303,36 @@ bool NetworkDataTaskCurl::shouldRedirectAsGET(const ResourceRequest& request, bo +@@ -293,6 +302,35 @@ bool NetworkDataTaskCurl::shouldRedirectAsGET(const ResourceRequest& request, bo return false; } @@ -9044,22 +9016,21 @@ index 50cd0b45a2fa5be6f217b686b2ff703b74c06078..c2d6b10762def97a222197d1e6f45d31 + return; + } + -+ if (-1 == FileSystem::writeToFile(m_downloadDestinationFile, std::span(m_dataURLResult.value().data.data(), m_dataURLResult.value().data.size()))) { ++ if (!m_downloadDestinationFile.write(std::span(m_dataURLResult.value().data.data(), m_dataURLResult.value().data.size()))) { + deleteDownloadFile(); + download.didFail(ResourceError(CURLE_WRITE_ERROR, m_response.url()), std::span()); + return; + } + + download.didReceiveData(m_dataURLResult.value().data.size(), 0, 0); -+ FileSystem::closeFile(m_downloadDestinationFile); -+ m_downloadDestinationFile = FileSystem::invalidPlatformFileHandle; ++ m_downloadDestinationFile = { }; + download.didFinish(); +} + void NetworkDataTaskCurl::invokeDidReceiveResponse() { didReceiveResponse(ResourceResponse(m_response), NegotiatedLegacyTLS::No, PrivateRelayed::No, std::nullopt, [this, protectedThis = Ref { *this }](PolicyAction policyAction) { -@@ -323,6 +362,8 @@ void NetworkDataTaskCurl::invokeDidReceiveResponse() +@@ -322,6 +360,8 @@ void NetworkDataTaskCurl::invokeDidReceiveResponse() download->didCreateDestination(m_pendingDownloadLocation); if (m_curlRequest) m_curlRequest->completeDidReceiveResponse(); @@ -9068,7 +9039,7 @@ index 50cd0b45a2fa5be6f217b686b2ff703b74c06078..c2d6b10762def97a222197d1e6f45d31 break; } default: -@@ -411,6 +452,8 @@ void NetworkDataTaskCurl::willPerformHTTPRedirection() +@@ -410,6 +450,8 @@ void NetworkDataTaskCurl::willPerformHTTPRedirection() m_curlRequest->setUserPass(m_initialCredential.user(), m_initialCredential.password()); m_curlRequest->setAuthenticationScheme(ProtectionSpace::AuthenticationScheme::HTTPBasic); } @@ -9078,7 +9049,7 @@ index 50cd0b45a2fa5be6f217b686b2ff703b74c06078..c2d6b10762def97a222197d1e6f45d31 if (m_state != State::Suspended) { m_state = State::Suspended; diff --git a/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.h b/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.h -index fc4d15308fcb12b084a65d058aec9e67fcf6021f..b1a689989c68798e6308b1da7b3bd7c066497114 100644 +index 7babe41b8351f8adbffcf194310d4df41637b192..2d6f0055cfcb0ec8a4629ce41b19cecd17ab4fdc 100644 --- a/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.h +++ b/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.h @@ -28,6 +28,7 @@ @@ -9115,7 +9086,7 @@ index fc4d15308fcb12b084a65d058aec9e67fcf6021f..b1a689989c68798e6308b1da7b3bd7c0 + + std::optional m_dataURLResult; + - FileSystem::PlatformFileHandle m_downloadDestinationFile { FileSystem::invalidPlatformFileHandle }; + FileSystem::FileHandle m_downloadDestinationFile; bool m_blockingCookies { false }; diff --git a/Source/WebKit/NetworkProcess/curl/NetworkSessionCurl.cpp b/Source/WebKit/NetworkProcess/curl/NetworkSessionCurl.cpp @@ -9189,7 +9160,7 @@ index 0ef9f38c7662f922b7a8e4da81613799b0fddc68..45704b96df02cead2f21fd9484555027 WebCore::CurlStreamScheduler& m_scheduler; diff --git a/Source/WebKit/NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in b/Source/WebKit/NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in -index a6d86f1388e8cae7c3939d1ecffb2c46eb5054c5..9e11224335211908fe8bf14edc4c22e9e4934582 100644 +index 9ecb3e28299aa54a8ac309e74afb6944833e8eae..383bdd7533fad2fd58b4d0cceee4e68962aa8455 100644 --- a/Source/WebKit/NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in +++ b/Source/WebKit/NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in @@ -451,9 +451,11 @@ @@ -9269,10 +9240,10 @@ index c0c9bf6f4f1879efb8dfdd24951ef67ae373fa2f..48aeb8ac1324a58dc59bdb125c8ec4e3 } diff --git a/Source/WebKit/PlatformGTK.cmake b/Source/WebKit/PlatformGTK.cmake -index 2f33b1c02a7d0e352a5662c19b44cfa7a161b8f5..263d65ba51319116f161db5918439c081cde43fd 100644 +index 2990740be0d39d16ebbc625639c57200cdd700a8..78d25fe109ac96343d4aeedbc1e6b0087b34299a 100644 --- a/Source/WebKit/PlatformGTK.cmake +++ b/Source/WebKit/PlatformGTK.cmake -@@ -320,6 +320,9 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES +@@ -321,6 +321,9 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES ${GSTREAMER_PBUTILS_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS} @@ -9282,7 +9253,7 @@ index 2f33b1c02a7d0e352a5662c19b44cfa7a161b8f5..263d65ba51319116f161db5918439c08 ) list(APPEND WebKit_INTERFACE_INCLUDE_DIRECTORIES -@@ -356,6 +359,9 @@ if (USE_LIBWEBRTC) +@@ -360,6 +363,9 @@ if (USE_LIBWEBRTC) list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES "${THIRDPARTY_DIR}/libwebrtc/Source/" "${THIRDPARTY_DIR}/libwebrtc/Source/webrtc" @@ -9292,7 +9263,7 @@ index 2f33b1c02a7d0e352a5662c19b44cfa7a161b8f5..263d65ba51319116f161db5918439c08 ) endif () -@@ -407,6 +413,12 @@ else () +@@ -411,6 +417,12 @@ else () set(WebKitGTK_ENUM_HEADER_TEMPLATE ${WEBKIT_DIR}/UIProcess/API/gtk/WebKitEnumTypesGtk3.h.in) endif () @@ -9306,10 +9277,10 @@ index 2f33b1c02a7d0e352a5662c19b44cfa7a161b8f5..263d65ba51319116f161db5918439c08 set(WebKitGTK_ENUM_GENERATION_HEADERS ${WebKitGTK_INSTALLED_HEADERS}) list(REMOVE_ITEM WebKitGTK_ENUM_GENERATION_HEADERS ${WebKitGTK_DERIVED_SOURCES_DIR}/webkit/WebKitEnumTypes.h) diff --git a/Source/WebKit/PlatformWPE.cmake b/Source/WebKit/PlatformWPE.cmake -index 8376382ba17ea308210e2a46a60a90a2da2d013c..7935f07612a7ce44cae17e4a8650dcee36ceb49b 100644 +index d55e9837af868c774cadcd6791ec07ccee1a97a0..649c0668818d6f2d70036bb18ff108060318d0fb 100644 --- a/Source/WebKit/PlatformWPE.cmake +++ b/Source/WebKit/PlatformWPE.cmake -@@ -114,6 +114,8 @@ list(APPEND WebKit_SERIALIZATION_IN_FILES +@@ -121,6 +121,8 @@ list(APPEND WebKit_SERIALIZATION_IN_FILES Shared/glib/UserMessage.serialization.in Shared/soup/WebCoreArgumentCodersSoup.serialization.in @@ -9318,7 +9289,7 @@ index 8376382ba17ea308210e2a46a60a90a2da2d013c..7935f07612a7ce44cae17e4a8650dcee ) list(APPEND WebKit_DERIVED_SOURCES -@@ -213,6 +215,7 @@ set(WPE_API_HEADER_TEMPLATES +@@ -220,6 +222,7 @@ set(WPE_API_HEADER_TEMPLATES ${WEBKIT_DIR}/UIProcess/API/glib/WebKitWindowProperties.h.in ${WEBKIT_DIR}/UIProcess/API/glib/WebKitWebsitePolicies.h.in ${WEBKIT_DIR}/UIProcess/API/glib/webkit.h.in @@ -9326,7 +9297,7 @@ index 8376382ba17ea308210e2a46a60a90a2da2d013c..7935f07612a7ce44cae17e4a8650dcee ) if (ENABLE_2022_GLIB_API) -@@ -423,7 +426,16 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES +@@ -430,7 +433,16 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES ${GIO_UNIX_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS} @@ -9344,13 +9315,13 @@ index 8376382ba17ea308210e2a46a60a90a2da2d013c..7935f07612a7ce44cae17e4a8650dcee list(APPEND WebKit_LIBRARIES WPE::libwpe diff --git a/Source/WebKit/PlatformWin.cmake b/Source/WebKit/PlatformWin.cmake -index e0e9b52c43ee0742f705159c1369f2938b9c4956..05fa7d93a721f200143e9d3ac45a75ce942afc88 100644 +index 8429fc8b2e3721830edf197b3369f4f21bb70a9a..bb6779326529bfe16f47dd1b941f4e3d364cb4df 100644 --- a/Source/WebKit/PlatformWin.cmake +++ b/Source/WebKit/PlatformWin.cmake @@ -54,8 +54,13 @@ list(APPEND WebKit_SOURCES - UIProcess/WebsiteData/win/WebsiteDataStoreWin.cpp UIProcess/win/AutomationClientWin.cpp + UIProcess/win/AutomationSessionClientWin.cpp + + UIProcess/win/InspectorTargetProxyWin.cpp + UIProcess/win/InspectorPlaywrightAgentClientWin.cpp @@ -9369,7 +9340,7 @@ index e0e9b52c43ee0742f705159c1369f2938b9c4956..05fa7d93a721f200143e9d3ac45a75ce WebProcess/WebPage/AcceleratedSurface.cpp -@@ -120,8 +126,81 @@ list(APPEND WebKit_PUBLIC_FRAMEWORK_HEADERS +@@ -119,8 +125,81 @@ list(APPEND WebKit_PUBLIC_FRAMEWORK_HEADERS list(APPEND WebKit_PRIVATE_LIBRARIES comctl32 @@ -9452,10 +9423,10 @@ index e0e9b52c43ee0742f705159c1369f2938b9c4956..05fa7d93a721f200143e9d3ac45a75ce WebProcess/EntryPoint/win/WebProcessMain.cpp diff --git a/Source/WebKit/Shared/AuxiliaryProcess.h b/Source/WebKit/Shared/AuxiliaryProcess.h -index 4510beb0b7403138abef4c9b5e892271fe8beaab..dae8f31ef187d7b086dd84d7e356839dc2811154 100644 +index c36aaf218982ee54d433b023485ac36e7c45cb2b..1f07f8ebad02fd11a4d23a69ec5092122df73aa0 100644 --- a/Source/WebKit/Shared/AuxiliaryProcess.h +++ b/Source/WebKit/Shared/AuxiliaryProcess.h -@@ -212,6 +212,11 @@ struct AuxiliaryProcessInitializationParameters { +@@ -216,6 +216,11 @@ struct AuxiliaryProcessInitializationParameters { IPC::Connection::Identifier connectionIdentifier; HashMap extraInitializationData; WTF::AuxiliaryProcessType processType; @@ -9563,10 +9534,10 @@ index ea1eb9f00feaaecf73bdddc37c904e88f43bfa85..8a631e5293a11abd650958baad4e9678 #endif }; diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in -index db25c4a2dbb7fd8f9e0885757655eed1ebf64d35..1c21752b0f3710d7dfb0c70421c17de740274439 100644 +index c0ba2034b3d6a9fd9d807f1d67a62284c311c3dc..d688da2d022ded01cf9828fd6d56d0570fcc5735 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in -@@ -2814,6 +2814,9 @@ class WebCore::AuthenticationChallenge { +@@ -2826,6 +2826,9 @@ class WebCore::AuthenticationChallenge { class WebCore::DragData { #if PLATFORM(COCOA) String pasteboardName(); @@ -9576,7 +9547,7 @@ index db25c4a2dbb7fd8f9e0885757655eed1ebf64d35..1c21752b0f3710d7dfb0c70421c17de7 #endif WebCore::IntPoint clientPosition(); WebCore::IntPoint globalPosition(); -@@ -3621,6 +3624,7 @@ enum class WebCore::WasPrivateRelayed : bool; +@@ -3624,6 +3627,7 @@ enum class WebCore::WasPrivateRelayed : bool; String httpStatusText; String httpVersion; WebCore::HTTPHeaderMap httpHeaderFields; @@ -9690,7 +9661,7 @@ index 8e4e2d6d5ebb08fba210fe0a328d45290348dd11..32a43192ec1e918c33b1b046b71d2ec5 const String& text() const { return m_text; } diff --git a/Source/WebKit/Shared/WebMouseEvent.h b/Source/WebKit/Shared/WebMouseEvent.h -index fd4722dd38df74f259d8add02025549022a0a205..e1c33f6d766707170935b3e77e81098cc8e3786d 100644 +index 20a6e465457151b02daa22e6bc059cf0e117ece5..ef4b1f737aaa683bc13c447aa4ca77e5cf0d64d7 100644 --- a/Source/WebKit/Shared/WebMouseEvent.h +++ b/Source/WebKit/Shared/WebMouseEvent.h @@ -70,6 +70,7 @@ public: @@ -9702,10 +9673,10 @@ index fd4722dd38df74f259d8add02025549022a0a205..e1c33f6d766707170935b3e77e81098c void setPosition(const WebCore::IntPoint& position) { m_position = position; } const WebCore::IntPoint& globalPosition() const { return m_globalPosition; } diff --git a/Source/WebKit/Shared/WebPageCreationParameters.h b/Source/WebKit/Shared/WebPageCreationParameters.h -index 95aa13fdc5bd9d5ece8f3ff4279c0ebe03f3cdef..bde8a24fcce390b859f6241c9816eb6430376d6b 100644 +index 40b760ec89270bb9a8c586d05cab205c8bd9e7cf..db59f464cc96f9db4800034509e048a1f4d237ac 100644 --- a/Source/WebKit/Shared/WebPageCreationParameters.h +++ b/Source/WebKit/Shared/WebPageCreationParameters.h -@@ -309,6 +309,8 @@ struct WebPageCreationParameters { +@@ -301,6 +301,8 @@ struct WebPageCreationParameters { WebCore::ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking { WebCore::ShouldRelaxThirdPartyCookieBlocking::No }; bool httpsUpgradeEnabled { true }; @@ -9715,10 +9686,10 @@ index 95aa13fdc5bd9d5ece8f3ff4279c0ebe03f3cdef..bde8a24fcce390b859f6241c9816eb64 #if ENABLE(APP_HIGHLIGHTS) WebCore::HighlightVisibility appHighlightsVisible { WebCore::HighlightVisibility::Hidden }; diff --git a/Source/WebKit/Shared/WebPageCreationParameters.serialization.in b/Source/WebKit/Shared/WebPageCreationParameters.serialization.in -index 0c9594c44a6f1962642835b221b1c27d15cc33d7..b6898b100be0fe64aba87305711846f066bf9459 100644 +index d137231cfb0f9d9632e788f8e08968ad514f2227..6647f08415b1f0b213e4162ca93697e39280c888 100644 --- a/Source/WebKit/Shared/WebPageCreationParameters.serialization.in +++ b/Source/WebKit/Shared/WebPageCreationParameters.serialization.in -@@ -229,6 +229,8 @@ enum class WebCore::UserInterfaceLayoutDirection : bool; +@@ -223,6 +223,8 @@ enum class WebCore::UserInterfaceLayoutDirection : bool; bool httpsUpgradeEnabled; @@ -9728,7 +9699,7 @@ index 0c9594c44a6f1962642835b221b1c27d15cc33d7..b6898b100be0fe64aba87305711846f0 WebCore::HighlightVisibility appHighlightsVisible; #endif diff --git a/Source/WebKit/Shared/glib/ProcessExecutablePathGLib.cpp b/Source/WebKit/Shared/glib/ProcessExecutablePathGLib.cpp -index 40d482a1860598391363027e63b010b3ce32b014..e654ae5e7c54b788badd463b1f2b653160d0370e 100644 +index 9899d60864664d1abff2b71c1c01e564e5dfb08c..391e0e42ca6a39f82b5a12c6aede069d61095ee2 100644 --- a/Source/WebKit/Shared/glib/ProcessExecutablePathGLib.cpp +++ b/Source/WebKit/Shared/glib/ProcessExecutablePathGLib.cpp @@ -32,7 +32,7 @@ @@ -9865,24 +9836,11 @@ index 053e9336017d8818b3cbea79ce7c145fd5c46274..5632498d6ef875df80fc68ec206a9d08 else if (!strcmp(argv[i], "-configure-jsc-for-testing")) JSC::Config::configureForTesting(); else if (!strcmp(argv[i], "-disable-jit")) -diff --git a/Source/WebKit/Shared/win/WebEventFactory.cpp b/Source/WebKit/Shared/win/WebEventFactory.cpp -index 23ec85c142892f8f9bee7d063c3ff0cafe6c53a5..4f0021ad12ded82b7ac958fc6c752410ce2ac764 100644 ---- a/Source/WebKit/Shared/win/WebEventFactory.cpp -+++ b/Source/WebKit/Shared/win/WebEventFactory.cpp -@@ -484,7 +484,7 @@ WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message - #if ENABLE(TOUCH_EVENTS) - WebTouchEvent WebEventFactory::createWebTouchEvent() - { -- return WebTouchEvent(); -+ return WebTouchEvent({ WebEventType::TouchMove, OptionSet { }, WallTime::now()}, { }, { }, { }); - } - #endif // ENABLE(TOUCH_EVENTS) - diff --git a/Source/WebKit/Sources.txt b/Source/WebKit/Sources.txt -index 1a8bb4ce0d39f7d03264bb010be3158f3bd87000..34e2d4d603fae0acc8ace9afb8e3ff549e87bf07 100644 +index 8580135604e6c41c0d2adc22b37ce50366069788..dcbca0904ab9acff6bfcc5cffd6f821af682ecb5 100644 --- a/Source/WebKit/Sources.txt +++ b/Source/WebKit/Sources.txt -@@ -389,6 +389,7 @@ Shared/XR/XRDeviceProxy.cpp +@@ -391,6 +391,7 @@ UIProcess/AboutSchemeHandler.cpp UIProcess/AuxiliaryProcessProxy.cpp UIProcess/BackgroundProcessResponsivenessTimer.cpp UIProcess/BrowsingContextGroup.cpp @@ -9890,7 +9848,7 @@ index 1a8bb4ce0d39f7d03264bb010be3158f3bd87000..34e2d4d603fae0acc8ace9afb8e3ff54 UIProcess/DeviceIdHashSaltStorage.cpp UIProcess/DisplayLink.cpp UIProcess/DisplayLinkProcessProxyClient.cpp -@@ -398,16 +399,20 @@ UIProcess/FrameLoadState.cpp +@@ -400,16 +401,20 @@ UIProcess/FrameLoadState.cpp UIProcess/FrameProcess.cpp UIProcess/GeolocationPermissionRequestManagerProxy.cpp UIProcess/GeolocationPermissionRequestProxy.cpp @@ -9911,7 +9869,7 @@ index 1a8bb4ce0d39f7d03264bb010be3158f3bd87000..34e2d4d603fae0acc8ace9afb8e3ff54 UIProcess/RemotePageDrawingAreaProxy.cpp UIProcess/RemotePageFullscreenManagerProxy.cpp UIProcess/RemotePageProxy.cpp -@@ -450,6 +455,8 @@ UIProcess/WebOpenPanelResultListenerProxy.cpp +@@ -452,6 +457,8 @@ UIProcess/WebOpenPanelResultListenerProxy.cpp UIProcess/WebPageDiagnosticLoggingClient.cpp UIProcess/WebPageGroup.cpp UIProcess/WebPageInjectedBundleClient.cpp @@ -9920,7 +9878,7 @@ index 1a8bb4ce0d39f7d03264bb010be3158f3bd87000..34e2d4d603fae0acc8ace9afb8e3ff54 UIProcess/WebPageProxy.cpp UIProcess/WebPageProxyMessageReceiverRegistration.cpp UIProcess/WebPageProxyTesting.cpp -@@ -596,7 +603,11 @@ UIProcess/Inspector/WebInspectorUtilities.cpp +@@ -598,7 +605,11 @@ UIProcess/Inspector/WebInspectorUtilities.cpp UIProcess/Inspector/WebPageDebuggable.cpp UIProcess/Inspector/WebPageInspectorController.cpp @@ -9933,10 +9891,10 @@ index 1a8bb4ce0d39f7d03264bb010be3158f3bd87000..34e2d4d603fae0acc8ace9afb8e3ff54 UIProcess/Media/AudioSessionRoutingArbitratorProxy.cpp UIProcess/Media/MediaUsageManager.cpp diff --git a/Source/WebKit/SourcesCocoa.txt b/Source/WebKit/SourcesCocoa.txt -index 19e2664300bd081ae5124d6b08055c028f04ebb2..5b851c95b75262264dec59503b74c2548b9ee737 100644 +index 768e618630ca0090790320007cbcadad8e0d6452..536e02dc95c2adff1be74a1c2116ab8e8e98196c 100644 --- a/Source/WebKit/SourcesCocoa.txt +++ b/Source/WebKit/SourcesCocoa.txt -@@ -270,6 +270,7 @@ UIProcess/API/Cocoa/_WKArchiveExclusionRule.mm +@@ -272,6 +272,7 @@ UIProcess/API/Cocoa/_WKArchiveExclusionRule.mm UIProcess/API/Cocoa/_WKAttachment.mm UIProcess/API/Cocoa/_WKAutomationSession.mm UIProcess/API/Cocoa/_WKAutomationSessionConfiguration.mm @@ -9944,7 +9902,7 @@ index 19e2664300bd081ae5124d6b08055c028f04ebb2..5b851c95b75262264dec59503b74c254 UIProcess/API/Cocoa/_WKContentRuleListAction.mm UIProcess/API/Cocoa/_WKContextMenuElementInfo.mm UIProcess/API/Cocoa/_WKCustomHeaderFields.mm @no-unify -@@ -467,6 +468,7 @@ UIProcess/Inspector/ios/WKInspectorHighlightView.mm +@@ -468,6 +469,7 @@ UIProcess/Inspector/ios/WKInspectorHighlightView.mm UIProcess/Inspector/ios/WKInspectorNodeSearchGestureRecognizer.mm UIProcess/Inspector/mac/RemoteWebInspectorUIProxyMac.mm @@ -10042,7 +10000,7 @@ index 42931a643d8ff85469b5e162a42e9a09a8bdca3f..f962949675b8de7b79e6d56bb8bb3f34 WebProcess/WebPage/AcceleratedSurface.cpp diff --git a/Source/WebKit/UIProcess/API/APIPageConfiguration.cpp b/Source/WebKit/UIProcess/API/APIPageConfiguration.cpp -index 0f27d0e97c1039ad5c95086c0e7eb72f4fe85db1..9ca848d9e0c78bce8b77721debf18deba76f4b2c 100644 +index a9d9e1ecd35d4f3b55fa27ff913d054513d423a6..d7f083485598a5b458c09c278a7c0181272d0e93 100644 --- a/Source/WebKit/UIProcess/API/APIPageConfiguration.cpp +++ b/Source/WebKit/UIProcess/API/APIPageConfiguration.cpp @@ -275,6 +275,11 @@ WebPageProxy* PageConfiguration::relatedPage() const @@ -10058,7 +10016,7 @@ index 0f27d0e97c1039ad5c95086c0e7eb72f4fe85db1..9ca848d9e0c78bce8b77721debf18deb { return m_data.pageToCloneSessionStorageFrom.get(); diff --git a/Source/WebKit/UIProcess/API/APIPageConfiguration.h b/Source/WebKit/UIProcess/API/APIPageConfiguration.h -index ba3fed377d5f2a799fbf7b38a5bc2f03efbe7535..e4c41d0a444d12f80d0402591dfa39e8e68e4fd0 100644 +index ceb054aec4899fa74d160c08e1009dba052488e7..e4c817a32510d006b95d0039d9dc763351831978 100644 --- a/Source/WebKit/UIProcess/API/APIPageConfiguration.h +++ b/Source/WebKit/UIProcess/API/APIPageConfiguration.h @@ -160,6 +160,10 @@ public: @@ -10096,7 +10054,7 @@ index e256b905bf9727aa7c8a48012237a6a6bc9acdbc..4e855c441af6f235f0fd8dfdd57b9bd6 copy->m_shouldTakeUIBackgroundAssertion = this->m_shouldTakeUIBackgroundAssertion; copy->m_shouldCaptureDisplayInUIProcess = this->m_shouldCaptureDisplayInUIProcess; diff --git a/Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.h b/Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.h -index d2e8261c6db000aa41697b3bb50e2bea149edcdf..500c38b81d6804f64ff891e32b2c72ed98c9a629 100644 +index af4944e5a5d373bc51995b50d1ea7c70f64ef1b3..403cfdeda26db2b648e32aa0e5f3ef6e076634f6 100644 --- a/Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.h +++ b/Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.h @@ -96,6 +96,16 @@ public: @@ -10128,10 +10086,10 @@ index d2e8261c6db000aa41697b3bb50e2bea149edcdf..500c38b81d6804f64ff891e32b2c72ed bool m_shouldTakeUIBackgroundAssertion { true }; bool m_shouldCaptureDisplayInUIProcess { DEFAULT_CAPTURE_DISPLAY_IN_UI_PROCESS }; diff --git a/Source/WebKit/UIProcess/API/APIUIClient.h b/Source/WebKit/UIProcess/API/APIUIClient.h -index fdfb6489e3b5f8b70da8918e72612c74ada771f6..fe58191302f84065523d048463b0b6b2fe71c394 100644 +index 4d3564df93fcad126ddd9b3ca851618976cd7571..826aad7a5675b4daf90d83d36a6f352751932f03 100644 --- a/Source/WebKit/UIProcess/API/APIUIClient.h +++ b/Source/WebKit/UIProcess/API/APIUIClient.h -@@ -115,6 +115,7 @@ public: +@@ -114,6 +114,7 @@ public: virtual void runJavaScriptAlert(WebKit::WebPageProxy&, const WTF::String&, WebKit::WebFrameProxy*, WebKit::FrameInfoData&&, Function&& completionHandler) { completionHandler(); } virtual void runJavaScriptConfirm(WebKit::WebPageProxy&, const WTF::String&, WebKit::WebFrameProxy*, WebKit::FrameInfoData&&, Function&& completionHandler) { completionHandler(false); } virtual void runJavaScriptPrompt(WebKit::WebPageProxy&, const WTF::String&, const WTF::String&, WebKit::WebFrameProxy*, WebKit::FrameInfoData&&, Function&& completionHandler) { completionHandler(WTF::String()); } @@ -10183,11 +10141,11 @@ index 026121d114c5fcad84c1396be8d692625beaa3bd..edd6e5cae033124c589959a42522fde0 } #endif diff --git a/Source/WebKit/UIProcess/API/C/WKPage.cpp b/Source/WebKit/UIProcess/API/C/WKPage.cpp -index 2e9502e5767eb93b7a2d6488a113e69f953f3e4a..56969ab93991ef77dc020e0d3f7b7e4e71324416 100644 +index 55aeb95735a56d285d7faeb21e9cec6e4ce6762e..9ecc7e8b24a3550659e62e01e704ea0064a985aa 100644 --- a/Source/WebKit/UIProcess/API/C/WKPage.cpp +++ b/Source/WebKit/UIProcess/API/C/WKPage.cpp -@@ -1899,6 +1899,13 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient - completionHandler(String()); +@@ -1935,6 +1935,13 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient + m_client.addMessageToConsole(toAPI(&page), toAPI(message.impl()), m_client.base.clientInfo); } + void handleJavaScriptDialog(WebPageProxy& page, bool accept, const String& value) final { @@ -10200,7 +10158,7 @@ index 2e9502e5767eb93b7a2d6488a113e69f953f3e4a..56969ab93991ef77dc020e0d3f7b7e4e void setStatusText(WebPageProxy* page, const String& text) final { if (!m_client.setStatusText) -@@ -1928,6 +1935,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient +@@ -1964,6 +1971,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient { if (!m_client.didNotHandleKeyEvent) return; @@ -10210,7 +10168,7 @@ index 2e9502e5767eb93b7a2d6488a113e69f953f3e4a..56969ab93991ef77dc020e0d3f7b7e4e } diff --git a/Source/WebKit/UIProcess/API/C/WKPageUIClient.h b/Source/WebKit/UIProcess/API/C/WKPageUIClient.h -index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4ae00b7497 100644 +index fc43c44a85a0fc6bf5f8c643bd120a16ce762914..ee86fd213d25682f9b6553ec7da99bc8a812212b 100644 --- a/Source/WebKit/UIProcess/API/C/WKPageUIClient.h +++ b/Source/WebKit/UIProcess/API/C/WKPageUIClient.h @@ -98,6 +98,7 @@ typedef void (*WKPageRunBeforeUnloadConfirmPanelCallback)(WKPageRef page, WKStri @@ -10221,7 +10179,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a typedef void (*WKPageRequestStorageAccessConfirmCallback)(WKPageRef page, WKFrameRef frame, WKStringRef requestingDomain, WKStringRef currentDomain, WKPageRequestStorageAccessConfirmResultListenerRef listener, const void *clientInfo); typedef void (*WKPageTakeFocusCallback)(WKPageRef page, WKFocusDirection direction, const void *clientInfo); typedef void (*WKPageFocusCallback)(WKPageRef page, const void *clientInfo); -@@ -1364,6 +1365,7 @@ typedef struct WKPageUIClientV14 { +@@ -1365,6 +1366,7 @@ typedef struct WKPageUIClientV14 { // Version 14. WKPageRunWebAuthenticationPanelCallback runWebAuthenticationPanel; @@ -10229,7 +10187,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a } WKPageUIClientV14; typedef struct WKPageUIClientV15 { -@@ -1471,6 +1473,7 @@ typedef struct WKPageUIClientV15 { +@@ -1472,6 +1474,7 @@ typedef struct WKPageUIClientV15 { // Version 14. WKPageRunWebAuthenticationPanelCallback runWebAuthenticationPanel; @@ -10237,7 +10195,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a // Version 15. WKPageDecidePolicyForSpeechRecognitionPermissionRequestCallback decidePolicyForSpeechRecognitionPermissionRequest; -@@ -1582,6 +1585,7 @@ typedef struct WKPageUIClientV16 { +@@ -1583,6 +1586,7 @@ typedef struct WKPageUIClientV16 { // Version 14. WKPageRunWebAuthenticationPanelCallback runWebAuthenticationPanel; @@ -10245,7 +10203,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a // Version 15. WKPageDecidePolicyForSpeechRecognitionPermissionRequestCallback decidePolicyForSpeechRecognitionPermissionRequest; -@@ -1696,6 +1700,7 @@ typedef struct WKPageUIClientV17 { +@@ -1697,6 +1701,7 @@ typedef struct WKPageUIClientV17 { // Version 14. WKPageRunWebAuthenticationPanelCallback runWebAuthenticationPanel; @@ -10253,7 +10211,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a // Version 15. WKPageDecidePolicyForSpeechRecognitionPermissionRequestCallback decidePolicyForSpeechRecognitionPermissionRequest; -@@ -1810,6 +1815,7 @@ typedef struct WKPageUIClientV18 { +@@ -1811,6 +1816,7 @@ typedef struct WKPageUIClientV18 { // Version 14. WKPageRunWebAuthenticationPanelCallback runWebAuthenticationPanel; @@ -10261,7 +10219,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a // Version 15. WKPageDecidePolicyForSpeechRecognitionPermissionRequestCallback decidePolicyForSpeechRecognitionPermissionRequest; -@@ -1926,6 +1932,7 @@ typedef struct WKPageUIClientV19 { +@@ -1927,6 +1933,7 @@ typedef struct WKPageUIClientV19 { // Version 14. WKPageRunWebAuthenticationPanelCallback runWebAuthenticationPanel; @@ -10270,7 +10228,7 @@ index 1484f064ec89ee8c25c35df9f0a4462896699415..0622f4d5fc9144b9059395d9d0730a4a // Version 15. WKPageDecidePolicyForSpeechRecognitionPermissionRequestCallback decidePolicyForSpeechRecognitionPermissionRequest; diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm b/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm -index 53350803b70ce91ba7d326b94435f7cbca375afa..4595ba4208f1a2e7864b2b0ed6e66db42c2d00a6 100644 +index 3298711f75bd5be68290738a21de99c927e2d925..6943df0b1f4ee684db0b52f336b5d7f8922c2707 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm @@ -712,6 +712,16 @@ - (void)_setMediaCaptureRequiresSecureConnection:(BOOL)requiresSecureConnection @@ -10291,7 +10249,7 @@ index 53350803b70ce91ba7d326b94435f7cbca375afa..4595ba4208f1a2e7864b2b0ed6e66db4 { return _preferences->inactiveMediaCaptureStreamRepromptIntervalInMinutes(); diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h -index 34d46d52f796ed9b06fafabd03864229cdd307ab..a74a1ea9f9a55421bd569d958d7c0baa8618ca6d 100644 +index 41525c8980f698aa4326e7d4d232311cee2c43d5..1f1745431c6cc7af66b47dd5d015c52389626e6f 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h @@ -119,6 +119,7 @@ typedef NS_ENUM(NSInteger, _WKPitchCorrectionAlgorithm) { @@ -10303,7 +10261,7 @@ index 34d46d52f796ed9b06fafabd03864229cdd307ab..a74a1ea9f9a55421bd569d958d7c0baa @property (nonatomic, setter=_setICECandidateFilteringEnabled:) BOOL _iceCandidateFilteringEnabled WK_API_AVAILABLE(macos(10.13.4), ios(11.3)); @property (nonatomic, setter=_setInactiveMediaCaptureStreamRepromptIntervalInMinutes:) double _inactiveMediaCaptureStreamRepromptIntervalInMinutes WK_API_AVAILABLE(macos(10.13.4), ios(11.3)); diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegate.h b/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegate.h -index ef512c7ba09298b2291d0248988574163ff2e7c8..d42e7f9f7b66840b68e61ce46b4bbd6254518ef7 100644 +index 5f4e98c6ab6bb3cce607b96600cac66c70753224..1c2af77331d0da8d9ba232ba508b96e4f7276fe8 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegate.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegate.h @@ -149,6 +149,12 @@ WK_SWIFT_UI_ACTOR @@ -10320,10 +10278,10 @@ index ef512c7ba09298b2291d0248988574163ff2e7c8..d42e7f9f7b66840b68e61ce46b4bbd62 /*! @abstract A delegate to request permission for microphone audio and camera video access. @param webView The web view invoking the delegate method. diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.h b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.h -index eff4cf557033561ab20762d93a58c2d71f5505f0..9418e2c51e50190dee2b5d7c854eacea8c5a4b76 100644 +index 930357ac3469195e9f33d5ffce92777018bb0b13..f62555ec562f8416976d31692e8fb1751a04d458 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.h -@@ -126,6 +126,8 @@ WK_CLASS_AVAILABLE(macos(10.11), ios(9.0)) +@@ -138,6 +138,8 @@ WK_CLASS_AVAILABLE(macos(10.11), ios(9.0)) #endif #endif @@ -10333,7 +10291,7 @@ index eff4cf557033561ab20762d93a58c2d71f5505f0..9418e2c51e50190dee2b5d7c854eacea NS_ASSUME_NONNULL_END diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm -index 97a87094e648a15e2501fb39a46bf0fbb4cb50d9..b08c212a0372175859645f7dba0c704fe865451c 100644 +index 5b6b451a4ab86b3c280c107e5f7bec822155ccde..99eeb8633ab25d8f4f174440e328ba5287e3a2b8 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm @@ -55,6 +55,7 @@ @@ -10344,7 +10302,7 @@ index 97a87094e648a15e2501fb39a46bf0fbb4cb50d9..b08c212a0372175859645f7dba0c704f #import #import #import -@@ -552,6 +553,11 @@ - (void)removeDataOfTypes:(NSSet *)dataTypes modifiedSince:(NSDate *)date comple +@@ -523,6 +524,11 @@ - (void)removeDataOfTypes:(NSSet *)dataTypes modifiedSince:(NSDate *)date comple }); } @@ -10496,7 +10454,7 @@ index 426c7cbc897e22fd2e962dd3744959975d6ae6a0..f7a52359d7d42f970ef424b6c702b0ec @property (nonatomic) BOOL processSwapsOnNavigationWithinSameNonHTTPFamilyProtocol WK_API_AVAILABLE(macos(12.0), ios(15.0)); @property (nonatomic) BOOL prewarmsProcessesAutomatically WK_API_AVAILABLE(macos(10.14.4), ios(12.2)); diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm -index 38b516d32edc4038508c664e0c4dc804cd477787..891dd8b2451ab771e451ea8ef74906d364e76007 100644 +index 7ccfd9a46cd024c8f6644594b9d0cde8ae7e60db..921ec683dce77583f24d27a0b1b6f478242dfb42 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm @@ -241,6 +241,16 @@ - (BOOL)processSwapsOnNavigation @@ -10529,7 +10487,7 @@ index 4974e14214e2bb3e982325b885bab33e54f83998..cacdf8c71fab248d38d2faf03f7affdc typedef NS_ENUM(NSInteger, _WKUserStyleLevel) { _WKUserStyleUserLevel, diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKWebPushSubscriptionData.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKWebPushSubscriptionData.mm -index 0a78daba00961bf10564a0b53067960b71d8b61d..696408b77c82e205a134edf2ee17ff971af226f3 100644 +index d139d35f09a999e2c85f48799a399d791d421ffd..f79c073291c6071c566220a40f7dd4bbc57192a1 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/_WKWebPushSubscriptionData.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/_WKWebPushSubscriptionData.mm @@ -28,6 +28,9 @@ @@ -10745,7 +10703,7 @@ index 0000000000000000000000000000000000000000..e0b1da48465c850f541532ed961d1b77 +WebKit::WebPageProxy* webkitBrowserInspectorCreateNewPageInContext(WebKitWebContext*); +void webkitBrowserInspectorQuitApplication(); diff --git a/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp b/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp -index 9b6d8db85df969e609de036fac67374226ecd48e..2b293d5f24e562f06859432580f2cdfe40f8b9a6 100644 +index 1080e5d6a44b4d7ec649913c39af89aa702d2ee4..2ef7037a1778982275f4b1a5190a089295c8e59c 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp @@ -101,6 +101,10 @@ private: @@ -10829,10 +10787,10 @@ index c1945fbe717a42afc1f51d64a80c7de3fa9009ba..ab63fe19b00ecbd64c9421e6eecad3e2 #endif +int webkitWebContextExistingCount(); diff --git a/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp b/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp -index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d3cd02c51 100644 +index df1381b577be94114401e1faaf1979183d82614f..7739fac45b41e07d39a6ac1568641b44f67d5f56 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp -@@ -37,6 +37,7 @@ +@@ -39,6 +39,7 @@ #include "WebContextMenuItem.h" #include "WebContextMenuItemData.h" #include "WebFrameProxy.h" @@ -10840,7 +10798,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d #include "WebKitAuthenticationRequestPrivate.h" #include "WebKitBackForwardListPrivate.h" #include "WebKitContextMenuClient.h" -@@ -150,6 +151,7 @@ enum { +@@ -152,6 +153,7 @@ enum { CLOSE, SCRIPT_DIALOG, @@ -10848,7 +10806,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d DECIDE_POLICY, PERMISSION_REQUEST, -@@ -518,6 +520,16 @@ GRefPtr WebKitWebViewClient::showOptionMenu(WebKitPopupMenu& p +@@ -520,6 +522,16 @@ GRefPtr WebKitWebViewClient::showOptionMenu(WebKitPopupMenu& p void WebKitWebViewClient::frameDisplayed(WKWPE::View&) { @@ -10865,7 +10823,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d { SetForScope inFrameDisplayedGuard(m_webView->priv->inFrameDisplayed, true); for (const auto& callback : m_webView->priv->frameDisplayedCallbacks) { -@@ -534,6 +546,18 @@ void WebKitWebViewClient::frameDisplayed(WKWPE::View&) +@@ -536,6 +548,18 @@ void WebKitWebViewClient::frameDisplayed(WKWPE::View&) } } @@ -10884,7 +10842,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d void WebKitWebViewClient::willStartLoad(WKWPE::View&) { webkitWebViewWillStartLoad(m_webView); -@@ -620,7 +644,7 @@ static gboolean webkitWebViewDecidePolicy(WebKitWebView*, WebKitPolicyDecision* +@@ -622,7 +646,7 @@ static gboolean webkitWebViewDecidePolicy(WebKitWebView*, WebKitPolicyDecision* static gboolean webkitWebViewPermissionRequest(WebKitWebView*, WebKitPermissionRequest* request) { @@ -10893,7 +10851,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d if (WEBKIT_IS_POINTER_LOCK_PERMISSION_REQUEST(request)) { webkit_permission_request_allow(request); return TRUE; -@@ -943,6 +967,10 @@ static void webkitWebViewConstructed(GObject* object) +@@ -945,6 +969,10 @@ static void webkitWebViewConstructed(GObject* object) priv->websitePolicies = adoptGRef(webkit_website_policies_new()); Ref configuration = priv->relatedView && priv->relatedView->priv->configurationForNextRelatedView ? priv->relatedView->priv->configurationForNextRelatedView.releaseNonNull() : webkitWebViewCreatePageConfiguration(webView); @@ -10904,7 +10862,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d webkitWebViewCreatePage(webView, WTFMove(configuration)); webkitWebContextWebViewCreated(priv->context.get(), webView); -@@ -1982,6 +2010,15 @@ static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass) +@@ -1984,6 +2012,15 @@ static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass) G_TYPE_BOOLEAN, 1, WEBKIT_TYPE_SCRIPT_DIALOG); @@ -10920,7 +10878,7 @@ index 7d8de31fc7e373871e89ab41fc8f3a63fceea1c6..7d2785ed1798bd661d0f98e5ab68037d /** * WebKitWebView::decide-policy: * @web_view: the #WebKitWebView on which the signal is emitted -@@ -2767,6 +2804,23 @@ void webkitWebViewRunJavaScriptBeforeUnloadConfirm(WebKitWebView* webView, const +@@ -2769,6 +2806,23 @@ void webkitWebViewRunJavaScriptBeforeUnloadConfirm(WebKitWebView* webView, const webkit_script_dialog_unref(webView->priv->currentScriptDialog); } @@ -11107,7 +11065,7 @@ index 496079da90993ac37689b060b69ecd4a67c2b6a8..af30181ca922f16c0f6e245c70e5ce7d G_BEGIN_DECLS diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp -index 597dcdbd94e8899f60fc9a0566698fd142170926..8d7f77586e25d299c3043128536fd3801eccde4a 100644 +index 2add5ab84105ad5e79635524b385fb5cb1de1a1b..0585e8474fc8cf42db262be75b514ea68fd5274b 100644 --- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp +++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp @@ -2873,6 +2873,11 @@ void webkitWebViewBaseResetClickCounter(WebKitWebViewBase* webkitWebViewBase) @@ -11196,7 +11154,7 @@ index 7636ad733e7be66a74f8fede966b0acb905a5842..cf54287353d1e529c6765e3caf8d283a virtual void didChangePageID(WKWPE::View&) { } virtual void didReceiveUserMessage(WKWPE::View&, WebKit::UserMessage&&, CompletionHandler&& completionHandler) { completionHandler(WebKit::UserMessage()); } diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp -index 76f77a9a50f721e2d71def6ca5c81ad68274653a..c9b583bd70d87586fd7ea727c20ada6f01dffb72 100644 +index aec5bdaa6d2ceea13ad61e3996fd69f9f6a2dc55..ca51881ce7783979f9d36035093b571db93a9253 100644 --- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp +++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp @@ -35,9 +35,12 @@ @@ -11244,7 +11202,7 @@ index 76f77a9a50f721e2d71def6ca5c81ad68274653a..c9b583bd70d87586fd7ea727c20ada6f } RefPtr PageClientImpl::createDateTimePicker(WebPageProxy& page) -@@ -518,6 +527,64 @@ void PageClientImpl::selectionDidChange() +@@ -523,6 +532,64 @@ void PageClientImpl::selectionDidChange() m_view.selectionDidChange(); } @@ -11309,7 +11267,7 @@ index 76f77a9a50f721e2d71def6ca5c81ad68274653a..c9b583bd70d87586fd7ea727c20ada6f WebKitWebResourceLoadManager* PageClientImpl::webResourceLoadManager() { return m_view.webResourceLoadManager(); -@@ -528,4 +595,11 @@ void PageClientImpl::callAfterNextPresentationUpdate(CompletionHandler&& +@@ -533,4 +600,11 @@ void PageClientImpl::callAfterNextPresentationUpdate(CompletionHandler&& m_view.callAfterNextPresentationUpdate(WTFMove(callback)); } @@ -11525,19 +11483,20 @@ index 2f1182cb91a00353eace0b71612df096391c2450..d71d7fc724b046fab41285bb8f390cb6 void didChangePageID(WKWPE::View&) override; void didReceiveUserMessage(WKWPE::View&, WebKit::UserMessage&&, CompletionHandler&&) override; diff --git a/Source/WebKit/UIProcess/Automation/WebAutomationSession.h b/Source/WebKit/UIProcess/Automation/WebAutomationSession.h -index 8c97bd6e955005ba6246715aac63c4d675f5a713..1440dd5233d20ada05fee01a1916ad784a745214 100644 +index 6ae7054c30026bbaca91936c46ffff0760039d5a..5e97a1247dc43118c901b548f8dafa377e3f98ae 100644 --- a/Source/WebKit/UIProcess/Automation/WebAutomationSession.h +++ b/Source/WebKit/UIProcess/Automation/WebAutomationSession.h -@@ -279,6 +279,8 @@ public: +@@ -285,6 +285,9 @@ public: void didDestroyFrame(WebCore::FrameIdentifier); + static std::optional platformGetBase64EncodedPNGData(const ViewSnapshot&); + - private: ++private: RefPtr webPageProxyForHandle(const String&); String handleForWebPageProxy(const WebPageProxy&); -@@ -329,7 +331,6 @@ private: + +@@ -336,7 +339,6 @@ private: // Get base64-encoded PNG data from a bitmap. static std::optional platformGetBase64EncodedPNGData(WebCore::ShareableBitmap::Handle&&); @@ -11546,7 +11505,7 @@ index 8c97bd6e955005ba6246715aac63c4d675f5a713..1440dd5233d20ada05fee01a1916ad78 // Save base64-encoded file contents to a local file path and return the path. // This reuses the basename of the remote file path so that the filename exposed to DOM API remains the same. diff --git a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp -index 601d3874b710ca87c18a2dbcfbfb7368ac61d602..26c9ae063679b6f97be8c6cce8432aad3e90b3a4 100644 +index a4d1f15cd80b95ec8c82ac07cac3b8dfdae5916c..5182ffa031fd7278ea3bce5bb56e0a648f9223e2 100644 --- a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp +++ b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp @@ -172,7 +172,11 @@ void AuxiliaryProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& lau @@ -11562,7 +11521,7 @@ index 601d3874b710ca87c18a2dbcfbfb7368ac61d602..26c9ae063679b6f97be8c6cce8432aad platformGetLaunchOptions(launchOptions); } diff --git a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h -index eed9d40f00e29fd1e36a2c7893639544439e53ce..eeb0b85018d13f46279eb5b9d281fdc34de92e02 100644 +index 1bca45f83bccfd8f917fc8a49b39414d9a8b6548..bcf8c5e5acb0652d04201aa8a8a840537e17e66f 100644 --- a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h +++ b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h @@ -296,13 +296,16 @@ protected: @@ -11584,14 +11543,18 @@ index eed9d40f00e29fd1e36a2c7893639544439e53ce..eeb0b85018d13f46279eb5b9d281fdc3 // Connection::Client diff --git a/Source/WebKit/UIProcess/BackingStore.h b/Source/WebKit/UIProcess/BackingStore.h -index 945c62704e0b25f04e9ee4be88b21f88aeda8bd9..409c1c560b2462bf59f19dfee7941748b54fd22c 100644 +index 945c62704e0b25f04e9ee4be88b21f88aeda8bd9..ff9a8ee47e2669260743ae2174801b1cc3c4c413 100644 --- a/Source/WebKit/UIProcess/BackingStore.h +++ b/Source/WebKit/UIProcess/BackingStore.h -@@ -67,6 +67,7 @@ public: +@@ -67,6 +67,11 @@ public: float deviceScaleFactor() const { return m_deviceScaleFactor; } void paint(PlatformPaintContextPtr, const WebCore::IntRect&); ++#if PLATFORM(GTK) || USE(CAIRO) + RefPtr surface() const { return m_surface; } ++#elif USE(SKIA) ++ sk_sp surface() const { return m_surface; } ++#endif void incorporateUpdate(UpdateInfo&&); private: @@ -11719,10 +11682,10 @@ index 89d125f7742f81ead8c50f218ecb1771b8000636..baa6cf58ad502c6c033ee6293a6cc8d4 namespace WebKit { diff --git a/Source/WebKit/UIProcess/Cocoa/UIDelegate.h b/Source/WebKit/UIProcess/Cocoa/UIDelegate.h -index 8d9915d0802f215ea2a3e298b8653636338250b9..50e0ad091bcfc6356708ccfaf242787188e49a82 100644 +index 505b934da3a86e218d11e2db1406b0a0a4c7ec36..2e800a8f78fb6209de7f2950fbf84ae062633a4d 100644 --- a/Source/WebKit/UIProcess/Cocoa/UIDelegate.h +++ b/Source/WebKit/UIProcess/Cocoa/UIDelegate.h -@@ -102,6 +102,7 @@ private: +@@ -103,6 +103,7 @@ private: void runJavaScriptAlert(WebPageProxy&, const WTF::String&, WebFrameProxy*, FrameInfoData&&, Function&& completionHandler) final; void runJavaScriptConfirm(WebPageProxy&, const WTF::String&, WebFrameProxy*, FrameInfoData&&, Function&& completionHandler) final; void runJavaScriptPrompt(WebPageProxy&, const WTF::String&, const WTF::String&, WebFrameProxy*, FrameInfoData&&, Function&&) final; @@ -11730,7 +11693,7 @@ index 8d9915d0802f215ea2a3e298b8653636338250b9..50e0ad091bcfc6356708ccfaf2427871 void presentStorageAccessConfirmDialog(const WTF::String& requestingDomain, const WTF::String& currentDomain, CompletionHandler&&); void requestStorageAccessConfirm(WebPageProxy&, WebFrameProxy*, const WebCore::RegistrableDomain& requestingDomain, const WebCore::RegistrableDomain& currentDomain, std::optional&&, CompletionHandler&&) final; void decidePolicyForGeolocationPermissionRequest(WebPageProxy&, WebFrameProxy&, const FrameInfoData&, Function&) final; -@@ -220,6 +221,7 @@ private: +@@ -221,6 +222,7 @@ private: bool webViewRunJavaScriptAlertPanelWithMessageInitiatedByFrameCompletionHandler : 1; bool webViewRunJavaScriptConfirmPanelWithMessageInitiatedByFrameCompletionHandler : 1; bool webViewRunJavaScriptTextInputPanelWithPromptDefaultTextInitiatedByFrameCompletionHandler : 1; @@ -11739,10 +11702,10 @@ index 8d9915d0802f215ea2a3e298b8653636338250b9..50e0ad091bcfc6356708ccfaf2427871 bool webViewRequestStorageAccessPanelForDomainUnderCurrentDomainForQuirkDomainsCompletionHandler : 1; bool webViewRunBeforeUnloadConfirmPanelWithMessageInitiatedByFrameCompletionHandler : 1; diff --git a/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm b/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm -index da5818f79cb0f7a384af46f4d39eae4d2cf573df..44bf65e9a20428656d7eac39b9470c72e97987cf 100644 +index 54b225795a14877140396f612330d5212bbf5fea..9c006877aa2a9d4804c71ad34766750fe74539b9 100644 --- a/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm +++ b/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm -@@ -135,6 +135,7 @@ void UIDelegate::setDelegate(id delegate) +@@ -134,6 +134,7 @@ void UIDelegate::setDelegate(id delegate) m_delegateMethods.webViewRunJavaScriptAlertPanelWithMessageInitiatedByFrameCompletionHandler = [delegate respondsToSelector:@selector(webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:)]; m_delegateMethods.webViewRunJavaScriptConfirmPanelWithMessageInitiatedByFrameCompletionHandler = [delegate respondsToSelector:@selector(webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:)]; m_delegateMethods.webViewRunJavaScriptTextInputPanelWithPromptDefaultTextInitiatedByFrameCompletionHandler = [delegate respondsToSelector:@selector(webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler:)]; @@ -11750,7 +11713,7 @@ index da5818f79cb0f7a384af46f4d39eae4d2cf573df..44bf65e9a20428656d7eac39b9470c72 m_delegateMethods.webViewRequestStorageAccessPanelUnderFirstPartyCompletionHandler = [delegate respondsToSelector:@selector(_webView:requestStorageAccessPanelForDomain:underCurrentDomain:completionHandler:)]; m_delegateMethods.webViewRequestStorageAccessPanelForDomainUnderCurrentDomainForQuirkDomainsCompletionHandler = [delegate respondsToSelector:@selector(_webView:requestStorageAccessPanelForDomain:underCurrentDomain:forQuirkDomains:completionHandler:)]; m_delegateMethods.webViewRunBeforeUnloadConfirmPanelWithMessageInitiatedByFrameCompletionHandler = [delegate respondsToSelector:@selector(_webView:runBeforeUnloadConfirmPanelWithMessage:initiatedByFrame:completionHandler:)]; -@@ -466,6 +467,15 @@ void UIDelegate::UIClient::runJavaScriptPrompt(WebPageProxy& page, const WTF::St +@@ -494,6 +495,15 @@ void UIDelegate::UIClient::runJavaScriptPrompt(WebPageProxy& page, const WTF::St }).get()]; } @@ -11760,14 +11723,14 @@ index da5818f79cb0f7a384af46f4d39eae4d2cf573df..44bf65e9a20428656d7eac39b9470c72 + auto delegate = m_uiDelegate->m_delegate.get(); + if (!delegate) + return; -+ [delegate webView:m_uiDelegate->m_webView.get().get() handleJavaScriptDialog:accept value:value]; ++ [delegate webView:m_uiDelegate->m_webView.get().get() handleJavaScriptDialog:accept value:value.createNSString().get()]; +} + void UIDelegate::UIClient::requestStorageAccessConfirm(WebPageProxy& webPageProxy, WebFrameProxy*, const WebCore::RegistrableDomain& requestingDomain, const WebCore::RegistrableDomain& currentDomain, std::optional&& organizationStorageAccessPromptQuirk, CompletionHandler&& completionHandler) { RefPtr uiDelegate = m_uiDelegate.get(); diff --git a/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm -index c0c9d8098ce76a6c2f7860165f990a854fbcc0e9..082322d49bf84f3b15e66c9cb89125d64f16d80a 100644 +index 1f2f95479d04ad49499db7dae7954c1c54db6d3e..4aee549a4b5a371b35ce012bcdc258147ddeba8c 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm @@ -42,7 +42,9 @@ @@ -11779,13 +11742,13 @@ index c0c9d8098ce76a6c2f7860165f990a854fbcc0e9..082322d49bf84f3b15e66c9cb89125d6 +#import "PasteboardTypes.h" #import "PlatformXRSystem.h" #import "PlaybackSessionManagerProxy.h" - #import "QuickLookThumbnailLoader.h" -@@ -326,11 +328,86 @@ bool WebPageProxy::scrollingUpdatesDisabledForTesting() + #import "RemoteLayerTreeTransaction.h" +@@ -336,11 +338,86 @@ bool WebPageProxy::scrollingUpdatesDisabledForTesting() void WebPageProxy::startDrag(const DragItem& dragItem, ShareableBitmap::Handle&& dragImageHandle) { + if (m_interceptDrags) { -+ NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName: m_overrideDragPasteboardName]; ++ NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName: m_overrideDragPasteboardName.createNSString().get()]; + + m_dragSelectionData = String([pasteboard name]); + if (auto replyID = grantAccessToCurrentPasteboardData(String([pasteboard name]), [] () { })) @@ -11798,7 +11761,7 @@ index c0c9d8098ce76a6c2f7860165f990a854fbcc0e9..082322d49bf84f3b15e66c9cb89125d6 + dragCancelled(); + return; + } -+ NSString *utiType = attachment->utiType(); ++ NSString *utiType = attachment->utiType().createNSString().get(); + if (!utiType.length) { + dragCancelled(); + return; @@ -11806,7 +11769,7 @@ index c0c9d8098ce76a6c2f7860165f990a854fbcc0e9..082322d49bf84f3b15e66c9cb89125d6 + + for (size_t index = 0; index < info.additionalTypesAndData.size(); ++index) { + auto nsData = info.additionalTypesAndData[index].second->createNSData(); -+ [pasteboard setData:nsData.get() forType:info.additionalTypesAndData[index].first]; ++ [pasteboard setData:nsData.get() forType:info.additionalTypesAndData[index].first.createNSString().get()]; + } + } else { + [pasteboard setString:@"" forType:PasteboardTypes::WebDummyPboardType]; @@ -11869,19 +11832,19 @@ index c0c9d8098ce76a6c2f7860165f990a854fbcc0e9..082322d49bf84f3b15e66c9cb89125d6 #if ENABLE(ATTACHMENT_ELEMENT) diff --git a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm -index d14a7dfa79daabce7bd835aa76625937e6107aad..0907c8ac8e9407231df5661180d3766b5c263dfd 100644 +index c20de41306158d48f4a761e93618acad0f2ae130..0bc1c2e90f4c80c9b506de3cfaf17ae06c173406 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm -@@ -432,7 +432,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END +@@ -434,7 +434,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END auto screenProperties = WebCore::collectScreenProperties(); parameters.screenProperties = WTFMove(screenProperties); #if PLATFORM(MAC) - parameters.useOverlayScrollbars = ([NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay); + parameters.useOverlayScrollbars = m_configuration->forceOverlayScrollbars() || ([NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay); #endif - - #if (PLATFORM(IOS) || PLATFORM(VISION)) && HAVE(AGX_COMPILER_SERVICE) -@@ -841,8 +841,8 @@ void WebProcessPool::registerNotificationObservers() + + #if PLATFORM(VISION) +@@ -833,8 +833,8 @@ void WebProcessPool::registerNotificationObservers() }]; m_scrollerStyleNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSPreferredScrollerStyleDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *notification) { @@ -11893,7 +11856,7 @@ index d14a7dfa79daabce7bd835aa76625937e6107aad..0907c8ac8e9407231df5661180d3766b m_activationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidBecomeActiveNotification object:NSApp queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *notification) { diff --git a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp -index 9b45c78e2fd36e825c4e67d8e824771128c8fe15..9f748e729fce8103278b3888552d5c374ed6bc67 100644 +index 183a2dc44f4d2921e68a6ff5fd2fb0fb815753af..a17d0ed7d287694516305ba57a849ad8042e9287 100644 --- a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp +++ b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp @@ -33,6 +33,7 @@ @@ -11915,21 +11878,19 @@ index 9b45c78e2fd36e825c4e67d8e824771128c8fe15..9f748e729fce8103278b3888552d5c37 #include #endif -@@ -49,6 +52,13 @@ +@@ -49,6 +52,11 @@ #include #endif +#if PLATFORM(WIN) -+#include +#include +#include -+#include +#endif + namespace WebKit { using namespace WebCore; -@@ -182,6 +192,11 @@ void DrawingAreaProxyCoordinatedGraphics::deviceScaleFactorDidChange(CompletionH +@@ -182,6 +190,11 @@ void DrawingAreaProxyCoordinatedGraphics::deviceScaleFactorDidChange(CompletionH sendWithAsyncReply(Messages::DrawingArea::SetDeviceScaleFactor(m_webPageProxy->deviceScaleFactor()), WTFMove(completionHandler)); } @@ -11941,38 +11902,20 @@ index 9b45c78e2fd36e825c4e67d8e824771128c8fe15..9f748e729fce8103278b3888552d5c37 void DrawingAreaProxyCoordinatedGraphics::setBackingStoreIsDiscardable(bool isBackingStoreDiscardable) { #if !PLATFORM(WPE) -@@ -243,6 +258,45 @@ void DrawingAreaProxyCoordinatedGraphics::updateAcceleratedCompositingMode(uint6 +@@ -243,6 +256,42 @@ void DrawingAreaProxyCoordinatedGraphics::updateAcceleratedCompositingMode(uint6 updateAcceleratedCompositingMode(layerTreeContext); } -+#if PLATFORM(WIN) -+void DrawingAreaProxyCoordinatedGraphics::didChangeAcceleratedCompositingMode(bool enabled) -+{ -+ m_isInAcceleratedCompositingMode = enabled; -+} -+#endif -+ -+#if !PLATFORM(WPE) ++#if PLATFORM(GTK) +void DrawingAreaProxyCoordinatedGraphics::captureFrame() +{ + RefPtr surface; -+#if PLATFORM(WIN) -+ HWndDC dc; -+ if (m_isInAcceleratedCompositingMode) { -+ dc.setHWnd(reinterpret_cast(protectedWebPageProxy()->viewWidget())); -+ surface = adoptRef(cairo_win32_surface_create(dc)); -+#else + if (isInAcceleratedCompositingMode()) { -+# if PLATFORM(GTK) + AcceleratedBackingStore* backingStore = webkitWebViewBaseGetAcceleratedBackingStore(WEBKIT_WEB_VIEW_BASE(protectedWebPageProxy()->viewWidget())); + if (!backingStore) + return; + + surface = backingStore->surface(); -+# else -+ fprintf(stderr, "captureFrame() is not supported in accelerated compositing mode on this platform.\n"); -+# endif -+#endif + } else if (m_backingStore) { + surface = m_backingStore->surface(); + } @@ -11982,12 +11925,27 @@ index 9b45c78e2fd36e825c4e67d8e824771128c8fe15..9f748e729fce8103278b3888552d5c37 + + protectedWebPageProxy()->inspectorController().didPaint(surface.get()); +} -+#endif ++#endif // PLATFORM(GTK) ++ ++#if PLATFORM(WIN) ++void DrawingAreaProxyCoordinatedGraphics::captureFrame() ++{ ++ if (!m_backingStore) ++ return; ++ auto surface = m_backingStore->surface(); ++ if (!surface) ++ return; ++ auto image = surface->makeImageSnapshot(); ++ if (!image) ++ return; ++ protectedWebPageProxy()->inspectorController().didPaint(WTFMove(image)); ++} ++#endif // PLATFORM(WIN) + bool DrawingAreaProxyCoordinatedGraphics::alwaysUseCompositing() const { if (!m_webPageProxy) -@@ -310,6 +364,12 @@ void DrawingAreaProxyCoordinatedGraphics::didUpdateGeometry() +@@ -310,6 +359,12 @@ void DrawingAreaProxyCoordinatedGraphics::didUpdateGeometry() // we need to resend the new size here. if (m_lastSentSize != m_size) sendUpdateGeometry(); @@ -12001,7 +11959,7 @@ index 9b45c78e2fd36e825c4e67d8e824771128c8fe15..9f748e729fce8103278b3888552d5c37 #if !PLATFORM(WPE) diff --git a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h -index 9c2bde0db0e4032a32e6ae02dc45af335df92f7a..7ae72e6c791c264ea7e0db4c93a0422b5b699e19 100644 +index 9c2bde0db0e4032a32e6ae02dc45af335df92f7a..3f3a58ee9b0f5c0ddad84f41cf3acd6199d81c37 100644 --- a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h +++ b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h @@ -29,6 +29,7 @@ @@ -12023,17 +11981,7 @@ index 9c2bde0db0e4032a32e6ae02dc45af335df92f7a..7ae72e6c791c264ea7e0db4c93a0422b void dispatchAfterEnsuringDrawing(CompletionHandler&&); -@@ -86,6 +91,9 @@ private: - void exitAcceleratedCompositingMode(uint64_t backingStoreStateID, UpdateInfo&&) override; - void updateAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext&) override; - void dispatchPresentationCallbacksAfterFlushingLayers(IPC::Connection&, Vector&&) override; -+#if PLATFORM(WIN) -+ void didChangeAcceleratedCompositingMode(bool enabled) override; -+#endif - - bool shouldSendWheelEventsToEventDispatcher() const override { return true; } - -@@ -129,6 +137,7 @@ private: +@@ -129,6 +134,7 @@ private: // The last size we sent to the web process. WebCore::IntSize m_lastSentSize; @@ -12041,19 +11989,8 @@ index 9c2bde0db0e4032a32e6ae02dc45af335df92f7a..7ae72e6c791c264ea7e0db4c93a0422b #if !PLATFORM(WPE) bool m_isBackingStoreDiscardable { true }; -@@ -137,6 +146,10 @@ private: - RunLoop::Timer m_discardBackingStoreTimer; - #endif - std::unique_ptr m_drawingMonitor; -+ -+#if PLATFORM(WIN) -+ bool m_isInAcceleratedCompositingMode { false }; -+#endif - }; - - } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp b/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp -index 5d0385c213e4951ca9bec3c17bd2135887348cb0..634193dc71e82245b5017709bb93cfb5fdfede83 100644 +index 248a3bc1d03be7bca3c4324b93f13b452d91cc42..1ebb98bea247b882335569560d9fe8678f5d4793 100644 --- a/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp +++ b/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp @@ -41,8 +41,10 @@ @@ -12067,7 +12004,7 @@ index 5d0385c213e4951ca9bec3c17bd2135887348cb0..634193dc71e82245b5017709bb93cfb5 #if PLATFORM(MAC) #include -@@ -89,7 +91,10 @@ DownloadProxy::DownloadProxy(DownloadProxyMap& downloadProxyMap, WebsiteDataStor +@@ -66,7 +68,10 @@ DownloadProxy::DownloadProxy(DownloadProxyMap& downloadProxyMap, WebsiteDataStor #if HAVE(MODERN_DOWNLOADPROGRESS) , m_assertion(ProcessAssertion::create(getCurrentProcessID(), "WebKit DownloadProxy DecideDestination"_s, ProcessAssertionType::FinishTaskInterruptable)) #endif @@ -12078,7 +12015,7 @@ index 5d0385c213e4951ca9bec3c17bd2135887348cb0..634193dc71e82245b5017709bb93cfb5 } DownloadProxy::~DownloadProxy() -@@ -109,12 +114,15 @@ void DownloadProxy::cancel(CompletionHandler&& completionHandl +@@ -86,12 +91,15 @@ void DownloadProxy::cancel(CompletionHandler&& completionHandl { m_downloadIsCancelled = true; if (m_dataStore) { @@ -12095,7 +12032,7 @@ index 5d0385c213e4951ca9bec3c17bd2135887348cb0..634193dc71e82245b5017709bb93cfb5 if (RefPtr downloadProxyMap = protectedThis->m_downloadProxyMap.get()) downloadProxyMap->downloadFinished(*protectedThis); }); -@@ -186,6 +194,33 @@ void DownloadProxy::decideDestinationWithSuggestedFilename(const WebCore::Resour +@@ -163,6 +171,33 @@ void DownloadProxy::decideDestinationWithSuggestedFilename(const WebCore::Resour suggestedFilename = m_suggestedFilename; suggestedFilename = MIMETypeRegistry::appendFileExtensionIfNecessary(suggestedFilename, response.mimeType()); @@ -12129,8 +12066,8 @@ index 5d0385c213e4951ca9bec3c17bd2135887348cb0..634193dc71e82245b5017709bb93cfb5 protectedClient()->decideDestinationWithSuggestedFilename(*this, response, ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename), [this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)] (AllowOverwrite allowOverwrite, String destination) mutable { SandboxExtension::Handle sandboxExtensionHandle; if (!destination.isNull()) { -@@ -250,6 +285,8 @@ void DownloadProxy::didFinish() - m_client->didFinish(*this); +@@ -227,6 +262,8 @@ void DownloadProxy::didFinish() + protectedClient()->didFinish(*this); if (m_downloadIsCancelled) return; + if (auto* instrumentation = m_dataStore->downloadInstrumentation()) @@ -12138,17 +12075,17 @@ index 5d0385c213e4951ca9bec3c17bd2135887348cb0..634193dc71e82245b5017709bb93cfb5 // This can cause the DownloadProxy object to be deleted. if (RefPtr downloadProxyMap = m_downloadProxyMap.get()) -@@ -264,6 +301,8 @@ void DownloadProxy::didFail(const ResourceError& error, std::span +@@ -241,6 +278,8 @@ void DownloadProxy::didFail(const ResourceError& error, std::span m_legacyResumeData = createData(resumeData); - m_client->didFail(*this, error, m_legacyResumeData.get()); + protectedClient()->didFail(*this, error, m_legacyResumeData.get()); + if (auto* instrumentation = m_dataStore->downloadInstrumentation()) + instrumentation->downloadFinished(m_uuid, error.localizedDescription()); // This can cause the DownloadProxy object to be deleted. if (RefPtr downloadProxyMap = m_downloadProxyMap.get()) diff --git a/Source/WebKit/UIProcess/Downloads/DownloadProxy.h b/Source/WebKit/UIProcess/Downloads/DownloadProxy.h -index d82b401d7d58a34fadd38c61ef3b0f521ed432e4..9403574a7d238282998065729ec9e9d165c37f2f 100644 +index 9a92a8cde3b5d1da0fbbf5fe7c549cebb8a7f2f7..9ce201ca2d7aa002c7bd389f1fe03edfb306df5d 100644 --- a/Source/WebKit/UIProcess/Downloads/DownloadProxy.h +++ b/Source/WebKit/UIProcess/Downloads/DownloadProxy.h @@ -166,6 +166,7 @@ private: @@ -12160,7 +12097,7 @@ index d82b401d7d58a34fadd38c61ef3b0f521ed432e4..9403574a7d238282998065729ec9e9d1 } // namespace WebKit diff --git a/Source/WebKit/UIProcess/DrawingAreaProxy.h b/Source/WebKit/UIProcess/DrawingAreaProxy.h -index e1f55b4a7fbc452ca1f2eb025b0c88ec9f4b845f..4e4238f4f656208c0f11822406172ea13a21e2d4 100644 +index e1f55b4a7fbc452ca1f2eb025b0c88ec9f4b845f..beb3c7a6619b554bb3186a0de917f497ac0f47f8 100644 --- a/Source/WebKit/UIProcess/DrawingAreaProxy.h +++ b/Source/WebKit/UIProcess/DrawingAreaProxy.h @@ -94,6 +94,7 @@ public: @@ -12171,29 +12108,6 @@ index e1f55b4a7fbc452ca1f2eb025b0c88ec9f4b845f..4e4238f4f656208c0f11822406172ea1 virtual void minimumSizeForAutoLayoutDidChange() { } virtual void sizeToContentAutoSizeMaximumSizeDidChange() { } -@@ -181,6 +182,10 @@ private: - virtual void update(uint64_t /* backingStoreStateID */, UpdateInfo&&) { } - virtual void exitAcceleratedCompositingMode(uint64_t /* backingStoreStateID */, UpdateInfo&&) { } - #endif -+ -+#if PLATFORM(WIN) -+ virtual void didChangeAcceleratedCompositingMode(bool) { } -+#endif - }; - - } // namespace WebKit -diff --git a/Source/WebKit/UIProcess/DrawingAreaProxy.messages.in b/Source/WebKit/UIProcess/DrawingAreaProxy.messages.in -index b03ac2dcf12c771da4a2347b81e5ba93fbd5f6a4..b69c5d3244306e1d1c10206d499d9a833a3efeec 100644 ---- a/Source/WebKit/UIProcess/DrawingAreaProxy.messages.in -+++ b/Source/WebKit/UIProcess/DrawingAreaProxy.messages.in -@@ -35,4 +35,7 @@ messages -> DrawingAreaProxy { - Update(uint64_t stateID, struct WebKit::UpdateInfo updateInfo) CanDispatchOutOfOrder - ExitAcceleratedCompositingMode(uint64_t backingStoreStateID, struct WebKit::UpdateInfo updateInfo) - #endif -+#if PLATFORM(WIN) -+ DidChangeAcceleratedCompositingMode(bool enabled) -+#endif - } diff --git a/Source/WebKit/UIProcess/Inspector/Agents/CairoJpegEncoder.cpp b/Source/WebKit/UIProcess/Inspector/Agents/CairoJpegEncoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8d20e2aa36ba0f7996c20a6a02792c7f151bbed5 @@ -12484,7 +12398,7 @@ index 0000000000000000000000000000000000000000..4ec8b96bbbddf8a7b042f53a8068754a +cairo_status_t cairo_image_surface_write_to_jpeg_mem(cairo_surface_t *sfc, unsigned char **data, size_t *len, int quality); diff --git a/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.cpp b/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..033bf77bca2de127e55cbf55a9e0b0c358fc81f9 +index 0000000000000000000000000000000000000000..55a0cf8cd5be1cdd33165fe904a8f0ffd815f6d4 --- /dev/null +++ b/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.cpp @@ -0,0 +1,392 @@ @@ -12593,7 +12507,7 @@ index 0000000000000000000000000000000000000000..033bf77bca2de127e55cbf55a9e0b0c3 +void InspectorScreencastAgent::didPaint(sk_sp&& surface) +{ + sk_sp image(surface); -+#if PLATFORM(WPE) ++#if PLATFORM(WPE) || PLATFORM(WIN) + // Get actual image size (in device pixels). + WebCore::IntSize displaySize(image->width(), image->height()); + @@ -12866,7 +12780,7 @@ index 0000000000000000000000000000000000000000..033bf77bca2de127e55cbf55a9e0b0c3 +} +#endif + -+#if (USE(CAIRO) && !PLATFORM(WPE)) || PLATFORM(GTK) ++#if PLATFORM(GTK) || PLATFORM(WIN) +void InspectorScreencastAgent::encodeFrame() +{ + if (!m_encoder && !m_screencast) @@ -13667,7 +13581,7 @@ index 0000000000000000000000000000000000000000..e2ce910f3fd7f587add552275b7e7176 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp b/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp -index 77bdb25abf77bc0f7f00d3dbd57b0eb751c99488..6d3a93a009cff0f70f2b1a1e674827c24a26a947 100644 +index 76cc869ca4d5fc311040a285d50a44f00273fd63..633f8323697f0c2f30311a35ac3876bf8e03b741 100644 --- a/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp +++ b/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp @@ -28,7 +28,7 @@ @@ -13695,7 +13609,7 @@ index 77bdb25abf77bc0f7f00d3dbd57b0eb751c99488..6d3a93a009cff0f70f2b1a1e674827c2 target->m_provisionalPage = provisionalPage; return target; } -@@ -105,6 +105,31 @@ void InspectorTargetProxy::didCommitProvisionalTarget() +@@ -108,6 +108,31 @@ void InspectorTargetProxy::didCommitProvisionalTarget() m_provisionalPage = nullptr; } @@ -13761,7 +13675,7 @@ index edd6e7f1799279ed3d0eb81b6c2eef9f5b375134..d4231f84f3c52641f4d9e88559e8e1a4 String m_identifier; Inspector::InspectorTargetType m_type; diff --git a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp -index e4f2f719746ed69b1226be2d47b16666f2067772..96956711b594d80ea9cbdce11a6f62b2992530aa 100644 +index 45eb87344ce4249eea90dc0a73a2c717f69f55fa..b79c9ce9e9fd3ceb41fe6f34861536ab0bdf2e54 100644 --- a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp +++ b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp @@ -26,13 +26,22 @@ @@ -14118,7 +14032,7 @@ index e4f2f719746ed69b1226be2d47b16666f2067772..96956711b594d80ea9cbdce11a6f62b2 + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h -index 29457fc3c76c963bf50b44c011f64398efbae676..3e098aae8cd02d26aa32a6e15ed16e95695afaf7 100644 +index c219e0a072057a8d40d8a30a1d404851d6c12d43..42e40b5ff6bd1b49d7662a1c7d60bfd4b8d61e29 100644 --- a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h +++ b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h @@ -26,19 +26,43 @@ @@ -14254,8 +14168,8 @@ index 29457fc3c76c963bf50b44c011f64398efbae676..3e098aae8cd02d26aa32a6e15ed16e95 void addTarget(std::unique_ptr&&); + void adjustPageSettings(); - Ref m_frontendRouter; - Ref m_backendDispatcher; + const Ref m_frontendRouter; + const Ref m_backendDispatcher; @@ -101,9 +176,16 @@ private: CheckedPtr m_targetAgent; HashMap> m_targets; @@ -14506,7 +14420,7 @@ index 0000000000000000000000000000000000000000..d0e11ed81a6257c011df23d5870da740 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..7535756c45428e307e27807d31884ae6c5216543 +index 0000000000000000000000000000000000000000..59827ffa02a8a3c7890ab0b5a8f54244f6a0680b --- /dev/null +++ b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp @@ -0,0 +1,1011 @@ @@ -15443,7 +15357,7 @@ index 0000000000000000000000000000000000000000..7535756c45428e307e27807d31884ae6 +{ + if (!m_isEnabled) + return; -+ String frameID = WebCore::InspectorPageAgent::serializeFrameID(*frameInfoData.frameID); ++ String frameID = WebCore::InspectorPageAgent::serializeFrameID(frameInfoData.frameID); + m_downloads.set(uuid, download); + m_frontendDispatcher->downloadCreated( + toPageProxyIDProtocolString(*page), @@ -15748,7 +15662,7 @@ index 0000000000000000000000000000000000000000..e7a3dcc533294bb6e12f65d79b5b716b + +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp b/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp -index 9959b634faa60d620d4aac6635b31914bb063979..a8a38fe41b5fdc2836d068cc69940a5b21876e8d 100644 +index 272e0a6edea50acd35032a854d625b5af5a1472e..486c807ba879ecf534db1ffb593709d117e0c35a 100644 --- a/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp +++ b/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp @@ -168,6 +168,13 @@ void ProcessLauncher::launchProcess() @@ -15804,10 +15718,10 @@ index a108acd8a4503a07309fe8c54afc80b0f4175eae..1421d9a761042c31a6ecf3cc78ce3f0e BOOL result = ::CreateProcess(0, commandLine.data(), 0, 0, true, 0, 0, 0, &startupInfo, &processInformation); diff --git a/Source/WebKit/UIProcess/PageClient.h b/Source/WebKit/UIProcess/PageClient.h -index a5e7596eea86c6b7bdecf0a3adc906c3007a2f5d..2507c278685870936a6a26e9dd0d3474bcde51bc 100644 +index 0cffbf89f6b402b9aaa65bf9edd7387f0aff65da..baa97c9400538243f96af3bde2482dbd3542b38d 100644 --- a/Source/WebKit/UIProcess/PageClient.h +++ b/Source/WebKit/UIProcess/PageClient.h -@@ -75,6 +75,11 @@ +@@ -74,6 +74,11 @@ #include #endif @@ -15819,7 +15733,7 @@ index a5e7596eea86c6b7bdecf0a3adc906c3007a2f5d..2507c278685870936a6a26e9dd0d3474 OBJC_CLASS AVPlayerViewController; OBJC_CLASS CALayer; OBJC_CLASS NSFileWrapper; -@@ -96,6 +101,12 @@ OBJC_CLASS WKView; +@@ -95,6 +100,12 @@ OBJC_CLASS WKView; #endif #endif @@ -15832,7 +15746,7 @@ index a5e7596eea86c6b7bdecf0a3adc906c3007a2f5d..2507c278685870936a6a26e9dd0d3474 namespace API { class Attachment; class HitTestResult; -@@ -376,7 +387,20 @@ public: +@@ -380,7 +391,20 @@ public: virtual void selectionDidChange() = 0; #endif @@ -15987,7 +15901,7 @@ index 0000000000000000000000000000000000000000..a8a92a6c5f4b03724decc97828291f6f + +#endif // ENABLE(FULLSCREEN_API) diff --git a/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp b/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp -index 3399db4087c395fbc516cc90f20f70d535a31c65..ec217611aa4a5823567c5cde84a211a2dcfb26be 100644 +index 6a324ab3945475beed2774ab596645c72a6f658e..9045758645d48123c574a2ba7529a433c1a99f57 100644 --- a/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp +++ b/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp @@ -25,6 +25,7 @@ @@ -16306,7 +16220,7 @@ index 0000000000000000000000000000000000000000..6d04f9290135069359ce6bf872654648 + +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/WebContextMenuProxy.h b/Source/WebKit/UIProcess/WebContextMenuProxy.h -index c951d8577473371d8eb59ad651451737349c4389..b38cd0967ae45733a94594fd25900ddc1d49c3f6 100644 +index 697a350812e1bf73dd44cc3d723a6a291f9d59d1..a8e1edd710d88f48632d51fd05aa964732d727d3 100644 --- a/Source/WebKit/UIProcess/WebContextMenuProxy.h +++ b/Source/WebKit/UIProcess/WebContextMenuProxy.h @@ -49,6 +49,7 @@ public: @@ -16316,7 +16230,7 @@ index c951d8577473371d8eb59ad651451737349c4389..b38cd0967ae45733a94594fd25900ddc + virtual void hide() {} WebPageProxy* page() const { return m_page.get(); } - + RefPtr protectedPage() const; diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0a8d10ae990997684766df46719c65aa8dd77f28 @@ -17058,18 +16972,18 @@ index 0000000000000000000000000000000000000000..26a2a3c0791c334f811ec99a630314f8 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp -index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c65861c1847e8 100644 +index 8596e12ab413647f88507c1bbfebcd5ca441a190..11ce2408b8358bc2ebe3abfc79c47662e991d7c0 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp -@@ -200,6 +200,7 @@ +@@ -203,6 +203,7 @@ #include #include #include +#include #include #include - #include -@@ -210,6 +211,7 @@ + #include +@@ -214,6 +215,7 @@ #include #include #include @@ -17077,7 +16991,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #include #include #include -@@ -233,6 +235,7 @@ +@@ -238,6 +240,7 @@ #include #include #include @@ -17085,7 +16999,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #include #include #include -@@ -240,10 +243,13 @@ +@@ -245,10 +248,13 @@ #include #include #include @@ -17099,7 +17013,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #include #include #include -@@ -330,6 +336,9 @@ +@@ -336,6 +342,9 @@ #if USE(GBM) #include "AcceleratedBackingStoreDMABuf.h" #endif @@ -17109,7 +17023,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #include #endif -@@ -455,6 +464,8 @@ static constexpr Seconds tryCloseTimeoutDelay = 50_ms; +@@ -461,6 +470,8 @@ static constexpr Seconds tryCloseTimeoutDelay = 50_ms; static constexpr Seconds audibleActivityClearDelay = 10_s; #endif @@ -17118,7 +17032,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, webPageProxyCounter, ("WebPageProxy")); #if PLATFORM(COCOA) -@@ -966,6 +977,10 @@ WebPageProxy::~WebPageProxy() +@@ -983,6 +994,10 @@ WebPageProxy::~WebPageProxy() #endif internals().updatePlayingMediaDidChangeTimer.stop(); @@ -17129,7 +17043,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } Ref WebPageProxy::Internals::protectedPage() const -@@ -1548,7 +1563,7 @@ void WebPageProxy::didAttachToRunningProcess() +@@ -1556,7 +1571,7 @@ void WebPageProxy::didAttachToRunningProcess() #if ENABLE(FULLSCREEN_API) ASSERT(!m_fullScreenManager); @@ -17138,7 +17052,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #endif #if ENABLE(VIDEO_PRESENTATION_MODE) ASSERT(!m_playbackSessionManager); -@@ -1714,6 +1729,7 @@ void WebPageProxy::initializeWebPage(const Site& site, WebCore::SandboxFlags eff +@@ -1722,6 +1737,7 @@ void WebPageProxy::initializeWebPage(const Site& site, WebCore::SandboxFlags eff if (preferences->siteIsolationEnabled()) browsingContextGroup->addPage(*this); process->send(Messages::WebProcess::CreateWebPage(m_webPageID, creationParameters(process, *m_drawingArea, m_mainFrame->frameID(), std::nullopt)), 0); @@ -17146,7 +17060,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #if ENABLE(WINDOW_PROXY_PROPERTY_ACCESS_NOTIFICATION) internals().frameLoadStateObserver = makeUniqueWithoutRefCountedCheck(*this); -@@ -2006,6 +2022,21 @@ Ref WebPageProxy::ensureProtectedRunningProcess() +@@ -1988,6 +2004,21 @@ Ref WebPageProxy::ensureProtectedRunningProcess() return ensureRunningProcess(); } @@ -17168,18 +17082,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 RefPtr WebPageProxy::loadRequest(WebCore::ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, IsPerformingHTTPFallback isPerformingHTTPFallback, std::unique_ptr&& lastNavigationAction, API::Object* userData) { if (m_isClosed) -@@ -2062,8 +2093,8 @@ void WebPageProxy::loadRequestWithNavigationShared(Ref&& proces - - auto url = request.url(); - #if PLATFORM(COCOA) -- bool urlIsInvalidButNotNull = !url.isValid() && !url.isNull(); -- if (urlIsInvalidButNotNull && WTF::linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::ConvertsInvalidURLsToNull)) { -+ bool urlIsInvalidButNotEmpty = !url.isValid() && !url.isEmpty(); -+ if (urlIsInvalidButNotEmpty && WTF::linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::ConvertsInvalidURLsToNull)) { - RunLoop::protectedMain()->dispatch([weakThis = WeakPtr { *this }, request, navigation = Ref { navigation }] { - RefPtr protectedThis = weakThis.get(); - if (!protectedThis) -@@ -2116,11 +2147,29 @@ void WebPageProxy::loadRequestWithNavigationShared(Ref&& proces +@@ -2103,11 +2134,29 @@ void WebPageProxy::loadRequestWithNavigationShared(Ref&& proces navigation->setIsLoadedWithNavigationShared(true); protectedProcess->markProcessAsRecentlyUsed(); @@ -17213,7 +17116,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 }); } -@@ -2671,6 +2720,61 @@ void WebPageProxy::setControlledByAutomation(bool controlled) +@@ -2661,6 +2710,63 @@ void WebPageProxy::setControlledByAutomation(bool controlled) protectedWebsiteDataStore()->protectedNetworkProcess()->send(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation), 0); } @@ -17269,13 +17172,15 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 + +void WebPageProxy::logToStderr(const String& str) +{ ++WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN + fprintf(stderr, "RENDERER: %s\n", str.utf8().data()); ++WTF_ALLOW_UNSAFE_BUFFER_USAGE_END +} + void WebPageProxy::createInspectorTarget(IPC::Connection& connection, const String& targetId, Inspector::InspectorTargetType type) { MESSAGE_CHECK_BASE(!targetId.isEmpty(), connection); -@@ -2924,6 +3028,24 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpdate) +@@ -2920,6 +3026,24 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpdate) bool wasVisible = isViewVisible(); RefPtr pageClient = this->pageClient(); internals().activityState.remove(flagsToUpdate); @@ -17300,7 +17205,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 if (flagsToUpdate & ActivityState::IsFocused && pageClient->isViewFocused()) internals().activityState.add(ActivityState::IsFocused); if (flagsToUpdate & ActivityState::WindowIsActive && pageClient->isViewWindowActive()) -@@ -3706,7 +3828,7 @@ void WebPageProxy::performDragOperation(DragData& dragData, const String& dragSt +@@ -3683,7 +3807,7 @@ void WebPageProxy::performDragOperation(DragData& dragData, const String& dragSt if (!hasRunningProcess()) return; @@ -17309,7 +17214,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 URL url { dragData.asURL() }; if (url.protocolIsFile()) protectedLegacyMainFrameProcess()->assumeReadAccessToBaseURL(*this, url.string(), [] { }); -@@ -3734,6 +3856,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag +@@ -3711,6 +3835,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag if (!hasRunningProcess()) return; @@ -17318,7 +17223,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 auto completionHandler = [this, protectedThis = Ref { *this }, action, dragData] (std::optional dragOperation, WebCore::DragHandlingMethod dragHandlingMethod, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, const IntRect& insertionRect, const IntRect& editableElementRect, std::optional remoteUserInputEventData) mutable { if (!m_pageClient) return; -@@ -3745,7 +3869,7 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag +@@ -3722,7 +3848,7 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag dragData.setClientPosition(remoteUserInputEventData->transformedPoint); performDragControllerAction(action, dragData, remoteUserInputEventData->targetFrameID); }; @@ -17327,7 +17232,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 ASSERT(dragData.platformData()); sendWithAsyncReplyToProcessContainingFrame(frameID, Messages::WebPage::PerformDragControllerAction(action, dragData.clientPosition(), dragData.globalPosition(), dragData.draggingSourceOperationMask(), *dragData.platformData(), dragData.flags()), WTFMove(completionHandler)); #else -@@ -3780,14 +3904,35 @@ void WebPageProxy::didPerformDragControllerAction(std::optionalpageClient()) pageClient->didPerformDragControllerAction(); @@ -17367,7 +17272,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } didStartDrag(); } -@@ -3809,6 +3954,24 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo +@@ -3786,6 +3933,24 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo setDragCaretRect({ }); } @@ -17392,7 +17297,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 void WebPageProxy::didStartDrag() { if (!hasRunningProcess()) -@@ -3816,6 +3979,26 @@ void WebPageProxy::didStartDrag() +@@ -3793,6 +3958,26 @@ void WebPageProxy::didStartDrag() discardQueuedMouseEvents(); send(Messages::WebPage::DidStartDrag()); @@ -17419,7 +17324,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } void WebPageProxy::dragCancelled() -@@ -3981,26 +4164,47 @@ void WebPageProxy::processNextQueuedMouseEvent() +@@ -3958,26 +4143,47 @@ void WebPageProxy::processNextQueuedMouseEvent() process->startResponsivenessTimer(); } @@ -17474,12 +17379,12 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 + m_dragSelectionData = std::nullopt; + dragEnded(event.position(), event.globalPosition(), m_dragSourceOperationMask); + } -+ didReceiveEvent(eventType, true, std::nullopt); ++ didReceiveEventIPC(process->connection(), eventType, true, std::nullopt); + } } void WebPageProxy::doAfterProcessingAllPendingMouseEvents(WTF::Function&& action) -@@ -4197,6 +4401,8 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) +@@ -4174,6 +4380,8 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) if (RefPtr automationSession = m_configuration->processPool().automationSession()) automationSession->wheelEventsFlushedForPage(*this); @@ -17488,7 +17393,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } void WebPageProxy::cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent& nativeWheelEvent) -@@ -4332,7 +4538,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) +@@ -4310,7 +4518,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent) { @@ -17497,7 +17402,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 for (auto& touchPoint : touchStartEvent.touchPoints()) { auto location = touchPoint.locationInRootView(); auto update = [this, location](TrackingType& trackingType, EventTrackingRegions::EventType eventType) { -@@ -4971,6 +5177,7 @@ void WebPageProxy::receivedNavigationActionPolicyDecision(WebProcessProxy& proce +@@ -4972,6 +5180,7 @@ void WebPageProxy::receivedNavigationActionPolicyDecision(WebProcessProxy& proce void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, RefPtr&& websitePolicies, Ref&& navigationAction, WillContinueLoadInNewProcess willContinueLoadInNewProcess, std::optional sandboxExtensionHandle, std::optional&& consoleMessage, CompletionHandler&& completionHandler) { @@ -17505,7 +17410,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 if (!hasRunningProcess()) return completionHandler(PolicyDecision { }); -@@ -5967,6 +6174,7 @@ void WebPageProxy::viewScaleFactorDidChange(IPC::Connection& connection, double +@@ -5969,6 +6178,7 @@ void WebPageProxy::viewScaleFactorDidChange(IPC::Connection& connection, double MESSAGE_CHECK_BASE(scaleFactorIsValid(scaleFactor), connection); if (!legacyMainFrameProcess().hasConnection(connection)) return; @@ -17513,15 +17418,15 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 forEachWebContentProcess([&] (auto& process, auto pageID) { if (&process == &legacyMainFrameProcess()) -@@ -6609,6 +6817,7 @@ void WebPageProxy::didDestroyNavigationShared(Ref&& process, We +@@ -6606,6 +6816,7 @@ void WebPageProxy::didDestroyNavigationShared(Ref&& process, We RefPtr protectedPageClient { pageClient() }; protectedNavigationState()->didDestroyNavigation(process->coreProcessIdentifier(), navigationID); + m_inspectorController->didDestroyNavigation(navigationID); } - void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, std::optional navigationID, URL&& url, URL&& unreachableURL, const UserData& userData, WallTime timestamp) -@@ -6947,6 +7156,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p + void WebPageProxy::didStartProvisionalLoadForFrame(IPC::Connection& connection, FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, std::optional navigationID, URL&& url, URL&& unreachableURL, const UserData& userData, WallTime timestamp) +@@ -6947,6 +7158,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p m_failingProvisionalLoadURL = { }; @@ -17530,7 +17435,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 // If the provisional page's load fails then we destroy the provisional page. if (m_provisionalPage && m_provisionalPage->mainFrame() == &frame && willContinueLoading == WillContinueLoading::No) m_provisionalPage = nullptr; -@@ -8395,8 +8606,9 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w +@@ -8405,8 +8618,9 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w if (RefPtr page = originatingFrameInfo->page()) openerAppInitiatedState = page->lastNavigationWasAppInitiated(); @@ -17541,7 +17446,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 auto completionHandler = [ this, protectedThis = Ref { *this }, -@@ -8469,6 +8681,7 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w +@@ -8486,6 +8700,7 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w configuration->setOpenedMainFrameName(openedMainFrameName); if (!protectedPreferences()->siteIsolationEnabled()) configuration->setRelatedPage(*this); @@ -17549,7 +17454,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 if (RefPtr openerFrame = WebFrameProxy::webFrame(originatingFrameInfoData.frameID); navigationActionData.hasOpener && openerFrame) { configuration->setOpenerInfo({ { -@@ -8491,6 +8704,7 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w +@@ -8514,6 +8729,7 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w void WebPageProxy::showPage() { m_uiClient->showPage(this); @@ -17557,7 +17462,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } bool WebPageProxy::hasOpenedPage() const -@@ -8622,6 +8836,10 @@ void WebPageProxy::closePage() +@@ -8645,6 +8861,10 @@ void WebPageProxy::closePage() if (isClosed()) return; @@ -17568,7 +17473,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 WEBPAGEPROXY_RELEASE_LOG(Process, "closePage:"); if (RefPtr pageClient = this->pageClient()) pageClient->clearAllEditCommands(); -@@ -8660,6 +8878,8 @@ void WebPageProxy::runJavaScriptAlert(IPC::Connection& connection, FrameIdentifi +@@ -8683,6 +8903,8 @@ void WebPageProxy::runJavaScriptAlert(IPC::Connection& connection, FrameIdentifi } runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler&& completion) mutable { @@ -17577,7 +17482,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 page.m_uiClient->runJavaScriptAlert(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)]() mutable { reply(); completion(); -@@ -8682,6 +8902,8 @@ void WebPageProxy::runJavaScriptConfirm(IPC::Connection& connection, FrameIdenti +@@ -8705,6 +8927,8 @@ void WebPageProxy::runJavaScriptConfirm(IPC::Connection& connection, FrameIdenti if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -17586,7 +17491,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler&& completion) mutable { page.m_uiClient->runJavaScriptConfirm(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](bool result) mutable { -@@ -8706,6 +8928,8 @@ void WebPageProxy::runJavaScriptPrompt(IPC::Connection& connection, FrameIdentif +@@ -8729,6 +8953,8 @@ void WebPageProxy::runJavaScriptPrompt(IPC::Connection& connection, FrameIdentif if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -17595,7 +17500,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply), defaultValue](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler&& completion) mutable { page.m_uiClient->runJavaScriptPrompt(page, message, defaultValue, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](auto& result) mutable { -@@ -8841,6 +9065,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(IPC::Connection& connection, Fram +@@ -8864,6 +9090,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(IPC::Connection& connection, Fram return; } } @@ -17603,8 +17508,8 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 + m_inspectorDialogAgent->javascriptDialogOpening("beforeunload"_s, message); // Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer. - protectedLegacyMainFrameProcess()->stopResponsivenessTimer(); -@@ -9454,6 +9680,11 @@ void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, + WebProcessProxy::fromConnection(connection)->stopResponsivenessTimer(); +@@ -9481,6 +9709,11 @@ void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, } #if ENABLE(FULLSCREEN_API) @@ -17616,7 +17521,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 WebFullScreenManagerProxy* WebPageProxy::fullScreenManager() { return m_fullScreenManager.get(); -@@ -9576,6 +9807,17 @@ void WebPageProxy::requestDOMPasteAccess(DOMPasteAccessCategory pasteAccessCateg +@@ -9608,6 +9841,17 @@ void WebPageProxy::requestDOMPasteAccess(IPC::Connection& connection, DOMPasteAc } } @@ -17634,7 +17539,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 protectedPageClient()->requestDOMPasteAccess(pasteAccessCategory, requiresInteraction, elementRect, originIdentifier, WTFMove(completionHandler)); } -@@ -10605,6 +10847,8 @@ void WebPageProxy::mouseEventHandlingCompleted(std::optional event +@@ -10634,6 +10878,8 @@ void WebPageProxy::mouseEventHandlingCompleted(std::optional event if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->mouseEventsFlushedForPage(*this); didFinishProcessingAllPendingMouseEvents(); @@ -17643,7 +17548,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } } -@@ -10640,6 +10884,7 @@ void WebPageProxy::keyEventHandlingCompleted(std::optional eventTy +@@ -10669,6 +10915,7 @@ void WebPageProxy::keyEventHandlingCompleted(std::optional eventTy if (!canProcessMoreKeyEvents) { if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->keyboardEventsFlushedForPage(*this); @@ -17651,7 +17556,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 } } -@@ -11064,7 +11309,10 @@ void WebPageProxy::dispatchProcessDidTerminate(WebProcessProxy& process, Process +@@ -11101,7 +11348,10 @@ void WebPageProxy::dispatchProcessDidTerminate(WebProcessProxy& process, Process if (protectedPreferences()->siteIsolationEnabled()) protectedBrowsingContextGroup()->processDidTerminate(*this, process); @@ -17663,7 +17568,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 if (m_loaderClient) handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this); else -@@ -11719,6 +11967,8 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc +@@ -11752,6 +12002,8 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc parameters.canUseCredentialStorage = m_canUseCredentialStorage; parameters.httpsUpgradeEnabled = preferences->upgradeKnownHostsToHTTPSEnabled() ? m_configuration->httpsUpgradeEnabled() : false; @@ -17672,7 +17577,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 #if ENABLE(APP_HIGHLIGHTS) parameters.appHighlightsVisible = appHighlightsVisibility() ? HighlightVisibility::Visible : HighlightVisibility::Hidden; -@@ -11882,8 +12132,42 @@ void WebPageProxy::allowGamepadAccess() +@@ -11917,8 +12169,42 @@ void WebPageProxy::allowGamepadAccess() #endif // ENABLE(GAMEPAD) @@ -17715,7 +17620,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) { m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = Ref { *this }, authenticationChallenge] (bool shouldAllowLegacyTLS) { if (shouldAllowLegacyTLS) -@@ -11979,6 +12263,12 @@ void WebPageProxy::requestGeolocationPermissionForFrame(IPC::Connection& connect +@@ -12014,6 +12300,12 @@ void WebPageProxy::requestGeolocationPermissionForFrame(IPC::Connection& connect request->deny(); }; @@ -17728,7 +17633,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 // FIXME: Once iOS migrates to the new WKUIDelegate SPI, clean this up // and make it one UIClient call that calls the completionHandler with false // if there is no delegate instead of returning the completionHandler -@@ -12043,6 +12333,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi +@@ -12078,6 +12370,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi shouldChangeDeniedToPrompt = false; if (sessionID().isEphemeral()) { @@ -17741,7 +17646,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 completionHandler(shouldChangeDeniedToPrompt ? PermissionState::Prompt : PermissionState::Denied); return; } -@@ -12057,6 +12353,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi +@@ -12092,6 +12390,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi return; } @@ -17755,7 +17660,7 @@ index 92eef3dd76340b6f1761c0bfbe6e7a0896f532f2..daae2e0dfdddf8d44b075b2ddc6c6586 completionHandler(shouldChangeDeniedToPrompt ? PermissionState::Prompt : PermissionState::Denied); return; diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h -index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1650efbe6 100644 +index de568bc247847234de4bd8a34d80726a4fa22a53..85881e1a1a695907016c0924e3df38f072455f61 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -26,6 +26,7 @@ @@ -17795,7 +17700,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 class FloatRect; class FloatSize; class FontAttributeChanges; -@@ -729,6 +745,8 @@ public: +@@ -734,6 +750,8 @@ public: void setControlledByAutomation(bool); WebPageInspectorController& inspectorController() { return *m_inspectorController; } @@ -17804,15 +17709,15 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 #if PLATFORM(IOS_FAMILY) void showInspectorIndication(); -@@ -763,6 +781,7 @@ public: +@@ -767,6 +785,7 @@ public: bool hasSleepDisabler() const; #if ENABLE(FULLSCREEN_API) + void setFullScreenManagerClientOverride(std::unique_ptr&&); WebFullScreenManagerProxy* fullScreenManager(); + RefPtr protectedFullScreenManager(); void setFullScreenClientForTesting(std::unique_ptr&&); - -@@ -853,6 +872,12 @@ public: +@@ -858,6 +877,12 @@ public: void setPageLoadStateObserver(RefPtr&&); @@ -17825,7 +17730,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 void initializeWebPage(const WebCore::Site&, WebCore::SandboxFlags); void setDrawingArea(RefPtr&&); -@@ -884,6 +909,8 @@ public: +@@ -889,6 +914,8 @@ public: RefPtr loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy, WebCore::IsPerformingHTTPFallback); RefPtr loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy, WebCore::IsPerformingHTTPFallback, std::unique_ptr&&, API::Object* userData = nullptr); @@ -17834,7 +17739,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 RefPtr loadFile(const String& fileURL, const String& resourceDirectoryURL, bool isAppInitiated = true, API::Object* userData = nullptr); RefPtr loadData(Ref&&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData = nullptr); RefPtr loadData(Ref&&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData, WebCore::ShouldOpenExternalURLsPolicy); -@@ -972,6 +999,7 @@ public: +@@ -977,6 +1004,7 @@ public: PageClient* pageClient() const; RefPtr protectedPageClient() const; @@ -17842,7 +17747,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 void setViewNeedsDisplay(const WebCore::Region&); void requestScroll(const WebCore::FloatPoint& scrollPosition, const WebCore::IntPoint& scrollOrigin, WebCore::ScrollIsAnimated); -@@ -1609,14 +1637,20 @@ public: +@@ -1617,14 +1645,20 @@ public: void didStartDrag(); void dragCancelled(); void setDragCaretRect(const WebCore::IntRect&); @@ -17864,7 +17769,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 #endif void processDidBecomeUnresponsive(); -@@ -1869,6 +1903,7 @@ public: +@@ -1877,6 +1911,7 @@ public: void setViewportSizeForCSSViewportUnits(const WebCore::FloatSize&); WebCore::FloatSize viewportSizeForCSSViewportUnits() const; @@ -17872,7 +17777,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 void didReceiveAuthenticationChallengeProxy(Ref&&, NegotiatedLegacyTLS); void negotiatedLegacyTLS(); void didNegotiateModernTLS(const URL&); -@@ -1902,6 +1937,8 @@ public: +@@ -1910,6 +1945,8 @@ public: #if PLATFORM(COCOA) || PLATFORM(GTK) RefPtr takeViewSnapshot(std::optional&&); RefPtr takeViewSnapshot(std::optional&&, ForceSoftwareCapturingViewportSnapshot); @@ -17880,8 +17785,8 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 + RefPtr takeViewSnapshot(std::optional&&) { return nullptr; } #endif - void wrapCryptoKey(Vector&&, CompletionHandler>&&)>&&); -@@ -2901,6 +2938,7 @@ private: + void serializeAndWrapCryptoKey(IPC::Connection&, WebCore::CryptoKeyData&&, CompletionHandler>&&)>&&); +@@ -2919,6 +2956,7 @@ private: RefPtr launchProcessForReload(); void requestNotificationPermission(const String& originString, CompletionHandler&&); @@ -17889,7 +17794,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 void didChangeContentSize(const WebCore::IntSize&); void didChangeIntrinsicContentSize(const WebCore::IntSize&); -@@ -3424,8 +3462,10 @@ RefPtr protectedSpeechRecognitionPermissionM +@@ -3436,8 +3474,10 @@ private: String m_openedMainFrameName; RefPtr m_inspector; @@ -17900,7 +17805,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 RefPtr m_fullScreenManager; std::unique_ptr m_fullscreenClient; #endif -@@ -3622,6 +3662,22 @@ RefPtr protectedSpeechRecognitionPermissionM +@@ -3636,6 +3676,22 @@ private: std::optional m_currentDragOperation; bool m_currentDragIsOverFileInput { false }; unsigned m_currentDragNumberOfFilesToBeAccepted { 0 }; @@ -17923,7 +17828,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 #endif bool m_mainFrameHasHorizontalScrollbar { false }; -@@ -3795,6 +3851,10 @@ RefPtr protectedSpeechRecognitionPermissionM +@@ -3807,6 +3863,10 @@ private: RefPtr messageBody; }; Vector m_pendingInjectedBundleMessages; @@ -17935,7 +17840,7 @@ index 5eb0a3d277ecd9a0ea9c2e4f1223205bff9cb5a4..b770b73fb96af5dd106c8e66d02590c1 #if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION) RefPtr m_webDeviceOrientationUpdateProviderProxy; diff --git a/Source/WebKit/UIProcess/WebPageProxy.messages.in b/Source/WebKit/UIProcess/WebPageProxy.messages.in -index d1b9a0bec55cf3c64792b6c0c527c973a6ef3cdd..4b132c98aabe3dffef13b01e6251bdb7305ce200 100644 +index 0310e26fe8eb09a68db493035b108aa472dc573d..0182474dd3cb70bc87f239718ad52db972cc44ae 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.messages.in +++ b/Source/WebKit/UIProcess/WebPageProxy.messages.in @@ -35,6 +35,7 @@ messages -> WebPageProxy { @@ -17944,9 +17849,9 @@ index d1b9a0bec55cf3c64792b6c0c527c973a6ef3cdd..4b132c98aabe3dffef13b01e6251bdb7 MouseDidMoveOverElement(struct WebKit::WebHitTestResultData hitTestResultData, OptionSet modifiers, WebKit::UserData userData) + LogToStderr(String text) - DidReceiveEvent(enum:uint8_t WebKit::WebEventType eventType, bool handled, struct std::optional remoteUserInputEventData) + DidReceiveEventIPC(enum:uint8_t WebKit::WebEventType eventType, bool handled, struct std::optional remoteUserInputEventData) SetCursor(WebCore::Cursor cursor) -@@ -336,10 +337,14 @@ messages -> WebPageProxy { +@@ -333,10 +334,14 @@ messages -> WebPageProxy { StartDrag(struct WebCore::DragItem dragItem, WebCore::ShareableBitmapHandle dragImage) SetPromisedDataForImage(String pasteboardName, WebCore::SharedMemory::Handle imageHandle, String filename, String extension, String title, String url, String visibleURL, WebCore::SharedMemory::Handle archiveHandle, String originIdentifier) #endif @@ -17963,7 +17868,7 @@ index d1b9a0bec55cf3c64792b6c0c527c973a6ef3cdd..4b132c98aabe3dffef13b01e6251bdb7 DidHandleDragStartRequest(bool started) DidHandleAdditionalDragItemsRequest(bool added) diff --git a/Source/WebKit/UIProcess/WebProcessCache.cpp b/Source/WebKit/UIProcess/WebProcessCache.cpp -index 9153b4da4d4f8de14f27e9ee2e8c89059e9573f5..035009bdcf5651aedd9604e47dc02fbfcccc62a9 100644 +index dc6f440403cccc5cd93f75806cffbf05cc56041c..e880beba2034cc2b87dcfb3e1e8bacf1bed78cf3 100644 --- a/Source/WebKit/UIProcess/WebProcessCache.cpp +++ b/Source/WebKit/UIProcess/WebProcessCache.cpp @@ -100,6 +100,10 @@ bool WebProcessCache::canCacheProcess(WebProcessProxy& process) const @@ -17978,7 +17883,7 @@ index 9153b4da4d4f8de14f27e9ee2e8c89059e9573f5..035009bdcf5651aedd9604e47dc02fbf } diff --git a/Source/WebKit/UIProcess/WebProcessPool.cpp b/Source/WebKit/UIProcess/WebProcessPool.cpp -index c035cc55b3810bef6d30b753ee325905d8fbc6ab..d129f7ebbf7a9a5e97e09fd7ce6e58040a960abc 100644 +index 4be8b8ead49ba2ab0ce16af74aa92fab01352358..8e310f0e3bcada1ec9383161b979660e29c50724 100644 --- a/Source/WebKit/UIProcess/WebProcessPool.cpp +++ b/Source/WebKit/UIProcess/WebProcessPool.cpp @@ -445,10 +445,10 @@ void WebProcessPool::setAutomationClient(std::unique_ptr& @@ -18006,7 +17911,7 @@ index c035cc55b3810bef6d30b753ee325905d8fbc6ab..d129f7ebbf7a9a5e97e09fd7ce6e5804 void WebProcessPool::fullKeyboardAccessModeChanged(bool fullKeyboardAccessEnabled) { -@@ -947,7 +948,7 @@ void WebProcessPool::initializeNewWebProcess(WebProcessProxy& process, WebsiteDa +@@ -939,7 +940,7 @@ void WebProcessPool::initializeNewWebProcess(WebProcessProxy& process, WebsiteDa #endif parameters.cacheModel = LegacyGlobalSettings::singleton().cacheModel(); @@ -18016,10 +17921,10 @@ index c035cc55b3810bef6d30b753ee325905d8fbc6ab..d129f7ebbf7a9a5e97e09fd7ce6e5804 parameters.urlSchemesRegisteredAsEmptyDocument = copyToVector(m_schemesToRegisterAsEmptyDocument); diff --git a/Source/WebKit/UIProcess/WebProcessProxy.cpp b/Source/WebKit/UIProcess/WebProcessProxy.cpp -index 1ac3aeb44691040950f6992d77e6489d9c58744f..3b4ebb055f9d9e7b3c64bfbf830239a7d1baf770 100644 +index 45a11c271082cd40635c6b21ee01c75bea749a4b..4ba89b3eafebfa67d8518ec99db6f23ae968df29 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.cpp +++ b/Source/WebKit/UIProcess/WebProcessProxy.cpp -@@ -201,6 +201,11 @@ Vector> WebProcessProxy::allProcesses() +@@ -203,6 +203,11 @@ Vector> WebProcessProxy::allProcesses() }); } @@ -18031,7 +17936,7 @@ index 1ac3aeb44691040950f6992d77e6489d9c58744f..3b4ebb055f9d9e7b3c64bfbf830239a7 RefPtr WebProcessProxy::processForIdentifier(ProcessIdentifier identifier) { return allProcessMap().get(identifier); -@@ -568,6 +573,26 @@ void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOpt +@@ -572,6 +577,26 @@ void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOpt if (WebKit::isInspectorProcessPool(protectedProcessPool())) launchOptions.extraInitializationData.add("inspector-process"_s, "1"_s); @@ -18059,10 +17964,10 @@ index 1ac3aeb44691040950f6992d77e6489d9c58744f..3b4ebb055f9d9e7b3c64bfbf830239a7 if (isPrewarmed()) diff --git a/Source/WebKit/UIProcess/WebProcessProxy.h b/Source/WebKit/UIProcess/WebProcessProxy.h -index b82965d62515b10373be40b28e4ba567821d5994..e05268412a251474c64fc4b3bafa25363bc87057 100644 +index 4471fdf4a46180f78cea1cf035671bfe3c83b8e2..0858fe7f56eb3d8648467f5b82c2d77d6c958b77 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.h +++ b/Source/WebKit/UIProcess/WebProcessProxy.h -@@ -184,6 +184,7 @@ public: +@@ -185,6 +185,7 @@ public: static void forWebPagesWithOrigin(PAL::SessionID, const WebCore::SecurityOriginData&, NOESCAPE const Function&); static Vector> allowedFirstPartiesForCookies(); @@ -18071,10 +17976,10 @@ index b82965d62515b10373be40b28e4ba567821d5994..e05268412a251474c64fc4b3bafa2536 void initializeWebProcess(WebProcessCreationParameters&&); diff --git a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp -index 7b9780cb47d824cc47054333aad47754d6a0725c..34bb946afd3de5354e5a3759506537d2dd1af586 100644 +index ef4db4e5e75286a260646226dd48108810b1607e..bf1111ed48e9d3cb74b59d4258faefb814028540 100644 --- a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp +++ b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp -@@ -314,7 +314,8 @@ SOAuthorizationCoordinator& WebsiteDataStore::soAuthorizationCoordinator(const W +@@ -319,7 +319,8 @@ SOAuthorizationCoordinator& WebsiteDataStore::soAuthorizationCoordinator(const W static Ref networkProcessForSession(PAL::SessionID sessionID) { @@ -18084,7 +17989,7 @@ index 7b9780cb47d824cc47054333aad47754d6a0725c..34bb946afd3de5354e5a3759506537d2 if (sessionID.isEphemeral()) { // Reuse a previous persistent session network process for ephemeral sessions. for (auto& dataStore : allDataStores().values()) { -@@ -2507,6 +2508,12 @@ void WebsiteDataStore::originDirectoryForTesting(WebCore::ClientOrigin&& origin, +@@ -2511,6 +2512,12 @@ void WebsiteDataStore::originDirectoryForTesting(WebCore::ClientOrigin&& origin, protectedNetworkProcess()->websiteDataOriginDirectoryForTesting(m_sessionID, WTFMove(origin), type, WTFMove(completionHandler)); } @@ -18098,7 +18003,7 @@ index 7b9780cb47d824cc47054333aad47754d6a0725c..34bb946afd3de5354e5a3759506537d2 void WebsiteDataStore::hasAppBoundSession(CompletionHandler&& completionHandler) const { diff --git a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h -index 0439774485211a729ccd8b0c041c41547ea28625..e09aff8b56357687de4a417b1bdd644907bb8c23 100644 +index f1a113cc08f6543ab22be75a03823f49a3f52017..3d98a33003ae86f2ac5982ba2deb5eb3238b1385 100644 --- a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h +++ b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h @@ -98,6 +98,7 @@ class DeviceIdHashSaltStorage; @@ -18132,7 +18037,7 @@ index 0439774485211a729ccd8b0c041c41547ea28625..e09aff8b56357687de4a417b1bdd6449 class WebsiteDataStore : public API::ObjectImpl, public CanMakeWeakPtr { public: static Ref defaultDataStore(); -@@ -319,11 +329,13 @@ public: +@@ -318,11 +328,13 @@ public: const WebCore::CurlProxySettings& networkProxySettings() const { return m_proxySettings; } #endif @@ -18160,7 +18065,7 @@ index 0439774485211a729ccd8b0c041c41547ea28625..e09aff8b56357687de4a417b1bdd6449 void resetQuota(CompletionHandler&&); void resetStoragePersistedState(CompletionHandler&&); #if PLATFORM(IOS_FAMILY) -@@ -614,9 +632,11 @@ private: +@@ -616,9 +634,11 @@ private: WebCore::CurlProxySettings m_proxySettings; #endif @@ -18173,7 +18078,7 @@ index 0439774485211a729ccd8b0c041c41547ea28625..e09aff8b56357687de4a417b1bdd6449 WebCore::SoupNetworkProxySettings m_networkProxySettings; String m_cookiePersistentStoragePath; SoupCookiePersistentStorageType m_cookiePersistentStorageType { SoupCookiePersistentStorageType::SQLite }; -@@ -643,6 +663,10 @@ private: +@@ -645,6 +665,10 @@ private: RefPtr m_cookieStore; RefPtr m_networkProcess; @@ -18185,7 +18090,7 @@ index 0439774485211a729ccd8b0c041c41547ea28625..e09aff8b56357687de4a417b1bdd6449 std::unique_ptr m_soAuthorizationCoordinator; #endif diff --git a/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp b/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp -index ac64c2a3cb5d1e8e46ba1835623a5c12825e3ea1..6ad7a5adc5198ae035c5cb815b6db8f033910f3a 100644 +index 19934de3173ecc0507c5e59956bcf6730a8a88b4..bc0e52f6f28caeca8b9b7aab14b7ff991d7e6b41 100644 --- a/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp +++ b/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp @@ -114,6 +114,14 @@ void GeoclueGeolocationProvider::stop() @@ -18500,6 +18405,31 @@ index 0000000000000000000000000000000000000000..441442d899e4088f5c24ae9f70c3e4ff +} // namespace API + +#endif // ENABLE(REMOTE_INSPECTOR) +diff --git a/Source/WebKit/UIProcess/glib/SystemSettingsManagerProxy.cpp b/Source/WebKit/UIProcess/glib/SystemSettingsManagerProxy.cpp +index c6e25e57892c2a0e1350b207ce53b6d2c3d6258a..bd91f75db5a05ba0a6f5a982c03980ecc57479ca 100644 +--- a/Source/WebKit/UIProcess/glib/SystemSettingsManagerProxy.cpp ++++ b/Source/WebKit/UIProcess/glib/SystemSettingsManagerProxy.cpp +@@ -27,6 +27,7 @@ + #include "config.h" + #include "SystemSettingsManagerProxy.h" + ++#include "SystemSettingsManager.h" + #include "SystemSettingsManagerMessages.h" + #include "WebProcessPool.h" + #include +diff --git a/Source/WebKit/UIProcess/glib/WebProcessPoolGLib.cpp b/Source/WebKit/UIProcess/glib/WebProcessPoolGLib.cpp +index 51ba8b585ca37a2eed54bce5218e1c92c2844cc6..dc04a2ca0b0ac2333036b897dd18d5303c5237c6 100644 +--- a/Source/WebKit/UIProcess/glib/WebProcessPoolGLib.cpp ++++ b/Source/WebKit/UIProcess/glib/WebProcessPoolGLib.cpp +@@ -124,6 +124,8 @@ static OptionSet availableInputDevices() + return toAvailableInputDevices(gdk_seat_get_capabilities(seat)); + } + #endif ++ if (!WebCore::screenHasTouchDeviceOverride() || !WebCore::screenHasTouchDeviceOverride().value()) ++ return AvailableInputDevices::Mouse; + #if ENABLE(TOUCH_EVENTS) + return AvailableInputDevices::Touchscreen; + #else diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h index 5529f52048b24290f424e877cd9dbfb890e02ffb..c2b76b6188dd9596c4a1f31c137daff7d7644c7f 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h @@ -18522,7 +18452,7 @@ index 5529f52048b24290f424e877cd9dbfb890e02ffb..c2b76b6188dd9596c4a1f31c137daff7 virtual void unrealize() { }; virtual int renderHostFileDescriptor() { return -1; } diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp -index 461ce4fddb98edbf06be61d4b47f3256439f70a8..64b0c2e09a1ae8487bc8b2610f209a228a6988f6 100644 +index 395a409b6302d4146606fc0b45a15cff63c7f327..666d9629e0155e8c03b0672f7a9274de7f04d642 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp @@ -812,4 +812,30 @@ RefPtr AcceleratedBackingStoreDMABuf::bufferAsNativeImageF @@ -18557,7 +18487,7 @@ index 461ce4fddb98edbf06be61d4b47f3256439f70a8..64b0c2e09a1ae8487bc8b2610f209a22 + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h -index aedb88b1031475a603b6d15eb4fc042153c9b8c1..e6aca4b0e577dd2c729d262ff9b3d4d8fb27ef23 100644 +index 63dfbed03bd898c822d426a5449141fec2841226..eaf141843da3f02c5ba6fd0322f3175248a7195b 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h @@ -98,6 +98,7 @@ private: @@ -18570,7 +18500,7 @@ index aedb88b1031475a603b6d15eb4fc042153c9b8c1..e6aca4b0e577dd2c729d262ff9b3d4d8 RendererBufferFormat bufferFormat() const override; @@ -253,6 +254,9 @@ private: RefPtr m_committedBuffer; - WebCore::Region m_pendingDamageRegion; + Rects m_pendingDamageRects; HashMap> m_buffers; +// Playwright begin + RefPtr m_flippedSurface; @@ -18633,10 +18563,10 @@ index 0000000000000000000000000000000000000000..bf78de1915940c2d3292514cf0fe4e68 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/gtk/SystemSettingsManagerProxyGtk.cpp b/Source/WebKit/UIProcess/gtk/SystemSettingsManagerProxyGtk.cpp -index aa702b018d881739fb464246e7c7d7236adfe97b..f711e1608e6d8b988d2d0bc99893d5b0137a07ae 100644 +index 9ef483a6b0ab1558c059304d727311ea7984184d..f532d2d2dbe2d7cb66323b6ea5fe2daace15d7b8 100644 --- a/Source/WebKit/UIProcess/gtk/SystemSettingsManagerProxyGtk.cpp +++ b/Source/WebKit/UIProcess/gtk/SystemSettingsManagerProxyGtk.cpp -@@ -117,6 +117,8 @@ int SystemSettingsManagerProxy::xftDPI() const +@@ -126,6 +126,8 @@ int SystemSettingsManagerProxy::xftDPI() const bool SystemSettingsManagerProxy::followFontSystemSettings() const { @@ -18928,7 +18858,7 @@ index 0000000000000000000000000000000000000000..2aabc02a4b5432f68a6e85fd96897756 +} // namespace API diff --git a/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.mm b/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.mm new file mode 100644 -index 0000000000000000000000000000000000000000..a5c8b963636b24d4bb8ad090e4a19aedecbf56c3 +index 0000000000000000000000000000000000000000..a4b23bf4cf7c4cb6ef9d25d4121b20a611809479 --- /dev/null +++ b/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.mm @@ -0,0 +1,96 @@ @@ -18999,7 +18929,7 @@ index 0000000000000000000000000000000000000000..a5c8b963636b24d4bb8ad090e4a19aed + +std::unique_ptr InspectorPlaywrightAgentClientMac::createBrowserContext(WTF::String& error, const WTF::String& proxyServer, const WTF::String& proxyBypassList) +{ -+ _WKBrowserContext* wkBrowserContext = [[delegate_ createBrowserContext:proxyServer WithBypassList:proxyBypassList] autorelease]; ++ _WKBrowserContext* wkBrowserContext = [[delegate_ createBrowserContext:proxyServer.createNSString().get() WithBypassList:proxyBypassList.createNSString().get()] autorelease]; + auto browserContext = std::make_unique(); + browserContext->processPool = &static_cast([[wkBrowserContext processPool] _apiObject]); + browserContext->dataStore = &static_cast([[wkBrowserContext dataStore] _apiObject]); @@ -19077,10 +19007,10 @@ index 0000000000000000000000000000000000000000..8adbd51bfecad2a273117588bf50f8f7 + +#endif diff --git a/Source/WebKit/UIProcess/mac/PageClientImplMac.h b/Source/WebKit/UIProcess/mac/PageClientImplMac.h -index 51744982af6f55f269d407cfb82638a4eaba8a5c..3cb319ef5fd54de9397f8065a8b05ca7ff82ccee 100644 +index 8fc87c386fd82cdedc06ad1601d76919c6c27467..b5b2833b603e6996cf18c3416fde156940378f31 100644 --- a/Source/WebKit/UIProcess/mac/PageClientImplMac.h +++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.h -@@ -60,6 +60,8 @@ class PageClientImpl final : public PageClientImplCocoa +@@ -61,6 +61,8 @@ class PageClientImpl final : public PageClientImplCocoa WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(PageClientImpl); #endif public: @@ -19089,7 +19019,7 @@ index 51744982af6f55f269d407cfb82638a4eaba8a5c..3cb319ef5fd54de9397f8065a8b05ca7 PageClientImpl(NSView *, WKWebView *); virtual ~PageClientImpl(); -@@ -175,6 +177,9 @@ private: +@@ -176,6 +178,9 @@ private: void updateAcceleratedCompositingMode(const LayerTreeContext&) override; void didFirstLayerFlush(const LayerTreeContext&) override; @@ -19099,7 +19029,7 @@ index 51744982af6f55f269d407cfb82638a4eaba8a5c..3cb319ef5fd54de9397f8065a8b05ca7 RefPtr takeViewSnapshot(std::optional&&) override; RefPtr takeViewSnapshot(std::optional&&, ForceSoftwareCapturingViewportSnapshot) override; void wheelEventWasNotHandledByWebCore(const NativeWebWheelEvent&) override; -@@ -228,6 +233,10 @@ private: +@@ -229,6 +234,10 @@ private: void beganExitFullScreen(const WebCore::IntRect& initialFrame, const WebCore::IntRect& finalFrame, CompletionHandler&&) override; #endif @@ -19111,10 +19041,10 @@ index 51744982af6f55f269d407cfb82638a4eaba8a5c..3cb319ef5fd54de9397f8065a8b05ca7 void navigationGestureWillEnd(bool willNavigate, WebBackForwardListItem&) override; void navigationGestureDidEnd(bool willNavigate, WebBackForwardListItem&) override; diff --git a/Source/WebKit/UIProcess/mac/PageClientImplMac.mm b/Source/WebKit/UIProcess/mac/PageClientImplMac.mm -index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77effbabf664 100644 +index 5a5366f3f439d4727ca7dab149e0a523ccc27ce2..e02ab71cdf13af0d9b7b1aa04667c1dce54ed340 100644 --- a/Source/WebKit/UIProcess/mac/PageClientImplMac.mm +++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.mm -@@ -116,6 +116,13 @@ namespace WebKit { +@@ -110,6 +110,13 @@ namespace WebKit { using namespace WebCore; @@ -19128,7 +19058,7 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef PageClientImpl::PageClientImpl(NSView *view, WKWebView *webView) : PageClientImplCocoa(webView) , m_view(view) -@@ -169,6 +176,9 @@ NSWindow *PageClientImpl::activeWindow() const +@@ -163,6 +170,9 @@ NSWindow *PageClientImpl::activeWindow() const bool PageClientImpl::isViewWindowActive() { @@ -19136,9 +19066,9 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef + return true; + ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); - NSWindow *activeViewWindow = activeWindow(); - return activeViewWindow.isKeyWindow || (activeViewWindow && [NSApp keyWindow] == activeViewWindow); -@@ -176,6 +186,9 @@ bool PageClientImpl::isViewWindowActive() + RetainPtr activeViewWindow = activeWindow(); + return activeViewWindow.get().isKeyWindow || (activeViewWindow && [NSApp keyWindow] == activeViewWindow.get()); +@@ -170,6 +180,9 @@ bool PageClientImpl::isViewWindowActive() bool PageClientImpl::isViewFocused() { @@ -19148,17 +19078,17 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef // FIXME: This is called from the WebPageProxy constructor before we have a WebViewImpl. // Once WebViewImpl and PageClient merge, this won't be a problem. if (!m_impl) -@@ -199,6 +212,9 @@ void PageClientImpl::makeFirstResponder() +@@ -193,6 +206,9 @@ void PageClientImpl::makeFirstResponder() bool PageClientImpl::isViewVisible() { + if (_headless) + return true; + - NSView *activeView = this->activeView(); - NSWindow *activeViewWindow = activeWindow(); + RetainPtr activeView = this->activeView(); + RetainPtr activeViewWindow = activeWindow(); -@@ -282,7 +298,8 @@ void PageClientImpl::didRelaunchProcess() +@@ -267,7 +283,8 @@ void PageClientImpl::didRelaunchProcess() void PageClientImpl::preferencesDidChange() { @@ -19168,7 +19098,7 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef } void PageClientImpl::toolTipChanged(const String& oldToolTip, const String& newToolTip) -@@ -489,6 +506,8 @@ IntRect PageClientImpl::rootViewToAccessibilityScreen(const IntRect& rect) +@@ -475,6 +492,8 @@ IntRect PageClientImpl::rootViewToAccessibilityScreen(const IntRect& rect) void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool eventWasHandled) { @@ -19177,16 +19107,16 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef m_impl->doneWithKeyEvent(event.nativeEvent(), eventWasHandled); } -@@ -508,6 +527,8 @@ void PageClientImpl::computeHasVisualSearchResults(const URL& imageURL, Shareabl +@@ -494,6 +513,8 @@ void PageClientImpl::computeHasVisualSearchResults(const URL& imageURL, Shareabl RefPtr PageClientImpl::createPopupMenuProxy(WebPageProxy& page) { + if (_headless) + return nullptr; - return WebPopupMenuProxyMac::create(m_view, page.popupMenuClient()); + return WebPopupMenuProxyMac::create(m_view.get().get(), page.popupMenuClient()); } -@@ -643,6 +664,12 @@ CALayer *PageClientImpl::footerBannerLayer() const +@@ -634,6 +655,12 @@ CALayer *PageClientImpl::footerBannerLayer() const return m_impl->footerBannerLayer(); } @@ -19199,7 +19129,7 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef RefPtr PageClientImpl::takeViewSnapshot(std::optional&&) { return m_impl->takeViewSnapshot(); -@@ -858,6 +885,13 @@ void PageClientImpl::beganExitFullScreen(const IntRect& initialFrame, const IntR +@@ -850,6 +877,13 @@ void PageClientImpl::beganExitFullScreen(const IntRect& initialFrame, const IntR #endif // ENABLE(FULLSCREEN_API) @@ -19213,7 +19143,7 @@ index 6f5fd6287d435161dd7a7269fccc32cada2aabd5..79f171aaba64bca6a6bc853edb5b77ef void PageClientImpl::navigationGestureDidBegin() { m_impl->dismissContentRelativeChildWindowsWithAnimation(true); -@@ -1036,6 +1070,9 @@ void PageClientImpl::requestScrollToRect(const WebCore::FloatRect& targetRect, c +@@ -1030,6 +1064,9 @@ void PageClientImpl::requestScrollToRect(const WebCore::FloatRect& targetRect, c bool PageClientImpl::windowIsFrontWindowUnderMouse(const NativeWebMouseEvent& event) { @@ -19247,7 +19177,7 @@ index f46895285dbc84c624537a194814c18f771a0c08..29ef9e5afa13b8d2b47b7f2dd4ce3784 } +#endif diff --git a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h -index 1904f5ddedb75ee74aa84154fb9af248646c2fa2..af987387cc6537c225dd80515560c840319157f2 100644 +index a3c53c0bf913385d4d2d92900360d5f7d75927f8..e5570ef599ff1b59224648c353f8ab16f8fe7f88 100644 --- a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h +++ b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h @@ -81,6 +81,7 @@ private: @@ -19259,10 +19189,10 @@ index 1904f5ddedb75ee74aa84154fb9af248646c2fa2..af987387cc6537c225dd80515560c840 bool showAfterPostProcessingContextData(); diff --git a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm -index c962b29e655a421e3199505d2aa8546a83dbd583..85dc988495ba9b4eef48ee501afb5f854b687678 100644 +index 4cee7041f9b93d553b452447b33459832a5e4e36..2d5d79246c57be84ebf86f3935cd4583bf0cecdc 100644 --- a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm +++ b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm -@@ -538,6 +538,12 @@ RetainPtr WebContextMenuProxyMac::createShareMenuItem(ShareMenuItemT +@@ -540,6 +540,12 @@ RetainPtr WebContextMenuProxyMac::createShareMenuItem(ShareMenuItemT } #endif @@ -19467,10 +19397,18 @@ index 0000000000000000000000000000000000000000..dd52991f936aa1c046b404801ee97237 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.h b/Source/WebKit/UIProcess/mac/WebViewImpl.h -index 13b4e79c748071d02fedd8bced9ef0304943c51d..5830a3a3145b103a041cd2ff6666f8eff7666e3b 100644 +index a0e1fc432a9b4b8938051d39725a1ec36ebd7a1b..f2a331f460879e43e9b069f50a099f10bbf8b8d8 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.h +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.h -@@ -566,6 +566,9 @@ public: +@@ -35,6 +35,7 @@ + #include "WKLayoutMode.h" + #include "WKTextAnimationType.h" + #include ++#include + #include + #include + #include +@@ -569,6 +570,9 @@ public: void provideDataForPasteboard(NSPasteboard *, NSString *type); NSArray *namesOfPromisedFilesDroppedAtDestination(NSURL *dropDestination); @@ -19481,10 +19419,10 @@ index 13b4e79c748071d02fedd8bced9ef0304943c51d..5830a3a3145b103a041cd2ff6666f8ef RefPtr takeViewSnapshot(ForceSoftwareCapturingViewportSnapshot); void saveBackForwardSnapshotForCurrentItem(); diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.mm b/Source/WebKit/UIProcess/mac/WebViewImpl.mm -index 9fe23e004a89f08ca6583181b01a72b5025c1d93..3028a0946be2d5fe51d92fd1de5bc0668bbb55c8 100644 +index 1b42ae3cb6cdabee9830a1fb2ff55b9ae2325eb4..6998642cf147ec1af6e3feeb66d8688381791214 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.mm +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.mm -@@ -2415,6 +2415,11 @@ WebCore::DestinationColorSpace WebViewImpl::colorSpace() +@@ -2462,6 +2462,11 @@ WebCore::DestinationColorSpace WebViewImpl::colorSpace() if (!m_colorSpace) m_colorSpace = [NSColorSpace sRGBColorSpace]; } @@ -19496,7 +19434,7 @@ index 9fe23e004a89f08ca6583181b01a72b5025c1d93..3028a0946be2d5fe51d92fd1de5bc066 ASSERT(m_colorSpace); return WebCore::DestinationColorSpace { [m_colorSpace CGColorSpace] }; -@@ -4587,6 +4592,17 @@ static RetainPtr takeWindowSnapshot(CGSWindowID windowID, bool captu +@@ -4707,6 +4712,17 @@ static RetainPtr takeWindowSnapshot(CGSWindowID windowID, bool captu return WebCore::cgWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowID, imageOptions); } @@ -20424,10 +20362,10 @@ index 9b688ad328317fea4fd96ce66e9714bad8f0f937..402a36a9c565e13ec298aa7f014f0d92 } // namespace WebKit diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02fb0a39f7 100644 +index 07fb026a2a1ee93222d022a603b0057efbf848de..bedb13e953248741f32c61f8476e021233ce7e40 100644 --- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj +++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -@@ -1542,6 +1542,7 @@ +@@ -1546,6 +1546,7 @@ 5CABDC8722C40FED001EDE8E /* APIMessageListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CABDC8322C40FA7001EDE8E /* APIMessageListener.h */; }; 5CADDE05215046BD0067D309 /* WKWebProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C74300E21500492004BFA17 /* WKWebProcess.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5CAECB6627465AE400AB78D0 /* UnifiedSource115.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5CAECB5E27465AE300AB78D0 /* UnifiedSource115.cpp */; }; @@ -20435,7 +20373,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5CAF7AA726F93AB00003F19E /* adattributiond.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5CAF7AA526F93A950003F19E /* adattributiond.cpp */; }; 5CAFDE452130846300B1F7E1 /* _WKInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CAFDE422130843500B1F7E1 /* _WKInspector.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5CAFDE472130846A00B1F7E1 /* _WKInspectorInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CAFDE442130843600B1F7E1 /* _WKInspectorInternal.h */; }; -@@ -2323,6 +2324,18 @@ +@@ -2328,6 +2329,18 @@ DF0C5F28252ECB8E00D921DB /* WKDownload.h in Headers */ = {isa = PBXBuildFile; fileRef = DF0C5F24252ECB8D00D921DB /* WKDownload.h */; settings = {ATTRIBUTES = (Public, ); }; }; DF0C5F2A252ECB8E00D921DB /* WKDownloadDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF0C5F26252ECB8E00D921DB /* WKDownloadDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; DF0C5F2B252ED44000D921DB /* WKDownloadInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DF0C5F25252ECB8E00D921DB /* WKDownloadInternal.h */; }; @@ -20454,7 +20392,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 DF462E0F23F22F5500EFF35F /* WKHTTPCookieStorePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF462E0E23F22F5300EFF35F /* WKHTTPCookieStorePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; DF462E1223F338BE00EFF35F /* WKContentWorldPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF462E1123F338AD00EFF35F /* WKContentWorldPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; DF7A231C291B088D00B98DF3 /* WKSnapshotConfigurationPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF7A231B291B088D00B98DF3 /* WKSnapshotConfigurationPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; -@@ -2422,6 +2435,8 @@ +@@ -2427,6 +2440,8 @@ E5BEF6822130C48000F31111 /* WebDataListSuggestionsDropdownIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = E5BEF6802130C47F00F31111 /* WebDataListSuggestionsDropdownIOS.h */; }; E5CB07DC20E1678F0022C183 /* WKFormColorControl.h in Headers */ = {isa = PBXBuildFile; fileRef = E5CB07DA20E1678F0022C183 /* WKFormColorControl.h */; }; E5CBA76427A318E100DF7858 /* UnifiedSource120.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA75F27A3187800DF7858 /* UnifiedSource120.cpp */; }; @@ -20463,8 +20401,8 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 E5CBA76527A318E100DF7858 /* UnifiedSource118.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA76127A3187900DF7858 /* UnifiedSource118.cpp */; }; E5CBA76627A318E100DF7858 /* UnifiedSource116.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA76327A3187B00DF7858 /* UnifiedSource116.cpp */; }; E5CBA76727A318E100DF7858 /* UnifiedSource119.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA76027A3187900DF7858 /* UnifiedSource119.cpp */; }; -@@ -2447,6 +2462,9 @@ - ED82A7F2128C6FAF004477B3 /* WKBundlePageOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A22F0FF1289FCD90085E74F /* WKBundlePageOverlay.h */; settings = {ATTRIBUTES = (Private, ); }; }; +@@ -2456,6 +2471,9 @@ + EEA52F492D7055A700D578B5 /* WKStageMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0D3CDC2D3709BE00072978 /* WKStageMode.swift */; }; EEFE72792D64FE5600DC6214 /* StageModeInteractionState.h in Headers */ = {isa = PBXBuildFile; fileRef = EEFE72782D64FE5600DC6214 /* StageModeInteractionState.h */; }; F404455C2D5CFB56000E587E /* AppKitSoftLink.h in Headers */ = {isa = PBXBuildFile; fileRef = F404455A2D5CFB56000E587E /* AppKitSoftLink.h */; }; + F303B849249A8D640031DE5C /* ScreencastEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = F303B848249A8D3A0031DE5C /* ScreencastEncoder.h */; }; @@ -20473,7 +20411,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 F409BA181E6E64BC009DA28E /* WKDragDestinationAction.h in Headers */ = {isa = PBXBuildFile; fileRef = F409BA171E6E64B3009DA28E /* WKDragDestinationAction.h */; settings = {ATTRIBUTES = (Private, ); }; }; F40C3B712AB401C5007A3567 /* WKDatePickerPopoverController.h in Headers */ = {isa = PBXBuildFile; fileRef = F40C3B6F2AB40167007A3567 /* WKDatePickerPopoverController.h */; }; F41145682CD939E0004CDBD1 /* _WKTouchEventGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = F41145652CD939E0004CDBD1 /* _WKTouchEventGenerator.h */; settings = {ATTRIBUTES = (Private, ); }; }; -@@ -6307,6 +6325,7 @@ +@@ -6365,6 +6383,7 @@ 5CABDC8522C40FCC001EDE8E /* WKMessageListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKMessageListener.h; sourceTree = ""; }; 5CABE07A28F60E8A00D83FD9 /* WebPushMessage.serialization.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = WebPushMessage.serialization.in; sourceTree = ""; }; 5CADDE0D2151AA010067D309 /* AuthenticationChallengeDisposition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthenticationChallengeDisposition.h; sourceTree = ""; }; @@ -20481,7 +20419,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5CAECB5E27465AE300AB78D0 /* UnifiedSource115.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource115.cpp; sourceTree = ""; }; 5CAF7AA426F93A750003F19E /* adattributiond */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = adattributiond; sourceTree = BUILT_PRODUCTS_DIR; }; 5CAF7AA526F93A950003F19E /* adattributiond.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adattributiond.cpp; sourceTree = ""; }; -@@ -8025,6 +8044,19 @@ +@@ -8090,6 +8109,19 @@ DF0C5F24252ECB8D00D921DB /* WKDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDownload.h; sourceTree = ""; }; DF0C5F25252ECB8E00D921DB /* WKDownloadInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDownloadInternal.h; sourceTree = ""; }; DF0C5F26252ECB8E00D921DB /* WKDownloadDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDownloadDelegate.h; sourceTree = ""; }; @@ -20501,7 +20439,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 DF462E0E23F22F5300EFF35F /* WKHTTPCookieStorePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKHTTPCookieStorePrivate.h; sourceTree = ""; }; DF462E1123F338AD00EFF35F /* WKContentWorldPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKContentWorldPrivate.h; sourceTree = ""; }; DF58C6311371AC5800F9A37C /* NativeWebWheelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativeWebWheelEvent.h; sourceTree = ""; }; -@@ -8198,6 +8230,8 @@ +@@ -8263,6 +8295,8 @@ E5CBA76127A3187900DF7858 /* UnifiedSource118.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource118.cpp; sourceTree = ""; }; E5CBA76227A3187900DF7858 /* UnifiedSource117.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource117.cpp; sourceTree = ""; }; E5CBA76327A3187B00DF7858 /* UnifiedSource116.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource116.cpp; sourceTree = ""; }; @@ -20510,10 +20448,10 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 E5DEFA6726F8F42600AB68DB /* PhotosUISPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhotosUISPI.h; sourceTree = ""; }; EB0D312D275AE13300863D8F /* com.apple.webkit.webpushd.mac.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.webkit.webpushd.mac.plist; sourceTree = ""; }; EB0D312E275AE13300863D8F /* com.apple.webkit.webpushd.ios.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.webkit.webpushd.ios.plist; sourceTree = ""; }; -@@ -8234,6 +8268,14 @@ - F036978715F4BF0500C3A80E /* WebColorPicker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebColorPicker.cpp; sourceTree = ""; }; +@@ -8305,6 +8339,14 @@ F404455A2D5CFB56000E587E /* AppKitSoftLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppKitSoftLink.h; sourceTree = ""; }; F404455B2D5CFB56000E587E /* AppKitSoftLink.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AppKitSoftLink.mm; sourceTree = ""; }; + F4063DDE2D71481E00F3FE6E /* LLVMProfiling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LLVMProfiling.h; sourceTree = ""; }; + F303B847249A8D3A0031DE5C /* ScreencastEncoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScreencastEncoder.cpp; sourceTree = ""; }; + F303B848249A8D3A0031DE5C /* ScreencastEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreencastEncoder.h; sourceTree = ""; }; + F31E2DA424C76E4B004B2775 /* WebMFileWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebMFileWriter.cpp; sourceTree = ""; }; @@ -20525,7 +20463,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 F409BA171E6E64B3009DA28E /* WKDragDestinationAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDragDestinationAction.h; sourceTree = ""; }; F40C3B6F2AB40167007A3567 /* WKDatePickerPopoverController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WKDatePickerPopoverController.h; path = ios/forms/WKDatePickerPopoverController.h; sourceTree = ""; }; F40C3B702AB40167007A3567 /* WKDatePickerPopoverController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = WKDatePickerPopoverController.mm; path = ios/forms/WKDatePickerPopoverController.mm; sourceTree = ""; }; -@@ -8592,6 +8634,7 @@ +@@ -8710,6 +8752,7 @@ 3766F9EE189A1241003CF19B /* JavaScriptCore.framework in Frameworks */, 3766F9F1189A1254003CF19B /* libicucore.dylib in Frameworks */, 7B9FC5BB28A5233B007570E7 /* libWebKitPlatform.a in Frameworks */, @@ -20533,7 +20471,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 3766F9EF189A1244003CF19B /* QuartzCore.framework in Frameworks */, 37694525184FC6B600CDE21F /* Security.framework in Frameworks */, 37BEC4DD1948FC6A008B4286 /* WebCore.framework in Frameworks */, -@@ -11661,6 +11704,7 @@ +@@ -11841,6 +11884,7 @@ 99788ACA1F421DCA00C08000 /* _WKAutomationSessionConfiguration.mm */, 990D28A81C6404B000986977 /* _WKAutomationSessionDelegate.h */, 990D28AF1C65203900986977 /* _WKAutomationSessionInternal.h */, @@ -20541,7 +20479,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5C4609E222430E4C009943C2 /* _WKContentRuleListAction.h */, 5C4609E322430E4D009943C2 /* _WKContentRuleListAction.mm */, 5C4609E422430E4D009943C2 /* _WKContentRuleListActionInternal.h */, -@@ -13028,6 +13072,7 @@ +@@ -13218,6 +13262,7 @@ E34B110C27C46BC6006D2F2E /* libWebCoreTestShim.dylib */, E34B110F27C46D09006D2F2E /* libWebCoreTestSupport.dylib */, DDE992F4278D06D900F60D26 /* libWebKitAdditions.a */, @@ -20549,7 +20487,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 57A9FF15252C6AEF006A2040 /* libWTF.a */, 5750F32A2032D4E500389347 /* LocalAuthentication.framework */, 570DAAB0230273D200E8FC04 /* NearField.framework */, -@@ -13609,6 +13654,12 @@ +@@ -13799,6 +13844,12 @@ children = ( 9197940423DBC4BB00257892 /* InspectorBrowserAgent.cpp */, 9197940323DBC4BB00257892 /* InspectorBrowserAgent.h */, @@ -20562,7 +20500,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 ); path = Agents; sourceTree = ""; -@@ -13617,6 +13668,7 @@ +@@ -13807,6 +13858,7 @@ isa = PBXGroup; children = ( A5D3504D1D78F0D2005124A9 /* RemoteWebInspectorUIProxyMac.mm */, @@ -20570,7 +20508,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 1CA8B935127C774E00576C2B /* WebInspectorUIProxyMac.mm */, 99A7ACE326012919006D57FD /* WKInspectorResourceURLSchemeHandler.h */, 99A7ACE42601291A006D57FD /* WKInspectorResourceURLSchemeHandler.mm */, -@@ -14353,6 +14405,7 @@ +@@ -14560,6 +14612,7 @@ E1513C65166EABB200149FCB /* AuxiliaryProcessProxy.h */, 46A2B6061E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.cpp */, 46A2B6071E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.h */, @@ -20578,7 +20516,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5C6D69352AC3935D0099BDAF /* BrowsingContextGroup.cpp */, 5C6D69362AC3935D0099BDAF /* BrowsingContextGroup.h */, 5CA98549210BEB5A0057EB6B /* BrowsingWarning.h */, -@@ -14377,6 +14430,8 @@ +@@ -14584,6 +14637,8 @@ BC06F43912DBCCFB002D78DE /* GeolocationPermissionRequestProxy.cpp */, BC06F43812DBCCFB002D78DE /* GeolocationPermissionRequestProxy.h */, 2DD5A72A1EBF09A7009BA597 /* HiddenPageThrottlingAutoIncreasesCounter.h */, @@ -20587,15 +20525,15 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5CEABA2B2333251400797797 /* LegacyGlobalSettings.cpp */, 5CEABA2A2333247700797797 /* LegacyGlobalSettings.h */, 31607F3819627002009B87DA /* LegacySessionStateCoding.h */, -@@ -14410,6 +14465,7 @@ - 1A0C227D2451130A00ED614D /* QuickLookThumbnailingSoftLink.mm */, - 1AEE57232409F142002005D6 /* QuickLookThumbnailLoader.h */, - 1AEE57242409F142002005D6 /* QuickLookThumbnailLoader.mm */, +@@ -14613,6 +14668,7 @@ + 4683569B21E81CC7006E27A3 /* ProvisionalPageProxy.cpp */, + 4683569A21E81CC7006E27A3 /* ProvisionalPageProxy.h */, + 411B89CB27B2B89600F9EBD3 /* QueryPermissionResultCallback.h */, + D71A94392370F060002C4D9E /* RemoteInspectorPipe.h */, 5CCB54DC2A4FEA6A0005FAA8 /* RemotePageDrawingAreaProxy.cpp */, 5CCB54DB2A4FEA6A0005FAA8 /* RemotePageDrawingAreaProxy.h */, FABBBC802D35AC6800820017 /* RemotePageFullscreenManagerProxy.cpp */, -@@ -14512,6 +14568,8 @@ +@@ -14716,6 +14772,8 @@ BC7B6204129A0A6700D174A4 /* WebPageGroup.h */, 2D9EA3101A96D9EB002D2807 /* WebPageInjectedBundleClient.cpp */, 2D9EA30E1A96CBFF002D2807 /* WebPageInjectedBundleClient.h */, @@ -20604,7 +20542,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 9B7F8A502C785725000057F3 /* WebPageLoadTiming.h */, BC111B0B112F5E4F00337BAB /* WebPageProxy.cpp */, BC032DCB10F4389F0058C15A /* WebPageProxy.h */, -@@ -14690,6 +14748,7 @@ +@@ -14894,6 +14952,7 @@ BC646C1911DD399F006455B0 /* WKBackForwardListItemRef.h */, BC646C1611DD399F006455B0 /* WKBackForwardListRef.cpp */, BC646C1711DD399F006455B0 /* WKBackForwardListRef.h */, @@ -20612,7 +20550,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 BCB9E24A1120E15C00A137E0 /* WKContext.cpp */, BCB9E2491120E15C00A137E0 /* WKContext.h */, 1AE52F9319201F6B00A1FA37 /* WKContextConfigurationRef.cpp */, -@@ -15265,6 +15324,9 @@ +@@ -15468,6 +15527,9 @@ 07EF07592745A8160066EA04 /* DisplayCaptureSessionManager.h */, 07EF07582745A8160066EA04 /* DisplayCaptureSessionManager.mm */, 7AFA6F682A9F57C50055322A /* DisplayLinkMac.cpp */, @@ -20622,7 +20560,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 1AFDE65B1954E8D500C48FFA /* LegacySessionStateCoding.cpp */, 0FCB4E5818BBE3D9000FCFC9 /* PageClientImplMac.h */, 0FCB4E5918BBE3D9000FCFC9 /* PageClientImplMac.mm */, -@@ -15288,6 +15350,8 @@ +@@ -15491,6 +15553,8 @@ E568B92120A3AC6A00E3C856 /* WebDataListSuggestionsDropdownMac.mm */, E55CD20124D09F1F0042DB9C /* WebDateTimePickerMac.h */, E55CD20224D09F1F0042DB9C /* WebDateTimePickerMac.mm */, @@ -20631,7 +20569,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 BC857E8512B71EBB00EDEB2E /* WebPageProxyMac.mm */, BC5750951268F3C6006F0F12 /* WebPopupMenuProxyMac.h */, BC5750961268F3C6006F0F12 /* WebPopupMenuProxyMac.mm */, -@@ -16361,6 +16425,7 @@ +@@ -16577,6 +16641,7 @@ 99788ACB1F421DDA00C08000 /* _WKAutomationSessionConfiguration.h in Headers */, 990D28AC1C6420CF00986977 /* _WKAutomationSessionDelegate.h in Headers */, 990D28B11C65208D00986977 /* _WKAutomationSessionInternal.h in Headers */, @@ -20639,7 +20577,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5C4609E7224317B4009943C2 /* _WKContentRuleListAction.h in Headers */, 5C4609E8224317BB009943C2 /* _WKContentRuleListActionInternal.h in Headers */, 9B4CE9512CD99B7C00351173 /* _WKContentWorldConfiguration.h in Headers */, -@@ -16669,6 +16734,7 @@ +@@ -16885,6 +16950,7 @@ E170876C16D6CA6900F99226 /* BlobRegistryProxy.h in Headers */, 4F601432155C5AA2001FBDE0 /* BlockingResponseMap.h in Headers */, 1A5705111BE410E600874AF1 /* BlockSPI.h in Headers */, @@ -20647,7 +20585,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 5CA9854A210BEB640057EB6B /* BrowsingWarning.h in Headers */, A7E69BCC2B2117A100D43D3F /* BufferAndBackendInfo.h in Headers */, BC3065FA1259344E00E71278 /* CacheModel.h in Headers */, -@@ -16853,7 +16919,11 @@ +@@ -17067,7 +17133,11 @@ BC14DF77120B5B7900826C0C /* InjectedBundleScriptWorld.h in Headers */, CE550E152283752200D28791 /* InsertTextOptions.h in Headers */, 9197940523DBC4BB00257892 /* InspectorBrowserAgent.h in Headers */, @@ -20659,7 +20597,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 A5E391FD2183C1F800C8FB31 /* InspectorTargetProxy.h in Headers */, C5BCE5DF1C50766A00CDE3FA /* InteractionInformationAtPosition.h in Headers */, 2D4D2C811DF60BF3002EB10C /* InteractionInformationRequest.h in Headers */, -@@ -17115,6 +17185,7 @@ +@@ -17328,6 +17398,7 @@ 0F6E7C532C4C386800F1DB85 /* RemoteDisplayListRecorderMessages.h in Headers */, F451C0FE2703B263002BA03B /* RemoteDisplayListRecorderProxy.h in Headers */, A78A5FE42B0EB39E005036D3 /* RemoteImageBufferSetIdentifier.h in Headers */, @@ -20667,15 +20605,15 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 2D47B56D1810714E003A3AEE /* RemoteLayerBackingStore.h in Headers */, 2DDF731518E95060004F5A66 /* RemoteLayerBackingStoreCollection.h in Headers */, 1AB16AEA164B3A8800290D62 /* RemoteLayerTreeContext.h in Headers */, -@@ -17170,6 +17241,7 @@ +@@ -17384,6 +17455,7 @@ E1E552C516AE065F004ED653 /* SandboxInitializationParameters.h in Headers */, E36FF00327F36FBD004BE21A /* SandboxStateVariables.h in Headers */, 7BAB111025DD02B3008FC479 /* ScopedActiveMessageReceiveQueue.h in Headers */, + F303B849249A8D640031DE5C /* ScreencastEncoder.h in Headers */, + 6D4DF20C2D824242001F964C /* ScreenTimeWebsiteDataSupport.h in Headers */, 463BB93A2B9D08D80098C5C3 /* ScriptMessageHandlerIdentifier.h in Headers */, F4E28A362C923814008120DD /* ScriptTelemetry.h in Headers */, - E4D54D0421F1D72D007E3C36 /* ScrollingTreeFrameScrollingNodeRemoteIOS.h in Headers */, -@@ -17530,6 +17602,8 @@ +@@ -17745,6 +17817,8 @@ 939EF87029D112EE00F23AEE /* WebPageInlines.h in Headers */, 9197940823DBC4CB00257892 /* WebPageInspectorAgentBase.h in Headers */, A513F5402154A5D700662841 /* WebPageInspectorController.h in Headers */, @@ -20684,7 +20622,7 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 A543E30C215C8A8D00279CD9 /* WebPageInspectorTarget.h in Headers */, A543E30D215C8A9000279CD9 /* WebPageInspectorTargetController.h in Headers */, A543E307215AD13700279CD9 /* WebPageInspectorTargetFrontendChannel.h in Headers */, -@@ -20064,7 +20138,43 @@ +@@ -20348,7 +20422,43 @@ 522F792928D50EBB0069B45B /* HidService.mm in Sources */, 2749F6442146561B008380BF /* InjectedBundleNodeHandle.cpp in Sources */, 2749F6452146561E008380BF /* InjectedBundleRangeHandle.cpp in Sources */, @@ -20728,20 +20666,20 @@ index 53f900a15956048fd25f9b0d54d6b74580612abb..133eaf568fa054d93ebf3a41e8f78b02 1C5DC45F2909B05A0061EC62 /* JSWebExtensionWrapperCocoa.mm in Sources */, C14D37FE24ACE086007FF014 /* LaunchServicesDatabaseManager.mm in Sources */, C1710CF724AA643200D7C112 /* LaunchServicesDatabaseObserver.mm in Sources */, -@@ -20425,6 +20535,8 @@ +@@ -20746,6 +20856,8 @@ 074E87E12CF8EA3D0059E469 /* WebPage+NavigationDeciding.swift in Sources */, 078B04A02CF18EAB00B453A6 /* WebPage+NavigationPreferences.swift in Sources */, 07CB79962CE9435700199C49 /* WebPage.swift in Sources */, + D79902B1236E9404005D6F7E /* WebPageInspectorEmulationAgentMac.mm in Sources */, + D79902B3236E9404005D6F7E /* WebPageInspectorInputAgentMac.mm in Sources */, 7CE9CE101FA0767A000177DE /* WebPageUpdatePreferences.cpp in Sources */, + 079A4DA12D72CC0D00CA387F /* WebPageWebView.swift in Sources */, 7CEB00DD1FA69ABE0065473B /* WebPreferencesFeatures.cpp in Sources */, - 7CF1907125338F3800ABE183 /* WebPreferencesGetterSetters.cpp in Sources */, diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp -index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c9b213463 100644 +index ae80809efafab3264b06e7bac45771c72d4408b8..849694aea1738dac5e2a0741f31def13c34c8608 100644 --- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp +++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp -@@ -247,6 +247,11 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou +@@ -269,6 +269,11 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou } #endif @@ -20753,40 +20691,39 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c #if ENABLE(PDFJS) if (tryLoadingUsingPDFJSHandler(resourceLoader, trackingParameters)) return; -@@ -256,12 +261,16 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou - return; +@@ -283,12 +288,16 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou + } if (InspectorInstrumentationWebKit::shouldInterceptRequest(resourceLoader)) { -- InspectorInstrumentationWebKit::interceptRequest(resourceLoader, [this, protectedResourceLoader = Ref { resourceLoader }, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, resource](const ResourceRequest& request) { +- InspectorInstrumentationWebKit::interceptRequest(resourceLoader, [this, protectedThis = Ref { *this }, protectedResourceLoader = Ref { resourceLoader }, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, resource](const ResourceRequest& request) { - auto& resourceLoader = protectedResourceLoader.get(); - WEBLOADERSTRATEGY_RELEASE_LOG("scheduleLoad: intercepted URL will be scheduled with the NetworkProcess"); -- scheduleLoadFromNetworkProcess(resourceLoader, request, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, maximumBufferingTime(resource)); +- scheduleLoadFromNetworkProcess(resourceLoader, request, *trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, maximumBufferingTime(resource)); - }); - return; + bool isMainFrameNavigation = resourceLoader.frame() && resourceLoader.frame()->isMainFrame() && resourceLoader.options().mode == FetchOptions::Mode::Navigate; + // Do not intercept navigation request which could already have been intercepted and resumed. + if (!(isMainFrameNavigation && m_existingNetworkResourceLoadIdentifierToResume)) { -+ InspectorInstrumentationWebKit::interceptRequest(resourceLoader, [this, protectedResourceLoader = Ref { resourceLoader }, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, resource](const ResourceRequest& request) { ++ InspectorInstrumentationWebKit::interceptRequest(resourceLoader, [this, protectedThis = Ref { *this }, protectedResourceLoader = Ref { resourceLoader }, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, resource](const ResourceRequest& request) { + auto& resourceLoader = protectedResourceLoader.get(); + WEBLOADERSTRATEGY_RELEASE_LOG("scheduleLoad: intercepted URL will be scheduled with the NetworkProcess"); -+ scheduleLoadFromNetworkProcess(resourceLoader, request, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, maximumBufferingTime(resource)); ++ scheduleLoadFromNetworkProcess(resourceLoader, request, *trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, maximumBufferingTime(resource)); + }); + return; + } } WEBLOADERSTRATEGY_RELEASE_LOG_FORWARDABLE(WEBLOADERSTRATEGY_SCHEDULELOAD); -@@ -383,7 +392,8 @@ static void addParametersShared(const LocalFrame* frame, NetworkResourceLoadPara +@@ -413,7 +422,7 @@ static void addParametersShared(const LocalFrame* frame, NetworkResourceLoadPara parameters.linkPreconnectEarlyHintsEnabled = mainFrame->settings().linkPreconnectEarlyHintsEnabled(); } -void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceLoader, const ResourceRequest& request, const WebResourceLoader::TrackingParameters& trackingParameters, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Seconds maximumBufferingTime) -+// static +bool WebLoaderStrategy::fillParametersForNetworkProcessLoad(ResourceLoader& resourceLoader, const ResourceRequest& request, const WebResourceLoader::TrackingParameters& trackingParameters, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Seconds maximumBufferingTime, NetworkResourceLoadParameters& loadParameters) { auto identifier = *resourceLoader.identifier(); -@@ -395,10 +405,10 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -425,10 +434,10 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL && resourceLoader.frameLoader()->notifier().isInitialRequestIdentifier(identifier) ? MainFrameMainResource::Yes : MainFrameMainResource::No; if (!page->allowsLoadFromURL(request.url(), mainFrameMainResource)) { @@ -20799,15 +20736,22 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c } } -@@ -408,7 +418,6 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -438,14 +447,6 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL LOG(NetworkScheduling, "(WebProcess) WebLoaderStrategy::scheduleLoad, url '%s' will be scheduled with the NetworkProcess with priority %d, storedCredentialsPolicy %i", resourceLoader.url().string().latin1().data(), static_cast(resourceLoader.request().priority()), (int)storedCredentialsPolicy); -- NetworkResourceLoadParameters loadParameters; +- NetworkResourceLoadParameters loadParameters { +- trackingParameters.webPageProxyID, +- trackingParameters.pageID, +- trackingParameters.frameID, +- request +- }; +- loadParameters.createSandboxExtensionHandlesIfNecessary(); +- loadParameters.identifier = identifier; - loadParameters.webPageProxyID = trackingParameters.webPageProxyID; - loadParameters.webPageID = trackingParameters.pageID; -@@ -499,14 +508,11 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL + loadParameters.parentPID = legacyPresentingApplicationPID(); + loadParameters.contentSniffingPolicy = contentSniffingPolicy; +@@ -529,14 +530,11 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL if (loadParameters.options.mode != FetchOptions::Mode::Navigate) { ASSERT(loadParameters.sourceOrigin); @@ -20825,25 +20769,33 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c loadParameters.isMainFrameNavigation = isMainFrameNavigation; if (loadParameters.isMainFrameNavigation && document) -@@ -547,6 +553,17 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL - } +@@ -583,6 +581,25 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL + if (RefPtr frameLoader = resourceLoader.frameLoader()) + loadParameters.requiredCookiesVersion = frameLoader->requiredCookiesVersion(); - ASSERT((loadParameters.webPageID && loadParameters.webFrameID) || loadParameters.clientCredentialPolicy == ClientCredentialPolicy::CannotAskClientForCredentials); + return true; +} + +void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceLoader, const ResourceRequest& request, const WebResourceLoader::TrackingParameters& trackingParameters, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Seconds maximumBufferingTime) +{ -+ NetworkResourceLoadParameters loadParameters; ++ NetworkResourceLoadParameters loadParameters { ++ trackingParameters.webPageProxyID, ++ trackingParameters.pageID, ++ trackingParameters.frameID, ++ request ++ }; ++ loadParameters.createSandboxExtensionHandlesIfNecessary(); ++ + if (!fillParametersForNetworkProcessLoad(resourceLoader, request, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, maximumBufferingTime, loadParameters)) { + WEBLOADERSTRATEGY_RELEASE_LOG_ERROR("scheduleLoad: no sourceOrigin (priority=%d)", static_cast(resourceLoader.request().priority())); + scheduleInternallyFailedLoad(resourceLoader); + return; + } - ++ std::optional existingNetworkResourceLoadIdentifierToResume; if (loadParameters.isMainFrameNavigation) -@@ -562,7 +579,7 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL + existingNetworkResourceLoadIdentifierToResume = std::exchange(m_existingNetworkResourceLoadIdentifierToResume, std::nullopt); +@@ -597,7 +614,7 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL } auto loader = WebResourceLoader::create(resourceLoader, trackingParameters); @@ -20852,7 +20804,7 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c } void WebLoaderStrategy::scheduleInternallyFailedLoad(WebCore::ResourceLoader& resourceLoader) -@@ -966,7 +983,7 @@ void WebLoaderStrategy::didFinishPreconnection(WebCore::ResourceLoaderIdentifier +@@ -1015,7 +1032,7 @@ void WebLoaderStrategy::didFinishPreconnection(WebCore::ResourceLoaderIdentifier bool WebLoaderStrategy::isOnLine() const { @@ -20861,7 +20813,7 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c } void WebLoaderStrategy::addOnlineStateChangeListener(Function&& listener) -@@ -993,6 +1010,11 @@ void WebLoaderStrategy::isResourceLoadFinished(CachedResource& resource, Complet +@@ -1042,6 +1059,11 @@ void WebLoaderStrategy::isResourceLoadFinished(CachedResource& resource, Complet void WebLoaderStrategy::setOnLineState(bool isOnLine) { @@ -20873,7 +20825,7 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c if (m_isOnLine == isOnLine) return; -@@ -1001,6 +1023,12 @@ void WebLoaderStrategy::setOnLineState(bool isOnLine) +@@ -1050,6 +1072,12 @@ void WebLoaderStrategy::setOnLineState(bool isOnLine) listener(isOnLine); } @@ -20887,24 +20839,24 @@ index ac0032c3bf918b6dcadd21c18700f1adb74e9a78..4f55fe73551265aa22f743e7ec006e2c { WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::SetCaptureExtraNetworkLoadMetricsEnabled(enabled), 0); diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h -index dc88916914daebf7c421dc56c2be70fd47727a96..829fdad0490a5e4db8822fbc48c5f19a2c2e0703 100644 +index 6492031de6ed6effab3f28e5321419f3390f7651..678139fc26f26e2224a70b0087ddb849280b8ace 100644 --- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h +++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h -@@ -44,6 +44,7 @@ struct FetchOptions; - namespace WebKit { +@@ -26,6 +26,7 @@ + #pragma once - class NetworkProcessConnection; -+class NetworkResourceLoadParameters; - class WebFrame; - class WebPage; - class WebProcess; + #include "NetworkResourceLoadIdentifier.h" ++#include "NetworkResourceLoadParameters.h" + #include "WebResourceLoader.h" + #include + #include @@ -96,6 +97,9 @@ public: bool isOnLine() const final; void addOnlineStateChangeListener(Function&&) final; void setOnLineState(bool); + void setEmulateOfflineState(bool) final; + -+ static bool fillParametersForNetworkProcessLoad(WebCore::ResourceLoader&, const WebCore::ResourceRequest&, const WebResourceLoader::TrackingParameters&, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Seconds maximumBufferingTime, NetworkResourceLoadParameters&); ++ bool fillParametersForNetworkProcessLoad(WebCore::ResourceLoader&, const WebCore::ResourceRequest&, const WebResourceLoader::TrackingParameters&, bool shouldClearReferrerOnHTTPSToHTTPRedirect, Seconds maximumBufferingTime, NetworkResourceLoadParameters&); void setExistingNetworkResourceLoadIdentifierToResume(std::optional existingNetworkResourceLoadIdentifierToResume) { m_existingNetworkResourceLoadIdentifierToResume = existingNetworkResourceLoadIdentifierToResume; } @@ -20917,7 +20869,7 @@ index dc88916914daebf7c421dc56c2be70fd47727a96..829fdad0490a5e4db8822fbc48c5f19a } // namespace WebKit diff --git a/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp b/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp -index f0c21a35614b000e0a835ef4a4038feb0224842b..f9aecb9ed77192c00a3173521f72bd840ea6b571 100644 +index 9add95853b90e6cae9dd4627ab8bf9a9006bc7a8..f3af1c6680d1dc6d8f6db951a26a526d7079cd95 100644 --- a/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp +++ b/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp @@ -202,9 +202,6 @@ void WebResourceLoader::didReceiveResponse(ResourceResponse&& response, PrivateR @@ -20940,17 +20892,17 @@ index f0c21a35614b000e0a835ef4a4038feb0224842b..f9aecb9ed77192c00a3173521f72bd84 } diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp -index 33b01034cea7fc023ba4bef55142ae2700d5e48c..0b0236832df43047a7808af0d422a4572231dc5e 100644 +index e532ffcbe13d86fa0de4fbb2c63214615539ba2d..77cf20de73c57756eea1c10349fa666ce733b304 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp -@@ -490,6 +490,8 @@ void WebChromeClient::addMessageToConsole(MessageSource source, MessageLevel lev +@@ -491,6 +491,8 @@ void WebChromeClient::addMessageToConsole(MessageSource source, MessageLevel lev { // Notify the bundle client. auto page = protectedPage(); + if (level == MessageLevel::Error) + page->send(Messages::WebPageProxy::LogToStderr(message)); + // FIXME: Remove this after rdar://143399667 is fixed. page->injectedBundleUIClient().willAddMessageToConsole(page.ptr(), source, level, message, lineNumber, columnNumber, sourceID); - } diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebDragClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebDragClient.cpp index dd03326b1ad54e1d363d722cfe86bb779fbeead1..f54d631dc4034c995cbd74ec6b545c3c7dd25990 100644 @@ -20966,7 +20918,7 @@ index dd03326b1ad54e1d363d722cfe86bb779fbeead1..f54d631dc4034c995cbd74ec6b545c3c { } diff --git a/Source/WebKit/WebProcess/WebCoreSupport/mac/WebDragClientMac.mm b/Source/WebKit/WebProcess/WebCoreSupport/mac/WebDragClientMac.mm -index 2713e431893efd17c95d163f14aec45b27e0d6fc..b96b1d9cba20af611837915412fa4202667e800d 100644 +index 6bd8b7c5de08a061c0a155f4783affe9050b8103..610f2b7870588de14f6b658967a9f9acb1a01baa 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/mac/WebDragClientMac.mm +++ b/Source/WebKit/WebProcess/WebCoreSupport/mac/WebDragClientMac.mm @@ -128,7 +128,8 @@ static WebCore::CachedImage* cachedImage(Element& element) @@ -21106,7 +21058,7 @@ index 0000000000000000000000000000000000000000..226b3bf6bd83d2606a0aeb627ae9302f + +#endif // ENABLE(DRAG_SUPPORT) diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp -index ac7fa01ba9a3828cf4c48cacc862aab95145093e..3246812f8ebb8833b2172f9f5f00dff6053387c9 100644 +index ac7fa01ba9a3828cf4c48cacc862aab95145093e..89414a407a49d30b937f26cd9cd570c59c51aab3 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp @@ -39,6 +39,7 @@ @@ -21117,32 +21069,8 @@ index ac7fa01ba9a3828cf4c48cacc862aab95145093e..3246812f8ebb8833b2172f9f5f00dff6 #include #include #include -@@ -562,6 +563,11 @@ void DrawingAreaCoordinatedGraphics::enterAcceleratedCompositingMode(GraphicsLay - m_scrollOffset = IntSize(); - m_displayTimer.stop(); - m_isWaitingForDidUpdate = false; -+// Playwright begin -+#if PLATFORM(WIN) -+ didChangeAcceleratedCompositingMode(true); -+#endif -+// Playwright end - } - - void DrawingAreaCoordinatedGraphics::sendEnterAcceleratedCompositingModeIfNeeded() -@@ -619,6 +625,11 @@ void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingMode() - // UI process, we still need to let it know about the new contents, so send an Update message. - send(Messages::DrawingAreaProxy::Update(0, WTFMove(updateInfo))); - } -+// Playwright begin -+#if PLATFORM(WIN) -+ didChangeAcceleratedCompositingMode(false); -+#endif -+// Playwright end - } - - void DrawingAreaCoordinatedGraphics::scheduleDisplay() diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h -index f19473587ec9f1ce8fb2e249feaf1e45b0ec1b80..0908807628b0f96925d3b669b6962f3553f422ca 100644 +index 2b955369c53a76ca65613a549bff0d490db48a4c..f67651d2cc64a7f164ff91ad5f613a661976bdbb 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h @@ -138,6 +138,7 @@ public: @@ -21154,7 +21082,7 @@ index f19473587ec9f1ce8fb2e249feaf1e45b0ec1b80..0908807628b0f96925d3b669b6962f35 void updateRootLayer(); WebCore::FloatRect visibleContentsRect() const; diff --git a/Source/WebKit/WebProcess/WebPage/DrawingArea.cpp b/Source/WebKit/WebProcess/WebPage/DrawingArea.cpp -index 5ba445d43ad37ff55f239660cddeada7abe2a6e8..ee3bfd8a34d4e7ea4d632cd8524457f5fb57ef15 100644 +index c07ca9e847d03c27877cf650b67b96b3c3f7bddb..b966edc8778d4a68c6e5bae0c0883ab3bfd0a387 100644 --- a/Source/WebKit/WebProcess/WebPage/DrawingArea.cpp +++ b/Source/WebKit/WebProcess/WebPage/DrawingArea.cpp @@ -27,6 +27,7 @@ @@ -21165,34 +21093,6 @@ index 5ba445d43ad37ff55f239660cddeada7abe2a6e8..ee3bfd8a34d4e7ea4d632cd8524457f5 #include "Logging.h" #include "WebPage.h" #include "WebPageCreationParameters.h" -@@ -115,6 +116,13 @@ void DrawingArea::tryMarkLayersVolatile(CompletionHandler&& completi - completionFunction(true); - } - -+#if PLATFORM(WIN) -+void DrawingArea::didChangeAcceleratedCompositingMode(bool enabled) -+{ -+ send(Messages::DrawingAreaProxy::DidChangeAcceleratedCompositingMode(enabled)); -+} -+#endif -+ - void DrawingArea::removeMessageReceiverIfNeeded() - { - if (m_hasRemovedMessageReceiver) -diff --git a/Source/WebKit/WebProcess/WebPage/DrawingArea.h b/Source/WebKit/WebProcess/WebPage/DrawingArea.h -index 6c61c9a0ee5b7a11408d5a0a06f37035e9601e25..a5ddf926e95182bd3575147aff2123f3e6a77109 100644 ---- a/Source/WebKit/WebProcess/WebPage/DrawingArea.h -+++ b/Source/WebKit/WebProcess/WebPage/DrawingArea.h -@@ -172,6 +172,9 @@ public: - virtual bool enterAcceleratedCompositingModeIfNeeded() = 0; - virtual void backgroundColorDidChange() { }; - #endif -+#if PLATFORM(WIN) -+ void didChangeAcceleratedCompositingMode(bool enabled); -+#endif - - #if PLATFORM(WPE) && USE(GBM) && ENABLE(WPE_PLATFORM) - virtual void preferredBufferFormatsDidChange() { } diff --git a/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp b/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp index 70a6c3c9ed0fcf42393df2c0fb66eb50d290030d..06fb88ca78db70de4bd4ed9cdf33172bee1a958e 100644 --- a/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp @@ -21232,10 +21132,10 @@ index f9c1d07d4e1f4b5421a627a179ed4f86cf1a1b92..9a8089ed85eebbfb8af0804dfbc0698b void setOptInCookiePartitioningEnabled(bool); #endif diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp -index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba155ecaf80 100644 +index 4f0c774675bcc9adc7fddd608953bde8840e30dd..224289ffe67a7e16f93d8373eb573849dfb38b06 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp -@@ -241,6 +241,7 @@ +@@ -243,6 +243,7 @@ #include #include #include @@ -21243,7 +21143,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 #include #include #include -@@ -1156,6 +1157,12 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) +@@ -1152,6 +1153,12 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) setLinkDecorationFilteringData(WTFMove(parameters.linkDecorationFilteringData)); setAllowedQueryParametersForAdvancedPrivacyProtections(WTFMove(parameters.allowedQueryParametersForAdvancedPrivacyProtections)); #endif @@ -21279,7 +21179,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 void WebPage::loadRequest(LoadParameters&& loadParameters) { WEBPAGE_RELEASE_LOG_FORWARDABLE(Loading, WEBPAGE_LOADREQUEST, loadParameters.navigationID ? loadParameters.navigationID->toUInt64() : 0, static_cast(loadParameters.shouldTreatAsContinuingLoad), loadParameters.request.isAppInitiated(), loadParameters.existingNetworkResourceLoadIdentifierToResume ? loadParameters.existingNetworkResourceLoadIdentifierToResume->toUInt64() : 0); -@@ -2289,7 +2312,9 @@ void WebPage::stopLoading() +@@ -2290,7 +2313,9 @@ void WebPage::stopLoading() void WebPage::stopLoadingDueToProcessSwap() { SetForScope isStoppingLoadingDueToProcessSwap(m_isStoppingLoadingDueToProcessSwap, true); @@ -21289,7 +21189,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 } bool WebPage::defersLoading() const -@@ -2865,7 +2890,7 @@ void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArgum +@@ -2866,7 +2891,7 @@ void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArgum #if PLATFORM(IOS_FAMILY) if (m_viewportConfiguration.setViewportArguments(viewportArguments)) viewportConfigurationChanged(); @@ -21298,7 +21198,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 // Adjust view dimensions when using fixed layout. RefPtr localMainFrame = this->localMainFrame(); RefPtr view = localMainFrame ? localMainFrame->view() : nullptr; -@@ -3652,6 +3677,13 @@ void WebPage::flushDeferredScrollEvents() +@@ -3623,6 +3648,13 @@ void WebPage::flushDeferredScrollEvents() protectedCorePage()->flushDeferredScrollEvents(); } @@ -21312,7 +21212,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 void WebPage::flushDeferredDidReceiveMouseEvent() { if (auto info = std::exchange(m_deferredDidReceiveMouseEvent, std::nullopt)) -@@ -3913,6 +3945,97 @@ void WebPage::touchEvent(const WebTouchEvent& touchEvent, CompletionHandlersendMessageToTargetBackend(targetId, message); } @@ -21427,7 +21327,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 void WebPage::insertNewlineInQuotedContent() { RefPtr frame = protectedCorePage()->checkedFocusController()->focusedOrMainFrame(); -@@ -4251,6 +4384,7 @@ void WebPage::setMainFrameDocumentVisualUpdatesAllowed(bool allowed) +@@ -4215,6 +4348,7 @@ void WebPage::setMainFrameDocumentVisualUpdatesAllowed(bool allowed) void WebPage::show() { send(Messages::WebPageProxy::ShowPage()); @@ -21435,7 +21335,7 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 } void WebPage::setIsTakingSnapshotsForApplicationSuspension(bool isTakingSnapshotsForApplicationSuspension) -@@ -5439,7 +5573,7 @@ NotificationPermissionRequestManager* WebPage::notificationPermissionRequestMana +@@ -5426,7 +5560,7 @@ RefPtr WebPage::protectedNotificationPermi #if ENABLE(DRAG_SUPPORT) @@ -21444,8 +21344,8 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 void WebPage::performDragControllerAction(DragControllerAction action, const IntPoint& clientPosition, const IntPoint& globalPosition, OptionSet draggingSourceOperationMask, SelectionData&& selectionData, OptionSet flags, CompletionHandler, DragHandlingMethod, bool, unsigned, IntRect, IntRect, std::optional)>&& completionHandler) { if (!m_page) -@@ -7914,6 +8048,10 @@ void WebPage::didCommitLoad(WebFrame* frame) - #endif +@@ -7913,6 +8047,10 @@ void WebPage::didCommitLoad(WebFrame* frame) + m_needsFixedContainerEdgesUpdate = true; flushDeferredDidReceiveMouseEvent(); +// Playwright begin @@ -21455,9 +21355,9 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 } void WebPage::didFinishDocumentLoad(WebFrame& frame) -@@ -8233,6 +8371,9 @@ Ref WebPage::createDocumentLoader(LocalFrame& frame, const Resou - WebsitePoliciesData::applyToDocumentLoader(WTFMove(*m_internals->pendingWebsitePolicies), documentLoader); - m_internals->pendingWebsitePolicies = std::nullopt; +@@ -8222,6 +8360,9 @@ Ref WebPage::createDocumentLoader(LocalFrame& frame, const Resou + m_allowsContentJavaScriptFromMostRecentNavigation = m_internals->pendingWebsitePolicies->allowsContentJavaScript; + WebsitePoliciesData::applyToDocumentLoader(*std::exchange(m_internals->pendingWebsitePolicies, std::nullopt), documentLoader); } + } else if (m_pendingFrameNavigationID) { + documentLoader->setNavigationID(*m_pendingFrameNavigationID); @@ -21466,10 +21366,10 @@ index a790490171adbb2bd153e84f37df524099d11496..6aa0bc62374ce01171b04d7025105ba1 return documentLoader; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h -index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6808152b0 100644 +index 8bd2bc55df904c4d23bf47d38981581faf89b2af..c0f2d51469875510b65b252b2ba4c15ef252b9c6 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit/WebProcess/WebPage/WebPage.h -@@ -43,6 +43,7 @@ +@@ -45,6 +45,7 @@ #include #include #include @@ -21477,7 +21377,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 #include #include #include -@@ -1256,11 +1257,11 @@ public: +@@ -1272,11 +1273,11 @@ public: void clearSelection(); void restoreSelectionInFocusedEditableElement(); @@ -21491,7 +21391,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 void performDragControllerAction(std::optional, DragControllerAction, WebCore::DragData&&, CompletionHandler, WebCore::DragHandlingMethod, bool, unsigned, WebCore::IntRect, WebCore::IntRect, std::optional)>&&); void performDragOperation(WebCore::DragData&&, SandboxExtensionHandle&&, Vector&&, CompletionHandler&&); #endif -@@ -1275,6 +1276,9 @@ public: +@@ -1291,6 +1292,9 @@ public: void didStartDrag(); void dragCancelled(); OptionSet allowedDragSourceActions() const { return m_allowedDragSourceActions; } @@ -21501,7 +21401,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 #endif #if ENABLE(MODEL_PROCESS) -@@ -1361,8 +1365,11 @@ public: +@@ -1377,8 +1381,11 @@ public: void gestureEvent(WebCore::FrameIdentifier, const WebGestureEvent&, CompletionHandler, bool, std::optional)>&&); #endif @@ -21514,7 +21414,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 void dynamicViewportSizeUpdate(const DynamicViewportSizeUpdate&); bool scaleWasSetByUIProcess() const { return m_scaleWasSetByUIProcess; } void willStartUserTriggeredZooming(); -@@ -1513,6 +1520,8 @@ public: +@@ -1531,6 +1538,8 @@ public: void connectInspector(const String& targetId, Inspector::FrontendChannel::ConnectionType); void disconnectInspector(const String& targetId); void sendMessageToTargetBackend(const String& targetId, const String& message); @@ -21523,7 +21423,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 void insertNewlineInQuotedContent(); -@@ -1930,6 +1939,7 @@ public: +@@ -1949,6 +1958,7 @@ public: void showContextMenuFromFrame(const FrameInfoData&, const ContextMenuContextData&, const UserData&); #endif void loadRequest(LoadParameters&&); @@ -21531,7 +21431,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 void setObscuredContentInsets(const WebCore::FloatBoxExtent&); -@@ -2121,6 +2131,7 @@ private: +@@ -2145,6 +2155,7 @@ private: void updatePotentialTapSecurityOrigin(const WebTouchEvent&, bool wasHandled); #elif ENABLE(TOUCH_EVENTS) void touchEvent(const WebTouchEvent&, CompletionHandler, bool)>&&); @@ -21539,7 +21439,7 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 #endif void cancelPointer(WebCore::PointerID, const WebCore::IntPoint&); -@@ -2884,6 +2895,7 @@ private: +@@ -2913,6 +2924,7 @@ private: UserActivity m_userActivity; Markable m_pendingNavigationID; @@ -21548,10 +21448,10 @@ index 889f9a1c62a652c3de5e5edb14c9781932b8ef7b..1a708ef7835d63cdaf60db1eb4d5ade6 bool m_mainFrameProgressCompleted { false }; bool m_shouldDispatchFakeMouseMoveEvents { true }; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in -index d38d0ecfb0e77838cc6d24b199c8be5d30d13953..1b6ddebd36f1a53a1d34a9157dffc9e8147dae19 100644 +index 16e97e9e8d9158d290c98231e2a2a9c3b3d2f567..49d4d7eaaba21b3460dd84583a48501deb337664 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in +++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in -@@ -57,10 +57,13 @@ messages -> WebPage WantsAsyncDispatchMessage { +@@ -58,10 +58,13 @@ messages -> WebPage WantsAsyncDispatchMessage { MouseEvent(WebCore::FrameIdentifier frameID, WebKit::WebMouseEvent event, std::optional> sandboxExtensions) SetLastKnownMousePosition(WebCore::FrameIdentifier frameID, WebCore::IntPoint eventPoint, WebCore::IntPoint globalPoint); @@ -21563,10 +21463,10 @@ index d38d0ecfb0e77838cc6d24b199c8be5d30d13953..1b6ddebd36f1a53a1d34a9157dffc9e8 SetSceneIdentifier(String sceneIdentifier) SetViewportConfigurationViewLayoutSize(WebCore::FloatSize size, double scaleFactor, double minimumEffectiveDeviceWidth) - SetDeviceOrientation(WebCore::IntDegrees deviceOrientation) - SetOverrideViewportArguments(std::optional arguments) + SetOverrideViewportArguments(struct std::optional arguments) DynamicViewportSizeUpdate(struct WebKit::DynamicViewportSizeUpdate target) -@@ -152,6 +155,7 @@ messages -> WebPage WantsAsyncDispatchMessage { +@@ -153,6 +156,7 @@ messages -> WebPage WantsAsyncDispatchMessage { ConnectInspector(String targetId, Inspector::FrontendChannel::ConnectionType connectionType) DisconnectInspector(String targetId) SendMessageToTargetBackend(String targetId, String message) @@ -21574,15 +21474,15 @@ index d38d0ecfb0e77838cc6d24b199c8be5d30d13953..1b6ddebd36f1a53a1d34a9157dffc9e8 #if ENABLE(REMOTE_INSPECTOR) SetIndicating(bool indicating); -@@ -162,6 +166,7 @@ messages -> WebPage WantsAsyncDispatchMessage { +@@ -163,6 +167,7 @@ messages -> WebPage WantsAsyncDispatchMessage { #endif #if !ENABLE(IOS_TOUCH_EVENTS) && ENABLE(TOUCH_EVENTS) - TouchEvent(WebKit::WebTouchEvent event) -> (std::optional eventType, bool handled) + TouchEvent(WebKit::WebTouchEvent event) -> (enum:uint8_t std::optional eventType, bool handled) + FakeTouchTap(WebCore::IntPoint position, uint8_t modifiers) -> () Async #endif CancelPointer(WebCore::PointerID pointerId, WebCore::IntPoint documentPoint) -@@ -189,6 +194,7 @@ messages -> WebPage WantsAsyncDispatchMessage { +@@ -190,6 +195,7 @@ messages -> WebPage WantsAsyncDispatchMessage { CreateProvisionalFrame(struct WebKit::ProvisionalFrameCreationParameters creationParameters) DestroyProvisionalFrame(WebCore::FrameIdentifier frameID); LoadDidCommitInAnotherProcess(WebCore::FrameIdentifier frameID, std::optional layerHostingContextIdentifier) @@ -21590,20 +21490,20 @@ index d38d0ecfb0e77838cc6d24b199c8be5d30d13953..1b6ddebd36f1a53a1d34a9157dffc9e8 LoadRequestWaitingForProcessLaunch(struct WebKit::LoadParameters loadParameters, URL resourceDirectoryURL, WebKit::WebPageProxyIdentifier pageID, bool checkAssumedReadAccessToResourceURL) LoadData(struct WebKit::LoadParameters loadParameters) LoadSimulatedRequestAndResponse(struct WebKit::LoadParameters loadParameters, WebCore::ResourceResponse simulatedResponse) -@@ -352,10 +358,10 @@ messages -> WebPage WantsAsyncDispatchMessage { +@@ -351,10 +357,10 @@ messages -> WebPage WantsAsyncDispatchMessage { RemoveLayerForFindOverlay() -> () # Drag and drop. -#if PLATFORM(GTK) && ENABLE(DRAG_SUPPORT) +#if (PLATFORM(GTK) || PLATFORM(WPE)) && ENABLE(DRAG_SUPPORT) - PerformDragControllerAction(enum:uint8_t WebKit::DragControllerAction action, WebCore::IntPoint clientPosition, WebCore::IntPoint globalPosition, OptionSet draggingSourceOperationMask, WebCore::SelectionData selection, OptionSet flags) -> (std::optional dragOperation, enum:uint8_t WebCore::DragHandlingMethod dragHandlingMethod, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, WebCore::IntRect insertionRect, WebCore::IntRect editableElementRect, struct std::optional remoteUserInputEventData) + PerformDragControllerAction(enum:uint8_t WebKit::DragControllerAction action, WebCore::IntPoint clientPosition, WebCore::IntPoint globalPosition, OptionSet draggingSourceOperationMask, WebCore::SelectionData selection, OptionSet flags) -> (enum:uint8_t std::optional dragOperation, enum:uint8_t WebCore::DragHandlingMethod dragHandlingMethod, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, WebCore::IntRect insertionRect, WebCore::IntRect editableElementRect, struct std::optional remoteUserInputEventData) #endif -#if !PLATFORM(GTK) && ENABLE(DRAG_SUPPORT) +#if !PLATFORM(GTK) && !PLATFORM(WPE) && ENABLE(DRAG_SUPPORT) - PerformDragControllerAction(std::optional frameID, enum:uint8_t WebKit::DragControllerAction action, WebCore::DragData dragData) -> (std::optional dragOperation, enum:uint8_t WebCore::DragHandlingMethod dragHandlingMethod, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, WebCore::IntRect insertionRect, WebCore::IntRect editableElementRect, struct std::optional remoteUserInputEventData) + PerformDragControllerAction(std::optional frameID, enum:uint8_t WebKit::DragControllerAction action, WebCore::DragData dragData) -> (enum:uint8_t std::optional dragOperation, enum:uint8_t WebCore::DragHandlingMethod dragHandlingMethod, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, WebCore::IntRect insertionRect, WebCore::IntRect editableElementRect, struct std::optional remoteUserInputEventData) PerformDragOperation(WebCore::DragData dragData, WebKit::SandboxExtensionHandle sandboxExtensionHandle, Vector sandboxExtensionsForUpload) -> (bool handled) #endif -@@ -371,6 +377,10 @@ messages -> WebPage WantsAsyncDispatchMessage { +@@ -370,6 +376,10 @@ messages -> WebPage WantsAsyncDispatchMessage { StageModeSessionDidEnd(std::optional elementID) #endif @@ -21614,16 +21514,54 @@ index d38d0ecfb0e77838cc6d24b199c8be5d30d13953..1b6ddebd36f1a53a1d34a9157dffc9e8 #if PLATFORM(IOS_FAMILY) && ENABLE(DRAG_SUPPORT) RequestDragStart(WebCore::IntPoint clientPosition, WebCore::IntPoint globalPosition, OptionSet allowedActionsMask) RequestAdditionalItemsForDragSession(WebCore::IntPoint clientPosition, WebCore::IntPoint globalPosition, OptionSet allowedActionsMask) +diff --git a/Source/WebKit/WebProcess/WebPage/glib/WebPageGLib.cpp b/Source/WebKit/WebProcess/WebPage/glib/WebPageGLib.cpp +index 40ec42bb4f998774a2ce4a19e82f68512ad2ebb8..080794e14bfbb3a336d8a89791baee0e1aec3c75 100644 +--- a/Source/WebKit/WebProcess/WebPage/glib/WebPageGLib.cpp ++++ b/Source/WebKit/WebProcess/WebPage/glib/WebPageGLib.cpp +@@ -210,16 +210,23 @@ String WebPage::platformUserAgent(const URL& url) const + + bool WebPage::hoverSupportedByPrimaryPointingDevice() const + { ++ if (screenHasTouchDeviceOverride()) ++ return !screenHasTouchDeviceOverride().value(); + return WebProcess::singleton().primaryPointingDevice() == AvailableInputDevices::Mouse; + } + + bool WebPage::hoverSupportedByAnyAvailablePointingDevice() const + { ++ if (screenHasTouchDeviceOverride()) ++ return !screenHasTouchDeviceOverride().value(); + return WebProcess::singleton().availableInputDevices().contains(AvailableInputDevices::Mouse); + } + + std::optional WebPage::pointerCharacteristicsOfPrimaryPointingDevice() const + { ++ if (screenHasTouchDeviceOverride() && screenHasTouchDeviceOverride().value()) ++ return PointerCharacteristics::Coarse; ++ + const auto& primaryPointingDevice = WebProcess::singleton().primaryPointingDevice(); + if (primaryPointingDevice == AvailableInputDevices::Mouse) + return PointerCharacteristics::Fine; +@@ -230,6 +237,9 @@ std::optional WebPage::pointerCharacteristicsOfPrimaryPo + + OptionSet WebPage::pointerCharacteristicsOfAllAvailablePointingDevices() const + { ++ if (screenHasTouchDeviceOverride() && screenHasTouchDeviceOverride().value()) ++ return PointerCharacteristics::Coarse; ++ + OptionSet pointerCharacteristics; + const auto& availableInputs = WebProcess::singleton().availableInputDevices(); + if (availableInputs.contains(AvailableInputDevices::Mouse)) diff --git a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm -index 740a118cfe40d803ecccbf2025efa7b813d9b918..c8acb88dd1f3b47332e3928035f74b8bd3c57049 100644 +index 932e27ac30ca12459321e4bc1386e7489f647831..ae8dafebd63dbc78c4000dad9f86f3f2208b69b9 100644 --- a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm +++ b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm -@@ -696,21 +696,37 @@ String WebPage::platformUserAgent(const URL&) const +@@ -703,21 +703,37 @@ String WebPage::platformUserAgent(const URL&) const bool WebPage::hoverSupportedByPrimaryPointingDevice() const { +#if ENABLE(TOUCH_EVENTS) -+ return !screenIsTouchPrimaryInputDevice(); ++ return !screenHasTouchDevice(); +#else return true; +#endif @@ -21641,7 +21579,7 @@ index 740a118cfe40d803ecccbf2025efa7b813d9b918..c8acb88dd1f3b47332e3928035f74b8b std::optional WebPage::pointerCharacteristicsOfPrimaryPointingDevice() const { +#if ENABLE(TOUCH_EVENTS) -+ if (screenIsTouchPrimaryInputDevice()) ++ if (screenHasTouchDevice()) + return PointerCharacteristics::Coarse; +#endif return PointerCharacteristics::Fine; @@ -21657,7 +21595,7 @@ index 740a118cfe40d803ecccbf2025efa7b813d9b918..c8acb88dd1f3b47332e3928035f74b8b } diff --git a/Source/WebKit/WebProcess/WebPage/win/WebPageWin.cpp b/Source/WebKit/WebProcess/WebPage/win/WebPageWin.cpp -index f17f5d719d892309ed9c7093384945866b5117b9..1dba47bbf0dbd0362548423a74b380346dbea147 100644 +index f17f5d719d892309ed9c7093384945866b5117b9..adffe0aa4440c626879e3f5701dd5fea4b2d1a0f 100644 --- a/Source/WebKit/WebProcess/WebPage/win/WebPageWin.cpp +++ b/Source/WebKit/WebProcess/WebPage/win/WebPageWin.cpp @@ -43,6 +43,7 @@ @@ -21673,7 +21611,7 @@ index f17f5d719d892309ed9c7093384945866b5117b9..1dba47bbf0dbd0362548423a74b38034 bool WebPage::hoverSupportedByPrimaryPointingDevice() const { +#if ENABLE(TOUCH_EVENTS) -+ return !screenIsTouchPrimaryInputDevice(); ++ return !screenHasTouchDevice(); +#else return true; +#endif @@ -21691,7 +21629,7 @@ index f17f5d719d892309ed9c7093384945866b5117b9..1dba47bbf0dbd0362548423a74b38034 std::optional WebPage::pointerCharacteristicsOfPrimaryPointingDevice() const { +#if ENABLE(TOUCH_EVENTS) -+ if (screenIsTouchPrimaryInputDevice()) ++ if (screenHasTouchDevice()) + return PointerCharacteristics::Coarse; +#endif return PointerCharacteristics::Fine; @@ -21707,10 +21645,10 @@ index f17f5d719d892309ed9c7093384945866b5117b9..1dba47bbf0dbd0362548423a74b38034 } diff --git a/Source/WebKit/WebProcess/WebProcess.cpp b/Source/WebKit/WebProcess/WebProcess.cpp -index aeaaf015db254f2562085e003a84c29ea8636611..6779f7b116db8f9a0686bef70965cdf9a7d2d6f4 100644 +index 757208df2beb144defe64fe4181d972f823ec89a..5c1bc069b031a5787515e787ed9abe441c124971 100644 --- a/Source/WebKit/WebProcess/WebProcess.cpp +++ b/Source/WebKit/WebProcess/WebProcess.cpp -@@ -91,6 +91,7 @@ +@@ -93,6 +93,7 @@ #include "WebsiteData.h" #include "WebsiteDataStoreParameters.h" #include "WebsiteDataType.h" @@ -21718,7 +21656,7 @@ index aeaaf015db254f2562085e003a84c29ea8636611..6779f7b116db8f9a0686bef70965cdf9 #include #include #include -@@ -378,6 +379,14 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter +@@ -388,6 +389,14 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter { JSC::Options::AllowUnfinalizedAccessScope scope; JSC::Options::allowNonSPTagging() = false; @@ -21733,7 +21671,7 @@ index aeaaf015db254f2562085e003a84c29ea8636611..6779f7b116db8f9a0686bef70965cdf9 JSC::Options::notifyOptionsChanged(); } -@@ -385,6 +394,8 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter +@@ -395,6 +404,8 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter platformInitializeProcess(parameters); updateCPULimit(); @@ -21742,16 +21680,17 @@ index aeaaf015db254f2562085e003a84c29ea8636611..6779f7b116db8f9a0686bef70965cdf9 } void WebProcess::initializeConnection(IPC::Connection* connection) -@@ -953,6 +964,7 @@ void WebProcess::createWebPage(PageIdentifier pageID, WebPageCreationParameters& +@@ -975,6 +986,8 @@ void WebProcess::createWebPage(PageIdentifier pageID, WebPageCreationParameters& accessibilityRelayProcessSuspended(false); } ASSERT(result.iterator->value); ++ + result.iterator->value->didAddWebPageToWebProcess(); } void WebProcess::removeWebPage(PageIdentifier pageID) diff --git a/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm b/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm -index 60dc8836dbd8ed1f3f4a2e476d91be6ca7ab0840..20dcdedb9e4eb742c213193eddaa947ac10ec219 100644 +index c0fa99b99c2d0464f976bed096ee774db8a7e3c9..7b9fb12ef6198e8a052efe0e6d37768ad161d3aa 100644 --- a/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm +++ b/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm @@ -4223,7 +4223,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END @@ -21764,10 +21703,10 @@ index 60dc8836dbd8ed1f3f4a2e476d91be6ca7ab0840..20dcdedb9e4eb742c213193eddaa947a - (void)touch:(WebEvent *)event { diff --git a/Source/WebKitLegacy/mac/WebView/WebView.mm b/Source/WebKitLegacy/mac/WebView/WebView.mm -index e3a23565b11b4dc4eafa3616e15d04e528de8f94..366cf2dd2348350b6dd133ce660ca5b1f73d88d3 100644 +index e7d231ff9a7a975d442f9b790a0aad2d666e9972..263d30a59c67e4bd930a1961941a9742760df4d3 100644 --- a/Source/WebKitLegacy/mac/WebView/WebView.mm +++ b/Source/WebKitLegacy/mac/WebView/WebView.mm -@@ -3991,7 +3991,7 @@ + (void)_doNotStartObservingNetworkReachability +@@ -3992,7 +3992,7 @@ + (void)_doNotStartObservingNetworkReachability } #endif // PLATFORM(IOS_FAMILY) @@ -21776,7 +21715,7 @@ index e3a23565b11b4dc4eafa3616e15d04e528de8f94..366cf2dd2348350b6dd133ce660ca5b1 - (NSArray *)_touchEventRegions { -@@ -4033,7 +4033,7 @@ - (NSArray *)_touchEventRegions +@@ -4034,7 +4034,7 @@ - (NSArray *)_touchEventRegions }).autorelease(); } @@ -21817,12 +21756,12 @@ index 0000000000000000000000000000000000000000..dd6a53e2d57318489b7e49dd7373706d + LIBVPX_LIBRARIES +) diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake -index d46ee01c5af78d95469d7cfdb7144e8863f00d5a..4a6d5d34007183f28853e6c5192f282b3925a7ae 100644 +index dc6bd15038f36e65b47974960a414b0d7c170e63..da2dba81c6cb4617cc2b946f24060ec8fcf46c87 100644 --- a/Source/cmake/OptionsGTK.cmake +++ b/Source/cmake/OptionsGTK.cmake -@@ -8,6 +8,10 @@ SET_PROJECT_VERSION(2 47 4) - set(USER_AGENT_BRANDING "" CACHE STRING "Branding to add to user agent string") +@@ -9,6 +9,10 @@ set(USER_AGENT_BRANDING "" CACHE STRING "Branding to add to user agent string") + # Update Source/WTF/wtf/Platform.h to match required GLib versions. find_package(GLIB 2.70.0 REQUIRED COMPONENTS gio gio-unix gobject gthread gmodule) + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) @@ -21831,7 +21770,7 @@ index d46ee01c5af78d95469d7cfdb7144e8863f00d5a..4a6d5d34007183f28853e6c5192f282b find_package(Cairo 1.16.0 REQUIRED) find_package(LibGcrypt 1.7.0 REQUIRED) find_package(Libtasn1 REQUIRED) -@@ -23,6 +27,10 @@ find_package(ZLIB REQUIRED) +@@ -24,6 +28,10 @@ find_package(ZLIB REQUIRED) find_package(WebP REQUIRED COMPONENTS demux) find_package(ATSPI 2.5.3) @@ -21842,7 +21781,7 @@ index d46ee01c5af78d95469d7cfdb7144e8863f00d5a..4a6d5d34007183f28853e6c5192f282b include(GStreamerDefinitions) include(FindGLibCompileResources) -@@ -70,6 +78,10 @@ WEBKIT_OPTION_DEFINE(USE_SYSTEM_UNIFDEF "Whether to use a system-provided unifde +@@ -71,6 +79,10 @@ WEBKIT_OPTION_DEFINE(USE_SYSTEM_UNIFDEF "Whether to use a system-provided unifde WEBKIT_OPTION_DEPEND(USE_SYSTEM_SYSPROF_CAPTURE USE_SYSPROF_CAPTURE) @@ -21853,7 +21792,7 @@ index d46ee01c5af78d95469d7cfdb7144e8863f00d5a..4a6d5d34007183f28853e6c5192f282b SET_AND_EXPOSE_TO_BUILD(ENABLE_DEVELOPER_MODE ${DEVELOPER_MODE}) if (DEVELOPER_MODE) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_API_TESTS PRIVATE ON) -@@ -148,6 +160,20 @@ endif () +@@ -149,6 +161,20 @@ endif () WEBKIT_OPTION_DEPEND(ENABLE_GPU_PROCESS USE_GBM) @@ -21875,10 +21814,10 @@ index d46ee01c5af78d95469d7cfdb7144e8863f00d5a..4a6d5d34007183f28853e6c5192f282b # Finalize the value for all options. Do not attempt to use an option before diff --git a/Source/cmake/OptionsWPE.cmake b/Source/cmake/OptionsWPE.cmake -index bb046a1110023fbd6641943d4b677d1fc68cdea5..d34028c618600a172225246db9286e2a7ab1b9eb 100644 +index 8c12c0a16e14d3d537704114afc15f6935e17dcd..0c70ff37d45230597a5210e166d242a78bfb8730 100644 --- a/Source/cmake/OptionsWPE.cmake +++ b/Source/cmake/OptionsWPE.cmake -@@ -22,6 +22,9 @@ find_package(WebP REQUIRED COMPONENTS demux) +@@ -23,6 +23,9 @@ find_package(WebP REQUIRED COMPONENTS demux) find_package(WPE REQUIRED) find_package(ZLIB REQUIRED) @@ -21888,7 +21827,7 @@ index bb046a1110023fbd6641943d4b677d1fc68cdea5..d34028c618600a172225246db9286e2a WEBKIT_OPTION_BEGIN() SET_AND_EXPOSE_TO_BUILD(ENABLE_DEVELOPER_MODE ${DEVELOPER_MODE}) -@@ -84,6 +87,21 @@ if (WPE_VERSION VERSION_GREATER_EQUAL 1.13.90) +@@ -85,6 +88,21 @@ if (WPE_VERSION VERSION_GREATER_EQUAL 1.13.90) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_GAMEPAD PUBLIC ON) endif () @@ -21910,7 +21849,7 @@ index bb046a1110023fbd6641943d4b677d1fc68cdea5..d34028c618600a172225246db9286e2a # Public options specific to the WPE port. Do not add any options here unless # there is a strong reason we should support changing the value of the option, # and the option is not relevant to other WebKit ports. -@@ -119,6 +137,11 @@ WEBKIT_OPTION_DEPEND(USE_QT6 ENABLE_WPE_PLATFORM) +@@ -120,6 +138,11 @@ WEBKIT_OPTION_DEPEND(USE_QT6 ENABLE_WPE_PLATFORM) WEBKIT_OPTION_DEPEND(USE_SKIA_OPENTYPE_SVG USE_SKIA) WEBKIT_OPTION_DEPEND(USE_SYSTEM_SYSPROF_CAPTURE USE_SYSPROF_CAPTURE) @@ -21923,10 +21862,10 @@ index bb046a1110023fbd6641943d4b677d1fc68cdea5..d34028c618600a172225246db9286e2a WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_BUBBLEWRAP_SANDBOX PUBLIC ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEMORY_SAMPLER PRIVATE ON) diff --git a/Source/cmake/OptionsWin.cmake b/Source/cmake/OptionsWin.cmake -index b43e68ea44419ed9ea7f539df747cff5d9e4bb36..f62b9a303ad3aba3f04220f1ca9dd208cf3380d4 100644 +index ee7c004624e7da8e0a7af18356fc9a378f82020a..9fcabed645095932d997ed5a69a9a5534ac3cb35 100644 --- a/Source/cmake/OptionsWin.cmake +++ b/Source/cmake/OptionsWin.cmake -@@ -76,6 +76,27 @@ find_package(ZLIB 1.2.11 REQUIRED) +@@ -73,6 +73,27 @@ find_package(ZLIB 1.2.11 REQUIRED) find_package(LibPSL 0.20.2 REQUIRED) find_package(WebP REQUIRED COMPONENTS demux) @@ -21954,7 +21893,7 @@ index b43e68ea44419ed9ea7f539df747cff5d9e4bb36..f62b9a303ad3aba3f04220f1ca9dd208 WEBKIT_OPTION_BEGIN() # FIXME: Most of these options should not be public. -@@ -133,6 +154,14 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_FTPDIR PRIVATE OFF) +@@ -131,6 +152,14 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_FTPDIR PRIVATE OFF) SET_AND_EXPOSE_TO_BUILD(ENABLE_WEBDRIVER_KEYBOARD_INTERACTIONS ON) SET_AND_EXPOSE_TO_BUILD(ENABLE_WEBDRIVER_MOUSE_INTERACTIONS ON) @@ -21970,7 +21909,7 @@ index b43e68ea44419ed9ea7f539df747cff5d9e4bb36..f62b9a303ad3aba3f04220f1ca9dd208 set(USE_ANGLE_EGL ON) diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake -index 4882f3de3d9cb011a666563f0bc0e142a705518f..d3f674bf78792088a60a631c245a1fa86dca4d53 100644 +index de1cb6c496ccce806010ec1da8f6a4d6d731e0c1..17ea31b9623d939fc2db0249b04caeb8f78fd725 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -122,7 +122,7 @@ macro(WEBKIT_ADD_TARGET_CXX_FLAGS _target) @@ -22385,10 +22324,10 @@ index 8433f5360dc4a5f43b68b67192fb3d9bf5064cf1..9fa8f53e90fe5a32be1c8e7a9daa6404 g_clear_object(&interfaceSettings); diff --git a/Tools/MiniBrowser/wpe/main.cpp b/Tools/MiniBrowser/wpe/main.cpp -index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f66566471ad9733f 100644 +index 6a898a5bd79a4e6f2f26be7f63347cef7a5c0ab4..d895941dab0f7a0bdfafcf60f203aa82c46874a6 100644 --- a/Tools/MiniBrowser/wpe/main.cpp +++ b/Tools/MiniBrowser/wpe/main.cpp -@@ -49,6 +49,9 @@ static gboolean headlessMode; +@@ -52,6 +52,9 @@ static gboolean headlessMode; static gboolean privateMode; static gboolean automationMode; static gboolean ignoreTLSErrors; @@ -22398,7 +22337,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 static const char* contentFilter; static const char* cookiesFile; static const char* cookiesPolicy; -@@ -127,6 +130,9 @@ static const GOptionEntry commandLineOptions[] = +@@ -130,6 +133,9 @@ static const GOptionEntry commandLineOptions[] = #endif { "size", 's', 0, G_OPTION_ARG_CALLBACK, reinterpret_cast(parseWindowSize), "Specify the window size to use, e.g. --size=\"800x600\"", nullptr }, { "version", 'v', 0, G_OPTION_ARG_NONE, &printVersion, "Print the WPE version", nullptr }, @@ -22408,7 +22347,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &uriArguments, nullptr, "[URL]" }, { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } }; -@@ -285,15 +291,38 @@ static void filterSavedCallback(WebKitUserContentFilterStore *store, GAsyncResul +@@ -288,15 +294,38 @@ static void filterSavedCallback(WebKitUserContentFilterStore *store, GAsyncResul g_main_loop_quit(data->mainLoop); } @@ -22449,7 +22388,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 { auto backend = createViewBackend(defaultWindowWidthLegacyAPI, defaultWindowHeightLegacyAPI); WebKitWebViewBackend* viewBackend = nullptr; -@@ -308,12 +337,27 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi +@@ -311,12 +340,27 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi }, backend.release()); } @@ -22483,18 +22422,23 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 #if ENABLE_WPE_PLATFORM if (auto* wpeView = webkit_web_view_get_wpe_view(newWebView)) { -@@ -328,6 +372,10 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi - - g_hash_table_add(openViews, newWebView); +@@ -328,9 +372,13 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi + g_signal_connect(newWebView, "create", G_CALLBACK(createWebView), user_data); + g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), user_data); +- ++// Playwright begin + g_signal_connect(newWebView, "load-failed", G_CALLBACK(webViewLoadFailed), nullptr); + g_signal_connect(newWebView, "script-dialog", G_CALLBACK(scriptDialog), nullptr); + g_signal_connect(newWebView, "script-dialog-handled", G_CALLBACK(scriptDialogHandled), nullptr); + g_signal_connect(newWebView, "decide-policy", G_CALLBACK(webViewDecidePolicy), nullptr); ++// Playwright end + g_hash_table_add(openViews, newWebView); +- return newWebView; } -@@ -415,13 +463,105 @@ void loadConfigFile(WPESettings* settings) +@@ -418,13 +466,105 @@ void loadConfigFile(WPESettings* settings) } #endif @@ -22601,7 +22545,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 webkit_network_session_set_itp_enabled(networkSession, enableITP); if (proxy) { -@@ -448,10 +588,18 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -451,10 +591,18 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesFile, storageType); } } @@ -22622,7 +22566,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 webkit_website_data_manager_set_itp_enabled(manager, enableITP); if (proxy) { -@@ -482,6 +630,7 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -485,6 +633,7 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* } #endif @@ -22630,7 +22574,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 WebKitUserContentManager* userContentManager = nullptr; if (contentFilter) { GFile* contentFilterFile = g_file_new_for_commandline_arg(contentFilter); -@@ -560,6 +709,15 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -563,6 +712,15 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* "autoplay", WEBKIT_AUTOPLAY_ALLOW, nullptr); @@ -22646,7 +22590,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 auto* webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, "backend", viewBackend, "web-context", webContext, -@@ -606,8 +764,6 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -609,12 +767,16 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* } #endif @@ -22655,7 +22599,17 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 g_signal_connect(webContext, "automation-started", G_CALLBACK(automationStartedCallback), webView); g_signal_connect(webView, "permission-request", G_CALLBACK(decidePermissionRequest), nullptr); g_signal_connect(webView, "create", G_CALLBACK(createWebView), application); -@@ -619,16 +775,11 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* + g_signal_connect(webView, "close", G_CALLBACK(webViewClose), application); ++// Playwright begin ++ g_signal_connect(webView, "load-failed", G_CALLBACK(webViewLoadFailed), nullptr); ++ g_signal_connect(webView, "script-dialog", G_CALLBACK(scriptDialog), nullptr); ++ g_signal_connect(webView, "script-dialog-handled", G_CALLBACK(scriptDialogHandled), nullptr); ++ g_signal_connect(webView, "decide-policy", G_CALLBACK(webViewDecidePolicy), nullptr); ++// Playwright end + g_hash_table_add(openViews, webView); + + WebKitColor color; +@@ -622,16 +784,11 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* webkit_web_view_set_background_color(webView, &color); if (uriArguments) { @@ -22677,7 +22631,7 @@ index f5403031c914d7fedeb4649c4891beb7d681007e..eb1fe97ebb4e55eb1d4443e5f6656647 webkit_web_view_load_uri(webView, "https://wpewebkit.org"); g_object_unref(webContext); -@@ -725,8 +876,14 @@ int main(int argc, char *argv[]) +@@ -728,8 +885,14 @@ int main(int argc, char *argv[]) } } @@ -22705,10 +22659,10 @@ index 1067b31bc989748dfcc5502209d36d001b9b239e..7629263fb8bc93dca6dfc01c75eed8d2 + add_subdirectory(Playwright/win) +endif () diff --git a/Tools/Scripts/build-webkit b/Tools/Scripts/build-webkit -index ad5ecfe242d13ef1b363215361ae15091a3ffd2a..417325f1742625caf6aff22c9d0b1f5ac061743b 100755 +index df483701f5b9c69718669fff32bb35475c7bf0c9..5cff91cb4618417691586a5771291eec9f89bee3 100755 --- a/Tools/Scripts/build-webkit +++ b/Tools/Scripts/build-webkit -@@ -273,7 +273,7 @@ if (isAppleCocoaWebKit()) { +@@ -274,7 +274,7 @@ if (isAppleCocoaWebKit()) { push @projects, ("Source/WebKit"); if (!isEmbeddedWebKit()) { @@ -22733,19 +22687,27 @@ index 9e53f459e444b9c10fc5248f0e8059df6c1e0041..c17c875a7dd3ca05c4489578ab32378b "${WebKitTestRunner_DIR}/InjectedBundle/Bindings/AccessibilityController.idl" "${WebKitTestRunner_DIR}/InjectedBundle/Bindings/AccessibilityTextMarker.idl" diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp -index ea8a7dc0d6b63e28ca39e8ecb77fa6c2c3889d32..b3c292fb5db977649e90fbc598626d27ab39a349 100644 +index ce8aa4f20d8e0f14a51ae27d8d9440b55e0ea433..cf93f834e135f932ffba7e7101052fcbd9e6b4b2 100644 --- a/Tools/WebKitTestRunner/TestController.cpp +++ b/Tools/WebKitTestRunner/TestController.cpp -@@ -1087,6 +1087,7 @@ void TestController::createWebViewWithOptions(const TestOptions& options) - 0, // requestStorageAccessConfirm +@@ -711,6 +711,7 @@ PlatformWebView* TestController::createOtherPlatformWebView(PlatformWebView* par + nullptr, // requestStorageAccessConfirm + nullptr, // shouldAllowDeviceOrientationAndMotionAccess + nullptr, // runWebAuthenticationPanel ++ 0, // handleJavaScriptDialog + nullptr, // decidePolicyForSpeechRecognitionPermissionRequest + nullptr, // decidePolicyForMediaKeySystemPermissionRequest + nullptr, // queryPermission +@@ -1186,6 +1187,7 @@ void TestController::createWebViewWithOptions(const TestOptions& options) + nullptr, // requestStorageAccessConfirm shouldAllowDeviceOrientationAndMotionAccess, runWebAuthenticationPanel, + 0, // handleJavaScriptDialog - 0, + nullptr, // decidePolicyForSpeechRecognitionPermissionRequest decidePolicyForMediaKeySystemPermissionRequest, queryPermission, diff --git a/Tools/WebKitTestRunner/mac/EventSenderProxy.mm b/Tools/WebKitTestRunner/mac/EventSenderProxy.mm -index 0435d345aadde9eaa3a4b7e8625e0872db8c1840..856c36909fd15a178bf1e929aec3d391196f7d36 100644 +index 9419696018c076d0e5b4ef04ea7c58be9504cd96..06c537e3c022517449068a11ea59400413e8f4dc 100644 --- a/Tools/WebKitTestRunner/mac/EventSenderProxy.mm +++ b/Tools/WebKitTestRunner/mac/EventSenderProxy.mm @@ -961,4 +961,51 @@ void EventSenderProxy::waitForPendingMouseEvents() @@ -22801,7 +22763,7 @@ index 0435d345aadde9eaa3a4b7e8625e0872db8c1840..856c36909fd15a178bf1e929aec3d391 + } // namespace WTR diff --git a/Tools/jhbuild/jhbuild-minimal.modules b/Tools/jhbuild/jhbuild-minimal.modules -index 3a0b7425900b14ce2aa0d48aa914cd69bff1f332..22fb1ab2ea76e1e253c79ba1fa3fa448f7584b43 100644 +index 3a0b7425900b14ce2aa0d48aa914cd69bff1f332..0ce07cc1368c6f521b51d6300dca9c4d078beef3 100644 --- a/Tools/jhbuild/jhbuild-minimal.modules +++ b/Tools/jhbuild/jhbuild-minimal.modules @@ -67,8 +67,8 @@ @@ -22834,6 +22796,17 @@ index 3a0b7425900b14ce2aa0d48aa914cd69bff1f332..22fb1ab2ea76e1e253c79ba1fa3fa448 +@@ -194,8 +193,8 @@ + + + + diff --git a/Tools/wpe/backends/fdo/HeadlessViewBackendFdo.cpp b/Tools/wpe/backends/fdo/HeadlessViewBackendFdo.cpp index df22308266c6f69d24a60905f8d05e4e80f21b9b..2d0838070dc10793418cbb648b095a5ffa76f1b8 100644 --- a/Tools/wpe/backends/fdo/HeadlessViewBackendFdo.cpp @@ -22897,7 +22870,7 @@ index df22308266c6f69d24a60905f8d05e4e80f21b9b..2d0838070dc10793418cbb648b095a5f static cairo_user_data_key_t bufferKey; cairo_surface_set_user_data(m_snapshot, &bufferKey, buffer, diff --git a/WebKit.xcworkspace/contents.xcworkspacedata b/WebKit.xcworkspace/contents.xcworkspacedata -index a0f02e61be7e667c6ba164ca578109af36ac28d9..8f5af2e82c167ba6798fb7fde24a9f641f6554a5 100644 +index 3ad442a8691847c6921c5f66a805b7b0523b1e27..95407368a95d2f7d6fb697110912014cabe29afa 100644 --- a/WebKit.xcworkspace/contents.xcworkspacedata +++ b/WebKit.xcworkspace/contents.xcworkspacedata @@ -4,6 +4,9 @@ From 6e14ce0493540f07028d1f83758d19d5fe05eb15 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 4 Jun 2025 13:52:15 +0100 Subject: [PATCH 004/222] chore: update WebKit version to 18.5 (#36190) --- README.md | 4 +- packages/playwright-core/browsers.json | 2 +- .../src/server/deviceDescriptorsSource.json | 162 +++++++++--------- .../src/server/webkit/wkBrowser.ts | 4 +- 4 files changed, 86 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index fa3b3162cd0dd..f45dfec1980ba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.4-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.4-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.4-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -9,7 +9,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | | Chromium 138.0.7204.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| WebKit 18.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| WebKit 18.5 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 139.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details. diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index a64ea919aaf71..39fad9bf68af5 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -55,7 +55,7 @@ "ubuntu20.04-x64": "2092", "ubuntu20.04-arm64": "2092" }, - "browserVersion": "18.4" + "browserVersion": "18.5" }, { "name": "ffmpeg", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index fcd7ca624b47d..916504ba5bdfd 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -1,6 +1,6 @@ { "Blackberry PlayBook": { - "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.4 Safari/536.2+", + "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.5 Safari/536.2+", "viewport": { "width": 600, "height": 1024 @@ -11,7 +11,7 @@ "defaultBrowserType": "webkit" }, "Blackberry PlayBook landscape": { - "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.4 Safari/536.2+", + "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.5 Safari/536.2+", "viewport": { "width": 1024, "height": 600 @@ -22,7 +22,7 @@ "defaultBrowserType": "webkit" }, "BlackBerry Z30": { - "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.4 Mobile Safari/537.10+", + "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.5 Mobile Safari/537.10+", "viewport": { "width": 360, "height": 640 @@ -33,7 +33,7 @@ "defaultBrowserType": "webkit" }, "BlackBerry Z30 landscape": { - "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.4 Mobile Safari/537.10+", + "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.5 Mobile Safari/537.10+", "viewport": { "width": 640, "height": 360 @@ -44,7 +44,7 @@ "defaultBrowserType": "webkit" }, "Galaxy Note 3": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.4 Mobile Safari/534.30", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.5 Mobile Safari/534.30", "viewport": { "width": 360, "height": 640 @@ -55,7 +55,7 @@ "defaultBrowserType": "webkit" }, "Galaxy Note 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.4 Mobile Safari/534.30", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.5 Mobile Safari/534.30", "viewport": { "width": 640, "height": 360 @@ -66,7 +66,7 @@ "defaultBrowserType": "webkit" }, "Galaxy Note II": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.4 Mobile Safari/534.30", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.5 Mobile Safari/534.30", "viewport": { "width": 360, "height": 640 @@ -77,7 +77,7 @@ "defaultBrowserType": "webkit" }, "Galaxy Note II landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.4 Mobile Safari/534.30", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.5 Mobile Safari/534.30", "viewport": { "width": 640, "height": 360 @@ -88,7 +88,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S III": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.4 Mobile Safari/534.30", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.5 Mobile Safari/534.30", "viewport": { "width": 360, "height": 640 @@ -99,7 +99,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S III landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.4 Mobile Safari/534.30", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.5 Mobile Safari/534.30", "viewport": { "width": 640, "height": 360 @@ -264,7 +264,7 @@ "defaultBrowserType": "chromium" }, "iPad (gen 5)": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 768, "height": 1024 @@ -275,7 +275,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 5) landscape": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 1024, "height": 768 @@ -286,7 +286,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 6)": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 768, "height": 1024 @@ -297,7 +297,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 6) landscape": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 1024, "height": 768 @@ -308,7 +308,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 7)": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 810, "height": 1080 @@ -319,7 +319,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 7) landscape": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 1080, "height": 810 @@ -330,7 +330,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 11)": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/19E241 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/19E241 Safari/604.1", "viewport": { "width": 656, "height": 944 @@ -341,7 +341,7 @@ "defaultBrowserType": "webkit" }, "iPad (gen 11) landscape": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/19E241 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/19E241 Safari/604.1", "viewport": { "width": 944, "height": 656 @@ -352,7 +352,7 @@ "defaultBrowserType": "webkit" }, "iPad Mini": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 768, "height": 1024 @@ -363,7 +363,7 @@ "defaultBrowserType": "webkit" }, "iPad Mini landscape": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 1024, "height": 768 @@ -374,7 +374,7 @@ "defaultBrowserType": "webkit" }, "iPad Pro 11": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 834, "height": 1194 @@ -385,7 +385,7 @@ "defaultBrowserType": "webkit" }, "iPad Pro 11 landscape": { - "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 1194, "height": 834 @@ -396,7 +396,7 @@ "defaultBrowserType": "webkit" }, "iPhone 6": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 375, "height": 667 @@ -407,7 +407,7 @@ "defaultBrowserType": "webkit" }, "iPhone 6 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 667, "height": 375 @@ -418,7 +418,7 @@ "defaultBrowserType": "webkit" }, "iPhone 6 Plus": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 414, "height": 736 @@ -429,7 +429,7 @@ "defaultBrowserType": "webkit" }, "iPhone 6 Plus landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 736, "height": 414 @@ -440,7 +440,7 @@ "defaultBrowserType": "webkit" }, "iPhone 7": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 375, "height": 667 @@ -451,7 +451,7 @@ "defaultBrowserType": "webkit" }, "iPhone 7 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 667, "height": 375 @@ -462,7 +462,7 @@ "defaultBrowserType": "webkit" }, "iPhone 7 Plus": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 414, "height": 736 @@ -473,7 +473,7 @@ "defaultBrowserType": "webkit" }, "iPhone 7 Plus landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 736, "height": 414 @@ -484,7 +484,7 @@ "defaultBrowserType": "webkit" }, "iPhone 8": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 375, "height": 667 @@ -495,7 +495,7 @@ "defaultBrowserType": "webkit" }, "iPhone 8 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 667, "height": 375 @@ -506,7 +506,7 @@ "defaultBrowserType": "webkit" }, "iPhone 8 Plus": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 414, "height": 736 @@ -517,7 +517,7 @@ "defaultBrowserType": "webkit" }, "iPhone 8 Plus landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 736, "height": 414 @@ -528,7 +528,7 @@ "defaultBrowserType": "webkit" }, "iPhone SE": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.4 Mobile/14E304 Safari/602.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.5 Mobile/14E304 Safari/602.1", "viewport": { "width": 320, "height": 568 @@ -539,7 +539,7 @@ "defaultBrowserType": "webkit" }, "iPhone SE landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.4 Mobile/14E304 Safari/602.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.5 Mobile/14E304 Safari/602.1", "viewport": { "width": 568, "height": 320 @@ -550,7 +550,7 @@ "defaultBrowserType": "webkit" }, "iPhone SE (3rd gen)": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.4 Mobile/19E241 Safari/602.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.5 Mobile/19E241 Safari/602.1", "viewport": { "width": 375, "height": 667 @@ -561,7 +561,7 @@ "defaultBrowserType": "webkit" }, "iPhone SE (3rd gen) landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.4 Mobile/19E241 Safari/602.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.5 Mobile/19E241 Safari/602.1", "viewport": { "width": 667, "height": 375 @@ -572,7 +572,7 @@ "defaultBrowserType": "webkit" }, "iPhone X": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 375, "height": 812 @@ -583,7 +583,7 @@ "defaultBrowserType": "webkit" }, "iPhone X landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.4 Mobile/15A372 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.5 Mobile/15A372 Safari/604.1", "viewport": { "width": 812, "height": 375 @@ -594,7 +594,7 @@ "defaultBrowserType": "webkit" }, "iPhone XR": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 414, "height": 896 @@ -605,7 +605,7 @@ "defaultBrowserType": "webkit" }, "iPhone XR landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "viewport": { "width": 896, "height": 414 @@ -616,7 +616,7 @@ "defaultBrowserType": "webkit" }, "iPhone 11": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 414, "height": 896 @@ -631,7 +631,7 @@ "defaultBrowserType": "webkit" }, "iPhone 11 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 414, "height": 896 @@ -646,7 +646,7 @@ "defaultBrowserType": "webkit" }, "iPhone 11 Pro": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 375, "height": 812 @@ -661,7 +661,7 @@ "defaultBrowserType": "webkit" }, "iPhone 11 Pro landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 375, "height": 812 @@ -676,7 +676,7 @@ "defaultBrowserType": "webkit" }, "iPhone 11 Pro Max": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 414, "height": 896 @@ -691,7 +691,7 @@ "defaultBrowserType": "webkit" }, "iPhone 11 Pro Max landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 414, "height": 896 @@ -706,7 +706,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -721,7 +721,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -736,7 +736,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 Pro": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -751,7 +751,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 Pro landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -766,7 +766,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 Pro Max": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 428, "height": 926 @@ -781,7 +781,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 Pro Max landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 428, "height": 926 @@ -796,7 +796,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 Mini": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 375, "height": 812 @@ -811,7 +811,7 @@ "defaultBrowserType": "webkit" }, "iPhone 12 Mini landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 375, "height": 812 @@ -826,7 +826,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -841,7 +841,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -856,7 +856,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 Pro": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -871,7 +871,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 Pro landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -886,7 +886,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 Pro Max": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 428, "height": 926 @@ -901,7 +901,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 Pro Max landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 428, "height": 926 @@ -916,7 +916,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 Mini": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 375, "height": 812 @@ -931,7 +931,7 @@ "defaultBrowserType": "webkit" }, "iPhone 13 Mini landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 375, "height": 812 @@ -946,7 +946,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -961,7 +961,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 390, "height": 844 @@ -976,7 +976,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 Plus": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 428, "height": 926 @@ -991,7 +991,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 Plus landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 428, "height": 926 @@ -1006,7 +1006,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 Pro": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 393, "height": 852 @@ -1021,7 +1021,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 Pro landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 393, "height": 852 @@ -1036,7 +1036,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 Pro Max": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 430, "height": 932 @@ -1051,7 +1051,7 @@ "defaultBrowserType": "webkit" }, "iPhone 14 Pro Max landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 430, "height": 932 @@ -1066,7 +1066,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 393, "height": 852 @@ -1081,7 +1081,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 393, "height": 852 @@ -1096,7 +1096,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 Plus": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 430, "height": 932 @@ -1111,7 +1111,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 Plus landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 430, "height": 932 @@ -1126,7 +1126,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 Pro": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 393, "height": 852 @@ -1141,7 +1141,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 Pro landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 393, "height": 852 @@ -1156,7 +1156,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 Pro Max": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 430, "height": 932 @@ -1171,7 +1171,7 @@ "defaultBrowserType": "webkit" }, "iPhone 15 Pro Max landscape": { - "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", "screen": { "width": 430, "height": 932 @@ -1717,7 +1717,7 @@ "defaultBrowserType": "firefox" }, "Desktop Safari": { - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15", "screen": { "width": 1792, "height": 1120 diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index 800d0b6bbf0ce..4cdeb3a353b2f 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -32,8 +32,8 @@ import type { Protocol } from './protocol'; import type { PageProxyMessageReceivedPayload } from './wkConnection'; import type * as channels from '@protocol/channels'; -const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15'; -const BROWSER_VERSION = '18.4'; +const BROWSER_VERSION = '18.5'; +const DEFAULT_USER_AGENT = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/${BROWSER_VERSION} Safari/605.1.15`; export class WKBrowser extends Browser { private readonly _connection: WKConnection; From 126239bb554a634ff7974f6e0bfeb45790193682 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 4 Jun 2025 14:21:00 +0100 Subject: [PATCH 005/222] test: skip permission tests on Debian 11 (#36194) --- tests/library/permissions.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/library/permissions.spec.ts b/tests/library/permissions.spec.ts index b305d92691635..b0a1aa67137af 100644 --- a/tests/library/permissions.spec.ts +++ b/tests/library/permissions.spec.ts @@ -16,6 +16,7 @@ */ import { contextTest as it, expect } from '../config/browserTest'; +import { hostPlatform } from '../../packages/playwright-core/src/server/utils/hostPlatform'; function getPermission(page, name) { return page.evaluate(name => navigator.permissions.query({ name }).then(result => result.state), name); @@ -31,6 +32,7 @@ it.describe('permissions', () => { it('should deny permission when not listed', async ({ page, context, server, browserName, isMac, macVersion }) => { it.skip(browserName === 'webkit' && isMac && macVersion === 13, 'WebKit on macOS 13 is frozen.'); + it.skip(hostPlatform.startsWith('debian11'), 'WebKit on Debian 11 is frozen.'); await page.goto(server.EMPTY_PAGE); await context.grantPermissions([], { origin: server.EMPTY_PAGE }); From 5659432229fd28bfc8cd97b60b71e4a9ec5e8b56 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 4 Jun 2025 14:44:08 +0100 Subject: [PATCH 006/222] test: skip scrolling tests on Android (#36195) Signed-off-by: Max Schmitt Co-authored-by: Dmitry Gozman --- tests/page/page-click-timeout-4.spec.ts | 3 ++- tests/page/page-click.spec.ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/page/page-click-timeout-4.spec.ts b/tests/page/page-click-timeout-4.spec.ts index d27802797b2c6..a973061ed81ea 100644 --- a/tests/page/page-click-timeout-4.spec.ts +++ b/tests/page/page-click-timeout-4.spec.ts @@ -49,7 +49,8 @@ it('should click for the second time after first timeout', async ({ page, server expect(await page.evaluate('result')).toBe('Clicked'); }); -it('should fail to click the button behind a large header after scrolling around', async ({ page }) => { +it('should fail to click the button behind a large header after scrolling around', async ({ page, isAndroid }) => { + it.skip(isAndroid, 'Different viewport size'); await page.setViewportSize({ width: 500, height: 240 }); await page.setContent(` + +
    +
  1. hi1
  2. hi2
  3. hi3
  4. hi4
  5. hi5
  6. hi6
  7. hi7
  8. hi8
  9. +
  10. hi9
  11. +
  12. hi10
  13. hi11
  14. hi12
  15. hi13
  16. hi14
  17. +
+ +
Overlay
+ `); + await page.$eval('ol', e => { + const target = document.querySelector('#target') as HTMLElement; + e.scrollTo({ top: target.offsetTop, behavior: 'instant' }); + }); + await page.click('#target'); + expect(await page.evaluate(() => window['__clicked'])).toBe(true); +}); + it('should click the button with px border with offset', async ({ page, server, browserName }) => { await page.goto(server.PREFIX + '/input/button.html'); await page.$eval('button', button => button.style.borderWidth = '8px'); From 2973b0b0c0404aef8eac15f2cc181892ca1ee408 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 18 Jun 2025 12:04:33 +0200 Subject: [PATCH 070/222] test: prevent indexeddb race conditions (#36347) --- tests/library/browsercontext-storage-state.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/library/browsercontext-storage-state.spec.ts b/tests/library/browsercontext-storage-state.spec.ts index 9aed5f7466217..972453e1b2eb9 100644 --- a/tests/library/browsercontext-storage-state.spec.ts +++ b/tests/library/browsercontext-storage-state.spec.ts @@ -360,10 +360,19 @@ it('should roundtrip local storage in third-party context', async ({ page, conte it('should support IndexedDB', async ({ page, server, contextFactory }) => { await page.goto(server.PREFIX + '/to-do-notifications/index.html'); + + await expect(page.locator('#notifications')).toMatchAriaSnapshot(` + - list: + - listitem: Database initialised. + `); await page.getByLabel('Task title').fill('Pet the cat'); await page.getByLabel('Hours').fill('1'); await page.getByLabel('Mins').fill('1'); await page.getByText('Add Task').click(); + await expect(page.locator('#notifications')).toMatchAriaSnapshot(` + - list: + - listitem: "Transaction completed: database modification finished." + `); const storageState = await page.context().storageState({ indexedDB: true }); expect(storageState.origins).toEqual([ From f050c3fc61574d2fcca469d1871da7230cfc0aa5 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 18 Jun 2025 13:59:09 +0100 Subject: [PATCH 071/222] fix(trace): include method into "Fetch" action title (#36350) --- .../src/utils/isomorphic/protocolMetainfo.ts | 2 +- packages/protocol/src/protocol.yml | 2 +- packages/trace-viewer/src/ui/actionList.tsx | 5 ++++- tests/library/trace-viewer.spec.ts | 12 ++++++------ tests/library/tracing.spec.ts | 2 +- tests/playwright-test/playwright.trace.spec.ts | 14 +++++++------- tests/playwright-test/reporter-html.spec.ts | 10 +++++----- tests/playwright-test/test-step.spec.ts | 6 +++--- 8 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts index 53fc12499dfb0..673dcd2612286 100644 --- a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts +++ b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts @@ -17,7 +17,7 @@ // This file is generated by generate_channels.js, do not edit manually. export const methodMetainfo = new Map([ - ['APIRequestContext.fetch', { title: 'Fetch "{url}"', }], + ['APIRequestContext.fetch', { title: '{method} "{url}"', }], ['APIRequestContext.fetchResponseBody', { internal: true, }], ['APIRequestContext.fetchLog', { internal: true, }], ['APIRequestContext.storageState', { internal: true, }], diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 849bc91305b4a..c33c381bdfdcc 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -366,7 +366,7 @@ APIRequestContext: commands: fetch: - title: Fetch "{url}" + title: '{method} "{url}"' parameters: url: string encodedParams: string? diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index 6e86410e24d33..ec8d90bd7eb9c 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -164,7 +164,10 @@ export function renderTitleForCall(action: ActionTraceEvent): { elements: React. title.push(chunk); const param = formatProtocolParam(action.params, quotedText); - elements.push({param}); + if (match.index === 0) + elements.push(param); + else + elements.push({param}); title.push(param); currentIndex = match.index + fullMatch.length; } diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 71dc0c70f6dc5..942f89422f92a 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -1056,16 +1056,16 @@ test('should open two trace files', async ({ context, page, request, server, sho const traceViewer = await showTraceViewer([contextTrace, apiTrace]); - await traceViewer.selectAction('FETCH', 0); - await traceViewer.selectAction('FETCH', 1); - await traceViewer.selectAction('FETCH', 2); + await traceViewer.selectAction('GET'); + await traceViewer.selectAction('HEAD'); + await traceViewer.selectAction('POST'); await expect(traceViewer.actionTitles).toHaveText([ - /Fetch "\/simple\.json"/, + /GET "\/simple\.json"/, /Navigate to "\/input\/button\.html"/, - /Fetch "\/simplezip\.json"/, + /HEAD "\/simplezip\.json"/, /Click.*locator\('button'\)/, /Click.*locator\('button'\)/, - /Fetch "\/one-style\.css"/, + /POST "\/one-style\.css"/, ]); await traceViewer.page.getByRole('tab', { name: 'Metadata' }).click(); diff --git a/tests/library/tracing.spec.ts b/tests/library/tracing.spec.ts index 1a88370e38338..e1aff51bc24ad 100644 --- a/tests/library/tracing.spec.ts +++ b/tests/library/tracing.spec.ts @@ -165,7 +165,7 @@ test('should include context API requests', async ({ context, page, server }, te await page.request.post(server.PREFIX + '/simple.json', { data: { foo: 'bar' } }); await context.tracing.stop({ path: testInfo.outputPath('trace.zip') }); const { events, actions } = await parseTraceRaw(testInfo.outputPath('trace.zip')); - expect(actions).toContain('Fetch "/simple.json"'); + expect(actions).toContain('POST "/simple.json"'); const harEntry = events.find(e => e.type === 'resource-snapshot'); expect(harEntry).toBeTruthy(); expect(harEntry.snapshot.request.url).toBe(server.PREFIX + '/simple.json'); diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 08d49c00e70bd..745a1dee31156 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -98,7 +98,7 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => { ' Fixture "page"', ' Create page', 'Navigate to "about:blank"', - 'Fetch "/empty.html"', + 'GET "/empty.html"', 'After Hooks', ' Fixture "page"', ' Fixture "context"', @@ -108,7 +108,7 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => { expect(trace2.actionTree).toEqual([ 'Before Hooks', 'Create request context', - 'Fetch "/empty.html"', + 'GET "/empty.html"', 'After Hooks', ]); const trace3 = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.zip')); @@ -121,7 +121,7 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => { ' Fixture "page"', ' Create page', 'Navigate to "about:blank"', - 'Fetch "/empty.html"', + 'GET "/empty.html"', 'Expect "toBe"', 'After Hooks', ' Fixture "page"', @@ -328,7 +328,7 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve ' afterAll hook', ' Fixture "request"', ' Create request context', - ' Fetch "/empty.html"', + ' GET "/empty.html"', ' Fixture "request"', 'Worker Cleanup', ' Fixture "browser"', @@ -488,7 +488,7 @@ test(`trace:retain-on-failure should create trace if request context is disposed }, { trace: 'retain-on-failure' }); const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip'); const trace = await parseTrace(tracePath); - expect(trace.titles).toContain('Fetch "/empty.html"'); + expect(trace.titles).toContain('GET "/empty.html"'); expect(result.failed).toBe(1); }); @@ -1137,7 +1137,7 @@ test('trace:retain-on-first-failure should create trace if request context is di }, { trace: 'retain-on-first-failure' }); const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip'); const trace = await parseTrace(tracePath); - expect(trace.titles).toContain('Fetch "/empty.html"'); + expect(trace.titles).toContain('GET "/empty.html"'); expect(result.failed).toBe(1); }); @@ -1243,7 +1243,7 @@ test('should not nest top level expect into unfinished api calls ', { ' Fixture "page"', ' Create page', 'Navigate to "/index"', - 'Fetch "/hang"', + 'GET "/hang"', 'Expect "toBeVisible"', 'After Hooks', ' Fixture "page"', diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index fc57ad7f7314b..83465d8d06bbf 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -637,7 +637,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { await page.click('text=Source'); await expect(page.locator('.source-line-running')).toContainText('page.evaluate'); - await page.click('.action-title >> text=FETCH'); + await page.click('.action-title >> text=GET'); await page.click('text=Source'); await expect(page.locator('.source-line-running')).toContainText('request.get'); }); @@ -668,10 +668,10 @@ for (const useIntermediateMergeReport of [true, false] as const) { await page.getByRole('link', { name: 'View Trace' }).click(); // Trace viewer should not hang here when displaying parallal requests. - await expect(page.getByTestId('actions-tree')).toContainText('Fetch'); - await page.getByText('Fetch').nth(2).click(); - await page.getByText('Fetch').nth(1).click(); - await page.getByText('Fetch').nth(0).click(); + await expect(page.getByTestId('actions-tree')).toContainText('GET'); + await page.getByText('GET').nth(2).click(); + await page.getByText('GET').nth(1).click(); + await page.getByText('GET').nth(0).click(); }); test('should warn user when viewing via file:// protocol', async ({ runInlineTest, page, showReport }, testInfo) => { diff --git a/tests/playwright-test/test-step.spec.ts b/tests/playwright-test/test-step.spec.ts index af3aa21219b41..2e2447dab5051 100644 --- a/tests/playwright-test/test-step.spec.ts +++ b/tests/playwright-test/test-step.spec.ts @@ -1222,9 +1222,9 @@ pw:api |Wait for navigation @ a.test.ts:5 pw:api |Navigate to "data:" @ a.test.ts:6 pw:api |Click locator('button') @ a.test.ts:8 pw:api |Click getByRole('button') @ a.test.ts:9 -pw:api |Fetch "/empty.html" @ a.test.ts:10 +pw:api |GET "/empty.html" @ a.test.ts:10 pw:api |↪ error: -pw:api |Fetch "/empty.html" @ a.test.ts:11 +pw:api |GET "/empty.html" @ a.test.ts:11 pw:api |↪ error: hook |After Hooks fixture | request @@ -1519,7 +1519,7 @@ fixture | page pw:api | Create page test.step |custom step @ a.test.ts:4 pw:api | Navigate to "/empty.html" @ a.test.ts:12 -pw:api | Fetch "/empty.html" @ a.test.ts:6 +pw:api | GET "/empty.html" @ a.test.ts:6 expect | toBe @ a.test.ts:8 hook |After Hooks fixture | page From 50cb8a1ffb50ae2f7e365a2bedbdfed387415e09 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 19 Jun 2025 11:28:29 +0200 Subject: [PATCH 072/222] chore: prevent launching more browsers in server mode (#36353) --- .../src/remote/playwrightConnection.ts | 12 +++++------- .../src/remote/playwrightServer.ts | 8 -------- .../src/server/dispatchers/androidDispatcher.ts | 8 +++++++- .../server/dispatchers/browserTypeDispatcher.ts | 13 ++++++++++++- .../src/server/dispatchers/electronDispatcher.ts | 6 +++++- .../server/dispatchers/playwrightDispatcher.ts | 16 +++++++++------- .../playwright-core/src/server/utils/wsServer.ts | 3 --- tests/library/browsertype-connect.spec.ts | 6 ++++++ 8 files changed, 44 insertions(+), 28 deletions(-) diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 94fe34e7aa5b6..1b56d45698530 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -131,16 +131,13 @@ export class PlaywrightConnection { const browser = await playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); browser.options.sdkLanguage = options.sdkLanguage; - this._cleanups.push(async () => { - for (const browser of playwright.allBrowsers()) - await browser.close({ reason: 'Connection terminated' }); - }); + this._cleanups.push(() => browser.close({ reason: 'Connection terminated' })); browser.on(Browser.Events.Disconnected, () => { // Underlying browser did close for some reason - force disconnect the client. this.close({ code: 1001, reason: 'Browser closed' }); }); - return new PlaywrightDispatcher(scope, playwright, { socksProxy: ownedSocksProxy, preLaunchedBrowser: browser }); + return new PlaywrightDispatcher(scope, playwright, { socksProxy: ownedSocksProxy, preLaunchedBrowser: browser, denyLaunch: true, }); } private async _initPreLaunchedBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { @@ -161,6 +158,7 @@ export class PlaywrightConnection { socksProxy: this._preLaunched.socksProxy, preLaunchedBrowser: browser, sharedBrowser: this._options.sharedBrowser, + denyLaunch: true, }); // In pre-launched mode, keep only the pre-launched browser. for (const b of playwright.allBrowsers()) { @@ -179,7 +177,7 @@ export class PlaywrightConnection { // Underlying browser did close for some reason - force disconnect the client. this.close({ code: 1001, reason: 'Android device disconnected' }); }); - const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { preLaunchedAndroidDevice: androidDevice }); + const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { preLaunchedAndroidDevice: androidDevice, denyLaunch: true }); this._cleanups.push(() => playwrightDispatcher.cleanup()); return playwrightDispatcher; } @@ -241,7 +239,7 @@ export class PlaywrightConnection { } }); - const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { preLaunchedBrowser: browser }); + const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { preLaunchedBrowser: browser, denyLaunch: true }); return playwrightDispatcher; } diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index e823f8ce87935..44b204a962637 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -16,7 +16,6 @@ import { PlaywrightConnection } from './playwrightConnection'; import { createPlaywright } from '../server/playwright'; -import { debugLogger } from '../server/utils/debugLogger'; import { Semaphore } from '../utils/isomorphic/semaphore'; import { DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT } from '../utils/isomorphic/time'; import { WSServer } from '../server/utils/wsServer'; @@ -132,13 +131,6 @@ export class PlaywrightServer { }, id, () => semaphore.release()); }, - - onClose: async () => { - debugLogger.log('server', 'closing browsers'); - if (this._preLaunchedPlaywright) - await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close({ reason: 'Playwright Server stopped' }))); - debugLogger.log('server', 'closed browsers'); - } }); } diff --git a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts index cce5f325d1508..ea5d31461c961 100644 --- a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts @@ -27,8 +27,10 @@ import type * as channels from '@protocol/channels'; export class AndroidDispatcher extends Dispatcher implements channels.AndroidChannel { _type_Android = true; - constructor(scope: RootDispatcher, android: Android) { + _denyLaunch: boolean; + constructor(scope: RootDispatcher, android: Android, denyLaunch: boolean) { super(scope, android, 'Android', {}); + this._denyLaunch = denyLaunch; } async devices(params: channels.AndroidDevicesParams): Promise { @@ -159,6 +161,8 @@ export class AndroidDeviceDispatcher extends Dispatcher { + if (this.parentScope()._denyLaunch) + throw new Error(`Launching more browsers is not allowed.`); const context = await this._object.launchBrowser(params.pkg, params); return { context: BrowserContextDispatcher.from(this, context) }; } @@ -168,6 +172,8 @@ export class AndroidDeviceDispatcher extends Dispatcher { + if (this.parentScope()._denyLaunch) + throw new Error(`Launching more browsers is not allowed.`); return { context: BrowserContextDispatcher.from(this, await this._object.connectToWebView(params.socketName)) }; } } diff --git a/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts index e97b7cdc757d0..3b71e04072ac7 100644 --- a/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts @@ -25,19 +25,27 @@ import type * as channels from '@protocol/channels'; export class BrowserTypeDispatcher extends Dispatcher implements channels.BrowserTypeChannel { _type_BrowserType = true; - constructor(scope: RootDispatcher, browserType: BrowserType) { + private readonly _denyLaunch: boolean; + constructor(scope: RootDispatcher, browserType: BrowserType, denyLaunch: boolean) { super(scope, browserType, 'BrowserType', { executablePath: browserType.executablePath(), name: browserType.name() }); + this._denyLaunch = denyLaunch; } async launch(params: channels.BrowserTypeLaunchParams, metadata: CallMetadata): Promise { + if (this._denyLaunch) + throw new Error(`Launching more browsers is not allowed.`); + const browser = await this._object.launch(metadata, params); return { browser: new BrowserDispatcher(this, browser) }; } async launchPersistentContext(params: channels.BrowserTypeLaunchPersistentContextParams, metadata: CallMetadata): Promise { + if (this._denyLaunch) + throw new Error(`Launching more browsers is not allowed.`); + const browserContext = await this._object.launchPersistentContext(metadata, params.userDataDir, params); const browserDispatcher = new BrowserDispatcher(this, browserContext._browser); const contextDispatcher = BrowserContextDispatcher.from(browserDispatcher, browserContext); @@ -45,6 +53,9 @@ export class BrowserTypeDispatcher extends Dispatcher { + if (this._denyLaunch) + throw new Error(`Launching more browsers is not allowed.`); + const browser = await this._object.connectOverCDP(metadata, params.endpointURL, params); const browserDispatcher = new BrowserDispatcher(this, browser); return { diff --git a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts index 4eab4fcfc2438..7660526176980 100644 --- a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts @@ -28,12 +28,16 @@ import type * as channels from '@protocol/channels'; export class ElectronDispatcher extends Dispatcher implements channels.ElectronChannel { _type_Electron = true; + _denyLaunch: boolean; - constructor(scope: RootDispatcher, electron: Electron) { + constructor(scope: RootDispatcher, electron: Electron, denyLaunch: boolean) { super(scope, electron, 'Electron', {}); + this._denyLaunch = denyLaunch; } async launch(params: channels.ElectronLaunchParams): Promise { + if (this._denyLaunch) + throw new Error(`Launching more browsers is not allowed.`); const electronApplication = await this._object.launch(params); return { electronApplication: new ElectronApplicationDispatcher(this, electronApplication) }; } diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index 63e2ba997fe35..74ad4b4ab06f8 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -37,6 +37,7 @@ import type * as channels from '@protocol/channels'; type PlaywrightDispatcherOptions = { socksProxy?: SocksProxy; + denyLaunch?: boolean; preLaunchedBrowser?: Browser; preLaunchedAndroidDevice?: AndroidDevice; sharedBrowser?: boolean; @@ -47,12 +48,13 @@ export class PlaywrightDispatcher extends Dispatcher void; onUpgrade: (request: http.IncomingMessage, socket: stream.Duplex) => { error: string } | undefined; onConnection: (request: http.IncomingMessage, url: URL, ws: WebSocket, id: string) => WSConnection; - onClose(): Promise; }; export class WSServer { @@ -137,7 +136,5 @@ export class WSServer { this._wsServer = undefined; this.server = undefined; debugLogger.log('server', 'closed server'); - - await this._delegate.onClose?.(); } } diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index 1298e38b5e6a4..80653941e60b6 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -1039,6 +1039,12 @@ test.describe('launchServer only', () => { }); } }); + + test('cannot launch another browser', async ({ connect, startRemoteServer }) => { + const remoteServer = await startRemoteServer('launchServer'); + const browser = await connect(remoteServer.wsEndpoint()) as any; + await expect(browser._parent.launch({ timeout: 0 })).rejects.toThrowError('Launching more browsers is not allowed.'); + }); }); test('should refuse connecting when versions do not match', async ({ connect, childProcess }) => { From 66e903021290f6cde0f7be53b70fdd02b976ed05 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 19 Jun 2025 12:49:58 +0200 Subject: [PATCH 073/222] chore: lift up playwright prelaunch (#36330) --- .../src/remote/playwrightConnection.ts | 37 ++++++++----------- .../src/remote/playwrightServer.ts | 14 +++---- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 1b56d45698530..f96eded9149f7 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -15,7 +15,7 @@ */ import { SocksProxy } from '../server/utils/socksProxy'; -import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server'; +import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher } from '../server'; import { AndroidDevice } from '../server/android/android'; import { Browser } from '../server/browser'; import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher'; @@ -42,7 +42,6 @@ type Options = { }; type PreLaunched = { - playwright?: Playwright | undefined; browser?: Browser | undefined; androidDevice?: AndroidDevice | undefined; socksProxy?: SocksProxy | undefined; @@ -55,18 +54,18 @@ export class PlaywrightConnection { private _cleanups: (() => Promise)[] = []; private _id: string; private _disconnected = false; + private _playwright: Playwright; private _preLaunched: PreLaunched; private _options: Options; private _root: DispatcherScope; private _profileName: string; - constructor(lock: Promise, clientType: ClientType, ws: WebSocket, options: Options, preLaunched: PreLaunched, id: string, onClose: () => void) { + constructor(lock: Promise, clientType: ClientType, ws: WebSocket, options: Options, playwright: Playwright, preLaunched: PreLaunched, id: string, onClose: () => void) { this._ws = ws; + this._playwright = playwright; this._preLaunched = preLaunched; this._options = options; options.launchOptions = filterLaunchOptions(options.launchOptions, options.allowFSPaths); - if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser-or-android') - assert(preLaunched.playwright); if (clientType === 'pre-launched-browser-or-android') assert(preLaunched.browser || preLaunched.androidDevice); this._onClose = onClose; @@ -118,8 +117,6 @@ export class PlaywrightConnection { private async _initLaunchBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`); - const playwright = createPlaywright({ sdkLanguage: options.sdkLanguage, isServer: true }); - const ownedSocksProxy = await this._createOwnedSocksProxy(); let browserName = this._options.browserName; if ('bidi' === browserName) { @@ -128,7 +125,7 @@ export class PlaywrightConnection { else browserName = 'bidiChromium'; } - const browser = await playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); + const browser = await this._playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); browser.options.sdkLanguage = options.sdkLanguage; this._cleanups.push(() => browser.close({ reason: 'Connection terminated' })); @@ -137,12 +134,11 @@ export class PlaywrightConnection { this.close({ code: 1001, reason: 'Browser closed' }); }); - return new PlaywrightDispatcher(scope, playwright, { socksProxy: ownedSocksProxy, preLaunchedBrowser: browser, denyLaunch: true, }); + return new PlaywrightDispatcher(scope, this._playwright, { socksProxy: ownedSocksProxy, preLaunchedBrowser: browser, denyLaunch: true, }); } private async _initPreLaunchedBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { debugLogger.log('server', `[${this._id}] engaged pre-launched (browser) mode`); - const playwright = this._preLaunched.playwright!; // Note: connected client owns the socks proxy and configures the pattern. this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern); @@ -154,14 +150,14 @@ export class PlaywrightConnection { this.close({ code: 1001, reason: 'Browser closed' }); }); - const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { + const playwrightDispatcher = new PlaywrightDispatcher(scope, this._playwright, { socksProxy: this._preLaunched.socksProxy, preLaunchedBrowser: browser, sharedBrowser: this._options.sharedBrowser, denyLaunch: true, }); // In pre-launched mode, keep only the pre-launched browser. - for (const b of playwright.allBrowsers()) { + for (const b of this._playwright.allBrowsers()) { if (b !== browser) await b.close({ reason: 'Connection terminated' }); } @@ -171,22 +167,20 @@ export class PlaywrightConnection { private async _initPreLaunchedAndroidMode(scope: RootDispatcher) { debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`); - const playwright = this._preLaunched.playwright!; const androidDevice = this._preLaunched.androidDevice!; androidDevice.on(AndroidDevice.Events.Close, () => { // Underlying browser did close for some reason - force disconnect the client. this.close({ code: 1001, reason: 'Android device disconnected' }); }); - const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { preLaunchedAndroidDevice: androidDevice, denyLaunch: true }); + const playwrightDispatcher = new PlaywrightDispatcher(scope, this._playwright, { preLaunchedAndroidDevice: androidDevice, denyLaunch: true }); this._cleanups.push(() => playwrightDispatcher.cleanup()); return playwrightDispatcher; } private _initDebugControllerMode(): DebugControllerDispatcher { debugLogger.log('server', `[${this._id}] engaged reuse controller mode`); - const playwright = this._preLaunched.playwright!; // Always create new instance based on the reused Playwright instance. - return new DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController); + return new DebugControllerDispatcher(this._dispatcherConnection, this._playwright.debugController); } private async _initReuseBrowsersMode(scope: RootDispatcher, options: channels.RootInitializeParams) { @@ -194,10 +188,9 @@ export class PlaywrightConnection { // clients come and go, while the browser stays the same. debugLogger.log('server', `[${this._id}] engaged reuse browsers mode for ${this._options.browserName}`); - const playwright = this._preLaunched.playwright!; const requestedOptions = launchOptionsHash(this._options.launchOptions); - let browser = playwright.allBrowsers().find(b => { + let browser = this._playwright.allBrowsers().find(b => { if (b.options.name !== this._options.browserName) return false; const existingOptions = launchOptionsHash(b.options.originalLaunchOptions); @@ -205,7 +198,7 @@ export class PlaywrightConnection { }); // Close remaining browsers of this type+channel. Keep different browser types for the speed. - for (const b of playwright.allBrowsers()) { + for (const b of this._playwright.allBrowsers()) { if (b === browser) continue; if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel) @@ -213,7 +206,7 @@ export class PlaywrightConnection { } if (!browser) { - browser = await playwright[(this._options.browserName || 'chromium') as 'chromium'].launch(serverSideCallMetadata(), { + browser = await this._playwright[(this._options.browserName || 'chromium') as 'chromium'].launch(serverSideCallMetadata(), { ...this._options.launchOptions, headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS, }); @@ -227,7 +220,7 @@ export class PlaywrightConnection { this._cleanups.push(async () => { // Don't close the pages so that user could debug them, // but close all the empty browsers and contexts to clean up. - for (const browser of playwright.allBrowsers()) { + for (const browser of this._playwright.allBrowsers()) { for (const context of browser.contexts()) { if (!context.pages().length) await context.close({ reason: 'Connection terminated' }); @@ -239,7 +232,7 @@ export class PlaywrightConnection { } }); - const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, { preLaunchedBrowser: browser, denyLaunch: true }); + const playwrightDispatcher = new PlaywrightDispatcher(scope, this._playwright, { preLaunchedBrowser: browser, denyLaunch: true }); return playwrightDispatcher; } diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index 44b204a962637..a6add53eb4e14 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -40,16 +40,17 @@ type ServerOptions = { }; export class PlaywrightServer { - private _preLaunchedPlaywright: Playwright | undefined; + private _playwright: Playwright; private _options: ServerOptions; private _wsServer: WSServer; constructor(options: ServerOptions) { this._options = options; if (options.preLaunchedBrowser) - this._preLaunchedPlaywright = options.preLaunchedBrowser.attribution.playwright; + this._playwright = options.preLaunchedBrowser.attribution.playwright; if (options.preLaunchedAndroidDevice) - this._preLaunchedPlaywright = options.preLaunchedAndroidDevice._android.attribution.playwright; + this._playwright = options.preLaunchedAndroidDevice._android.attribution.playwright; + this._playwright ??= createPlaywright({ sdkLanguage: 'javascript', isServer: true }); const browserSemaphore = new Semaphore(this._options.maxConnections); const controllerSemaphore = new Semaphore(1); @@ -95,11 +96,6 @@ export class PlaywrightServer { // Instantiate playwright for the extension modes. const isExtension = this._options.mode === 'extension'; - if (isExtension) { - if (!this._preLaunchedPlaywright) - this._preLaunchedPlaywright = createPlaywright({ sdkLanguage: 'javascript', isServer: true }); - } - let clientType: ClientType = 'launch-browser'; let semaphore: Semaphore = browserSemaphore; if (isExtension && url.searchParams.has('debug-controller')) { @@ -123,8 +119,8 @@ export class PlaywrightServer { allowFSPaths: this._options.mode === 'extension', sharedBrowser: this._options.mode === 'launchServerShared', }, + this._playwright, { - playwright: this._preLaunchedPlaywright, browser: this._options.preLaunchedBrowser, androidDevice: this._options.preLaunchedAndroidDevice, socksProxy: this._options.preLaunchedSocksProxy, From 5f65f32d26f99a8c7243e7ec0bf75f835d789030 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 19 Jun 2025 11:06:48 -0700 Subject: [PATCH 074/222] test: update expectation for secure cookie test on WK Win (#36361) --- tests/library/browsercontext-proxy.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/library/browsercontext-proxy.spec.ts b/tests/library/browsercontext-proxy.spec.ts index fcdae718239c0..7a68a3c318efa 100644 --- a/tests/library/browsercontext-proxy.spec.ts +++ b/tests/library/browsercontext-proxy.spec.ts @@ -64,7 +64,7 @@ it('should use proxy', async ({ contextFactory, server, proxyServer }) => { await context.close(); }); -it('should send secure cookies to subdomain.localhost', async ({ contextFactory, browserName, server, proxyServer }) => { +it('should send secure cookies to subdomain.localhost', async ({ contextFactory, browserName, server, isWindows, proxyServer }) => { proxyServer.forwardTo(server.PORT); const context = await contextFactory({ proxy: { server: `localhost:${proxyServer.PORT}` }, @@ -88,7 +88,7 @@ it('should send secure cookies to subdomain.localhost', async ({ contextFactory, name: 'non-secure', domain: 'subdomain.localhost', }, - ...(browserName === 'webkit' ? [] : [{ + ...((browserName === 'webkit') && !isWindows ? [] : [{ name: 'secure', domain: 'subdomain.localhost', }]), From 07d18242c93fb9e6a3f523dfc6474272afe0dec5 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 20 Jun 2025 08:59:05 +0200 Subject: [PATCH 075/222] docs: correct spelling of 'informational' in README badge link (#36367) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5dcab4d78cfc..d912d0d166770 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.23-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.23-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) From ab7b18e36c03ac92bec08733a8dd9ce528aa4c32 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 20 Jun 2025 09:23:56 +0200 Subject: [PATCH 076/222] fix(ct): fsWatcher update comparison (#36366) --- packages/playwright/src/fsWatcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright/src/fsWatcher.ts b/packages/playwright/src/fsWatcher.ts index 4078e4f9945c7..a6989defcfcd7 100644 --- a/packages/playwright/src/fsWatcher.ts +++ b/packages/playwright/src/fsWatcher.ts @@ -33,7 +33,7 @@ export class Watcher { } async update(watchedPaths: string[], ignoredFolders: string[], reportPending: boolean) { - if (JSON.stringify([this._watchedPaths, this._ignoredFolders]) === JSON.stringify(watchedPaths, ignoredFolders)) + if (JSON.stringify([this._watchedPaths, this._ignoredFolders]) === JSON.stringify([watchedPaths, ignoredFolders])) return; if (reportPending) From d4c0d752371136c422434b28200cf0cbb68c7fc5 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 09:01:46 +0100 Subject: [PATCH 077/222] chore: make fetch progress "strict" (#36318) --- packages/playwright-core/src/server/fetch.ts | 49 +++++++------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 6d0d2281fc3f5..26339540fa015 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -27,7 +27,7 @@ import { BrowserContext, verifyClientCertificates } from './browserContext'; import { Cookie, CookieStore, domainMatches, parseRawCookie } from './cookieStore'; import { MultipartFormData } from './formData'; import { SdkObject } from './instrumentation'; -import { ProgressController } from './progress'; +import { isAbortError, ProgressController } from './progress'; import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor'; import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent, timingForSocket } from './utils/happyEyeballs'; import { Tracing } from './trace/recorder/tracing'; @@ -84,11 +84,12 @@ export type APIRequestFinishedEvent = { type SendRequestOptions = https.RequestOptions & { maxRedirects: number, - deadline: number, headers: HeadersObject, __testHookLookup?: (hostname: string) => LookupAddress[] }; +type SendRequestResult = Omit & { body: Buffer }; + export abstract class APIRequestContext extends SdkObject { static Events = { Dispose: 'dispose', @@ -185,16 +186,11 @@ export abstract class APIRequestContext extends SdkObject { let maxRedirects = params.maxRedirects ?? (defaults.maxRedirects ?? 20); maxRedirects = maxRedirects === 0 ? -1 : maxRedirects; - const timeout = params.timeout; - const deadline = timeout && (monotonicTime() + timeout); - const options: SendRequestOptions = { method, headers, agent, maxRedirects, - timeout, - deadline, ...getMatchingTLSOptionsForOrigin(this._defaultOptions().clientCertificates, requestUrl.origin), __testHookLookup: (params as any).__testHookLookup, }; @@ -205,10 +201,10 @@ export abstract class APIRequestContext extends SdkObject { const postData = serializePostData(params, headers); if (postData) setHeader(headers, 'content-length', String(postData.byteLength)); - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); const fetchResponse = await controller.run(progress => { return this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries); - }, timeout); + }, params.timeout); const fetchUid = this._storeResponseBody(fetchResponse.body); this.fetchLog.set(fetchUid, controller.metadata.log); const failOnStatusCode = params.failOnStatusCode !== undefined ? params.failOnStatusCode : !!defaults.failOnStatusCode; @@ -252,10 +248,10 @@ export abstract class APIRequestContext extends SdkObject { return cookies; } - private async _updateRequestCookieHeader(url: URL, headers: HeadersObject) { + private async _updateRequestCookieHeader(progress: Progress, url: URL, headers: HeadersObject) { if (getHeader(headers, 'cookie') !== undefined) return; - const contextCookies = await this._cookies(url); + const contextCookies = await progress.race(this._cookies(url)); // Browser context returns cookies with domain matching both .example.com and // example.com. Those without leading dot are only sent when domain is strictly // matching example.com, but not for sub.example.com. @@ -266,31 +262,33 @@ export abstract class APIRequestContext extends SdkObject { } } - private async _sendRequestWithRetries(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer, maxRetries?: number): Promise & { body: Buffer }>{ + private async _sendRequestWithRetries(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer, maxRetries?: number): Promise{ maxRetries ??= 0; let backoff = 250; for (let i = 0; i <= maxRetries; i++) { try { return await this._sendRequest(progress, url, options, postData); } catch (e) { + if (isAbortError(e)) + throw e; e = rewriteOpenSSLErrorIfNeeded(e); if (maxRetries === 0) throw e; - if (i === maxRetries || (options.deadline && monotonicTime() + backoff > options.deadline)) + if (i === maxRetries) throw new Error(`Failed after ${i + 1} attempt(s): ${e}`); // Retry on connection reset only. if (e.code !== 'ECONNRESET') throw e; progress.log(` Received ECONNRESET, will retry after ${backoff}ms.`); - await new Promise(f => setTimeout(f, backoff)); + await progress.wait(backoff); backoff *= 2; } } throw new Error('Unreachable'); } - private async _sendRequest(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer): Promise & { body: Buffer }>{ - await this._updateRequestCookieHeader(url, options.headers); + private async _sendRequest(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer): Promise{ + await this._updateRequestCookieHeader(progress, url, options.headers); const requestCookies = getHeader(options.headers, 'cookie')?.split(';').map(p => { const [name, value] = p.split('=').map(v => v.trim()); @@ -305,7 +303,7 @@ export abstract class APIRequestContext extends SdkObject { }; this.emit(APIRequestContext.Events.Request, requestEvent); - return new Promise((fulfill, reject) => { + const resultPromise = new Promise((fulfill, reject) => { const requestConstructor: ((url: URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void) => http.ClientRequest) = (url.protocol === 'https:' ? https : http).request; // If we have a proxy agent already, do not override it. @@ -402,8 +400,6 @@ export abstract class APIRequestContext extends SdkObject { headers, agent: options.agent, maxRedirects: options.maxRedirects - 1, - timeout: options.timeout, - deadline: options.deadline, ...getMatchingTLSOptionsForOrigin(this._defaultOptions().clientCertificates, url.origin), __testHookLookup: options.__testHookLookup, }; @@ -492,6 +488,7 @@ export abstract class APIRequestContext extends SdkObject { body.on('end', notifyBodyFinished); }); request.on('error', reject); + progress.cleanupWhenAborted(() => request.destroy()); listeners.push( eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => { @@ -543,23 +540,11 @@ export abstract class APIRequestContext extends SdkObject { progress.log(` ${name}: ${value}`); } - if (options.deadline) { - const rejectOnTimeout = () => { - reject(new Error(`Request timed out after ${options.timeout}ms`)); - request.destroy(); - }; - const remaining = options.deadline - monotonicTime(); - if (remaining <= 0) { - rejectOnTimeout(); - return; - } - request.setTimeout(remaining, rejectOnTimeout); - } - if (postData) request.write(postData); request.end(); }); + return progress.race(resultPromise); } private _getHttpCredentials(url: URL) { From 55cb7c916c820c85813ce7eab40914a35586c714 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 09:02:00 +0100 Subject: [PATCH 078/222] chore: make navigation actions' progress "strict" (#36321) --- packages/playwright-core/src/server/frames.ts | 41 ++++++++++--------- packages/playwright-core/src/server/helper.ts | 2 +- .../playwright-core/src/server/network.ts | 2 +- packages/playwright-core/src/server/page.ts | 26 +++++++----- tests/page/page-set-content.spec.ts | 28 +++++++++++++ 5 files changed, 66 insertions(+), 33 deletions(-) diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 8623e4b1303ad..4d2d62df9ab23 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -583,7 +583,7 @@ export class Frame extends SdkObject { } } - async raceNavigationAction(progress: Progress, options: types.GotoOptions, action: () => Promise): Promise { + async raceNavigationAction(progress: Progress, action: () => Promise): Promise { return LongStandingScope.raceMultiple([ this._detachedScope, this._page.openScope, @@ -592,7 +592,7 @@ export class Frame extends SdkObject { const data = this._redirectedNavigations.get(e.documentId); if (data) { progress.log(`waiting for redirected navigation to "${data.url}"`); - return data.gotoPromise; + return progress.race(data.gotoPromise); } } throw e; @@ -600,7 +600,7 @@ export class Frame extends SdkObject { } redirectNavigation(url: string, documentId: string, referer: string | undefined) { - const controller = new ProgressController(serverSideCallMetadata(), this); + const controller = new ProgressController(serverSideCallMetadata(), this, 'strict'); const data = { url, gotoPromise: controller.run(progress => this.gotoImpl(progress, url, { referer }), 0), @@ -610,10 +610,10 @@ export class Frame extends SdkObject { } async goto(metadata: CallMetadata, url: string, options: types.GotoOptions): Promise { - const constructedNavigationURL = constructURLBasedOnBaseURL(this._page.browserContext._options.baseURL, url); - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); return controller.run(progress => { - return this.raceNavigationAction(progress, options, async () => this.gotoImpl(progress, constructedNavigationURL, options)); + const constructedNavigationURL = constructURLBasedOnBaseURL(this._page.browserContext._options.baseURL, url); + return this.raceNavigationAction(progress, async () => this.gotoImpl(progress, constructedNavigationURL, options)); }, options.timeout); } @@ -633,7 +633,7 @@ export class Frame extends SdkObject { const navigationEvents: NavigationEvent[] = []; const collectNavigations = (arg: NavigationEvent) => navigationEvents.push(arg); this.on(Frame.Events.InternalNavigation, collectNavigations); - const navigateResult = await this._page.delegate.navigateFrame(this, url, referer).finally( + const navigateResult = await progress.race(this._page.delegate.navigateFrame(this, url, referer)).finally( () => this.off(Frame.Events.InternalNavigation, collectNavigations)); let event: NavigationEvent; @@ -669,7 +669,7 @@ export class Frame extends SdkObject { await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise; const request = event.newDocument ? event.newDocument.request : undefined; - const response = request ? request._finalRequest().response() : null; + const response = request ? progress.race(request._finalRequest().response()) : null; return response; } @@ -693,7 +693,7 @@ export class Frame extends SdkObject { await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise; const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined; - return request ? request._finalRequest().response() : null; + return request ? progress.race(request._finalRequest().response()) : null; } async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise { @@ -871,26 +871,27 @@ export class Frame extends SdkObject { } async setContent(metadata: CallMetadata, html: string, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); return controller.run(async progress => { - await this.raceNavigationAction(progress, options, async () => { + await this.raceNavigationAction(progress, async () => { const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; progress.log(`setting frame content, waiting until "${waitUntil}"`); const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`; - const context = await this._utilityContext(); - const lifecyclePromise = new Promise((resolve, reject) => { - this._page.frameManager._consoleMessageTags.set(tag, () => { - // Clear lifecycle right after document.open() - see 'tag' below. - this._onClearLifecycle(); - this._waitForLoadState(progress, waitUntil).then(resolve).catch(reject); - }); + const context = await progress.race(this._utilityContext()); + const tagPromise = new ManualPromise(); + this._page.frameManager._consoleMessageTags.set(tag, () => { + // Clear lifecycle right after document.open() - see 'tag' below. + this._onClearLifecycle(); + tagPromise.resolve(); }); - const contentPromise = context.evaluate(({ html, tag }) => { + progress.cleanupWhenAborted(() => this._page.frameManager._consoleMessageTags.delete(tag)); + const lifecyclePromise = progress.race(tagPromise).then(() => this._waitForLoadState(progress, waitUntil)); + const contentPromise = progress.race(context.evaluate(({ html, tag }) => { document.open(); console.debug(tag); // eslint-disable-line no-console document.write(html); document.close(); - }, { html, tag }); + }, { html, tag })); await Promise.all([contentPromise, lifecyclePromise]); return null; }); diff --git a/packages/playwright-core/src/server/helper.ts b/packages/playwright-core/src/server/helper.ts index e5bf38dd7777c..fb6622846ca99 100644 --- a/packages/playwright-core/src/server/helper.ts +++ b/packages/playwright-core/src/server/helper.ts @@ -72,7 +72,7 @@ class Helper { }); const dispose = () => eventsHelper.removeEventListeners(listeners); progress.cleanupWhenAborted(dispose); - return { promise, dispose }; + return { promise: progress.race(promise), dispose }; } static secondsToRoundishMillis(value: number): number { diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index 739c109d8c48d..5536693c75ab7 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -190,7 +190,7 @@ export class Request extends SdkObject { return this._overrides?.headers || this._rawRequestHeadersPromise; } - response(): PromiseLike { + response(): Promise { return this._waitForResponsePromise; } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 13da603752150..7e56b07abccb5 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -369,22 +369,22 @@ export class Page extends SdkObject { } async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(progress => this.mainFrame().raceNavigationAction(progress, async () => { // Note: waitForNavigation may fail before we get response to reload(), // so we should await it immediately. const [response] = await Promise.all([ // Reload must be a new document, and should not be confused with a stray pushState. this.mainFrame()._waitForNavigation(progress, true /* requiresNewDocument */, options), - this.delegate.reload(), + progress.race(this.delegate.reload()), ]); return response; }), options.timeout); } async goBack(metadata: CallMetadata, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(progress => this.mainFrame().raceNavigationAction(progress, async () => { // Note: waitForNavigation may fail before we get response to goBack, // so we should catch it immediately. let error: Error | undefined; @@ -392,9 +392,11 @@ export class Page extends SdkObject { error = e; return null; }); - const result = await this.delegate.goBack(); - if (!result) + const result = await progress.race(this.delegate.goBack()); + if (!result) { + waitPromise.catch(() => {}); // Avoid an unhandled rejection. return null; + } const response = await waitPromise; if (error) throw error; @@ -403,8 +405,8 @@ export class Page extends SdkObject { } async goForward(metadata: CallMetadata, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(progress => this.mainFrame().raceNavigationAction(progress, async () => { // Note: waitForNavigation may fail before we get response to goForward, // so we should catch it immediately. let error: Error | undefined; @@ -412,9 +414,11 @@ export class Page extends SdkObject { error = e; return null; }); - const result = await this.delegate.goForward(); - if (!result) + const result = await progress.race(this.delegate.goForward()); + if (!result) { + waitPromise.catch(() => {}); // Avoid an unhandled rejection. return null; + } const response = await waitPromise; if (error) throw error; diff --git a/tests/page/page-set-content.spec.ts b/tests/page/page-set-content.spec.ts index 27fe18c860af3..38f5f111cc02a 100644 --- a/tests/page/page-set-content.spec.ts +++ b/tests/page/page-set-content.spec.ts @@ -129,3 +129,31 @@ it('should return empty content there is no iframe src', async ({ page, browserN expect(page.frames().length).toBe(2); expect(await page.frames()[1].content()).toBe(''); }); + +it('should handle timeout properly', async ({ page, toImpl, browserName }) => { + it.skip(browserName === 'firefox', 'tampering with console.debug in utility world does not work'); + + await toImpl(page).mainFrame().evaluateExpression(String(() => { + window['saved'] = console.debug.bind(console); + console.debug = () => {}; + }), { isFunction: true, world: 'utility' }); + const error = await page.setContent(`
hello
`, { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('page.setContent: Timeout 1000ms exceeded'); + + // Should recover after timeout. + await toImpl(page).mainFrame().evaluateExpression(String(() => { + console.debug = window['saved']; + }), { isFunction: true, world: 'utility' }); + await page.setContent(`
world
`); + await expect(page.locator('div')).toHaveText('world'); +}); + +it('should handle timeout properly 2', async ({ page, toImpl }) => { + await toImpl(page).mainFrame().evaluateExpression(String(() => { + document.close = () => { + while (true) {} + }; + }), { isFunction: true, world: 'utility' }); + const error = await page.setContent(`
hello
`, { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('page.setContent: Timeout 1000ms exceeded'); +}); From 20b8784c918555e8cc4241d1e3b2e47a92f7b99d Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 09:02:14 +0100 Subject: [PATCH 079/222] chore: make screenshot progress "strict" (#36323) --- .../src/server/dispatchers/frameDispatcher.ts | 2 +- packages/playwright-core/src/server/dom.ts | 2 +- .../src/server/firefox/ffPage.ts | 1 - packages/playwright-core/src/server/frames.ts | 120 +++++++++--------- packages/playwright-core/src/server/page.ts | 7 +- .../src/server/screenshotter.ts | 51 +++----- 6 files changed, 85 insertions(+), 98 deletions(-) diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 3afab78c254f7..38c31056f2304 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -246,7 +246,7 @@ export class FrameDispatcher extends Dispatcher { - return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, await this._frame._waitForFunctionExpression(metadata, params.expression, params.isFunction, parseArgument(params.arg), params)) }; + return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, await this._frame.waitForFunctionExpression(metadata, params.expression, params.isFunction, parseArgument(params.arg), params)) }; } async title(params: channels.FrameTitleParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 989c31fa38cb2..ce9ce887c03b3 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -833,7 +833,7 @@ export class ElementHandle extends js.JSHandle { } async screenshot(metadata: CallMetadata, options: ScreenshotOptions & types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); return controller.run( progress => this._page.screenshotter.screenshotElement(progress, this, options), options.timeout); diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 49ddf5a5c9238..a096946457b69 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -419,7 +419,6 @@ export class FFPage implements PageDelegate { height: viewportRect!.height, }; } - progress.throwIfAborted(); const { data } = await this._session.send('Page.screenshot', { mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'), clip: documentRect, diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 4d2d62df9ab23..5ce23ffb5cbe0 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1136,7 +1136,7 @@ export class Frame extends SdkObject { async rafrafTimeoutScreenshotElementWithProgress(progress: Progress, selector: string, timeout: number, options: ScreenshotOptions): Promise { return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, async handle => { - await handle._frame.rafrafTimeout(timeout); + await handle._frame.rafrafTimeout(progress, timeout); return await this._page.screenshotter.screenshotElement(progress, handle, options); }); } @@ -1484,63 +1484,67 @@ export class Frame extends SdkObject { return { matches, received }; } - async _waitForFunctionExpression(metadata: CallMetadata, expression: string, isFunction: boolean | undefined, arg: any, options: types.WaitForFunctionOptions, world: types.World = 'main'): Promise> { - const controller = new ProgressController(metadata, this); + async waitForFunctionExpression(metadata: CallMetadata, expression: string, isFunction: boolean | undefined, arg: any, options: types.WaitForFunctionOptions): Promise> { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(progress => this.waitForFunctionExpressionImpl(progress, expression, isFunction, arg, options, 'main'), options.timeout); + } + + async waitForFunctionExpressionImpl(progress: Progress, expression: string, isFunction: boolean | undefined, arg: any, options: { pollingInterval?: number }, world: types.World = 'main'): Promise> { if (typeof options.pollingInterval === 'number') assert(options.pollingInterval > 0, 'Cannot poll with non-positive interval: ' + options.pollingInterval); expression = js.normalizeEvaluationExpression(expression, isFunction); - return controller.run(async progress => { - return this.retryWithProgressAndTimeouts(progress, [100], async () => { - const context = world === 'main' ? await this._mainContext() : await this._utilityContext(); - const injectedScript = await context.injectedScript(); - const handle = await injectedScript.evaluateHandle((injected, { expression, isFunction, polling, arg }) => { - const predicate = (): R => { - // NOTE: make sure to use `globalThis.eval` instead of `self.eval` due to a bug with sandbox isolation - // in firefox. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1814898 - let result = globalThis.eval(expression); - if (isFunction === true) { + return this.retryWithProgressAndTimeouts(progress, [100], async () => { + const context = world === 'main' ? await progress.race(this._mainContext()) : await progress.race(this._utilityContext()); + const injectedScript = await progress.race(context.injectedScript()); + const handle = await progress.race(injectedScript.evaluateHandle((injected, { expression, isFunction, polling, arg }) => { + const predicate = (): R => { + // NOTE: make sure to use `globalThis.eval` instead of `self.eval` due to a bug with sandbox isolation + // in firefox. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1814898 + let result = globalThis.eval(expression); + if (isFunction === true) { + result = result(arg); + } else if (isFunction === false) { + result = result; + } else { + // auto detect. + if (typeof result === 'function') result = result(arg); - } else if (isFunction === false) { - result = result; - } else { - // auto detect. - if (typeof result === 'function') - result = result(arg); - } - return result; - }; - - let fulfill: (result: R) => void; - let reject: (error: Error) => void; - let aborted = false; - const result = new Promise((f, r) => { fulfill = f; reject = r; }); - - const next = () => { - if (aborted) + } + return result; + }; + + let fulfill: (result: R) => void; + let reject: (error: Error) => void; + let aborted = false; + const result = new Promise((f, r) => { fulfill = f; reject = r; }); + + const next = () => { + if (aborted) + return; + try { + const success = predicate(); + if (success) { + fulfill(success); return; - try { - const success = predicate(); - if (success) { - fulfill(success); - return; - } - if (typeof polling !== 'number') - injected.utils.builtins.requestAnimationFrame(next); - else - injected.utils.builtins.setTimeout(next, polling); - } catch (e) { - reject(e); } - }; - - next(); - return { result, abort: () => aborted = true }; - }, { expression, isFunction, polling: options.pollingInterval, arg }); - progress.cleanupWhenAborted(() => handle.evaluate(h => h.abort()).catch(() => {})); - return handle.evaluateHandle(h => h.result); - }); - }, options.timeout); + if (typeof polling !== 'number') + injected.utils.builtins.requestAnimationFrame(next); + else + injected.utils.builtins.setTimeout(next, polling); + } catch (e) { + reject(e); + } + }; + + next(); + return { result, abort: () => aborted = true }; + }, { expression, isFunction, polling: options.pollingInterval, arg })); + progress.cleanupWhenAborted(() => handle.evaluate(h => h.abort()).finally(() => handle.dispose())); + const result = await progress.race(handle.evaluateHandle(h => h.result)); + handle.dispose(); + return result; + }); } async waitForFunctionValueInUtility(progress: Progress, pageFunction: js.Func1) { @@ -1550,7 +1554,7 @@ export class Frame extends SdkObject { return result; return JSON.stringify(result); }`; - const handle = await this._waitForFunctionExpression(serverSideCallMetadata(), expression, true, undefined, { timeout: progress.timeUntilDeadline() }, 'utility'); + const handle = await this.waitForFunctionExpressionImpl(progress, expression, true, undefined, {}, 'utility'); return JSON.parse(handle.rawValue()) as R; } @@ -1559,18 +1563,18 @@ export class Frame extends SdkObject { return context.evaluate(() => document.title); } - async rafrafTimeout(timeout: number): Promise { + async rafrafTimeout(progress: Progress, timeout: number): Promise { if (timeout === 0) return; - const context = await this._utilityContext(); + const context = await progress.race(this._utilityContext()); await Promise.all([ // wait for double raf - context.evaluate(() => new Promise(x => { + progress.race(context.evaluate(() => new Promise(x => { requestAnimationFrame(() => { requestAnimationFrame(x); }); - })), - new Promise(fulfill => setTimeout(fulfill, timeout)), + }))), + progress.wait(timeout), ]); } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 7e56b07abccb5..162a2b6d91dda 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -595,12 +595,12 @@ export class Page extends SdkObject { return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options || {}); } : async (progress: Progress, timeout: number) => { await this.performActionPreChecks(progress); - await this.mainFrame().rafrafTimeout(timeout); + await this.mainFrame().rafrafTimeout(progress, timeout); return await this.screenshotter.screenshotPage(progress, options || {}); }; const comparator = getComparator('image/png'); - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); if (!options.expected && options.isNot) return { errorMessage: '"not" matcher requires expected result' }; try { @@ -636,7 +636,6 @@ export class Page extends SdkObject { progress.log(` generating new stable screenshot expectation`); let isFirstIteration = true; while (true) { - progress.throwIfAborted(); if (this.isClosed()) throw new Error('The page has closed'); const screenshotTimeout = pollIntervals.shift() ?? 1000; @@ -644,6 +643,8 @@ export class Page extends SdkObject { progress.log(`waiting ${screenshotTimeout}ms before taking screenshot`); previous = actual; actual = await rafrafScreenshot(progress, screenshotTimeout).catch(e => { + if (isAbortError(e)) + throw e; progress.log(`failed to take screenshot - ` + e.message); return undefined; }); diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 30e8c9ae1f8da..306857ee2836e 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -206,7 +206,6 @@ export class Screenshotter { progress.log('taking page screenshot'); const viewportSize = await this._originalViewportSize(progress); await this._preparePageForScreenshot(progress, this._page.mainFrame(), options.style, options.caret !== 'initial', options.animations === 'disabled'); - progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. if (options.fullPage) { const fullPageSize = await this._fullPageSize(progress); @@ -215,14 +214,12 @@ export class Screenshotter { if (options.clip) documentRect = trimClipToSize(options.clip, documentRect); const buffer = await this._screenshot(progress, format, documentRect, undefined, fitsViewport, options); - progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. await this._restorePageAfterScreenshot(); return buffer; } const viewportRect = options.clip ? trimClipToSize(options.clip, viewportSize) : { x: 0, y: 0, ...viewportSize }; const buffer = await this._screenshot(progress, format, undefined, viewportRect, true, options); - progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. await this._restorePageAfterScreenshot(); return buffer; }); @@ -235,24 +232,19 @@ export class Screenshotter { const viewportSize = await this._originalViewportSize(progress); await this._preparePageForScreenshot(progress, handle._frame, options.style, options.caret !== 'initial', options.animations === 'disabled'); - progress.throwIfAborted(); // Do not do extra work. - await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */); - progress.throwIfAborted(); // Do not do extra work. - const boundingBox = await handle.boundingBox(); + const boundingBox = await progress.race(handle.boundingBox()); assert(boundingBox, 'Node is either not visible or not an HTMLElement'); assert(boundingBox.width !== 0, 'Node has 0 width.'); assert(boundingBox.height !== 0, 'Node has 0 height.'); const fitsViewport = boundingBox.width <= viewportSize.width && boundingBox.height <= viewportSize.height; - progress.throwIfAborted(); // Avoid extra work. const scrollOffset = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({ x: window.scrollX, y: window.scrollY })); const documentRect = { ...boundingBox }; documentRect.x += scrollOffset.x; documentRect.y += scrollOffset.y; const buffer = await this._screenshot(progress, format, helper.enclosingIntRect(documentRect), undefined, fitsViewport, options); - progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. await this._restorePageAfterScreenshot(); return buffer; }); @@ -262,13 +254,13 @@ export class Screenshotter { if (disableAnimations) progress.log(' disabled all CSS animations'); const syncAnimations = this._page.delegate.shouldToggleStyleSheetToSyncAnimations(); - await this._page.safeNonStallingEvaluateInAllFrames('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${hideCaret}, ${disableAnimations}, ${syncAnimations})`, 'utility'); + progress.cleanupWhenAborted(() => this._restorePageAfterScreenshot()); + await progress.race(this._page.safeNonStallingEvaluateInAllFrames('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${hideCaret}, ${disableAnimations}, ${syncAnimations})`, 'utility')); if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) { progress.log('waiting for fonts to load...'); - await frame.nonStallingEvaluateInExistingContext('document.fonts.ready', 'utility').catch(() => {}); + await progress.race(frame.nonStallingEvaluateInExistingContext('document.fonts.ready', 'utility').catch(() => {})); progress.log('fonts loaded'); } - progress.cleanupWhenAborted(() => this._restorePageAfterScreenshot()); } async _restorePageAfterScreenshot() { @@ -276,6 +268,9 @@ export class Screenshotter { } async _maskElements(progress: Progress, options: ScreenshotOptions): Promise<() => Promise> { + if (!options.mask || !options.mask.length) + return () => Promise.resolve(); + const framesToParsedSelectors: MultiMap = new MultiMap(); const cleanup = async () => { @@ -283,50 +278,38 @@ export class Screenshotter { await frame.hideHighlight(); })); }; + progress.cleanupWhenAborted(cleanup); - if (!options.mask || !options.mask.length) - return cleanup; - - await Promise.all((options.mask || []).map(async ({ frame, selector }) => { + await progress.race(Promise.all((options.mask || []).map(async ({ frame, selector }) => { const pair = await frame.selectors.resolveFrameForSelector(selector); if (pair) framesToParsedSelectors.set(pair.frame, pair.info.parsed); - })); - progress.throwIfAborted(); // Avoid extra work. + }))); - await Promise.all([...framesToParsedSelectors.keys()].map(async frame => { + await progress.race(Promise.all([...framesToParsedSelectors.keys()].map(async frame => { await frame.maskSelectors(framesToParsedSelectors.get(frame), options.maskColor || '#F0F'); - })); - progress.cleanupWhenAborted(cleanup); + }))); return cleanup; } private async _screenshot(progress: Progress, format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, fitsViewport: boolean, options: ScreenshotOptions): Promise { if ((options as any).__testHookBeforeScreenshot) - await (options as any).__testHookBeforeScreenshot(); - progress.throwIfAborted(); // Screenshotting is expensive - avoid extra work. + await progress.race((options as any).__testHookBeforeScreenshot()); const shouldSetDefaultBackground = options.omitBackground && format === 'png'; if (shouldSetDefaultBackground) { - await this._page.delegate.setBackgroundColor({ r: 0, g: 0, b: 0, a: 0 }); progress.cleanupWhenAborted(() => this._page.delegate.setBackgroundColor()); + await progress.race(this._page.delegate.setBackgroundColor({ r: 0, g: 0, b: 0, a: 0 })); } - progress.throwIfAborted(); // Avoid extra work. const cleanupHighlight = await this._maskElements(progress, options); - progress.throwIfAborted(); // Avoid extra work. - const quality = format === 'jpeg' ? options.quality ?? 80 : undefined; - const buffer = await this._page.delegate.takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, options.scale || 'device'); - progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. - + const buffer = await progress.race(this._page.delegate.takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, options.scale || 'device')); await cleanupHighlight(); - progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. if (shouldSetDefaultBackground) - await this._page.delegate.setBackgroundColor(); - progress.throwIfAborted(); // Avoid side effects. + await progress.race(this._page.delegate.setBackgroundColor()); if ((options as any).__testHookAfterScreenshot) - await (options as any).__testHookAfterScreenshot(); + await progress.race((options as any).__testHookAfterScreenshot()); return buffer; } } From 777d1e54b6f1389902ed25ead1c899fb4e1a6da7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 20 Jun 2025 10:10:54 +0200 Subject: [PATCH 080/222] chore: use different babel import in tsxTransform (#36370) --- packages/playwright-ct-core/src/tsxTransform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-ct-core/src/tsxTransform.ts b/packages/playwright-ct-core/src/tsxTransform.ts index f2fad4efa80bd..fbf3d0cc182cb 100644 --- a/packages/playwright-ct-core/src/tsxTransform.ts +++ b/packages/playwright-ct-core/src/tsxTransform.ts @@ -19,7 +19,7 @@ import path from 'path'; import { declare, traverse, types } from 'playwright/lib/transform/babelBundle'; import { setTransformData } from 'playwright/lib/transform/transform'; -import type { BabelAPI, PluginObj, T } from 'playwright/src/transform/babelBundle'; +import type { BabelAPI, PluginObj, T } from 'playwright/lib/transform/babelBundle'; const t: typeof T = types; let jsxComponentNames: Set; From 173b455941b6f0fd9320f53127f975d0cff63a90 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 20 Jun 2025 10:22:19 +0200 Subject: [PATCH 081/222] fix(html-reporter): show filtered stats when filtering for labels/annots (#36368) --- packages/html-reporter/src/filter.ts | 5 ++++- tests/playwright-test/reporter-html.spec.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/html-reporter/src/filter.ts b/packages/html-reporter/src/filter.ts index 135336e869907..04034c8ceb3fc 100644 --- a/packages/html-reporter/src/filter.ts +++ b/packages/html-reporter/src/filter.ts @@ -29,7 +29,10 @@ export class Filter { annotations: FilterToken[] = []; empty(): boolean { - return this.project.length + this.status.length + this.text.length === 0; + return ( + this.project.length + this.status.length + this.text.length + + this.labels.length + this.annotations.length + ) === 0; } static parse(expression: string): Filter { diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 83465d8d06bbf..4e9547e45b12b 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -1933,6 +1933,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { const names = ['one foo', 'two foo', 'three bar', 'four bar', 'five baz']; for (const name of names) { test('b-' + name, async ({}) => { + test.info().annotations.push({ type: 'issue', description: 'test issue' }); expect(name).not.toContain('one'); await new Promise(f => setTimeout(f, 1100)); }); @@ -1988,6 +1989,9 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('3'); await expect(page.locator('.subnav-item:has-text("Flaky") .counter')).toHaveText('0'); await expect(page.locator('.subnav-item:has-text("Skipped") .counter')).toHaveText('0'); + + await searchInput.fill('annot:issue'); + await expect(page.getByTestId('filtered-tests-count')).toContainText(`Filtered: 5`); }); test('labels should be applied together with status filter', async ({ runInlineTest, showReport, page }) => { From 1357f0ab8300a27b756e2cb1c788ce3a5ad76760 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 10:00:21 +0100 Subject: [PATCH 082/222] chore: simplify bidi browsers handling (#36363) --- .../playwright-core/src/browserServerImpl.ts | 4 +-- .../playwright-core/src/client/playwright.ts | 4 +-- .../playwright-core/src/inProcessFactory.ts | 4 +-- .../playwright-core/src/protocol/validator.ts | 4 +-- .../src/remote/playwrightConnection.ts | 9 +----- .../src/server/bidi/bidiChromium.ts | 2 +- .../src/server/bidi/bidiFirefox.ts | 6 +++- .../dispatchers/playwrightDispatcher.ts | 17 +++------- .../playwright-core/src/server/playwright.ts | 8 ++--- .../src/server/registry/index.ts | 31 ++++++------------- packages/protocol/src/channels.d.ts | 4 +-- packages/protocol/src/protocol.yml | 4 +-- tests/bidi/playwright.config.ts | 2 +- tests/config/remoteServer.ts | 6 ---- tests/library/browsertype-connect.spec.ts | 4 +-- tests/library/har.spec.ts | 8 ++--- 16 files changed, 41 insertions(+), 76 deletions(-) diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index ee20e3a102d01..9a8b295202e40 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -32,9 +32,9 @@ import type { WebSocketEventEmitter } from './utilsBundle'; import type { Browser } from './server/browser'; export class BrowserServerLauncherImpl implements BrowserServerLauncher { - private _browserName: 'chromium' | 'firefox' | 'webkit' | 'bidiFirefox' | 'bidiChromium'; + private _browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium'; - constructor(browserName: 'chromium' | 'firefox' | 'webkit' | 'bidiFirefox' | 'bidiChromium') { + constructor(browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium') { this._browserName = browserName; } diff --git a/packages/playwright-core/src/client/playwright.ts b/packages/playwright-core/src/client/playwright.ts index fac9a671ed172..48c6349080f15 100644 --- a/packages/playwright-core/src/client/playwright.ts +++ b/packages/playwright-core/src/client/playwright.ts @@ -58,9 +58,9 @@ export class Playwright extends ChannelOwner { this._android._playwright = this; this._electron = Electron.from(initializer.electron); this._electron._playwright = this; - this._bidiChromium = BrowserType.from(initializer.bidiChromium); + this._bidiChromium = BrowserType.from(initializer._bidiChromium); this._bidiChromium._playwright = this; - this._bidiFirefox = BrowserType.from(initializer.bidiFirefox); + this._bidiFirefox = BrowserType.from(initializer._bidiFirefox); this._bidiFirefox._playwright = this; this.devices = this._connection.localUtils()?.devices ?? {}; this.selectors = new Selectors(this._connection._platform); diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 0bb1d89d4b7b6..0dac70918cb08 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -42,8 +42,8 @@ export function createInProcessPlaywright(): PlaywrightAPI { playwrightAPI.firefox._serverLauncher = new BrowserServerLauncherImpl('firefox'); playwrightAPI.webkit._serverLauncher = new BrowserServerLauncherImpl('webkit'); playwrightAPI._android._serverLauncher = new AndroidServerLauncherImpl(); - playwrightAPI._bidiChromium._serverLauncher = new BrowserServerLauncherImpl('bidiChromium'); - playwrightAPI._bidiFirefox._serverLauncher = new BrowserServerLauncherImpl('bidiFirefox'); + playwrightAPI._bidiChromium._serverLauncher = new BrowserServerLauncherImpl('_bidiChromium'); + playwrightAPI._bidiFirefox._serverLauncher = new BrowserServerLauncherImpl('_bidiFirefox'); // Switch to async dispatch after we got Playwright object. dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message)); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 7e9073a25166d..2475526a43eb5 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -376,8 +376,8 @@ scheme.PlaywrightInitializer = tObject({ chromium: tChannel(['BrowserType']), firefox: tChannel(['BrowserType']), webkit: tChannel(['BrowserType']), - bidiChromium: tChannel(['BrowserType']), - bidiFirefox: tChannel(['BrowserType']), + _bidiChromium: tChannel(['BrowserType']), + _bidiFirefox: tChannel(['BrowserType']), android: tChannel(['Android']), electron: tChannel(['Electron']), utils: tOptional(tChannel(['LocalUtils'])), diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index f96eded9149f7..1a3dcb2a32327 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -118,14 +118,7 @@ export class PlaywrightConnection { private async _initLaunchBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`); const ownedSocksProxy = await this._createOwnedSocksProxy(); - let browserName = this._options.browserName; - if ('bidi' === browserName) { - if (this._options.launchOptions?.channel?.toLocaleLowerCase().includes('firefox')) - browserName = 'bidiFirefox'; - else - browserName = 'bidiChromium'; - } - const browser = await this._playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); + const browser = await this._playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); browser.options.sdkLanguage = options.sdkLanguage; this._cleanups.push(() => browser.close({ reason: 'Connection terminated' })); diff --git a/packages/playwright-core/src/server/bidi/bidiChromium.ts b/packages/playwright-core/src/server/bidi/bidiChromium.ts index 4347d0bcd62a1..38a23580c4e40 100644 --- a/packages/playwright-core/src/server/bidi/bidiChromium.ts +++ b/packages/playwright-core/src/server/bidi/bidiChromium.ts @@ -34,7 +34,7 @@ import type * as types from '../types'; export class BidiChromium extends BrowserType { constructor(parent: SdkObject) { - super(parent, 'bidi'); + super(parent, '_bidiChromium'); } override async connectToTransport(transport: ConnectionTransport, options: BrowserOptions, browserLogsCollector: RecentLogsCollector): Promise { diff --git a/packages/playwright-core/src/server/bidi/bidiFirefox.ts b/packages/playwright-core/src/server/bidi/bidiFirefox.ts index 28c2af7c1db86..f10808a13dd36 100644 --- a/packages/playwright-core/src/server/bidi/bidiFirefox.ts +++ b/packages/playwright-core/src/server/bidi/bidiFirefox.ts @@ -35,7 +35,11 @@ import type { RecentLogsCollector } from '../utils/debugLogger'; export class BidiFirefox extends BrowserType { constructor(parent: SdkObject) { - super(parent, 'bidi'); + super(parent, '_bidiFirefox'); + } + + override executablePath(): string { + return ''; } override async connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index 74ad4b4ab06f8..04bb144d1176a 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -52,15 +52,15 @@ export class PlaywrightDispatcher extends Dispatcher(); @@ -65,8 +65,8 @@ export class Playwright extends SdkObject { } }, null); this.chromium = new Chromium(this); - this.bidiChromium = new BidiChromium(this); - this.bidiFirefox = new BidiFirefox(this); + this._bidiChromium = new BidiChromium(this); + this._bidiFirefox = new BidiFirefox(this); this.firefox = new Firefox(this); this.webkit = new WebKit(this); this.electron = new Electron(this); diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 1a6f5f2f2b177..4a0e7cb989be7 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -384,7 +384,9 @@ const DOWNLOAD_PATHS: Record = { 'win64': 'builds/android/%s/android.zip', }, // TODO(bidi): implement downloads. - 'bidi': { + '_bidiFirefox': { + } as DownloadPaths, + '_bidiChromium': { } as DownloadPaths, }; @@ -480,7 +482,7 @@ function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { }); } -export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi'; +export type BrowserName = 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium'; type InternalTool = 'ffmpeg' | 'winldd' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | 'android'; type BidiChannel = 'moz-firefox' | 'moz-firefox-beta' | 'moz-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium'; type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary'; @@ -717,8 +719,8 @@ export class Registry { })); this._executables.push({ type: 'browser', - name: 'bidi-chromium', - browserName: 'bidi', + name: '_bidiChromium', + browserName: '_bidiChromium', directory: chromium.dir, executablePath: () => chromiumExecutable, executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage), @@ -842,21 +844,6 @@ export class Registry { _dependencyGroup: 'tools', _isHermeticInstallation: true, }); - - this._executables.push({ - type: 'browser', - name: 'bidi', - browserName: 'bidi', - directory: undefined, - executablePath: () => undefined, - executablePathOrDie: () => '', - installType: 'none', - _validateHostRequirements: () => Promise.resolve(), - downloadURLs: [], - _install: () => Promise.resolve(), - _dependencyGroup: 'tools', - _isHermeticInstallation: true, - }); } private _createChromiumChannel(name: ChromiumChannel, lookAt: Record<'linux' | 'darwin' | 'win32', string>, install?: () => Promise): ExecutableImpl { @@ -931,7 +918,7 @@ export class Registry { return { type: 'channel', name, - browserName: 'bidi', + browserName: '_bidiFirefox', directory: undefined, executablePath: (sdkLanguage: string) => executablePath(sdkLanguage, false), executablePathOrDie: (sdkLanguage: string) => executablePath(sdkLanguage, true)!, @@ -947,7 +934,7 @@ export class Registry { const suffix = lookAt[process.platform as 'linux' | 'darwin' | 'win32']; if (!suffix) { if (shouldThrow) - throw new Error(`Firefox distribution '${name}' is not supported on ${process.platform}`); + throw new Error(`Chromium distribution '${name}' is not supported on ${process.platform}`); return undefined; } const prefixes = (process.platform === 'win32' ? [ @@ -974,7 +961,7 @@ export class Registry { return { type: 'channel', name, - browserName: 'bidi', + browserName: '_bidiChromium', directory: undefined, executablePath: (sdkLanguage: string) => executablePath(sdkLanguage, false), executablePathOrDie: (sdkLanguage: string) => executablePath(sdkLanguage, true)!, diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 1afeaeec8b835..3e052e3d5726e 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -624,8 +624,8 @@ export type PlaywrightInitializer = { chromium: BrowserTypeChannel, firefox: BrowserTypeChannel, webkit: BrowserTypeChannel, - bidiChromium: BrowserTypeChannel, - bidiFirefox: BrowserTypeChannel, + _bidiChromium: BrowserTypeChannel, + _bidiFirefox: BrowserTypeChannel, android: AndroidChannel, electron: ElectronChannel, utils?: LocalUtilsChannel, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index c33c381bdfdcc..e9c765d045421 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -791,8 +791,8 @@ Playwright: chromium: BrowserType firefox: BrowserType webkit: BrowserType - bidiChromium: BrowserType - bidiFirefox: BrowserType + _bidiChromium: BrowserType + _bidiFirefox: BrowserType android: Android electron: Electron utils: LocalUtils? diff --git a/tests/bidi/playwright.config.ts b/tests/bidi/playwright.config.ts index d650233e7ceb2..a26b72bd53e8b 100644 --- a/tests/bidi/playwright.config.ts +++ b/tests/bidi/playwright.config.ts @@ -106,7 +106,7 @@ for (const [key, channels] of Object.entries(browserToChannels)) { use: { browserName, headless: !headed, - channel, + channel: channel === 'bidi-chromium' ? undefined : channel, video: 'off', launchOptions: { executablePath, diff --git a/tests/config/remoteServer.ts b/tests/config/remoteServer.ts index 94b252a86c838..4c25934581abc 100644 --- a/tests/config/remoteServer.ts +++ b/tests/config/remoteServer.ts @@ -103,12 +103,6 @@ export class RemoteServer implements PlaywrightServer { launchOptions, ...remoteServerOptions, }; - if ('bidi' === browserType.name()) { - if (channel.toLocaleLowerCase().includes('firefox')) - options.browserTypeName = '_bidiFirefox'; - else - options.browserTypeName = '_bidiChromium'; - } this._process = childProcess({ command: ['node', path.join(__dirname, 'remote-server-impl.js'), JSON.stringify(options)], }); diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index 80653941e60b6..1eb1342e8d5b5 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -258,9 +258,7 @@ for (const kind of ['launchServer', 'run-server'] as const) { }).catch(() => {}) ]); expect(request.headers['user-agent']).toBe(getUserAgent()); - // _bidiFirefox and _bidiChromium are initialized with 'bidi' as browser name. - const bidiAwareBrowserName = browserName.startsWith('_bidi') ? 'bidi' : browserName; - expect(request.headers['x-playwright-browser']).toBe(bidiAwareBrowserName); + expect(request.headers['x-playwright-browser']).toBe(browserName); expect(request.headers['foo']).toBe('bar'); }); diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index 82c1148289ed1..db5b14a5d135e 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -56,9 +56,7 @@ it('should have browser', async ({ browserName, browser, contextFactory, server await page.goto(server.EMPTY_PAGE); const log = await getLog(); - // _bidiFirefox and _bidiChromium are initialized with 'bidi' as browser name. - const harBrowserName = browserName.startsWith('_bidi') ? 'bidi' : browserName; - expect(log.browser!.name.toLowerCase()).toBe(harBrowserName); + expect(log.browser!.name.toLowerCase()).toBe(browserName); expect(log.browser!.version).toBe(browser.version()); }); @@ -915,8 +913,6 @@ it('should not hang on slow chunked response', async ({ browserName, browser, co await page.evaluate(() => (window as any).receivedFirstData); const log = await getLog(); - // _bidiFirefox and _bidiChromium are initialized with 'bidi' as browser name. - const harBrowserName = browserName.startsWith('_bidi') ? 'bidi' : browserName; - expect(log.browser!.name.toLowerCase()).toBe(harBrowserName); + expect(log.browser!.name.toLowerCase()).toBe(browserName); expect(log.browser!.version).toBe(browser.version()); }); From 84c69edbb85422820551403a9e0e76b4dcc26573 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 10:00:34 +0100 Subject: [PATCH 083/222] chore: make launch, newContext and newPage progress "strict" (#36336) --- .../playwright-core/src/server/browser.ts | 27 +++---- .../src/server/browserContext.ts | 71 ++++++++++--------- .../playwright-core/src/server/browserType.ts | 54 ++++++++------ .../src/server/chromium/chromium.ts | 13 ++-- .../src/server/debugController.ts | 2 +- .../dispatchers/browserContextDispatcher.ts | 2 +- .../server/dispatchers/browserDispatcher.ts | 4 +- .../server/dispatchers/electronDispatcher.ts | 5 +- .../dispatchers/localUtilsDispatcher.ts | 3 +- .../src/server/electron/electron.ts | 34 ++++----- .../socksClientCertificatesInterceptor.ts | 13 ++-- .../playwright-core/src/server/transport.ts | 11 +-- .../src/server/utils/processLauncher.ts | 2 +- tests/library/browser.spec.ts | 8 +++ 14 files changed, 136 insertions(+), 113 deletions(-) diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index fc2fcccdf7407..d985e96fa04b0 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -20,6 +20,7 @@ import { Download } from './download'; import { SdkObject } from './instrumentation'; import { Page } from './page'; import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; +import { ProgressController } from './progress'; import type { CallMetadata } from './instrumentation'; import type * as types from './types'; @@ -28,6 +29,7 @@ import type { RecentLogsCollector } from './utils/debugLogger'; import type * as channels from '@protocol/channels'; import type { ChildProcess } from 'child_process'; import type { Language } from '../utils'; +import type { Progress } from './progress'; export interface BrowserProcess { @@ -90,25 +92,26 @@ export abstract class Browser extends SdkObject { return this.options.sdkLanguage || this.attribution.playwright.options.sdkLanguage; } - async newContext(metadata: CallMetadata, options: types.BrowserContextOptions): Promise { + newContextFromMetadata(metadata: CallMetadata, options: types.BrowserContextOptions): Promise { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(progress => this.newContext(progress, options)); + } + + async newContext(progress: Progress, options: types.BrowserContextOptions): Promise { validateBrowserContextOptions(options, this.options); let clientCertificatesProxy: ClientCertificatesProxy | undefined; if (options.clientCertificates?.length) { - clientCertificatesProxy = new ClientCertificatesProxy(options); + clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options), proxy => proxy.close()); options = { ...options }; - options.proxyOverride = await clientCertificatesProxy.listen(); + options.proxyOverride = clientCertificatesProxy.proxySettings(); options.internalIgnoreHTTPSErrors = true; } - let context; - try { - context = await this.doCreateNewContext(options); - } catch (error) { - await clientCertificatesProxy?.close(); - throw error; - } + const context = await progress.raceWithCleanup(this.doCreateNewContext(options), context => context.close({ reason: 'Failed to create context' })); context._clientCertificatesProxy = clientCertificatesProxy; + if ((options as any).__testHookBeforeSetStorageState) + await progress.race((options as any).__testHookBeforeSetStorageState()); if (options.storageState) - await context.setStorageState(metadata, options.storageState); + await context.setStorageState(progress, options.storageState); this.emit(Browser.Events.Context, context); return context; } @@ -118,7 +121,7 @@ export abstract class Browser extends SdkObject { if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) { if (this._contextForReuse) await this._contextForReuse.context.close({ reason: 'Context reused' }); - this._contextForReuse = { context: await this.newContext(metadata, params), hash }; + this._contextForReuse = { context: await this.newContextFromMetadata(metadata, params), hash }; return { context: this._contextForReuse.context, needsReset: false }; } await this._contextForReuse.context.stopPendingOperations('Context recreated'); diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 5bd2577dad98f..0a5b7eac3aab5 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -218,7 +218,7 @@ export abstract class BrowserContext extends SdkObject { // Navigate to about:blank first to ensure no page scripts are running after this point. await page?.mainFrame().gotoImpl(progress, 'about:blank', {}); - await this._resetStorage(); + await this._resetStorage(progress); await this.clock.resetForReuse(); // TODO: following can be optimized to not perform noops. if (this._options.permissions) @@ -379,14 +379,13 @@ export abstract class BrowserContext extends SdkObject { async _loadDefaultContextAsIs(progress: Progress): Promise { if (!this.possiblyUninitializedPages().length) { const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page); - progress.cleanupWhenAborted(() => waitForEvent.dispose); // Race against BrowserContext.close await Promise.race([waitForEvent.promise, this._closePromise]); } const page = this.possiblyUninitializedPages()[0]; if (!page) return; - const pageOrError = await page.waitForInitializedOrError(); + const pageOrError = await progress.race(page.waitForInitializedOrError()); if (pageOrError instanceof Error) throw pageOrError; await page.mainFrame()._waitForLoadState(progress, 'load'); @@ -402,7 +401,7 @@ export abstract class BrowserContext extends SdkObject { // Workaround for: // - chromium fails to change isMobile for existing page; // - webkit fails to change locale for existing page. - await this.newPage(progress.metadata); + await this.newPage(progress, false); await defaultPage.close(); } } @@ -511,9 +510,14 @@ export abstract class BrowserContext extends SdkObject { await this._closePromise; } - async newPage(metadata: CallMetadata): Promise { - const page = await this.doCreateNewPage(metadata.isServerSide); - const pageOrError = await page.waitForInitializedOrError(); + newPageFromMetadata(metadata: CallMetadata): Promise { + const contoller = new ProgressController(metadata, this, 'strict'); + return contoller.run(progress => this.newPage(progress, false)); + } + + async newPage(progress: Progress, isServerSide: boolean): Promise { + const page = await progress.raceWithCleanup(this.doCreateNewPage(isServerSide), page => page.close()); + const pageOrError = await progress.race(page.waitForInitializedOrError()); if (pageOrError instanceof Page) { if (pageOrError.isClosed()) throw new Error('Page has been closed.'); @@ -526,7 +530,12 @@ export abstract class BrowserContext extends SdkObject { this._origins.add(origin); } - async storageState(indexedDB = false): Promise { + storageState(indexedDB = false): Promise { + const controller = new ProgressController(serverSideCallMetadata(), this, 'strict'); + return controller.run(progress => this.storageStateImpl(progress, indexedDB)); + } + + async storageStateImpl(progress: Progress, indexedDB: boolean): Promise { const result: channels.BrowserContextStorageStateResult = { cookies: await this.cookies(), origins: [] @@ -557,15 +566,14 @@ export abstract class BrowserContext extends SdkObject { // If there are still origins to save, create a blank page to iterate over origins. if (originsToSave.size) { - const internalMetadata = serverSideCallMetadata(); - const page = await this.newPage(internalMetadata); - page.addRequestInterceptor(route => { + const page = await this.newPage(progress, true); + await progress.race(page.addRequestInterceptor(route => { route.fulfill({ body: '' }).catch(() => {}); - }, 'prepend'); + }, 'prepend')); for (const origin of originsToSave) { const frame = page.mainFrame(); - await frame.goto(internalMetadata, origin, { timeout: 0 }); - const storage: SerializedStorage = await frame.evaluateExpression(collectScript, { world: 'utility' }); + await frame.gotoImpl(progress, origin, {}); + const storage: SerializedStorage = await progress.race(frame.evaluateExpression(collectScript, { world: 'utility' })); if (storage.localStorage.length || storage.indexedDB?.length) result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB }); } @@ -574,29 +582,27 @@ export abstract class BrowserContext extends SdkObject { return result; } - async _resetStorage() { + async _resetStorage(progress: Progress) { const oldOrigins = this._origins; const newOrigins = new Map(this._options.storageState?.origins?.map(p => [p.origin, p]) || []); if (!oldOrigins.size && !newOrigins.size) return; let page = this.pages()[0]; - const internalMetadata = serverSideCallMetadata(); - page = page || await this.newPage({ - ...internalMetadata, - // Do not mark this page as internal, because we will leave it for later reuse - // as a user-visible page. - isServerSide: false, - }); + // Do not mark this page as internal, because we will leave it for later reuse + // as a user-visible page. + page = page || await this.newPage(progress, false); const interceptor = (route: network.Route) => { route.fulfill({ body: '' }).catch(() => {}); }; - await page.addRequestInterceptor(interceptor, 'prepend'); + + progress.cleanupWhenAborted(() => page.removeRequestInterceptor(interceptor)); + await progress.race(page.addRequestInterceptor(interceptor, 'prepend')); for (const origin of new Set([...oldOrigins, ...newOrigins.keys()])) { const frame = page.mainFrame(); - await frame.goto(internalMetadata, origin, { timeout: 0 }); - await frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin)); + await frame.gotoImpl(progress, origin, {}); + await progress.race(frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin))); } await page.removeRequestInterceptor(interceptor); @@ -615,27 +621,26 @@ export abstract class BrowserContext extends SdkObject { return this._settingStorageState; } - async setStorageState(metadata: CallMetadata, state: NonNullable) { + async setStorageState(progress: Progress, state: NonNullable) { this._settingStorageState = true; try { if (state.cookies) - await this.addCookies(state.cookies); + await progress.race(this.addCookies(state.cookies)); if (state.origins && state.origins.length) { - const internalMetadata = serverSideCallMetadata(); - const page = await this.newPage(internalMetadata); - await page.addRequestInterceptor(route => { + const page = await this.newPage(progress, true); + await progress.race(page.addRequestInterceptor(route => { route.fulfill({ body: '' }).catch(() => {}); - }, 'prepend'); + }, 'prepend')); for (const originState of state.origins) { const frame = page.mainFrame(); - await frame.goto(metadata, originState.origin, { timeout: 0 }); + await frame.gotoImpl(progress, originState.origin, {}); const restoreScript = `(() => { const module = {}; ${rawStorageSource.source} const script = new (module.exports.StorageScript())(${this._browser.options.name === 'firefox'}); return script.restore(${JSON.stringify(originState)}); })()`; - await frame.evaluateExpression(restoreScript, { world: 'utility' }); + await progress.race(frame.evaluateExpression(restoreScript, { world: 'utility' })); } await page.close(); } diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index 5882687e90f1c..9febf7367aa82 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -23,7 +23,7 @@ import { debugMode } from './utils/debug'; import { assert } from '../utils/isomorphic/assert'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { DEFAULT_PLAYWRIGHT_TIMEOUT } from '../utils/isomorphic/time'; -import { existsAsync } from './utils/fileUtils'; +import { existsAsync, removeFolders } from './utils/fileUtils'; import { helper } from './helper'; import { SdkObject } from './instrumentation'; import { PipeTransport } from './pipeTransport'; @@ -69,7 +69,7 @@ export abstract class BrowserType extends SdkObject { async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise { options = this._validateLaunchOptions(options); - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); const browser = await controller.run(progress => { const seleniumHubUrl = (options as any).__testHookSeleniumRemoteURL || process.env.SELENIUM_REMOTE_URL; if (seleniumHubUrl) @@ -81,17 +81,16 @@ export abstract class BrowserType extends SdkObject { async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { timeout: number, cdpPort?: number, internalIgnoreHTTPSErrors?: boolean, socksProxyPort?: number }): Promise { const launchOptions = this._validateLaunchOptions(options); - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); const browser = await controller.run(async progress => { // Note: Any initial TLS requests will fail since we rely on the Page/Frames initialize which sets ignoreHTTPSErrors. let clientCertificatesProxy: ClientCertificatesProxy | undefined; if (options.clientCertificates?.length) { - clientCertificatesProxy = new ClientCertificatesProxy(options); - launchOptions.proxyOverride = await clientCertificatesProxy?.listen(); + clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options), proxy => proxy.close()); + launchOptions.proxyOverride = clientCertificatesProxy.proxySettings(); options = { ...options }; options.internalIgnoreHTTPSErrors = true; } - progress.cleanupWhenAborted(() => clientCertificatesProxy?.close()); const browser = await this._innerLaunchWithRetries(progress, launchOptions, options, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); }); browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy; return browser; @@ -118,7 +117,7 @@ export abstract class BrowserType extends SdkObject { const browserLogsCollector = new RecentLogsCollector(); const { browserProcess, userDataDir, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir); if ((options as any).__testHookBeforeCreateBrowser) - await (options as any).__testHookBeforeCreateBrowser(); + await progress.race((options as any).__testHookBeforeCreateBrowser()); const browserOptions: BrowserOptions = { name: this._name, isChromium: this._name === 'chromium', @@ -140,7 +139,7 @@ export abstract class BrowserType extends SdkObject { if (persistent) validateBrowserContextOptions(persistent, browserOptions); copyTestHooks(options, browserOptions); - const browser = await this.connectToTransport(transport, browserOptions, browserLogsCollector); + const browser = await progress.race(this.connectToTransport(transport, browserOptions, browserLogsCollector)); (browser as any)._userDataDirForTest = userDataDir; // We assume no control when using custom arguments, and do not prepare the default context in that case. if (persistent && !options.ignoreAllDefaultArgs) @@ -148,22 +147,16 @@ export abstract class BrowserType extends SdkObject { return browser; } - private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, browserLogsCollector: RecentLogsCollector, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, artifactsDir: string, userDataDir: string, transport: ConnectionTransport }> { + private async _prepareToLaunch(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string | undefined) { const { ignoreDefaultArgs, ignoreAllDefaultArgs, args = [], executablePath = null, - handleSIGINT = true, - handleSIGTERM = true, - handleSIGHUP = true, } = options; - - const env = options.env ? envArrayToObject(options.env) : process.env; - await this._createArtifactDirs(options); - const tempDirectories = []; + const tempDirectories: string[] = []; const artifactsDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-artifacts-')); tempDirectories.push(artifactsDir); @@ -178,7 +171,7 @@ export abstract class BrowserType extends SdkObject { } await this.prepareUserDataDir(options, userDataDir); - const browserArguments = []; + const browserArguments: string[] = []; if (ignoreAllDefaultArgs) browserArguments.push(...args); else if (ignoreDefaultArgs) @@ -199,15 +192,29 @@ export abstract class BrowserType extends SdkObject { await registry.validateHostRequirementsForExecutablesIfNeeded([registryExecutable], this.attribution.playwright.options.sdkLanguage); } + return { executable, browserArguments, userDataDir, artifactsDir, tempDirectories }; + } + + private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, browserLogsCollector: RecentLogsCollector, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, artifactsDir: string, userDataDir: string, transport: ConnectionTransport }> { + const { + handleSIGINT = true, + handleSIGTERM = true, + handleSIGHUP = true, + } = options; + + const env = options.env ? envArrayToObject(options.env) : process.env; + const prepared = await progress.race(this._prepareToLaunch(options, isPersistent, userDataDir)); + progress.cleanupWhenAborted(() => removeFolders(prepared.tempDirectories)); + // Note: it is important to define these variables before launchProcess, so that we don't get // "Cannot access 'browserServer' before initialization" if something went wrong. let transport: ConnectionTransport | undefined = undefined; let browserProcess: BrowserProcess | undefined = undefined; const exitPromise = new ManualPromise(); const { launchedProcess, gracefullyClose, kill } = await launchProcess({ - command: executable, - args: browserArguments, - env: this.amendEnvironment(env, userDataDir, executable, browserArguments), + command: prepared.executable, + args: prepared.browserArguments, + env: this.amendEnvironment(env, prepared.userDataDir, prepared.executable, prepared.browserArguments), handleSIGINT, handleSIGTERM, handleSIGHUP, @@ -216,7 +223,7 @@ export abstract class BrowserType extends SdkObject { browserLogsCollector.log(message); }, stdio: 'pipe', - tempDirectories, + tempDirectories: prepared.tempDirectories, attemptToGracefullyClose: async () => { if ((options as any).__testHookGracefullyClose) await (options as any).__testHookGracefullyClose(); @@ -253,7 +260,7 @@ export abstract class BrowserType extends SdkObject { kill }; progress.cleanupWhenAborted(() => closeOrKill(progress.timeUntilDeadline())); - const { wsEndpoint } = await Promise.race([ + const { wsEndpoint } = await progress.race([ this.waitForReadyState(options, browserLogsCollector), exitPromise.then(() => ({ wsEndpoint: undefined })), ]); @@ -263,7 +270,8 @@ export abstract class BrowserType extends SdkObject { const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; transport = new PipeTransport(stdio[3], stdio[4]); } - return { browserProcess, artifactsDir, userDataDir, transport }; + progress.cleanupWhenAborted(() => transport.close()); + return { browserProcess, artifactsDir: prepared.artifactsDir, userDataDir: prepared.userDataDir, transport }; } async _createArtifactDirs(options: types.LaunchOptions): Promise { diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index 075e56a4690e9..b35fbb83f0828 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -63,7 +63,7 @@ export class Chromium extends BrowserType { } override async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, headers?: types.HeadersArray, timeout: number }) { - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); return controller.run(async progress => { return await this._connectOverCDPInternal(progress, endpointURL, options); }, options.timeout); @@ -79,12 +79,11 @@ export class Chromium extends BrowserType { else if (headersMap && !Object.keys(headersMap).some(key => key.toLowerCase() === 'user-agent')) headersMap['User-Agent'] = getUserAgent(); - const artifactsDir = await fs.promises.mkdtemp(ARTIFACTS_FOLDER); + const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER)); const wsEndpoint = await urlToWSEndpoint(progress, endpointURL, headersMap); - progress.throwIfAborted(); - const chromeTransport = await WebSocketTransport.connect(progress, wsEndpoint, { headers: headersMap }); + progress.cleanupWhenAborted(() => chromeTransport.close()); const cleanedUp = new ManualPromise(); const doCleanup = async () => { await removeFolders([artifactsDir]); @@ -111,8 +110,7 @@ export class Chromium extends BrowserType { originalLaunchOptions: { timeout: options.timeout }, }; validateBrowserContextOptions(persistent, browserOptions); - progress.throwIfAborted(); - const browser = await CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions); + const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions)); browser._isCollocatedWithServer = false; browser.on(Browser.Events.Disconnected, doCleanup); return browser; @@ -174,7 +172,7 @@ export class Chromium extends BrowserType { } override async _launchWithSeleniumHub(progress: Progress, hubUrl: string, options: types.LaunchOptions): Promise { - await this._createArtifactDirs(options); + await progress.race(this._createArtifactDirs(options)); if (!hubUrl.endsWith('/')) hubUrl = hubUrl + '/'; @@ -390,6 +388,7 @@ async function urlToWSEndpoint(progress: Progress, endpointURL: string, headers: const json = await fetchData({ url: httpURL, headers, + timeout: progress.timeUntilDeadline(), }, async (_, resp) => new Error(`Unexpected status ${resp.statusCode} when connecting to ${httpURL}.\n` + `This does not look like a DevTools server, try connecting via ws://.`) ); diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 9338b59406dd5..92b94211a612f 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -106,7 +106,7 @@ export class DebugController extends SdkObject { if (!pages.length) { const [browser] = this._playwright.allBrowsers(); const { context } = await browser.newContextForReuse({}, internalMetadata); - await context.newPage(internalMetadata); + await context.newPageFromMetadata(internalMetadata); } // Update test id attribute. if (params.testIdAttributeName) { diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 033efda435e5d..9609d62a26730 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -245,7 +245,7 @@ export class BrowserContextDispatcher extends Dispatcher { - return { page: PageDispatcher.from(this, await this._context.newPage(metadata)) }; + return { page: PageDispatcher.from(this, await this._context.newPageFromMetadata(metadata)) }; } async cookies(params: channels.BrowserContextCookiesParams): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts index 186ec73faa03c..448440069a939 100644 --- a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts @@ -60,14 +60,14 @@ export class BrowserDispatcher extends Dispatcher { if (!this._options.isolateContexts) { - const context = await this._object.newContext(metadata, params); + const context = await this._object.newContextFromMetadata(metadata, params); const contextDispatcher = BrowserContextDispatcher.from(this, context); return { context: contextDispatcher }; } if (params.recordVideo) params.recordVideo.dir = this._object.options.artifactsDir; - const context = await this._object.newContext(metadata, params); + const context = await this._object.newContextFromMetadata(metadata, params); this._isolatedContexts.add(context); context.on(BrowserContext.Events.Close, () => this._isolatedContexts.delete(context)); const contextDispatcher = BrowserContextDispatcher.from(this, context); diff --git a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts index 7660526176980..8ca84caadeca0 100644 --- a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts @@ -24,6 +24,7 @@ import type { PageDispatcher } from './pageDispatcher'; import type { ConsoleMessage } from '../console'; import type { Electron } from '../electron/electron'; import type * as channels from '@protocol/channels'; +import type { CallMetadata } from '@protocol/callMetadata'; export class ElectronDispatcher extends Dispatcher implements channels.ElectronChannel { @@ -35,10 +36,10 @@ export class ElectronDispatcher extends Dispatcher { + async launch(params: channels.ElectronLaunchParams, metadata: CallMetadata): Promise { if (this._denyLaunch) throw new Error(`Launching more browsers is not allowed.`); - const electronApplication = await this._object.launch(params); + const electronApplication = await this._object.launch(metadata, params); return { electronApplication: new ElectronApplicationDispatcher(this, electronApplication) }; } } diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index 1a00d8c7f1f82..e614aae6219ef 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -83,7 +83,7 @@ export class LocalUtilsDispatcher extends Dispatcher { - const controller = new ProgressController(metadata, this._object); + const controller = new ProgressController(metadata, this._object, 'strict'); return await controller.run(async progress => { const wsHeaders = { 'User-Agent': getUserAgent(), @@ -146,7 +146,6 @@ async function urlToWSEndpoint(progress: Progress, endpointURL: string): Promise return new Error(`Unexpected status ${response.statusCode} when connecting to ${fetchUrl.toString()}.\n` + `This does not look like a Playwright server, try connecting via ws://.`); }); - progress.throwIfAborted(); const wsUrl = new URL(endpointURL); let wsEndpointPath = JSON.parse(json).wsEndpointPath; diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index b0c154effa1ad..d687571d47973 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -19,7 +19,7 @@ import os from 'os'; import path from 'path'; import * as readline from 'readline'; -import { ManualPromise } from '../../utils'; +import { ManualPromise, removeFolders } from '../../utils'; import { wrapInASCIIBox } from '../utils/ascii'; import { RecentLogsCollector } from '../utils/debugLogger'; import { eventsHelper } from '../utils/eventsHelper'; @@ -30,7 +30,7 @@ import { createHandle, CRExecutionContext } from '../chromium/crExecutionContext import { toConsoleMessageLocation } from '../chromium/crProtocolHelper'; import { ConsoleMessage } from '../console'; import { helper } from '../helper'; -import { SdkObject, serverSideCallMetadata } from '../instrumentation'; +import { CallMetadata, SdkObject } from '../instrumentation'; import * as js from '../javascript'; import { envArrayToObject, launchProcess } from '../utils/processLauncher'; import { ProgressController } from '../progress'; @@ -152,18 +152,15 @@ export class ElectronApplication extends SdkObject { export class Electron extends SdkObject { constructor(playwright: Playwright) { super(playwright, 'electron'); + this.logName = 'browser'; } - async launch(options: channels.ElectronLaunchParams): Promise { - const { - args = [], - } = options; - const controller = new ProgressController(serverSideCallMetadata(), this); - controller.setLogName('browser'); + async launch(metadata: CallMetadata, options: channels.ElectronLaunchParams): Promise { + const controller = new ProgressController(metadata, this, 'strict'); return controller.run(async progress => { let app: ElectronApplication | undefined = undefined; // --remote-debugging-port=0 must be the last playwright's argument, loader.ts relies on it. - let electronArguments = ['--inspect=0', '--remote-debugging-port=0', ...args]; + let electronArguments = ['--inspect=0', '--remote-debugging-port=0', ...(options.args || [])]; if (os.platform() === 'linux') { const runningAsRoot = process.geteuid && process.geteuid() === 0; @@ -171,7 +168,8 @@ export class Electron extends SdkObject { electronArguments.unshift('--no-sandbox'); } - const artifactsDir = await fs.promises.mkdtemp(ARTIFACTS_FOLDER); + const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER)); + progress.cleanupWhenAborted(() => removeFolders([artifactsDir])); const browserLogsCollector = new RecentLogsCollector(); const env = options.env ? envArrayToObject(options.env) : process.env; @@ -233,8 +231,8 @@ export class Electron extends SdkObject { // All waitForLines must be started immediately. // Otherwise the lines might come before we are ready. - const waitForXserverError = new Promise(async (resolve, reject) => { - waitForLine(progress, launchedProcess, /Unable to open X display/).then(() => reject(new Error([ + const waitForXserverError = waitForLine(progress, launchedProcess, /Unable to open X display/).then(() => { + throw new Error([ 'Unable to open X display!', `================================`, 'Most likely this is because there is no X server available.', @@ -242,7 +240,7 @@ export class Electron extends SdkObject { "For example: 'xvfb-run npm run test:e2e'", `================================`, progress.metadata.log - ].join('\n')))).catch(() => {}); + ].join('\n')); }); const nodeMatchPromise = waitForLine(progress, launchedProcess, /^Debugger listening on (ws:\/\/.*)$/); const chromeMatchPromise = waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/); @@ -250,6 +248,7 @@ export class Electron extends SdkObject { const nodeMatch = await nodeMatchPromise; const nodeTransport = await WebSocketTransport.connect(progress, nodeMatch[1]); + progress.cleanupWhenAborted(() => nodeTransport.close()); const nodeConnection = new CRConnection(this, nodeTransport, helper.debugProtocolLogger(), browserLogsCollector); // Immediately release exiting process under debug. @@ -261,6 +260,7 @@ export class Electron extends SdkObject { waitForXserverError, ]) as RegExpMatchArray; const chromeTransport = await WebSocketTransport.connect(progress, chromeMatch[1]); + progress.cleanupWhenAborted(() => chromeTransport.close()); const browserProcess: BrowserProcess = { onclose: undefined, process: launchedProcess, @@ -285,16 +285,16 @@ export class Electron extends SdkObject { originalLaunchOptions: { timeout: options.timeout }, }; validateBrowserContextOptions(contextOptions, browserOptions); - const browser = await CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions); + const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions)); app = new ElectronApplication(this, browser, nodeConnection, launchedProcess); - await app.initialize(); + await progress.race(app.initialize()); return app; }, options.timeout); } } function waitForLine(progress: Progress, process: childProcess.ChildProcess, regex: RegExp): Promise { - return new Promise((resolve, reject) => { + return progress.race(new Promise((resolve, reject) => { const rl = readline.createInterface({ input: process.stderr! }); const failError = new Error('Process failed to launch!'); const listeners = [ @@ -318,5 +318,5 @@ function waitForLine(progress: Progress, process: childProcess.ChildProcess, reg function cleanup() { eventsHelper.removeEventListeners(listeners); } - }); + })); } diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 2cb588378208b..896dd2f091d8b 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -244,7 +244,7 @@ export class ClientCertificatesProxy { alpnCache: ALPNCache; proxyAgentFromOptions: ReturnType; - constructor( + private constructor( contextOptions: Pick ) { verifyClientCertificates(contextOptions.clientCertificates); @@ -294,9 +294,14 @@ export class ClientCertificatesProxy { } } - public async listen() { - const port = await this._socksProxy.listen(0, '127.0.0.1'); - return { server: `socks5://127.0.0.1:${port}` }; + public static async create(contextOptions: Pick) { + const proxy = new ClientCertificatesProxy(contextOptions); + await proxy._socksProxy.listen(0, '127.0.0.1'); + return proxy; + } + + public proxySettings(): types.ProxySettings { + return { server: `socks5://127.0.0.1:${this._socksProxy.port()}` }; } public async close() { diff --git a/packages/playwright-core/src/server/transport.ts b/packages/playwright-core/src/server/transport.ts index e95ec8eaee429..30e87ae0c2eca 100644 --- a/packages/playwright-core/src/server/transport.ts +++ b/packages/playwright-core/src/server/transport.ts @@ -84,12 +84,8 @@ export class WebSocketTransport implements ConnectionTransport { const logUrl = stripQueryParams(url); progress?.log(` ${logUrl}`); const transport = new WebSocketTransport(progress, url, logUrl, { ...options, followRedirects: !!options.followRedirects && hadRedirects }); - let success = false; - progress?.cleanupWhenAborted(async () => { - if (!success) - await transport.closeAndWait().catch(e => null); - }); - const result = await new Promise<{ transport?: WebSocketTransport, redirect?: IncomingMessage }>((fulfill, reject) => { + progress?.cleanupWhenAborted(() => transport.closeAndWait()); + const resultPromise = new Promise<{ transport?: WebSocketTransport, redirect?: IncomingMessage }>((fulfill, reject) => { transport._ws.on('open', async () => { progress?.log(` ${logUrl}`); fulfill({ transport }); @@ -120,6 +116,7 @@ export class WebSocketTransport implements ConnectionTransport { }); }); }); + const result = progress ? await progress.race(resultPromise) : await resultPromise; if (result.redirect) { // Strip authorization headers from the redirected request. @@ -128,8 +125,6 @@ export class WebSocketTransport implements ConnectionTransport { })); return WebSocketTransport._connect(progress, result.redirect.headers.location!, { ...options, headers: newHeaders }, true /* hadRedirects */); } - - success = true; return transport; } diff --git a/packages/playwright-core/src/server/utils/processLauncher.ts b/packages/playwright-core/src/server/utils/processLauncher.ts index 90bc1507fc74a..2f204674ec5db 100644 --- a/packages/playwright-core/src/server/utils/processLauncher.ts +++ b/packages/playwright-core/src/server/utils/processLauncher.ts @@ -164,7 +164,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise { failed(new Error('Failed to launch: ' + error)); }); - return cleanup().then(() => failedPromise).then(e => Promise.reject(e)); + return failedPromise.then(e => Promise.reject(e)); } options.log(` pid=${spawnedProcess.pid}`); diff --git a/tests/library/browser.spec.ts b/tests/library/browser.spec.ts index 17bdc142131d0..19d47f871a4ea 100644 --- a/tests/library/browser.spec.ts +++ b/tests/library/browser.spec.ts @@ -62,3 +62,11 @@ test('should dispatch page.on(close) upon browser.close and reject evaluate', as const error = await promise; expect(error.message).toContain(kTargetClosedErrorMessage); }); + +test('newContext should not leave a context upon failure', async ({ browser, toImpl }) => { + const error = await browser.newContext({ + __testHookBeforeSetStorageState: () => Promise.reject(new Error('Oh my')), + } as any).catch(e => e); + expect(error.message).toContain('Oh my'); + await expect.poll(() => toImpl(browser).contexts().length).toBe(0); +}); From c0da19366092bddc35dae1f5b5f2cd072079604d Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 10:47:36 +0100 Subject: [PATCH 084/222] chore: make various progress instances "strict" (#36349) --- .../src/server/android/android.ts | 62 ++++++++++--------- .../src/server/browserContext.ts | 32 +++++----- .../src/server/chromium/videoRecorder.ts | 3 +- .../server/dispatchers/androidDispatcher.ts | 8 +-- packages/playwright-core/src/server/frames.ts | 22 +++---- packages/playwright-core/src/server/page.ts | 54 ++++++++-------- .../src/server/recorder/recorderApp.ts | 2 +- .../src/server/trace/viewer/traceViewer.ts | 2 +- 8 files changed, 97 insertions(+), 88 deletions(-) diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index b8a4c4772f428..3b7e4cebf34e3 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -32,9 +32,9 @@ import { chromiumSwitches } from '../chromium/chromiumSwitches'; import { CRBrowser } from '../chromium/crBrowser'; import { removeFolders } from '../utils/fileUtils'; import { helper } from '../helper'; -import { SdkObject, serverSideCallMetadata } from '../instrumentation'; +import { CallMetadata, SdkObject } from '../instrumentation'; import { gracefullyCloseSet } from '../utils/processLauncher'; -import { ProgressController } from '../progress'; +import { Progress, ProgressController } from '../progress'; import { registry } from '../registry'; import type { BrowserOptions, BrowserProcess } from '../browser'; @@ -122,6 +122,7 @@ export class AndroidDevice extends SdkObject { this.model = model; this.serial = backend.serial; this._options = options; + this.logName = 'browser'; } static async create(android: Android, backend: DeviceBackend, options: channels.AndroidDevicesOptions): Promise { @@ -258,18 +259,21 @@ export class AndroidDevice extends SdkObject { this.emit(AndroidDevice.Events.Close); } - async launchBrowser(pkg: string = 'com.android.chrome', options: channels.AndroidDeviceLaunchBrowserParams): Promise { - debug('pw:android')('Force-stopping', pkg); - await this._backend.runCommand(`shell:am force-stop ${pkg}`); - const socketName = isUnderTest() ? 'webview_devtools_remote_playwright_test' : ('playwright_' + createGuid() + '_devtools_remote'); - const commandLine = this._defaultArgs(options, socketName).join(' '); - debug('pw:android')('Starting', pkg, commandLine); - // encode commandLine to base64 to avoid issues (bash encoding) with special characters - await this._backend.runCommand(`shell:echo "${Buffer.from(commandLine).toString('base64')}" | base64 -d > /data/local/tmp/chrome-command-line`); - await this._backend.runCommand(`shell:am start -a android.intent.action.VIEW -d about:blank ${pkg}`); - const browserContext = await this._connectToBrowser(socketName, options); - await this._backend.runCommand(`shell:rm /data/local/tmp/chrome-command-line`); - return browserContext; + async launchBrowser(metadata: CallMetadata, pkg: string = 'com.android.chrome', options: channels.AndroidDeviceLaunchBrowserParams): Promise { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(async progress => { + debug('pw:android')('Force-stopping', pkg); + await this._backend.runCommand(`shell:am force-stop ${pkg}`); + const socketName = isUnderTest() ? 'webview_devtools_remote_playwright_test' : ('playwright_' + createGuid() + '_devtools_remote'); + const commandLine = this._defaultArgs(options, socketName).join(' '); + debug('pw:android')('Starting', pkg, commandLine); + // encode commandLine to base64 to avoid issues (bash encoding) with special characters + await progress.race(this._backend.runCommand(`shell:echo "${Buffer.from(commandLine).toString('base64')}" | base64 -d > /data/local/tmp/chrome-command-line`)); + await progress.race(this._backend.runCommand(`shell:am start -a android.intent.action.VIEW -d about:blank ${pkg}`)); + const browserContext = await this._connectToBrowser(progress, socketName, options); + await progress.race(this._backend.runCommand(`shell:rm /data/local/tmp/chrome-command-line`)); + return browserContext; + }); } private _defaultArgs(options: channels.AndroidDeviceLaunchBrowserParams, socketName: string): string[] { @@ -301,25 +305,30 @@ export class AndroidDevice extends SdkObject { return chromeArguments; } - async connectToWebView(socketName: string): Promise { - const webView = this._webViews.get(socketName); - if (!webView) - throw new Error('WebView has been closed'); - return await this._connectToBrowser(socketName); + async connectToWebView(metadata: CallMetadata, socketName: string): Promise { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(async progress => { + const webView = this._webViews.get(socketName); + if (!webView) + throw new Error('WebView has been closed'); + return await this._connectToBrowser(progress, socketName); + }); } - private async _connectToBrowser(socketName: string, options: types.BrowserContextOptions = {}): Promise { - const socket = await this._waitForLocalAbstract(socketName); + private async _connectToBrowser(progress: Progress, socketName: string, options: types.BrowserContextOptions = {}): Promise { + const socket = await progress.race(this._waitForLocalAbstract(socketName)); const androidBrowser = new AndroidBrowser(this, socket); - await androidBrowser._init(); + progress.cleanupWhenAborted(() => androidBrowser.close()); + await progress.race(androidBrowser._init()); this._browserConnections.add(androidBrowser); - const artifactsDir = await fs.promises.mkdtemp(ARTIFACTS_FOLDER); + const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER)); const cleanupArtifactsDir = async () => { const errors = (await removeFolders([artifactsDir])).filter(Boolean); for (let i = 0; i < (errors || []).length; ++i) debug('pw:android')(`exception while removing ${artifactsDir}: ${errors[i]}`); }; + progress.cleanupWhenAborted(cleanupArtifactsDir); gracefullyCloseSet.add(cleanupArtifactsDir); socket.on('close', async () => { gracefullyCloseSet.delete(cleanupArtifactsDir); @@ -341,12 +350,9 @@ export class AndroidDevice extends SdkObject { }; validateBrowserContextOptions(options, browserOptions); - const browser = await CRBrowser.connect(this.attribution.playwright, androidBrowser, browserOptions); - const controller = new ProgressController(serverSideCallMetadata(), this); + const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, androidBrowser, browserOptions)); const defaultContext = browser._defaultContext!; - await controller.run(async progress => { - await defaultContext._loadDefaultContextAsIs(progress); - }); + await defaultContext._loadDefaultContextAsIs(progress); return defaultContext; } diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 0a5b7eac3aab5..bd7e934e7ddc2 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -191,12 +191,12 @@ export abstract class BrowserContext extends SdkObject { } async resetForReuse(metadata: CallMetadata, params: channels.BrowserNewContextForReuseParams | null) { - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); return controller.run(progress => this.resetForReuseImpl(progress, params)); } async resetForReuseImpl(progress: Progress, params: channels.BrowserNewContextForReuseParams | null) { - await this.tracing.resetForReuse(); + await progress.race(this.tracing.resetForReuse()); if (params) { for (const key of paramsThatAllowContextReuse) @@ -219,18 +219,22 @@ export abstract class BrowserContext extends SdkObject { await page?.mainFrame().gotoImpl(progress, 'about:blank', {}); await this._resetStorage(progress); - await this.clock.resetForReuse(); - // TODO: following can be optimized to not perform noops. - if (this._options.permissions) - await this.grantPermissions(this._options.permissions); - else - await this.clearPermissions(); - await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []); - await this.setGeolocation(this._options.geolocation); - await this.setOffline(!!this._options.offline); - await this.setUserAgent(this._options.userAgent); - await this.clearCache(); - await this._resetCookies(); + + const resetOptions = async () => { + await this.clock.resetForReuse(); + // TODO: following can be optimized to not perform noops. + if (this._options.permissions) + await this.grantPermissions(this._options.permissions); + else + await this.clearPermissions(); + await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []); + await this.setGeolocation(this._options.geolocation); + await this.setOffline(!!this._options.offline); + await this.setUserAgent(this._options.userAgent); + await this.clearCache(); + await this._resetCookies(); + }; + await progress.race(resetOptions()); await page?.resetForReuse(progress); } diff --git a/packages/playwright-core/src/server/chromium/videoRecorder.ts b/packages/playwright-core/src/server/chromium/videoRecorder.ts index 160fd354812b6..5b15cf5a20344 100644 --- a/packages/playwright-core/src/server/chromium/videoRecorder.ts +++ b/packages/playwright-core/src/server/chromium/videoRecorder.ts @@ -42,10 +42,11 @@ export class VideoRecorder { if (!options.outputFile.endsWith('.webm')) throw new Error('File must have .webm extension'); - const controller = new ProgressController(serverSideCallMetadata(), page); + const controller = new ProgressController(serverSideCallMetadata(), page, 'strict'); controller.setLogName('browser'); return await controller.run(async progress => { const recorder = new VideoRecorder(page, ffmpegPath, progress); + progress.cleanupWhenAborted(() => recorder.stop()); await recorder._launch(options); return recorder; }); diff --git a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts index ea5d31461c961..6972d50f2d21c 100644 --- a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts @@ -160,10 +160,10 @@ export class AndroidDeviceDispatcher extends Dispatcher { + async launchBrowser(params: channels.AndroidDeviceLaunchBrowserParams, metadata: CallMetadata): Promise { if (this.parentScope()._denyLaunch) throw new Error(`Launching more browsers is not allowed.`); - const context = await this._object.launchBrowser(params.pkg, params); + const context = await this._object.launchBrowser(metadata, params.pkg, params); return { context: BrowserContextDispatcher.from(this, context) }; } @@ -171,10 +171,10 @@ export class AndroidDeviceDispatcher extends Dispatcher { + async connectToWebView(params: channels.AndroidDeviceConnectToWebViewParams, metadata: CallMetadata): Promise { if (this.parentScope()._denyLaunch) throw new Error(`Launching more browsers is not allowed.`); - return { context: BrowserContextDispatcher.from(this, await this._object.connectToWebView(params.socketName)) }; + return { context: BrowserContextDispatcher.from(this, await this._object.connectToWebView(metadata, params.socketName)) }; } } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 5ce23ffb5cbe0..a0bc51b7f37c4 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1370,9 +1370,9 @@ export class Frame extends SdkObject { } async ariaSnapshot(metadata: CallMetadata, selector: string, options: { forAI?: boolean } & types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this); + const controller = new ProgressController(metadata, this, 'strict'); return controller.run(async progress => { - return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => handle.ariaSnapshot(options)); + return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => progress.race(handle.ariaSnapshot(options))); }, options.timeout); } @@ -1391,7 +1391,7 @@ export class Frame extends SdkObject { const start = timeout > 0 ? monotonicTime() : 0; // Step 1: perform locator handlers checkpoint with a specified timeout. - await (new ProgressController(metadata, this)).run(async progress => { + await (new ProgressController(metadata, this, 'strict')).run(async progress => { progress.log(`${renderTitleForCall(metadata)}${timeout ? ` with timeout ${timeout}ms` : ''}`); if (selector) progress.log(`waiting for ${this._asLocator(selector)}`); @@ -1402,7 +1402,7 @@ export class Frame extends SdkObject { // Supports the case of `expect(locator).toBeVisible({ timeout: 1 })` // that should succeed when the locator is already visible. try { - const resultOneShot = await (new ProgressController(metadata, this)).run(async progress => { + const resultOneShot = await (new ProgressController(metadata, this, 'strict')).run(async progress => { return await this._expectInternal(progress, selector, options, lastIntermediateResult); }); if (resultOneShot.matches !== options.isNot) @@ -1420,7 +1420,7 @@ export class Frame extends SdkObject { return { matches: options.isNot, log: compressCallLog(metadata.log), timedOut: true, received: lastIntermediateResult.received }; // Step 3: auto-retry expect with increasing timeouts. Bounded by the total remaining time. - return await (new ProgressController(metadata, this)).run(async progress => { + return await (new ProgressController(metadata, this, 'strict')).run(async progress => { return await this.retryWithProgressAndTimeouts(progress, [100, 250, 500, 1000], async continuePolling => { await this._page.performActionPreChecks(progress); const { matches, received } = await this._expectInternal(progress, selector, options, lastIntermediateResult); @@ -1448,16 +1448,14 @@ export class Frame extends SdkObject { } private async _expectInternal(progress: Progress, selector: string | undefined, options: FrameExpectParams, lastIntermediateResult: { received?: any, isSet: boolean }) { - const selectorInFrame = selector ? await this.selectors.resolveFrameForSelector(selector, { strict: true }) : undefined; - progress.throwIfAborted(); + const selectorInFrame = selector ? await progress.race(this.selectors.resolveFrameForSelector(selector, { strict: true })) : undefined; const { frame, info } = selectorInFrame || { frame: this, info: undefined }; const world = options.expression === 'to.have.property' ? 'main' : (info?.world ?? 'utility'); - const context = await frame._context(world); - const injected = await context.injectedScript(); - progress.throwIfAborted(); + const context = await progress.race(frame._context(world)); + const injected = await progress.race(context.injectedScript()); - const { log, matches, received, missingReceived } = await injected.evaluate(async (injected, { info, options, callId }) => { + const { log, matches, received, missingReceived } = await progress.race(injected.evaluate(async (injected, { info, options, callId }) => { const elements = info ? injected.querySelectorAll(info.parsed, document) : []; if (callId) injected.markTargetElements(new Set(elements), callId); @@ -1470,7 +1468,7 @@ export class Frame extends SdkObject { else if (elements.length) log = ` locator resolved to ${injected.previewNode(elements[0])}`; return { log, ...await injected.expect(elements[0], options, elements) }; - }, { info, options, callId: progress.metadata.id }); + }, { info, options, callId: progress.metadata.id })); if (log) progress.log(log); diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 162a2b6d91dda..9a9bf878a1ab1 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -812,10 +812,13 @@ export class Page extends SdkObject { this._isServerSideOnly = true; } - async snapshotForAI(metadata: CallMetadata): Promise { - this.lastSnapshotFrameIds = []; - const snapshot = await snapshotFrameForAI(metadata, this.mainFrame(), 0, this.lastSnapshotFrameIds); - return snapshot.join('\n'); + snapshotForAI(metadata: CallMetadata): Promise { + const controller = new ProgressController(metadata, this, 'strict'); + return controller.run(async progress => { + this.lastSnapshotFrameIds = []; + const snapshot = await snapshotFrameForAI(progress, this.mainFrame(), 0, this.lastSnapshotFrameIds); + return snapshot.join('\n'); + }); } } @@ -991,29 +994,26 @@ class FrameThrottler { } } -async function snapshotFrameForAI(metadata: CallMetadata, frame: frames.Frame, frameOrdinal: number, frameIds: string[]): Promise { +async function snapshotFrameForAI(progress: Progress, frame: frames.Frame, frameOrdinal: number, frameIds: string[]): Promise { // Only await the topmost navigations, inner frames will be empty when racing. - const controller = new ProgressController(metadata, frame); - const snapshot = await controller.run(progress => { - return frame.retryWithProgressAndTimeouts(progress, [1000, 2000, 4000, 8000], async continuePolling => { - try { - const context = await frame._utilityContext(); - const injectedScript = await context.injectedScript(); - const snapshotOrRetry = await injectedScript.evaluate((injected, refPrefix) => { - const node = injected.document.body; - if (!node) - return true; - return injected.ariaSnapshot(node, { forAI: true, refPrefix }); - }, frameOrdinal ? 'f' + frameOrdinal : ''); - if (snapshotOrRetry === true) - return continuePolling; - return snapshotOrRetry; - } catch (e) { - if (isAbortError(e) || isSessionClosedError(e) || js.isJavaScriptErrorInEvaluate(e)) - throw e; + const snapshot = await frame.retryWithProgressAndTimeouts(progress, [1000, 2000, 4000, 8000], async continuePolling => { + try { + const context = await progress.race(frame._utilityContext()); + const injectedScript = await progress.race(context.injectedScript()); + const snapshotOrRetry = await progress.race(injectedScript.evaluate((injected, refPrefix) => { + const node = injected.document.body; + if (!node) + return true; + return injected.ariaSnapshot(node, { forAI: true, refPrefix }); + }, frameOrdinal ? 'f' + frameOrdinal : '')); + if (snapshotOrRetry === true) return continuePolling; - } - }); + return snapshotOrRetry; + } catch (e) { + if (isAbortError(e) || isSessionClosedError(e) || js.isJavaScriptErrorInEvaluate(e)) + throw e; + return continuePolling; + } }); const lines = snapshot.split('\n'); @@ -1029,7 +1029,7 @@ async function snapshotFrameForAI(metadata: CallMetadata, frame: frames.Frame, f const ref = match[2]; const frameSelector = `aria-ref=${ref} >> internal:control=enter-frame`; const frameBodySelector = `${frameSelector} >> body`; - const child = await frame.selectors.resolveFrameForSelector(frameBodySelector, { strict: true }); + const child = await progress.race(frame.selectors.resolveFrameForSelector(frameBodySelector, { strict: true })); if (!child) { result.push(line); continue; @@ -1037,7 +1037,7 @@ async function snapshotFrameForAI(metadata: CallMetadata, frame: frames.Frame, f const frameOrdinal = frameIds.length + 1; frameIds.push(child.frame._id); try { - const childSnapshot = await snapshotFrameForAI(metadata, child.frame, frameOrdinal, frameIds); + const childSnapshot = await snapshotFrameForAI(progress, child.frame, frameOrdinal, frameIds); result.push(line + ':', ...childSnapshot.map(l => leadingSpace + ' ' + l)); } catch { result.push(line); diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 5f69e225f2cfc..f9aea392e86da 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -121,7 +121,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { timeout: 0, } }); - const controller = new ProgressController(serverSideCallMetadata(), context._browser); + const controller = new ProgressController(serverSideCallMetadata(), context._browser, 'strict'); await controller.run(async progress => { await context._browser._defaultContext!._loadDefaultContextAsIs(progress); }); diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index 68a5fdd5c76b7..c202d65e6fc38 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -180,7 +180,7 @@ export async function openTraceViewerApp(url: string, browserName: string, optio }, }); - const controller = new ProgressController(serverSideCallMetadata(), context._browser); + const controller = new ProgressController(serverSideCallMetadata(), context._browser, 'strict'); await controller.run(async progress => { await context._browser._defaultContext!._loadDefaultContextAsIs(progress); }); From d3970a22d1df8ff2c96e0cb101d88738f3d63d70 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 20 Jun 2025 13:04:41 +0200 Subject: [PATCH 085/222] chore: smaller codex fixes (#36374) --- packages/playwright/src/matchers/toMatchSnapshot.ts | 2 +- packages/playwright/src/runner/testServer.ts | 2 +- packages/playwright/src/util.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index 98fc72a6f2f85..b2ef3c332326e 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -135,7 +135,7 @@ class SnapshotHelper { this.locator = locator; this.updateSnapshots = testInfo.config.updateSnapshots; - this.mimeType = mime.getType(path.basename(this.expectedPath)) ?? 'application/octet-string'; + this.mimeType = mime.getType(path.basename(this.expectedPath)) ?? 'application/octet-stream'; this.comparator = getComparator(this.mimeType); this.testInfo = testInfo; diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 61be3b2929960..4a182fbdc72ee 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -142,7 +142,7 @@ export class TestServerDispatcher implements TestServerInterface { process.stdout.columns = params.cols; process.stdout.rows = params.rows; process.stderr.columns = params.cols; - process.stderr.columns = params.rows; + process.stderr.rows = params.rows; } async checkBrowsers(): Promise<{ hasBrowsers: boolean; }> { diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index 00a839cb19eb8..a7d120770ff44 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -147,7 +147,7 @@ export function createTitleMatcher(patterns: RegExp | RegExp[]): Matcher { }; } -export function mergeObjects(a: A | undefined | void, b: B | undefined | void, c: B | undefined | void): A & B & C { +export function mergeObjects(a: A | undefined | void, b: B | undefined | void, c: C | undefined | void): A & B & C { const result = { ...a } as any; for (const x of [b, c].filter(Boolean)) { for (const [name, value] of Object.entries(x as any)) { From 71088f6e9dce156ceecb3e381368942f9b6b0a8e Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 20 Jun 2025 13:40:11 +0200 Subject: [PATCH 086/222] chore: refactor browser creation from PlaywrightConnection into PlaywrightServer (#36369) Signed-off-by: Simon Knott Co-authored-by: Dmitry Gozman --- .../src/remote/playwrightConnection.ts | 249 +++--------------- .../src/remote/playwrightServer.ts | 227 ++++++++++++++-- .../dispatchers/playwrightDispatcher.ts | 2 +- 3 files changed, 233 insertions(+), 245 deletions(-) diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 1a3dcb2a32327..d92caeee78835 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -14,63 +14,35 @@ * limitations under the License. */ -import { SocksProxy } from '../server/utils/socksProxy'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher } from '../server'; import { AndroidDevice } from '../server/android/android'; import { Browser } from '../server/browser'; import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher'; -import { serverSideCallMetadata } from '../server/instrumentation'; -import { assert } from '../utils/isomorphic/assert'; -import { isUnderTest } from '../server/utils/debug'; import { startProfiling, stopProfiling } from '../server/utils/profiler'; -import { monotonicTime } from '../utils'; +import { monotonicTime, Semaphore } from '../utils'; import { debugLogger } from '../server/utils/debugLogger'; +import { PlaywrightDispatcherOptions } from '../server/dispatchers/playwrightDispatcher'; import type { DispatcherScope, Playwright } from '../server'; -import type { LaunchOptions } from '../server/types'; import type { WebSocket } from '../utilsBundle'; -import type * as channels from '@protocol/channels'; - -export type ClientType = 'controller' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android'; - -type Options = { - allowFSPaths: boolean, - socksProxyPattern: string | undefined, - browserName: string | null, - launchOptions: LaunchOptions, - sharedBrowser?: boolean, -}; - -type PreLaunched = { - browser?: Browser | undefined; - androidDevice?: AndroidDevice | undefined; - socksProxy?: SocksProxy | undefined; -}; export class PlaywrightConnection { private _ws: WebSocket; - private _onClose: () => void; + private _semaphore: Semaphore; private _dispatcherConnection: DispatcherConnection; private _cleanups: (() => Promise)[] = []; private _id: string; private _disconnected = false; - private _playwright: Playwright; - private _preLaunched: PreLaunched; - private _options: Options; private _root: DispatcherScope; private _profileName: string; - constructor(lock: Promise, clientType: ClientType, ws: WebSocket, options: Options, playwright: Playwright, preLaunched: PreLaunched, id: string, onClose: () => void) { + constructor(semaphore: Semaphore, ws: WebSocket, controller: boolean, playwright: Playwright, initialize: () => Promise }>, id: string) { this._ws = ws; - this._playwright = playwright; - this._preLaunched = preLaunched; - this._options = options; - options.launchOptions = filterLaunchOptions(options.launchOptions, options.allowFSPaths); - if (clientType === 'pre-launched-browser-or-android') - assert(preLaunched.browser || preLaunched.androidDevice); - this._onClose = onClose; + this._semaphore = semaphore; this._id = id; - this._profileName = `${new Date().toISOString()}-${clientType}`; + this._profileName = new Date().toISOString(); + + const lock = this._semaphore.acquire(); this._dispatcherConnection = new DispatcherConnection(); this._dispatcherConnection.onmessage = async message => { @@ -98,148 +70,39 @@ export class PlaywrightConnection { ws.on('close', () => this._onDisconnect()); ws.on('error', (error: Error) => this._onDisconnect(error)); - if (clientType === 'controller') { - this._root = this._initDebugControllerMode(); + if (controller) { + debugLogger.log('server', `[${this._id}] engaged reuse controller mode`); + this._root = new DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController); return; } - this._root = new RootDispatcher(this._dispatcherConnection, async (scope, options) => { + this._root = new RootDispatcher(this._dispatcherConnection, async (scope, params) => { await startProfiling(); - if (clientType === 'reuse-browser') - return await this._initReuseBrowsersMode(scope, options); - if (clientType === 'pre-launched-browser-or-android') - return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope, options) : await this._initPreLaunchedAndroidMode(scope); - if (clientType === 'launch-browser') - return await this._initLaunchBrowserMode(scope, options); - throw new Error('Unsupported client type: ' + clientType); - }); - } - - private async _initLaunchBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { - debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`); - const ownedSocksProxy = await this._createOwnedSocksProxy(); - const browser = await this._playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); - browser.options.sdkLanguage = options.sdkLanguage; - - this._cleanups.push(() => browser.close({ reason: 'Connection terminated' })); - browser.on(Browser.Events.Disconnected, () => { - // Underlying browser did close for some reason - force disconnect the client. - this.close({ code: 1001, reason: 'Browser closed' }); - }); - - return new PlaywrightDispatcher(scope, this._playwright, { socksProxy: ownedSocksProxy, preLaunchedBrowser: browser, denyLaunch: true, }); - } - - private async _initPreLaunchedBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { - debugLogger.log('server', `[${this._id}] engaged pre-launched (browser) mode`); - // Note: connected client owns the socks proxy and configures the pattern. - this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern); - - const browser = this._preLaunched.browser!; - browser.options.sdkLanguage = options.sdkLanguage; - browser.on(Browser.Events.Disconnected, () => { - // Underlying browser did close for some reason - force disconnect the client. - this.close({ code: 1001, reason: 'Browser closed' }); - }); - - const playwrightDispatcher = new PlaywrightDispatcher(scope, this._playwright, { - socksProxy: this._preLaunched.socksProxy, - preLaunchedBrowser: browser, - sharedBrowser: this._options.sharedBrowser, - denyLaunch: true, - }); - // In pre-launched mode, keep only the pre-launched browser. - for (const b of this._playwright.allBrowsers()) { - if (b !== browser) - await b.close({ reason: 'Connection terminated' }); - } - this._cleanups.push(() => playwrightDispatcher.cleanup()); - return playwrightDispatcher; - } - - private async _initPreLaunchedAndroidMode(scope: RootDispatcher) { - debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`); - const androidDevice = this._preLaunched.androidDevice!; - androidDevice.on(AndroidDevice.Events.Close, () => { - // Underlying browser did close for some reason - force disconnect the client. - this.close({ code: 1001, reason: 'Android device disconnected' }); - }); - const playwrightDispatcher = new PlaywrightDispatcher(scope, this._playwright, { preLaunchedAndroidDevice: androidDevice, denyLaunch: true }); - this._cleanups.push(() => playwrightDispatcher.cleanup()); - return playwrightDispatcher; - } - - private _initDebugControllerMode(): DebugControllerDispatcher { - debugLogger.log('server', `[${this._id}] engaged reuse controller mode`); - // Always create new instance based on the reused Playwright instance. - return new DebugControllerDispatcher(this._dispatcherConnection, this._playwright.debugController); - } - - private async _initReuseBrowsersMode(scope: RootDispatcher, options: channels.RootInitializeParams) { - // Note: reuse browser mode does not support socks proxy, because - // clients come and go, while the browser stays the same. - - debugLogger.log('server', `[${this._id}] engaged reuse browsers mode for ${this._options.browserName}`); - - const requestedOptions = launchOptionsHash(this._options.launchOptions); - let browser = this._playwright.allBrowsers().find(b => { - if (b.options.name !== this._options.browserName) - return false; - const existingOptions = launchOptionsHash(b.options.originalLaunchOptions); - return existingOptions === requestedOptions; - }); - - // Close remaining browsers of this type+channel. Keep different browser types for the speed. - for (const b of this._playwright.allBrowsers()) { - if (b === browser) - continue; - if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel) - await b.close({ reason: 'Connection terminated' }); - } - - if (!browser) { - browser = await this._playwright[(this._options.browserName || 'chromium') as 'chromium'].launch(serverSideCallMetadata(), { - ...this._options.launchOptions, - headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS, - }); - browser.on(Browser.Events.Disconnected, () => { - // Underlying browser did close for some reason - force disconnect the client. - this.close({ code: 1001, reason: 'Browser closed' }); - }); - } - browser.options.sdkLanguage = options.sdkLanguage; - - this._cleanups.push(async () => { - // Don't close the pages so that user could debug them, - // but close all the empty browsers and contexts to clean up. - for (const browser of this._playwright.allBrowsers()) { - for (const context of browser.contexts()) { - if (!context.pages().length) - await context.close({ reason: 'Connection terminated' }); - else - await context.stopPendingOperations('Connection closed'); - } - if (!browser.contexts()) - await browser.close({ reason: 'Connection terminated' }); + const options = await initialize(); + if (options.preLaunchedBrowser) { + const browser = options.preLaunchedBrowser; + browser.options.sdkLanguage = params.sdkLanguage; + browser.on(Browser.Events.Disconnected, () => { + // Underlying browser did close for some reason - force disconnect the client. + this.close({ code: 1001, reason: 'Browser closed' }); + }); } - }); + if (options.preLaunchedAndroidDevice) { + const androidDevice = options.preLaunchedAndroidDevice; + androidDevice.on(AndroidDevice.Events.Close, () => { + // Underlying android device did close for some reason - force disconnect the client. + this.close({ code: 1001, reason: 'Android device disconnected' }); + }); + } + if (options.dispose) + this._cleanups.push(options.dispose); - const playwrightDispatcher = new PlaywrightDispatcher(scope, this._playwright, { preLaunchedBrowser: browser, denyLaunch: true }); - return playwrightDispatcher; - } + const dispatcher = new PlaywrightDispatcher(scope, playwright, options); + this._cleanups.push(() => dispatcher.cleanup()); - private async _createOwnedSocksProxy(): Promise { - if (!this._options.socksProxyPattern) { - this._options.launchOptions.socksProxyPort = undefined; - return; - } - const socksProxy = new SocksProxy(); - socksProxy.setPattern(this._options.socksProxyPattern); - this._options.launchOptions.socksProxyPort = await socksProxy.listen(0); - debugLogger.log('server', `[${this._id}] started socks proxy on port ${this._options.launchOptions.socksProxyPort}`); - this._cleanups.push(() => socksProxy.close()); - return socksProxy; + return dispatcher; + }); } private async _onDisconnect(error?: Error) { @@ -250,7 +113,7 @@ export class PlaywrightConnection { for (const cleanup of this._cleanups) await cleanup().catch(() => {}); await stopProfiling(this._profileName); - this._onClose(); + this._semaphore.release(); debugLogger.log('server', `[${this._id}] finished cleanup`); } @@ -275,47 +138,3 @@ export class PlaywrightConnection { } } } - -function launchOptionsHash(options: LaunchOptions) { - const copy = { ...options }; - for (const k of Object.keys(copy)) { - const key = k as keyof LaunchOptions; - if (copy[key] === defaultLaunchOptions[key]) - delete copy[key]; - } - for (const key of optionsThatAllowBrowserReuse) - delete copy[key]; - return JSON.stringify(copy); -} - -function filterLaunchOptions(options: LaunchOptions, allowFSPaths: boolean): LaunchOptions { - return { - channel: options.channel, - args: options.args, - ignoreAllDefaultArgs: options.ignoreAllDefaultArgs, - ignoreDefaultArgs: options.ignoreDefaultArgs, - timeout: options.timeout, - headless: options.headless, - proxy: options.proxy, - chromiumSandbox: options.chromiumSandbox, - firefoxUserPrefs: options.firefoxUserPrefs, - slowMo: options.slowMo, - executablePath: (isUnderTest() || allowFSPaths) ? options.executablePath : undefined, - downloadsPath: allowFSPaths ? options.downloadsPath : undefined, - }; -} - -const defaultLaunchOptions: Partial = { - ignoreAllDefaultArgs: false, - handleSIGINT: false, - handleSIGTERM: false, - handleSIGHUP: false, - headless: true, - devtools: false, -}; - -const optionsThatAllowBrowserReuse: (keyof LaunchOptions)[] = [ - 'headless', - 'timeout', - 'tracesDir', -]; diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index a6add53eb4e14..ced787c64bc40 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -21,9 +21,10 @@ import { DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT } from '../utils/isomorphic/time'; import { WSServer } from '../server/utils/wsServer'; import { wrapInASCIIBox } from '../server/utils/ascii'; import { getPlaywrightVersion } from '../server/utils/userAgent'; +import { debugLogger, isUnderTest } from '../utils'; +import { serverSideCallMetadata } from '../server'; +import { SocksProxy } from '../server/utils/socksProxy'; -import type { ClientType } from './playwrightConnection'; -import type { SocksProxy } from '../server/utils/socksProxy'; import type { AndroidDevice } from '../server/android/android'; import type { Browser } from '../server/browser'; import type { Playwright } from '../server/playwright'; @@ -94,42 +95,166 @@ export class PlaywrightServer { } catch (e) { } - // Instantiate playwright for the extension modes. const isExtension = this._options.mode === 'extension'; - let clientType: ClientType = 'launch-browser'; - let semaphore: Semaphore = browserSemaphore; - if (isExtension && url.searchParams.has('debug-controller')) { - clientType = 'controller'; - semaphore = controllerSemaphore; - } else if (isExtension) { - clientType = 'reuse-browser'; - semaphore = reuseBrowserSemaphore; - } else if (this._options.mode === 'launchServer' || this._options.mode === 'launchServerShared') { - clientType = 'pre-launched-browser-or-android'; - semaphore = browserSemaphore; + const allowFSPaths = isExtension; + launchOptions = filterLaunchOptions(launchOptions, allowFSPaths); + + if (isExtension) { + if (url.searchParams.has('debug-controller')) { + return new PlaywrightConnection( + controllerSemaphore, + ws, + true, + this._playwright, + async () => { throw new Error('shouldnt be used'); }, + id, + ); + } + return new PlaywrightConnection( + reuseBrowserSemaphore, + ws, + false, + this._playwright, + () => this._initReuseBrowsersMode(browserName, launchOptions, id), + id, + ); + } + + if (this._options.mode === 'launchServer' || this._options.mode === 'launchServerShared') { + if (this._options.preLaunchedBrowser) { + return new PlaywrightConnection( + browserSemaphore, + ws, + false, + this._playwright, + () => this._initPreLaunchedBrowserMode(id), + id, + ); + } + + return new PlaywrightConnection( + browserSemaphore, + ws, + false, + this._playwright, + () => this._initPreLaunchedAndroidMode(id), + id, + ); } return new PlaywrightConnection( - semaphore.acquire(), - clientType, ws, - { - socksProxyPattern: proxyValue, - browserName, - launchOptions, - allowFSPaths: this._options.mode === 'extension', - sharedBrowser: this._options.mode === 'launchServerShared', - }, + browserSemaphore, + ws, + false, this._playwright, - { - browser: this._options.preLaunchedBrowser, - androidDevice: this._options.preLaunchedAndroidDevice, - socksProxy: this._options.preLaunchedSocksProxy, - }, - id, () => semaphore.release()); + () => this._initLaunchBrowserMode(browserName, proxyValue, launchOptions, id), + id, + ); }, }); } + private async _initReuseBrowsersMode(browserName: string | null, launchOptions: LaunchOptions, id: string) { + // Note: reuse browser mode does not support socks proxy, because + // clients come and go, while the browser stays the same. + + debugLogger.log('server', `[${id}] engaged reuse browsers mode for ${browserName}`); + + const requestedOptions = launchOptionsHash(launchOptions); + let browser = this._playwright.allBrowsers().find(b => { + if (b.options.name !== browserName) + return false; + const existingOptions = launchOptionsHash(b.options.originalLaunchOptions); + return existingOptions === requestedOptions; + }); + + // Close remaining browsers of this type+channel. Keep different browser types for the speed. + for (const b of this._playwright.allBrowsers()) { + if (b === browser) + continue; + if (b.options.name === browserName && b.options.channel === launchOptions.channel) + await b.close({ reason: 'Connection terminated' }); + } + + if (!browser) { + browser = await this._playwright[(browserName || 'chromium') as 'chromium'].launch(serverSideCallMetadata(), { + ...launchOptions, + headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS, + }); + } + + return { + preLaunchedBrowser: browser, + denyLaunch: true, + dispose: async () => { + // Don't close the pages so that user could debug them, + // but close all the empty browsers and contexts to clean up. + for (const browser of this._playwright.allBrowsers()) { + for (const context of browser.contexts()) { + if (!context.pages().length) + await context.close({ reason: 'Connection terminated' }); + else + await context.stopPendingOperations('Connection closed'); + } + if (!browser.contexts()) + await browser.close({ reason: 'Connection terminated' }); + } + } + }; + } + + private async _initPreLaunchedBrowserMode(id: string) { + debugLogger.log('server', `[${id}] engaged pre-launched (browser) mode`); + + const browser = this._options.preLaunchedBrowser!; + + // In pre-launched mode, keep only the pre-launched browser. + for (const b of this._playwright.allBrowsers()) { + if (b !== browser) + await b.close({ reason: 'Connection terminated' }); + } + + return { + preLaunchedBrowser: browser, + socksProxy: this._options.preLaunchedSocksProxy, + sharedBrowser: this._options.mode === 'launchServerShared', + denyLaunch: true, + }; + } + + private async _initPreLaunchedAndroidMode(id: string) { + debugLogger.log('server', `[${id}] engaged pre-launched (Android) mode`); + const androidDevice = this._options.preLaunchedAndroidDevice!; + return { + preLaunchedAndroidDevice: androidDevice, + denyLaunch: true, + }; + } + + private async _initLaunchBrowserMode(browserName: string | null, proxyValue: string | undefined, launchOptions: LaunchOptions, id: string) { + debugLogger.log('server', `[${id}] engaged launch mode for "${browserName}"`); + let socksProxy: SocksProxy | undefined; + if (proxyValue) { + socksProxy = new SocksProxy(); + socksProxy.setPattern(proxyValue); + launchOptions.socksProxyPort = await socksProxy.listen(0); + debugLogger.log('server', `[${id}] started socks proxy on port ${launchOptions.socksProxyPort}`); + } else { + launchOptions.socksProxyPort = undefined; + } + const browser = await this._playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), launchOptions); + return { + preLaunchedBrowser: browser, + socksProxy, + sharedBrowser: true, + denyLaunch: true, + dispose: async () => { + await browser.close({ reason: 'Connection terminated' }); + socksProxy?.close(); + }, + }; + } + async listen(port: number = 0, hostname?: string): Promise { return this._wsServer.listen(port, hostname, this._options.path); } @@ -163,3 +288,47 @@ function userAgentVersionMatchesErrorMessage(userAgent: string) { ].join('\n'), 1); } } + +function launchOptionsHash(options: LaunchOptions) { + const copy = { ...options }; + for (const k of Object.keys(copy)) { + const key = k as keyof LaunchOptions; + if (copy[key] === defaultLaunchOptions[key]) + delete copy[key]; + } + for (const key of optionsThatAllowBrowserReuse) + delete copy[key]; + return JSON.stringify(copy); +} + +function filterLaunchOptions(options: LaunchOptions, allowFSPaths: boolean): LaunchOptions { + return { + channel: options.channel, + args: options.args, + ignoreAllDefaultArgs: options.ignoreAllDefaultArgs, + ignoreDefaultArgs: options.ignoreDefaultArgs, + timeout: options.timeout, + headless: options.headless, + proxy: options.proxy, + chromiumSandbox: options.chromiumSandbox, + firefoxUserPrefs: options.firefoxUserPrefs, + slowMo: options.slowMo, + executablePath: (isUnderTest() || allowFSPaths) ? options.executablePath : undefined, + downloadsPath: allowFSPaths ? options.downloadsPath : undefined, + }; +} + +const defaultLaunchOptions: Partial = { + ignoreAllDefaultArgs: false, + handleSIGINT: false, + handleSIGTERM: false, + handleSIGHUP: false, + headless: true, + devtools: false, +}; + +const optionsThatAllowBrowserReuse: (keyof LaunchOptions)[] = [ + 'headless', + 'timeout', + 'tracesDir', +]; diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index 04bb144d1176a..a80aa49456299 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -35,7 +35,7 @@ import type { Browser } from '../browser'; import type { Playwright } from '../playwright'; import type * as channels from '@protocol/channels'; -type PlaywrightDispatcherOptions = { +export type PlaywrightDispatcherOptions = { socksProxy?: SocksProxy; denyLaunch?: boolean; preLaunchedBrowser?: Browser; From 73f840c1d014bbdf4d8d8da594e89430cdbcffa4 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 20 Jun 2025 12:50:52 +0100 Subject: [PATCH 087/222] chore: use isNonRetriableError in more places (#36373) --- packages/playwright-core/src/server/dom.ts | 7 +++---- packages/playwright-core/src/server/frames.ts | 12 ++++++------ packages/playwright-core/src/server/page.ts | 9 ++++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index ce9ce887c03b3..35ed3cabd2bcf 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -17,10 +17,9 @@ import fs from 'fs'; import * as js from './javascript'; -import { isAbortError, ProgressController } from './progress'; +import { ProgressController } from './progress'; import { asLocator, isUnderTest } from '../utils'; import { prepareFilesForUpload } from './fileUploadUtils'; -import { isSessionClosedError } from './protocolError'; import * as rawInjectedScriptSource from '../generated/injectedScriptSource'; import type * as frames from './frames'; @@ -141,7 +140,7 @@ export class ElementHandle extends js.JSHandle { const utility = await this._frame._utilityContext(); return await utility.evaluate(pageFunction, [await utility.injectedScript(), this, arg]); } catch (e) { - if (isAbortError(e) || js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) + if (this._frame.isNonRetriableError(e)) throw e; return 'error:notconnected'; } @@ -152,7 +151,7 @@ export class ElementHandle extends js.JSHandle { const utility = await this._frame._utilityContext(); return await utility.evaluateHandle(pageFunction, [await utility.injectedScript(), this, arg]); } catch (e) { - if (isAbortError(e) || js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) + if (this._frame.isNonRetriableError(e)) throw e; return 'error:notconnected'; } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index a0bc51b7f37c4..9e32a7d8370a2 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -864,7 +864,7 @@ export class Frame extends SdkObject { return retVal; }); } catch (e) { - if (isAbortError(e) || js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) + if (this.isNonRetriableError(e)) throw e; throw new Error(`Unable to retrieve content because the page is navigating and changing the content.`); } @@ -1060,14 +1060,14 @@ export class Frame extends SdkObject { continue; return result as R; } catch (e) { - if (this._isErrorThatCannotBeRetried(e)) + if (this.isNonRetriableError(e)) throw e; continue; } } } - private _isErrorThatCannotBeRetried(e: Error) { + isNonRetriableError(e: Error) { if (isAbortError(e)) return true; // Always fail on JavaScript errors or when the main connection is closed. @@ -1288,7 +1288,7 @@ export class Frame extends SdkObject { return state.matches; }, { info: resolved.info, root: resolved.frame === this ? scope : undefined })); } catch (e) { - if (isAbortError(e) || js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e) || isSessionClosedError(e)) + if (this.isNonRetriableError(e)) throw e; return false; } @@ -1408,7 +1408,7 @@ export class Frame extends SdkObject { if (resultOneShot.matches !== options.isNot) return resultOneShot; } catch (e) { - if (isAbortError(e) || js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e)) + if (this.isNonRetriableError(e)) throw e; // Ignore any other errors from one-shot, we'll handle them during retries. } @@ -1434,7 +1434,7 @@ export class Frame extends SdkObject { }); }, timeout); } catch (e) { - // Q: Why not throw upon isSessionClosedError(e) as in other places? + // Q: Why not throw upon isNonRetriableError(e) as in other places? // A: We want user to receive a friendly message containing the last intermediate result. if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e)) throw e; diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 9a9bf878a1ab1..42ad32e9c020e 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -25,7 +25,7 @@ import { helper } from './helper'; import * as input from './input'; import { SdkObject } from './instrumentation'; import * as js from './javascript'; -import { isAbortError, ProgressController } from './progress'; +import { ProgressController } from './progress'; import { Screenshotter, validateScreenshotOptions } from './screenshotter'; import { LongStandingScope, assert, renderTitleForCall, trimStringWithEllipsis } from '../utils'; import { asLocator } from '../utils'; @@ -36,7 +36,6 @@ import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { parseEvaluationResultValue } from '../utils/isomorphic/utilityScriptSerializers'; import { compressCallLog } from './callLog'; import * as rawBindingsControllerSource from '../generated/bindingsControllerSource'; -import { isSessionClosedError } from './protocolError'; import type { Artifact } from './artifact'; import type * as dom from './dom'; @@ -643,7 +642,7 @@ export class Page extends SdkObject { progress.log(`waiting ${screenshotTimeout}ms before taking screenshot`); previous = actual; actual = await rafrafScreenshot(progress, screenshotTimeout).catch(e => { - if (isAbortError(e)) + if (this.mainFrame().isNonRetriableError(e)) throw e; progress.log(`failed to take screenshot - ` + e.message); return undefined; @@ -676,7 +675,7 @@ export class Page extends SdkObject { } throw new Error(intermediateResult!.errorMessage); }, callTimeout).catch(e => { - // Q: Why not throw upon isSessionClosedError(e) as in other places? + // Q: Why not throw upon isNonRetriableError(e) as in other places? // A: We want user to receive a friendly diff between actual and expected/previous. if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e)) throw e; @@ -1010,7 +1009,7 @@ async function snapshotFrameForAI(progress: Progress, frame: frames.Frame, frame return continuePolling; return snapshotOrRetry; } catch (e) { - if (isAbortError(e) || isSessionClosedError(e) || js.isJavaScriptErrorInEvaluate(e)) + if (frame.isNonRetriableError(e)) throw e; return continuePolling; } From 556fea9444681220982cf7797cb98fdf33522d96 Mon Sep 17 00:00:00 2001 From: Kevin Tan Date: Fri, 20 Jun 2025 20:09:32 +0800 Subject: [PATCH 088/222] fix: adding trialing slash detection logic back in urlToWSEndpoint (#36357) --- .../src/server/chromium/chromium.ts | 2 ++ tests/library/chromium/connect-over-cdp.spec.ts | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index b35fbb83f0828..fdeabb672656a 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -382,6 +382,8 @@ async function urlToWSEndpoint(progress: Progress, endpointURL: string, headers: return endpointURL; progress.log(` retrieving websocket url from ${endpointURL}`); const url = new URL(endpointURL); + if (!url.pathname.endsWith('/')) + url.pathname = url.pathname + '/'; url.pathname += 'json/version/'; const httpURL = url.toString(); diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index d5ff7915675a4..389f8838eef37 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -41,6 +41,23 @@ test('should connect to an existing cdp session', async ({ browserType, mode }, } }); +test('should connect to an existing cdp session with verbose path', async ({ browserType, mode }, testInfo) => { + const port = 9339 + testInfo.workerIndex; + const browserServer = await browserType.launch({ + args: ['--remote-debugging-port=' + port] + }); + try { + const cdpBrowser = await browserType.connectOverCDP({ + endpointURL: `http://127.0.0.1:${port}/json/version/abcdefg`, + }); + const contexts = cdpBrowser.contexts(); + expect(contexts.length).toBe(1); + await cdpBrowser.close(); + } finally { + await browserServer.close(); + } +}); + test('should use logger in default context', async ({ browserType }, testInfo) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28813' }); const port = 9339 + testInfo.workerIndex; From 0027bd97cb080220051cadc8f67ed66c3caf5404 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 20 Jun 2025 16:26:43 +0200 Subject: [PATCH 089/222] chore: browserserver, design two (#36382) --- .../playwright-core/src/client/browserType.ts | 4 +- .../src/remote/playwrightConnection.ts | 6 +- .../src/remote/playwrightServer.ts | 62 ++++++++++++++++--- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index a9116101b6cab..e41b0db69a325 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -119,8 +119,8 @@ export class BrowserType extends ChannelOwner imple }); } - connect(options: api.ConnectOptions & { wsEndpoint: string }): Promise; - connect(wsEndpoint: string, options?: api.ConnectOptions): Promise; + connect(options: api.ConnectOptions & { wsEndpoint: string }): Promise; + connect(wsEndpoint: string, options?: api.ConnectOptions): Promise; async connect(optionsOrWsEndpoint: string | (api.ConnectOptions & { wsEndpoint: string }), options?: api.ConnectOptions): Promise{ if (typeof optionsOrWsEndpoint === 'string') return await this._connect({ ...options, wsEndpoint: optionsOrWsEndpoint }); diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index d92caeee78835..8a529f4b7a48a 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -26,6 +26,10 @@ import { PlaywrightDispatcherOptions } from '../server/dispatchers/playwrightDis import type { DispatcherScope, Playwright } from '../server'; import type { WebSocket } from '../utilsBundle'; +export interface PlaywrightInitializeResult extends PlaywrightDispatcherOptions { + dispose?(): Promise; +} + export class PlaywrightConnection { private _ws: WebSocket; private _semaphore: Semaphore; @@ -36,7 +40,7 @@ export class PlaywrightConnection { private _root: DispatcherScope; private _profileName: string; - constructor(semaphore: Semaphore, ws: WebSocket, controller: boolean, playwright: Playwright, initialize: () => Promise }>, id: string) { + constructor(semaphore: Semaphore, ws: WebSocket, controller: boolean, playwright: Playwright, initialize: () => Promise, id: string) { this._ws = ws; this._semaphore = semaphore; this._id = id; diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index ced787c64bc40..6eb077fa929e7 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PlaywrightConnection } from './playwrightConnection'; +import { PlaywrightConnection, PlaywrightInitializeResult } from './playwrightConnection'; import { createPlaywright } from '../server/playwright'; import { Semaphore } from '../utils/isomorphic/semaphore'; import { DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT } from '../utils/isomorphic/time'; @@ -24,9 +24,9 @@ import { getPlaywrightVersion } from '../server/utils/userAgent'; import { debugLogger, isUnderTest } from '../utils'; import { serverSideCallMetadata } from '../server'; import { SocksProxy } from '../server/utils/socksProxy'; +import { Browser } from '../server/browser'; import type { AndroidDevice } from '../server/android/android'; -import type { Browser } from '../server/browser'; import type { Playwright } from '../server/playwright'; import type { LaunchOptions } from '../server/types'; @@ -45,10 +45,14 @@ export class PlaywrightServer { private _options: ServerOptions; private _wsServer: WSServer; + private _dontReuseBrowsers = new Set(); + constructor(options: ServerOptions) { this._options = options; - if (options.preLaunchedBrowser) + if (options.preLaunchedBrowser) { this._playwright = options.preLaunchedBrowser.attribution.playwright; + this._dontReuse(options.preLaunchedBrowser); + } if (options.preLaunchedAndroidDevice) this._playwright = options.preLaunchedAndroidDevice._android.attribution.playwright; this._playwright ??= createPlaywright({ sdkLanguage: 'javascript', isServer: true }); @@ -99,6 +103,20 @@ export class PlaywrightServer { const allowFSPaths = isExtension; launchOptions = filterLaunchOptions(launchOptions, allowFSPaths); + if (process.env.PW_BROWSER_SERVER && url.searchParams.has('connect')) { + const filter = url.searchParams.get('connect'); + if (filter !== 'first') + throw new Error(`Unknown connect filter: ${filter}`); + return new PlaywrightConnection( + browserSemaphore, + ws, + false, + this._playwright, + () => this._initConnectMode(id, filter, browserName, launchOptions), + id, + ); + } + if (isExtension) { if (url.searchParams.has('debug-controller')) { return new PlaywrightConnection( @@ -154,7 +172,7 @@ export class PlaywrightServer { }); } - private async _initReuseBrowsersMode(browserName: string | null, launchOptions: LaunchOptions, id: string) { + private async _initReuseBrowsersMode(browserName: string | null, launchOptions: LaunchOptions, id: string): Promise { // Note: reuse browser mode does not support socks proxy, because // clients come and go, while the browser stays the same. @@ -164,6 +182,8 @@ export class PlaywrightServer { let browser = this._playwright.allBrowsers().find(b => { if (b.options.name !== browserName) return false; + if (this._dontReuseBrowsers.has(b)) + return false; const existingOptions = launchOptionsHash(b.options.originalLaunchOptions); return existingOptions === requestedOptions; }); @@ -172,6 +192,8 @@ export class PlaywrightServer { for (const b of this._playwright.allBrowsers()) { if (b === browser) continue; + if (this._dontReuseBrowsers.has(b)) + continue; if (b.options.name === browserName && b.options.channel === launchOptions.channel) await b.close({ reason: 'Connection terminated' }); } @@ -203,7 +225,25 @@ export class PlaywrightServer { }; } - private async _initPreLaunchedBrowserMode(id: string) { + private async _initConnectMode(id: string, filter: 'first', browserName: string | null, launchOptions: LaunchOptions): Promise { + browserName ??= 'chromium'; + + debugLogger.log('server', `[${id}] engaged connect mode`); + + let browser = this._playwright.allBrowsers().find(b => b.options.name === browserName); + if (!browser) { + browser = await this._playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), launchOptions); + this._dontReuse(browser); + } + + return { + preLaunchedBrowser: browser, + denyLaunch: true, + sharedBrowser: true, + }; + } + + private async _initPreLaunchedBrowserMode(id: string): Promise { debugLogger.log('server', `[${id}] engaged pre-launched (browser) mode`); const browser = this._options.preLaunchedBrowser!; @@ -222,7 +262,7 @@ export class PlaywrightServer { }; } - private async _initPreLaunchedAndroidMode(id: string) { + private async _initPreLaunchedAndroidMode(id: string): Promise { debugLogger.log('server', `[${id}] engaged pre-launched (Android) mode`); const androidDevice = this._options.preLaunchedAndroidDevice!; return { @@ -231,7 +271,7 @@ export class PlaywrightServer { }; } - private async _initLaunchBrowserMode(browserName: string | null, proxyValue: string | undefined, launchOptions: LaunchOptions, id: string) { + private async _initLaunchBrowserMode(browserName: string | null, proxyValue: string | undefined, launchOptions: LaunchOptions, id: string): Promise { debugLogger.log('server', `[${id}] engaged launch mode for "${browserName}"`); let socksProxy: SocksProxy | undefined; if (proxyValue) { @@ -243,6 +283,7 @@ export class PlaywrightServer { launchOptions.socksProxyPort = undefined; } const browser = await this._playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), launchOptions); + this._dontReuseBrowsers.add(browser); return { preLaunchedBrowser: browser, socksProxy, @@ -255,6 +296,13 @@ export class PlaywrightServer { }; } + private _dontReuse(browser: Browser) { + this._dontReuseBrowsers.add(browser); + browser.on(Browser.Events.Disconnected, () => { + this._dontReuseBrowsers.delete(browser); + }); + } + async listen(port: number = 0, hostname?: string): Promise { return this._wsServer.listen(port, hostname, this._options.path); } From d8c257f79b2729190be78ce8b6fad6664226ff16 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:48:52 +0200 Subject: [PATCH 090/222] feat(chromium): roll to r1180 (#36384) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/deviceDescriptorsSource.json | 108 +++++++++--------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index d912d0d166770..96ab31796073a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.23-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.35-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 138.0.7204.23 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 138.0.7204.35 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.5 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 139.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index ae4ec5d7beb55..fe6214087fb93 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1179", + "revision": "1180", "installByDefault": true, - "browserVersion": "138.0.7204.23" + "browserVersion": "138.0.7204.35" }, { "name": "chromium-headless-shell", - "revision": "1179", + "revision": "1180", "installByDefault": true, - "browserVersion": "138.0.7204.23" + "browserVersion": "138.0.7204.35" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index c42a1abf3e4b0..f260c0d5e39d9 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S24": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 480, "height": 1040 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S24 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 1040, "height": 480 @@ -198,7 +198,7 @@ "defaultBrowserType": "chromium" }, "Galaxy A55": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 480, "height": 1040 @@ -209,7 +209,7 @@ "defaultBrowserType": "chromium" }, "Galaxy A55 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 1040, "height": 480 @@ -220,7 +220,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -231,7 +231,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -242,7 +242,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S9": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 640, "height": 1024 @@ -253,7 +253,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S9 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 1024, "height": 640 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1318,7 +1318,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1329,7 +1329,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1340,7 +1340,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1351,7 +1351,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1472,7 +1472,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1483,7 +1483,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1494,7 +1494,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1505,7 +1505,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1516,7 +1516,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1527,7 +1527,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1538,7 +1538,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1549,7 +1549,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1560,7 +1560,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1575,7 +1575,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1590,7 +1590,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1605,7 +1605,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1620,7 +1620,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1635,7 +1635,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1650,7 +1650,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1661,7 +1661,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1672,7 +1672,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1687,7 +1687,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36 Edg/138.0.7204.23", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36 Edg/138.0.7204.35", "screen": { "width": 1792, "height": 1120 @@ -1732,7 +1732,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1747,7 +1747,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.23 Safari/537.36 Edg/138.0.7204.23", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.35 Safari/537.36 Edg/138.0.7204.35", "screen": { "width": 1920, "height": 1080 From 41bcfc998643dcd878700dac67a5cf040a06d6f0 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:49:04 +0200 Subject: [PATCH 091/222] feat(webkit): roll to r2186 (#36391) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index fe6214087fb93..ddb9eea603779 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2185", + "revision": "2186", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From ed7e552006f03d7040d8877f569a1e1bbef79e6f Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 23 Jun 2025 08:46:04 +0200 Subject: [PATCH 092/222] chore: don't close other browsers in reuse-browsers mode (#36383) Signed-off-by: Simon Knott Co-authored-by: Dmitry Gozman --- .../src/remote/playwrightServer.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index 6eb077fa929e7..9839cff9b3fcc 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -210,16 +210,13 @@ export class PlaywrightServer { denyLaunch: true, dispose: async () => { // Don't close the pages so that user could debug them, - // but close all the empty browsers and contexts to clean up. - for (const browser of this._playwright.allBrowsers()) { - for (const context of browser.contexts()) { - if (!context.pages().length) - await context.close({ reason: 'Connection terminated' }); - else - await context.stopPendingOperations('Connection closed'); - } - if (!browser.contexts()) - await browser.close({ reason: 'Connection terminated' }); + // but close all the empty contexts to clean up. + // keep around browser so it can be reused by the next connection. + for (const context of browser.contexts()) { + if (!context.pages().length) + await context.close({ reason: 'Connection terminated' }); + else + await context.stopPendingOperations('Connection closed'); } } }; From 07c49587772cf9c7cb80ccb0fd0673c46520e06c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 23 Jun 2025 09:45:02 +0200 Subject: [PATCH 093/222] docs(clock): add snippets for 'Test with predefined time' for ports (#36393) --- docs/src/clock.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/src/clock.md b/docs/src/clock.md index 44582f450befb..e898899b18251 100644 --- a/docs/src/clock.md +++ b/docs/src/clock.md @@ -64,6 +64,47 @@ await page.clock.setFixedTime(new Date('2024-02-02T10:30:00')); await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM'); ``` +```python async +await page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 0, 0)) +await page.goto("http://localhost:3333") +await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM") + +await page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 30, 0)) +# We know that the page has a timer that updates the time every second. +await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM") +``` + +```python sync +page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 0, 0)) +page.goto("http://localhost:3333") +expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM") +page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 30, 0)) +# We know that the page has a timer that updates the time every second. +expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM") +``` + +```java +SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss"); +page.clock().setFixedTime(format.parse("2024-02-02T10:00:00")); +page.navigate("http://localhost:3333"); +Locator locator = page.getByTestId("current-time"); +assertThat(locator).hasText("2/2/2024, 10:00:00 AM"); +page.clock().setFixedTime(format.parse("2024-02-02T10:30:00")); +// We know that the page has a timer that updates the time every second. +assertThat(locator).hasText("2/2/2024, 10:30:00 AM"); +``` + +```csharp +// Set the fixed time for the clock. +await Page.Clock.SetFixedTimeAsync(new DateTime(2024, 2, 2, 10, 0, 0)); +await Page.GotoAsync("http://localhost:3333"); +await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM"); +// Set the fixed time for the clock. +await Page.Clock.SetFixedTimeAsync(new DateTime(2024, 2, 2, 10, 30, 0)); +// We know that the page has a timer that updates the time every second. +await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM"); +``` + ## Consistent time and timers Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time. From 6c26c5f6ac1ba58c94e85f881ac7e0b2013f9cfa Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:45:07 +0200 Subject: [PATCH 094/222] feat(chromium-tip-of-tree): roll to r1342 (#36379) --- packages/playwright-core/browsers.json | 8 ++++---- tests/library/chromium/tracing.spec.ts | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index ddb9eea603779..3b75b5f38177c 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,15 +15,15 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1341", + "revision": "1342", "installByDefault": false, - "browserVersion": "139.0.7244.0" + "browserVersion": "139.0.7248.0" }, { "name": "chromium-tip-of-tree-headless-shell", - "revision": "1341", + "revision": "1342", "installByDefault": false, - "browserVersion": "139.0.7244.0" + "browserVersion": "139.0.7248.0" }, { "name": "firefox", diff --git a/tests/library/chromium/tracing.spec.ts b/tests/library/chromium/tracing.spec.ts index a39f21ba274f6..4787fdefa8c3c 100644 --- a/tests/library/chromium/tracing.spec.ts +++ b/tests/library/chromium/tracing.spec.ts @@ -49,11 +49,12 @@ it('should create directories as needed', async ({ browser, server }, testInfo) it('should run with custom categories if provided', async ({ browser }, testInfo) => { const page = await browser.newPage(); const outputTraceFile = testInfo.outputPath(path.join(`trace.json`)); - await browser.startTracing(page, { path: outputTraceFile, categories: ['disabled-by-default-v8.cpu_profiler.hires'] }); + await browser.startTracing(page, { path: outputTraceFile, categories: ['disabled-by-default-cc.debug'] }); + await page.evaluate(() => 1 + 1); await browser.stopTracing(); const traceJson = JSON.parse(fs.readFileSync(outputTraceFile).toString()); - expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires'); + expect(traceJson.traceEvents.filter(event => event.cat === 'disabled-by-default-cc.debug').length).toBeGreaterThan(0); await page.close(); }); From 06a065de7cb95003c95d93a09d76f3099804de80 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 23 Jun 2025 11:01:14 +0100 Subject: [PATCH 095/222] test: skip `should handle timeout properly 2` on tracing bots (#36399) --- tests/page/page-set-content.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/page/page-set-content.spec.ts b/tests/page/page-set-content.spec.ts index 38f5f111cc02a..33e04c8604a30 100644 --- a/tests/page/page-set-content.spec.ts +++ b/tests/page/page-set-content.spec.ts @@ -148,7 +148,9 @@ it('should handle timeout properly', async ({ page, toImpl, browserName }) => { await expect(page.locator('div')).toHaveText('world'); }); -it('should handle timeout properly 2', async ({ page, toImpl }) => { +it('should handle timeout properly 2', async ({ page, toImpl, trace }) => { + it.skip(trace === 'on'); + await toImpl(page).mainFrame().evaluateExpression(String(() => { document.close = () => { while (true) {} From 184fb046040dcd01fe6d19843e387178cf274452 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:35:57 +0200 Subject: [PATCH 096/222] test: roll stable-test-runner to 1.54.0-alpha-2025-06-23 (#36401) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- .../stable-test-runner/package-lock.json | 46 +++++++++---------- .../stable-test-runner/package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/playwright-test/stable-test-runner/package-lock.json b/tests/playwright-test/stable-test-runner/package-lock.json index 31feaa6e26246..31a57c66996d2 100644 --- a/tests/playwright-test/stable-test-runner/package-lock.json +++ b/tests/playwright-test/stable-test-runner/package-lock.json @@ -5,16 +5,16 @@ "packages": { "": { "dependencies": { - "@playwright/test": "^1.54.0-alpha-2025-06-16" + "@playwright/test": "^1.54.0-alpha-2025-06-23" } }, "node_modules/@playwright/test": { - "version": "1.54.0-alpha-2025-06-16", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.0-alpha-2025-06-16.tgz", - "integrity": "sha512-TENymjKYtOyPPFWPoJGmpB9ajjRnCjXS/DtUFNX2X1VL97K0Y+Cu15ClS+WmHD9Hpd424x6ov32qPCTdbBGdBQ==", + "version": "1.54.0-alpha-2025-06-23", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.0-alpha-2025-06-23.tgz", + "integrity": "sha512-DPbNqSuZys9IvxDMZsYynHL/BLFjGRvkpaYT98Z2cqNEgEfP1XDHKlgtm3QdzgBuHuyjAYqaTqjkscQy7MDr0g==", "license": "Apache-2.0", "dependencies": { - "playwright": "1.54.0-alpha-2025-06-16" + "playwright": "1.54.0-alpha-2025-06-23" }, "bin": { "playwright": "cli.js" @@ -38,12 +38,12 @@ } }, "node_modules/playwright": { - "version": "1.54.0-alpha-2025-06-16", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.0-alpha-2025-06-16.tgz", - "integrity": "sha512-2w+M4qAh6XzGkeZMSp6UmuN4xfqyAFGG+zd6CiLsvTT7ZfBVuSLpDxO9N/bTBcp2auk5tmt8173OiwG1kmLXeQ==", + "version": "1.54.0-alpha-2025-06-23", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.0-alpha-2025-06-23.tgz", + "integrity": "sha512-TsaHVhZXvyaPFk0RIoxisit3PRQ3DUvwihGYKFW2GLeQJt8G5e6Oke/0VQ6VKzqltpTXJeDRIzc4MPRr9HjRDw==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.54.0-alpha-2025-06-16" + "playwright-core": "1.54.0-alpha-2025-06-23" }, "bin": { "playwright": "cli.js" @@ -56,9 +56,9 @@ } }, "node_modules/playwright-core": { - "version": "1.54.0-alpha-2025-06-16", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.0-alpha-2025-06-16.tgz", - "integrity": "sha512-egCxymKutvP+lWzAQmKfHnomfAyiHpjSseZrTMn52B5hqVjW3BKbvwIU66Z/wY6ZBr6CUoRmiSChUWDd8jH/oA==", + "version": "1.54.0-alpha-2025-06-23", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.0-alpha-2025-06-23.tgz", + "integrity": "sha512-bhIwCBEYtGYxre6BZyTA1/nk8QlgfMoJy5NWoIj63j3J7QP84nIzAQDmnXiamn53CA8ajTVUTPgxo2bhE7GLKw==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -70,11 +70,11 @@ }, "dependencies": { "@playwright/test": { - "version": "1.54.0-alpha-2025-06-16", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.0-alpha-2025-06-16.tgz", - "integrity": "sha512-TENymjKYtOyPPFWPoJGmpB9ajjRnCjXS/DtUFNX2X1VL97K0Y+Cu15ClS+WmHD9Hpd424x6ov32qPCTdbBGdBQ==", + "version": "1.54.0-alpha-2025-06-23", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.0-alpha-2025-06-23.tgz", + "integrity": "sha512-DPbNqSuZys9IvxDMZsYynHL/BLFjGRvkpaYT98Z2cqNEgEfP1XDHKlgtm3QdzgBuHuyjAYqaTqjkscQy7MDr0g==", "requires": { - "playwright": "1.54.0-alpha-2025-06-16" + "playwright": "1.54.0-alpha-2025-06-23" } }, "fsevents": { @@ -84,18 +84,18 @@ "optional": true }, "playwright": { - "version": "1.54.0-alpha-2025-06-16", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.0-alpha-2025-06-16.tgz", - "integrity": "sha512-2w+M4qAh6XzGkeZMSp6UmuN4xfqyAFGG+zd6CiLsvTT7ZfBVuSLpDxO9N/bTBcp2auk5tmt8173OiwG1kmLXeQ==", + "version": "1.54.0-alpha-2025-06-23", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.0-alpha-2025-06-23.tgz", + "integrity": "sha512-TsaHVhZXvyaPFk0RIoxisit3PRQ3DUvwihGYKFW2GLeQJt8G5e6Oke/0VQ6VKzqltpTXJeDRIzc4MPRr9HjRDw==", "requires": { "fsevents": "2.3.2", - "playwright-core": "1.54.0-alpha-2025-06-16" + "playwright-core": "1.54.0-alpha-2025-06-23" } }, "playwright-core": { - "version": "1.54.0-alpha-2025-06-16", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.0-alpha-2025-06-16.tgz", - "integrity": "sha512-egCxymKutvP+lWzAQmKfHnomfAyiHpjSseZrTMn52B5hqVjW3BKbvwIU66Z/wY6ZBr6CUoRmiSChUWDd8jH/oA==" + "version": "1.54.0-alpha-2025-06-23", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.0-alpha-2025-06-23.tgz", + "integrity": "sha512-bhIwCBEYtGYxre6BZyTA1/nk8QlgfMoJy5NWoIj63j3J7QP84nIzAQDmnXiamn53CA8ajTVUTPgxo2bhE7GLKw==" } } } diff --git a/tests/playwright-test/stable-test-runner/package.json b/tests/playwright-test/stable-test-runner/package.json index fc3aa0e2e21ea..f643b0e498c6f 100644 --- a/tests/playwright-test/stable-test-runner/package.json +++ b/tests/playwright-test/stable-test-runner/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "@playwright/test": "^1.54.0-alpha-2025-06-16" + "@playwright/test": "^1.54.0-alpha-2025-06-23" } } From 669341733e901c55edddc2e974400d5cee85bef9 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 23 Jun 2025 13:55:39 +0100 Subject: [PATCH 097/222] chore: make progress strict by default (#36389) --- .github/workflows/tests_others.yml | 21 ++++++ .../src/server/android/android.ts | 4 +- .../src/server/bidi/bidiPage.ts | 4 +- .../playwright-core/src/server/browser.ts | 2 +- .../src/server/browserContext.ts | 6 +- .../playwright-core/src/server/browserType.ts | 6 +- .../src/server/chromium/chromium.ts | 14 ++-- .../src/server/chromium/crPage.ts | 5 +- .../src/server/chromium/videoRecorder.ts | 2 +- .../dispatchers/localUtilsDispatcher.ts | 5 +- packages/playwright-core/src/server/dom.ts | 37 +++++----- .../src/server/electron/electron.ts | 2 +- packages/playwright-core/src/server/fetch.ts | 2 +- .../src/server/firefox/ffPage.ts | 4 +- packages/playwright-core/src/server/frames.ts | 52 +++++++------- packages/playwright-core/src/server/input.ts | 22 +++--- packages/playwright-core/src/server/page.ts | 13 ++-- .../playwright-core/src/server/progress.ts | 69 +++++++++++-------- .../src/server/recorder/recorderApp.ts | 2 +- .../src/server/registry/index.ts | 2 +- .../server/registry/oopDownloadBrowserMain.ts | 3 +- .../src/server/screenshotter.ts | 2 +- .../src/server/trace/viewer/traceViewer.ts | 2 +- .../playwright-core/src/server/transport.ts | 4 +- .../src/server/utils/network.ts | 33 +++++---- .../src/server/webkit/wkPage.ts | 2 +- tests/library/browsertype-connect.spec.ts | 11 +++ 27 files changed, 181 insertions(+), 150 deletions(-) diff --git a/.github/workflows/tests_others.yml b/.github/workflows/tests_others.yml index ed18e1541c635..b35a61d3b234d 100644 --- a/.github/workflows/tests_others.yml +++ b/.github/workflows/tests_others.yml @@ -134,6 +134,27 @@ jobs: env: PW_CLOCK: ${{ matrix.clock }} + test_legacy_progress_timeouts: + name: legacy progress timeouts + environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} + permissions: + id-token: write # This is required for OIDC login (azure/login) to succeed + contents: read # This is required for actions/checkout to succeed + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/run-test + with: + node-version: 20 + browsers-to-install: chromium + command: npm run test -- --project=chromium-* + bot-name: "legacy-progress-timeouts-linux" + flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} + flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} + flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} + env: + PLAYWRIGHT_LEGACY_TIMEOUTS: 1 + test_electron: name: Electron - ${{ matrix.os }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index 3b7e4cebf34e3..69452e4e3b442 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -260,7 +260,7 @@ export class AndroidDevice extends SdkObject { } async launchBrowser(metadata: CallMetadata, pkg: string = 'com.android.chrome', options: channels.AndroidDeviceLaunchBrowserParams): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { debug('pw:android')('Force-stopping', pkg); await this._backend.runCommand(`shell:am force-stop ${pkg}`); @@ -306,7 +306,7 @@ export class AndroidDevice extends SdkObject { } async connectToWebView(metadata: CallMetadata, socketName: string): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const webView = this._webViews.get(socketName); if (!webView) diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index eacbe3e88f6d3..606d6fb416f4b 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -368,7 +368,7 @@ export class BidiPage implements PageDelegate { async takeScreenshot(progress: Progress, format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise { const rect = (documentRect || viewportRect)!; - const { data } = await this._session.send('browsingContext.captureScreenshot', { + const { data } = await progress.race(this._session.send('browsingContext.captureScreenshot', { context: this._session.sessionId, format: { type: `image/${format === 'png' ? 'png' : 'jpeg'}`, @@ -379,7 +379,7 @@ export class BidiPage implements PageDelegate { type: 'box', ...rect, } - }); + })); return Buffer.from(data, 'base64'); } diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index d985e96fa04b0..f19c124afdfeb 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -93,7 +93,7 @@ export abstract class Browser extends SdkObject { } newContextFromMetadata(metadata: CallMetadata, options: types.BrowserContextOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => this.newContext(progress, options)); } diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index bd7e934e7ddc2..3615c3b1bdcab 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -191,7 +191,7 @@ export abstract class BrowserContext extends SdkObject { } async resetForReuse(metadata: CallMetadata, params: channels.BrowserNewContextForReuseParams | null) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => this.resetForReuseImpl(progress, params)); } @@ -515,7 +515,7 @@ export abstract class BrowserContext extends SdkObject { } newPageFromMetadata(metadata: CallMetadata): Promise { - const contoller = new ProgressController(metadata, this, 'strict'); + const contoller = new ProgressController(metadata, this); return contoller.run(progress => this.newPage(progress, false)); } @@ -535,7 +535,7 @@ export abstract class BrowserContext extends SdkObject { } storageState(indexedDB = false): Promise { - const controller = new ProgressController(serverSideCallMetadata(), this, 'strict'); + const controller = new ProgressController(serverSideCallMetadata(), this); return controller.run(progress => this.storageStateImpl(progress, indexedDB)); } diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index 9febf7367aa82..ba0cfa4aa148e 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -69,7 +69,7 @@ export abstract class BrowserType extends SdkObject { async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise { options = this._validateLaunchOptions(options); - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); const browser = await controller.run(progress => { const seleniumHubUrl = (options as any).__testHookSeleniumRemoteURL || process.env.SELENIUM_REMOTE_URL; if (seleniumHubUrl) @@ -81,7 +81,7 @@ export abstract class BrowserType extends SdkObject { async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { timeout: number, cdpPort?: number, internalIgnoreHTTPSErrors?: boolean, socksProxyPort?: number }): Promise { const launchOptions = this._validateLaunchOptions(options); - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); const browser = await controller.run(async progress => { // Note: Any initial TLS requests will fail since we rely on the Page/Frames initialize which sets ignoreHTTPSErrors. let clientCertificatesProxy: ClientCertificatesProxy | undefined; @@ -259,7 +259,7 @@ export abstract class BrowserType extends SdkObject { close: () => closeOrKill((options as any).__testHookBrowserCloseTimeout || DEFAULT_PLAYWRIGHT_TIMEOUT), kill }; - progress.cleanupWhenAborted(() => closeOrKill(progress.timeUntilDeadline())); + progress.cleanupWhenAborted(() => closeOrKill(DEFAULT_PLAYWRIGHT_TIMEOUT)); const { wsEndpoint } = await progress.race([ this.waitForReadyState(options, browserLogsCollector), exitPromise.then(() => ({ wsEndpoint: undefined })), diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index fdeabb672656a..8ae2cdc864629 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -63,7 +63,7 @@ export class Chromium extends BrowserType { } override async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, headers?: types.HeadersArray, timeout: number }) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return await this._connectOverCDPInternal(progress, endpointURL, options); }, options.timeout); @@ -199,7 +199,7 @@ export class Chromium extends BrowserType { } progress.log(` connecting to ${hubUrl}`); - const response = await fetchData({ + const response = await fetchData(progress, { url: hubUrl + 'session', method: 'POST', headers: { @@ -209,7 +209,6 @@ export class Chromium extends BrowserType { data: JSON.stringify({ capabilities: { alwaysMatch: desiredCapabilities } }), - timeout: progress.timeUntilDeadline(), }, seleniumErrorHandler); const value = JSON.parse(response).value; const sessionId = value.sessionId; @@ -217,7 +216,8 @@ export class Chromium extends BrowserType { const disconnectFromSelenium = async () => { progress.log(` disconnecting from sessionId=${sessionId}`); - await fetchData({ + // Do not pass "progress" to disconnect even after the progress has aborted. + await fetchData(undefined, { url: hubUrl + 'session/' + sessionId, method: 'DELETE', headers, @@ -253,10 +253,9 @@ export class Chromium extends BrowserType { if (endpointURL.hostname === 'localhost' || endpointURL.hostname === '127.0.0.1') { const sessionInfoUrl = new URL(hubUrl).origin + '/grid/api/testsession?session=' + sessionId; try { - const sessionResponse = await fetchData({ + const sessionResponse = await fetchData(progress, { url: sessionInfoUrl, method: 'GET', - timeout: progress.timeUntilDeadline(), headers, }, seleniumErrorHandler); const proxyId = JSON.parse(sessionResponse).proxyId; @@ -387,10 +386,9 @@ async function urlToWSEndpoint(progress: Progress, endpointURL: string, headers: url.pathname += 'json/version/'; const httpURL = url.toString(); - const json = await fetchData({ + const json = await fetchData(progress, { url: httpURL, headers, - timeout: progress.timeUntilDeadline(), }, async (_, resp) => new Error(`Unexpected status ${resp.statusCode} when connecting to ${httpURL}.\n` + `This does not look like a DevTools server, try connecting via ws://.`) ); diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 17e6182714b7e..502d135c81cad 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -256,7 +256,7 @@ export class CRPage implements PageDelegate { } async takeScreenshot(progress: Progress, format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise { - const { visualViewport } = await this._mainFrameSession._client.send('Page.getLayoutMetrics'); + const { visualViewport } = await progress.race(this._mainFrameSession._client.send('Page.getLayoutMetrics')); if (!documentRect) { documentRect = { x: visualViewport.pageX + viewportRect!.x, @@ -274,8 +274,7 @@ export class CRPage implements PageDelegate { const deviceScaleFactor = this._browserContext._options.deviceScaleFactor || 1; clip.scale /= deviceScaleFactor; } - progress.throwIfAborted(); - const result = await this._mainFrameSession._client.send('Page.captureScreenshot', { format, quality, clip, captureBeyondViewport: !fitsViewport }); + const result = await progress.race(this._mainFrameSession._client.send('Page.captureScreenshot', { format, quality, clip, captureBeyondViewport: !fitsViewport })); return Buffer.from(result.data, 'base64'); } diff --git a/packages/playwright-core/src/server/chromium/videoRecorder.ts b/packages/playwright-core/src/server/chromium/videoRecorder.ts index 5b15cf5a20344..59dc50c837c07 100644 --- a/packages/playwright-core/src/server/chromium/videoRecorder.ts +++ b/packages/playwright-core/src/server/chromium/videoRecorder.ts @@ -42,7 +42,7 @@ export class VideoRecorder { if (!options.outputFile.endsWith('.webm')) throw new Error('File must have .webm extension'); - const controller = new ProgressController(serverSideCallMetadata(), page, 'strict'); + const controller = new ProgressController(serverSideCallMetadata(), page); controller.setLogName('browser'); return await controller.run(async progress => { const recorder = new VideoRecorder(page, ffmpegPath, progress); diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index e614aae6219ef..850a339564323 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -83,7 +83,7 @@ export class LocalUtilsDispatcher extends Dispatcher { - const controller = new ProgressController(metadata, this._object, 'strict'); + const controller = new ProgressController(metadata, this._object); return await controller.run(async progress => { const wsHeaders = { 'User-Agent': getUserAgent(), @@ -137,10 +137,9 @@ async function urlToWSEndpoint(progress: Progress, endpointURL: string): Promise if (!fetchUrl.pathname.endsWith('/')) fetchUrl.pathname += '/'; fetchUrl.pathname += 'json'; - const json = await fetchData({ + const json = await fetchData(progress, { url: fetchUrl.toString(), method: 'GET', - timeout: progress.timeUntilDeadline(), headers: { 'User-Agent': getUserAgent() }, }, async (params: HTTPRequestParams, response: http.IncomingMessage) => { return new Error(`Unexpected status ${response.statusCode} when connecting to ${fetchUrl.toString()}.\n` + diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 35ed3cabd2bcf..f1f234864dfe9 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -257,7 +257,7 @@ export class ElementHandle extends js.JSHandle { } async scrollIntoViewIfNeeded(metadata: CallMetadata, options: types.TimeoutOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run( progress => this._waitAndScrollIntoViewIfNeeded(progress, false /* waitForVisible */), options.timeout); @@ -336,7 +336,6 @@ export class ElementHandle extends js.JSHandle { const waitTime = [0, 20, 100, 100, 500]; while (true) { - progress.throwIfAborted(); if (retry) { progress.log(`retrying ${actionName} action${options.trial ? ' (trial run)' : ''}`); const timeout = waitTime[Math.min(retry - 1, waitTime.length - 1)]; @@ -426,7 +425,6 @@ export class ElementHandle extends js.JSHandle { // Best-effort scroll to make sure any iframes containing this element are scrolled // into view and visible, so they are not throttled. // See https://github.com/microsoft/playwright/issues/27196 for an example. - progress.throwIfAborted(); // Avoid action that has side-effects. await progress.race(doScrollIntoView().catch(() => {})); } @@ -448,7 +446,6 @@ export class ElementHandle extends js.JSHandle { await progress.race((options as any).__testHookAfterStable()); progress.log(' scrolling into view if needed'); - progress.throwIfAborted(); // Avoid action that has side-effects. const scrolled = await progress.race(doScrollIntoView()); if (scrolled !== 'done') return scrolled; @@ -494,7 +491,6 @@ export class ElementHandle extends js.JSHandle { const actionResult = await this._page.frameManager.waitForSignalsCreatedBy(progress, options.waitAfter === true, async () => { if ((options as any).__testHookBeforePointerAction) await progress.race((options as any).__testHookBeforePointerAction()); - progress.throwIfAborted(); // Avoid action that has side-effects. let restoreModifiers: types.KeyboardModifier[] | undefined; if (options && options.modifiers) restoreModifiers = await this._page.keyboard.ensureModifiers(progress, options.modifiers); @@ -538,7 +534,7 @@ export class ElementHandle extends js.JSHandle { } async hover(metadata: CallMetadata, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._hover(progress, options); @@ -551,7 +547,7 @@ export class ElementHandle extends js.JSHandle { } async click(metadata: CallMetadata, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._click(progress, { ...options, waitAfter: !options.noWaitAfter }); @@ -564,7 +560,7 @@ export class ElementHandle extends js.JSHandle { } async dblclick(metadata: CallMetadata, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._dblclick(progress, options); @@ -577,7 +573,7 @@ export class ElementHandle extends js.JSHandle { } async tap(metadata: CallMetadata, options: types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._tap(progress, options); @@ -590,7 +586,7 @@ export class ElementHandle extends js.JSHandle { } async selectOption(metadata: CallMetadata, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._selectOption(progress, elements, values, options); @@ -626,7 +622,7 @@ export class ElementHandle extends js.JSHandle { } async fill(metadata: CallMetadata, value: string, options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._fill(progress, value, options); @@ -648,7 +644,6 @@ export class ElementHandle extends js.JSHandle { } return injected.fill(node, value); }, { value, force: options.force })); - progress.throwIfAborted(); // Avoid action that has side-effects. if (result === 'needsinput') { if (value) await this._page.keyboard._insertText(progress, value); @@ -662,7 +657,7 @@ export class ElementHandle extends js.JSHandle { } async selectText(metadata: CallMetadata, options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const result = await this._retryAction(progress, 'selectText', async () => { if (!options.force) @@ -681,7 +676,7 @@ export class ElementHandle extends js.JSHandle { } async setInputFiles(metadata: CallMetadata, params: channels.ElementHandleSetInputFilesParams) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const inputFileItems = await progress.race(prepareFilesForUpload(this._frame, params)); await this._markAsTargetElement(progress); @@ -731,7 +726,7 @@ export class ElementHandle extends js.JSHandle { } async focus(metadata: CallMetadata): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); await controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._focus(progress); @@ -748,7 +743,7 @@ export class ElementHandle extends js.JSHandle { } async type(metadata: CallMetadata, text: string, options: { delay?: number } & types.TimeoutOptions & types.StrictOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._type(progress, text, options); @@ -767,7 +762,7 @@ export class ElementHandle extends js.JSHandle { } async press(metadata: CallMetadata, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & types.StrictOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this._markAsTargetElement(progress); const result = await this._press(progress, key, options); @@ -788,7 +783,7 @@ export class ElementHandle extends js.JSHandle { } async check(metadata: CallMetadata, options: { position?: types.Point } & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const result = await this._setChecked(progress, true, options); return assertDone(throwRetargetableDOMError(result)); @@ -796,7 +791,7 @@ export class ElementHandle extends js.JSHandle { } async uncheck(metadata: CallMetadata, options: { position?: types.Point } & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const result = await this._setChecked(progress, false, options); return assertDone(throwRetargetableDOMError(result)); @@ -832,7 +827,7 @@ export class ElementHandle extends js.JSHandle { } async screenshot(metadata: CallMetadata, options: ScreenshotOptions & types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run( progress => this._page.screenshotter.screenshotElement(progress, this, options), options.timeout); @@ -879,7 +874,7 @@ export class ElementHandle extends js.JSHandle { } async waitForElementState(metadata: CallMetadata, state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' | 'editable', options: types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const actionName = `wait for ${state}`; const result = await this._retryAction(progress, actionName, async () => { diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index d687571d47973..b0c6e769ba18d 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -156,7 +156,7 @@ export class Electron extends SdkObject { } async launch(metadata: CallMetadata, options: channels.ElectronLaunchParams): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { let app: ElectronApplication | undefined = undefined; // --remote-debugging-port=0 must be the last playwright's argument, loader.ts relies on it. diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 26339540fa015..288d2d91b1af7 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -201,7 +201,7 @@ export abstract class APIRequestContext extends SdkObject { const postData = serializePostData(params, headers); if (postData) setHeader(headers, 'content-length', String(postData.byteLength)); - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); const fetchResponse = await controller.run(progress => { return this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries); }, params.timeout); diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index a096946457b69..c282649e7726d 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -419,12 +419,12 @@ export class FFPage implements PageDelegate { height: viewportRect!.height, }; } - const { data } = await this._session.send('Page.screenshot', { + const { data } = await progress.race(this._session.send('Page.screenshot', { mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'), clip: documentRect, quality, omitDeviceScaleFactor: scale === 'css', - }); + })); return Buffer.from(data, 'base64'); } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 9e32a7d8370a2..33e738ac7ddf0 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -600,7 +600,7 @@ export class Frame extends SdkObject { } redirectNavigation(url: string, documentId: string, referer: string | undefined) { - const controller = new ProgressController(serverSideCallMetadata(), this, 'strict'); + const controller = new ProgressController(serverSideCallMetadata(), this); const data = { url, gotoPromise: controller.run(progress => this.gotoImpl(progress, url, { referer }), 0), @@ -610,7 +610,7 @@ export class Frame extends SdkObject { } async goto(metadata: CallMetadata, url: string, options: types.GotoOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => { const constructedNavigationURL = constructURLBasedOnBaseURL(this._page.browserContext._options.baseURL, url); return this.raceNavigationAction(progress, async () => this.gotoImpl(progress, constructedNavigationURL, options)); @@ -744,7 +744,7 @@ export class Frame extends SdkObject { } async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions, scope?: dom.ElementHandle): Promise | null> { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { if ((options as any).visibility) throw new Error('options.visibility is not supported, did you mean options.state?'); @@ -871,7 +871,7 @@ export class Frame extends SdkObject { } async setContent(metadata: CallMetadata, html: string, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { await this.raceNavigationAction(progress, async () => { const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; @@ -1142,21 +1142,21 @@ export class Frame extends SdkObject { } async click(metadata: CallMetadata, selector: string, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._click(progress, { ...options, waitAfter: !options.noWaitAfter }))); }, options.timeout); } async dblclick(metadata: CallMetadata, selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._dblclick(progress, options))); }, options.timeout); } async dragAndDrop(metadata: CallMetadata, source: string, target: string, options: types.DragActionOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); await controller.run(async progress => { dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, options.strict, !options.force /* performActionPreChecks */, async handle => { return handle._retryPointerAction(progress, 'move and down', false, async point => { @@ -1183,7 +1183,7 @@ export class Frame extends SdkObject { } async tap(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { if (!this._page.browserContext._options.hasTouch) throw new Error('The page does not support tap. Use hasTouch context option to enable touch support.'); @@ -1192,21 +1192,21 @@ export class Frame extends SdkObject { } async fill(metadata: CallMetadata, selector: string, value: string, options: types.TimeoutOptions & types.StrictOptions & { force?: boolean }) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._fill(progress, value, options))); }, options.timeout); } async focus(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); await controller.run(async progress => { dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._focus(progress))); }, options.timeout); } async blur(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); await controller.run(async progress => { dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._blur(progress))); }, options.timeout); @@ -1270,7 +1270,7 @@ export class Frame extends SdkObject { } async isVisible(metadata: CallMetadata, selector: string, options: types.StrictOptions = {}, scope?: dom.ElementHandle): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { progress.log(` checking visibility of ${this._asLocator(selector)}`); return await this.isVisibleInternal(progress, selector, options, scope); @@ -1315,14 +1315,14 @@ export class Frame extends SdkObject { } async hover(metadata: CallMetadata, selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._hover(progress, options))); }, options.timeout); } async selectOption(metadata: CallMetadata, selector: string, elements: dom.ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._selectOption(progress, elements, values, options)); }, options.timeout); @@ -1330,47 +1330,47 @@ export class Frame extends SdkObject { async setInputFiles(metadata: CallMetadata, selector: string, params: channels.FrameSetInputFilesParams): Promise { const inputFileItems = await prepareFilesForUpload(this, params); - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params.strict, true /* performActionPreChecks */, handle => handle._setInputFiles(progress, inputFileItems))); }, params.timeout); } async type(metadata: CallMetadata, selector: string, text: string, options: { delay?: number } & types.TimeoutOptions & types.StrictOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._type(progress, text, options))); }, options.timeout); } async press(metadata: CallMetadata, selector: string, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & types.StrictOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._press(progress, key, options))); }, options.timeout); } async check(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._setChecked(progress, true, options))); }, options.timeout); } async uncheck(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._setChecked(progress, false, options))); }, options.timeout); } async waitForTimeout(metadata: CallMetadata, timeout: number) { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => progress.wait(timeout)); } async ariaSnapshot(metadata: CallMetadata, selector: string, options: { forAI?: boolean } & types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => progress.race(handle.ariaSnapshot(options))); }, options.timeout); @@ -1391,7 +1391,7 @@ export class Frame extends SdkObject { const start = timeout > 0 ? monotonicTime() : 0; // Step 1: perform locator handlers checkpoint with a specified timeout. - await (new ProgressController(metadata, this, 'strict')).run(async progress => { + await (new ProgressController(metadata, this)).run(async progress => { progress.log(`${renderTitleForCall(metadata)}${timeout ? ` with timeout ${timeout}ms` : ''}`); if (selector) progress.log(`waiting for ${this._asLocator(selector)}`); @@ -1402,7 +1402,7 @@ export class Frame extends SdkObject { // Supports the case of `expect(locator).toBeVisible({ timeout: 1 })` // that should succeed when the locator is already visible. try { - const resultOneShot = await (new ProgressController(metadata, this, 'strict')).run(async progress => { + const resultOneShot = await (new ProgressController(metadata, this)).run(async progress => { return await this._expectInternal(progress, selector, options, lastIntermediateResult); }); if (resultOneShot.matches !== options.isNot) @@ -1420,7 +1420,7 @@ export class Frame extends SdkObject { return { matches: options.isNot, log: compressCallLog(metadata.log), timedOut: true, received: lastIntermediateResult.received }; // Step 3: auto-retry expect with increasing timeouts. Bounded by the total remaining time. - return await (new ProgressController(metadata, this, 'strict')).run(async progress => { + return await (new ProgressController(metadata, this)).run(async progress => { return await this.retryWithProgressAndTimeouts(progress, [100, 250, 500, 1000], async continuePolling => { await this._page.performActionPreChecks(progress); const { matches, received } = await this._expectInternal(progress, selector, options, lastIntermediateResult); @@ -1483,7 +1483,7 @@ export class Frame extends SdkObject { } async waitForFunctionExpression(metadata: CallMetadata, expression: string, isFunction: boolean | undefined, arg: any, options: types.WaitForFunctionOptions): Promise> { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => this.waitForFunctionExpressionImpl(progress, expression, isFunction, arg, options, 'main'), options.timeout); } @@ -1590,7 +1590,7 @@ export class Frame extends SdkObject { } private async _callOnElementOnceMatches(metadata: CallMetadata, selector: string, body: ElementCallback, taskData: T, options: types.TimeoutOptions & types.StrictOptions & { mainWorld?: boolean }, scope?: dom.ElementHandle): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { const callbackText = body.toString(); progress.log(`waiting for ${this._asLocator(selector)}`); diff --git a/packages/playwright-core/src/server/input.ts b/packages/playwright-core/src/server/input.ts index 99c7fea2dc7cc..c5c34acb852aa 100644 --- a/packages/playwright-core/src/server/input.ts +++ b/packages/playwright-core/src/server/input.ts @@ -55,7 +55,7 @@ export class Keyboard { } async down(metadata: CallMetadata, key: string) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._down(progress, key)); } @@ -82,7 +82,7 @@ export class Keyboard { } async up(metadata: CallMetadata, key: string) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._up(progress, key)); } @@ -95,7 +95,7 @@ export class Keyboard { } async insertText(metadata: CallMetadata, text: string) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._insertText(progress, text)); } @@ -104,7 +104,7 @@ export class Keyboard { } async type(metadata: CallMetadata, text: string, options?: { delay?: number }) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._type(progress, text, options)); } @@ -122,7 +122,7 @@ export class Keyboard { } async press(metadata: CallMetadata, key: string, options: { delay?: number }) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._press(progress, key, options)); } @@ -214,7 +214,7 @@ export class Mouse { } async move(metadata: CallMetadata, x: number, y: number, options: { steps?: number, forClick?: boolean }) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._move(progress, x, y, options)); } @@ -232,7 +232,7 @@ export class Mouse { } async down(metadata: CallMetadata, options: { button?: types.MouseButton, clickCount?: number }) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._down(progress, options)); } @@ -244,7 +244,7 @@ export class Mouse { } async up(metadata: CallMetadata, options: { button?: types.MouseButton, clickCount?: number }) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._up(progress, options)); } @@ -256,7 +256,7 @@ export class Mouse { } async click(metadata: CallMetadata, x: number, y: number, options: { delay?: number, button?: types.MouseButton, clickCount?: number }) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._click(progress, x, y, options)); } @@ -283,7 +283,7 @@ export class Mouse { } async wheel(metadata: CallMetadata, deltaX: number, deltaY: number) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(async progress => { await this._raw.wheel(progress, this._x, this._y, this._buttons, this._keyboard._modifiers(), deltaX, deltaY); }); @@ -364,7 +364,7 @@ export class Touchscreen { } async tap(metadata: CallMetadata, x: number, y: number) { - const controller = new ProgressController(metadata, this._page, 'strict'); + const controller = new ProgressController(metadata, this._page); return controller.run(progress => this._tap(progress, x, y)); } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 42ad32e9c020e..59d5bce46c03b 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -368,7 +368,7 @@ export class Page extends SdkObject { } async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => this.mainFrame().raceNavigationAction(progress, async () => { // Note: waitForNavigation may fail before we get response to reload(), // so we should await it immediately. @@ -382,7 +382,7 @@ export class Page extends SdkObject { } async goBack(metadata: CallMetadata, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => this.mainFrame().raceNavigationAction(progress, async () => { // Note: waitForNavigation may fail before we get response to goBack, // so we should catch it immediately. @@ -404,7 +404,7 @@ export class Page extends SdkObject { } async goForward(metadata: CallMetadata, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(progress => this.mainFrame().raceNavigationAction(progress, async () => { // Note: waitForNavigation may fail before we get response to goForward, // so we should catch it immediately. @@ -451,9 +451,7 @@ export class Page extends SdkObject { async performActionPreChecks(progress: Progress) { await this._performWaitForNavigationCheck(progress); - progress.throwIfAborted(); await this._performLocatorHandlersCheckpoint(progress); - progress.throwIfAborted(); // Wait once again, just in case a locator handler caused a navigation. await this._performWaitForNavigationCheck(progress); } @@ -489,7 +487,6 @@ export class Page extends SdkObject { ++this._locatorHandlerRunningCounter; progress.log(` found ${asLocator(this.browserContext._browser.sdkLanguage(), handler.selector)}, intercepting action to run the handler`); const promise = handler.resolved.then(async () => { - progress.throwIfAborted(); if (!handler.noWaitAfter) { progress.log(` locator handler has finished, waiting for ${asLocator(this.browserContext._browser.sdkLanguage(), handler.selector)} to be hidden`); await this.mainFrame().waitForSelectorInternal(progress, handler.selector, false, { state: 'hidden' }); @@ -599,7 +596,7 @@ export class Page extends SdkObject { }; const comparator = getComparator('image/png'); - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); if (!options.expected && options.isNot) return { errorMessage: '"not" matcher requires expected result' }; try { @@ -812,7 +809,7 @@ export class Page extends SdkObject { } snapshotForAI(metadata: CallMetadata): Promise { - const controller = new ProgressController(metadata, this, 'strict'); + const controller = new ProgressController(metadata, this); return controller.run(async progress => { this.lastSnapshotFrameIds = []; const snapshot = await snapshotFrameForAI(progress, this.mainFrame(), 0, this.lastSnapshotFrameIds); diff --git a/packages/playwright-core/src/server/progress.ts b/packages/playwright-core/src/server/progress.ts index e01576bbc8a00..4e4c8a0ac4f1e 100644 --- a/packages/playwright-core/src/server/progress.ts +++ b/packages/playwright-core/src/server/progress.ts @@ -15,17 +15,30 @@ */ import { TimeoutError } from './errors'; -import { assert, monotonicTime } from '../utils'; +import { assert } from '../utils'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation'; import type { LogName } from './utils/debugLogger'; +// Most server operations are run inside a Progress instance. +// Each method that takes a Progress must result in one of the three outcomes: +// - It finishes successfully, returning a value, before the Progress is aborted. +// - It throws some error, before the Progress is aborted. +// - It throws the Progress's aborted error, because the Progress was aborted before +// the method could finish. +// As a rule of thumb, the above is achieved by: +// - Passing the Progress instance when awaiting other methods. +// - Using `progress.race()` when awaiting other methods that do not take a Progress argument. +// In this case, it is important that awaited method has no side effects, for example +// it is a read-only browser protocol call. +// - In rare cases, when the awaited method does not take a Progress argument, +// but it does have side effects such as creating a page - a proper cleanup +// must be taken in case Progress is aborted before the awaited method finishes. +// That's usually done by `progress.raceWithCleanup()` or `progress.cleanupWhenAborted()`. export interface Progress { log(message: string): void; - timeUntilDeadline(): number; - cleanupWhenAborted(cleanup: () => any): void; - throwIfAborted(): void; + cleanupWhenAborted(cleanup: (error: Error | undefined) => any): void; race(promise: Promise | Promise[]): Promise; raceWithCleanup(promise: Promise, cleanup: (result: T) => any): Promise; wait(timeout: number): Promise; @@ -37,7 +50,7 @@ export class ProgressController { private _donePromise = new ManualPromise(); // Cleanups to be run only in the case of abort. - private _cleanups: (() => any)[] = []; + private _cleanups: ((error: Error | undefined) => any)[] = []; // Lenient mode races against the timeout. This guarantees that timeout is respected, // but may have some work being done after the timeout due to parallel control flow. @@ -48,13 +61,12 @@ export class ProgressController { private _logName: LogName; private _state: 'before' | 'running' | { error: Error } | 'finished' = 'before'; - private _deadline: number = 0; readonly metadata: CallMetadata; readonly instrumentation: Instrumentation; readonly sdkObject: SdkObject; - constructor(metadata: CallMetadata, sdkObject: SdkObject, strictMode?: 'strict') { - this._strictMode = strictMode === 'strict'; + constructor(metadata: CallMetadata, sdkObject: SdkObject) { + this._strictMode = !process.env.PLAYWRIGHT_LEGACY_TIMEOUTS; this.metadata = metadata; this.sdkObject = sdkObject; this.instrumentation = sdkObject.instrumentation; @@ -77,8 +89,6 @@ export class ProgressController { } async run(task: (progress: Progress) => Promise, timeout?: number): Promise { - this._deadline = timeout ? monotonicTime() + timeout : 0; - assert(this._state === 'before'); this._state = 'running'; this.sdkObject.attribution.context?._activeProgressControllers.add(this); @@ -90,16 +100,17 @@ export class ProgressController { // Note: we might be sending logs after progress has finished, for example browser logs. this.instrumentation.onCallLog(this.sdkObject, this.metadata, this._logName, message); }, - timeUntilDeadline: () => this._deadline ? this._deadline - monotonicTime() : 2147483647, // 2^31-1 safe setTimeout in Node. - cleanupWhenAborted: (cleanup: () => any) => { + cleanupWhenAborted: (cleanup: (error: Error | undefined) => any) => { + if (this._strictMode) { + if (this._state !== 'running') + throw new Error('Internal error: cannot register cleanup after operation has finished.'); + this._cleanups.push(cleanup); + return; + } if (this._state === 'running') this._cleanups.push(cleanup); else - runCleanup(cleanup); - }, - throwIfAborted: () => { - if (typeof this._state === 'object') - throw this._state.error; + runCleanup(typeof this._state === 'object' ? this._state.error : undefined, cleanup); }, metadata: this.metadata, race: (promise: Promise | Promise[]) => { @@ -119,13 +130,17 @@ export class ProgressController { }, }; - const timeoutError = new TimeoutError(`Timeout ${timeout}ms exceeded.`); - const timer = setTimeout(() => { - if (this._state === 'running') { - this._state = { error: timeoutError }; - this._forceAbortPromise.reject(timeoutError); - } - }, progress.timeUntilDeadline()); + let timer: NodeJS.Timeout | undefined; + if (timeout) { + const timeoutError = new TimeoutError(`Timeout ${timeout}ms exceeded.`); + timer = setTimeout(() => { + if (this._state === 'running') { + this._state = { error: timeoutError }; + this._forceAbortPromise.reject(timeoutError); + } + }, Math.min(timeout, 2147483647)); // 2^31-1 safe setTimeout in Node. + } + try { const promise = task(progress); const result = this._strictMode ? await promise : await Promise.race([promise, this._forceAbortPromise]); @@ -133,7 +148,7 @@ export class ProgressController { return result; } catch (error) { this._state = { error }; - await Promise.all(this._cleanups.splice(0).map(runCleanup)); + await Promise.all(this._cleanups.splice(0).map(cleanup => runCleanup(error, cleanup))); throw error; } finally { this.sdkObject.attribution.context?._activeProgressControllers.delete(this); @@ -143,9 +158,9 @@ export class ProgressController { } } -async function runCleanup(cleanup: () => any) { +async function runCleanup(error: Error | undefined, cleanup: (error: Error | undefined) => any) { try { - await cleanup(); + await cleanup(error); } catch (e) { } } diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index f9aea392e86da..5f69e225f2cfc 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -121,7 +121,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { timeout: 0, } }); - const controller = new ProgressController(serverSideCallMetadata(), context._browser, 'strict'); + const controller = new ProgressController(serverSideCallMetadata(), context._browser); await controller.run(async progress => { await context._browser._defaultContext!._loadDefaultContextAsIs(progress); }); diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 4a0e7cb989be7..433a6f35f6ca9 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -1197,7 +1197,7 @@ export class Registry { private async _installMSEdgeChannel(channel: 'msedge'|'msedge-beta'|'msedge-dev', scripts: Record<'linux' | 'darwin' | 'win32', string>) { const scriptArgs: string[] = []; if (process.platform !== 'linux') { - const products = lowercaseAllKeys(JSON.parse(await fetchData({ url: 'https://edgeupdates.microsoft.com/api/products' }))); + const products = lowercaseAllKeys(JSON.parse(await fetchData(undefined, { url: 'https://edgeupdates.microsoft.com/api/products' }))); const productName = { 'msedge': 'Stable', diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index c0619d95e429b..9c59d0e6f2c97 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -48,13 +48,12 @@ function downloadFile(options: DownloadParams): Promise { let totalBytes = 0; const promise = new ManualPromise(); - httpRequest({ url: options.url, headers: { 'User-Agent': options.userAgent, }, - timeout: options.socketTimeout, + socketTimeout: options.socketTimeout, }, response => { log(`-- response status code: ${response.statusCode}`); if (response.statusCode !== 200) { diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 306857ee2836e..ea8b09e4864a1 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -303,7 +303,7 @@ export class Screenshotter { const cleanupHighlight = await this._maskElements(progress, options); const quality = format === 'jpeg' ? options.quality ?? 80 : undefined; - const buffer = await progress.race(this._page.delegate.takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, options.scale || 'device')); + const buffer = await this._page.delegate.takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, options.scale || 'device'); await cleanupHighlight(); if (shouldSetDefaultBackground) diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index c202d65e6fc38..68a5fdd5c76b7 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -180,7 +180,7 @@ export async function openTraceViewerApp(url: string, browserName: string, optio }, }); - const controller = new ProgressController(serverSideCallMetadata(), context._browser, 'strict'); + const controller = new ProgressController(serverSideCallMetadata(), context._browser); await controller.run(async progress => { await context._browser._defaultContext!._loadDefaultContextAsIs(progress); }); diff --git a/packages/playwright-core/src/server/transport.ts b/packages/playwright-core/src/server/transport.ts index 30e87ae0c2eca..c2fefad6d2070 100644 --- a/packages/playwright-core/src/server/transport.ts +++ b/packages/playwright-core/src/server/transport.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { DEFAULT_PLAYWRIGHT_TIMEOUT, makeWaitForNextTask } from '../utils'; +import { makeWaitForNextTask } from '../utils'; import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from './utils/happyEyeballs'; import { ws } from '../utilsBundle'; @@ -133,8 +133,6 @@ export class WebSocketTransport implements ConnectionTransport { this._logUrl = logUrl; this._ws = new ws(url, [], { maxPayload: 256 * 1024 * 1024, // 256Mb, - // Prevent internal http client error when passing negative timeout. - handshakeTimeout: Math.max(progress?.timeUntilDeadline() ?? DEFAULT_PLAYWRIGHT_TIMEOUT, 1), headers: options.headers, followRedirects: options.followRedirects, agent: (/^(https|wss):\/\//.test(url)) ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent, diff --git a/packages/playwright-core/src/server/utils/network.ts b/packages/playwright-core/src/server/utils/network.ts index 5afa861280352..d12b72a1c389e 100644 --- a/packages/playwright-core/src/server/utils/network.ts +++ b/packages/playwright-core/src/server/utils/network.ts @@ -25,19 +25,20 @@ import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from './happyEyeballs import type net from 'net'; import type { ProxySettings } from '../types'; +import type { Progress } from '../progress'; export type HTTPRequestParams = { url: string, method?: string, headers?: http.OutgoingHttpHeaders, data?: string | Buffer, - timeout?: number, rejectUnauthorized?: boolean, + socketTimeout?: number, }; export const NET_DEFAULT_TIMEOUT = 30_000; -export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.IncomingMessage) => void, onError: (error: Error) => void) { +export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.IncomingMessage) => void, onError: (error: Error) => void): { cancel(error: Error | undefined): void } { const parsedUrl = url.parse(params.url); let options: https.RequestOptions = { ...parsedUrl, @@ -48,8 +49,6 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco if (params.rejectUnauthorized !== undefined) options.rejectUnauthorized = params.rejectUnauthorized; - const timeout = params.timeout ?? NET_DEFAULT_TIMEOUT; - const proxyURL = getProxyForUrl(params.url); if (proxyURL) { const parsedProxyURL = url.parse(proxyURL); @@ -69,13 +68,14 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco } } + let cancelRequest: (e: Error | undefined) => void; const requestCallback = (res: http.IncomingMessage) => { const statusCode = res.statusCode || 0; if (statusCode >= 300 && statusCode < 400 && res.headers.location) { // Close the original socket before following the redirect. Otherwise // it may stay idle and cause a timeout error. request.destroy(); - httpRequest({ ...params, url: new URL(res.headers.location, params.url).toString() }, onResponse, onError); + cancelRequest = httpRequest({ ...params, url: new URL(res.headers.location, params.url).toString() }, onResponse, onError).cancel; } else { onResponse(res); } @@ -84,23 +84,20 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco https.request(options, requestCallback) : http.request(options, requestCallback); request.on('error', onError); - if (timeout !== undefined) { - const rejectOnTimeout = () => { - onError(new Error(`Request to ${params.url} timed out after ${timeout}ms`)); + if (params.socketTimeout !== undefined) { + request.setTimeout(params.socketTimeout, () => { + onError(new Error(`Request to ${params.url} timed out after ${params.socketTimeout}ms`)); request.abort(); - }; - if (timeout <= 0) { - rejectOnTimeout(); - return; - } - request.setTimeout(timeout, rejectOnTimeout); + }); } + cancelRequest = e => request.destroy(e); request.end(params.data); + return { cancel: e => cancelRequest(e) }; } -export function fetchData(params: HTTPRequestParams, onError?: (params: HTTPRequestParams, response: http.IncomingMessage) => Promise): Promise { - return new Promise((resolve, reject) => { - httpRequest(params, async response => { +export function fetchData(progress: Progress | undefined, params: HTTPRequestParams, onError?: (params: HTTPRequestParams, response: http.IncomingMessage) => Promise): Promise { + const promise = new Promise((resolve, reject) => { + const { cancel } = httpRequest(params, async response => { if (response.statusCode !== 200) { const error = onError ? await onError(params, response) : new Error(`fetch failed: server returned code ${response.statusCode}. URL: ${params.url}`); reject(error); @@ -111,7 +108,9 @@ export function fetchData(params: HTTPRequestParams, onError?: (params: HTTPRequ response.on('error', (error: any) => reject(error)); response.on('end', () => resolve(body)); }, reject); + progress?.cleanupWhenAborted(cancel); }); + return progress ? progress.race(promise) : promise; } function shouldBypassProxy(url: URL, bypass?: string): boolean { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 99707016843c5..36f66daaa7c68 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -869,7 +869,7 @@ export class WKPage implements PageDelegate { const omitDeviceScaleFactor = scale === 'css'; this.validateScreenshotDimension(rect.width, omitDeviceScaleFactor); this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor); - const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport', omitDeviceScaleFactor }); + const result = await progress.race(this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport', omitDeviceScaleFactor })); const prefix = 'data:image/png;base64,'; let buffer: Buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); if (format === 'jpeg') diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index 1eb1342e8d5b5..9efe3b0f98de4 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -1054,3 +1054,14 @@ test('should refuse connecting when versions do not match', async ({ connect, ch expect(error.message).toContain('server version: v1.2'); expect(error.message).toContain('client version: v' + getPlaywrightVersion(true)); }); + +test('should timeout after redirect when connecting over http', async ({ connect, server }) => { + server.setRedirect('/connect/json', '/connect/slow'); + let aborted = false; + server.setRoute('/connect/slow', (req, res) => { + req.socket.on('close', () => aborted = true); + }); + const error = await connect(`${server.PREFIX}/connect`, { timeout: 2000, headers: { 'Connection': 'Close' } }).catch(e => e); + expect(error.message).toContain('Timeout 2000ms exceeded.'); + await expect.poll(() => aborted).toBe(true); +}); From 5013e2c1c8526a41ecb03b324a5491da7b829ec5 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 23 Jun 2025 16:35:56 +0200 Subject: [PATCH 098/222] test: chromium tracing test rebase (#36403) --- tests/library/chromium/tracing.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/library/chromium/tracing.spec.ts b/tests/library/chromium/tracing.spec.ts index 4787fdefa8c3c..3baac85902a0b 100644 --- a/tests/library/chromium/tracing.spec.ts +++ b/tests/library/chromium/tracing.spec.ts @@ -54,7 +54,11 @@ it('should run with custom categories if provided', async ({ browser }, testInfo await browser.stopTracing(); const traceJson = JSON.parse(fs.readFileSync(outputTraceFile).toString()); - expect(traceJson.traceEvents.filter(event => event.cat === 'disabled-by-default-cc.debug').length).toBeGreaterThan(0); + expect( + // NOTE: trace-config is deprecated as per http://crrev.com/c/6628182 + traceJson.metadata['trace-config']?.includes('disabled-by-default-cc.debug') || + traceJson.traceEvents.filter(event => event.cat === 'disabled-by-default-cc.debug').length > 0 + ).toBe(true); await page.close(); }); From a5b68a5c0e87ff2bbd0582cb39f4d150108340ab Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 23 Jun 2025 17:25:43 +0200 Subject: [PATCH 099/222] devops: remove redundant scripts (#36408) --- utils/check_chromium_cdn.js | 196 ------------------------ utils/doclint/generateJavaSnippets.js | 154 ------------------- utils/doclint/generatePythonSnippets.js | 120 --------------- utils/doclint/since.js | 81 ---------- utils/draft_release_notes.sh | 39 ----- utils/limits.sh | 2 - utils/list_closed_issues.sh | 25 --- utils/ts_to_java.js | 165 -------------------- 8 files changed, 782 deletions(-) delete mode 100755 utils/check_chromium_cdn.js delete mode 100644 utils/doclint/generateJavaSnippets.js delete mode 100644 utils/doclint/generatePythonSnippets.js delete mode 100644 utils/doclint/since.js delete mode 100755 utils/draft_release_notes.sh delete mode 100755 utils/limits.sh delete mode 100755 utils/list_closed_issues.sh delete mode 100644 utils/ts_to_java.js diff --git a/utils/check_chromium_cdn.js b/utils/check_chromium_cdn.js deleted file mode 100755 index c509624152dcd..0000000000000 --- a/utils/check_chromium_cdn.js +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright 2017 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const assert = require('assert'); -const https = require('https'); -const util = require('util'); -const URL = require('url'); -const SUPPORTER_PLATFORMS = ['linux', 'mac', 'win64']; - -const colors = { - reset: '\x1b[0m', - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m' -}; - -class Table { - /** - * @param {!Array} columnWidths - */ - constructor(columnWidths) { - this.widths = columnWidths; - } - - /** - * @param {!Array} values - */ - drawRow(values) { - assert(values.length === this.widths.length); - let row = ''; - for (let i = 0; i < values.length; ++i) - row += padCenter(values[i], this.widths[i]); - console.log(row); - } -} - -if (process.argv.length === 2) { - checkOmahaProxyAvailability(); - return; -} -if (process.argv.length !== 4) { - console.log(` - Usage: node check_revisions.js [fromRevision] [toRevision] - -This script checks availability of different prebuild chromium revisions. -Running command without arguments will check against omahaproxy revisions.`); - return; -} - -const fromRevision = parseInt(process.argv[2], 10); -const toRevision = parseInt(process.argv[3], 10); -checkRangeAvailability(fromRevision, toRevision, false /* stopWhenAllAvailable */); - -async function checkOmahaProxyAvailability() { - const lastchanged = (await Promise.all([ - fetch('https://storage.googleapis.com/chromium-browser-snapshots/Mac/LAST_CHANGE'), - fetch('https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/LAST_CHANGE'), - fetch('https://storage.googleapis.com/chromium-browser-snapshots/Win/LAST_CHANGE'), - fetch('https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/LAST_CHANGE'), - ])).map(s => parseInt(s, 10)); - const from = Math.max(...lastchanged); - checkRangeAvailability(from, 0, true /* stopWhenAllAvailable */); -} - -/** - * @param {number} fromRevision - * @param {number} toRevision - * @param {boolean} stopWhenAllAvailable - */ -async function checkRangeAvailability(fromRevision, toRevision, stopWhenAllAvailable) { - const table = new Table([15, 7, 7, 7]); - table.drawRow([''].concat(SUPPORTER_PLATFORMS)); - const inc = fromRevision < toRevision ? 1 : -1; - for (let revision = fromRevision; revision !== toRevision; revision += inc) { - const allAvailable = await checkAndDrawRevisionAvailability(table, 'chromium', revision); - if (allAvailable && stopWhenAllAvailable) - break; - } -} - -async function canDownload(revision, platform) { - const serverHost = 'https://storage.googleapis.com'; - const urlTemplate = new Map([ - ['linux', '%s/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip'], - ['mac', '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip'], - ['win64', '%s/chromium-browser-snapshots/Win_x64/%d/chrome-win.zip'], - ]).get(platform); - assert(urlTemplate, `ERROR: Playwright does not support ${platform}`); - const url = util.format(urlTemplate, serverHost, revision); - return await headRequest(url); -} - -/** - * @param {!Table} table - * @param {string} name - * @param {number} revision - * @return {boolean} - */ -async function checkAndDrawRevisionAvailability(table, name, revision) { - const promises = SUPPORTER_PLATFORMS.map(platform => canDownload(revision, platform)); - const availability = await Promise.all(promises); - const allAvailable = availability.every(e => !!e); - const values = [name + ' ' + (allAvailable ? colors.green + revision + colors.reset : revision)]; - for (let i = 0; i < availability.length; ++i) { - const decoration = availability[i] ? '+' : '-'; - const color = availability[i] ? colors.green : colors.red; - values.push(color + decoration + colors.reset); - } - table.drawRow(values); - return allAvailable; -} - -/** - * @param {string} url - * @return {!Promise} - */ -function fetch(url) { - let resolve; - const promise = new Promise(x => resolve = x); - https.get(url, response => { - if (response.statusCode !== 200) { - resolve(null); - return; - } - let body = ''; - response.on('data', function(chunk){ - body += chunk; - }); - response.on('end', function(){ - resolve(body); - }); - }).on('error', function(e){ - console.error('Error fetching json: ' + e); - resolve(null); - }); - return promise; -} - -/** - * @param {number} size - * @return {string} - */ -function spaceString(size) { - return new Array(size).fill(' ').join(''); -} - -/** - * @param {string} text - * @return {string} - */ -function filterOutColors(text) { - for (const colorName in colors) { - const color = colors[colorName]; - text = text.replace(color, ''); - } - return text; -} - -/** - * @param {string} text - * @param {number} length - * @return {string} - */ -function padCenter(text, length) { - const printableCharacters = filterOutColors(text); - if (printableCharacters.length >= length) - return text; - const left = Math.floor((length - printableCharacters.length) / 2); - const right = Math.ceil((length - printableCharacters.length) / 2); - return spaceString(left) + text + spaceString(right); -} - -async function headRequest(url) { - return new Promise(resolve => { - let options = URL.parse(url); - options.method = 'HEAD'; - const request = https.request(options, res => resolve(res.statusCode === 200)); - request.on('error', error => resolve(false)); - request.end(); - }); -} - diff --git a/utils/doclint/generateJavaSnippets.js b/utils/doclint/generateJavaSnippets.js deleted file mode 100644 index 097381131728e..0000000000000 --- a/utils/doclint/generateJavaSnippets.js +++ /dev/null @@ -1,154 +0,0 @@ -// @ts-check - -const fs = require("fs"); -const md = require("../markdown"); - - -/** - * @param {string[]} input - */ -function transformValue(input) { - const out = []; - const suffix = []; - for (let line of input) { - let match = line.match(/const { (\w+) } = require\('playwright'\);/); - if (match) { - out.push('import com.microsoft.playwright.*;'); - out.push(''); - out.push('public class Example {'); - out.push(' public static void main(String[] args) {'); - out.push(' try (Playwright playwright = Playwright.create()) {'); - out.push(` BrowserType ${match[1]} = playwright.${match[1]}();`); - suffix.push(' }'); - suffix.push(' }'); - suffix.push('}'); - continue; - } - if (line.trim() === '(async () => {' || line.trim() === '})();') - continue; - if (!line) - continue; - if (line.trim() === '}') - continue; - - // Remove await/Promise.all - line = line.replace(/const \[(.+)\] = await Promise.all\(\[/g, '$1 ='); - line = line.replace(/Promise\.all\(\[/g, ''); - line = line.replace(/await /g, ''); - - // Rename some methods - line = line.replace(/\.goto\(/g, '.navigate('); - line = line.replace(/\.continue\(/g, '.resume('); - line = line.replace(/\.\$eval\(/g, '.evalOnSelector('); - line = line.replace(/\.\$\$eval\(/g, '.evalOnSelectorAll('); - line = line.replace(/\.\$\(/g, '.querySelector('); - line = line.replace(/\.\$\$\(/g, '.querySelectorAll('); - - line = line.replace(/console.log/g, 'System.out.println'); - - line = line.replace(/page.evaluate\((\(\) => [^\)]+)\)/g, 'page.evaluate("$1")'); - - // Convert properties to methods - line = line.replace(/\.keyboard\./g, '.keyboard().'); - line = line.replace(/\.mouse\./g, '.mouse().'); - line = line.replace(/\.coverage\./g, '.coverage().'); - line = line.replace(/\.accessibility\./g, '.accessibility().'); - line = line.replace(/\.chromium\./g, '.chromium().'); - line = line.replace(/\.webkit\./g, '.webkit().'); - line = line.replace(/\.firefox\./g, '.firefox().'); - line = line.replace(/\.length/g, '.size()'); - - // JUnit asserts - line = line.replace(/expect\((.+)\).toBeTruthy\(\);/g, 'assertNotNull($1);'); - line = line.replace(/expect\(error.message\)\.toContain\((.+)\);/g, 'assertTrue(e.getMessage().contains($1));'); - line = line.replace(/expect\((.+)\)\.toContain\((.+)\);/g, 'assertTrue($1.contains($2));'); - line = line.replace(/expect\((.+)\)\.toBe\(null\);/g, 'assertNull($1);'); - line = line.replace(/expect\((.+)\)\.not.toBe\(null\);/g, 'assertNotNull($1);'); - line = line.replace(/expect\((.+)\)\.toBe\(true\);/g, 'assertTrue($1);'); - line = line.replace(/expect\((.+)\)\.toBe\((.+)\);/g, 'assertEquals($2, $1);'); - line = line.replace(/expect\((.+)\)\.toEqual\(\[(.+)\]\);/g, 'assertEquals(Arrays.asList($2), $1);'); - line = line.replace(/expect\((.+)\)\.toEqual\((.+)\);/g, 'assertEquals($2, $1);'); - - line = line.replace(/\[('[^']+')\]/g, '.get("$1")'); - line = line.replace(/.push\(/g, '.add('); - - // Define common types - line = line.replace(/const browser = /g, 'Browser browser = '); - line = line.replace(/const context = /g, 'BrowserContext context = '); - line = line.replace(/const page = /g, 'Page page = '); - line = line.replace(/const newPage = /g, 'Page newPage = '); - line = line.replace(/const button/g, 'ElementHandle button'); - line = line.replace(/const result = /g, 'Object result = '); - line = line.replace(/const response = /g, 'Response response = '); - line = line.replace(/const request = /g, 'Request request = '); - line = line.replace(/const requests = \[\];/g, 'List requests = new ArrayList<>();'); - line = line.replace(/const snapshot = page.accessibility/g, 'String snapshot = page.accessibility'); - line = line.replace(/snapshot\.children\./g, 'snapshot.children().'); - line = line.replace(/const (.+) = \[\];/g, 'List<> $1 = new ArrayList<>();'); - line = line.replace(/const (\w+ = .+evalOnSelector)/g, 'Object $1'); - line = line.replace(/const (\w+ = .+querySelector)/g, 'ElementHandle $1'); - line = line.replace(/const (.+= page.waitForNavigation)/g, 'Response $1'); - line = line.replace(/const messages = \[\]/g, 'List messages = new ArrayList<>()'); - line = line.replace(/const frame = /g, 'Frame frame = '); - line = line.replace(/const elementHandle = (.+)/g, 'JSHandle jsHandle = $1\n ElementHandle elementHandle = jsHandle.asElement();\n'); - line = line.replace(/const (\w+ = \w+\.boundingBox)/g, 'BoundingBox $1'); - line = line.replace(/setViewportSize\({ width: (\d+), height: (\d+) }\)/g, 'setViewportSize($1, $2)'); - line = line.replace(/\.on\('([^']+)'/g, (match, p1, offset, string) => `.on${toTitleCase(p1)}(`); - line = line.replace(/\.waitForEvent\('([^']+)'/g, (match, p1, offset, string) => `page.waitFor${toTitleCase(p1)}(() -> {})`); - - line = line.replace(/[`']/g, '"'); - - out.push(line) - } - return [...out, ...suffix].join("\n"); -} - -/** - * @param {string} name - */ -function toTitleCase(name) { - return name[0].toUpperCase() + name.substring(1); -} - -/** - * @param {md.MarkdownNode} node - */ -function generateComment(node) { - const commentNode = md.clone(node) - commentNode.codeLang = 'java'; - commentNode.lines = ['// FIXME', ...transformValue(node.lines).split("\n")]; - return commentNode; -} - -/** - * - * @param {md.MarkdownNode[]} spec - */ -function multiplyComment(spec) { - const children = [] - for (const node of (spec || [])) { - if (node.codeLang === "js") - children.push(node, generateComment(node)); - else - children.push(node); - } - return children; -} - -for (const name of fs.readdirSync("docs/src")) { - if (!name.endsWith(".md")) - continue; - if (name.includes('android')) - continue; - const inputFile = `docs/src/${name}`; - const fileline = fs.readFileSync(inputFile).toString(); - const nodes = md.parse(fileline); - - md.visitAll(nodes, node => { - if (node.children) - node.children = multiplyComment(node.children); - }); - - const out = md.render(nodes, 120); - fs.writeFileSync(inputFile, out); -} diff --git a/utils/doclint/generatePythonSnippets.js b/utils/doclint/generatePythonSnippets.js deleted file mode 100644 index 98275966cced0..0000000000000 --- a/utils/doclint/generatePythonSnippets.js +++ /dev/null @@ -1,120 +0,0 @@ -// @ts-check - -const fs = require("fs"); -const md = require("../markdown"); - - -/** - * @param {string[]} input - * @param {boolean} isSync - */ -function transformValue(input, isSync) { - const out = []; - const suffix = []; - for (let line of input) { - let match = line.match(/const { (\w+) } = require\('playwright'\);/); - if (match) { - if (isSync) { - out.push('from playwright.sync_api import sync_playwright, Playwright'); - out.push(''); - out.push('def run(playwright: Playwright):'); - out.push(` ${match[1]} = playwright.${match[1]}`); - suffix.push(``); - suffix.push(`with sync_playwright() as playwright:`); - suffix.push(` run(playwright)`); - } else { - out.push('import asyncio'); - out.push('from playwright.async_api import async_playwright, Playwright'); - out.push(''); - out.push('async def run(playwright: Playwright):'); - out.push(` ${match[1]} = playwright.${match[1]}`); - suffix.push(``); - suffix.push(`async def main():`); - suffix.push(` async with async_playwright() as playwright:`); - suffix.push(` await run(playwright)`); - suffix.push(`asyncio.run(main())`); - } - continue; - } - if (line.trim() === '(async () => {' || line.trim() === '})();') - continue; - if (!line) - continue; - if (line.trim() === '}') - continue; - line = line.replace(/\$\$eval/g, 'eval_on_selector_all'); - line = line.replace(/\$eval/g, 'eval_on_selector'); - line = line.replace(/\$\$/g, 'query_selector_all'); - line = line.replace(/\$/g, 'query_selector'); - line = line.replace(/([a-zA-Z$]+)/g, (match, p1) => toSnakeCase(p1)); - line = line.replace(/try {/, 'try:'); - line = line.replace(/async \(([^)]+)\) => {/, 'lambda $1:'); - line = line.replace(/} catch \(e\) {/, 'except Error as e:'); - line = line.replace(/;$/, ''); - line = line.replace(/ /g, ' '); - line = line.replace(/'/g, '"'); - line = line.replace(/const /g, ''); - line = line.replace(/{\s*(\w+):\s*([^} ]+)\s*}/, "$1=$2"); - line = line.replace(/\/\/ /, "# "); - line = line.replace(/\(\) => /, 'lambda: '); - line = line.replace(/console.log/, 'print'); - line = line.replace(/function /, 'def '); - line = line.replace(/{$/, ''); - if (isSync) - line = line.replace(/await /g, "") - out.push(line) - } - return [...out, ...suffix].join("\n"); -} - -/** - * - * @param {md.MarkdownNode} node - * @param {boolean} isSync - */ -function generateComment(node, isSync) { - const commentNode = md.clone(node) - commentNode.codeLang = isSync ? "python sync" : "python async"; - commentNode.lines = ['# FIXME', ...transformValue(node.lines, isSync).split("\n")]; - return commentNode; -} - -/** - * - * @param {md.MarkdownNode[]} spec - */ -function multiplyComment(spec) { - const children = [] - for (const node of (spec || [])) { - if (node.codeLang === "js") - children.push(node, generateComment(node, false), generateComment(node, true)); - else - children.push(node); - } - return children; -} - -/** - * @param {string} name - */ -function toSnakeCase(name) { - const toSnakeCaseRegex = /((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g; - return name.replace(toSnakeCaseRegex, `_$1`).toLowerCase(); -} - -for (const name of fs.readdirSync("docs/src")) { - if (!name.endsWith(".md")) - continue; - const inputFile = `docs/src/${name}`; - const fileContent = fs.readFileSync(inputFile).toString(); - const nodes = md.parse(fileContent); - - md.visitAll(nodes, node => { - if (node.children) - node.children = multiplyComment(node.children); - }); - - - const out = md.render(nodes, 120); - fs.writeFileSync(inputFile, out); -} diff --git a/utils/doclint/since.js b/utils/doclint/since.js deleted file mode 100644 index 5f97ec563071a..0000000000000 --- a/utils/doclint/since.js +++ /dev/null @@ -1,81 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const { ZipFile } = require('../../packages/playwright-core/lib/utils'); - -(async () => { - // Extract API.json - if (!process.env.DRIVERS_DIR) { - console.log('DRIVERS_DIR env should contain downloaded drivers'); - process.exit(1); - } - - for (const name of fs.readdirSync(process.env.DRIVERS_DIR)) { - const match = name.match(/playwright-(.*).0-linux.zip/); - if (!match) - continue; - const apiName = path.join(process.env.DRIVERS_DIR, `api-${match[1]}.json`); - if (!fs.existsSync(apiName)) { - const zipFile = new ZipFile(path.join(process.env.DRIVERS_DIR, name)); - const buffer = await zipFile.read('package/api.json'); - fs.writeFileSync(path.join(process.env.DRIVERS_DIR, `api-${match[1]}.json`), buffer); - zipFile.close(); - } - } - - // Build Since map. - const since = new Map(); - for (const name of fs.readdirSync(process.env.DRIVERS_DIR)) { - const match = name.match(/api-(.*).json/); - if (!match) - continue; - const version = match[1]; - const json = JSON.parse(fs.readFileSync(path.join(process.env.DRIVERS_DIR, `api-${match[1]}.json`), 'utf-8')); - for (const clazz of json) { - add(since, `# class: ${clazz.name}`, version); - for (const member of clazz.members) { - add(since, `## ${member.async ? 'async ' : ''}${member.kind}: ${clazz.name}.${member.name}`, version); - for (const arg of member.args) { - if (arg.name === 'options') { - for (const option of arg.type.properties) - add(since, `### option: ${clazz.name}.${member.name}.${option.name}`, version); - } else { - add(since, `### param: ${clazz.name}.${member.name}.${arg.name}`, version); - } - } - } - } - } - - // Patch docs - for (const name of fs.readdirSync('docs/src/api')) { - const lines = fs.readFileSync(path.join('docs/src/api', name), 'utf-8'); - const toPatch = new Map(); - for (const line of lines.split('\n')) { - if (!line.startsWith('# class:') && !line.startsWith('## method:') && !line.startsWith('## async method:') && !line.startsWith('## property:') && !line.startsWith('## event:') && !line.startsWith('### param:') && !line.startsWith('### option:')) - continue; - const key = line.includes('=') ? line.substring(0, line.indexOf('=')).trim() : line; - const version = since.get(key); - console.log(key); - - if (!version) { - console.log('Not yet released: ' + line); - continue; - } - - toPatch.set(line +'\n', line + `\n* since: v${version}\n`); - } - if (toPatch.size) { - let newContent = lines; - for (const [from, to] of toPatch) - newContent = newContent.replace(new RegExp(from, 'g'), to); - fs.writeFileSync(path.join('docs/src/api', name), newContent); - } - } - -})(); - -function add(since, name, version) { - let v = since.get(name); - if (!v || (+v.split('.')[1]) > (+version.split('.')[1])) - since.set(name, version); -} diff --git a/utils/draft_release_notes.sh b/utils/draft_release_notes.sh deleted file mode 100755 index c27df96a4aaf8..0000000000000 --- a/utils/draft_release_notes.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -set -e -set +x - -trap "cd $(pwd -P)" EXIT -cd "$(dirname $0)" - -git fetch --tags git@github.com:microsoft/playwright.git >/dev/null 2>/dev/null -LAST_RELEASE=$(git describe --tags $(git rev-list --tags --max-count=1)) - -echo "## Highlights" -echo -echo "TODO: asked teammates for the highlights" -echo -echo "## Browser Versions" -echo -node ./print_versions.js -echo -echo "## New APIs" -echo -echo "TODO: \`git diff -w ${LAST_RELEASE}..HEAD src/client\`" -echo -CLOSED_ISSUES=$(./list_closed_issues.sh "${LAST_RELEASE}") -ISSUES_COUNT=$(echo "${CLOSED_ISSUES}" | wc -l | xargs) -echo "
" -echo " Issues Closed (${ISSUES_COUNT})" -echo -echo "${CLOSED_ISSUES}" -echo -echo "
" - -COMMITS=$(git log --pretty="%h - %s" "${LAST_RELEASE}"..HEAD) -COMMITS_COUNT=$(echo "${COMMITS}" | wc -l | xargs) -echo "
" -echo " Commits (${COMMITS_COUNT})" -echo -echo "${COMMITS}" -echo -echo "
" diff --git a/utils/limits.sh b/utils/limits.sh deleted file mode 100755 index 71baec19a4b8f..0000000000000 --- a/utils/limits.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -sudo sysctl -w fs.inotify.max_user_instances=1024 diff --git a/utils/list_closed_issues.sh b/utils/list_closed_issues.sh deleted file mode 100755 index 512da442835b9..0000000000000 --- a/utils/list_closed_issues.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -e -set +x - -if [[ ($1 == '--help') || ($1 == '-h') ]]; then - echo "usage: $(basename $0) " - echo - echo "List Playwright closed issues since the given commit was landed" - echo - echo "Example: $(basename $0) HEAD~100" - exit 0 -fi - -if [[ $# == 0 ]]; then - echo "missing git SHA" - echo "try './$(basename $0) --help' for more information" - exit 1 -fi - -COMMIT_DATE_WEIRD_ISO=$(git show -s --format=%cd --date=iso $1) -COMMIT_DATE=$(node -e "console.log(new Date('${COMMIT_DATE_WEIRD_ISO}').toISOString())") - -curl -s "https://api.github.com/repos/microsoft/playwright/issues?state=closed&since=${COMMIT_DATE}&direction=asc&per_page=100" | \ - node -e "console.log(JSON.parse(require('fs').readFileSync(0, 'utf8')).filter(issue => !issue.pull_request && new Date(issue.closed_at) > new Date('${COMMIT_DATE}')).map(issue => '#' + issue.number + ' - ' + issue.title).join('\n'))" - diff --git a/utils/ts_to_java.js b/utils/ts_to_java.js deleted file mode 100644 index d071945a9fa73..0000000000000 --- a/utils/ts_to_java.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// The script does basic transformation of .spec.ts code to .java unit tests. - -const path = require('path'); -const fs = require('fs'); -const os = require('os'); -const util = require('util'); -const { argv } = require('process'); - -(async () => { - if (process.argv.length < 3) throw new Error("Usage: node to_java.js .spec.js"); - const file = argv[2]; - if (!file.endsWith('.spec.ts')) throw new Error("Unexpected input: " + file); - console.log('Reading: ' + file); - let content = await util.promisify(fs.readFile)(file); - content = content.toString(); - - function toCamelCase(match, p1, offset, string) { - return p1.toUpperCase(); - } - - function itReplacer(match, p1, p2, p3, offset, string) { - // let name = p1.replace(/[ :'\-=\[\]\<\>]/g, '_'); - let name = p1.replace(/\W+(.)/g, toCamelCase); - // Remove special chars from the end. - name = name.replace(/\W+$/, ''); - // console.log(name); - return `@Test -void ${name}() {`; - } - content = content.replace(/playwrightTest\('(.+)',.*{/g, itReplacer); - content = content.replace(/browserTest\('(.+)',.*{/g, itReplacer); - content = content.replace(/pageTest\('(.+)',.*{/g, itReplacer); - content = content.replace(/test\('(.+)',.*{/g, itReplacer); - content = content.replace(/it\('(.+)',.*{/g, itReplacer); - content = content.replace(/it\(`(.+)`,.*{/g, itReplacer); - - // Test's closing bracket: }); - content = content.replace(/\n\}\);/g, '\n}'); - - content = content.replace(/ async route => {/g, ' (route, request) -> {'); - content = content.replace(/ route => {/g, ' (route, request) -> {'); - content = content.replace(/ async \(route, request\) => {/g, ' (route, request) -> {'); - content = content.replace(/ \(route, request\) => {/g, ' (route, request) -> {'); - content = content.replace(/(server.setRoute.+)\(req, res\) => \{/g, '$1exchange -> {'); - - content = content.replace(/([^\\])"/g, '$1SINGLE_QUOTE'); - // content = content.replace(/(\) \=\>.*)'/g, '$1 XXX'); - - // Replace single quotes with double quotes - content = content.replace(/''/g, '""'); - content = content.replace(/(?.*)([^\\])'/g, '$1"'); - // Replace double quotes with single quotes - content = content.replace(/SINGLE_QUOTE/g, "'"); - content = content.replace(/`/g, '"'); - - // quote lambdas - content = content.replace(/request => requests.push\(request\)/g, 'request -> requests.add(request)'); - // content = content.replace(/, ([^,\(]+ \=\> [^\)]+)\)/g, ', "$1")'); - content = content.replace(/page.evaluate\((\(\) => [^\)]+)\)/g, 'page.evaluate("$1")'); - - // Remove await/Promise.all - // Match all [^;] inside Promise.all([...]); to overcome greedy match and not include next foo([...]) calls. - content = content.replace(/const \[(.+)\] = await Promise.all\(\[([^;]+|)\]\);/g, '$1 = $2'); - content = content.replace(/await Promise.all\(\[([^;]+|)\]\);/g, '$1'); - content = content.replace(/Promise\.all\(\[/g, ''); - content = content.replace(/await /g, ''); - - // Rename some methods - content = content.replace(/context\.tracing/g, 'context.tracing()'); - content = content.replace(/\.goto\(/g, '.navigate('); - content = content.replace(/\.continue\(/g, '.resume('); - content = content.replace(/\.\$eval\(/g, '.evalOnSelector('); - content = content.replace(/\.\$\$eval\(/g, '.evalOnSelectorAll('); - content = content.replace(/\.\$\(/g, '.querySelector('); - content = content.replace(/\.\$\$\(/g, '.querySelectorAll('); - - content = content.replace(/\.keyboard\./g, '.keyboard().'); - content = content.replace(/\.mouse\./g, '.mouse().'); - content = content.replace(/\.coverage\./g, '.coverage().'); - content = content.replace(/\.accessibility\./g, '.accessibility().'); - content = content.replace(/\.length/g, '.size()'); - - content = content.replace(/expect\((.+)\).toBeTruthy\(\);/g, 'assertNotNull($1);'); - content = content.replace(/expect\(error.message\)\.toContain\((.+)\);/g, 'assertTrue(e.getMessage().contains($1), e.getMessage());'); - content = content.replace(/expect\((.+)\)\.toContain\((.+)\);/g, 'assertTrue($1.contains($2));'); - content = content.replace(/expect\((.+)\)\.toBe\(null\);/g, 'assertNull($1);'); - content = content.replace(/expect\((.+)\)\.not.toBe\(null\);/g, 'assertNotNull($1);'); - content = content.replace(/expect\((.+\.evaluate.+)\)\.toBe\(true\);/g, 'assertEquals(true, $1);'); - content = content.replace(/expect\((.+)\)\.toBe\(true\);/g, 'assertTrue($1);'); - content = content.replace(/expect\((.+)\)\.toBe\((.+)\);/g, 'assertEquals($2, $1);'); - // Match all [^;] inside .toEqual([...]); to overcome greedy match and not include next foo([...]) calls. - content = content.replace(/expect\((.+)\)\.toEqual\(\[([^;]+|)\]\);/g, 'assertEquals(asList($2), $1);'); - for (let before = null; before !== content;) { - before = content; - content = content.replace(/(asList\([^\)\']*)'/g, '$1"'); - } - content = content.replace(/expect\((.+)\)\.toEqual\((.+)\);/g, 'assertEquals($2, $1);'); - - content = content.replace(/(? requests = new ArrayList<>();'); - content = content.replace(/const snapshot = page.accessibility/g, 'AccessibilityNode snapshot = page.accessibility'); - content = content.replace(/snapshot\.children\./g, 'snapshot.children().'); - content = content.replace(/const (.+) = \[\];/g, 'List<> $1 = new ArrayList<>();'); - content = content.replace(/const (\w+ = .+evalOnSelector)/g, 'Object $1'); - content = content.replace(/const (\w+ = .+querySelector)/g, 'ElementHandle $1'); - content = content.replace(/const messages = \[\]/g, 'List messages = new ArrayList<>()'); - content = content.replace(/const frame = /g, 'Frame frame = '); - content = content.replace(/const elementHandle = (.+)/g, 'JSHandle jsHandle = $1\n ElementHandle elementHandle = jsHandle.asElement();\n assertNotNull(elementHandle);'); - content = content.replace(/const (\w+ = \w+\.boundingBox)/g, 'ElementHandle.BoundingBox $1'); - content = content.replace(/assertEquals\({ x: (\d+), y: (\d+), width: (\d+), height: (\d+) }, box\);/g, `assertEquals(box.x, $1); - assertEquals(box.y, $2); - assertEquals(box.width, $3); - assertEquals(box.height, $4);`); - content = content.replace(/setViewportSize\({ width: (\d+), height: (\d+) }\)/g, 'setViewportSize($1, $2)'); - content = content.replace(/\.on\("([^"]+)", /g, (match, p1, offset, string) => `\.on${toTitleCase(p1)}(`); - content = content.replace(/page.waitForEvent\("([^"]+)"/g, (match, p1, offset, string) => `page.waitFor${toTitleCase(p1)}(`); - content = content.replace(/server.waitForRequest/g, 'server.futureRequest'); - content = content.replace(/context.request/g, 'context.request()'); - content = content.replace(/page.request/g, 'page.request()'); - content = content.replace(/playwright.request/g, 'playwright.request()'); - - // try/catch - content = content.replace(/const error = /g, 'try {\n'); - content = content.replace(/\.catch\(e => e\)[;,]/g, ';\nfail("did not throw");\n} catch (PlaywrightException e) {}\n'); - content = content.replace(/(.+)\.catch\(e => error = e\);/g, ' try {\n $1;\n fail("did not throw");\n } catch (PlaywrightException e) {\n }\n'); - - const output = file.replace(/\.spec\.ts$/, ".java") - console.log('Writing: ' + output); - await util.promisify(fs.writeFile)(output, content) -})(); - -function toTitleCase(s) { - return s[0].toUpperCase() + s.substr(1); -} \ No newline at end of file From 68d7f66566db8230ab3adb9efd61b9d98f78c0c6 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 23 Jun 2025 08:39:47 -0700 Subject: [PATCH 100/222] chore: move Page.close() tests to tests/library (#36390) --- tests/library/page-close.spec.ts | 231 ++++++++++++++++++++ tests/page/expect-timeout.spec.ts | 10 - tests/page/page-add-locator-handler.spec.ts | 18 -- tests/page/page-basic.spec.ts | 89 -------- tests/page/page-click.spec.ts | 10 - tests/page/page-close.spec.ts | 38 ---- tests/page/page-event-network.spec.ts | 21 -- tests/page/page-event-popup.spec.ts | 16 -- tests/page/page-expose-function.spec.ts | 17 -- tests/page/page-network-response.spec.ts | 20 -- tests/page/page-request-continue.spec.ts | 27 --- 11 files changed, 231 insertions(+), 266 deletions(-) create mode 100644 tests/library/page-close.spec.ts delete mode 100644 tests/page/page-close.spec.ts diff --git a/tests/library/page-close.spec.ts b/tests/library/page-close.spec.ts new file mode 100644 index 0000000000000..f8b712fc433fc --- /dev/null +++ b/tests/library/page-close.spec.ts @@ -0,0 +1,231 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { stripAnsi } from '../config/utils'; +import { browserTest as test, expect } from '../config/browserTest'; +import { kTargetClosedErrorMessage } from '../config/errors'; + +test('should close page with active dialog', async ({ page }) => { + await page.evaluate('"trigger builtins.setTimeout"'); + await page.setContent(``); + void page.click('button').catch(() => {}); + await page.waitForEvent('dialog'); + await page.close(); +}); + +test('should not accept dialog after close', async ({ page, mode }) => { + test.fixme(mode.startsWith('service2'), 'Times out'); + const promise = page.waitForEvent('dialog'); + page.evaluate(() => alert()).catch(() => {}); + const dialog = await promise; + await page.close(); + const e = await dialog.dismiss().catch(e => e); + expect(e.message).toContain('Target page, context or browser has been closed'); +}); + +test('expect should not print timed out error message when page closes', async ({ page }) => { + await page.setContent('
Text content
'); + const [error] = await Promise.all([ + expect(page.locator('div')).toHaveText('hey', { timeout: 100000 }).catch(e => e), + page.close(), + ]); + expect(stripAnsi(error.message)).toContain(`expect(locator).toHaveText(expected)`); + expect(stripAnsi(error.message)).not.toContain('Timed out'); +}); + +test('addLocatorHandler should throw when page closes', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/handle-locator.html'); + + await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => { + await page.close(); + }); + + await page.locator('#aside').hover(); + await page.evaluate(() => { + (window as any).clicked = 0; + (window as any).setupAnnoyingInterstitial('mouseover', 1); + }); + const error = await page.locator('#target').click().catch(e => e); + expect(error.message).toContain(kTargetClosedErrorMessage); +}); + +test('should reject all promises when page is closed', async ({ page }) => { + let error = null; + await Promise.all([ + page.evaluate(() => new Promise(r => {})).catch(e => error = e), + page.close(), + ]); + expect(error.message).toContain(kTargetClosedErrorMessage); +}); + +test('should set the page close state', async ({ page }) => { + expect(page.isClosed()).toBe(false); + await page.close(); + expect(page.isClosed()).toBe(true); +}); + +test('should pass page to close event', async ({ page }) => { + const [closedPage] = await Promise.all([ + page.waitForEvent('close'), + page.close() + ]); + expect(closedPage).toBe(page); +}); + +test('should terminate network waiters', async ({ page, server }) => { + const results = await Promise.all([ + page.waitForRequest(server.EMPTY_PAGE).catch(e => e), + page.waitForResponse(server.EMPTY_PAGE).catch(e => e), + page.close() + ]); + for (let i = 0; i < 2; i++) { + const message = results[i].message; + expect(message).toContain(kTargetClosedErrorMessage); + expect(message).not.toContain('Timeout'); + } +}); + +test('should be callable twice', async ({ page }) => { + await Promise.all([ + page.close(), + page.close(), + ]); + await page.close(); +}); + +test('should return null if parent page has been closed', async ({ page }) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank')), + ]); + await page.close(); + const opener = await popup.opener(); + expect(opener).toBe(null); +}); + +test('should fail with error upon disconnect', async ({ page }) => { + let error; + const waitForPromise = page.waitForEvent('download').catch(e => error = e); + await page.close(); + await waitForPromise; + expect(error.message).toContain(kTargetClosedErrorMessage); +}); + +test('page.close should work with window.close', async function({ page }) { + const closedPromise = new Promise(x => page.on('close', x)); + await page.close(); + await closedPromise; +}); + +test('should not throw UnhandledPromiseRejection when page closes', async ({ page, browserName, isWindows }) => { + test.fixme(browserName === 'firefox' && isWindows, 'makes the next test to always timeout'); + + await Promise.all([ + page.close(), + page.mouse.click(1, 2), + ]).catch(e => {}); +}); + +test('interrupt request.response() and request.allHeaders() on page.close', async ({ page, server, browserName }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27227' }); + server.setRoute('/one-style.css', (req, res) => { + res.setHeader('Content-Type', 'text/css'); + }); + const reqPromise = page.waitForRequest('**/one-style.css'); + await page.goto(server.PREFIX + '/one-style.html', { waitUntil: 'domcontentloaded' }); + const req = await reqPromise; + const respPromise = req.response().catch(e => e); + const headersPromise = req.allHeaders().catch(e => e); + await page.close(); + expect((await respPromise).message).toContain(kTargetClosedErrorMessage); + // All headers are the same as "provisional" headers in Firefox. + if (browserName === 'firefox') + expect((await headersPromise)['user-agent']).toBeTruthy(); + else + expect((await headersPromise).message).toContain(kTargetClosedErrorMessage); +}); + +test('should not treat navigations as new popups', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('
yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + let badSecondPopup = false; + page.on('popup', () => badSecondPopup = true); + await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await page.close(); + expect(badSecondPopup).toBe(false); +}); + +test('should not result in unhandled rejection', async ({ page }) => { + const closedPromise = page.waitForEvent('close'); + await page.exposeFunction('foo', async () => { + await page.close(); + }); + await page.evaluate(() => { + window.builtins.setTimeout(() => (window as any).foo(), 0); + return undefined; + }); + await closedPromise; + // Make a round-trip to be sure we did not throw immediately after closing. + expect(await page.evaluate('1 + 1').catch(e => e)).toBeInstanceOf(Error); +}); + +test('should reject response.finished if page closes', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/get', (req, res) => { + // In Firefox, |fetch| will be hanging until it receives |Content-Type| header + // from server. + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.write('hello '); + }); + // send request and wait for server response + const [pageResponse] = await Promise.all([ + page.waitForEvent('response'), + page.evaluate(() => fetch('./get', { method: 'GET' })), + ]); + + const finishPromise = pageResponse.finished().catch(e => e); + await page.close(); + const error = await finishPromise; + expect(error.message).toContain('closed'); +}); + +test('should not throw when continuing while page is closing', async ({ page, server }) => { + let done; + await page.route('**/*', async route => { + done = Promise.all([ + void route.continue(), + page.close(), + ]); + }); + await page.goto(server.EMPTY_PAGE).catch(e => e); + await done; +}); + +test('should not throw when continuing after page is closed', async ({ page, server }) => { + let done; + await page.route('**/*', async route => { + await page.close(); + done = route.continue(); + }); + const error = await page.goto(server.EMPTY_PAGE).catch(e => e); + await done; + expect(error).toBeInstanceOf(Error); +}); diff --git a/tests/page/expect-timeout.spec.ts b/tests/page/expect-timeout.spec.ts index d8a062dd25b7a..e048ec744df5f 100644 --- a/tests/page/expect-timeout.spec.ts +++ b/tests/page/expect-timeout.spec.ts @@ -41,16 +41,6 @@ test('should print timed out error message when value does not match with imposs expect(stripAnsi(error.message)).toContain(`Timed out 1ms waiting for expect(locator).toHaveText(expected)`); }); -test('should not print timed out error message when page closes', async ({ page }) => { - await page.setContent('
Text content
'); - const [error] = await Promise.all([ - expect(page.locator('div')).toHaveText('hey', { timeout: 100000 }).catch(e => e), - page.close(), - ]); - expect(stripAnsi(error.message)).toContain(`expect(locator).toHaveText(expected)`); - expect(stripAnsi(error.message)).not.toContain('Timed out'); -}); - test('should have timeout error name', async ({ page }) => { const error = await page.waitForSelector('#not-found', { timeout: 1 }).catch(e => e); expect(error.name).toBe('TimeoutError'); diff --git a/tests/page/page-add-locator-handler.spec.ts b/tests/page/page-add-locator-handler.spec.ts index 605e63da821e6..069dde024158b 100644 --- a/tests/page/page-add-locator-handler.spec.ts +++ b/tests/page/page-add-locator-handler.spec.ts @@ -15,7 +15,6 @@ */ import { test, expect } from './pageTest'; -import { kTargetClosedErrorMessage } from '../config/errors'; test('should work', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/handle-locator.html'); @@ -121,23 +120,6 @@ test('should not work with force:true', async ({ page, server }) => { expect(await page.evaluate('window.clicked')).toBe(undefined); }); -test('should throw when page closes', async ({ page, server, isAndroid }) => { - test.fixme(isAndroid, 'GPU process crash: https://issues.chromium.org/issues/324909825'); - await page.goto(server.PREFIX + '/input/handle-locator.html'); - - await page.addLocatorHandler(page.getByText('This interstitial covers the button'), async () => { - await page.close(); - }); - - await page.locator('#aside').hover(); - await page.evaluate(() => { - (window as any).clicked = 0; - (window as any).setupAnnoyingInterstitial('mouseover', 1); - }); - const error = await page.locator('#target').click().catch(e => e); - expect(error.message).toContain(kTargetClosedErrorMessage); -}); - test('should throw when handler times out', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/handle-locator.html'); diff --git a/tests/page/page-basic.spec.ts b/tests/page/page-basic.spec.ts index 0b99d0c8c5f64..d8e8d40dec7ce 100644 --- a/tests/page/page-basic.spec.ts +++ b/tests/page/page-basic.spec.ts @@ -15,66 +15,8 @@ * limitations under the License. */ -import { kTargetClosedErrorMessage } from '../config/errors'; import { test as it, expect } from './pageTest'; -it('should reject all promises when page is closed', async ({ page, isWebView2, isAndroid }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - it.fixme(isAndroid, '"Target crashed" instead of "Target closed"'); - - let error = null; - await Promise.all([ - page.evaluate(() => new Promise(r => {})).catch(e => error = e), - page.close(), - ]); - expect(error.message).toContain(kTargetClosedErrorMessage); -}); - -it('should set the page close state', async ({ page, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - expect(page.isClosed()).toBe(false); - await page.close(); - expect(page.isClosed()).toBe(true); -}); - -it('should pass page to close event', async ({ page, isAndroid, isWebView2 }) => { - it.fixme(isAndroid); - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - const [closedPage] = await Promise.all([ - page.waitForEvent('close'), - page.close() - ]); - expect(closedPage).toBe(page); -}); - -it('should terminate network waiters', async ({ page, server, isAndroid, isWebView2 }) => { - it.fixme(isAndroid); - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - const results = await Promise.all([ - page.waitForRequest(server.EMPTY_PAGE).catch(e => e), - page.waitForResponse(server.EMPTY_PAGE).catch(e => e), - page.close() - ]); - for (let i = 0; i < 2; i++) { - const message = results[i].message; - expect(message).toContain(kTargetClosedErrorMessage); - expect(message).not.toContain('Timeout'); - } -}); - -it('should be callable twice', async ({ page, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - await Promise.all([ - page.close(), - page.close(), - ]); - await page.close(); -}); - it('should fire load when expected', async ({ page }) => { await Promise.all([ page.goto('about:blank'), @@ -101,18 +43,6 @@ it('should provide access to the opener page', async ({ page }) => { expect(opener).toBe(page); }); -it('should return null if parent page has been closed', async ({ page, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank')), - ]); - await page.close(); - const opener = await popup.opener(); - expect(opener).toBe(null); -}); - it('should fire domcontentloaded when expected', async ({ page }) => { const navigatedPromise = page.goto('about:blank'); await page.waitForEvent('domcontentloaded'); @@ -135,17 +65,6 @@ it('should pass self as argument to load event', async ({ page }) => { expect(eventArg).toBe(page); }); -it('should fail with error upon disconnect', async ({ page, isAndroid, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - it.fixme(isAndroid); - - let error; - const waitForPromise = page.waitForEvent('download').catch(e => error = e); - await page.close(); - await waitForPromise; - expect(error.message).toContain(kTargetClosedErrorMessage); -}); - it('page.url should work', async ({ page, server }) => { expect(page.url()).toBe('about:blank'); await page.goto(server.EMPTY_PAGE); @@ -175,14 +94,6 @@ it('page.close should work with window.close', async function({ page }) { await closedPromise; }); -it('page.close should work with page.close', async function({ page, isWebView2 }) { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - const closedPromise = new Promise(x => page.on('close', x)); - await page.close(); - await closedPromise; -}); - it('page.frame should respect name', async function({ page }) { await page.setContent(``); expect(page.frame({ name: 'bogus' })).toBe(null); diff --git a/tests/page/page-click.spec.ts b/tests/page/page-click.spec.ts index b206480740c8b..f048ea6c5710b 100644 --- a/tests/page/page-click.spec.ts +++ b/tests/page/page-click.spec.ts @@ -85,16 +85,6 @@ it('should click on a span with an inline element inside', async ({ page }) => { expect(await page.evaluate('CLICKED')).toBe(42); }); -it('should not throw UnhandledPromiseRejection when page closes', async ({ page, isWebView2, browserName, isWindows }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - it.fixme(browserName === 'firefox' && isWindows, 'makes the next test to always timeout'); - - await Promise.all([ - page.close(), - page.mouse.click(1, 2), - ]).catch(e => {}); -}); - it('should click the aligned 1x1 div', async ({ page }) => { await page.setContent(`
`); await page.click('div'); diff --git a/tests/page/page-close.spec.ts b/tests/page/page-close.spec.ts deleted file mode 100644 index fd215f8b60400..0000000000000 --- a/tests/page/page-close.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { test as it, expect } from './pageTest'; - -it.skip(({ isWebView2 }) => isWebView2, 'Page.close() is not supported in WebView2'); - -it('should close page with active dialog', async ({ page }) => { - await page.evaluate('"trigger builtins.setTimeout"'); - await page.setContent(``); - void page.click('button').catch(() => {}); - await page.waitForEvent('dialog'); - await page.close(); -}); - -it('should not accept dialog after close', async ({ page, mode }) => { - it.fixme(mode.startsWith('service2'), 'Times out'); - const promise = page.waitForEvent('dialog'); - page.evaluate(() => alert()).catch(() => {}); - const dialog = await promise; - await page.close(); - const e = await dialog.dismiss().catch(e => e); - expect(e.message).toContain('Target page, context or browser has been closed'); -}); diff --git a/tests/page/page-event-network.spec.ts b/tests/page/page-event-network.spec.ts index 8aa1462d5a71e..e7004f576a245 100644 --- a/tests/page/page-event-network.spec.ts +++ b/tests/page/page-event-network.spec.ts @@ -17,7 +17,6 @@ import type { ServerResponse } from 'http'; import { test as it, expect } from './pageTest'; -import { kTargetClosedErrorMessage } from '../config/errors'; it('Page.Events.Request @smoke', async ({ page, server }) => { const requests = []; @@ -138,23 +137,3 @@ it('should resolve responses after a navigation', async ({ page, server, browser // the response should resolve to null, because the page navigated. expect(await responsePromise).toBe(null); }); - -it('interrupt request.response() and request.allHeaders() on page.close', async ({ page, server, browserName }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27227' }); - server.setRoute('/one-style.css', (req, res) => { - res.setHeader('Content-Type', 'text/css'); - }); - const reqPromise = page.waitForRequest('**/one-style.css'); - await page.goto(server.PREFIX + '/one-style.html', { waitUntil: 'domcontentloaded' }); - const req = await reqPromise; - const respPromise = req.response().catch(e => e); - const headersPromise = req.allHeaders().catch(e => e); - await page.close(); - expect((await respPromise).message).toContain(kTargetClosedErrorMessage); - // All headers are the same as "provisional" headers in Firefox. - if (browserName === 'firefox') - expect((await headersPromise)['user-agent']).toBeTruthy(); - else - expect((await headersPromise).message).toContain(kTargetClosedErrorMessage); - -}); diff --git a/tests/page/page-event-popup.spec.ts b/tests/page/page-event-popup.spec.ts index 07b203709ae38..152136acdebf5 100644 --- a/tests/page/page-event-popup.spec.ts +++ b/tests/page/page-event-popup.spec.ts @@ -146,22 +146,6 @@ it('should work with clicking target=_blank and rel=noopener', async ({ page, se expect(await popup.evaluate(() => !!window.opener)).toBe(false); }); -it('should not treat navigations as new popups', async ({ page, server, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a'), - ]); - let badSecondPopup = false; - page.on('popup', () => badSecondPopup = true); - await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - await page.close(); - expect(badSecondPopup).toBe(false); -}); - it('should report popup opened from iframes', async ({ page, server, browserName }) => { await page.goto(server.PREFIX + '/frames/two-frames.html'); const frame = page.frame('uno'); diff --git a/tests/page/page-expose-function.spec.ts b/tests/page/page-expose-function.spec.ts index b16546ef61f94..e9334c4df08d3 100644 --- a/tests/page/page-expose-function.spec.ts +++ b/tests/page/page-expose-function.spec.ts @@ -221,23 +221,6 @@ it('exposeBindingHandle should throw for multiple arguments', async ({ page }) = expect(error.message).toContain('exposeBindingHandle supports a single argument, 2 received'); }); -it('should not result in unhandled rejection', async ({ page, isAndroid, isWebView2 }) => { - it.fixme(isAndroid); - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - const closedPromise = page.waitForEvent('close'); - await page.exposeFunction('foo', async () => { - await page.close(); - }); - await page.evaluate(() => { - window.builtins.setTimeout(() => (window as any).foo(), 0); - return undefined; - }); - await closedPromise; - // Make a round-trip to be sure we did not throw immediately after closing. - expect(await page.evaluate('1 + 1').catch(e => e)).toBeInstanceOf(Error); -}); - it('exposeBinding(handle) should work with element handles', async ({ page }) => { let cb; const promise = new Promise(f => cb = f); diff --git a/tests/page/page-network-response.spec.ts b/tests/page/page-network-response.spec.ts index 1224713b2cfc4..e353aa6956daa 100644 --- a/tests/page/page-network-response.spec.ts +++ b/tests/page/page-network-response.spec.ts @@ -109,26 +109,6 @@ it('should wait until response completes', async ({ page, server }) => { expect(await responseText).toBe('hello world!'); }); -it('should reject response.finished if page closes', async ({ page, server }) => { - await page.goto(server.EMPTY_PAGE); - server.setRoute('/get', (req, res) => { - // In Firefox, |fetch| will be hanging until it receives |Content-Type| header - // from server. - res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - res.write('hello '); - }); - // send request and wait for server response - const [pageResponse] = await Promise.all([ - page.waitForEvent('response'), - page.evaluate(() => fetch('./get', { method: 'GET' })), - ]); - - const finishPromise = pageResponse.finished().catch(e => e); - await page.close(); - const error = await finishPromise; - expect(error.message).toContain('closed'); -}); - it('should return json', async ({ page, server }) => { const response = await page.goto(server.PREFIX + '/simple.json'); expect(await response.json()).toEqual({ foo: 'bar' }); diff --git a/tests/page/page-request-continue.spec.ts b/tests/page/page-request-continue.spec.ts index a2a7e408626e1..7533b27a16667 100644 --- a/tests/page/page-request-continue.spec.ts +++ b/tests/page/page-request-continue.spec.ts @@ -150,33 +150,6 @@ it('should not allow changing protocol when overriding url', async ({ page, serv expect(error.message).toContain('New URL must have same protocol as overridden URL'); }); -it('should not throw when continuing while page is closing', async ({ page, server, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - let done; - await page.route('**/*', async route => { - done = Promise.all([ - void route.continue(), - page.close(), - ]); - }); - await page.goto(server.EMPTY_PAGE).catch(e => e); - await done; -}); - -it('should not throw when continuing after page is closed', async ({ page, server, isWebView2 }) => { - it.skip(isWebView2, 'Page.close() is not supported in WebView2'); - - let done; - await page.route('**/*', async route => { - await page.close(); - done = route.continue(); - }); - const error = await page.goto(server.EMPTY_PAGE).catch(e => e); - await done; - expect(error).toBeInstanceOf(Error); -}); - it('should not throw if request was cancelled by the page', async ({ page, server, browserName }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28490' }); let interceptCallback; From b1a1e11ad8254af1c3ece5387bb3d15b6d698400 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 23 Jun 2025 22:43:15 +0200 Subject: [PATCH 101/222] chore: delete utils/doclint/generateFullConfigDoc.js (#36413) --- utils/doclint/generateFullConfigDoc.js | 121 ------------------------- 1 file changed, 121 deletions(-) delete mode 100644 utils/doclint/generateFullConfigDoc.js diff --git a/utils/doclint/generateFullConfigDoc.js b/utils/doclint/generateFullConfigDoc.js deleted file mode 100644 index 8c00f7f8a2767..0000000000000 --- a/utils/doclint/generateFullConfigDoc.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// @ts-check - -const path = require('path'); -const fs = require('fs'); -const PROJECT_DIR = path.join(__dirname, '..', '..'); - -function generateFullConfigClass(fromClassName, toClassName, allowList) { - const allowedNames = new Set(allowList); - - const content = fs.readFileSync(path.join(PROJECT_DIR, `docs/src/test-api/class-${fromClassName.toLowerCase()}.md`)).toString(); - let sections = content.split('\n## '); - sections = filterAllowedSections(sections, allowedNames); - if (allowedNames.size) - console.log(`Undocumented properties for ${fromClassName}:\n ${[...allowedNames].join('\n ')}`); - sections = changeClassName(sections, fromClassName, toClassName); - sections = replacePropertyDescriptions(sections, fromClassName); - const fullconfig = sections.join('\n## '); - fs.writeFileSync(path.join(PROJECT_DIR, `docs/src/test-api/class-${toClassName.toLowerCase()}.md`), fullconfig); -} - -function propertyNameFromSection(section) { - section = section.split('\n')[0]; - const match = /\.(\w+)/.exec(section); - if (!match) - return null; - return match[1]; -} - -function filterAllowedSections(sections, allowedNames) { - return sections.filter(section => { - section = section.split('\n')[0]; - const name = propertyNameFromSection(section); - if (!name) - return true; - return allowedNames.delete(name); - }); -} - -function changeClassName(sections, from, to) { - return sections.map(section => { - const lines = section.split('\n'); - lines[0] = lines[0].replace(from, to); - return lines.join('\n'); - }); -} - -function replacePropertyDescriptions(sections, configClassName) { - return sections.map(section => { - const parts = section.split('\n\n'); - section = parts[0]; - const name = propertyNameFromSection(section); - if (!name) - return `${section}\n`; - return `${section}\n\nSee [\`property: ${configClassName}.${name}\`].\n`; - }); -} - -function generateFullConfig() { - generateFullConfigClass('TestConfig', 'FullConfig', [ - 'forbidOnly', - 'fullyParallel', - 'globalSetup', - 'globalTeardown', - 'globalTimeout', - 'grep', - 'grepInvert', - 'maxFailures', - 'metadata', - 'version', - 'preserveOutput', - 'projects', - 'reporter', - 'reportSlowTests', - 'rootDir', - 'quiet', - 'shard', - 'updateSnapshots', - 'workers', - 'webServer', - 'configFile', - ]); -} - -function generateFullProject() { - generateFullConfigClass('TestProject', 'FullProject', [ - 'grep', - 'grepInvert', - 'metadata', - 'name', - 'dependencies', - 'snapshotDir', - 'outputDir', - 'repeatEach', - 'retries', - 'teardown', - 'testDir', - 'testIgnore', - 'testMatch', - 'timeout', - 'use', - ]); -} - -generateFullConfig(); -generateFullProject(); From 896cb8536e7ca561fd9a6179cbb9643cccc848bc Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 23 Jun 2025 22:52:21 +0200 Subject: [PATCH 102/222] chore: fix Cannot find module '@testIsomorphic/types' in recorder (#36414) --- packages/recorder/tsconfig.json | 1 + packages/recorder/tsconfig.node.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/recorder/tsconfig.json b/packages/recorder/tsconfig.json index 166ee1a010ca1..4defa85f64dd4 100644 --- a/packages/recorder/tsconfig.json +++ b/packages/recorder/tsconfig.json @@ -20,6 +20,7 @@ "@isomorphic/*": ["../playwright-core/src/utils/isomorphic/*"], "@protocol/*": ["../protocol/src/*"], "@recorder/*": ["../recorder/src/*"], + "@testIsomorphic/*": ["../playwright/src/isomorphic/*"], "@web/*": ["../web/src/*"], } }, diff --git a/packages/recorder/tsconfig.node.json b/packages/recorder/tsconfig.node.json index e993792cb12c9..a336f895aab52 100644 --- a/packages/recorder/tsconfig.node.json +++ b/packages/recorder/tsconfig.node.json @@ -2,7 +2,8 @@ "compilerOptions": { "composite": true, "module": "esnext", - "moduleResolution": "node" + "moduleResolution": "node", + "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts"] } From 11705781bd87d15527903843b100a9d0ddfe21b0 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 24 Jun 2025 10:55:40 +0100 Subject: [PATCH 103/222] chore: make sure _generateLocatorString does not timeout (#36381) --- .../src/client/elementHandle.ts | 5 --- .../playwright-core/src/client/locator.ts | 3 +- .../playwright-core/src/protocol/validator.ts | 10 +++--- .../dispatchers/elementHandlerDispatcher.ts | 4 --- .../src/server/dispatchers/frameDispatcher.ts | 4 +++ packages/playwright-core/src/server/dom.ts | 34 +------------------ packages/playwright-core/src/server/frames.ts | 32 +++++++++++++++++ .../src/utils/isomorphic/protocolMetainfo.ts | 2 +- packages/protocol/src/channels.d.ts | 16 +++++---- packages/protocol/src/protocol.yml | 12 ++++--- tests/page/page-aria-snapshot-ai.spec.ts | 4 +++ 11 files changed, 67 insertions(+), 59 deletions(-) diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index 85c91e8c22d73..93a600336d698 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -61,11 +61,6 @@ export class ElementHandle extends JSHandle implements return Frame.fromNullable((await this._elementChannel.contentFrame()).frame); } - async _generateLocatorString(): Promise { - const value = (await this._elementChannel.generateLocatorString()).value; - return value === undefined ? null : value; - } - async getAttribute(name: string): Promise { const value = (await this._elementChannel.getAttribute({ name })).value; return value === undefined ? null : value; diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index ea780c2fbe34d..b52c99e92259d 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -250,7 +250,8 @@ export class Locator implements api.Locator { } async _generateLocatorString(): Promise { - return await this._withElement(h => h._generateLocatorString(), { title: 'Generate locator string', internal: true }); + const { value } = await this._frame._channel.generateLocatorString({ selector: this._selector }); + return value === undefined ? null : value; } async getAttribute(name: string, options?: TimeoutOptions): Promise { diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 2475526a43eb5..a44bb2d096578 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -1651,6 +1651,12 @@ scheme.FrameFrameElementParams = tOptional(tObject({})); scheme.FrameFrameElementResult = tObject({ element: tChannel(['ElementHandle']), }); +scheme.FrameGenerateLocatorStringParams = tObject({ + selector: tString, +}); +scheme.FrameGenerateLocatorStringResult = tObject({ + value: tOptional(tString), +}); scheme.FrameHighlightParams = tObject({ selector: tString, }); @@ -2044,10 +2050,6 @@ scheme.ElementHandleFillParams = tObject({ scheme.ElementHandleFillResult = tOptional(tObject({})); scheme.ElementHandleFocusParams = tOptional(tObject({})); scheme.ElementHandleFocusResult = tOptional(tObject({})); -scheme.ElementHandleGenerateLocatorStringParams = tOptional(tObject({})); -scheme.ElementHandleGenerateLocatorStringResult = tObject({ - value: tOptional(tString), -}); scheme.ElementHandleGetAttributeParams = tObject({ name: tString, }); diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index c9c50829a9a56..4f708270482b8 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -65,10 +65,6 @@ export class ElementHandleDispatcher extends JSHandleDispatcher return { frame: frame ? FrameDispatcher.from(this._browserContextDispatcher(), frame) : undefined }; } - async generateLocatorString(params: channels.ElementHandleGenerateLocatorStringParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.generateLocatorString() }; - } - async getAttribute(params: channels.ElementHandleGetAttributeParams, metadata: CallMetadata): Promise { const value = await this._elementHandle.getAttribute(metadata, params.name); return { value: value === null ? undefined : value }; diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 38c31056f2304..5db8c7c56121b 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -178,6 +178,10 @@ export class FrameDispatcher extends Dispatcher { + return { value: await this._frame.generateLocatorString(metadata, params.selector) }; + } + async getAttribute(params: channels.FrameGetAttributeParams, metadata: CallMetadata): Promise { const value = await this._frame.getAttribute(metadata, params.selector, params.name, params); return { value: value === null ? undefined : value }; diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index f1f234864dfe9..ffb61584fc2cd 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import * as js from './javascript'; import { ProgressController } from './progress'; -import { asLocator, isUnderTest } from '../utils'; +import { isUnderTest } from '../utils'; import { prepareFilesForUpload } from './fileUploadUtils'; import * as rawInjectedScriptSource from '../generated/injectedScriptSource'; @@ -183,38 +183,6 @@ export class ElementHandle extends js.JSHandle { return this._page.delegate.getContentFrame(this); } - async generateLocatorString(): Promise { - const selectors = await this._generateSelectorString(); - if (!selectors.length) - return; - return asLocator('javascript', selectors.reverse().join(' >> internal:control=enter-frame >> ')); - } - - private async _generateSelectorString(): Promise { - const selector = await this.evaluateInUtility(async ([injected, node]) => { - return injected.generateSelectorSimple(node as unknown as Element); - }, {}); - if (selector === 'error:notconnected') - return []; - - let frame: frames.Frame | null = this._frame; - const result: string[] = [selector]; - while (frame?.parentFrame()) { - const frameElement = await frame.frameElement(); - if (frameElement) { - const selector = await frameElement.evaluateInUtility(async ([injected, node]) => { - return injected.generateSelectorSimple(node as unknown as Element); - }, {}); - frameElement.dispose(); - if (selector === 'error:notconnected') - return []; - result.push(selector); - } - frame = frame.parentFrame(); - } - return result; - } - async getAttribute(metadata: CallMetadata, name: string): Promise { return this._frame.getAttribute(metadata, ':scope', name, { timeout: 0 }, this); } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 33e738ac7ddf0..ecb5757c397e3 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1212,6 +1212,38 @@ export class Frame extends SdkObject { }, options.timeout); } + async generateLocatorString(metadata: CallMetadata, selector: string): Promise { + const controller = new ProgressController(metadata, this); + return controller.run(async progress => { + const element = await progress.race(this.selectors.query(selector)); + if (!element) + throw new Error(`No element matching ${this._asLocator(selector)}`); + + const generated = await progress.race(element.evaluateInUtility(async ([injected, node]) => { + return injected.generateSelectorSimple(node as unknown as Element); + }, {})); + if (!generated) + throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`); + + let frame: Frame | null = element._frame; + const result = [generated]; + while (frame?.parentFrame()) { + const frameElement = await progress.race(frame.frameElement()); + if (frameElement) { + const generated = await progress.race(frameElement.evaluateInUtility(async ([injected, node]) => { + return injected.generateSelectorSimple(node as unknown as Element); + }, {})); + frameElement.dispose(); + if (generated === 'error:notconnected' || !generated) + throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`); + result.push(generated); + } + frame = frame.parentFrame(); + } + return asLocator(this._page.browserContext._browser.sdkLanguage(), result.reverse().join(' >> internal:control=enter-frame >> ')); + }); + } + async textContent(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.textContent, undefined, options, scope); } diff --git a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts index 673dcd2612286..2075f076ddab0 100644 --- a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts +++ b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts @@ -154,6 +154,7 @@ export const methodMetainfo = new Map; focus(params: FrameFocusParams, metadata?: CallMetadata): Promise; frameElement(params?: FrameFrameElementParams, metadata?: CallMetadata): Promise; + generateLocatorString(params: FrameGenerateLocatorStringParams, metadata?: CallMetadata): Promise; highlight(params: FrameHighlightParams, metadata?: CallMetadata): Promise; getAttribute(params: FrameGetAttributeParams, metadata?: CallMetadata): Promise; goto(params: FrameGotoParams, metadata?: CallMetadata): Promise; @@ -2890,6 +2891,15 @@ export type FrameFrameElementOptions = {}; export type FrameFrameElementResult = { element: ElementHandleChannel, }; +export type FrameGenerateLocatorStringParams = { + selector: string, +}; +export type FrameGenerateLocatorStringOptions = { + +}; +export type FrameGenerateLocatorStringResult = { + value?: string, +}; export type FrameHighlightParams = { selector: string, }; @@ -3395,7 +3405,6 @@ export interface ElementHandleChannel extends ElementHandleEventTarget, JSHandle dispatchEvent(params: ElementHandleDispatchEventParams, metadata?: CallMetadata): Promise; fill(params: ElementHandleFillParams, metadata?: CallMetadata): Promise; focus(params?: ElementHandleFocusParams, metadata?: CallMetadata): Promise; - generateLocatorString(params?: ElementHandleGenerateLocatorStringParams, metadata?: CallMetadata): Promise; getAttribute(params: ElementHandleGetAttributeParams, metadata?: CallMetadata): Promise; hover(params: ElementHandleHoverParams, metadata?: CallMetadata): Promise; innerHTML(params?: ElementHandleInnerHTMLParams, metadata?: CallMetadata): Promise; @@ -3531,11 +3540,6 @@ export type ElementHandleFillResult = void; export type ElementHandleFocusParams = {}; export type ElementHandleFocusOptions = {}; export type ElementHandleFocusResult = void; -export type ElementHandleGenerateLocatorStringParams = {}; -export type ElementHandleGenerateLocatorStringOptions = {}; -export type ElementHandleGenerateLocatorStringResult = { - value?: string, -}; export type ElementHandleGetAttributeParams = { name: string, }; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index e9c765d045421..be56d9af827f1 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -2289,6 +2289,13 @@ Frame: returns: element: ElementHandle + generateLocatorString: + internal: true + parameters: + selector: string + returns: + value: string? + highlight: internal: true parameters: @@ -2937,11 +2944,6 @@ ElementHandle: slowMo: true snapshot: true - generateLocatorString: - internal: true - returns: - value: string? - getAttribute: internal: true parameters: diff --git a/tests/page/page-aria-snapshot-ai.spec.ts b/tests/page/page-aria-snapshot-ai.spec.ts index 35d591bf9fc8e..3d12cd086fee7 100644 --- a/tests/page/page-aria-snapshot-ai.spec.ts +++ b/tests/page/page-aria-snapshot-ai.spec.ts @@ -101,6 +101,10 @@ it('should stitch all frame snapshots', async ({ page, server }) => { const locator = await (page.locator('aria-ref=f2e2').describe('foo bar') as any)._generateLocatorString(); expect(locator).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`); } + { + const error = await (page.locator('aria-ref=e1000') as any)._generateLocatorString().catch(e => e); + expect(error.message).toContain(`No element matching locator('aria-ref=e1000')`); + } }); it('should not generate refs for elements with pointer-events:none', async ({ page }) => { From c68c3f3f4ce9d786a87025938af035cd22cd42ba Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 24 Jun 2025 16:20:49 +0200 Subject: [PATCH 104/222] test: make babel test pass under Node.js 24 (#36420) --- tests/playwright-test/babel.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/playwright-test/babel.spec.ts b/tests/playwright-test/babel.spec.ts index 340fda91ad584..0d7572de5ba1a 100644 --- a/tests/playwright-test/babel.spec.ts +++ b/tests/playwright-test/babel.spec.ts @@ -145,11 +145,15 @@ test('should not transform external', async ({ runInlineTest }) => { 'a.spec.ts': ` const { test, expect, Page } = require('@playwright/test'); let page: Page; - test('succeeds', () => {}); + enum MyEnum { Value = 'value' } + + test('succeeds', () => { + expect(MyEnum.Value).toBe('value'); + }); ` }); expect(result.exitCode).toBe(1); - expect(result.output).toContain(`SyntaxError: Unexpected token ':'`); + expect(result.output).toMatch(/(SyntaxError: Unexpected token ':')|(SyntaxError: TypeScript enum is not supported)/); }); for (const type of ['module', undefined]) { From 17a5f4252d661971ad4e0d943190afb2f5d68538 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 24 Jun 2025 16:24:22 +0200 Subject: [PATCH 105/222] test: remove usage of url.parse (#36419) --- tests/config/testserver/index.ts | 22 +++++++++++----------- tests/third_party/proxy/index.ts | 6 ++---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/config/testserver/index.ts b/tests/config/testserver/index.ts index 7538badf9de6c..91f8d8840fb12 100644 --- a/tests/config/testserver/index.ts +++ b/tests/config/testserver/index.ts @@ -20,7 +20,6 @@ import type http from 'http'; import mime from 'mime'; import type net from 'net'; import path from 'path'; -import url from 'url'; import util from 'util'; import type stream from 'stream'; import ws from 'ws'; @@ -90,7 +89,7 @@ export class TestServer { this._upgradeCallback({ doUpgrade, socket }); return; } - const pathname = url.parse(request.url!).path; + const pathname = new URL(request.url, 'http://localhost').pathname; if (pathname === '/ws-401') { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\nUnauthorized body'); socket.destroy(); @@ -218,10 +217,11 @@ export class TestServer { }); request.on('end', () => resolve(Buffer.concat(chunks))); }); - const path = url.parse(request.url!).path; - this.debugServer(`request ${request.method} ${path}`); - if (this._auths.has(path)) { - const auth = this._auths.get(path)!; + const url = new URL(request.url, 'http://localhost'); + const pathWithSearch = url.pathname + url.search; + this.debugServer(`request ${request.method} ${pathWithSearch}`); + if (this._auths.has(pathWithSearch)) { + const auth = this._auths.get(pathWithSearch)!; const credentials = Buffer.from((request.headers.authorization || '').split(' ')[1] || '', 'base64').toString(); this.debugServer(`request credentials ${credentials}`); this.debugServer(`actual credentials ${auth.username}:${auth.password}`); @@ -233,11 +233,11 @@ export class TestServer { } } // Notify request subscriber. - if (this._requestSubscribers.has(path)) { - this._requestSubscribers.get(path)![fulfillSymbol].call(null, request); - this._requestSubscribers.delete(path); + if (this._requestSubscribers.has(pathWithSearch)) { + this._requestSubscribers.get(pathWithSearch)![fulfillSymbol].call(null, request); + this._requestSubscribers.delete(pathWithSearch); } - const handler = this._routes.get(path); + const handler = this._routes.get(pathWithSearch); if (handler) handler.call(null, request, response); else @@ -251,7 +251,7 @@ export class TestServer { } private async _serveFile(request: http.IncomingMessage, response: http.ServerResponse, filePath?: string): Promise { - let pathName = url.parse(request.url!).path; + let pathName = new URL(request.url, 'http://localhost').pathname; if (!filePath) { if (pathName === '/') pathName = '/index.html'; diff --git a/tests/third_party/proxy/index.ts b/tests/third_party/proxy/index.ts index e3faaec65741d..b3b4c450f2d65 100644 --- a/tests/third_party/proxy/index.ts +++ b/tests/third_party/proxy/index.ts @@ -1,6 +1,5 @@ import assert from 'assert'; import * as net from 'net'; -import * as url from 'url'; import * as http from 'http'; import * as os from 'os'; import { pipeline } from 'stream/promises'; @@ -101,7 +100,7 @@ async function onrequest( } socket.resume(); - const parsed = url.parse(req.url || '/'); + const parsed = new URL(req.url, 'http://localhost'); // setup outbound proxy request HTTP headers const headers: http.OutgoingHttpHeaders = {}; @@ -197,8 +196,7 @@ async function onrequest( } let gotResponse = false; - const proxyReq = http.request({ - ...parsed, + const proxyReq = http.request(parsed, { method: req.method, headers, localAddress: this.localAddress, From 1c3488aa5f0454135ca954fd8828bc0935e1a468 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:22:05 +0200 Subject: [PATCH 106/222] feat(chromium-tip-of-tree): roll to r1343 (#36424) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 3b75b5f38177c..32e2538b6ab9a 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,15 +15,15 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1342", + "revision": "1343", "installByDefault": false, - "browserVersion": "139.0.7248.0" + "browserVersion": "139.0.7258.0" }, { "name": "chromium-tip-of-tree-headless-shell", - "revision": "1342", + "revision": "1343", "installByDefault": false, - "browserVersion": "139.0.7248.0" + "browserVersion": "139.0.7258.0" }, { "name": "firefox", From 86c2ee6f3cf83e26972e5bb06a3d537a88b47d63 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 24 Jun 2025 19:53:57 +0100 Subject: [PATCH 107/222] chore: do not select a disabled option (#36418) --- packages/injected/src/injectedScript.ts | 4 +- packages/injected/src/roleUtils.ts | 8 ++- packages/playwright-core/src/server/dom.ts | 6 +- tests/page/page-select-option.spec.ts | 64 +++++++++++++++++++++- tests/page/selectors-role.spec.ts | 21 +++++++ 5 files changed, 98 insertions(+), 5 deletions(-) diff --git a/packages/injected/src/injectedScript.ts b/packages/injected/src/injectedScript.ts index bcc6be892e4e9..e31bf7b1c42f3 100644 --- a/packages/injected/src/injectedScript.ts +++ b/packages/injected/src/injectedScript.ts @@ -748,7 +748,7 @@ export class InjectedScript { throw this.createStacklessError(`Unexpected element state "${state}"`); } - selectOptions(node: Node, optionsToSelect: (Node | { valueOrLabel?: string, value?: string, label?: string, index?: number })[]): string[] | 'error:notconnected' | 'error:optionsnotfound' { + selectOptions(node: Node, optionsToSelect: (Node | { valueOrLabel?: string, value?: string, label?: string, index?: number })[]): string[] | 'error:notconnected' | 'error:optionsnotfound' | 'error:optionnotenabled' { const element = this.retarget(node, 'follow-label'); if (!element) return 'error:notconnected'; @@ -776,6 +776,8 @@ export class InjectedScript { }; if (!remainingOptionsToSelect.some(filter)) continue; + if (!this.elementState(option, 'enabled').matches) + return 'error:optionnotenabled'; selectedOptions.push(option); if (select.multiple) { remainingOptionsToSelect = remainingOptionsToSelect.filter(o => !filter(o)); diff --git a/packages/injected/src/roleUtils.ts b/packages/injected/src/roleUtils.ts index 139b26bb6394b..75bb580dad60c 100644 --- a/packages/injected/src/roleUtils.ts +++ b/packages/injected/src/roleUtils.ts @@ -1060,8 +1060,12 @@ export function getAriaDisabled(element: Element): boolean { function isNativelyDisabled(element: Element) { // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings - const isNativeFormControl = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTION', 'OPTGROUP'].includes(element.tagName); - return isNativeFormControl && (element.hasAttribute('disabled') || belongsToDisabledFieldSet(element)); + const isNativeFormControl = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTION', 'OPTGROUP'].includes(elementSafeTagName(element)); + return isNativeFormControl && (element.hasAttribute('disabled') || belongsToDisabledOptGroup(element) || belongsToDisabledFieldSet(element)); +} + +function belongsToDisabledOptGroup(element: Element): boolean { + return elementSafeTagName(element) === 'OPTION' && !!element.closest('OPTGROUP[DISABLED]'); } function belongsToDisabledFieldSet(element: Element): boolean { diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index ffb61584fc2cd..60abad7dfb3b6 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -38,7 +38,7 @@ export type InputFilesItems = { }; type ActionName = 'click' | 'hover' | 'dblclick' | 'tap' | 'move and up' | 'move and down'; -type PerformActionResult = 'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:optionsnotfound' | { missingState: ElementState } | { hitTargetDescription: string } | 'done'; +type PerformActionResult = 'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:optionsnotfound' | 'error:optionnotenabled' | { missingState: ElementState } | { hitTargetDescription: string } | 'done'; export class NonRecoverableDOMError extends Error { } @@ -336,6 +336,10 @@ export class ElementHandle extends js.JSHandle { progress.log(' did not find some options'); continue; } + if (result === 'error:optionnotenabled') { + progress.log(' option being selected is not enabled'); + continue; + } if (typeof result === 'object' && 'hitTargetDescription' in result) { progress.log(` ${result.hitTargetDescription} intercepts pointer events`); continue; diff --git a/tests/page/page-select-option.spec.ts b/tests/page/page-select-option.spec.ts index 160996446d61a..0355b4592bbd8 100644 --- a/tests/page/page-select-option.spec.ts +++ b/tests/page/page-select-option.spec.ts @@ -320,7 +320,7 @@ it('input event.composed should be true and cross shadow dom boundary', async ({ expect(await page.evaluate(() => window['firedBodyEvents'])).toEqual(['input:true']); }); -it('should wait for option to be enabled', async ({ page }) => { +it('should wait for select to be enabled', async ({ page }) => { await page.setContent(` + + + + + + `); + + const error = await page.locator('select').selectOption('two', { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('option being selected is not enabled'); + + const selectPromise = page.locator('select').selectOption('two'); + await new Promise(f => setTimeout(f, 1000)); + await page.evaluate(() => (window as any).hydrate()); + await selectPromise; + expect(await page.evaluate(() => window['result'])).toEqual('two'); + await expect(page.locator('select')).toHaveValue('two'); +}); + +it('should wait for optgroup to be enabled', async ({ page }) => { + await page.setContent(` + + + + `); + + const error = await page.locator('select').selectOption('two', { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('option being selected is not enabled'); + + const selectPromise = page.locator('select').selectOption('two'); + await new Promise(f => setTimeout(f, 1000)); + await page.evaluate(() => (window as any).hydrate()); + await selectPromise; + expect(await page.evaluate(() => window['result'])).toEqual('two'); + await expect(page.locator('select')).toHaveValue('two'); +}); + it('should wait for select to be swapped', async ({ page }) => { await page.setContent(` + + + + + + + + `); expect(await page.locator(`role=button[disabled]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([ ``, @@ -241,6 +250,18 @@ test('should support disabled', async ({ page }) => { ``, ``, ]); + expect(await page.getByRole('option', { disabled: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([ + ``, + ``, + ]); + expect(await page.getByRole('option', { disabled: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([ + ``, + ]); + expect(await page.getByRole('option').evaluateAll(els => els.map(e => e.outerHTML))).toEqual([ + ``, + ``, + ``, + ]); }); test('should inherit disabled from the ancestor', async ({ page }) => { From 04b10c56c1bdcba5e0722009822a495cbc7407d7 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 24 Jun 2025 12:05:19 -0700 Subject: [PATCH 108/222] chore: bump commander.js version to 13 (#36426) --- packages/playwright-core/ThirdPartyNotices.txt | 6 +++--- .../playwright-core/bundles/utils/package-lock.json | 11 ++++++----- packages/playwright-core/bundles/utils/package.json | 2 +- .../playwright-core/src/cli/programWithTestStub.ts | 4 +++- packages/playwright/src/program.ts | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index 744720e00d113..d2aeb156ad027 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -10,7 +10,7 @@ This project incorporates components from the projects listed below. The origina - buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32) - codemirror@5.65.18 (https://github.com/codemirror/CodeMirror) - colors@1.4.0 (https://github.com/Marak/colors.js) -- commander@8.3.0 (https://github.com/tj/commander.js) +- commander@13.1.0 (https://github.com/tj/commander.js) - concat-map@0.0.1 (https://github.com/substack/node-concat-map) - debug@4.3.4 (https://github.com/debug-js/debug) - debug@4.4.0 (https://github.com/debug-js/debug) @@ -331,7 +331,7 @@ THE SOFTWARE. ========================================= END OF colors@1.4.0 AND INFORMATION -%% commander@8.3.0 NOTICES AND INFORMATION BEGIN HERE +%% commander@13.1.0 NOTICES AND INFORMATION BEGIN HERE ========================================= (The MIT License) @@ -356,7 +356,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF commander@8.3.0 AND INFORMATION +END OF commander@13.1.0 AND INFORMATION %% concat-map@0.0.1 NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/packages/playwright-core/bundles/utils/package-lock.json b/packages/playwright-core/bundles/utils/package-lock.json index bc58353c99f08..7646662695132 100644 --- a/packages/playwright-core/bundles/utils/package-lock.json +++ b/packages/playwright-core/bundles/utils/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "dependencies": { "colors": "1.4.0", - "commander": "8.3.0", + "commander": "^13.0.0", "debug": "^4.3.4", "diff": "^7.0.0", "dotenv": "^16.4.5", @@ -173,11 +173,12 @@ } }, "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=18" } }, "node_modules/concat-map": { diff --git a/packages/playwright-core/bundles/utils/package.json b/packages/playwright-core/bundles/utils/package.json index 7e7ff502ba7bc..c0559de57d1dd 100644 --- a/packages/playwright-core/bundles/utils/package.json +++ b/packages/playwright-core/bundles/utils/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "colors": "1.4.0", - "commander": "8.3.0", + "commander": "^13.0.0", "debug": "^4.3.4", "diff": "^7.0.0", "dotenv": "^16.4.5", diff --git a/packages/playwright-core/src/cli/programWithTestStub.ts b/packages/playwright-core/src/cli/programWithTestStub.ts index 29b5d87ed143e..e7c42575260ee 100644 --- a/packages/playwright-core/src/cli/programWithTestStub.ts +++ b/packages/playwright-core/src/cli/programWithTestStub.ts @@ -55,7 +55,9 @@ const kExternalPlaywrightTestCommands = [ ]; function addExternalPlaywrightTestCommands() { for (const [command, description] of kExternalPlaywrightTestCommands) { - const playwrightTest = program.command(command).allowUnknownOption(true); + const playwrightTest = program.command(command) + .allowUnknownOption(true) + .allowExcessArguments(true); playwrightTest.description(`${description} Available in @playwright/test package.`); playwrightTest.action(async () => { printPlaywrightTestError(command); diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 874f645001ed7..9907a5a18ce6d 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -382,7 +382,7 @@ const testOptions: [string, string][] = [ ['--fully-parallel', `Run all tests in parallel (default: false)`], ['--global-timeout ', `Maximum time this test suite can run in milliseconds (default: unlimited)`], ['-g, --grep ', `Only run tests matching this regular expression (default: ".*")`], - ['-gv, --grep-invert ', `Only run tests that do not match this regular expression`], + ['--grep-invert ', `Only run tests that do not match this regular expression`], ['--headed', `Run tests in headed browsers (default: headless)`], ['--ignore-snapshots', `Ignore screenshot and snapshot expectations`], ['--last-failed', `Only re-run the failures`], From a41e16bed0042b0d1f0b1658ae87ebe52aa726e8 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 24 Jun 2025 21:22:43 +0100 Subject: [PATCH 109/222] chore: use a single Progress instance for expect (#36407) --- packages/playwright-core/src/server/frames.ts | 70 ++++++++----------- .../playwright-core/src/server/progress.ts | 47 ++++++++++--- 2 files changed, 66 insertions(+), 51 deletions(-) diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index ecb5757c397e3..d428f79eb953e 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -27,7 +27,7 @@ import * as network from './network'; import { Page } from './page'; import { isAbortError, ProgressController } from './progress'; import * as types from './types'; -import { LongStandingScope, asLocator, assert, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime, renderTitleForCall } from '../utils'; +import { LongStandingScope, asLocator, assert, constructURLBasedOnBaseURL, makeWaitForNextTask, renderTitleForCall } from '../utils'; import { isSessionClosedError } from './protocolError'; import { debugLogger } from './utils/debugLogger'; import { eventsHelper } from './utils/eventsHelper'; @@ -1417,26 +1417,21 @@ export class Frame extends SdkObject { } private async _expectImpl(metadata: CallMetadata, selector: string | undefined, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }> { + const controller = new ProgressController(metadata, this); const lastIntermediateResult: { received?: any, isSet: boolean } = { isSet: false }; - try { - let timeout = options.timeout; - const start = timeout > 0 ? monotonicTime() : 0; - + return await controller.run(async progress => { // Step 1: perform locator handlers checkpoint with a specified timeout. - await (new ProgressController(metadata, this)).run(async progress => { - progress.log(`${renderTitleForCall(metadata)}${timeout ? ` with timeout ${timeout}ms` : ''}`); - if (selector) - progress.log(`waiting for ${this._asLocator(selector)}`); - await this._page.performActionPreChecks(progress); - }, timeout); + progress.log(`${renderTitleForCall(metadata)}${options.timeout ? ` with timeout ${options.timeout}ms` : ''}`); + if (selector) + progress.log(`waiting for ${this._asLocator(selector)}`); + await this._page.performActionPreChecks(progress); // Step 2: perform one-shot expect check without a timeout. // Supports the case of `expect(locator).toBeVisible({ timeout: 1 })` // that should succeed when the locator is already visible. + progress.legacyDisableTimeout(); try { - const resultOneShot = await (new ProgressController(metadata, this)).run(async progress => { - return await this._expectInternal(progress, selector, options, lastIntermediateResult); - }); + const resultOneShot = await this._expectInternal(progress, selector, options, lastIntermediateResult, true); if (resultOneShot.matches !== options.isNot) return resultOneShot; } catch (e) { @@ -1444,28 +1439,21 @@ export class Frame extends SdkObject { throw e; // Ignore any other errors from one-shot, we'll handle them during retries. } - if (timeout > 0) { - const elapsed = monotonicTime() - start; - timeout -= elapsed; - } - if (timeout < 0) - return { matches: options.isNot, log: compressCallLog(metadata.log), timedOut: true, received: lastIntermediateResult.received }; + progress.legacyEnableTimeout(); // Step 3: auto-retry expect with increasing timeouts. Bounded by the total remaining time. - return await (new ProgressController(metadata, this)).run(async progress => { - return await this.retryWithProgressAndTimeouts(progress, [100, 250, 500, 1000], async continuePolling => { - await this._page.performActionPreChecks(progress); - const { matches, received } = await this._expectInternal(progress, selector, options, lastIntermediateResult); - if (matches === options.isNot) { - // Keep waiting in these cases: - // expect(locator).conditionThatDoesNotMatch - // expect(locator).not.conditionThatDoesMatch - return continuePolling; - } - return { matches, received }; - }); - }, timeout); - } catch (e) { + return await this.retryWithProgressAndTimeouts(progress, [100, 250, 500, 1000], async continuePolling => { + await this._page.performActionPreChecks(progress); + const { matches, received } = await this._expectInternal(progress, selector, options, lastIntermediateResult, false); + if (matches === options.isNot) { + // Keep waiting in these cases: + // expect(locator).conditionThatDoesNotMatch + // expect(locator).not.conditionThatDoesMatch + return continuePolling; + } + return { matches, received }; + }); + }, options.timeout).catch(e => { // Q: Why not throw upon isNonRetriableError(e) as in other places? // A: We want user to receive a friendly message containing the last intermediate result. if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e)) @@ -1476,18 +1464,20 @@ export class Frame extends SdkObject { if (e instanceof TimeoutError) result.timedOut = true; return result; - } + }); } - private async _expectInternal(progress: Progress, selector: string | undefined, options: FrameExpectParams, lastIntermediateResult: { received?: any, isSet: boolean }) { - const selectorInFrame = selector ? await progress.race(this.selectors.resolveFrameForSelector(selector, { strict: true })) : undefined; + private async _expectInternal(progress: Progress, selector: string | undefined, options: FrameExpectParams, lastIntermediateResult: { received?: any, isSet: boolean }, noAbort: boolean) { + // The first expect check, a.k.a. one-shot, always finishes - even when progress is aborted. + const race = (p: Promise) => noAbort ? p : progress.race(p); + const selectorInFrame = selector ? await race(this.selectors.resolveFrameForSelector(selector, { strict: true })) : undefined; const { frame, info } = selectorInFrame || { frame: this, info: undefined }; const world = options.expression === 'to.have.property' ? 'main' : (info?.world ?? 'utility'); - const context = await progress.race(frame._context(world)); - const injected = await progress.race(context.injectedScript()); + const context = await race(frame._context(world)); + const injected = await race(context.injectedScript()); - const { log, matches, received, missingReceived } = await progress.race(injected.evaluate(async (injected, { info, options, callId }) => { + const { log, matches, received, missingReceived } = await race(injected.evaluate(async (injected, { info, options, callId }) => { const elements = info ? injected.querySelectorAll(info.parsed, document) : []; if (callId) injected.markTargetElements(new Set(elements), callId); diff --git a/packages/playwright-core/src/server/progress.ts b/packages/playwright-core/src/server/progress.ts index 4e4c8a0ac4f1e..fe680785720bd 100644 --- a/packages/playwright-core/src/server/progress.ts +++ b/packages/playwright-core/src/server/progress.ts @@ -15,7 +15,7 @@ */ import { TimeoutError } from './errors'; -import { assert } from '../utils'; +import { assert, monotonicTime } from '../utils'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation'; @@ -43,6 +43,10 @@ export interface Progress { raceWithCleanup(promise: Promise, cleanup: (result: T) => any): Promise; wait(timeout: number): Promise; metadata: CallMetadata; + + // Legacy lenient mode api only. To be removed. + legacyDisableTimeout(): void; + legacyEnableTimeout(): void; } export class ProgressController { @@ -93,6 +97,26 @@ export class ProgressController { this._state = 'running'; this.sdkObject.attribution.context?._activeProgressControllers.add(this); + const deadline = timeout ? Math.min(monotonicTime() + timeout, 2147483647) : 0; // 2^31-1 safe setTimeout in Node. + const timeoutError = new TimeoutError(`Timeout ${timeout}ms exceeded.`); + + let timer: NodeJS.Timeout | undefined; + const startTimer = () => { + if (!deadline) + return; + const onTimeout = () => { + if (this._state === 'running') { + this._state = { error: timeoutError }; + this._forceAbortPromise.reject(timeoutError); + } + }; + const remaining = deadline - monotonicTime(); + if (remaining <= 0) + onTimeout(); + else + timer = setTimeout(onTimeout, remaining); + }; + const progress: Progress = { log: message => { if (this._state === 'running') @@ -128,18 +152,19 @@ export class ProgressController { const promise = new Promise(f => timer = setTimeout(f, timeout)); return progress.race(promise).finally(() => clearTimeout(timer)); }, + legacyDisableTimeout: () => { + if (this._strictMode) + return; + clearTimeout(timer); + }, + legacyEnableTimeout: () => { + if (this._strictMode) + return; + startTimer(); + }, }; - let timer: NodeJS.Timeout | undefined; - if (timeout) { - const timeoutError = new TimeoutError(`Timeout ${timeout}ms exceeded.`); - timer = setTimeout(() => { - if (this._state === 'running') { - this._state = { error: timeoutError }; - this._forceAbortPromise.reject(timeoutError); - } - }, Math.min(timeout, 2147483647)); // 2^31-1 safe setTimeout in Node. - } + startTimer(); try { const promise = task(progress); From 25e64e976ead84c46be7a185cf4aaacdc01ca7fe Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 24 Jun 2025 13:27:28 -0700 Subject: [PATCH 110/222] fix(cli): throw an error if invalid choice is specified for --update-snapshots option (#36427) --- packages/playwright/src/program.ts | 92 +++++++++++++++--------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 9907a5a18ce6d..c2f5f4540ce7e 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -44,7 +44,17 @@ function addTestCommand(program: Command) { const command = program.command('test [test-filter...]'); command.description('run tests with Playwright Test'); const options = testOptions.sort((a, b) => a[0].replace(/-/g, '').localeCompare(b[0].replace(/-/g, ''))); - options.forEach(([name, description]) => command.option(name, description)); + options.forEach(([name, { description, choices, preset }]) => { + const option = command.createOption(name, description); + if (choices) + option.choices(choices); + if (preset) + option.preset(preset); + // We don't set the default value here, because we want not specified options to + // fall back to the user config, which we haven't parsed yet. + command.addOption(option); + return command; + }); command.action(async (args, opts) => { try { await runTests(args, opts); @@ -269,12 +279,6 @@ async function mergeReports(reportDir: string | undefined, opts: { [key: string] } function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrides { - let updateSnapshots: 'all' | 'changed' | 'missing' | 'none' | undefined; - if (['all', 'changed', 'missing', 'none'].includes(options.updateSnapshots)) - updateSnapshots = options.updateSnapshots; - else - updateSnapshots = 'updateSnapshots' in options ? 'changed' : undefined; - const overrides: ConfigCLIOverrides = { failOnFlakyTests: options.failOnFlakyTests ? true : undefined, forbidOnly: options.forbidOnly ? true : undefined, @@ -290,7 +294,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid timeout: options.timeout ? parseInt(options.timeout, 10) : undefined, tsconfig: options.tsconfig ? path.resolve(process.cwd(), options.tsconfig) : undefined, ignoreSnapshots: options.ignoreSnapshots ? !!options.ignoreSnapshots : undefined, - updateSnapshots, + updateSnapshots: options.updateSnapshots, updateSourceMethod: options.updateSourceMethod, workers: options.workers, }; @@ -315,8 +319,6 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid process.env.PWDEBUG = '1'; } if (!options.ui && options.trace) { - if (!kTraceModes.includes(options.trace)) - throw new Error(`Unsupported trace mode "${options.trace}", must be one of ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`); overrides.use = overrides.use || {}; overrides.use.trace = options.trace; } @@ -373,41 +375,41 @@ const kTraceModes: TraceMode[] = ['on', 'off', 'on-first-retry', 'on-all-retries // Note: update docs/src/test-cli-js.md when you update this, program is the source of truth. -const testOptions: [string, string][] = [ - /* deprecated */ ['--browser ', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`], - ['-c, --config ', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`], - ['--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`], - ['--fail-on-flaky-tests', `Fail if any test is flagged as flaky (default: false)`], - ['--forbid-only', `Fail if test.only is called (default: false)`], - ['--fully-parallel', `Run all tests in parallel (default: false)`], - ['--global-timeout ', `Maximum time this test suite can run in milliseconds (default: unlimited)`], - ['-g, --grep ', `Only run tests matching this regular expression (default: ".*")`], - ['--grep-invert ', `Only run tests that do not match this regular expression`], - ['--headed', `Run tests in headed browsers (default: headless)`], - ['--ignore-snapshots', `Ignore screenshot and snapshot expectations`], - ['--last-failed', `Only re-run the failures`], - ['--list', `Collect all the tests and report them, but do not run`], - ['--max-failures ', `Stop after the first N failures`], - ['--no-deps', 'Do not run project dependencies'], - ['--output ', `Folder for output artifacts (default: "test-results")`], - ['--only-changed [ref]', `Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.`], - ['--pass-with-no-tests', `Makes test run succeed even if no tests were found`], - ['--project ', `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)`], - ['--quiet', `Suppress stdio`], - ['--repeat-each ', `Run each test N times (default: 1)`], - ['--reporter ', `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${defaultReporter}")`], - ['--retries ', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`], - ['--shard ', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`], - ['--timeout ', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`], - ['--trace ', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`], - ['--tsconfig ', `Path to a single tsconfig applicable to all imported files (default: look up tsconfig for each imported file separately)`], - ['--ui', `Run tests in interactive UI mode`], - ['--ui-host ', 'Host to serve UI on; specifying this option opens UI in a browser tab'], - ['--ui-port ', 'Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab'], - ['-u, --update-snapshots [mode]', `Update snapshots with actual results. Possible values are "all", "changed", "missing", and "none". Running tests without the flag defaults to "missing"; running tests with the flag but without a value defaults to "changed".`], - ['--update-source-method ', `Chooses the way source is updated. Possible values are 'overwrite', '3way' and 'patch'. Defaults to 'patch'`], - ['-j, --workers ', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`], - ['-x', `Stop after the first failure`], +const testOptions: [string, { description: string, choices?: string[], preset?: string }][] = [ + /* deprecated */ ['--browser ', { description: `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")` }], + ['-c, --config ', { description: `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"` }], + ['--debug', { description: `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options` }], + ['--fail-on-flaky-tests', { description: `Fail if any test is flagged as flaky (default: false)` }], + ['--forbid-only', { description: `Fail if test.only is called (default: false)` }], + ['--fully-parallel', { description: `Run all tests in parallel (default: false)` }], + ['--global-timeout ', { description: `Maximum time this test suite can run in milliseconds (default: unlimited)` }], + ['-g, --grep ', { description: `Only run tests matching this regular expression (default: ".*")` }], + ['--grep-invert ', { description: `Only run tests that do not match this regular expression` }], + ['--headed', { description: `Run tests in headed browsers (default: headless)` }], + ['--ignore-snapshots', { description: `Ignore screenshot and snapshot expectations` }], + ['--last-failed', { description: `Only re-run the failures` }], + ['--list', { description: `Collect all the tests and report them, but do not run` }], + ['--max-failures ', { description: `Stop after the first N failures` }], + ['--no-deps', { description: `Do not run project dependencies` }], + ['--output ', { description: `Folder for output artifacts (default: "test-results")` }], + ['--only-changed [ref]', { description: `Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.` }], + ['--pass-with-no-tests', { description: `Makes test run succeed even if no tests were found` }], + ['--project ', { description: `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)` }], + ['--quiet', { description: `Suppress stdio` }], + ['--repeat-each ', { description: `Run each test N times (default: 1)` }], + ['--reporter ', { description: `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${defaultReporter}")` }], + ['--retries ', { description: `Maximum retry count for flaky tests, zero for no retries (default: no retries)` }], + ['--shard ', { description: `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"` }], + ['--timeout ', { description: `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})` }], + ['--trace ', { description: `Force tracing mode`, choices: kTraceModes as string[] }], + ['--tsconfig ', { description: `Path to a single tsconfig applicable to all imported files (default: look up tsconfig for each imported file separately)` }], + ['--ui', { description: `Run tests in interactive UI mode` }], + ['--ui-host ', { description: `Host to serve UI on; specifying this option opens UI in a browser tab` }], + ['--ui-port ', { description: `Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab` }], + ['-u, --update-snapshots [mode]', { description: `Update snapshots with actual results. Running tests without the flag defaults to "missing"`, choices: ['all', 'changed', 'missing', 'none'], preset: 'changed' }], + ['--update-source-method ', { description: `Chooses the way source is updated (default: "patch")`, choices: ['overwrite', '3way', 'patch'] }], + ['-j, --workers ', { description: `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)` }], + ['-x', { description: `Stop after the first failure` }], ]; addTestCommand(program); From 6b231cbf791aa0edf3285456ab07f571d474efdb Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 24 Jun 2025 16:43:12 -0700 Subject: [PATCH 111/222] chore: add playwright-mdd experiment (#36430) --- package-lock.json | 120 ++++++++- packages/playwright-mdd/cli.js | 19 ++ packages/playwright-mdd/package.json | 33 +++ packages/playwright-mdd/src/context.ts | 232 ++++++++++++++++++ packages/playwright-mdd/src/format.ts | 53 ++++ packages/playwright-mdd/src/loop.ts | 108 ++++++++ packages/playwright-mdd/src/manualPromise.ts | 127 ++++++++++ packages/playwright-mdd/src/program.ts | 50 ++++ packages/playwright-mdd/src/tools.ts | 27 ++ packages/playwright-mdd/src/tools/done.ts | 39 +++ packages/playwright-mdd/src/tools/navigate.ts | 88 +++++++ packages/playwright-mdd/src/tools/snapshot.ts | 194 +++++++++++++++ packages/playwright-mdd/src/tools/tool.ts | 60 +++++ packages/playwright-mdd/src/tools/utils.ts | 91 +++++++ utils/workspace.js | 5 + 15 files changed, 1234 insertions(+), 12 deletions(-) create mode 100755 packages/playwright-mdd/cli.js create mode 100644 packages/playwright-mdd/package.json create mode 100644 packages/playwright-mdd/src/context.ts create mode 100644 packages/playwright-mdd/src/format.ts create mode 100644 packages/playwright-mdd/src/loop.ts create mode 100644 packages/playwright-mdd/src/manualPromise.ts create mode 100644 packages/playwright-mdd/src/program.ts create mode 100644 packages/playwright-mdd/src/tools.ts create mode 100644 packages/playwright-mdd/src/tools/done.ts create mode 100644 packages/playwright-mdd/src/tools/navigate.ts create mode 100644 packages/playwright-mdd/src/tools/snapshot.ts create mode 100644 packages/playwright-mdd/src/tools/tool.ts create mode 100644 packages/playwright-mdd/src/tools/utils.ts diff --git a/package-lock.json b/package-lock.json index 3ac58bc145d17..da1d9b61133cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1373,6 +1373,10 @@ "resolved": "packages/playwright-ct-vue", "link": true }, + "node_modules/@playwright/mdd": { + "resolved": "packages/playwright-mdd", + "link": true + }, "node_modules/@playwright/test": { "resolved": "packages/playwright-test", "link": true @@ -1754,6 +1758,16 @@ "@types/tern": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1812,6 +1826,13 @@ "@types/node": "*" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "18.19.76", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", @@ -2978,6 +2999,15 @@ "node": ">=0.1.90" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3139,9 +3169,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3305,10 +3335,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "dev": true, + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5933,6 +5962,27 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.7.0.tgz", + "integrity": "sha512-zXWawZl6J/P5Wz57/nKzVT3kJQZvogfuyuNVCdEp4/XU2UNrjL7SsuNpWAyLZbo6HVymwmnfno9toVzBhelygA==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7809,10 +7859,10 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -7956,12 +8006,20 @@ "version": "3.24.2", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "packages/html-reporter": { "version": "0.0.0" }, @@ -8706,6 +8764,44 @@ "node": ">=18" } }, + "packages/playwright-mdd": { + "name": "@playwright/mdd", + "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "commander": "^13.1.0", + "debug": "^4.4.1", + "dotenv": "^16.5.0", + "mime": "^4.0.7", + "openai": "^5.7.0", + "playwright-core": "1.54.0-next", + "zod-to-json-schema": "^3.24.4" + }, + "bin": { + "playwright-mdd": "cli.js" + }, + "devDependencies": { + "@types/debug": "^4.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "packages/playwright-mdd/node_modules/mime": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", + "integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "packages/playwright-test": { "name": "@playwright/test", "version": "1.54.0-next", diff --git a/packages/playwright-mdd/cli.js b/packages/playwright-mdd/cli.js new file mode 100755 index 0000000000000..7ce5384a0c650 --- /dev/null +++ b/packages/playwright-mdd/cli.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { program } = require('./lib/program'); +void program.parseAsync(process.argv); diff --git a/packages/playwright-mdd/package.json b/packages/playwright-mdd/package.json new file mode 100644 index 0000000000000..f5131e5ed070e --- /dev/null +++ b/packages/playwright-mdd/package.json @@ -0,0 +1,33 @@ +{ + "name": "@playwright/mdd", + "version": "0.0.1", + "description": "Playwright MDD", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/microsoft/playwright.git" + }, + "homepage": "https://playwright.dev", + "engines": { + "node": ">=18" + }, + "author": { + "name": "Microsoft Corporation" + }, + "license": "Apache-2.0", + "dependencies": { + "commander": "^13.1.0", + "debug": "^4.4.1", + "dotenv": "^16.5.0", + "mime": "^4.0.7", + "openai": "^5.7.0", + "playwright-core": "1.54.0-next", + "zod-to-json-schema": "^3.24.4" + }, + "devDependencies": { + "@types/debug": "^4.1.7" + }, + "bin": { + "playwright-mdd": "cli.js" + } +} diff --git a/packages/playwright-mdd/src/context.ts b/packages/playwright-mdd/src/context.ts new file mode 100644 index 0000000000000..d998e51247c11 --- /dev/null +++ b/packages/playwright-mdd/src/context.ts @@ -0,0 +1,232 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import debug from 'debug'; +import * as playwright from 'playwright'; + +import { callOnPageNoTrace, waitForCompletion } from './tools/utils'; +import { ManualPromise } from './manualPromise'; + +import type { ModalState, Tool, ToolActionResult } from './tools/tool'; + +type PendingAction = { + dialogShown: ManualPromise; +}; + +type PageEx = playwright.Page & { + _snapshotForAI: () => Promise; +}; + +const testDebug = debug('pw:mcp:test'); + +export class Context { + readonly browser: playwright.Browser; + readonly page: playwright.Page; + readonly tools: Tool[]; + private _modalStates: ModalState[] = []; + private _pendingAction: PendingAction | undefined; + private _downloads: { download: playwright.Download, finished: boolean, outputFile: string }[] = []; + + constructor(browser: playwright.Browser, page: playwright.Page, tools: Tool[]) { + this.browser = browser; + this.page = page; + this.tools = tools; + testDebug('create context'); + } + + static async create(tools: Tool[]): Promise { + const browser = await playwright.chromium.launch({ + headless: false, + }); + const context = await browser.newContext(); + const page = await context.newPage(); + return new Context(browser, page, tools); + } + + async close() { + await this.browser.close(); + } + + modalStates(): ModalState[] { + return this._modalStates; + } + + setModalState(modalState: ModalState) { + this._modalStates.push(modalState); + } + + clearModalState(modalState: ModalState) { + this._modalStates = this._modalStates.filter(state => state !== modalState); + } + + modalStatesMarkdown(): string[] { + const result: string[] = ['### Modal state']; + if (this._modalStates.length === 0) + result.push('- There is no modal state present'); + for (const state of this._modalStates) { + const tool = this.tools.find(tool => tool.clearsModalState === state.type); + result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`); + } + return result; + } + + async run(tool: Tool, params: Record | undefined): Promise<{ content: string, code: string[] }> { + const toolResult = await tool.handle(this, tool.schema.inputSchema.parse(params || {})); + const { code, action, waitForNetwork, captureSnapshot } = toolResult; + const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined; + + if (waitForNetwork) + await waitForCompletion(this, async () => racingAction?.()); + else + await racingAction?.(); + + const result: string[] = []; + + if (this.modalStates().length) { + result.push(...this.modalStatesMarkdown()); + return { + code, + content: result.join('\n'), + }; + } + + if (this._downloads.length) { + result.push('', '### Downloads'); + for (const entry of this._downloads) { + if (entry.finished) + result.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`); + else + result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`); + } + result.push(''); + } + + result.push( + `- Page URL: ${this.page.url()}`, + `- Page Title: ${await this.title()}` + ); + + if (captureSnapshot && !this._javaScriptBlocked()) + result.push(await this._snapshot()); + + return { + code, + content: result.join('\n'), + }; + } + + async title(): Promise { + return await callOnPageNoTrace(this.page, page => page.title()); + } + + async waitForTimeout(time: number) { + if (this._javaScriptBlocked()) { + await new Promise(f => setTimeout(f, time)); + return; + } + + await callOnPageNoTrace(this.page, page => { + return page.evaluate(() => new Promise(f => setTimeout(f, 1000))); + }); + } + + async waitForLoadState(state: 'load', options?: { timeout?: number }): Promise { + await callOnPageNoTrace(this.page, page => page.waitForLoadState(state, options).catch(() => {})); + } + + async navigate(url: string) { + const downloadEvent = callOnPageNoTrace(this.page, page => page.waitForEvent('download').catch(() => {})); + try { + await this.page.goto(url, { waitUntil: 'domcontentloaded' }); + } catch (_e: unknown) { + const e = _e as Error; + const mightBeDownload = + e.message.includes('net::ERR_ABORTED') // chromium + || e.message.includes('Download is starting'); // firefox + webkit + if (!mightBeDownload) + throw e; + // on chromium, the download event is fired *after* page.goto rejects, so we wait a lil bit + const download = await Promise.race([ + downloadEvent, + new Promise(resolve => setTimeout(resolve, 1000)), + ]); + if (!download) + throw e; + } + + // Cap load event to 5 seconds, the page is operational at this point. + await this.waitForLoadState('load', { timeout: 5000 }); + } + + refLocator(params: { element: string, ref: string }): playwright.Locator { + return this.page.locator(`aria-ref=${params.ref}`).describe(params.element); + } + + private async _raceAgainstModalDialogs(action: () => Promise): Promise { + this._pendingAction = { + dialogShown: new ManualPromise(), + }; + + let result: ToolActionResult | undefined; + try { + await Promise.race([ + action().then(r => result = r), + this._pendingAction.dialogShown, + ]); + } finally { + this._pendingAction = undefined; + } + return result; + } + + private _javaScriptBlocked(): boolean { + return this._modalStates.some(state => state.type === 'dialog'); + } + + dialogShown(dialog: playwright.Dialog) { + this.setModalState({ + type: 'dialog', + description: `"${dialog.type()}" dialog with message "${dialog.message()}"`, + dialog, + }); + this._pendingAction?.dialogShown.resolve(); + } + + async downloadStarted(download: playwright.Download) { + const entry = { + download, + finished: false, + outputFile: this._outputFile(download.suggestedFilename()) + }; + this._downloads.push(entry); + await download.saveAs(entry.outputFile); + entry.finished = true; + } + + private async _snapshot() { + const snapshot = await callOnPageNoTrace(this.page, page => (page as PageEx)._snapshotForAI()); + return [ + `- Page Snapshot`, + '```yaml', + snapshot, + '```', + ].join('\n'); + } + + private _outputFile(filename: string) { + return filename; + } +} diff --git a/packages/playwright-mdd/src/format.ts b/packages/playwright-mdd/src/format.ts new file mode 100644 index 0000000000000..a1fabbd97d125 --- /dev/null +++ b/packages/playwright-mdd/src/format.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// adapted from: +// - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/utils/isomorphic/stringUtils.ts +// - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/server/codegen/javascript.ts + +// NOTE: this function should not be used to escape any selectors. +export function escapeWithQuotes(text: string, char: string = '\'') { + const stringified = JSON.stringify(text); + const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"'); + if (char === '\'') + return char + escapedText.replace(/[']/g, '\\\'') + char; + if (char === '"') + return char + escapedText.replace(/["]/g, '\\"') + char; + if (char === '`') + return char + escapedText.replace(/[`]/g, '`') + char; + throw new Error('Invalid escape char'); +} + +export function quote(text: string) { + return escapeWithQuotes(text, '\''); +} + +export function formatObject(value: any, indent = ' '): string { + if (typeof value === 'string') + return quote(value); + if (Array.isArray(value)) + return `[${value.map(o => formatObject(o)).join(', ')}]`; + if (typeof value === 'object') { + const keys = Object.keys(value).filter(key => value[key] !== undefined).sort(); + if (!keys.length) + return '{}'; + const tokens: string[] = []; + for (const key of keys) + tokens.push(`${key}: ${formatObject(value[key])}`); + return `{\n${indent}${tokens.join(`,\n${indent}`)}\n}`; + } + return String(value); +} diff --git a/packages/playwright-mdd/src/loop.ts b/packages/playwright-mdd/src/loop.ts new file mode 100644 index 0000000000000..64d715d3b2e64 --- /dev/null +++ b/packages/playwright-mdd/src/loop.ts @@ -0,0 +1,108 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import OpenAI from 'openai'; +import debug from 'debug'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from './tools/tool'; +import { Context } from './context'; + +/* eslint-disable no-console */ + +export async function runTasks(context: Context, tasks: string[]): Promise { + const openai = new OpenAI(); + const allCode: string[] = [ + `test('generated code', async ({ page }) => {`, + ]; + for (const task of tasks) { + const { taskCode } = await runTask(openai, context, task); + if (taskCode.length) + allCode.push('', ...taskCode.map(code => ` ${code}`)); + } + allCode.push('});'); + return allCode.join('\n'); +} + +async function runTask(openai: OpenAI, context: Context, task: string): Promise<{ taskCode: string[] }> { + console.log('Perform task:', task); + + const taskCode: string[] = [ + `// ${task}`, + ]; + + const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ + { + role: 'user', + content: `Peform following task: ${task}. Once the task is complete, call the "done" tool.` + } + ]; + + for (let iteration = 0; iteration < 5; ++iteration) { + debug('history')(messages); + const response = await openai.chat.completions.create({ + model: 'gpt-4.1', + messages, + tools: context.tools.map(asOpenAIDeclaration), + tool_choice: 'auto' + }); + + const message = response.choices[0].message; + if (!message.tool_calls?.length) + throw new Error('Unexpected response from LLM: ' + message.content); + + messages.push({ + role: 'assistant', + tool_calls: message.tool_calls + }); + + for (const toolCall of message.tool_calls) { + const functionCall = toolCall.function; + console.log('Call tool:', functionCall.name, functionCall.arguments); + + if (functionCall.name === 'done') + return { taskCode }; + + const tool = context.tools.find(tool => tool.schema.name === functionCall.name); + if (!tool) + throw new Error('Unknown tool: ' + functionCall.name); + + const { code, content } = await context.run(tool, JSON.parse(functionCall.arguments)); + taskCode.push(...code); + + messages.push({ + role: 'tool', + tool_call_id: toolCall.id, + content, + }); + } + } + throw new Error('Failed to perform step, max attempts reached'); +} + +function asOpenAIDeclaration(tool: Tool): OpenAI.Chat.Completions.ChatCompletionTool { + const parameters = zodToJsonSchema(tool.schema.inputSchema); + delete parameters.$schema; + delete (parameters as any).additionalProperties; + return { + type: 'function', + function: { + name: tool.schema.name, + description: tool.schema.description, + parameters, + }, + }; +} diff --git a/packages/playwright-mdd/src/manualPromise.ts b/packages/playwright-mdd/src/manualPromise.ts new file mode 100644 index 0000000000000..a5034e05ece5f --- /dev/null +++ b/packages/playwright-mdd/src/manualPromise.ts @@ -0,0 +1,127 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class ManualPromise extends Promise { + private _resolve!: (t: T) => void; + private _reject!: (e: Error) => void; + private _isDone: boolean; + + constructor() { + let resolve: (t: T) => void; + let reject: (e: Error) => void; + super((f, r) => { + resolve = f; + reject = r; + }); + this._isDone = false; + this._resolve = resolve!; + this._reject = reject!; + } + + isDone() { + return this._isDone; + } + + resolve(t: T) { + this._isDone = true; + this._resolve(t); + } + + reject(e: Error) { + this._isDone = true; + this._reject(e); + } + + static override get [Symbol.species]() { + return Promise; + } + + override get [Symbol.toStringTag]() { + return 'ManualPromise'; + } +} + +export class LongStandingScope { + private _terminateError: Error | undefined; + private _closeError: Error | undefined; + private _terminatePromises = new Map, string[]>(); + private _isClosed = false; + + reject(error: Error) { + this._isClosed = true; + this._terminateError = error; + for (const p of this._terminatePromises.keys()) + p.resolve(error); + } + + close(error: Error) { + this._isClosed = true; + this._closeError = error; + for (const [p, frames] of this._terminatePromises) + p.resolve(cloneError(error, frames)); + } + + isClosed() { + return this._isClosed; + } + + static async raceMultiple(scopes: LongStandingScope[], promise: Promise): Promise { + return Promise.race(scopes.map(s => s.race(promise))); + } + + async race(promise: Promise | Promise[]): Promise { + return this._race(Array.isArray(promise) ? promise : [promise], false) as Promise; + } + + async safeRace(promise: Promise, defaultValue?: T): Promise { + return this._race([promise], true, defaultValue); + } + + private async _race(promises: Promise[], safe: boolean, defaultValue?: any): Promise { + const terminatePromise = new ManualPromise(); + const frames = captureRawStack(); + if (this._terminateError) + terminatePromise.resolve(this._terminateError); + if (this._closeError) + terminatePromise.resolve(cloneError(this._closeError, frames)); + this._terminatePromises.set(terminatePromise, frames); + try { + return await Promise.race([ + terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)), + ...promises + ]); + } finally { + this._terminatePromises.delete(terminatePromise); + } + } +} + +function cloneError(error: Error, frames: string[]) { + const clone = new Error(); + clone.name = error.name; + clone.message = error.message; + clone.stack = [error.name + ':' + error.message, ...frames].join('\n'); + return clone; +} + +function captureRawStack(): string[] { + const stackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 50; + const error = new Error(); + const stack = error.stack || ''; + Error.stackTraceLimit = stackTraceLimit; + return stack.split('\n'); +} diff --git a/packages/playwright-mdd/src/program.ts b/packages/playwright-mdd/src/program.ts new file mode 100644 index 0000000000000..aebfae0f124a5 --- /dev/null +++ b/packages/playwright-mdd/src/program.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dotenv from 'dotenv'; +import { program } from 'commander'; + +import { runTasks } from './loop'; +import { Context } from './context'; +import { tools } from './tools'; + +/* eslint-disable no-console */ + +dotenv.config(); + +const packageJSON = require('../package.json'); + +program + .version('Version ' + packageJSON.version) + .name(packageJSON.name) + .action(async () => { + const context = await Context.create(tools); + const code = await runTasks(context, script); + console.log('Output code:'); + console.log('```javascript'); + console.log(code); + console.log('```'); + await context.close(); + }); + +const script = [ + 'Navigate to https://debs-obrien.github.io/playwright-movies-app', + 'Click search icon', + 'Type "Twister" in the search field and hit Enter', + 'Click on the link for the movie "Twisters"', +]; + +export { program }; diff --git a/packages/playwright-mdd/src/tools.ts b/packages/playwright-mdd/src/tools.ts new file mode 100644 index 0000000000000..e4b7c9a6ce117 --- /dev/null +++ b/packages/playwright-mdd/src/tools.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import snapshot from './tools/snapshot'; +import done from './tools/done'; +import navigate from './tools/navigate'; + +import type { Tool } from './tools/tool.js'; + +export const tools: Tool[] = [ + ...navigate, + ...snapshot, + ...done, +]; diff --git a/packages/playwright-mdd/src/tools/done.ts b/packages/playwright-mdd/src/tools/done.ts new file mode 100644 index 0000000000000..8818d4368d360 --- /dev/null +++ b/packages/playwright-mdd/src/tools/done.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; + +import { defineTool } from './tool'; + +const doneTool = defineTool({ + schema: { + name: 'done', + description: 'Call this tool to indicate that the task is complete', + inputSchema: z.object({}), + }, + + handle: async () => { + return { + code: [], + captureSnapshot: false, + waitForNetwork: false, + }; + }, +}); + +export default [ + doneTool, +]; diff --git a/packages/playwright-mdd/src/tools/navigate.ts b/packages/playwright-mdd/src/tools/navigate.ts new file mode 100644 index 0000000000000..cf6f695fef9c0 --- /dev/null +++ b/packages/playwright-mdd/src/tools/navigate.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; + +import { defineTool } from './tool.js'; + +const navigate = defineTool({ + schema: { + name: 'browser_navigate', + description: 'Navigate to a URL', + inputSchema: z.object({ + url: z.string().describe('The URL to navigate to'), + }), + }, + + handle: async (context, params) => { + const code = [ + `await page.goto('${params.url}');`, + ]; + await context.navigate(params.url); + + return { + code, + captureSnapshot: true, + waitForNetwork: false, + }; + }, +}); + +const goBack = defineTool({ + schema: { + name: 'browser_navigate_back', + description: 'Go back to the previous page', + inputSchema: z.object({}), + }, + + handle: async context => { + await context.page.goBack(); + const code = [ + `await page.goBack();`, + ]; + + return { + code, + captureSnapshot: true, + waitForNetwork: false, + }; + }, +}); + +const goForward = defineTool({ + schema: { + name: 'browser_navigate_forward', + description: 'Go forward to the next page', + inputSchema: z.object({}), + }, + handle: async context => { + await context.page.goForward(); + const code = [ + `await page.goForward();`, + ]; + return { + code, + captureSnapshot: true, + waitForNetwork: false, + }; + }, +}); + +export default [ + navigate, + goBack, + goForward, +]; diff --git a/packages/playwright-mdd/src/tools/snapshot.ts b/packages/playwright-mdd/src/tools/snapshot.ts new file mode 100644 index 0000000000000..e2a90ad8523d4 --- /dev/null +++ b/packages/playwright-mdd/src/tools/snapshot.ts @@ -0,0 +1,194 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; + +import { defineTool } from './tool'; +import * as format from '../format'; +import { generateLocator } from './utils'; + +const snapshot = defineTool({ + schema: { + name: 'browser_snapshot', + description: 'Capture accessibility snapshot of the current page, this is better than screenshot', + inputSchema: z.object({}), + }, + + handle: async () => { + return { + code: [], + captureSnapshot: true, + waitForNetwork: false, + }; + }, +}); + +const elementSchema = z.object({ + element: z.string().describe('Human-readable element description used to obtain permission to interact with the element'), + ref: z.string().describe('Exact target element reference from the page snapshot'), +}); + +const click = defineTool({ + schema: { + name: 'browser_click', + description: 'Perform click on a web page', + inputSchema: elementSchema, + }, + + handle: async (context, params) => { + const locator = context.refLocator(params); + + const code = [ + `await page.${await generateLocator(locator)}.click();` + ]; + + return { + code, + action: () => locator.click(), + captureSnapshot: true, + waitForNetwork: true, + }; + }, +}); + +const drag = defineTool({ + schema: { + name: 'browser_drag', + description: 'Perform drag and drop between two elements', + inputSchema: z.object({ + startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'), + startRef: z.string().describe('Exact source element reference from the page snapshot'), + endElement: z.string().describe('Human-readable target element description used to obtain the permission to interact with the element'), + endRef: z.string().describe('Exact target element reference from the page snapshot'), + }), + }, + + handle: async (context, params) => { + const startLocator = context.refLocator({ ref: params.startRef, element: params.startElement }); + const endLocator = context.refLocator({ ref: params.endRef, element: params.endElement }); + + const code = [ + `await page.${await generateLocator(startLocator)}.dragTo(page.${await generateLocator(endLocator)});` + ]; + + return { + code, + action: () => startLocator.dragTo(endLocator), + captureSnapshot: true, + waitForNetwork: true, + }; + }, +}); + +const hover = defineTool({ + schema: { + name: 'browser_hover', + description: 'Hover over element on page', + inputSchema: elementSchema, + }, + + handle: async (context, params) => { + const locator = context.refLocator(params); + + const code = [ + `await page.${await generateLocator(locator)}.hover();` + ]; + + return { + code, + action: () => locator.hover(), + captureSnapshot: true, + waitForNetwork: true, + }; + }, +}); + +const typeSchema = elementSchema.extend({ + text: z.string().describe('Text to type into the element'), + submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'), + slowly: z.boolean().optional().describe('Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.'), +}); + +const type = defineTool({ + schema: { + name: 'browser_type', + description: 'Type text into editable element', + inputSchema: typeSchema, + }, + + handle: async (context, params) => { + const locator = context.refLocator(params); + + const code: string[] = []; + const steps: (() => Promise)[] = []; + + if (params.slowly) { + code.push(`await page.${await generateLocator(locator)}.pressSequentially(${format.quote(params.text)});`); + steps.push(() => locator.pressSequentially(params.text)); + } else { + code.push(`await page.${await generateLocator(locator)}.fill(${format.quote(params.text)});`); + steps.push(() => locator.fill(params.text)); + } + + if (params.submit) { + code.push(`await page.${await generateLocator(locator)}.press('Enter');`); + steps.push(() => locator.press('Enter')); + } + + return { + code, + action: () => steps.reduce((acc, step) => acc.then(step), Promise.resolve()), + captureSnapshot: true, + waitForNetwork: true, + }; + }, +}); + +const selectOptionSchema = elementSchema.extend({ + values: z.array(z.string()).describe('Array of values to select in the dropdown. This can be a single value or multiple values.'), +}); + +const selectOption = defineTool({ + schema: { + name: 'browser_select_option', + description: 'Select an option in a dropdown', + inputSchema: selectOptionSchema, + }, + + handle: async (context, params) => { + const locator = context.refLocator(params); + + const code = [ + `await page.${await generateLocator(locator)}.selectOption(${format.formatObject(params.values)});` + ]; + + return { + code, + action: () => locator.selectOption(params.values).then(() => {}), + captureSnapshot: true, + waitForNetwork: true, + }; + }, +}); + +export default [ + snapshot, + click, + drag, + hover, + type, + selectOption, +]; diff --git a/packages/playwright-mdd/src/tools/tool.ts b/packages/playwright-mdd/src/tools/tool.ts new file mode 100644 index 0000000000000..9f840051eb587 --- /dev/null +++ b/packages/playwright-mdd/src/tools/tool.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { z } from 'zod'; +import type * as playwright from 'playwright-core'; +import type { Context } from '../context'; + +export type ToolSchema = { + name: string; + description: string; + inputSchema: Input; +}; + +type InputType = z.Schema; + +export type FileUploadModalState = { + type: 'fileChooser'; + description: string; + fileChooser: playwright.FileChooser; +}; + +export type DialogModalState = { + type: 'dialog'; + description: string; + dialog: playwright.Dialog; +}; + +export type ModalState = FileUploadModalState | DialogModalState; + +export type ToolActionResult = string | undefined | void; + +export type ToolResult = { + code: string[]; + action?: () => Promise; + captureSnapshot: boolean; + waitForNetwork: boolean; +}; + +export type Tool = { + schema: ToolSchema; + clearsModalState?: ModalState['type']; + handle: (context: Context, params: z.output) => Promise; +}; + +export function defineTool(tool: Tool): Tool { + return tool; +} diff --git a/packages/playwright-mdd/src/tools/utils.ts b/packages/playwright-mdd/src/tools/utils.ts new file mode 100644 index 0000000000000..d82095edc80a0 --- /dev/null +++ b/packages/playwright-mdd/src/tools/utils.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type * as playwright from 'playwright'; +import type { Context } from '../context'; + +export async function waitForCompletion(context: Context, callback: () => Promise): Promise { + const requests = new Set(); + let frameNavigated = false; + let waitCallback: () => void = () => {}; + const waitBarrier = new Promise(f => { waitCallback = f; }); + + const requestListener = (request: playwright.Request) => requests.add(request); + const requestFinishedListener = (request: playwright.Request) => { + requests.delete(request); + if (!requests.size) + waitCallback(); + }; + + const frameNavigateListener = (frame: playwright.Frame) => { + if (frame.parentFrame()) + return; + frameNavigated = true; + dispose(); + clearTimeout(timeout); + void context.waitForLoadState('load').then(waitCallback); + }; + + const onTimeout = () => { + dispose(); + waitCallback(); + }; + + context.page.on('request', requestListener); + context.page.on('requestfinished', requestFinishedListener); + context.page.on('framenavigated', frameNavigateListener); + const timeout = setTimeout(onTimeout, 10000); + + const dispose = () => { + context.page.off('request', requestListener); + context.page.off('requestfinished', requestFinishedListener); + context.page.off('framenavigated', frameNavigateListener); + clearTimeout(timeout); + }; + + try { + const result = await callback(); + if (!requests.size && !frameNavigated) + waitCallback(); + await waitBarrier; + await context.page.waitForTimeout(1000); + return result; + } finally { + dispose(); + } +} + +export function sanitizeForFilePath(s: string) { + const sanitize = (s: string) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); + const separator = s.lastIndexOf('.'); + if (separator === -1) + return sanitize(s); + return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1)); +} + +export async function generateLocator(locator: playwright.Locator): Promise { + try { + return await (locator as any)._generateLocatorString(); + } catch (e) { + if (e instanceof Error && /locator._generateLocatorString: Timeout .* exceeded/.test(e.message)) + throw new Error('Ref not found, likely because element was removed. Use browser_snapshot to see what elements are currently on the page.'); + throw e; + } +} + +export async function callOnPageNoTrace(page: playwright.Page, callback: (page: playwright.Page) => Promise): Promise { + return await (page as any)._wrapApiCall(() => callback(page), { internal: true }); +} diff --git a/utils/workspace.js b/utils/workspace.js index 5331dc3335ef7..928c61a0a0405 100755 --- a/utils/workspace.js +++ b/utils/workspace.js @@ -219,6 +219,11 @@ const workspace = new Workspace(ROOT_PATH, [ path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue'), files: ['LICENSE'], }), + new PWPackage({ + name: 'playwright-mdd', + path: path.join(ROOT_PATH, 'packages', 'playwright-mdd'), + files: ['LICENSE'], + }), ]); if (require.main === module) { From 56594a09771ed2f899e609d2fad8f74fee8edf4c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 25 Jun 2025 09:26:30 +0200 Subject: [PATCH 112/222] fix: get rid of url.parse in network code (#36423) --- .../src/server/utils/network.ts | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/packages/playwright-core/src/server/utils/network.ts b/packages/playwright-core/src/server/utils/network.ts index d12b72a1c389e..6054f60878618 100644 --- a/packages/playwright-core/src/server/utils/network.ts +++ b/packages/playwright-core/src/server/utils/network.ts @@ -39,9 +39,8 @@ export type HTTPRequestParams = { export const NET_DEFAULT_TIMEOUT = 30_000; export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.IncomingMessage) => void, onError: (error: Error) => void): { cancel(error: Error | undefined): void } { - const parsedUrl = url.parse(params.url); - let options: https.RequestOptions = { - ...parsedUrl, + const parsedUrl = new URL(params.url); + const options: https.RequestOptions = { agent: parsedUrl.protocol === 'https:' ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent, method: params.method || 'GET', headers: params.headers, @@ -51,19 +50,15 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco const proxyURL = getProxyForUrl(params.url); if (proxyURL) { - const parsedProxyURL = url.parse(proxyURL); + const parsedProxyURL = new URL(proxyURL); if (params.url.startsWith('http:')) { - options = { - path: parsedUrl.href, - host: parsedProxyURL.hostname, - port: parsedProxyURL.port, - headers: options.headers, - method: options.method - }; + parsedUrl.pathname = parsedUrl.href; + parsedUrl.host = parsedProxyURL.host; } else { - (parsedProxyURL as any).secureProxy = parsedProxyURL.protocol === 'https:'; - - options.agent = new HttpsProxyAgent(parsedProxyURL); + options.agent = new HttpsProxyAgent({ + ...convertURLtoLegacyUrl(parsedProxyURL), + secureProxy: parsedProxyURL.protocol === 'https:', + }); options.rejectUnauthorized = false; } } @@ -81,8 +76,8 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco } }; const request = options.protocol === 'https:' ? - https.request(options, requestCallback) : - http.request(options, requestCallback); + https.request(parsedUrl, options, requestCallback) : + http.request(parsedUrl, options, requestCallback); request.on('error', onError); if (params.socketTimeout !== undefined) { request.setTimeout(params.socketTimeout, () => { @@ -137,23 +132,27 @@ export function createProxyAgent(proxy?: ProxySettings, forUrl?: URL) { if (!/^\w+:\/\//.test(proxyServer)) proxyServer = 'http://' + proxyServer; - const proxyOpts = url.parse(proxyServer); + const proxyOpts = new URL(proxyServer); if (proxyOpts.protocol?.startsWith('socks')) { return new SocksProxyAgent({ host: proxyOpts.hostname, port: proxyOpts.port || undefined, }); } - if (proxy.username) - proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`; + if (proxy.username) { + proxyOpts.username = proxy.username; + proxyOpts.password = proxy.password || ''; + } if (forUrl && ['ws:', 'wss:'].includes(forUrl.protocol)) { // Force CONNECT method for WebSockets. - return new HttpsProxyAgent(proxyOpts); + // TODO: switch to URL instance instead of legacy object once https-proxy-agent supports it. + return new HttpsProxyAgent(convertURLtoLegacyUrl(proxyOpts)); } // TODO: We should use HttpProxyAgent conditional on proxyOpts.protocol instead of always using CONNECT method. - return new HttpsProxyAgent(proxyOpts); + // TODO: switch to URL instance instead of legacy object once https-proxy-agent supports it. + return new HttpsProxyAgent(convertURLtoLegacyUrl(proxyOpts)); } export function createHttpServer(requestListener?: (req: http.IncomingMessage, res: http.ServerResponse) => void): http.Server; @@ -226,3 +225,20 @@ function decorateServer(server: net.Server) { return close.call(server, callback); }; } + +function convertURLtoLegacyUrl(url: URL): url.Url { + return { + auth: url.username ? url.username + ':' + url.password : null, + hash: url.hash || null, + host: url.hostname ? url.hostname + ':' + url.port : null, + hostname: url.hostname || null, + href: url.href, + path: url.pathname + url.search, + pathname: url.pathname, + protocol: url.protocol, + search: url.search || null, + slashes: true, + port: url.port || null, + query: url.search.slice(1) || null, + }; +} From 2c78f842cec3898d6cb775962e765ae562716abc Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:17:34 +0200 Subject: [PATCH 113/222] feat(webkit): roll to r2187 (#36428) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 32e2538b6ab9a..8c98ba59c6524 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2186", + "revision": "2187", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From 40970238b4afec1d5e51c1a75ecdb606fb6c80b8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 25 Jun 2025 11:52:57 +0200 Subject: [PATCH 114/222] test: fix installation tests under Node.js 24 (#36434) --- tests/installation/npmTest.ts | 2 ++ tests/installation/playwright-cli-install-should-work.spec.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index 09849077b5985..9360ce8a50170 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -107,6 +107,8 @@ export const test = _test const npmLines = [ `registry = ${registry.url()}/`, `cache = ${testInfo.outputPath('npm_cache')}`, + // Required after https://github.com/npm/cli/pull/8185. + 'replace-registry-host=never', ]; if (!allowGlobalInstall) { yarnLines.push(`prefix "${testInfo.outputPath('npm_global')}"`); diff --git a/tests/installation/playwright-cli-install-should-work.spec.ts b/tests/installation/playwright-cli-install-should-work.spec.ts index 9f909945a076d..ebbb22adb2230 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -38,7 +38,6 @@ test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk await test.step('playwright install --list', async () => { const result = await exec('npx playwright install --list'); - console.log('result', result); expect.soft(result).toMatch(/Playwright version: \d+\.\d+/); expect.soft(result).toMatch(/chromium-\d+/); expect.soft(result).toMatch(/chromium_headless_shell-\d+/); From 5efa0656964f93cdfeaef1e6423d397bd3e89c9b Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 25 Jun 2025 12:03:34 +0200 Subject: [PATCH 115/222] chore: set title from global teardown (#36433) --- packages/playwright/src/reporters/html.ts | 9 ++---- tests/playwright-test/reporter-html.spec.ts | 34 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 297fa7849c351..1b6ff2f2ad98f 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -57,7 +57,6 @@ class HtmlReporter implements ReporterV2 { private _open: string | undefined; private _port: number | undefined; private _host: string | undefined; - private _title: string | undefined; private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined; private _topLevelErrors: api.TestError[] = []; @@ -78,13 +77,12 @@ class HtmlReporter implements ReporterV2 { } onBegin(suite: api.Suite) { - const { outputFolder, open, attachmentsBaseURL, host, port, title } = this._resolveOptions(); + const { outputFolder, open, attachmentsBaseURL, host, port } = this._resolveOptions(); this._outputFolder = outputFolder; this._open = open; this._host = host; this._port = port; this._attachmentsBaseURL = attachmentsBaseURL; - this._title = title; const reportedWarnings = new Set(); for (const project of this.config.projects) { if (this._isSubdirectory(outputFolder, project.outputDir) || this._isSubdirectory(project.outputDir, outputFolder)) { @@ -104,7 +102,7 @@ class HtmlReporter implements ReporterV2 { this.suite = suite; } - _resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption, attachmentsBaseURL: string, host: string | undefined, port: number | undefined, title: string | undefined } { + _resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption, attachmentsBaseURL: string, host: string | undefined, port: number | undefined } { const outputFolder = reportFolderFromEnv() ?? resolveReporterOutputPath('playwright-report', this._options.configDir, this._options.outputFolder); return { outputFolder, @@ -112,7 +110,6 @@ class HtmlReporter implements ReporterV2 { attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || 'data/', host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host, port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port, - title: process.env.PLAYWRIGHT_HTML_TITLE || this._options.title, }; } @@ -128,7 +125,7 @@ class HtmlReporter implements ReporterV2 { async onEnd(result: api.FullResult) { const projectSuites = this.suite.suites; await removeFolders([this._outputFolder]); - const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, this._title); + const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, process.env.PLAYWRIGHT_HTML_TITLE || this._options.title); this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors); } diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 4e9547e45b12b..6b99892b026a9 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -481,6 +481,40 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(anchorLocator.nth(1)).toHaveAttribute('href', 'http://microsoft.com'); }); + test('should allow setting title from env in global teardown', async ({ runInlineTest, page, showReport }, testInfo) => { + test.skip(useIntermediateMergeReport, 'env vars are not available in merge report'); + + const result = await runInlineTest({ + 'playwright.config.ts': ` + module.exports = { + globalTeardown: './global-teardown.js', + }; + `, + 'omega-star.test.js': ` + import { test, expect } from '@playwright/test'; + import fs from 'fs/promises'; + test('version check', async ({}, testInfo) => { + const apiVersion = 'abcde'; + await fs.writeFile(testInfo.outputPath('omega_star_version'), apiVersion); + expect(2).toEqual(2); + }); + `, + 'global-teardown.js': ` + import fs from 'fs/promises'; + import path from 'path'; + export default async (config) => { + const apiVersion = await fs.readFile(path.join('test-results', 'omega-star-version-check', 'omega_star_version'), 'utf-8'); + process.env.PLAYWRIGHT_HTML_TITLE = 'Omega Star Test Suite (Version: ' + apiVersion + ')'; + }; + `, + }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + + await showReport(); + await expect(page.locator('.header-title')).toHaveText('Omega Star Test Suite (Version: abcde)'); + }); + test('should include stdio', async ({ runInlineTest, page, showReport }) => { const result = await runInlineTest({ 'a.test.js': ` From 2cc526a327ad16168fb857ac3b73f5b780b1a2d3 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 25 Jun 2025 13:08:03 +0200 Subject: [PATCH 116/222] devops: introduce Node.js 24 (Current) bots (#36402) --- .github/workflows/tests_primary.yml | 11 +++++++++++ .github/workflows/tests_secondary.yml | 2 ++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index cae05b22f343c..ec575d808e798 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -42,6 +42,9 @@ jobs: - os: ubuntu-22.04 node-version: 22 browser: chromium + - os: ubuntu-22.04 + node-version: 24 + browser: chromium runs-on: ${{ matrix.os }} permissions: id-token: write # This is required for OIDC login (azure/login) to succeed @@ -109,6 +112,14 @@ jobs: node-version: 22 shardIndex: 2 shardTotal: 2 + - os: ubuntu-latest + node-version: 24 + shardIndex: 1 + shardTotal: 2 + - os: ubuntu-latest + node-version: 24 + shardIndex: 2 + shardTotal: 2 runs-on: ${{ matrix.os }} permissions: id-token: write # This is required for OIDC login (azure/login) to succeed diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index c01be04b96d82..bc7276d8ed589 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -102,6 +102,8 @@ jobs: node_version: 20 - os: ubuntu-latest node_version: 22 + - os: ubuntu-latest + node_version: 24 timeout-minutes: 30 steps: - uses: actions/checkout@v4 From a3eff54e9456e626da91846091b2fb535547979e Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 25 Jun 2025 13:05:51 +0100 Subject: [PATCH 117/222] chore: migrate some dispatchers from CallMetadata to Progress (part 1) (#36429) --- packages/injected/src/injectedScript.ts | 2 +- .../src/server/debugController.ts | 7 +- .../src/server/dispatchers/dispatcher.ts | 13 +- .../dispatchers/elementHandlerDispatcher.ts | 144 +++--- .../src/server/dispatchers/frameDispatcher.ts | 194 ++++---- packages/playwright-core/src/server/dom.ts | 260 +++++------ .../src/server/fileUploadUtils.ts | 2 +- packages/playwright-core/src/server/frames.ts | 442 ++++++++---------- packages/playwright-core/src/server/page.ts | 2 +- .../playwright-core/src/server/progress.ts | 29 +- .../src/server/recorder/recorderApp.ts | 3 +- .../src/server/recorder/recorderRunner.ts | 38 +- .../src/server/trace/viewer/traceViewer.ts | 16 +- packages/playwright-core/src/server/types.ts | 8 +- .../src/utils/isomorphic/protocolMetainfo.ts | 5 + packages/protocol/src/channels.d.ts | 167 +++---- packages/protocol/src/progress.d.ts | 45 ++ packages/protocol/src/protocol.yml | 4 + utils/generate_channels.js | 12 +- 19 files changed, 662 insertions(+), 731 deletions(-) create mode 100644 packages/protocol/src/progress.d.ts diff --git a/packages/injected/src/injectedScript.ts b/packages/injected/src/injectedScript.ts index e31bf7b1c42f3..2c9b11f36a090 100644 --- a/packages/injected/src/injectedScript.ts +++ b/packages/injected/src/injectedScript.ts @@ -47,7 +47,7 @@ import type { ElementText, TextMatcher } from './selectorUtils'; import type { Builtins } from './utilityScript'; -export type FrameExpectParams = Omit & { expectedValue?: any }; +export type FrameExpectParams = Omit & { expectedValue?: any }; export type ElementState = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked' | 'unchecked' | 'indeterminate' | 'stable'; export type ElementStateWithoutStable = Exclude; diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 92b94211a612f..a861facd8c06f 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -22,6 +22,7 @@ import { parseAriaSnapshotUnsafe } from '../utils/isomorphic/ariaSnapshot'; import { yaml } from '../utilsBundle'; import { EmptyRecorderApp } from './recorder/recorderApp'; import { unsafeLocatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser'; +import { ProgressController } from './progress'; import type { Language } from '../utils'; import type { Browser } from './browser'; @@ -83,8 +84,10 @@ export class DebugController extends SdkObject { } async navigate(url: string) { - for (const p of this._playwright.allPages()) - await p.mainFrame().goto(internalMetadata, url, { timeout: DEFAULT_PLAYWRIGHT_TIMEOUT }); + for (const p of this._playwright.allPages()) { + const controller = new ProgressController(internalMetadata, this); + await controller.run(progress => p.mainFrame().goto(progress, url), DEFAULT_PLAYWRIGHT_TIMEOUT); + } } async setRecorderMode(params: { mode: Mode, file?: string, testIdAttributeName?: string }) { diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index cc38a398dfc43..db91f38accadc 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -24,7 +24,8 @@ import { TargetClosedError, isTargetClosedError, serializeError } from '../error import { createRootSdkObject, SdkObject } from '../instrumentation'; import { isProtocolError } from '../protocolError'; import { compressCallLog } from '../callLog'; -import { methodMetainfo } from '../../utils/isomorphic/protocolMetainfo'; +import { methodMetainfo, progressTypes } from '../../utils/isomorphic/protocolMetainfo'; +import { ProgressController } from '../progress'; import type { CallMetadata } from '../instrumentation'; import type { PlaywrightDispatcher } from './playwrightDispatcher'; @@ -101,8 +102,16 @@ export class Dispatcher (this as any)[method](validParams, progress), validParams?.timeout); + } + return (this as any)[method](validParams, callMetadata); + } + async _handleCommand(callMetadata: CallMetadata, method: string, validParams: any) { - const commandPromise = (this as any)[method](validParams, callMetadata); + const commandPromise = this._runCommand(callMetadata, method, validParams); try { return await this._openScope.race(commandPromise); } catch (e) { diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index 4f708270482b8..215aa288f6975 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -20,9 +20,9 @@ import { JSHandleDispatcher, parseArgument, serializeResult } from './jsHandleDi import type { ElementHandle } from '../dom'; import type { Frame } from '../frames'; -import type { CallMetadata } from '../instrumentation'; import type * as js from '../javascript'; import type * as channels from '@protocol/channels'; +import type { Progress } from '@protocol/progress'; export class ElementHandleDispatcher extends JSHandleDispatcher implements channels.ElementHandleChannel { @@ -55,161 +55,161 @@ export class ElementHandleDispatcher extends JSHandleDispatcher this._elementHandle = elementHandle; } - async ownerFrame(params: channels.ElementHandleOwnerFrameParams, metadata: CallMetadata): Promise { + async ownerFrame(params: channels.ElementHandleOwnerFrameParams, progress: Progress): Promise { const frame = await this._elementHandle.ownerFrame(); return { frame: frame ? FrameDispatcher.from(this._browserContextDispatcher(), frame) : undefined }; } - async contentFrame(params: channels.ElementHandleContentFrameParams, metadata: CallMetadata): Promise { - const frame = await this._elementHandle.contentFrame(); + async contentFrame(params: channels.ElementHandleContentFrameParams, progress: Progress): Promise { + const frame = await progress.race(this._elementHandle.contentFrame()); return { frame: frame ? FrameDispatcher.from(this._browserContextDispatcher(), frame) : undefined }; } - async getAttribute(params: channels.ElementHandleGetAttributeParams, metadata: CallMetadata): Promise { - const value = await this._elementHandle.getAttribute(metadata, params.name); + async getAttribute(params: channels.ElementHandleGetAttributeParams, progress: Progress): Promise { + const value = await this._elementHandle.getAttribute(progress, params.name); return { value: value === null ? undefined : value }; } - async inputValue(params: channels.ElementHandleInputValueParams, metadata: CallMetadata): Promise { - const value = await this._elementHandle.inputValue(metadata); + async inputValue(params: channels.ElementHandleInputValueParams, progress: Progress): Promise { + const value = await this._elementHandle.inputValue(progress); return { value }; } - async textContent(params: channels.ElementHandleTextContentParams, metadata: CallMetadata): Promise { - const value = await this._elementHandle.textContent(metadata); + async textContent(params: channels.ElementHandleTextContentParams, progress: Progress): Promise { + const value = await this._elementHandle.textContent(progress); return { value: value === null ? undefined : value }; } - async innerText(params: channels.ElementHandleInnerTextParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.innerText(metadata) }; + async innerText(params: channels.ElementHandleInnerTextParams, progress: Progress): Promise { + return { value: await this._elementHandle.innerText(progress) }; } - async innerHTML(params: channels.ElementHandleInnerHTMLParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.innerHTML(metadata) }; + async innerHTML(params: channels.ElementHandleInnerHTMLParams, progress: Progress): Promise { + return { value: await this._elementHandle.innerHTML(progress) }; } - async isChecked(params: channels.ElementHandleIsCheckedParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.isChecked(metadata) }; + async isChecked(params: channels.ElementHandleIsCheckedParams, progress: Progress): Promise { + return { value: await this._elementHandle.isChecked(progress) }; } - async isDisabled(params: channels.ElementHandleIsDisabledParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.isDisabled(metadata) }; + async isDisabled(params: channels.ElementHandleIsDisabledParams, progress: Progress): Promise { + return { value: await this._elementHandle.isDisabled(progress) }; } - async isEditable(params: channels.ElementHandleIsEditableParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.isEditable(metadata) }; + async isEditable(params: channels.ElementHandleIsEditableParams, progress: Progress): Promise { + return { value: await this._elementHandle.isEditable(progress) }; } - async isEnabled(params: channels.ElementHandleIsEnabledParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.isEnabled(metadata) }; + async isEnabled(params: channels.ElementHandleIsEnabledParams, progress: Progress): Promise { + return { value: await this._elementHandle.isEnabled(progress) }; } - async isHidden(params: channels.ElementHandleIsHiddenParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.isHidden(metadata) }; + async isHidden(params: channels.ElementHandleIsHiddenParams, progress: Progress): Promise { + return { value: await this._elementHandle.isHidden(progress) }; } - async isVisible(params: channels.ElementHandleIsVisibleParams, metadata: CallMetadata): Promise { - return { value: await this._elementHandle.isVisible(metadata) }; + async isVisible(params: channels.ElementHandleIsVisibleParams, progress: Progress): Promise { + return { value: await this._elementHandle.isVisible(progress) }; } - async dispatchEvent(params: channels.ElementHandleDispatchEventParams, metadata: CallMetadata): Promise { - await this._elementHandle.dispatchEvent(metadata, params.type, parseArgument(params.eventInit)); + async dispatchEvent(params: channels.ElementHandleDispatchEventParams, progress: Progress): Promise { + await this._elementHandle.dispatchEvent(progress, params.type, parseArgument(params.eventInit)); } - async scrollIntoViewIfNeeded(params: channels.ElementHandleScrollIntoViewIfNeededParams, metadata: CallMetadata): Promise { - await this._elementHandle.scrollIntoViewIfNeeded(metadata, params); + async scrollIntoViewIfNeeded(params: channels.ElementHandleScrollIntoViewIfNeededParams, progress: Progress): Promise { + await this._elementHandle.scrollIntoViewIfNeeded(progress); } - async hover(params: channels.ElementHandleHoverParams, metadata: CallMetadata): Promise { - return await this._elementHandle.hover(metadata, params); + async hover(params: channels.ElementHandleHoverParams, progress: Progress): Promise { + return await this._elementHandle.hover(progress, params); } - async click(params: channels.ElementHandleClickParams, metadata: CallMetadata): Promise { - return await this._elementHandle.click(metadata, params); + async click(params: channels.ElementHandleClickParams, progress: Progress): Promise { + return await this._elementHandle.click(progress, params); } - async dblclick(params: channels.ElementHandleDblclickParams, metadata: CallMetadata): Promise { - return await this._elementHandle.dblclick(metadata, params); + async dblclick(params: channels.ElementHandleDblclickParams, progress: Progress): Promise { + return await this._elementHandle.dblclick(progress, params); } - async tap(params: channels.ElementHandleTapParams, metadata: CallMetadata): Promise { - return await this._elementHandle.tap(metadata, params); + async tap(params: channels.ElementHandleTapParams, progress: Progress): Promise { + return await this._elementHandle.tap(progress, params); } - async selectOption(params: channels.ElementHandleSelectOptionParams, metadata: CallMetadata): Promise { + async selectOption(params: channels.ElementHandleSelectOptionParams, progress: Progress): Promise { const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle); - return { values: await this._elementHandle.selectOption(metadata, elements, params.options || [], params) }; + return { values: await this._elementHandle.selectOption(progress, elements, params.options || [], params) }; } - async fill(params: channels.ElementHandleFillParams, metadata: CallMetadata): Promise { - return await this._elementHandle.fill(metadata, params.value, params); + async fill(params: channels.ElementHandleFillParams, progress: Progress): Promise { + return await this._elementHandle.fill(progress, params.value, params); } - async selectText(params: channels.ElementHandleSelectTextParams, metadata: CallMetadata): Promise { - await this._elementHandle.selectText(metadata, params); + async selectText(params: channels.ElementHandleSelectTextParams, progress: Progress): Promise { + await this._elementHandle.selectText(progress, params); } - async setInputFiles(params: channels.ElementHandleSetInputFilesParams, metadata: CallMetadata): Promise { - return await this._elementHandle.setInputFiles(metadata, params); + async setInputFiles(params: channels.ElementHandleSetInputFilesParams, progress: Progress): Promise { + return await this._elementHandle.setInputFiles(progress, params); } - async focus(params: channels.ElementHandleFocusParams, metadata: CallMetadata): Promise { - await this._elementHandle.focus(metadata); + async focus(params: channels.ElementHandleFocusParams, progress: Progress): Promise { + await this._elementHandle.focus(progress); } - async type(params: channels.ElementHandleTypeParams, metadata: CallMetadata): Promise { - return await this._elementHandle.type(metadata, params.text, params); + async type(params: channels.ElementHandleTypeParams, progress: Progress): Promise { + return await this._elementHandle.type(progress, params.text, params); } - async press(params: channels.ElementHandlePressParams, metadata: CallMetadata): Promise { - return await this._elementHandle.press(metadata, params.key, params); + async press(params: channels.ElementHandlePressParams, progress: Progress): Promise { + return await this._elementHandle.press(progress, params.key, params); } - async check(params: channels.ElementHandleCheckParams, metadata: CallMetadata): Promise { - return await this._elementHandle.check(metadata, params); + async check(params: channels.ElementHandleCheckParams, progress: Progress): Promise { + return await this._elementHandle.check(progress, params); } - async uncheck(params: channels.ElementHandleUncheckParams, metadata: CallMetadata): Promise { - return await this._elementHandle.uncheck(metadata, params); + async uncheck(params: channels.ElementHandleUncheckParams, progress: Progress): Promise { + return await this._elementHandle.uncheck(progress, params); } - async boundingBox(params: channels.ElementHandleBoundingBoxParams, metadata: CallMetadata): Promise { - const value = await this._elementHandle.boundingBox(); + async boundingBox(params: channels.ElementHandleBoundingBoxParams, progress: Progress): Promise { + const value = await progress.race(this._elementHandle.boundingBox()); return { value: value || undefined }; } - async screenshot(params: channels.ElementHandleScreenshotParams, metadata: CallMetadata): Promise { + async screenshot(params: channels.ElementHandleScreenshotParams, progress: Progress): Promise { const mask: { frame: Frame, selector: string }[] = (params.mask || []).map(({ frame, selector }) => ({ frame: (frame as FrameDispatcher)._object, selector, })); - return { binary: await this._elementHandle.screenshot(metadata, { ...params, mask }) }; + return { binary: await this._elementHandle.screenshot(progress, { ...params, mask }) }; } - async querySelector(params: channels.ElementHandleQuerySelectorParams, metadata: CallMetadata): Promise { - const handle = await this._elementHandle.querySelector(params.selector, params); + async querySelector(params: channels.ElementHandleQuerySelectorParams, progress: Progress): Promise { + const handle = await progress.race(this._elementHandle.querySelector(params.selector, params)); return { element: ElementHandleDispatcher.fromNullable(this.parentScope(), handle) }; } - async querySelectorAll(params: channels.ElementHandleQuerySelectorAllParams, metadata: CallMetadata): Promise { - const elements = await this._elementHandle.querySelectorAll(params.selector); + async querySelectorAll(params: channels.ElementHandleQuerySelectorAllParams, progress: Progress): Promise { + const elements = await progress.race(this._elementHandle.querySelectorAll(params.selector)); return { elements: elements.map(e => ElementHandleDispatcher.from(this.parentScope(), e)) }; } - async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._elementHandle.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg))) }; + async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, progress: Progress): Promise { + return { value: serializeResult(await progress.race(this._elementHandle.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg)))) }; } - async evalOnSelectorAll(params: channels.ElementHandleEvalOnSelectorAllParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._elementHandle.evalOnSelectorAll(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; + async evalOnSelectorAll(params: channels.ElementHandleEvalOnSelectorAllParams, progress: Progress): Promise { + return { value: serializeResult(await progress.race(this._elementHandle.evalOnSelectorAll(params.selector, params.expression, params.isFunction, parseArgument(params.arg)))) }; } - async waitForElementState(params: channels.ElementHandleWaitForElementStateParams, metadata: CallMetadata): Promise { - await this._elementHandle.waitForElementState(metadata, params.state, params); + async waitForElementState(params: channels.ElementHandleWaitForElementStateParams, progress: Progress): Promise { + await this._elementHandle.waitForElementState(progress, params.state); } - async waitForSelector(params: channels.ElementHandleWaitForSelectorParams, metadata: CallMetadata): Promise { - return { element: ElementHandleDispatcher.fromNullable(this.parentScope(), await this._elementHandle.waitForSelector(metadata, params.selector, params)) }; + async waitForSelector(params: channels.ElementHandleWaitForSelectorParams, progress: Progress): Promise { + return { element: ElementHandleDispatcher.fromNullable(this.parentScope(), await this._elementHandle.waitForSelector(progress, params.selector, params)) }; } private _browserContextDispatcher(): BrowserContextDispatcher { diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 5db8c7c56121b..2b4f17f2d4eec 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -23,7 +23,7 @@ import { RequestDispatcher } from './networkDispatchers'; import { parseAriaSnapshotUnsafe } from '../../utils/isomorphic/ariaSnapshot'; import { yaml } from '../../utilsBundle'; -import type { CallMetadata } from '../instrumentation'; +import type { Progress } from '../progress'; import type { BrowserContextDispatcher } from './browserContextDispatcher'; import type { PageDispatcher } from './pageDispatcher'; import type { NavigationEvent } from '../frames'; @@ -75,204 +75,204 @@ export class FrameDispatcher extends Dispatcher { - return { response: ResponseDispatcher.fromNullable(this._browserContextDispatcher, await this._frame.goto(metadata, params.url, params)) }; + async goto(params: channels.FrameGotoParams, progress: Progress): Promise { + return { response: ResponseDispatcher.fromNullable(this._browserContextDispatcher, await this._frame.goto(progress, params.url, params)) }; } - async frameElement(): Promise { - return { element: ElementHandleDispatcher.from(this, await this._frame.frameElement()) }; + async frameElement(params: channels.FrameFrameElementParams, progress: Progress): Promise { + return { element: ElementHandleDispatcher.from(this, await progress.race(this._frame.frameElement())) }; } - async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._frame.evaluateExpression(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg))) }; + async evaluateExpression(params: channels.FrameEvaluateExpressionParams, progress: Progress): Promise { + return { value: serializeResult(await progress.race(this._frame.evaluateExpression(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg)))) }; } - async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise { - return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, await this._frame.evaluateExpressionHandle(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg))) }; + async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, progress: Progress): Promise { + return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, await progress.race(this._frame.evaluateExpressionHandle(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg)))) }; } - async waitForSelector(params: channels.FrameWaitForSelectorParams, metadata: CallMetadata): Promise { - return { element: ElementHandleDispatcher.fromNullable(this, await this._frame.waitForSelector(metadata, params.selector, params)) }; + async waitForSelector(params: channels.FrameWaitForSelectorParams, progress: Progress): Promise { + return { element: ElementHandleDispatcher.fromNullable(this, await this._frame.waitForSelector(progress, params.selector, true, params)) }; } - async dispatchEvent(params: channels.FrameDispatchEventParams, metadata: CallMetadata): Promise { - return this._frame.dispatchEvent(metadata, params.selector, params.type, parseArgument(params.eventInit), params); + async dispatchEvent(params: channels.FrameDispatchEventParams, progress: Progress): Promise { + return this._frame.dispatchEvent(progress, params.selector, params.type, parseArgument(params.eventInit), params); } - async evalOnSelector(params: channels.FrameEvalOnSelectorParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._frame.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg))) }; + async evalOnSelector(params: channels.FrameEvalOnSelectorParams, progress: Progress): Promise { + return { value: serializeResult(await progress.race(this._frame.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg)))) }; } - async evalOnSelectorAll(params: channels.FrameEvalOnSelectorAllParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._frame.evalOnSelectorAll(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; + async evalOnSelectorAll(params: channels.FrameEvalOnSelectorAllParams, progress: Progress): Promise { + return { value: serializeResult(await progress.race(this._frame.evalOnSelectorAll(params.selector, params.expression, params.isFunction, parseArgument(params.arg)))) }; } - async querySelector(params: channels.FrameQuerySelectorParams, metadata: CallMetadata): Promise { - return { element: ElementHandleDispatcher.fromNullable(this, await this._frame.querySelector(params.selector, params)) }; + async querySelector(params: channels.FrameQuerySelectorParams, progress: Progress): Promise { + return { element: ElementHandleDispatcher.fromNullable(this, await progress.race(this._frame.querySelector(params.selector, params))) }; } - async querySelectorAll(params: channels.FrameQuerySelectorAllParams, metadata: CallMetadata): Promise { - const elements = await this._frame.querySelectorAll(params.selector); + async querySelectorAll(params: channels.FrameQuerySelectorAllParams, progress: Progress): Promise { + const elements = await progress.race(this._frame.querySelectorAll(params.selector)); return { elements: elements.map(e => ElementHandleDispatcher.from(this, e)) }; } - async queryCount(params: channels.FrameQueryCountParams): Promise { - return { value: await this._frame.queryCount(params.selector) }; + async queryCount(params: channels.FrameQueryCountParams, progress: Progress): Promise { + return { value: await progress.race(this._frame.queryCount(params.selector)) }; } - async content(): Promise { - return { value: await this._frame.content() }; + async content(params: channels.FrameContentParams, progress: Progress): Promise { + return { value: await progress.race(this._frame.content()) }; } - async setContent(params: channels.FrameSetContentParams, metadata: CallMetadata): Promise { - return await this._frame.setContent(metadata, params.html, params); + async setContent(params: channels.FrameSetContentParams, progress: Progress): Promise { + return await this._frame.setContent(progress, params.html, params); } - async addScriptTag(params: channels.FrameAddScriptTagParams, metadata: CallMetadata): Promise { - return { element: ElementHandleDispatcher.from(this, await this._frame.addScriptTag(params)) }; + async addScriptTag(params: channels.FrameAddScriptTagParams, progress: Progress): Promise { + return { element: ElementHandleDispatcher.from(this, await progress.race(this._frame.addScriptTag(params))) }; } - async addStyleTag(params: channels.FrameAddStyleTagParams, metadata: CallMetadata): Promise { - return { element: ElementHandleDispatcher.from(this, await this._frame.addStyleTag(params)) }; + async addStyleTag(params: channels.FrameAddStyleTagParams, progress: Progress): Promise { + return { element: ElementHandleDispatcher.from(this, await progress.race(this._frame.addStyleTag(params))) }; } - async click(params: channels.FrameClickParams, metadata: CallMetadata): Promise { - metadata.potentiallyClosesScope = true; - return await this._frame.click(metadata, params.selector, params); + async click(params: channels.FrameClickParams, progress: Progress): Promise { + progress.metadata.potentiallyClosesScope = true; + return await this._frame.click(progress, params.selector, params); } - async dblclick(params: channels.FrameDblclickParams, metadata: CallMetadata): Promise { - return await this._frame.dblclick(metadata, params.selector, params); + async dblclick(params: channels.FrameDblclickParams, progress: Progress): Promise { + return await this._frame.dblclick(progress, params.selector, params); } - async dragAndDrop(params: channels.FrameDragAndDropParams, metadata: CallMetadata): Promise { - return await this._frame.dragAndDrop(metadata, params.source, params.target, params); + async dragAndDrop(params: channels.FrameDragAndDropParams, progress: Progress): Promise { + return await this._frame.dragAndDrop(progress, params.source, params.target, params); } - async tap(params: channels.FrameTapParams, metadata: CallMetadata): Promise { - return await this._frame.tap(metadata, params.selector, params); + async tap(params: channels.FrameTapParams, progress: Progress): Promise { + return await this._frame.tap(progress, params.selector, params); } - async fill(params: channels.FrameFillParams, metadata: CallMetadata): Promise { - return await this._frame.fill(metadata, params.selector, params.value, params); + async fill(params: channels.FrameFillParams, progress: Progress): Promise { + return await this._frame.fill(progress, params.selector, params.value, params); } - async focus(params: channels.FrameFocusParams, metadata: CallMetadata): Promise { - await this._frame.focus(metadata, params.selector, params); + async focus(params: channels.FrameFocusParams, progress: Progress): Promise { + await this._frame.focus(progress, params.selector, params); } - async blur(params: channels.FrameBlurParams, metadata: CallMetadata): Promise { - await this._frame.blur(metadata, params.selector, params); + async blur(params: channels.FrameBlurParams, progress: Progress): Promise { + await this._frame.blur(progress, params.selector, params); } - async textContent(params: channels.FrameTextContentParams, metadata: CallMetadata): Promise { - const value = await this._frame.textContent(metadata, params.selector, params); + async textContent(params: channels.FrameTextContentParams, progress: Progress): Promise { + const value = await this._frame.textContent(progress, params.selector, params); return { value: value === null ? undefined : value }; } - async innerText(params: channels.FrameInnerTextParams, metadata: CallMetadata): Promise { - return { value: await this._frame.innerText(metadata, params.selector, params) }; + async innerText(params: channels.FrameInnerTextParams, progress: Progress): Promise { + return { value: await this._frame.innerText(progress, params.selector, params) }; } - async innerHTML(params: channels.FrameInnerHTMLParams, metadata: CallMetadata): Promise { - return { value: await this._frame.innerHTML(metadata, params.selector, params) }; + async innerHTML(params: channels.FrameInnerHTMLParams, progress: Progress): Promise { + return { value: await this._frame.innerHTML(progress, params.selector, params) }; } - async generateLocatorString(params: channels.FrameGenerateLocatorStringParams, metadata: CallMetadata): Promise { - return { value: await this._frame.generateLocatorString(metadata, params.selector) }; + async generateLocatorString(params: channels.FrameGenerateLocatorStringParams, progress: Progress): Promise { + return { value: await this._frame.generateLocatorString(progress, params.selector) }; } - async getAttribute(params: channels.FrameGetAttributeParams, metadata: CallMetadata): Promise { - const value = await this._frame.getAttribute(metadata, params.selector, params.name, params); + async getAttribute(params: channels.FrameGetAttributeParams, progress: Progress): Promise { + const value = await this._frame.getAttribute(progress, params.selector, params.name, params); return { value: value === null ? undefined : value }; } - async inputValue(params: channels.FrameInputValueParams, metadata: CallMetadata): Promise { - const value = await this._frame.inputValue(metadata, params.selector, params); + async inputValue(params: channels.FrameInputValueParams, progress: Progress): Promise { + const value = await this._frame.inputValue(progress, params.selector, params); return { value }; } - async isChecked(params: channels.FrameIsCheckedParams, metadata: CallMetadata): Promise { - return { value: await this._frame.isChecked(metadata, params.selector, params) }; + async isChecked(params: channels.FrameIsCheckedParams, progress: Progress): Promise { + return { value: await this._frame.isChecked(progress, params.selector, params) }; } - async isDisabled(params: channels.FrameIsDisabledParams, metadata: CallMetadata): Promise { - return { value: await this._frame.isDisabled(metadata, params.selector, params) }; + async isDisabled(params: channels.FrameIsDisabledParams, progress: Progress): Promise { + return { value: await this._frame.isDisabled(progress, params.selector, params) }; } - async isEditable(params: channels.FrameIsEditableParams, metadata: CallMetadata): Promise { - return { value: await this._frame.isEditable(metadata, params.selector, params) }; + async isEditable(params: channels.FrameIsEditableParams, progress: Progress): Promise { + return { value: await this._frame.isEditable(progress, params.selector, params) }; } - async isEnabled(params: channels.FrameIsEnabledParams, metadata: CallMetadata): Promise { - return { value: await this._frame.isEnabled(metadata, params.selector, params) }; + async isEnabled(params: channels.FrameIsEnabledParams, progress: Progress): Promise { + return { value: await this._frame.isEnabled(progress, params.selector, params) }; } - async isHidden(params: channels.FrameIsHiddenParams, metadata: CallMetadata): Promise { - return { value: await this._frame.isHidden(metadata, params.selector, params) }; + async isHidden(params: channels.FrameIsHiddenParams, progress: Progress): Promise { + return { value: await this._frame.isHidden(progress, params.selector, params) }; } - async isVisible(params: channels.FrameIsVisibleParams, metadata: CallMetadata): Promise { - return { value: await this._frame.isVisible(metadata, params.selector, params) }; + async isVisible(params: channels.FrameIsVisibleParams, progress: Progress): Promise { + return { value: await this._frame.isVisible(progress, params.selector, params) }; } - async hover(params: channels.FrameHoverParams, metadata: CallMetadata): Promise { - return await this._frame.hover(metadata, params.selector, params); + async hover(params: channels.FrameHoverParams, progress: Progress): Promise { + return await this._frame.hover(progress, params.selector, params); } - async selectOption(params: channels.FrameSelectOptionParams, metadata: CallMetadata): Promise { + async selectOption(params: channels.FrameSelectOptionParams, progress: Progress): Promise { const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle); - return { values: await this._frame.selectOption(metadata, params.selector, elements, params.options || [], params) }; + return { values: await this._frame.selectOption(progress, params.selector, elements, params.options || [], params) }; } - async setInputFiles(params: channels.FrameSetInputFilesParams, metadata: CallMetadata): Promise { - return await this._frame.setInputFiles(metadata, params.selector, params); + async setInputFiles(params: channels.FrameSetInputFilesParams, progress: Progress): Promise { + return await this._frame.setInputFiles(progress, params.selector, params); } - async type(params: channels.FrameTypeParams, metadata: CallMetadata): Promise { - return await this._frame.type(metadata, params.selector, params.text, params); + async type(params: channels.FrameTypeParams, progress: Progress): Promise { + return await this._frame.type(progress, params.selector, params.text, params); } - async press(params: channels.FramePressParams, metadata: CallMetadata): Promise { - return await this._frame.press(metadata, params.selector, params.key, params); + async press(params: channels.FramePressParams, progress: Progress): Promise { + return await this._frame.press(progress, params.selector, params.key, params); } - async check(params: channels.FrameCheckParams, metadata: CallMetadata): Promise { - return await this._frame.check(metadata, params.selector, params); + async check(params: channels.FrameCheckParams, progress: Progress): Promise { + return await this._frame.check(progress, params.selector, params); } - async uncheck(params: channels.FrameUncheckParams, metadata: CallMetadata): Promise { - return await this._frame.uncheck(metadata, params.selector, params); + async uncheck(params: channels.FrameUncheckParams, progress: Progress): Promise { + return await this._frame.uncheck(progress, params.selector, params); } - async waitForTimeout(params: channels.FrameWaitForTimeoutParams, metadata: CallMetadata): Promise { - return await this._frame.waitForTimeout(metadata, params.timeout); + async waitForTimeout(params: channels.FrameWaitForTimeoutParams, progress: Progress): Promise { + return await this._frame.waitForTimeout(progress, params.timeout); } - async waitForFunction(params: channels.FrameWaitForFunctionParams, metadata: CallMetadata): Promise { - return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, await this._frame.waitForFunctionExpression(metadata, params.expression, params.isFunction, parseArgument(params.arg), params)) }; + async waitForFunction(params: channels.FrameWaitForFunctionParams, progress: Progress): Promise { + return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this, await this._frame.waitForFunctionExpression(progress, params.expression, params.isFunction, parseArgument(params.arg), params)) }; } - async title(params: channels.FrameTitleParams, metadata: CallMetadata): Promise { - return { value: await this._frame.title() }; + async title(params: channels.FrameTitleParams, progress: Progress): Promise { + return { value: await progress.race(this._frame.title()) }; } - async highlight(params: channels.FrameHighlightParams, metadata: CallMetadata): Promise { - return await this._frame.highlight(params.selector); + async highlight(params: channels.FrameHighlightParams, progress: Progress): Promise { + return await this._frame.highlight(progress, params.selector); } - async expect(params: channels.FrameExpectParams, metadata: CallMetadata): Promise { - metadata.potentiallyClosesScope = true; + async expect(params: channels.FrameExpectParams, progress: Progress): Promise { + progress.metadata.potentiallyClosesScope = true; let expectedValue = params.expectedValue ? parseArgument(params.expectedValue) : undefined; if (params.expression === 'to.match.aria' && expectedValue) expectedValue = parseAriaSnapshotUnsafe(yaml, expectedValue); - const result = await this._frame.expect(metadata, params.selector, { ...params, expectedValue }); + const result = await this._frame.expect(progress, params.selector, { ...params, expectedValue }, params.timeout); if (result.received !== undefined) result.received = serializeResult(result.received); return result; } - async ariaSnapshot(params: channels.FrameAriaSnapshotParams, metadata: CallMetadata): Promise { - return { snapshot: await this._frame.ariaSnapshot(metadata, params.selector, params) }; + async ariaSnapshot(params: channels.FrameAriaSnapshotParams, progress: Progress): Promise { + return { snapshot: await this._frame.ariaSnapshot(progress, params.selector, params) }; } } diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 60abad7dfb3b6..89cc1dc01af19 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -17,14 +17,12 @@ import fs from 'fs'; import * as js from './javascript'; -import { ProgressController } from './progress'; import { isUnderTest } from '../utils'; import { prepareFilesForUpload } from './fileUploadUtils'; import * as rawInjectedScriptSource from '../generated/injectedScriptSource'; import type * as frames from './frames'; import type { ElementState, HitTargetInterceptionResult, InjectedScript, InjectedScriptOptions } from '@injected/injectedScript'; -import type { CallMetadata } from './instrumentation'; import type { Page } from './page'; import type { Progress } from './progress'; import type { ScreenshotOptions } from './screenshotter'; @@ -183,28 +181,28 @@ export class ElementHandle extends js.JSHandle { return this._page.delegate.getContentFrame(this); } - async getAttribute(metadata: CallMetadata, name: string): Promise { - return this._frame.getAttribute(metadata, ':scope', name, { timeout: 0 }, this); + async getAttribute(progress: Progress, name: string): Promise { + return this._frame.getAttribute(progress, ':scope', name, {}, this); } - async inputValue(metadata: CallMetadata): Promise { - return this._frame.inputValue(metadata, ':scope', { timeout: 0 }, this); + async inputValue(progress: Progress): Promise { + return this._frame.inputValue(progress, ':scope', {}, this); } - async textContent(metadata: CallMetadata): Promise { - return this._frame.textContent(metadata, ':scope', { timeout: 0 }, this); + async textContent(progress: Progress): Promise { + return this._frame.textContent(progress, ':scope', {}, this); } - async innerText(metadata: CallMetadata): Promise { - return this._frame.innerText(metadata, ':scope', { timeout: 0 }, this); + async innerText(progress: Progress): Promise { + return this._frame.innerText(progress, ':scope', {}, this); } - async innerHTML(metadata: CallMetadata): Promise { - return this._frame.innerHTML(metadata, ':scope', { timeout: 0 }, this); + async innerHTML(progress: Progress): Promise { + return this._frame.innerHTML(progress, ':scope', {}, this); } - async dispatchEvent(metadata: CallMetadata, type: string, eventInit: Object = {}) { - return this._frame.dispatchEvent(metadata, ':scope', type, eventInit, { timeout: 0 }, this); + async dispatchEvent(progress: Progress, type: string, eventInit: Object = {}) { + return this._frame.dispatchEvent(progress, ':scope', type, eventInit, {}, this); } async _scrollRectIntoViewIfNeeded(progress: Progress, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'> { @@ -224,11 +222,8 @@ export class ElementHandle extends js.JSHandle { assertDone(throwRetargetableDOMError(result)); } - async scrollIntoViewIfNeeded(metadata: CallMetadata, options: types.TimeoutOptions) { - const controller = new ProgressController(metadata, this); - return controller.run( - progress => this._waitAndScrollIntoViewIfNeeded(progress, false /* waitForVisible */), - options.timeout); + async scrollIntoViewIfNeeded(progress: Progress) { + await this._waitAndScrollIntoViewIfNeeded(progress, false /* waitForVisible */); } private async _clickablePoint(): Promise { @@ -353,7 +348,7 @@ export class ElementHandle extends js.JSHandle { } async _retryPointerAction(progress: Progress, actionName: ActionName, waitForEnabled: boolean, action: (point: types.Point) => Promise, - options: Omit<{ waitAfter: boolean | 'disabled' } & types.PointerActionOptions & types.PointerActionWaitOptions, 'timeout'>): Promise<'error:notconnected' | 'done'> { + options: { waitAfter: boolean | 'disabled' } & types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { // Note: do not perform locator handlers checkpoint to avoid moving the mouse in the middle of a drag operation. const skipActionPreChecks = actionName === 'move and up'; return await this._retryAction(progress, actionName, async retry => { @@ -378,7 +373,7 @@ export class ElementHandle extends js.JSHandle { waitForEnabled: boolean, action: (point: types.Point) => Promise, forceScrollOptions: ScrollIntoViewOptions | undefined, - options: Omit<{ waitAfter: boolean | 'disabled' } & types.PointerActionOptions & types.PointerActionWaitOptions, 'timeout'>, + options: { waitAfter: boolean | 'disabled' } & types.PointerActionOptions & types.PointerActionWaitOptions, ): Promise { const { force = false, position } = options; @@ -505,65 +500,50 @@ export class ElementHandle extends js.JSHandle { }, progress.metadata.id)); } - async hover(metadata: CallMetadata, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._hover(progress, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._hover(progress, options); + return assertDone(throwRetargetableDOMError(result)); } _hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { return this._retryPointerAction(progress, 'hover', false /* waitForEnabled */, point => this._page.mouse._move(progress, point.x, point.y), { ...options, waitAfter: 'disabled' }); } - async click(metadata: CallMetadata, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._click(progress, { ...options, waitAfter: !options.noWaitAfter }); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async click(progress: Progress, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._click(progress, { ...options, waitAfter: !options.noWaitAfter }); + return assertDone(throwRetargetableDOMError(result)); } _click(progress: Progress, options: { waitAfter: boolean | 'disabled' } & types.MouseClickOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { return this._retryPointerAction(progress, 'click', true /* waitForEnabled */, point => this._page.mouse._click(progress, point.x, point.y, options), options); } - async dblclick(metadata: CallMetadata, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._dblclick(progress, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._dblclick(progress, options); + return assertDone(throwRetargetableDOMError(result)); } _dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { return this._retryPointerAction(progress, 'dblclick', true /* waitForEnabled */, point => this._page.mouse._click(progress, point.x, point.y, { ...options, clickCount: 2 }), { ...options, waitAfter: 'disabled' }); } - async tap(metadata: CallMetadata, options: types.PointerActionWaitOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._tap(progress, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async tap(progress: Progress, options: types.PointerActionWaitOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._tap(progress, options); + return assertDone(throwRetargetableDOMError(result)); } _tap(progress: Progress, options: types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { return this._retryPointerAction(progress, 'tap', true /* waitForEnabled */, point => this._page.touchscreen._tap(progress, point.x, point.y), { ...options, waitAfter: 'disabled' }); } - async selectOption(metadata: CallMetadata, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._selectOption(progress, elements, values, options); - return throwRetargetableDOMError(result); - }, options.timeout); + async selectOption(progress: Progress, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._selectOption(progress, elements, values, options); + return throwRetargetableDOMError(result); } async _selectOption(progress: Progress, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { @@ -593,13 +573,10 @@ export class ElementHandle extends js.JSHandle { return resultingOptions; } - async fill(metadata: CallMetadata, value: string, options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._fill(progress, value, options); - assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async fill(progress: Progress, value: string, options: types.CommonActionOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._fill(progress, value, options); + assertDone(throwRetargetableDOMError(result)); } async _fill(progress: Progress, value: string, options: types.CommonActionOptions): Promise<'error:notconnected' | 'done'> { @@ -628,33 +605,27 @@ export class ElementHandle extends js.JSHandle { }, options); } - async selectText(metadata: CallMetadata, options: types.CommonActionOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - const result = await this._retryAction(progress, 'selectText', async () => { - if (!options.force) - progress.log(' waiting for element to be visible'); - return await progress.race(this.evaluateInUtility(async ([injected, node, { force }]) => { - if (!force) { - const checkResult = await injected.checkElementStates(node, ['visible']); - if (checkResult) - return checkResult; - } - return injected.selectText(node); - }, { force: options.force })); - }, options); - assertDone(throwRetargetableDOMError(result)); - }, options.timeout); - } - - async setInputFiles(metadata: CallMetadata, params: channels.ElementHandleSetInputFilesParams) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - const inputFileItems = await progress.race(prepareFilesForUpload(this._frame, params)); - await this._markAsTargetElement(progress); - const result = await this._setInputFiles(progress, inputFileItems); - return assertDone(throwRetargetableDOMError(result)); - }, params.timeout); + async selectText(progress: Progress, options: types.CommonActionOptions): Promise { + const result = await this._retryAction(progress, 'selectText', async () => { + if (!options.force) + progress.log(' waiting for element to be visible'); + return await progress.race(this.evaluateInUtility(async ([injected, node, { force }]) => { + if (!force) { + const checkResult = await injected.checkElementStates(node, ['visible']); + if (checkResult) + return checkResult; + } + return injected.selectText(node); + }, { force: options.force })); + }, options); + assertDone(throwRetargetableDOMError(result)); + } + + async setInputFiles(progress: Progress, params: Omit) { + const inputFileItems = await progress.race(prepareFilesForUpload(this._frame, params)); + await this._markAsTargetElement(progress); + const result = await this._setInputFiles(progress, inputFileItems); + return assertDone(throwRetargetableDOMError(result)); } async _setInputFiles(progress: Progress, items: InputFilesItems): Promise<'error:notconnected' | 'done'> { @@ -697,13 +668,10 @@ export class ElementHandle extends js.JSHandle { return 'done'; } - async focus(metadata: CallMetadata): Promise { - const controller = new ProgressController(metadata, this); - await controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._focus(progress); - return assertDone(throwRetargetableDOMError(result)); - }, 0); + async focus(progress: Progress): Promise { + await this._markAsTargetElement(progress); + const result = await this._focus(progress); + return assertDone(throwRetargetableDOMError(result)); } async _focus(progress: Progress, resetSelectionIfNotFocused?: boolean): Promise<'error:notconnected' | 'done'> { @@ -714,16 +682,13 @@ export class ElementHandle extends js.JSHandle { return await progress.race(this.evaluateInUtility(([injected, node]) => injected.blurNode(node), {})); } - async type(metadata: CallMetadata, text: string, options: { delay?: number } & types.TimeoutOptions & types.StrictOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._type(progress, text, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async type(progress: Progress, text: string, options: { delay?: number } & types.StrictOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._type(progress, text, options); + return assertDone(throwRetargetableDOMError(result)); } - async _type(progress: Progress, text: string, options: { delay?: number } & types.TimeoutOptions & types.StrictOptions): Promise<'error:notconnected' | 'done'> { + async _type(progress: Progress, text: string, options: { delay?: number } & types.StrictOptions): Promise<'error:notconnected' | 'done'> { progress.log(`elementHandle.type("${text}")`); await progress.race(this.instrumentation.onBeforeInputAction(this, progress.metadata)); const result = await this._focus(progress, true /* resetSelectionIfNotFocused */); @@ -733,16 +698,13 @@ export class ElementHandle extends js.JSHandle { return 'done'; } - async press(metadata: CallMetadata, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & types.StrictOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this._markAsTargetElement(progress); - const result = await this._press(progress, key, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async press(progress: Progress, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.StrictOptions): Promise { + await this._markAsTargetElement(progress); + const result = await this._press(progress, key, options); + return assertDone(throwRetargetableDOMError(result)); } - async _press(progress: Progress, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & types.StrictOptions): Promise<'error:notconnected' | 'done'> { + async _press(progress: Progress, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.StrictOptions): Promise<'error:notconnected' | 'done'> { progress.log(`elementHandle.press("${key}")`); await progress.race(this.instrumentation.onBeforeInputAction(this, progress.metadata)); return this._page.frameManager.waitForSignalsCreatedBy(progress, !options.noWaitAfter, async () => { @@ -754,20 +716,14 @@ export class ElementHandle extends js.JSHandle { }); } - async check(metadata: CallMetadata, options: { position?: types.Point } & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - const result = await this._setChecked(progress, true, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async check(progress: Progress, options: { position?: types.Point } & types.PointerActionWaitOptions) { + const result = await this._setChecked(progress, true, options); + return assertDone(throwRetargetableDOMError(result)); } - async uncheck(metadata: CallMetadata, options: { position?: types.Point } & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - const result = await this._setChecked(progress, false, options); - return assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async uncheck(progress: Progress, options: { position?: types.Point } & types.PointerActionWaitOptions) { + const result = await this._setChecked(progress, false, options); + return assertDone(throwRetargetableDOMError(result)); } async _setChecked(progress: Progress, state: boolean, options: { position?: types.Point } & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { @@ -798,11 +754,8 @@ export class ElementHandle extends js.JSHandle { return await this.evaluateInUtility(([injected, element, options]) => injected.ariaSnapshot(element, options), options); } - async screenshot(metadata: CallMetadata, options: ScreenshotOptions & types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run( - progress => this._page.screenshotter.screenshotElement(progress, this, options), - options.timeout); + async screenshot(progress: Progress, options: ScreenshotOptions): Promise { + return await this._page.screenshotter.screenshotElement(progress, this, options); } async querySelector(selector: string, options: types.StrictOptions): Promise { @@ -821,45 +774,42 @@ export class ElementHandle extends js.JSHandle { return this._frame.evalOnSelectorAll(selector, expression, isFunction, arg, this); } - async isVisible(metadata: CallMetadata): Promise { - return this._frame.isVisible(metadata, ':scope', {}, this); + async isVisible(progress: Progress): Promise { + return this._frame.isVisible(progress, ':scope', {}, this); } - async isHidden(metadata: CallMetadata): Promise { - return this._frame.isHidden(metadata, ':scope', {}, this); + async isHidden(progress: Progress): Promise { + return this._frame.isHidden(progress, ':scope', {}, this); } - async isEnabled(metadata: CallMetadata): Promise { - return this._frame.isEnabled(metadata, ':scope', { timeout: 0 }, this); + async isEnabled(progress: Progress): Promise { + return this._frame.isEnabled(progress, ':scope', {}, this); } - async isDisabled(metadata: CallMetadata): Promise { - return this._frame.isDisabled(metadata, ':scope', { timeout: 0 }, this); + async isDisabled(progress: Progress): Promise { + return this._frame.isDisabled(progress, ':scope', {}, this); } - async isEditable(metadata: CallMetadata): Promise { - return this._frame.isEditable(metadata, ':scope', { timeout: 0 }, this); + async isEditable(progress: Progress): Promise { + return this._frame.isEditable(progress, ':scope', {}, this); } - async isChecked(metadata: CallMetadata): Promise { - return this._frame.isChecked(metadata, ':scope', { timeout: 0 }, this); + async isChecked(progress: Progress): Promise { + return this._frame.isChecked(progress, ':scope', {}, this); } - async waitForElementState(metadata: CallMetadata, state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' | 'editable', options: types.TimeoutOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - const actionName = `wait for ${state}`; - const result = await this._retryAction(progress, actionName, async () => { - return await progress.race(this.evaluateInUtility(async ([injected, node, state]) => { - return (await injected.checkElementStates(node, [state])) || 'done'; - }, state)); - }, {}); - assertDone(throwRetargetableDOMError(result)); - }, options.timeout); + async waitForElementState(progress: Progress, state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' | 'editable'): Promise { + const actionName = `wait for ${state}`; + const result = await this._retryAction(progress, actionName, async () => { + return await progress.race(this.evaluateInUtility(async ([injected, node, state]) => { + return (await injected.checkElementStates(node, [state])) || 'done'; + }, state)); + }, {}); + assertDone(throwRetargetableDOMError(result)); } - async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions): Promise | null> { - return this._frame.waitForSelector(metadata, selector, options, this); + async waitForSelector(progress: Progress, selector: string, options: types.WaitForElementOptions): Promise | null> { + return await this._frame.waitForSelector(progress, selector, true, options, this); } async _adoptTo(context: FrameExecutionContext): Promise> { diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts index 9b1668ad80fd6..5a3487a5c10be 100644 --- a/packages/playwright-core/src/server/fileUploadUtils.ts +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -34,7 +34,7 @@ async function filesExceedUploadLimit(files: string[]) { return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit; } -export async function prepareFilesForUpload(frame: Frame, params: channels.ElementHandleSetInputFilesParams): Promise { +export async function prepareFilesForUpload(frame: Frame, params: Omit): Promise { const { payloads, streams, directoryStream } = params; let { localPaths, localDirectory } = params; diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index d428f79eb953e..c8dfa9999c04a 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -37,7 +37,6 @@ import { compressCallLog } from './callLog'; import type { ConsoleMessage } from './console'; import type { ElementStateWithoutStable, FrameExpectParams, InjectedScript } from '@injected/injectedScript'; -import type { CallMetadata } from './instrumentation'; import type { Progress } from './progress'; import type { ScreenshotOptions } from './screenshotter'; import type { RegisteredListener } from './utils/eventsHelper'; @@ -609,15 +608,12 @@ export class Frame extends SdkObject { data.gotoPromise.finally(() => this._redirectedNavigations.delete(documentId)); } - async goto(metadata: CallMetadata, url: string, options: types.GotoOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(progress => { - const constructedNavigationURL = constructURLBasedOnBaseURL(this._page.browserContext._options.baseURL, url); - return this.raceNavigationAction(progress, async () => this.gotoImpl(progress, constructedNavigationURL, options)); - }, options.timeout); + async goto(progress: Progress, url: string, options: types.GotoOptions = {}): Promise { + const constructedNavigationURL = constructURLBasedOnBaseURL(this._page.browserContext._options.baseURL, url); + return this.raceNavigationAction(progress, async () => this.gotoImpl(progress, constructedNavigationURL, options)); } - async gotoImpl(progress: Progress, url: string, options: Omit): Promise { + async gotoImpl(progress: Progress, url: string, options: types.GotoOptions): Promise { const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); progress.log(`navigating to "${url}", waiting until "${waitUntil}"`); const headers = this._page.extraHTTPHeaders() || []; @@ -673,7 +669,7 @@ export class Frame extends SdkObject { return response; } - async _waitForNavigation(progress: Progress, requiresNewDocument: boolean, options: types.NavigateOptions): Promise { + async _waitForNavigation(progress: Progress, requiresNewDocument: boolean, options: Omit): Promise { const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); progress.log(`waiting for navigation until "${waitUntil}"`); @@ -743,25 +739,18 @@ export class Frame extends SdkObject { return this.selectors.query(selector, options); } - async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions, scope?: dom.ElementHandle): Promise | null> { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - if ((options as any).visibility) - throw new Error('options.visibility is not supported, did you mean options.state?'); - if ((options as any).waitFor && (options as any).waitFor !== 'visible') - throw new Error('options.waitFor is not supported, did you mean options.state?'); - const { state = 'visible' } = options; - if (!['attached', 'detached', 'visible', 'hidden'].includes(state)) - throw new Error(`state: expected one of (attached|detached|visible|hidden)`); - progress.log(`waiting for ${this._asLocator(selector)}${state === 'attached' ? '' : ' to be ' + state}`); - return await this.waitForSelectorInternal(progress, selector, true, options, scope); - }, options.timeout); - } - - async waitForSelectorInternal(progress: Progress, selector: string, performActionPreChecks: boolean, options: Omit, scope?: dom.ElementHandle): Promise | null> { + async waitForSelector(progress: Progress, selector: string, performActionPreChecksAndLog: boolean, options: types.WaitForElementOptions, scope?: dom.ElementHandle): Promise | null> { + if ((options as any).visibility) + throw new Error('options.visibility is not supported, did you mean options.state?'); + if ((options as any).waitFor && (options as any).waitFor !== 'visible') + throw new Error('options.waitFor is not supported, did you mean options.state?'); const { state = 'visible' } = options; + if (!['attached', 'detached', 'visible', 'hidden'].includes(state)) + throw new Error(`state: expected one of (attached|detached|visible|hidden)`); + if (performActionPreChecksAndLog) + progress.log(`waiting for ${this._asLocator(selector)}${state === 'attached' ? '' : ' to be ' + state}`); const promise = this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => { - if (performActionPreChecks) + if (performActionPreChecksAndLog) await this._page.performActionPreChecks(progress); const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, options, scope)); @@ -814,8 +803,8 @@ export class Frame extends SdkObject { return scope ? scope._context._raceAgainstContextDestroyed(promise) : promise; } - async dispatchEvent(metadata: CallMetadata, selector: string, type: string, eventInit: Object = {}, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - await this._callOnElementOnceMatches(metadata, selector, (injectedScript, element, data) => { + async dispatchEvent(progress: Progress, selector: string, type: string, eventInit: Object = {}, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { + await this._callOnElementOnceMatches(progress, selector, (injectedScript, element, data) => { injectedScript.dispatchEvent(element, data.type, data.eventInit); }, { type, eventInit }, { mainWorld: true, ...options }, scope); } @@ -870,32 +859,29 @@ export class Frame extends SdkObject { } } - async setContent(metadata: CallMetadata, html: string, options: types.NavigateOptions): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - await this.raceNavigationAction(progress, async () => { - const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; - progress.log(`setting frame content, waiting until "${waitUntil}"`); - const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`; - const context = await progress.race(this._utilityContext()); - const tagPromise = new ManualPromise(); - this._page.frameManager._consoleMessageTags.set(tag, () => { - // Clear lifecycle right after document.open() - see 'tag' below. - this._onClearLifecycle(); - tagPromise.resolve(); - }); - progress.cleanupWhenAborted(() => this._page.frameManager._consoleMessageTags.delete(tag)); - const lifecyclePromise = progress.race(tagPromise).then(() => this._waitForLoadState(progress, waitUntil)); - const contentPromise = progress.race(context.evaluate(({ html, tag }) => { - document.open(); - console.debug(tag); // eslint-disable-line no-console - document.write(html); - document.close(); - }, { html, tag })); - await Promise.all([contentPromise, lifecyclePromise]); - return null; + async setContent(progress: Progress, html: string, options: Omit): Promise { + await this.raceNavigationAction(progress, async () => { + const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; + progress.log(`setting frame content, waiting until "${waitUntil}"`); + const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`; + const context = await progress.race(this._utilityContext()); + const tagPromise = new ManualPromise(); + this._page.frameManager._consoleMessageTags.set(tag, () => { + // Clear lifecycle right after document.open() - see 'tag' below. + this._onClearLifecycle(); + tagPromise.resolve(); }); - }, options.timeout); + progress.cleanupWhenAborted(() => this._page.frameManager._consoleMessageTags.delete(tag)); + const lifecyclePromise = progress.race(tagPromise).then(() => this._waitForLoadState(progress, waitUntil)); + const contentPromise = progress.race(context.evaluate(({ html, tag }) => { + document.open(); + console.debug(tag); // eslint-disable-line no-console + document.write(html); + document.close(); + }, { html, tag })); + await Promise.all([contentPromise, lifecyclePromise]); + return null; + }); } name(): string { @@ -1141,131 +1127,107 @@ export class Frame extends SdkObject { }); } - async click(metadata: CallMetadata, selector: string, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._click(progress, { ...options, waitAfter: !options.noWaitAfter }))); - }, options.timeout); + async click(progress: Progress, selector: string, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions) { + return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._click(progress, { ...options, waitAfter: !options.noWaitAfter }))); } - async dblclick(metadata: CallMetadata, selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._dblclick(progress, options))); - }, options.timeout); + async dblclick(progress: Progress, selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions) { + return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._dblclick(progress, options))); } - async dragAndDrop(metadata: CallMetadata, source: string, target: string, options: types.DragActionOptions & types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this); - await controller.run(async progress => { - dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, options.strict, !options.force /* performActionPreChecks */, async handle => { - return handle._retryPointerAction(progress, 'move and down', false, async point => { - await this._page.mouse._move(progress, point.x, point.y); - await this._page.mouse._down(progress); - }, { - ...options, - waitAfter: 'disabled', - position: options.sourcePosition, - }); - })); - // Note: do not perform locator handlers checkpoint to avoid moving the mouse in the middle of a drag operation. - dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, options.strict, false /* performActionPreChecks */, async handle => { - return handle._retryPointerAction(progress, 'move and up', false, async point => { - await this._page.mouse._move(progress, point.x, point.y); - await this._page.mouse._up(progress); - }, { - ...options, - waitAfter: 'disabled', - position: options.targetPosition, - }); - })); - }, options.timeout); + async dragAndDrop(progress: Progress, source: string, target: string, options: types.DragActionOptions & types.PointerActionWaitOptions) { + dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, options.strict, !options.force /* performActionPreChecks */, async handle => { + return handle._retryPointerAction(progress, 'move and down', false, async point => { + await this._page.mouse._move(progress, point.x, point.y); + await this._page.mouse._down(progress); + }, { + ...options, + waitAfter: 'disabled', + position: options.sourcePosition, + }); + })); + // Note: do not perform locator handlers checkpoint to avoid moving the mouse in the middle of a drag operation. + dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, options.strict, false /* performActionPreChecks */, async handle => { + return handle._retryPointerAction(progress, 'move and up', false, async point => { + await this._page.mouse._move(progress, point.x, point.y); + await this._page.mouse._up(progress); + }, { + ...options, + waitAfter: 'disabled', + position: options.targetPosition, + }); + })); } - async tap(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - if (!this._page.browserContext._options.hasTouch) - throw new Error('The page does not support tap. Use hasTouch context option to enable touch support.'); - return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._tap(progress, options))); - }, options.timeout); + async tap(progress: Progress, selector: string, options: types.PointerActionWaitOptions) { + if (!this._page.browserContext._options.hasTouch) + throw new Error('The page does not support tap. Use hasTouch context option to enable touch support.'); + return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._tap(progress, options))); } - async fill(metadata: CallMetadata, selector: string, value: string, options: types.TimeoutOptions & types.StrictOptions & { force?: boolean }) { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._fill(progress, value, options))); - }, options.timeout); + async fill(progress: Progress, selector: string, value: string, options: types.StrictOptions & { force?: boolean }) { + return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performActionPreChecks */, handle => handle._fill(progress, value, options))); } - async focus(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions) { - const controller = new ProgressController(metadata, this); - await controller.run(async progress => { - dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._focus(progress))); - }, options.timeout); + async focus(progress: Progress, selector: string, options: types.StrictOptions) { + dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._focus(progress))); } - async blur(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions) { - const controller = new ProgressController(metadata, this); - await controller.run(async progress => { - dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._blur(progress))); - }, options.timeout); + async blur(progress: Progress, selector: string, options: types.StrictOptions) { + dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._blur(progress))); } - async generateLocatorString(metadata: CallMetadata, selector: string): Promise { - const controller = new ProgressController(metadata, this); - return controller.run(async progress => { - const element = await progress.race(this.selectors.query(selector)); - if (!element) - throw new Error(`No element matching ${this._asLocator(selector)}`); - - const generated = await progress.race(element.evaluateInUtility(async ([injected, node]) => { - return injected.generateSelectorSimple(node as unknown as Element); - }, {})); - if (!generated) - throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`); - - let frame: Frame | null = element._frame; - const result = [generated]; - while (frame?.parentFrame()) { - const frameElement = await progress.race(frame.frameElement()); - if (frameElement) { - const generated = await progress.race(frameElement.evaluateInUtility(async ([injected, node]) => { - return injected.generateSelectorSimple(node as unknown as Element); - }, {})); - frameElement.dispose(); - if (generated === 'error:notconnected' || !generated) - throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`); - result.push(generated); - } - frame = frame.parentFrame(); + async generateLocatorString(progress: Progress, selector: string): Promise { + const element = await progress.race(this.selectors.query(selector)); + if (!element) + throw new Error(`No element matching ${this._asLocator(selector)}`); + + const generated = await progress.race(element.evaluateInUtility(async ([injected, node]) => { + return injected.generateSelectorSimple(node as unknown as Element); + }, {})); + if (!generated) + throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`); + + let frame: Frame | null = element._frame; + const result = [generated]; + while (frame?.parentFrame()) { + const frameElement = await progress.race(frame.frameElement()); + if (frameElement) { + const generated = await progress.race(frameElement.evaluateInUtility(async ([injected, node]) => { + return injected.generateSelectorSimple(node as unknown as Element); + }, {})); + frameElement.dispose(); + if (generated === 'error:notconnected' || !generated) + throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`); + result.push(generated); } - return asLocator(this._page.browserContext._browser.sdkLanguage(), result.reverse().join(' >> internal:control=enter-frame >> ')); - }); + frame = frame.parentFrame(); + } + return asLocator(this._page.browserContext._browser.sdkLanguage(), result.reverse().join(' >> internal:control=enter-frame >> ')); } - async textContent(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.textContent, undefined, options, scope); + async textContent(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { + return this._callOnElementOnceMatches(progress, selector, (injected, element) => element.textContent, undefined, options, scope); } - async innerText(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(metadata, selector, (injectedScript, element) => { + async innerText(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { + return this._callOnElementOnceMatches(progress, selector, (injectedScript, element) => { if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml') throw injectedScript.createStacklessError('Node is not an HTMLElement'); return (element as HTMLElement).innerText; }, undefined, options, scope); } - async innerHTML(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.innerHTML, undefined, options, scope); + async innerHTML(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { + return this._callOnElementOnceMatches(progress, selector, (injected, element) => element.innerHTML, undefined, options, scope); } - async getAttribute(metadata: CallMetadata, selector: string, name: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(metadata, selector, (injected, element, data) => element.getAttribute(data.name), { name }, options, scope); + async getAttribute(progress: Progress, selector: string, name: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise { + return this._callOnElementOnceMatches(progress, selector, (injected, element, data) => element.getAttribute(data.name), { name }, options, scope); } - async inputValue(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions, scope?: dom.ElementHandle): Promise { - return this._callOnElementOnceMatches(metadata, selector, (injectedScript, node) => { + async inputValue(progress: Progress, selector: string, options: types.StrictOptions, scope?: dom.ElementHandle): Promise { + return this._callOnElementOnceMatches(progress, selector, (injectedScript, node) => { const element = injectedScript.retarget(node, 'follow-label'); if (!element || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')) throw injectedScript.createStacklessError('Node is not an , should not report AAA as a child of the textarea. if (ariaNode.role !== 'textbox' && text) @@ -72,10 +75,8 @@ export function generateAriaTree(rootElement: Element, options?: { forAI?: boole return; const element = node as Element; - let isVisible = !roleUtils.isElementHiddenForAria(element); - if (options?.forAI) - isVisible = isVisible || isElementVisible(element); - if (!isVisible) + const isElementHiddenForAria = roleUtils.isElementHiddenForAria(element); + if (isElementHiddenForAria && !options?.forAI) return; const ariaChildren: Element[] = []; @@ -88,16 +89,17 @@ export function generateAriaTree(rootElement: Element, options?: { forAI?: boole } } - const childAriaNode = toAriaNode(element, options); + const visible = !isElementHiddenForAria || isElementVisible(element); + const childAriaNode = visible ? toAriaNode(element, options) : null; if (childAriaNode) { if (childAriaNode.ref) snapshot.elements.set(childAriaNode.ref, element); ariaNode.children.push(childAriaNode); } - processElement(childAriaNode || ariaNode, element, ariaChildren); + processElement(childAriaNode || ariaNode, element, ariaChildren, visible); }; - function processElement(ariaNode: AriaNode, element: Element, ariaChildren: Element[] = []) { + function processElement(ariaNode: AriaNode, element: Element, ariaChildren: Element[], parentElementVisible: boolean) { // Surround every element with spaces for the sake of concatenated text nodes. const display = getElementComputedStyle(element)?.display || 'inline'; const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : ''; @@ -108,20 +110,20 @@ export function generateAriaTree(rootElement: Element, options?: { forAI?: boole const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : []; if (assignedNodes.length) { for (const child of assignedNodes) - visit(ariaNode, child); + visit(ariaNode, child, parentElementVisible); } else { for (let child = element.firstChild; child; child = child.nextSibling) { if (!(child as Element | Text).assignedSlot) - visit(ariaNode, child); + visit(ariaNode, child, parentElementVisible); } if (element.shadowRoot) { for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling) - visit(ariaNode, child); + visit(ariaNode, child, parentElementVisible); } } for (const child of ariaChildren) - visit(ariaNode, child); + visit(ariaNode, child, parentElementVisible); ariaNode.children.push(roleUtils.getCSSContent(element, '::after') || ''); @@ -139,7 +141,7 @@ export function generateAriaTree(rootElement: Element, options?: { forAI?: boole roleUtils.beginAriaCaches(); try { - visit(snapshot.root, rootElement); + visit(snapshot.root, rootElement, true); } finally { roleUtils.endAriaCaches(); } diff --git a/tests/page/page-aria-snapshot-ai.spec.ts b/tests/page/page-aria-snapshot-ai.spec.ts index 12bc6186326ca..09829911ed51b 100644 --- a/tests/page/page-aria-snapshot-ai.spec.ts +++ b/tests/page/page-aria-snapshot-ai.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { test as it, expect } from './pageTest'; +import { test as it, expect, unshift } from './pageTest'; function snapshotForAI(page: any): Promise { return page._snapshotForAI(); @@ -257,6 +257,31 @@ it('should auto-wait for blocking CSS', async ({ page, server }) => { expect(await snapshotForAI(page)).toContainYaml('Hello World'); }); +it('should show visible children of hidden elements', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36296' } }, async ({ page }) => { + await page.setContent(` +
+
+ +
+
+ +
+
+
+ +
+ +
+
+ `); + + expect(await snapshotForAI(page)).toEqual(unshift(` + - generic [active] [ref=e1]: + - button "Visible" [ref=e3] + - button "Visible" [ref=e4] + `)); +}); + it('should include active element information', async ({ page }) => { await page.setContent(` diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index ce19ea435106f..e08f4830da0f6 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -15,20 +15,7 @@ */ import type { Locator } from '@playwright/test'; -import { test as it, expect } from './pageTest'; - -function unshift(snapshot: string): string { - const lines = snapshot.split('\n'); - let whitespacePrefixLength = 100; - for (const line of lines) { - if (!line.trim()) - continue; - const match = line.match(/^(\s*)/); - if (match && match[1].length < whitespacePrefixLength) - whitespacePrefixLength = match[1].length; - } - return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n'); -} +import { test as it, expect, unshift } from './pageTest'; async function checkAndMatchSnapshot(locator: Locator, snapshot: string) { expect.soft(await locator.ariaSnapshot()).toBe(unshift(snapshot)); @@ -658,3 +645,27 @@ it('should not report textarea textContent', async ({ page }) => { - textbox: After `); }); + +it('should not show visible children of hidden elements', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36296' } }, async ({ page }) => { + await page.setContent(` +
+
+ +
+
+ `); + + expect(await page.locator('body').ariaSnapshot()).toBe(''); +}); + +it('should not show unhidden children of aria-hidden elements', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36296' } }, async ({ page }) => { + await page.setContent(` + + `); + + expect(await page.locator('body').ariaSnapshot()).toBe(''); +}); diff --git a/tests/page/pageTest.ts b/tests/page/pageTest.ts index f6647563885f6..52d9b95c67429 100644 --- a/tests/page/pageTest.ts +++ b/tests/page/pageTest.ts @@ -54,6 +54,19 @@ export function roundBox(box: BoundingBox): BoundingBox { }; } +export function unshift(snapshot: string): string { + const lines = snapshot.split('\n'); + let whitespacePrefixLength = 100; + for (const line of lines) { + if (!line.trim()) + continue; + const match = line.match(/^(\s*)/); + if (match && match[1].length < whitespacePrefixLength) + whitespacePrefixLength = match[1].length; + } + return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n'); +} + export const expect = baseExpect.extend({ toContainYaml(received: string, expected: string) { const trimmed = expected.split('\n').filter(a => !!a.trim()); From a0cdb1666da5429c48165c40058120a6f4ea87dc Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 09:32:56 +0200 Subject: [PATCH 182/222] feat(webkit): roll to r2190 (#36584) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- packages/playwright-core/src/server/webkit/protocol.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index affe00ada2970..61b36318d3e05 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2189", + "revision": "2190", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index 86d9919df8001..69fefd94fc63b 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -732,9 +732,9 @@ export module Protocol { */ export interface Grouping { /** - * Source of the media query: "media-rule" if specified by a @media rule, "media-import-rule" if specified by an @import rule, "media-link-node" if specified by a "media" attribute in a linked style sheet's LINK tag, "media-style-node" if specified by a "media" attribute in an inline style sheet's STYLE tag, "supports-rule" if specified by an @supports rule, "layer-rule" if specified by an @layer rule, "container-rule" if specified by an @container rule, "style-rule" if specified by a CSSStyleRule containing the rule inside this grouping. + * Source of the media query: "media-rule" if specified by a @media rule, "media-import-rule" if specified by an @import rule, "media-link-node" if specified by a "media" attribute in a linked style sheet's LINK tag, "media-style-node" if specified by a "media" attribute in an inline style sheet's STYLE tag, "supports-rule" if specified by an @supports rule, "layer-rule" if specified by an @layer rule, "container-rule" if specified by an @container rule, "scope-rule" if specified by a @scope rule, "starting-style-rule" if specified by a @starting-style rule, "style-rule" if specified by a CSSStyleRule containing the rule inside this grouping. */ - type: "media-rule"|"media-import-rule"|"media-link-node"|"media-style-node"|"supports-rule"|"layer-rule"|"layer-import-rule"|"container-rule"|"style-rule"; + type: "media-rule"|"media-import-rule"|"media-link-node"|"media-style-node"|"supports-rule"|"layer-rule"|"layer-import-rule"|"container-rule"|"scope-rule"|"starting-style-rule"|"style-rule"; /** * The CSS rule identifier for the `@rule` (absent for non-editable grouping rules) or the nesting parent style rule's selector. In CSSOM terms, this is the parent rule of either the previous Grouping for a CSSRule, or of a CSSRule itself. */ From 544017ca16a75e6968099d8c63098c6642029577 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:55:21 +0200 Subject: [PATCH 183/222] feat(chromium-tip-of-tree): roll to r1345 (#36590) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 61b36318d3e05..4ccf665f818c3 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,15 +15,15 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1343", + "revision": "1345", "installByDefault": false, - "browserVersion": "139.0.7258.0" + "browserVersion": "140.0.7271.0" }, { "name": "chromium-tip-of-tree-headless-shell", - "revision": "1343", + "revision": "1345", "installByDefault": false, - "browserVersion": "139.0.7258.0" + "browserVersion": "140.0.7271.0" }, { "name": "firefox", From 39a930573ccf2185c31265503de10bb4938413c4 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 8 Jul 2025 05:28:56 -0700 Subject: [PATCH 184/222] chore: remove unnecessary casts to any (#36555) --- packages/playwright/src/index.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index c145cf3b6307b..e2220395fa744 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -27,8 +27,10 @@ import { stepTitle } from './util'; import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test'; import type { ContextReuseMode } from './common/config'; import type { TestInfoImpl, TestStepInternal } from './worker/testInfo'; -import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation'; +import type { ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation'; import type { Playwright as PlaywrightImpl } from '../../playwright-core/src/client/playwright'; +import type { Browser as BrowserImpl } from '../../playwright-core/src/client/browser'; +import type { BrowserContext as BrowserContextImpl } from '../../playwright-core/src/client/browserContext'; import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core'; export { expect } from './matchers/expect'; @@ -110,7 +112,7 @@ const playwrightFixtures: Fixtures = ({ }, }); await use(browser); - await (browser as any)._wrapApiCall(async () => { + await (browser as BrowserImpl)._wrapApiCall(async () => { await browser.close({ reason: 'Test ended.' }); }, { internal: true }); return; @@ -118,7 +120,7 @@ const playwrightFixtures: Fixtures = ({ const browser = await playwright[browserName].launch(); await use(browser); - await (browser as any)._wrapApiCall(async () => { + await (browser as BrowserImpl)._wrapApiCall(async () => { await browser.close({ reason: 'Test ended.' }); }, { internal: true }); }, { scope: 'worker', timeout: 0 }], @@ -318,7 +320,7 @@ const playwrightFixtures: Fixtures = ({ }, }; - const clientInstrumentation = (playwright as any)._instrumentation as ClientInstrumentation; + const clientInstrumentation = (playwright as PlaywrightImpl)._instrumentation; clientInstrumentation.addListener(csiListener); await use(); @@ -355,12 +357,12 @@ const playwrightFixtures: Fixtures = ({ context.on('page', page => contextData.pagesWithVideo.push(page)); if (process.env.PW_CLOCK === 'frozen') { - await (context as any)._wrapApiCall(async () => { + await (context as BrowserContextImpl)._wrapApiCall(async () => { await context.clock.install({ time: 0 }); await context.clock.pauseAt(1000); }, { internal: true }); } else if (process.env.PW_CLOCK === 'realtime') { - await (context as any)._wrapApiCall(async () => { + await (context as BrowserContextImpl)._wrapApiCall(async () => { await context.clock.install({ time: 0 }); }, { internal: true }); } @@ -371,7 +373,7 @@ const playwrightFixtures: Fixtures = ({ let counter = 0; const closeReason = testInfo.status === 'timedOut' ? 'Test timeout of ' + testInfo.timeout + 'ms exceeded.' : 'Test ended.'; await Promise.all([...contexts.keys()].map(async context => { - await (context as any)._wrapApiCall(async () => { + await (context as BrowserContextImpl)._wrapApiCall(async () => { await context.close({ reason: closeReason }); }, { internal: true }); const testFailed = testInfo.status !== testInfo.expectedStatus; @@ -413,10 +415,10 @@ const playwrightFixtures: Fixtures = ({ } const defaultContextOptions = (playwright.chromium as any)._defaultContextOptions as BrowserContextOptions; - const context = await (browser as any)._newContextForReuse(defaultContextOptions); + const context = await (browser as BrowserImpl)._newContextForReuse(defaultContextOptions); await use(context); const closeReason = testInfo.status === 'timedOut' ? 'Test timeout of ' + testInfo.timeout + 'ms exceeded.' : 'Test ended.'; - await (browser as any)._disconnectFromReusedContext(closeReason); + await (browser as BrowserImpl)._disconnectFromReusedContext(closeReason); }, page: async ({ context, _reuseContext }, use) => { @@ -471,7 +473,7 @@ function normalizeScreenshotMode(screenshot: ScreenshotOption): ScreenshotMode { } function attachConnectedHeaderIfNeeded(testInfo: TestInfo, browser: Browser | null) { - const connectHeaders: { name: string, value: string }[] | undefined = (browser as any)?._connection.headers; + const connectHeaders: { name: string, value: string }[] | undefined = (browser as BrowserImpl | null)?._connection.headers; if (!connectHeaders) return; for (const header of connectHeaders) { From 966f7f58950e59560f152df601e9d92bf8753b2c Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 8 Jul 2025 14:02:18 +0100 Subject: [PATCH 185/222] chore: update dotnet channels generator (#36593) --- utils/generate_dotnet_channels.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/utils/generate_dotnet_channels.js b/utils/generate_dotnet_channels.js index 15a04af5f1d83..6ff4b8925d315 100644 --- a/utils/generate_dotnet_channels.js +++ b/utils/generate_dotnet_channels.js @@ -109,7 +109,7 @@ function inlineType(type, indent = '', name, level) { if (type.type.startsWith('object')) { const optional = type.type.endsWith('?'); - const custom = processCustomType(type, optional); + const custom = processCustomType(type, optional, name); if (custom) return custom; if (level >= 1) { @@ -186,8 +186,8 @@ fs.mkdirSync(dir, { recursive: true }); for (const [name, item] of Object.entries(protocol)) { if (item.type === 'interface') { - const init = objectType(item.initializer || {}, ''); const initializerName = name + 'Initializer'; + const init = objectType(item.initializer || {}, '', false, initializerName); const superName = inherits.has(name) ? inherits.get(name) + 'Initializer' : null; writeCSharpClass(initializerName, superName, init.ts); } else if (item.type === 'object') { @@ -234,7 +234,7 @@ function toTitleCase(name) { return name.charAt(0).toUpperCase() + name.substring(1); } -function processCustomType(type, optional) { +function processCustomType(type, optional, fullName) { if (type.properties.name && type.properties.value && inlineType(type.properties.name).ts === 'string' @@ -258,4 +258,6 @@ function processCustomType(type, optional) { && inlineType(type.properties.name).ts === 'string') return { ts: 'DeviceDescriptorEntry', scheme: 'tObject()', optional }; + if (fullName === 'BrowserContextInitializerOptions') + return { ts: 'System.Text.Json.JsonElement', scheme: 'tObject()', optional }; } From 4c25573c3bc7793e953b33ac04faa54658ba5f4d Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 8 Jul 2025 06:48:47 -0700 Subject: [PATCH 186/222] fix(ct): enhance error messages when no index.html template is provided (#36556) --- packages/playwright-ct-core/src/devServer.ts | 3 ++- packages/playwright-ct-core/src/mount.ts | 2 ++ packages/playwright-ct-core/src/vitePlugin.ts | 4 +-- .../playwright.ct-react.spec.ts | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/playwright-ct-core/src/devServer.ts b/packages/playwright-ct-core/src/devServer.ts index 6e3b555a07918..c715f585a1381 100644 --- a/packages/playwright-ct-core/src/devServer.ts +++ b/packages/playwright-ct-core/src/devServer.ts @@ -18,6 +18,7 @@ import fs from 'fs'; import path from 'path'; import { Watcher } from 'playwright/lib/fsWatcher'; +import { colors } from 'playwright-core/lib/utilsBundle'; import { source as injectedSource } from './generated/indexSource'; import { createConfig, frameworkConfig, populateComponentsFromTests, resolveDirs, transformIndexFile } from './viteUtils'; @@ -35,7 +36,7 @@ export async function runDevServer(config: FullConfig): Promise<() => Promise {}; } const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8'); diff --git a/packages/playwright-ct-core/src/mount.ts b/packages/playwright-ct-core/src/mount.ts index 9899a1b414378..94c7de8d3d9a4 100644 --- a/packages/playwright-ct-core/src/mount.ts +++ b/packages/playwright-ct-core/src/mount.ts @@ -50,6 +50,8 @@ export const fixtures: Fixtures page: async ({ page }, use, info) => { if (!((info as any)._configInternal as FullConfigInternal).defineConfigWasUsed) throw new Error('Component testing requires the use of the defineConfig() in your playwright-ct.config.{ts,js}: https://aka.ms/playwright/ct-define-config'); + if (!process.env.PLAYWRIGHT_TEST_BASE_URL) + throw new Error('Component testing could not determine the base URL of your component under test. Ensure you have supplied a template playwright/index.html or have set the PLAYWRIGHT_TEST_BASE_URL environment variable.'); await (page as PageImpl)._wrapApiCall(async () => { await page.exposeFunction('__ctDispatchFunction', (ordinal: number, args: any[]) => { boundCallbacksForMount[ordinal](...args); diff --git a/packages/playwright-ct-core/src/vitePlugin.ts b/packages/playwright-ct-core/src/vitePlugin.ts index d31af197d1d18..f932bfd500265 100644 --- a/packages/playwright-ct-core/src/vitePlugin.ts +++ b/packages/playwright-ct-core/src/vitePlugin.ts @@ -23,7 +23,7 @@ import { removeDirAndLogToConsole } from 'playwright/lib/util'; import { stoppable } from 'playwright/lib/utilsBundle'; import { isURLAvailable } from 'playwright-core/lib/utils'; import { assert, calculateSha1, getPlaywrightVersion } from 'playwright-core/lib/utils'; -import { debug } from 'playwright-core/lib/utilsBundle'; +import { colors, debug } from 'playwright-core/lib/utilsBundle'; import { runDevServer } from './devServer'; import { source as injectedSource } from './generated/indexSource'; @@ -127,7 +127,7 @@ export async function buildBundle(config: FullConfig, configDir: string): Promis const dirs = await resolveDirs(configDir, config); if (!dirs) { // eslint-disable-next-line no-console - console.log(`Template file playwright/index.html is missing.`); + console.log(colors.red(`Component testing template file playwright/index.html is missing and there is no existing Vite server. Component tests will fail.\n`)); return null; } diff --git a/tests/playwright-test/playwright.ct-react.spec.ts b/tests/playwright-test/playwright.ct-react.spec.ts index ed3fa47cafb5b..a5a545347d98a 100644 --- a/tests/playwright-test/playwright.ct-react.spec.ts +++ b/tests/playwright-test/playwright.ct-react.spec.ts @@ -596,3 +596,30 @@ test('should allow import from shared file', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); }); + +test('should throw test error when template index.html is not provided', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/experimental-ct-react'; + export default defineConfig({}); + `, + 'src/component.jsx': ` + export const Component = () => <>; + `, + + 'src/component.test.jsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Component } from './component'; + + test('pass component', async ({ page, mount }) => { + const component = await mount(); + await expect(page).toHaveURL('http://127.0.0.1:8080/'); + }); + `, + }, { workers: 1 }); + + expect(result.exitCode).toBe(1); + expect(result.passed).toBe(0); + expect(result.output).toContain('Component testing template file playwright/index.html is missing and there is no existing Vite server. Component tests will fail.'); + expect(result.results[0].error.message).toBe('Error: Component testing could not determine the base URL of your component under test. Ensure you have supplied a template playwright/index.html or have set the PLAYWRIGHT_TEST_BASE_URL environment variable.'); +}); From 360c653b64f3de63843f53980d1214d8a34b16de Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 8 Jul 2025 06:49:12 -0700 Subject: [PATCH 187/222] fix(test): support whitespace in test tags (#36557) --- packages/html-reporter/src/filter.ts | 22 +++++++---- packages/html-reporter/src/headerView.tsx | 11 +++--- packages/html-reporter/src/testFileView.tsx | 5 +-- packages/playwright/src/common/test.ts | 14 ++++++- packages/playwright/src/runner/loadUtils.ts | 4 +- tests/playwright-test/reporter-html.spec.ts | 43 +++++++++++++++++++++ tests/playwright-test/test-tag.spec.ts | 3 ++ 7 files changed, 82 insertions(+), 20 deletions(-) diff --git a/packages/html-reporter/src/filter.ts b/packages/html-reporter/src/filter.ts index 04034c8ceb3fc..7e54c98d4d0dd 100644 --- a/packages/html-reporter/src/filter.ts +++ b/packages/html-reporter/src/filter.ts @@ -210,12 +210,16 @@ function cacheSearchValues(test: TestCaseSummary & { [searchValuesSymbol]?: Sear return searchValues; } -export function filterWithToken(tokens: string[], token: string, append: boolean): string { - if (append) { - if (!tokens.includes(token)) - return '#?q=' + [...tokens, token].join(' ').trim(); - return '#?q=' + tokens.filter(t => t !== token).join(' ').trim(); - } +// Extract quoted groups of search params, or tokens separated by whitespace +const SEARCH_PARAM_GROUP_REGEX = /("[^"]*"|"[^"]*$|\S+)/g; + +export function filterWithQuery(existingQuery: string, token: string, append: boolean): string { + const tokens = [...existingQuery.matchAll(SEARCH_PARAM_GROUP_REGEX)].map(m => { + const rawValue = m[0]; + return rawValue.startsWith('"') && rawValue.endsWith('"') && rawValue.length > 1 ? rawValue.slice(1, rawValue.length - 1) : rawValue; + }); + if (append) + return '#?q=' + joinTokens(!tokens.includes(token) ? [...tokens, token] : tokens.filter(t => t !== token)); // if metaKey or ctrlKey is not pressed, replace existing token with new token let prefix: 's:' | 'p:' | '@'; @@ -228,5 +232,9 @@ export function filterWithToken(tokens: string[], token: string, append: boolean const newTokens = tokens.filter(t => !t.startsWith(prefix)); newTokens.push(token); - return '#?q=' + newTokens.join(' ').trim(); + return '#?q=' + joinTokens(newTokens); +} + +function joinTokens(tokens: string[]): string { + return tokens.map(token => /\s/.test(token) ? `"${token}"` : token).join(' ').trim(); } diff --git a/packages/html-reporter/src/headerView.tsx b/packages/html-reporter/src/headerView.tsx index 0b80590212b8d..c0804a6681762 100644 --- a/packages/html-reporter/src/headerView.tsx +++ b/packages/html-reporter/src/headerView.tsx @@ -22,7 +22,7 @@ import './headerView.css'; import * as icons from './icons'; import { Link, navigate, SearchParamsContext } from './links'; import { statusIcon } from './statusIcon'; -import { filterWithToken } from './filter'; +import { filterWithQuery } from './filter'; import { linkifyText } from '@web/renderUtils'; export const HeaderView: React.FC<{ @@ -83,21 +83,20 @@ const StatsNavView: React.FC<{ }> = ({ stats }) => { const searchParams = React.useContext(SearchParamsContext); const q = searchParams.get('q')?.toString() || ''; - const tokens = q.split(' '); return ; diff --git a/packages/html-reporter/src/testFileView.tsx b/packages/html-reporter/src/testFileView.tsx index 2086272bb852d..ed26b1fc5f984 100644 --- a/packages/html-reporter/src/testFileView.tsx +++ b/packages/html-reporter/src/testFileView.tsx @@ -18,7 +18,7 @@ import type { TestCaseSummary, TestFileSummary } from './types'; import * as React from 'react'; import { hashStringToInt, msToString } from './utils'; import { Chip } from './chip'; -import { filterWithToken } from './filter'; +import { filterWithQuery } from './filter'; import { Link, LinkBadge, navigate, ProjectLink, SearchParamsContext, testResultHref, TraceLink } from './links'; import { statusIcon } from './statusIcon'; import './testFileView.css'; @@ -93,8 +93,7 @@ const LabelsClickView: React.FC { e.preventDefault(); const q = searchParams.get('q')?.toString() || ''; - const tokens = q.split(' '); - navigate(filterWithToken(tokens, label, e.metaKey || e.ctrlKey)); + navigate(filterWithQuery(q, label, e.metaKey || e.ctrlKey)); }; return labels.length > 0 ? ( diff --git a/packages/playwright/src/common/test.ts b/packages/playwright/src/common/test.ts index 7ef91d27c5dd5..dce6cbcd836ad 100644 --- a/packages/playwright/src/common/test.ts +++ b/packages/playwright/src/common/test.ts @@ -289,7 +289,12 @@ export class TestCase extends Base implements reporterTypes.TestCase { } get tags(): string[] { - return this._grepTitle().match(/@[\S]+/g) || []; + const titleTags = this._grepBaseTitlePath().join(' ').match(/@[\S]+/g) || []; + + return [ + ...titleTags, + ...this._tags, + ]; } _serialize(): any { @@ -354,10 +359,15 @@ export class TestCase extends Base implements reporterTypes.TestCase { return result; } - _grepTitle() { + _grepBaseTitlePath(): string[] { const path: string[] = []; this.parent._collectGrepTitlePath(path); path.push(this.title); + return path; + } + + _grepTitleWithTags(): string { + const path = this._grepBaseTitlePath(); path.push(...this._tags); return path.join(' '); } diff --git a/packages/playwright/src/runner/loadUtils.ts b/packages/playwright/src/runner/loadUtils.ts index 200f668e5df59..f4963c478fed9 100644 --- a/packages/playwright/src/runner/loadUtils.ts +++ b/packages/playwright/src/runner/loadUtils.ts @@ -225,7 +225,7 @@ function createProjectSuite(project: FullProjectInternal, fileSuites: Suite[]): const grepMatcher = createTitleMatcher(project.project.grep); const grepInvertMatcher = project.project.grepInvert ? createTitleMatcher(project.project.grepInvert) : null; filterTestsRemoveEmptySuites(projectSuite, (test: TestCase) => { - const grepTitle = test._grepTitle(); + const grepTitle = test._grepTitleWithTags(); if (grepInvertMatcher?.(grepTitle)) return false; return grepMatcher(grepTitle); @@ -244,7 +244,7 @@ function filterProjectSuite(projectSuite: Suite, options: { cliFileFilters: Test if (options.testIdMatcher) filterByTestIds(result, options.testIdMatcher); filterTestsRemoveEmptySuites(result, (test: TestCase) => { - if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitle())) + if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags())) return false; if (options.additionalFileMatcher && !options.additionalFileMatcher(test.location.file)) return false; diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index d8f1df6afa004..0cef505d20d2c 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -1928,6 +1928,49 @@ for (const useIntermediateMergeReport of [true, false] as const) { } }); + test('tags with whitespace', async ({ runInlineTest, showReport, page }) => { + const result = await runInlineTest({ + 'a.test.js': ` + const { expect, test } = require('@playwright/test'); + const tags = ['@smoke-p1 with other text', '@issue[123] issue[456]', '@issue#123 issue#456', '@$$$ ???', '@tl/dr didn\\'t read']; + + test.describe('Error Pages', () => { + tags.forEach(tag => { + test(tag.replace('@', '') + ' passes', { tag: [tag] }, async ({}) => { + expect(1).toBe(1); + }); + }); + }); + `, + }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(5); + + await showReport(); + const tags = ['smoke-p1 with other text', 'issue[123] issue[456]', 'issue#123 issue#456', '$$$ ???', 'tl/dr didn\'t read']; + const searchInput = page.locator('.subnav-search-input'); + + for (const tag of tags) { + const tagButton = page.locator('.label').getByText(tag, { exact: true }); + await expect(tagButton).toBeVisible(); + + await tagButton.click(); + await expect(page.locator('.test-file-test')).toHaveCount(1); + await expect(page.locator('.chip')).toHaveCount(1); + await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1); + await expect(page.locator('.test-file-test .test-file-title')).toHaveText(`Error Pages › ${tag} passes`); + + const testTitle = page.locator('.test-file-test .test-file-title', { hasText: `${tag} passes` }); + await testTitle.click(); + await expect(page.locator('.header-title', { hasText: `${tag} passes` })).toBeVisible(); + await expect(page.locator('.label', { hasText: tag })).toBeVisible(); + + await page.goBack(); + await searchInput.clear(); + } + }); + test('click label should change URL', async ({ runInlineTest, showReport, page }) => { const result = await runInlineTest({ 'a.test.js': ` diff --git a/tests/playwright-test/test-tag.spec.ts b/tests/playwright-test/test-tag.spec.ts index 9487e31ea3a42..049ad49f3c00e 100644 --- a/tests/playwright-test/test-tag.spec.ts +++ b/tests/playwright-test/test-tag.spec.ts @@ -47,6 +47,8 @@ test('should have correct tags', async ({ runInlineTest }) => { }); test('foo-bar-tags', { tag: ['@foo', '@bar'] }, () => { }); + test('foo-bar-tags with @inline', { tag: ['@foo', '@bar', '@this is a long tag', '@another', '@long one again'] }, () => { + }); test.skip('skip-foo-tag', { tag: '@foo' }, () => { }); test.fixme('fixme-bar-tag', { tag: '@bar' }, () => { @@ -77,6 +79,7 @@ test('should have correct tags', async ({ runInlineTest }) => { `title=no-tags, tags=`, `title=foo-tag @inline, tags=@inline,@foo`, `title=foo-bar-tags, tags=@foo,@bar`, + `title=foo-bar-tags with @inline, tags=@inline,@foo,@bar,@this is a long tag,@another,@long one again`, `title=skip-foo-tag, tags=@foo`, `title=fixme-bar-tag, tags=@bar`, `title=fail-foo-bar-tags, tags=@foo,@bar`, From 44614baea16cf6c546a4bba80681971098ab253f Mon Sep 17 00:00:00 2001 From: rkrisztian <2960445+rkrisztian@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:54:51 +0000 Subject: [PATCH 188/222] docs: clarify that the installation command also works for existing projects (#36537) (#36569) --- docs/src/intro-js.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/src/intro-js.md b/docs/src/intro-js.md index 574754e9109d9..62d9190bf59cd 100644 --- a/docs/src/intro-js.md +++ b/docs/src/intro-js.md @@ -17,7 +17,11 @@ Playwright Test was created specifically to accommodate the needs of end-to-end ## Installing Playwright -Get started by installing Playwright using npm, yarn or pnpm. Alternatively you can also get started and run your tests using the [VS Code Extension](./getting-started-vscode.md). +Get started by installing Playwright using one of the following methods. + +### Using npm, yarn or pnpm + +The command below either initializes a new project with Playwright, or adds Playwright setup to your current project. - Run the install command and select the following to get started: - Choose between TypeScript or JavaScript (default is TypeScript) - - Name of your Tests folder (default is tests or e2e if you already have a tests folder in your project) + - Name of your Tests folder (default is `tests`, or `e2e` if you already have a `tests` folder in your project) - Add a GitHub Actions workflow to easily run tests on CI - Install Playwright browsers (default is true) +### Using the VS Code Extension + +Alternatively you can also get started and run your tests using the [VS Code Extension](./getting-started-vscode.md). + ## What's Installed Playwright will download the browsers needed as well as create the following files. From f57577106ef63438913826642795c1593b1b5e77 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 8 Jul 2025 16:11:56 +0200 Subject: [PATCH 189/222] test: remove useless aria/active test (#36596) --- .../aria-snapshot-file.spec.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/tests/playwright-test/aria-snapshot-file.spec.ts b/tests/playwright-test/aria-snapshot-file.spec.ts index 0f031fc113c56..768b8d0e72d2d 100644 --- a/tests/playwright-test/aria-snapshot-file.spec.ts +++ b/tests/playwright-test/aria-snapshot-file.spec.ts @@ -250,26 +250,3 @@ test('should respect config.expect.toMatchAriaSnapshot.pathTemplate', async ({ r expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); }); - -test('should match active element after focus', async ({ runInlineTest }, testInfo) => { - const result = await runInlineTest({ - 'a.spec.ts-snapshots/test.aria.yml': ` - - textbox "First input" - - textbox "Second input" [active] - `, - 'a.spec.ts': ` - import { test, expect } from '@playwright/test'; - test('test', async ({ page }) => { - await page.setContent(\` - - - \`); - // Focus the second input - await page.locator('#input2').focus(); - await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml' }); - }); - ` - }); - - expect(result.exitCode).toBe(0); -}); From d3647da39c6e6a5963d3da86a15b90849e192f9d Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 8 Jul 2025 16:35:07 +0200 Subject: [PATCH 190/222] chore(ports): ConsoleMessage.type remains as string in java and dotnet (#36598) --- docs/src/api/class-consolemessage.md | 6 ++++++ packages/playwright-client/types/types.d.ts | 5 ----- packages/playwright-core/types/types.d.ts | 5 ----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/src/api/class-consolemessage.md b/docs/src/api/class-consolemessage.md index a207d92e5483d..f856dffe55968 100644 --- a/docs/src/api/class-consolemessage.md +++ b/docs/src/api/class-consolemessage.md @@ -139,8 +139,14 @@ The text of the console message. ## method: ConsoleMessage.type * since: v1.8 +* langs: js, python - returns: <[ConsoleMessageType]<"log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd">> +## method: ConsoleMessage.type +* since: v1.8 +* langs: csharp, java +- returns: <[string]> + One of the following values: `'log'`, `'debug'`, `'info'`, `'error'`, `'warning'`, `'dir'`, `'dirxml'`, `'table'`, `'trace'`, `'clear'`, `'startGroup'`, `'startGroupCollapsed'`, `'endGroup'`, `'assert'`, `'profile'`, `'profileEnd'`, `'count'`, `'timeEnd'`. diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 4b58b1b4d13c0..310552535ebe0 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -18852,11 +18852,6 @@ export interface ConsoleMessage { */ text(): string; - /** - * One of the following values: `'log'`, `'debug'`, `'info'`, `'error'`, `'warning'`, `'dir'`, `'dirxml'`, `'table'`, - * `'trace'`, `'clear'`, `'startGroup'`, `'startGroupCollapsed'`, `'endGroup'`, `'assert'`, `'profile'`, - * `'profileEnd'`, `'count'`, `'timeEnd'`. - */ type(): "log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd"; } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 4b58b1b4d13c0..310552535ebe0 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -18852,11 +18852,6 @@ export interface ConsoleMessage { */ text(): string; - /** - * One of the following values: `'log'`, `'debug'`, `'info'`, `'error'`, `'warning'`, `'dir'`, `'dirxml'`, `'table'`, - * `'trace'`, `'clear'`, `'startGroup'`, `'startGroupCollapsed'`, `'endGroup'`, `'assert'`, `'profile'`, - * `'profileEnd'`, `'count'`, `'timeEnd'`. - */ type(): "log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd"; } From 117dfd191464ea2c9f3ccee0d62857f3e1c98fd0 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 8 Jul 2025 16:24:30 +0100 Subject: [PATCH 191/222] fix(types): remove `partitionKey` from `storageState()` type (#36599) --- docs/src/api/class-browsercontext.md | 1 - packages/playwright-client/types/types.d.ts | 2 -- packages/playwright-core/types/types.d.ts | 2 -- 3 files changed, 5 deletions(-) diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 375b6a48b507a..aa98489296176 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -1507,7 +1507,6 @@ Whether to emulate network being offline for the browser context. - `httpOnly` <[boolean]> - `secure` <[boolean]> - `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">> - - `partitionKey` ?<[string]> - `origins` <[Array]<[Object]>> - `origin` <[string]> - `localStorage` <[Array]<[Object]>> diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 310552535ebe0..e840adc980d49 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -9313,8 +9313,6 @@ export interface BrowserContext { secure: boolean; sameSite: "Strict"|"Lax"|"None"; - - partitionKey?: string; }>; origins: Array<{ diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 310552535ebe0..e840adc980d49 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -9313,8 +9313,6 @@ export interface BrowserContext { secure: boolean; sameSite: "Strict"|"Lax"|"None"; - - partitionKey?: string; }>; origins: Array<{ From 5f4a0c767d09fc0e2d227af7bd26f2b66167bf0d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 8 Jul 2025 13:10:11 -0700 Subject: [PATCH 192/222] fix(toMatchSnapshot): use consistent diff colors (#36605) --- packages/playwright-core/src/server/utils/comparators.ts | 4 ++-- tests/playwright-test/golden.spec.ts | 8 ++++---- tests/playwright-test/reporter-html.spec.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/playwright-core/src/server/utils/comparators.ts b/packages/playwright-core/src/server/utils/comparators.ts index 06b6bdc6ff8f4..59871ac5ef815 100644 --- a/packages/playwright-core/src/server/utils/comparators.ts +++ b/packages/playwright-core/src/server/utils/comparators.ts @@ -121,9 +121,9 @@ function compareText(actual: Buffer | string, expectedBuffer: Buffer): Comparato const lines = diff.createPatch('file', expected, actual, undefined, undefined, { context: 5 }).split('\n'); const coloredLines = lines.slice(4).map(line => { if (line.startsWith('-')) - return colors.red(line); - if (line.startsWith('+')) return colors.green(line); + if (line.startsWith('+')) + return colors.red(line); if (line.startsWith('@@')) return colors.dim(line); return line; diff --git a/tests/playwright-test/golden.spec.ts b/tests/playwright-test/golden.spec.ts index 83ae3442c799f..da6c2a8022043 100644 --- a/tests/playwright-test/golden.spec.ts +++ b/tests/playwright-test/golden.spec.ts @@ -58,8 +58,8 @@ test('should work with non-txt extensions', async ({ runInlineTest }) => { ` }); expect(result.exitCode).toBe(1); - expect(result.rawOutput).toContain(colors.red('-1,2,3')); - expect(result.rawOutput).toContain(colors.green('+1,2,4')); + expect(result.rawOutput).toContain(colors.green('-1,2,3')); + expect(result.rawOutput).toContain(colors.red('+1,2,4')); }); @@ -203,8 +203,8 @@ Line7`, }); expect(result.exitCode).toBe(1); expect(result.output).toContain('Line1'); - expect(result.rawOutput).toContain(colors.red('-Line2')); - expect(result.rawOutput).toContain(colors.green('+Line22')); + expect(result.rawOutput).toContain(colors.green('-Line2')); + expect(result.rawOutput).toContain(colors.red('+Line22')); expect(result.output).toContain('Line3'); expect(result.output).toContain('Line5'); expect(result.output).toContain('Line7'); diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 0cef505d20d2c..96c7d916ae831 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -1227,8 +1227,8 @@ for (const useIntermediateMergeReport of [true, false] as const) { await showReport(); await page.getByRole('link', { name: 'is a test' }).click(); - await expect(page.locator('.test-error-view').getByText('-old')).toHaveCSS('color', 'rgb(205, 49, 49)'); - await expect(page.locator('.test-error-view').getByText('+new', { exact: true })).toHaveCSS('color', 'rgb(0, 188, 0)'); + await expect(page.locator('.test-error-view').getByText('-old')).toHaveCSS('color', 'rgb(0, 188, 0)'); + await expect(page.locator('.test-error-view').getByText('+new', { exact: true })).toHaveCSS('color', 'rgb(205, 49, 49)'); }); test('should highlight inline textual diff in toHaveText', async ({ runInlineTest, showReport, page }) => { From 689886c4c7699a9ce209f6a318fd6b7a8816cd50 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 8 Jul 2025 13:38:00 -0700 Subject: [PATCH 193/222] chore: experimental recorder api (#36604) --- package.json | 2 +- packages/injected/src/ariaSnapshot.ts | 6 +- packages/injected/src/injectedScript.ts | 6 + .../injected/src/recorder/pollingRecorder.ts | 4 +- packages/injected/src/recorder/recorder.ts | 269 ++++++++++++++++-- .../src/client/browserContext.ts | 11 +- .../playwright-core/src/protocol/validator.ts | 8 +- .../src/server/browserContext.ts | 1 + .../src/server/codegen/csharp.ts | 2 +- .../src/server/codegen/java.ts | 2 +- .../src/server/codegen/javascript.ts | 2 +- .../src/server/codegen/python.ts | 2 +- .../dispatchers/browserContextDispatcher.ts | 17 +- .../playwright-core/src/server/recorder.ts | 13 +- .../src/server/recorder/recorderApp.ts | 24 +- .../src/server/recorder/recorderUtils.ts | 18 +- .../src/utils/isomorphic/protocolMetainfo.ts | 1 + packages/playwright-mdd/src/loop.ts | 21 +- packages/playwright-mdd/src/program.ts | 11 +- packages/playwright-mdd/src/recorderLoop.ts | 57 ++++ packages/protocol/src/channels.d.ts | 14 +- packages/protocol/src/protocol.yml | 12 +- packages/recorder/src/actions.d.ts | 3 +- packages/recorder/src/recorderTypes.d.ts | 1 - packages/trace-viewer/src/ui/snapshotTab.tsx | 1 - tests/library/inspector/cli-codegen-1.spec.ts | 12 +- tests/library/inspector/cli-codegen-2.spec.ts | 8 +- tests/library/inspector/cli-codegen-3.spec.ts | 11 +- tests/library/inspector/inspectorTest.ts | 16 +- 29 files changed, 461 insertions(+), 94 deletions(-) create mode 100644 packages/playwright-mdd/src/recorderLoop.ts diff --git a/package.json b/package.json index 31ddee8dcfe1e..1547e0f7d9fa1 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "check-deps": "node utils/check_deps.js", "build-android-driver": "./utils/build_android_driver.sh", "innerloop": "playwright run-server --reuse-browser", - "mdd": "playwright-mdd packages/playwright-mdd/specs/integration.spec.md -o packages/playwright-mdd/tests/integration.spec.ts" + "mdd": "playwright-mdd run packages/playwright-mdd/specs/integration.spec.md -o packages/playwright-mdd/tests/integration.spec.ts" }, "workspaces": [ "packages/*" diff --git a/packages/injected/src/ariaSnapshot.ts b/packages/injected/src/ariaSnapshot.ts index 92b3059495047..2088b81cdc5b4 100644 --- a/packages/injected/src/ariaSnapshot.ts +++ b/packages/injected/src/ariaSnapshot.ts @@ -37,6 +37,7 @@ export type AriaNode = AriaProps & { export type AriaSnapshot = { root: AriaNode; elements: Map; + refs: Map; }; type AriaRef = { @@ -53,6 +54,7 @@ export function generateAriaTree(rootElement: Element, options?: { forAI?: boole const snapshot: AriaSnapshot = { root: { role: 'fragment', name: '', children: [], element: rootElement, props: {}, box: box(rootElement), receivesPointerEvents: true }, elements: new Map(), + refs: new Map(), }; const visit = (ariaNode: AriaNode, node: Node, parentElementVisible: boolean) => { @@ -92,8 +94,10 @@ export function generateAriaTree(rootElement: Element, options?: { forAI?: boole const visible = !isElementHiddenForAria || isElementVisible(element); const childAriaNode = visible ? toAriaNode(element, options) : null; if (childAriaNode) { - if (childAriaNode.ref) + if (childAriaNode.ref) { snapshot.elements.set(childAriaNode.ref, element); + snapshot.refs.set(element, childAriaNode.ref); + } ariaNode.children.push(childAriaNode); } processElement(childAriaNode || ariaNode, element, ariaChildren, visible); diff --git a/packages/injected/src/injectedScript.ts b/packages/injected/src/injectedScript.ts index 2c9b11f36a090..e315b9d324486 100644 --- a/packages/injected/src/injectedScript.ts +++ b/packages/injected/src/injectedScript.ts @@ -304,6 +304,12 @@ export class InjectedScript { return renderAriaTree(this._lastAriaSnapshot, options); } + ariaSnapshotForRecorder(): { ariaSnapshot: string, refs: Map } { + const tree = generateAriaTree(this.document.body, { forAI: true }); + const ariaSnapshot = renderAriaTree(tree, { forAI: true }); + return { ariaSnapshot, refs: tree.refs }; + } + getAllByAria(document: Document, template: AriaTemplateNode): Element[] { return getAllByAria(document.documentElement, template); } diff --git a/packages/injected/src/recorder/pollingRecorder.ts b/packages/injected/src/recorder/pollingRecorder.ts index c9e824951a950..dc567b37c0558 100644 --- a/packages/injected/src/recorder/pollingRecorder.ts +++ b/packages/injected/src/recorder/pollingRecorder.ts @@ -37,8 +37,8 @@ export class PollingRecorder implements RecorderDelegate { private _pollRecorderModeTimer: number | undefined; private _lastStateJSON: string | undefined; - constructor(injectedScript: InjectedScript) { - this._recorder = new Recorder(injectedScript); + constructor(injectedScript: InjectedScript, options?: { recorderMode?: 'default' | 'api' }) { + this._recorder = new Recorder(injectedScript, options); this._embedder = injectedScript.window as any; injectedScript.onGlobalListenersRemoved.add(() => this._recorder.installListeners()); diff --git a/packages/injected/src/recorder/recorder.ts b/packages/injected/src/recorder/recorder.ts index 433e5529fb96a..852d18f86fce7 100644 --- a/packages/injected/src/recorder/recorder.ts +++ b/packages/injected/src/recorder/recorder.ts @@ -60,6 +60,7 @@ interface RecorderTool { onMouseLeave?(event: MouseEvent): void; onFocus?(event: Event): void; onScroll?(event: Event): void; + onLoad?(event: Event): void; } class NoneTool implements RecorderTool { @@ -468,7 +469,7 @@ class RecordActionTool implements RecorderTool { return; // Only allow programmatic keyups, ignore user input. - if (this._recorder.state.recorderMode === 'perform' && !this._expectProgrammaticKeyUp) { + if (!this._expectProgrammaticKeyUp) { consumeEvent(event); return; } @@ -521,8 +522,7 @@ class RecordActionTool implements RecorderTool { } // Consume event if action is not being executed. - if (this._recorder.state.recorderMode === 'perform') - consumeEvent(event); + consumeEvent(event); return false; } @@ -541,18 +541,20 @@ class RecordActionTool implements RecorderTool { } private _consumeWhenAboutToPerform(event: Event) { - if (!this._performingActions.size && this._recorder.state.recorderMode === 'perform') + if (!this._performingActions.size) consumeEvent(event); } private _performAction(action: actions.PerformOnRecordAction) { this._recorder.updateHighlight(null, false); - let promise = Promise.resolve(); - if (this._recorder.state.recorderMode === 'perform') - promise = this._innerPerformAction(action); - else - this._recorder.recordAction(action); + this._performingActions.add(action); + + const promise = this._recorder.performAction(action).then(() => { + this._performingActions.delete(action); + // If that was a keyboard action, it similarly requires new selectors for active model. + this._onFocus(false); + }); if (!this._recorder.injectedScript.isUnderTest) return; @@ -567,16 +569,6 @@ class RecordActionTool implements RecorderTool { }); } - private async _innerPerformAction(action: actions.PerformOnRecordAction) { - this._performingActions.add(action); - - return this._recorder.performAction(action).then(() => { - this._performingActions.delete(action); - // If that was a keyboard action, it similarly requires new selectors for active model. - this._onFocus(false); - }); - } - private _shouldGenerateKeyPressFor(event: KeyboardEvent): boolean { // IME can generate keyboard events that don't provide a value for the key property (e.g. chrome autofill) if (typeof event.key !== 'string') @@ -626,8 +618,227 @@ class RecordActionTool implements RecorderTool { } private _updateHighlight(userGesture: boolean) { - if (this._recorder.state.recorderMode === 'perform' || this._recorder.injectedScript.isUnderTest) - this._recorder.updateHighlight(this._hoveredModel, userGesture); + this._recorder.updateHighlight(this._hoveredModel, userGesture); + } +} + +class JsonRecordActionTool implements RecorderTool { + private _recorder: Recorder; + + constructor(recorder: Recorder) { + this._recorder = recorder; + } + + cursor() { + return 'pointer'; + } + + install() { + this._pushSnapshot(); + } + + onClick(event: MouseEvent) { + // in webkit, sliding a range element may trigger a click event with a different target if the mouse is released outside the element bounding box. + // So we check the hovered element instead, and if it is a range input, we skip click handling + const element = this._recorder.deepEventTarget(event); + if (isRangeInput(element)) + return; + // Right clicks are handled by 'contextmenu' event if its auxclick + if (event.button === 2 && event.type === 'auxclick') + return; + if (this._shouldIgnoreMouseEvent(event)) + return; + + const checkbox = asCheckbox(element); + const { ariaSnapshot, selector } = this._ariaSnapshot(element); + if (checkbox && event.detail === 1) { + // Interestingly, inputElement.checked is reversed inside this event handler. + this._recorder.recordAction({ + name: checkbox.checked ? 'check' : 'uncheck', + selector, + signals: [], + ariaSnapshot, + }); + return; + } + + this._recorder.recordAction({ + name: 'click', + selector, + ariaSnapshot, + position: positionForEvent(event), + signals: [], + button: buttonForEvent(event), + modifiers: modifiersForEvent(event), + clickCount: event.detail, + }); + } + + onDblClick(event: MouseEvent) { + const element = this._recorder.deepEventTarget(event); + if (isRangeInput(element)) + return; + if (this._shouldIgnoreMouseEvent(event)) + return; + + const { ariaSnapshot, selector } = this._ariaSnapshot(element); + this._recorder.recordAction({ + name: 'click', + selector, + ariaSnapshot, + position: positionForEvent(event), + signals: [], + button: buttonForEvent(event), + modifiers: modifiersForEvent(event), + clickCount: event.detail + }); + } + + onInput(event: Event) { + const element = this._recorder.deepEventTarget(event); + + const { ariaSnapshot, selector } = this._ariaSnapshot(element); + if (isRangeInput(element)) { + this._recorder.recordAction({ + name: 'fill', + selector, + ariaSnapshot, + signals: [], + text: element.value, + }); + return; + } + + if (['INPUT', 'TEXTAREA'].includes(element.nodeName) || element.isContentEditable) { + if (element.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((element as HTMLInputElement).type.toLowerCase())) { + // Checkbox is handled in click, we can't let input trigger on checkbox - that would mean we dispatched click events while recording. + return; + } + + this._recorder.recordAction({ + name: 'fill', + selector, + ariaSnapshot, + signals: [], + text: element.isContentEditable ? element.innerText : (element as HTMLInputElement).value, + }); + return; + } + + if (element.nodeName === 'SELECT') { + const selectElement = element as HTMLSelectElement; + this._recorder.recordAction({ + name: 'select', + selector, + ariaSnapshot, + options: [...selectElement.selectedOptions].map(option => option.value), + signals: [] + }); + return; + } + } + + onKeyDown(event: KeyboardEvent) { + if (!this._shouldGenerateKeyPressFor(event)) + return; + + const element = this._recorder.deepEventTarget(event); + const { ariaSnapshot, selector } = this._ariaSnapshot(element); + + // Similarly to click, trigger checkbox on key event, not input. + if (event.key === ' ') { + const checkbox = asCheckbox(element); + if (checkbox && event.detail === 0) { + this._recorder.recordAction({ + name: checkbox.checked ? 'uncheck' : 'check', + selector, + ariaSnapshot, + signals: [], + }); + return; + } + } + + this._recorder.recordAction({ + name: 'press', + selector, + ariaSnapshot, + signals: [], + key: event.key, + modifiers: modifiersForEvent(event), + }); + } + + onLoad() { + this._pushSnapshot(); + } + + private _pushSnapshot() { + const { ariaSnapshot } = this._ariaSnapshot(this._recorder.document.body); + this._recorder.recordAction({ + selector: '', + name: 'assertSnapshot', + signals: [], + ariaSnapshot, + }); + } + + private _shouldIgnoreMouseEvent(event: MouseEvent): boolean { + const target = this._recorder.deepEventTarget(event); + const nodeName = target.nodeName; + if (nodeName === 'SELECT' || nodeName === 'OPTION') + return true; + if (nodeName === 'INPUT' && ['date', 'range'].includes((target as HTMLInputElement).type)) + return true; + return false; + } + + private _shouldGenerateKeyPressFor(event: KeyboardEvent): boolean { + // IME can generate keyboard events that don't provide a value for the key property (e.g. chrome autofill) + if (typeof event.key !== 'string') + return false; + + // Enter aka. new line is handled in input event. + if (event.key === 'Enter' && (this._recorder.deepEventTarget(event).nodeName === 'TEXTAREA' || this._recorder.deepEventTarget(event).isContentEditable)) + return false; + // Backspace, Delete, AltGraph are changing input, will handle it there. + if (['Backspace', 'Delete', 'AltGraph'].includes(event.key)) + return false; + // Ignore the QWERTZ shortcut for creating a at sign on MacOS + if (event.key === '@' && event.code === 'KeyL') + return false; + // Allow and ignore common used shortcut for pasting. + if (navigator.platform.includes('Mac')) { + if (event.key === 'v' && event.metaKey) + return false; + } else { + if (event.key === 'v' && event.ctrlKey) + return false; + if (event.key === 'Insert' && event.shiftKey) + return false; + } + if (['Shift', 'Control', 'Meta', 'Alt', 'Process'].includes(event.key)) + return false; + const hasModifier = event.ctrlKey || event.altKey || event.metaKey; + if (event.key.length === 1 && !hasModifier) + return !this._isEditable(this._recorder.deepEventTarget(event)); + return true; + } + + private _isEditable(element: HTMLElement) { + if (element.nodeName === 'TEXTAREA' || element.nodeName === 'INPUT') + return true; + if (element.isContentEditable) + return true; + return false; + } + + private _ariaSnapshot(element: HTMLElement): { ariaSnapshot: string, selector: string }; + private _ariaSnapshot(element: HTMLElement | undefined): { ariaSnapshot: string, selector?: string } { + const { ariaSnapshot, refs } = this._recorder.injectedScript.ariaSnapshotForRecorder(); + const ref = element ? refs.get(element) : undefined; + const selector = ref ? `aria-ref=${ref}` : undefined; + return { ariaSnapshot, selector }; } } @@ -746,7 +957,7 @@ class TextAssertionTool implements RecorderTool { name: 'assertSnapshot', selector: this._hoverHighlight.selector, signals: [], - snapshot: this._recorder.injectedScript.ariaSnapshot(target, { mode: 'regex' }), + ariaSnapshot: this._recorder.injectedScript.ariaSnapshot(target, { mode: 'regex' }), }; } else { const generated = this._recorder.injectedScript.generateSelector(target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }); @@ -772,7 +983,7 @@ class TextAssertionTool implements RecorderTool { if (action?.name === 'assertValue') return action.value; if (action?.name === 'assertSnapshot') - return action.snapshot; + return action.ariaSnapshot; return ''; } @@ -1060,7 +1271,6 @@ export class Recorder { private _stylesheet: CSSStyleSheet; state: UIState = { mode: 'none', - recorderMode: 'perform', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 }, @@ -1068,7 +1278,7 @@ export class Recorder { readonly document: Document; private _delegate: RecorderDelegate = {}; - constructor(injectedScript: InjectedScript) { + constructor(injectedScript: InjectedScript, options?: { recorderMode?: 'default' | 'api' }) { this.document = injectedScript.document; this.injectedScript = injectedScript; this.highlight = injectedScript.createHighlight(); @@ -1076,7 +1286,7 @@ export class Recorder { 'none': new NoneTool(), 'standby': new NoneTool(), 'inspecting': new InspectTool(this, false), - 'recording': new RecordActionTool(this), + 'recording': options?.recorderMode === 'api' ? new JsonRecordActionTool(this) : new RecordActionTool(this), 'recording-inspecting': new InspectTool(this, false), 'assertingText': new TextAssertionTool(this, 'text'), 'assertingVisibility': new InspectTool(this, true), @@ -1121,6 +1331,7 @@ export class Recorder { addEventListener(this.document, 'mouseenter', event => this._onMouseEnter(event as MouseEvent), true), addEventListener(this.document, 'focus', event => this._onFocus(event), true), addEventListener(this.document, 'scroll', event => this._onScroll(event), true), + addEventListener(this.injectedScript.window, 'load', event => this._onLoad(event), true), ]; this.highlight.install(); @@ -1304,6 +1515,12 @@ export class Recorder { this._currentTool.onFocus?.(event); } + private _onLoad(event: Event) { + if (!event.isTrusted) + return; + this._currentTool.onLoad?.(event); + } + private _onScroll(event: Event) { if (!event.isTrusted) return; diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 97170ce041f8e..384612cccd693 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -71,6 +71,7 @@ export class BrowserContext extends ChannelOwner _closingStatus: 'none' | 'closing' | 'closed' = 'none'; private _closeReason: string | undefined; private _harRouters: HarRouter[] = []; + private _onRecorderEventSink: ((event: string, data: any) => void) | undefined; static from(context: channels.BrowserContextChannel): BrowserContext { return (context as any)._object; @@ -140,6 +141,7 @@ export class BrowserContext extends ChannelOwner this._channel.on('requestFailed', ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, Page.fromNullable(page))); this._channel.on('requestFinished', params => this._onRequestFinished(params)); this._channel.on('response', ({ response, page }) => this._onResponse(network.Response.from(response), Page.fromNullable(page))); + this._channel.on('recorderEvent', ({ event, data }) => this._onRecorderEventSink?.(event, data)); this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f)); this._setEventToSubscriptionMapping(new Map([ @@ -499,9 +501,16 @@ export class BrowserContext extends ChannelOwner await this._closedPromise; } - async _enableRecorder(params: channels.BrowserContextEnableRecorderParams) { + async _enableRecorder(params: channels.BrowserContextEnableRecorderParams, callback?: (event: string, data: any) => void) { + if (callback) + this._onRecorderEventSink = callback; await this._channel.enableRecorder(params); } + + async _disableRecorder() { + this._onRecorderEventSink = undefined; + await this._channel.disableRecorder(); + } } async function prepareStorageState(platform: Platform, options: BrowserContextOptions): Promise { diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 8cb98e1d82d61..1d53084a397b3 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -979,6 +979,10 @@ scheme.BrowserContextResponseEvent = tObject({ response: tChannel(['Response']), page: tOptional(tChannel(['Page'])), }); +scheme.BrowserContextRecorderEventEvent = tObject({ + event: tString, + data: tAny, +}); scheme.BrowserContextAddCookiesParams = tObject({ cookies: tArray(tType('SetNetworkCookie')), }); @@ -1085,7 +1089,7 @@ scheme.BrowserContextPauseResult = tOptional(tObject({})); scheme.BrowserContextEnableRecorderParams = tObject({ language: tOptional(tString), mode: tOptional(tEnum(['inspecting', 'recording'])), - recorderMode: tOptional(tEnum(['record', 'perform'])), + recorderMode: tOptional(tEnum(['default', 'api'])), pauseOnNextStatement: tOptional(tBoolean), testIdAttributeName: tOptional(tString), launchOptions: tOptional(tAny), @@ -1097,6 +1101,8 @@ scheme.BrowserContextEnableRecorderParams = tObject({ omitCallTracking: tOptional(tBoolean), }); scheme.BrowserContextEnableRecorderResult = tOptional(tObject({})); +scheme.BrowserContextDisableRecorderParams = tOptional(tObject({})); +scheme.BrowserContextDisableRecorderResult = tOptional(tObject({})); scheme.BrowserContextNewCDPSessionParams = tObject({ page: tOptional(tChannel(['Page'])), frame: tOptional(tChannel(['Frame'])), diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index ad3a75ff5c8ec..531556374b58e 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -63,6 +63,7 @@ export abstract class BrowserContext extends SdkObject { RequestContinued: 'requestcontinued', BeforeClose: 'beforeclose', VideoStarted: 'videostarted', + RecorderEvent: 'recorderevent', }; readonly _pageBindings = new Map(); diff --git a/packages/playwright-core/src/server/codegen/csharp.ts b/packages/playwright-core/src/server/codegen/csharp.ts index 98b5a182fbda9..dc6745f73b211 100644 --- a/packages/playwright-core/src/server/codegen/csharp.ts +++ b/packages/playwright-core/src/server/codegen/csharp.ts @@ -157,7 +157,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator { return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } case 'assertSnapshot': - return `await Expect(${subject}.${this._asLocator(action.selector)}).ToMatchAriaSnapshotAsync(${quote(action.snapshot)});`; + return `await Expect(${subject}.${this._asLocator(action.selector)}).ToMatchAriaSnapshotAsync(${quote(action.ariaSnapshot)});`; } } diff --git a/packages/playwright-core/src/server/codegen/java.ts b/packages/playwright-core/src/server/codegen/java.ts index 42456eb4a9549..19fefdd1db6d1 100644 --- a/packages/playwright-core/src/server/codegen/java.ts +++ b/packages/playwright-core/src/server/codegen/java.ts @@ -135,7 +135,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`; } case 'assertSnapshot': - return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).matchesAriaSnapshot(${quote(action.snapshot)});`; + return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).matchesAriaSnapshot(${quote(action.ariaSnapshot)});`; } } diff --git a/packages/playwright-core/src/server/codegen/javascript.ts b/packages/playwright-core/src/server/codegen/javascript.ts index 568f0a5113c35..9c5f7273c578f 100644 --- a/packages/playwright-core/src/server/codegen/javascript.ts +++ b/packages/playwright-core/src/server/codegen/javascript.ts @@ -120,7 +120,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { } case 'assertSnapshot': { const commentIfNeeded = this._isTest ? '' : '// '; - return `${commentIfNeeded}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot, `${commentIfNeeded} `)});`; + return `${commentIfNeeded}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.ariaSnapshot, `${commentIfNeeded} `)});`; } } } diff --git a/packages/playwright-core/src/server/codegen/python.ts b/packages/playwright-core/src/server/codegen/python.ts index d7b056f083175..314489441a652 100644 --- a/packages/playwright-core/src/server/codegen/python.ts +++ b/packages/playwright-core/src/server/codegen/python.ts @@ -128,7 +128,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { return `expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } case 'assertSnapshot': - return `expect(${subject}.${this._asLocator(action.selector)}).to_match_aria_snapshot(${quote(action.snapshot)})`; + return `expect(${subject}.${this._asLocator(action.selector)}).to_match_aria_snapshot(${quote(action.ariaSnapshot)})`; } } diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 089cb0a6ea008..e58c2ed005302 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -34,7 +34,8 @@ import { WebSocketRouteDispatcher } from './webSocketRouteDispatcher'; import { WritableStreamDispatcher } from './writableStreamDispatcher'; import { createGuid } from '../utils/crypto'; import { urlMatches } from '../../utils/isomorphic/urlMatch'; -import { RecorderApp } from '../recorder/recorderApp'; +import { Recorder } from '../recorder'; +import { ProgrammaticRecorderApp, RecorderApp } from '../recorder/recorderApp'; import type { Artifact } from '../artifact'; import type { ConsoleMessage } from '../console'; @@ -199,6 +200,9 @@ export class BrowserContextDispatcher extends Dispatcher { + this._dispatchEvent('recorderEvent', { event, data }); + }); } private _shouldDispatchNetworkEvent(request: Request, event: channels.BrowserContextUpdateSubscriptionParams['event'] & channels.PageUpdateSubscriptionParams['event']): boolean { @@ -332,9 +336,20 @@ export class BrowserContextDispatcher extends Dispatcher { + const recorder = await Recorder.forContext(this._context, params); + if (params.recorderMode === 'api') { + await ProgrammaticRecorderApp.run(this._context, recorder); + return; + } await RecorderApp.show(this._context, params); } + async disableRecorder(params: channels.BrowserContextDisableRecorderParams, progress: Progress): Promise { + const recorder = Recorder.existingForContext(this._context); + if (recorder) + recorder.setMode('none'); + } + async pause(params: channels.BrowserContextPauseParams, progress: Progress) { // Debugger will take care of this. } diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 8d5d995dcb914..8579d0aa5cd07 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -82,7 +82,7 @@ export class Recorder extends EventEmitter implements Instrume private _debugger: Debugger; private _omitCallTracking = false; private _currentLanguage: Language = 'javascript'; - private _recorderMode: 'record' | 'perform'; + private _recorderMode: 'default' | 'api'; private _signalProcessor: RecorderSignalProcessor; private _pageAliases = new Map(); @@ -102,6 +102,10 @@ export class Recorder extends EventEmitter implements Instrume return recorderPromise; } + static existingForContext(context: BrowserContext): Recorder | undefined { + return (context as any)[recorderSymbol] as Recorder; + } + private static async _create(context: BrowserContext, params: channels.BrowserContextEnableRecorderParams = {}): Promise { const recorder = new Recorder(context, params); await recorder._install(); @@ -113,7 +117,7 @@ export class Recorder extends EventEmitter implements Instrume this._context = context; this._params = params; this._mode = params.mode || 'none'; - this._recorderMode = params.recorderMode ?? 'perform'; + this._recorderMode = params.recorderMode ?? 'default'; this.handleSIGINT = params.handleSIGINT; this._signalProcessor = new RecorderSignalProcessor(); @@ -172,7 +176,6 @@ export class Recorder extends EventEmitter implements Instrume } const uiState: UIState = { mode: this._mode, - recorderMode: this._recorderMode, actionPoint, actionSelector, ariaTemplate: this._highlightedElement.ariaTemplate, @@ -222,7 +225,7 @@ export class Recorder extends EventEmitter implements Instrume await this._context.exposeBinding(progress, '__pw_recorderRecordAction', false, (source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action)); - await this._context.extendInjectedScript(rawRecorderSource.source); + await this._context.extendInjectedScript(rawRecorderSource.source, { recorderMode: this._recorderMode }); }); if (this._debugger.isPaused()) @@ -510,7 +513,7 @@ export class Recorder extends EventEmitter implements Instrume frame: frameDescription, action, description: undefined, - startTime: monotonicTime() + startTime: monotonicTime(), }; return actionInContext; } diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 3c89c9bb2d80e..201fdf9d3fc5d 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -25,12 +25,12 @@ import { launchApp } from '../launchApp'; import { ProgressController } from '../progress'; import { ThrottledFile } from './throttledFile'; import { languageSet } from '../codegen/languages'; -import { collapseActions } from './recorderUtils'; +import { collapseActions, shouldMergeAction } from './recorderUtils'; import { generateCode } from '../codegen/language'; import { Recorder, RecorderEvent } from '../recorder'; import { monotonicTime } from '../../utils/isomorphic/time'; +import { BrowserContext } from '../browserContext'; -import type { BrowserContext } from '../browserContext'; import type { Page } from '../page'; import type * as actions from '@recorder/actions'; import type { CallLog, ElementInfo, Mode, Source } from '@recorder/recorderTypes'; @@ -165,6 +165,10 @@ export class RecorderApp { if (process.env.PW_CODEGEN_NO_INSPECTOR) return; const recorder = await Recorder.forContext(context, params); + if (params.recorderMode === 'api') { + await ProgrammaticRecorderApp.run(context, recorder); + return; + } await RecorderApp._show(recorder, context, params); } @@ -359,4 +363,20 @@ export class RecorderApp { } } +export class ProgrammaticRecorderApp { + static async run(inspectedContext: BrowserContext, recorder: Recorder) { + let lastAction: actions.ActionInContext | null = null; + recorder.on(RecorderEvent.ActionAdded, action => { + if (!lastAction || !shouldMergeAction(action, lastAction)) + inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'actionAdded', data: action }); + else + inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'actionUpdated', data: action }); + lastAction = action; + }); + recorder.on(RecorderEvent.SignalAdded, signal => { + inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'signalAdded', data: signal }); + }); + } +} + const recorderAppSymbol = Symbol('recorderApp'); diff --git a/packages/playwright-core/src/server/recorder/recorderUtils.ts b/packages/playwright-core/src/server/recorder/recorderUtils.ts index 7694cb5208210..61e4e31d5da24 100644 --- a/packages/playwright-core/src/server/recorder/recorderUtils.ts +++ b/packages/playwright-core/src/server/recorder/recorderUtils.ts @@ -73,13 +73,25 @@ export async function frameForAction(pageAliases: Map, actionInCon return result.frame; } +function isSameAction(a: actions.ActionInContext, b: actions.ActionInContext): boolean { + return a.action.name === b.action.name && a.frame.pageAlias === b.frame.pageAlias && a.frame.framePath.join('|') === b.frame.framePath.join('|'); +} + +function isSameSelector(action: actions.ActionInContext, lastAction: actions.ActionInContext): boolean { + return 'selector' in action.action && 'selector' in lastAction.action && action.action.selector === lastAction.action.selector; +} + +export function shouldMergeAction(action: actions.ActionInContext, lastAction: actions.ActionInContext | undefined): boolean { + if (!lastAction) + return false; + return isSameAction(action, lastAction) && (action.action.name === 'navigate' || (action.action.name === 'fill' && isSameSelector(action, lastAction))); +} + export function collapseActions(actions: actions.ActionInContext[]): actions.ActionInContext[] { const result: actions.ActionInContext[] = []; for (const action of actions) { const lastAction = result[result.length - 1]; - const isSameAction = lastAction && lastAction.action.name === action.action.name && lastAction.frame.pageAlias === action.frame.pageAlias && lastAction.frame.framePath.join('|') === action.frame.framePath.join('|'); - const isSameSelector = lastAction && 'selector' in lastAction.action && 'selector' in action.action && action.action.selector === lastAction.action.selector; - const shouldMerge = isSameAction && (action.action.name === 'navigate' || (action.action.name === 'fill' && isSameSelector)); + const shouldMerge = shouldMergeAction(action, lastAction); if (!shouldMerge) { result.push(action); continue; diff --git a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts index 6739497d0954b..6874c1dc9981c 100644 --- a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts +++ b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts @@ -88,6 +88,7 @@ export const methodMetainfo = new Map): OpenAI.Chat.Completions.ChatCompl }, }; } + +export async function runOneShot(prompt: string): Promise { + const openai = new OpenAI(); + const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ + { + role: 'user', + content: prompt + } + ]; + const response = await openai.chat.completions.create({ + model, + messages, + }); + const message = response.choices[0].message; + if (!message.content) + throw new Error('Unexpected response from LLM: ' + message.content); + return message.content; +} diff --git a/packages/playwright-mdd/src/program.ts b/packages/playwright-mdd/src/program.ts index 396103dc5a56f..e5931bb23c8a5 100644 --- a/packages/playwright-mdd/src/program.ts +++ b/packages/playwright-mdd/src/program.ts @@ -20,6 +20,7 @@ import dotenv from 'dotenv'; import { program } from 'commander'; import { Context } from './codegen/context'; +import { runRecorderLoop } from './recorderLoop'; /* eslint-disable no-console */ @@ -28,10 +29,9 @@ dotenv.config(); const packageJSON = require('../package.json'); program + .command('run ').description('Run a test') .version('Version ' + packageJSON.version) - .argument('', 'The test spec to generate code for') .option('-o, --output ', 'The path to save the generated code') - .name(packageJSON.name) .action(async (spec, options) => { const content = await fs.promises.readFile(spec, 'utf8'); const codegenContext = new Context(); @@ -42,4 +42,11 @@ program console.log(code); }); +program + .command('record').description('Record a test') + .version('Version ' + packageJSON.version) + .action(async () => { + await runRecorderLoop(); + }); + export { program }; diff --git a/packages/playwright-mdd/src/recorderLoop.ts b/packages/playwright-mdd/src/recorderLoop.ts new file mode 100644 index 0000000000000..7985626aff081 --- /dev/null +++ b/packages/playwright-mdd/src/recorderLoop.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable no-console */ + +import { chromium } from 'playwright-core'; + +import { runOneShot } from './loop'; + +import type { BrowserContext } from '../../playwright-core/src/client/browserContext'; +import type * as actions from '@recorder/actions'; + +export async function runRecorderLoop() { + const browser = await chromium.launch({ headless: false }); + const context = await browser.newContext() as BrowserContext; + await context._enableRecorder({ + mode: 'recording', + recorderMode: 'api', + }, async (event, data) => { + if (event !== 'actionAdded') + return; + const action = data.action as actions.Action; + if (action.name !== 'click' && action.name !== 'press') { + console.log('============= action', action.name); + return; + } + const response = await runOneShot(prompt(action)).catch(e => { + console.error(e); + }); + console.log(response); + }); + const page = await context.newPage(); + await page.goto('https://playwright.dev/'); +} + +const prompt = (action: actions.ClickAction | actions.PressAction) => [ + `- User performed an action on a page.`, + `- Please describe the action in a single phrase.`, + `- You'll be asked to perform the action again, so make sure to describe the action in a way that is easy to understand and perform.`, + `- Action: "${action.name}"`, + `- Element: [${action.selector}]`, + `- Snapshot:`, + action.ariaSnapshot, +].join('\n'); diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 7b8e8a90edc06..f8ae674528a46 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -1618,6 +1618,7 @@ export interface BrowserContextEventTarget { on(event: 'requestFailed', callback: (params: BrowserContextRequestFailedEvent) => void): this; on(event: 'requestFinished', callback: (params: BrowserContextRequestFinishedEvent) => void): this; on(event: 'response', callback: (params: BrowserContextResponseEvent) => void): this; + on(event: 'recorderEvent', callback: (params: BrowserContextRecorderEventEvent) => void): this; } export interface BrowserContextChannel extends BrowserContextEventTarget, EventTargetChannel { _type_BrowserContext: boolean; @@ -1641,6 +1642,7 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT storageState(params: BrowserContextStorageStateParams, progress?: Progress): Promise; pause(params?: BrowserContextPauseParams, progress?: Progress): Promise; enableRecorder(params: BrowserContextEnableRecorderParams, progress?: Progress): Promise; + disableRecorder(params?: BrowserContextDisableRecorderParams, progress?: Progress): Promise; newCDPSession(params: BrowserContextNewCDPSessionParams, progress?: Progress): Promise; harStart(params: BrowserContextHarStartParams, progress?: Progress): Promise; harExport(params: BrowserContextHarExportParams, progress?: Progress): Promise; @@ -1714,6 +1716,10 @@ export type BrowserContextResponseEvent = { response: ResponseChannel, page?: PageChannel, }; +export type BrowserContextRecorderEventEvent = { + event: string, + data: any, +}; export type BrowserContextAddCookiesParams = { cookies: SetNetworkCookie[], }; @@ -1887,7 +1893,7 @@ export type BrowserContextPauseResult = void; export type BrowserContextEnableRecorderParams = { language?: string, mode?: 'inspecting' | 'recording', - recorderMode?: 'record' | 'perform', + recorderMode?: 'default' | 'api', pauseOnNextStatement?: boolean, testIdAttributeName?: string, launchOptions?: any, @@ -1901,7 +1907,7 @@ export type BrowserContextEnableRecorderParams = { export type BrowserContextEnableRecorderOptions = { language?: string, mode?: 'inspecting' | 'recording', - recorderMode?: 'record' | 'perform', + recorderMode?: 'default' | 'api', pauseOnNextStatement?: boolean, testIdAttributeName?: string, launchOptions?: any, @@ -1913,6 +1919,9 @@ export type BrowserContextEnableRecorderOptions = { omitCallTracking?: boolean, }; export type BrowserContextEnableRecorderResult = void; +export type BrowserContextDisableRecorderParams = {}; +export type BrowserContextDisableRecorderOptions = {}; +export type BrowserContextDisableRecorderResult = void; export type BrowserContextNewCDPSessionParams = { page?: PageChannel, frame?: FrameChannel, @@ -2039,6 +2048,7 @@ export interface BrowserContextEvents { 'requestFailed': BrowserContextRequestFailedEvent; 'requestFinished': BrowserContextRequestFinishedEvent; 'response': BrowserContextResponseEvent; + 'recorderEvent': BrowserContextRecorderEventEvent; } // ----------- Page ----------- diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index fe889d8407ad1..3b34797d31e97 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1365,8 +1365,8 @@ BrowserContext: recorderMode: type: enum? literals: - - record - - perform + - default + - api pauseOnNextStatement: boolean? testIdAttributeName: string? launchOptions: json? @@ -1377,6 +1377,9 @@ BrowserContext: handleSIGINT: boolean? omitCallTracking: boolean? + disableRecorder: + internal: true + newCDPSession: internal: true parameters: @@ -1540,6 +1543,11 @@ BrowserContext: response: Response page: Page? + recorderEvent: + parameters: + event: string + data: json + Page: type: interface diff --git a/packages/recorder/src/actions.d.ts b/packages/recorder/src/actions.d.ts index d4c74b26562ab..a751dbf40e777 100644 --- a/packages/recorder/src/actions.d.ts +++ b/packages/recorder/src/actions.d.ts @@ -36,6 +36,7 @@ export type ActionName = export type ActionBase = { name: ActionName, signals: Signal[], + ariaSnapshot?: string, }; export type ActionWithSelector = ActionBase & { @@ -116,7 +117,7 @@ export type AssertVisibleAction = ActionWithSelector & { export type AssertSnapshotAction = ActionWithSelector & { name: 'assertSnapshot', - snapshot: string, + ariaSnapshot: string, }; export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction | AssertSnapshotAction; diff --git a/packages/recorder/src/recorderTypes.d.ts b/packages/recorder/src/recorderTypes.d.ts index e098ec11e0fdd..9d46447c179d1 100644 --- a/packages/recorder/src/recorderTypes.d.ts +++ b/packages/recorder/src/recorderTypes.d.ts @@ -53,7 +53,6 @@ export type OverlayState = { export type UIState = { mode: Mode; - recorderMode: 'record' | 'perform'; actionPoint?: Point; actionSelector?: string; ariaTemplate?: AriaTemplateNode; diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index c10875241c366..bfef4840d6739 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -254,7 +254,6 @@ export const InspectModeController: React.FunctionComponent<{ const ariaTemplate = parsedSnapshot?.errors.length === 0 ? parsedSnapshot.fragment : undefined; recorder.setUIState({ mode: isInspecting ? 'inspecting' : 'none', - recorderMode: 'perform', actionSelector, ariaTemplate, language: sdkLanguage, diff --git a/tests/library/inspector/cli-codegen-1.spec.ts b/tests/library/inspector/cli-codegen-1.spec.ts index a3b7a8ec867c0..92d65851a8f0b 100644 --- a/tests/library/inspector/cli-codegen-1.spec.ts +++ b/tests/library/inspector/cli-codegen-1.spec.ts @@ -14,13 +14,10 @@ * limitations under the License. */ -import { test, expect, matrixDescribe } from './inspectorTest'; +import { test, expect } from './inspectorTest'; import type { ConsoleMessage } from 'playwright'; -matrixDescribe<('record' | 'perform')>('cli codegen', ['record', 'perform'], recorderMode => { - test.skip(({ mode }) => mode !== 'default'); - test.use({ recorderMode }); - +test.describe('cli codegen', () => { test('should click', async ({ openRecorder }) => { const { page, recorder } = await openRecorder(); @@ -761,7 +758,6 @@ await page.Locator(\"#age\").SelectOptionAsync(new[] { \"2\" });`); test('should await popup', async ({ openRecorder, server }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36461' }); - test.skip(recorderMode === 'record', 'Navigation is dispatched concurrently (before click is recorded)'); const { page, recorder } = await openRecorder(); server.setRoute('/popup', (req, res) => { @@ -821,8 +817,6 @@ await page1.GetByRole(AriaRole.Button, new() { Name = \"Click me\" }).ClickAsync }); test('should attribute navigation to click', async ({ openRecorder }) => { - test.skip(recorderMode === 'record', 'Navigation is dispatched concurrently (before click is recorded)'); - const { page, recorder } = await openRecorder(); await recorder.setContentAndWait(`link`); @@ -878,8 +872,6 @@ await page.GetByText("link").ClickAsync();`); }); test('should attribute navigation to press/fill', async ({ openRecorder }) => { - test.skip(recorderMode === 'record', 'Navigation is dispatched concurrently (before press/fill is recorded)'); - const { page, recorder } = await openRecorder(); await recorder.setContentAndWait(``); diff --git a/tests/library/inspector/cli-codegen-2.spec.ts b/tests/library/inspector/cli-codegen-2.spec.ts index 50991805ceda1..313acc6b18533 100644 --- a/tests/library/inspector/cli-codegen-2.spec.ts +++ b/tests/library/inspector/cli-codegen-2.spec.ts @@ -14,13 +14,12 @@ * limitations under the License. */ -import { test, expect, matrixDescribe } from './inspectorTest'; +import { test, expect } from './inspectorTest'; import * as url from 'url'; import fs from 'fs'; -matrixDescribe<('record' | 'perform')>('cli codegen', ['record', 'perform'], recorderMode => { +test.describe('cli codegen', () => { test.skip(({ mode }) => mode !== 'default'); - test.use({ recorderMode }); test('should contain open page', async ({ openRecorder }) => { const { recorder } = await openRecorder(); @@ -196,7 +195,6 @@ await page.GetByRole(AriaRole.Button, new() { Name = "Choose File" }).SetInputFi }); test('should download files', async ({ openRecorder, server }) => { - test.skip(recorderMode === 'record', 'Navigation is dispatched concurrently (before click is recorded)'); const { page, recorder } = await openRecorder(); server.setRoute('/download', (req, res) => { @@ -248,7 +246,6 @@ var download = await page.RunAndWaitForDownloadAsync(async () => }); test('should handle dialogs', async ({ openRecorder }) => { - test.skip(recorderMode === 'record', 'Navigation is dispatched concurrently (before click is recorded)'); const { page, recorder } = await openRecorder(); await recorder.setContentAndWait(` @@ -313,7 +310,6 @@ await page.GetByRole(AriaRole.Button, new() { Name = "click me" }).ClickAsync(); }); test('should record open in a new tab with url', async ({ openRecorder, browserName }) => { - test.skip(recorderMode === 'record', 'Navigation is dispatched concurrently (before click is recorded)'); const { page, recorder } = await openRecorder(); await recorder.setContentAndWait(`link`); diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts index a5abaa8f508a7..d71276b5dd035 100644 --- a/tests/library/inspector/cli-codegen-3.spec.ts +++ b/tests/library/inspector/cli-codegen-3.spec.ts @@ -16,12 +16,11 @@ import type { TestServer } from 'tests/config/testserver'; import type { Recorder } from './inspectorTest'; -import { test, expect, matrixDescribe } from './inspectorTest'; +import { test, expect } from './inspectorTest'; import type { Page } from '@playwright/test'; -matrixDescribe<('record' | 'perform')>('cli codegen', ['record', 'perform'], recorderMode => { +test.describe('cli codegen', () => { test.skip(({ mode }) => mode !== 'default'); - test.use({ recorderMode }); test('should click locator.first', async ({ openRecorder }) => { const { page, recorder } = await openRecorder(); @@ -658,8 +657,8 @@ await page.GetByRole(AriaRole.Textbox, new() { Name = \"Coun\\\"try\" }).ClickAs expect(message.text()).toBe('clicked'); expect(await page.evaluate('log')).toEqual([ 'pointermove', 'mousemove', - // There is no second mouse move in record mode - ...(recorderMode === 'record' ? [] : ['pointermove', 'mousemove']), + 'pointermove', + 'mousemove', 'pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click', @@ -667,8 +666,6 @@ await page.GetByRole(AriaRole.Textbox, new() { Name = \"Coun\\\"try\" }).ClickAs }); test('should consume contextmenu events, despite a custom context menu', async ({ openRecorder, browserName, platform }) => { - test.skip(recorderMode === 'record', 'It actually works in record mode, perform mode is broken see comments inline'); - const { page, recorder } = await openRecorder(); await recorder.setContentAndWait(` diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts index 3baf057603b9d..e7ed9a952653f 100644 --- a/tests/library/inspector/inspectorTest.ts +++ b/tests/library/inspector/inspectorTest.ts @@ -28,9 +28,8 @@ export { expect } from '@playwright/test'; type CLITestArgs = { recorderPageGetter: () => Promise; closeRecorder: () => Promise; - openRecorder: (options?: { testIdAttributeName: string, recorderMode?: 'record' | 'perform' }) => Promise<{ recorder: Recorder, page: Page }>; + openRecorder: (options?: { testIdAttributeName: string }) => Promise<{ recorder: Recorder, page: Page }>; runCLI: (args: string[], options?: { autoExitWhen?: string }) => CLIMock; - recorderMode: 'record' | 'perform'; }; const codegenLang2Id: Map = new Map([ @@ -85,20 +84,17 @@ export const test = contextTest.extend({ }); }, - openRecorder: async ({ context, recorderPageGetter, recorderMode }, use) => { + openRecorder: async ({ context, recorderPageGetter }, use) => { await use(async options => { await (context as any)._enableRecorder({ language: 'javascript', mode: 'recording', - recorderMode, ...options }); const page = await context.newPage(); return { page, recorder: new Recorder(page, await recorderPageGetter()) }; }); }, - - recorderMode: 'perform', }); export class Recorder { @@ -285,11 +281,3 @@ class CLIMock { return stripAnsi(this.process.output); } } - -export function matrixDescribe(name: string, matrix: T[], fn: (mode: T) => void) { - for (const mode of matrix) { - test.describe(`${name} ${mode}`, () => { - fn(mode as T); - }); - } -} From 677ebc0c756a0c11aa4b44b431734ab2d6378608 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:19:32 +0200 Subject: [PATCH 194/222] feat(webkit): roll to r2191 (#36602) Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 4ccf665f818c3..5a8764b04970d 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2190", + "revision": "2191", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From 2cbe965880ba228b4ccdd5f3dc0eaf0a4e5005c2 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 8 Jul 2025 16:15:34 -0700 Subject: [PATCH 195/222] chore: group attachments in terminal reporters (#36607) --- packages/html-reporter/src/testErrorView.tsx | 8 +- packages/html-reporter/src/testResultView.tsx | 38 +++------ .../src/matchers/toMatchSnapshot.ts | 10 ++- packages/playwright/src/reporters/base.ts | 85 ++++++++++++++++--- tests/page/expect-matcher-result.spec.ts | 3 +- tests/playwright-test/golden.spec.ts | 6 +- tests/playwright-test/reporter-html.spec.ts | 46 +--------- .../to-have-screenshot.spec.ts | 2 + 8 files changed, 101 insertions(+), 97 deletions(-) diff --git a/packages/html-reporter/src/testErrorView.tsx b/packages/html-reporter/src/testErrorView.tsx index 7639e3577bd82..6fa7bc50ecb17 100644 --- a/packages/html-reporter/src/testErrorView.tsx +++ b/packages/html-reporter/src/testErrorView.tsx @@ -47,16 +47,10 @@ export const PromptButton: React.FC<{ prompt: string }> = ({ prompt }) => { }; export const TestScreenshotErrorView: React.FC<{ - errorPrefix?: string, diff: ImageDiff, - errorSuffix?: string, -}> = ({ errorPrefix, diff, errorSuffix }) => { - const prefixHtml = React.useMemo(() => ansiErrorToHtml(errorPrefix), [errorPrefix]); - const suffixHtml = React.useMemo(() => ansiErrorToHtml(errorSuffix), [errorSuffix]); +}> = ({ diff }) => { return
-
-
; }; diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index b023c27981595..c8f8f0402940d 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -86,7 +86,7 @@ export const TestResultView: React.FC<{ [...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a)); const otherAttachmentAnchors = [...otherAttachments].map(a => `attachment-${attachments.indexOf(a)}`); const diffs = groupImageDiffs(screenshots, result); - const errors = classifyErrors(result.errors.map(e => e.message), diffs); + const errors = result.errors.map(e => e.message); return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors, errorContext }; }, [result]); @@ -111,9 +111,11 @@ export const TestResultView: React.FC<{ )} {errors.map((error, index) => { - if (error.type === 'screenshot') - return ; - return ; + const diff = pickDiffForError(error, diffs); + return <> + + {diff && } + ; })} } {!!result.steps.length && @@ -167,29 +169,11 @@ export const TestResultView: React.FC<{ ; }; -function classifyErrors(testErrors: string[], diffs: ImageDiff[]) { - return testErrors.map(error => { - const firstLine = error.split('\n')[0]; - if (firstLine.includes('toHaveScreenshot') || firstLine.includes('toMatchSnapshot')) { - const matchingDiff = diffs.find(diff => { - const attachmentName = diff.actual?.attachment.name; - return attachmentName && error.includes(attachmentName); - }); - - if (matchingDiff) { - const lines = error.split('\n'); - const index = lines.findIndex(line => /Expected:|Previous:|Received:/.test(line)); - const errorPrefix = index !== -1 ? lines.slice(0, index).join('\n') : lines[0]; - - const diffIndex = lines.findIndex(line => / +Diff:/.test(line)); - const errorSuffix = diffIndex !== -1 ? lines.slice(diffIndex + 2).join('\n') : lines.slice(1).join('\n'); - - return { type: 'screenshot', diff: matchingDiff, errorPrefix, errorSuffix }; - } - } - - return { type: 'regular', error }; - }); +function pickDiffForError(error: string, diffs: ImageDiff[]): ImageDiff | undefined { + const firstLine = error.split('\n')[0]; + if (!firstLine.includes('toHaveScreenshot') && !firstLine.includes('toMatchSnapshot')) + return undefined; + return diffs.find(diff => error.includes(diff.name)); } const StepTreeItem: React.FC<{ diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index b2ef3c332326e..ac3e5344b353f 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -68,6 +68,7 @@ const NonConfigProperties: (keyof ToHaveScreenshotOptions)[] = [ class SnapshotHelper { readonly testInfo: TestInfoImpl; + readonly name: string; readonly attachmentBaseName: string; readonly legacyExpectedPath: string; readonly previousPath: string; @@ -101,6 +102,7 @@ class SnapshotHelper { name = nameFromOptions; } + this.name = Array.isArray(name) ? name.join(path.sep) : name || ''; const resolvedPaths = testInfo._resolveSnapshotPaths(matcherName === 'toHaveScreenshot' ? 'screenshot' : 'snapshot', name, 'updateSnapshotIndex', anonymousSnapshotExtension); this.expectedPath = resolvedPaths.absoluteSnapshotPath; this.attachmentBaseName = resolvedPaths.relativeOutputPath; @@ -208,27 +210,27 @@ class SnapshotHelper { log: string[] | undefined, step: TestStepInfoImpl | undefined): ImageMatcherResult { const output = [`${header}${indent(diffError, ' ')}`]; + if (this.name) { + output.push(''); + output.push(` Snapshot: ${this.name}`); + } if (expected !== undefined) { // Copy the expectation inside the `test-results/` folder for backwards compatibility, // so that one can upload `test-results/` directory and have all the data inside. writeFileSync(this.legacyExpectedPath, expected); step?._attachToStep({ name: addSuffixToFilePath(this.attachmentBaseName, '-expected'), contentType: this.mimeType, path: this.expectedPath }); - output.push(`\nExpected: ${colors.yellow(this.expectedPath)}`); } if (previous !== undefined) { writeFileSync(this.previousPath, previous); step?._attachToStep({ name: addSuffixToFilePath(this.attachmentBaseName, '-previous'), contentType: this.mimeType, path: this.previousPath }); - output.push(`Previous: ${colors.yellow(this.previousPath)}`); } if (actual !== undefined) { writeFileSync(this.actualPath, actual); step?._attachToStep({ name: addSuffixToFilePath(this.attachmentBaseName, '-actual'), contentType: this.mimeType, path: this.actualPath }); - output.push(`Received: ${colors.yellow(this.actualPath)}`); } if (diff !== undefined) { writeFileSync(this.diffPath, diff); step?._attachToStep({ name: addSuffixToFilePath(this.attachmentBaseName, '-diff'), contentType: this.mimeType, path: this.diffPath }); - output.push(` Diff: ${colors.yellow(this.diffPath)}`); } if (log?.length) diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index ee8640957fd61..5a3e033ef876d 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -354,29 +354,49 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`))); } resultLines.push(...errors.map(error => '\n' + error.message)); - for (let i = 0; i < result.attachments.length; ++i) { - const attachment = result.attachments[i]; + const attachmentGroups = groupAttachments(result.attachments); + for (let i = 0; i < attachmentGroups.length; ++i) { + const attachment = attachmentGroups[i]; if (attachment.name === 'error-context' && attachment.path) { resultLines.push(''); resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`)); continue; } + if (attachment.name.startsWith('_')) continue; + const hasPrintableContent = attachment.contentType.startsWith('text/'); if (!attachment.path && !hasPrintableContent) continue; + resultLines.push(''); - resultLines.push(screen.colors.cyan(separator(screen, ` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`))); - if (attachment.path) { - const relativePath = path.relative(process.cwd(), attachment.path); - resultLines.push(screen.colors.cyan(` ${relativePath}`)); + resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`))); + + if (attachment.actual?.path) { + if (attachment.expected?.path) { + const expectedPath = relativeFilePath(screen, config, attachment.expected.path); + resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`)); + } + const actualPath = relativeFilePath(screen, config, attachment.actual.path); + resultLines.push(screen.colors.dim(` Received: ${actualPath}`)); + if (attachment.previous?.path) { + const previousPath = relativeFilePath(screen, config, attachment.previous.path); + resultLines.push(screen.colors.dim(` Previous: ${previousPath}`)); + } + if (attachment.diff?.path) { + const diffPath = relativeFilePath(screen, config, attachment.diff.path); + resultLines.push(screen.colors.dim(` Diff: ${diffPath}`)); + } + } else if (attachment.path) { + const relativePath = relativeFilePath(screen, config, attachment.path); + resultLines.push(screen.colors.dim(` ${relativePath}`)); // Make this extensible if (attachment.name === 'trace') { const packageManagerCommand = getPackageManagerExecCommand(); - resultLines.push(screen.colors.cyan(` Usage:`)); + resultLines.push(screen.colors.dim(` Usage:`)); resultLines.push(''); - resultLines.push(screen.colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`)); + resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`)); resultLines.push(''); } } else { @@ -385,10 +405,10 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase if (text.length > 300) text = text.slice(0, 300) + '...'; for (const line of text.split('\n')) - resultLines.push(screen.colors.cyan(` ${line}`)); + resultLines.push(screen.colors.dim(` ${line}`)); } } - resultLines.push(screen.colors.cyan(separator(screen, ' '))); + resultLines.push(screen.colors.dim(separator(screen, ' '))); } lines.push(...resultLines); } @@ -532,7 +552,7 @@ export function separator(screen: Screen, text: string = ''): string { if (text) text += ' '; const columns = Math.min(100, screen.ttyWidth || 100); - return text + screen.colors.dim('─'.repeat(Math.max(0, columns - text.length))); + return text + screen.colors.dim('─'.repeat(Math.max(0, columns - stripAnsiEscapes(text).length))); } function indent(lines: string, tab: string) { @@ -640,3 +660,46 @@ export function resolveOutputFile(reporterName: string, options: { return { outputFile, outputDir }; } + +type TestAttachment = TestResult['attachments'][number]; + +type TestAttachmentGroup = TestAttachment & { + expected?: TestAttachment; + actual?: TestAttachment; + diff?: TestAttachment; + previous?: TestAttachment; +}; + +function groupAttachments(attachments: TestResult['attachments']): TestAttachmentGroup[] { + const result: TestAttachmentGroup[] = []; + const attachmentsByPrefix = new Map(); + for (const attachment of attachments) { + if (!attachment.path) { + result.push(attachment); + continue; + } + + const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/); + if (!match) { + result.push(attachment); + continue; + } + + const [, name, category] = match; + let group: TestAttachmentGroup | undefined = attachmentsByPrefix.get(name); + if (!group) { + group = { ...attachment, name }; + attachmentsByPrefix.set(name, group); + result.push(group); + } + if (category === 'expected') + group.expected = attachment; + else if (category === 'actual') + group.actual = attachment; + else if (category === 'diff') + group.diff = attachment; + else if (category === 'previous') + group.previous = attachment; + } + return result; +} diff --git a/tests/page/expect-matcher-result.spec.ts b/tests/page/expect-matcher-result.spec.ts index 676a29fc67167..81feca0b48a32 100644 --- a/tests/page/expect-matcher-result.spec.ts +++ b/tests/page/expect-matcher-result.spec.ts @@ -298,6 +298,5 @@ test('toHaveScreenshot should populate matcherResult', async ({ page, server, is expect.soft(stripAnsi(e.toString())).toContain(`Error: expect(page).toHaveScreenshot(expected) 23362 pixels (ratio 0.10 of all image pixels) are different. - -Expected:`); +`); }); diff --git a/tests/playwright-test/golden.spec.ts b/tests/playwright-test/golden.spec.ts index da6c2a8022043..7d59e7c3c04b0 100644 --- a/tests/playwright-test/golden.spec.ts +++ b/tests/playwright-test/golden.spec.ts @@ -228,7 +228,7 @@ test('should write detailed failure result to an output folder', async ({ runInl const expectedSnapshotArtifactPath = testInfo.outputPath('test-results', 'a-is-a-test', 'snapshot-expected.txt'); const actualSnapshotArtifactPath = testInfo.outputPath('test-results', 'a-is-a-test', 'snapshot-actual.txt'); expect(outputText).toMatch(/Expected:.*a\.spec\.js-snapshots.snapshot\.txt/); - expect(outputText).toContain(`Received: ${actualSnapshotArtifactPath}`); + expect(outputText).toContain(`Received: test-results${path.sep}a-is-a-test${path.sep}snapshot-actual.txt`); expect(fs.existsSync(expectedSnapshotArtifactPath)).toBe(true); expect(fs.existsSync(actualSnapshotArtifactPath)).toBe(true); }); @@ -672,8 +672,8 @@ test('should compare different PNG images', async ({ runInlineTest }, testInfo) const actualSnapshotArtifactPath = testInfo.outputPath('test-results', 'a-is-a-test', 'snapshot-actual.png'); const diffSnapshotArtifactPath = testInfo.outputPath('test-results', 'a-is-a-test', 'snapshot-diff.png'); expect(outputText).toMatch(/Expected:.*a\.spec\.js-snapshots.snapshot\.png/); - expect(outputText).toContain(`Received: ${actualSnapshotArtifactPath}`); - expect(outputText).toContain(`Diff: ${diffSnapshotArtifactPath}`); + expect(outputText).toContain(`Received: test-results${path.sep}a-is-a-test${path.sep}snapshot-actual.png`); + expect(outputText).toContain(`Diff: test-results${path.sep}a-is-a-test${path.sep}snapshot-diff.png`); expect(fs.existsSync(expectedSnapshotArtifactPath)).toBe(true); expect(fs.existsSync(actualSnapshotArtifactPath)).toBe(true); expect(fs.existsSync(diffSnapshotArtifactPath)).toBe(true); diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 96c7d916ae831..e4194be748f07 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -289,11 +289,9 @@ for (const useIntermediateMergeReport of [true, false] as const) { await showReport(); await page.getByRole('link', { name: 'fails' }).click(); - await expect(page.getByTestId('test-screenshot-error-view').getByTestId('error-suffix')).toContainText([ - `> 6 | await expect.soft(screenshot).toMatchSnapshot('expected.png');`, - `> 7 | await expect.soft(screenshot).toMatchSnapshot('expected.png');`, - `> 8 | await expect.soft(screenshot).toMatchSnapshot('expected.png');`, - ]); + await expect(page.locator('.test-error-view').first()).toContainText( + `> 6 | await expect.soft(screenshot).toMatchSnapshot('expected.png');`, + ); const imageDiffs = page.getByTestId('test-results-image-diff'); await expect(imageDiffs.getByTestId('test-result-image-mismatch')).toHaveCount(3); await expect(imageDiffs.getByText('Image mismatch:')).toHaveText([ @@ -303,44 +301,6 @@ for (const useIntermediateMergeReport of [true, false] as const) { ]); }); - test('should include image diff when screenshot failed to generate due to animation', async ({ runInlineTest, page, showReport }) => { - test.skip(process.env.PW_CLOCK === 'frozen', 'Assumes Date.now() changes'); - const result = await runInlineTest({ - 'playwright.config.ts': ` - module.exports = { use: { viewport: { width: 200, height: 200 }} }; - `, - 'a.test.js': ` - import { test, expect } from '@playwright/test'; - test('fails', async ({ page }, testInfo) => { - testInfo.snapshotSuffix = ''; - await page.evaluate(() => { - setInterval(() => { - document.body.textContent = Date.now(); - }, 50); - }); - await expect.soft(page).toHaveScreenshot({ timeout: 1000 }); - }); - `, - }, { 'reporter': 'dot,html', 'update-snapshots': true }, { PLAYWRIGHT_HTML_OPEN: 'never' }); - expect(result.exitCode).toBe(1); - expect(result.failed).toBe(1); - - await showReport(); - await page.getByRole('link', { name: 'fails' }).click(); - await expect(page.locator('text=Image mismatch')).toHaveCount(1); - await expect(page.locator('text=Snapshot mismatch')).toHaveCount(0); - await expect(page.locator('.chip-header', { hasText: 'Screenshots' })).toHaveCount(0); - const errorChip = page.getByTestId('test-screenshot-error-view'); - await expect(errorChip).toContainText('Failed to take two consecutive stable screenshots.'); - await expect(errorChip.getByTestId('test-result-image-mismatch-tabs').locator('div')).toHaveText([ - 'Diff', - 'Actual', - 'Previous', - 'Side by side', - 'Slider', - ]); - }); - test('should not include image diff with non-images', async ({ runInlineTest, page, showReport }) => { const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64'); const result = await runInlineTest({ diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index 330a7991d995a..df46e9c2bc5e2 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -157,6 +157,8 @@ test.describe('expect config animations option', () => { ` }, { 'update-snapshots': true }); expect(result.exitCode).toBe(1); + expect(result.output).toContain('is-a-test-1-actual.png'); + expect(result.output).toContain('is-a-test-1-previous.png'); expect(result.output).toContain('is-a-test-1-diff.png'); }); }); From 82b3ffd891a9f856d2ec94d84e8bb706cd41e30b Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 9 Jul 2025 11:04:18 +0200 Subject: [PATCH 196/222] test: unflake Chromium tracing tests (#36612) --- tests/library/chromium/tracing.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/library/chromium/tracing.spec.ts b/tests/library/chromium/tracing.spec.ts index 3baac85902a0b..f61072fa32a08 100644 --- a/tests/library/chromium/tracing.spec.ts +++ b/tests/library/chromium/tracing.spec.ts @@ -17,6 +17,7 @@ import { browserTest as it, expect } from '../../config/browserTest'; import fs from 'fs'; import path from 'path'; +import { rafraf } from '../../page/pageTest'; it('should output a trace', async ({ browser, server }, testInfo) => { let warning = null; @@ -50,7 +51,7 @@ it('should run with custom categories if provided', async ({ browser }, testInfo const page = await browser.newPage(); const outputTraceFile = testInfo.outputPath(path.join(`trace.json`)); await browser.startTracing(page, { path: outputTraceFile, categories: ['disabled-by-default-cc.debug'] }); - await page.evaluate(() => 1 + 1); + await rafraf(page); await browser.stopTracing(); const traceJson = JSON.parse(fs.readFileSync(outputTraceFile).toString()); From 6af944e59d70ce8a371f380af9b8f73aafc496ae Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 9 Jul 2025 13:26:38 +0200 Subject: [PATCH 197/222] chore: unflake tracing screencast frames (#36613) --- tests/library/tracing.spec.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/library/tracing.spec.ts b/tests/library/tracing.spec.ts index e1aff51bc24ad..ba06509c0456b 100644 --- a/tests/library/tracing.spec.ts +++ b/tests/library/tracing.spec.ts @@ -22,6 +22,7 @@ import { parseTraceRaw } from '../config/utils'; import type { StackFrame } from '@protocol/channels'; import type { ActionTraceEvent } from '../../packages/trace/src/trace'; import { artifactsFolderName } from '../../packages/playwright/src/isomorphic/folders'; +import { rafraf } from '../page/pageTest'; test.skip(({ trace }) => trace === 'on'); @@ -442,10 +443,13 @@ for (const params of [ await context.tracing.start({ screenshots: true, snapshots: true }); const page = await context.newPage(); // Make sure we have a chance to paint. - for (let i = 0; i < 10; ++i) { - await page.setContent(''); - await page.evaluate(() => new Promise(window.builtins.requestAnimationFrame)); + for (let i = 0; i < 100; ++i) { + const percentage = (i + 1) + '%'; + await page.setContent(``); + await rafraf(page); } + for (let i = 0; i < 10; ++i) + await rafraf(page); await context.tracing.stop({ path: testInfo.outputPath('trace.zip') }); const { events, resources } = await parseTraceRaw(testInfo.outputPath('trace.zip')); From cd33649da0d9bb4e43ff3eb5fa34b3c924fa676c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 9 Jul 2025 13:31:20 +0200 Subject: [PATCH 198/222] chore: remove --use-angle in Chromium switches (#36614) Signed-off-by: Max Schmitt --- packages/playwright-core/src/server/chromium/chromium.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index f2dfb7b2aba96..08149751b3b68 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -303,9 +303,6 @@ export class Chromium extends BrowserType { chromeArguments.push('--enable-use-zoom-for-dsf=false'); // See https://issues.chromium.org/issues/40277080 chromeArguments.push('--enable-unsafe-swiftshader'); - // See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025. - if (options.headless && (!options.channel || options.channel === 'chromium-headless-shell')) - chromeArguments.push('--use-angle'); } if (options.devtools) From 145e158e52e4207573104ff8d83a8dcd720433ea Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 9 Jul 2025 13:33:51 +0100 Subject: [PATCH 199/222] test: fix debug-controller.spec (#36616) --- tests/library/debug-controller.spec.ts | 39 +++++++++++++++++--------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index d08b4e8048987..3fe102446d4e1 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -22,7 +22,7 @@ import type { Browser, BrowserContext } from '@playwright/test'; import type * as channels from '@protocol/channels'; import { roundBox } from '../page/pageTest'; -type BrowserWithReuse = Browser & { _newContextForReuse: () => Promise }; +type BrowserWithReuse = Browser & { newContextForReuse: () => Promise }; type Fixtures = { wsEndpoint: string; backend: channels.DebugControllerChannel; @@ -55,6 +55,14 @@ const test = baseTest.extend({ }, }) as BrowserWithReuse; browsers.push(browser); + + let context: BrowserContext | undefined; + browser.newContextForReuse = async () => { + if (context) + await (browser as any)._disconnectFromReusedContext('reusedContext'); + context = await (browser as any)._newContextForReuse(); + return context; + }; return browser; }); for (const browser of browsers) @@ -74,7 +82,7 @@ test('should pick element', async ({ backend, connectedBrowser }) => { await backend.setRecorderMode({ mode: 'inspecting' }); - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const [page] = context.pages(); await page.setContent(''); @@ -104,7 +112,7 @@ test('should report pages', async ({ backend, connectedBrowser }) => { backend.on('stateChanged', event => events.push(event)); await backend.setReportStateChanged({ enabled: true }); - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const page1 = await context.newPage(); const page2 = await context.newPage(); await page1.close(); @@ -128,7 +136,7 @@ test('should report pages', async ({ backend, connectedBrowser }) => { }); test('should navigate all', async ({ backend, connectedBrowser }) => { - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const page1 = await context.newPage(); const page2 = await context.newPage(); @@ -139,19 +147,24 @@ test('should navigate all', async ({ backend, connectedBrowser }) => { }); test('should reset for reuse', async ({ backend, connectedBrowser }) => { - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const page1 = await context.newPage(); const page2 = await context.newPage(); await backend.navigate({ url: 'data:text/plain,Hello world' }); - const context2 = await connectedBrowser._newContextForReuse(); + const context2 = await connectedBrowser.newContextForReuse(); + expect(context2.pages().length).toBe(1); + expect(context2.pages()[0]).not.toBe(page1); expect(await context2.pages()[0].evaluate(() => window.location.href)).toBe('about:blank'); + // Note: ideally, `page1` would be unaccessible, because it was disposed. + // However, we currently do not check that, and since it keeps the same guid, sending + // messages to the server keeps working. expect(await page1.evaluate(() => window.location.href)).toBe('about:blank'); expect(await page2.evaluate(() => window.location.href).catch(e => e.message)).toContain('Target page, context or browser has been closed'); }); test('should highlight all', async ({ backend, connectedBrowser }) => { - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const page1 = await context.newPage(); const page2 = await context.newPage(); await backend.navigate({ url: 'data:text/html,' }); @@ -169,7 +182,7 @@ test('should record', async ({ backend, connectedBrowser }) => { await backend.setRecorderMode({ mode: 'recording' }); - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const [page] = context.pages(); await page.setContent(''); @@ -206,7 +219,7 @@ test('should record custom data-testid', async ({ backend, connectedBrowser }) = backend.on('sourceChanged', event => events.push(event)); // 1. "Show browser" (or "run test"). - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const page = await context.newPage(); await page.setContent(`
One
`); @@ -235,7 +248,7 @@ test('test', async ({ page }) => { test('should reset routes before reuse', async ({ server, connectedBrowserFactory }) => { const browser1 = await connectedBrowserFactory(); - const context1 = await browser1._newContextForReuse(); + const context1 = await browser1.newContextForReuse(); await context1.route(server.PREFIX + '/title.html', route => route.fulfill({ body: 'Hello', contentType: 'text/html' })); const page1 = await context1.newPage(); await page1.route(server.PREFIX + '/consolelog.html', route => route.fulfill({ body: 'World', contentType: 'text/html' })); @@ -247,7 +260,7 @@ test('should reset routes before reuse', async ({ server, connectedBrowserFactor await browser1.close(); const browser2 = await connectedBrowserFactory(); - const context2 = await browser2._newContextForReuse(); + const context2 = await browser2.newContextForReuse(); const page2 = await context2.newPage(); await page2.goto(server.PREFIX + '/title.html'); @@ -260,7 +273,7 @@ test('should reset routes before reuse', async ({ server, connectedBrowserFactor test('should highlight inside iframe', async ({ backend, connectedBrowser }, testInfo) => { testInfo.annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33146' }); - const context = await connectedBrowser._newContextForReuse(); + const context = await connectedBrowser.newContextForReuse(); const page = await context.newPage(); await backend.navigate({ url: `data:text/html,
bar