diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 062d7c7e74..60fea176c8 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -24,6 +24,7 @@ body: ## Make a minimal reproduction To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the bug. The simpler you can make it, the more likely we are to successfully verify and fix the bug. You can create a new project with `npm init playwright@latest new-project` and then add the test code there. + Please make sure you only include the code and the dependencies absolutely necessary for your repro. Due to the security considerations, we can only run the code we trust. Major web frameworks are Ok to use, but smaller convenience libraries are not. - type: markdown attributes: value: | diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml new file mode 100644 index 0000000000..8224d24883 --- /dev/null +++ b/.github/workflows/tests_bidi.yml @@ -0,0 +1,46 @@ +name: tests BiDi + +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - .github/workflows/tests_bidi.yml + schedule: + # Run every day at midnight + - cron: '0 0 * * *' + +env: + FORCE_COLOR: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + +jobs: + test_bidi: + name: BiDi + environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} + runs-on: ubuntu-24.04 + permissions: + id-token: write # This is required for OIDC login (azure/login) to succeed + contents: read # This is required for actions/checkout to succeed + strategy: + fail-fast: false + matrix: + channel: [bidi-chromium, bidi-firefox-beta] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm ci + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' + - run: npm run build + - run: npx playwright install --with-deps chromium + if: matrix.channel == 'bidi-chromium' + - run: npx -y @puppeteer/browsers install firefox@beta + if: matrix.channel == 'bidi-firefox-beta' + - name: Run tests + run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run biditest -- --project=${{ matrix.channel }}* + env: + PWTEST_USE_BIDI_EXPECTATIONS: '1' diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index a6b473b72b..8d4bbd1c78 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -50,7 +50,9 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-12, macos-13, macos-14] + # Intel: macos-13, macos-14-large + # Arm64: macos-13-xlarge, macos-14 + os: [macos-13, macos-13-xlarge, macos-14-large, macos-14] browser: [chromium, firefox, webkit] runs-on: ${{ matrix.os }} steps: @@ -235,7 +237,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, macos-12, windows-latest] + os: [ubuntu-20.04, macos-13, windows-latest] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/run-test diff --git a/.github/workflows/tests_service.yml b/.github/workflows/tests_service.yml index 2739680712..2788b0cf08 100644 --- a/.github/workflows/tests_service.yml +++ b/.github/workflows/tests_service.yml @@ -53,7 +53,7 @@ jobs: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - name: Download blob report artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-blob-reports path: all-blob-reports diff --git a/.gitignore b/.gitignore index 69d85e4975..aadc481067 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ test-results /tests/installation/output/ /tests/installation/.registry.json .cache/ -.eslintcache \ No newline at end of file +.eslintcache +playwright.env diff --git a/README.md b/README.md index fde98ea6fe..d0d8e53dd0 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-128.0.6613.36-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-129.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.0-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-129.0.6668.42-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-130.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.0-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) @@ -8,9 +8,9 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 128.0.6613.36 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 129.0.6668.42 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Firefox 129.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 130.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/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000000..78cb929fb1 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,17 @@ +# Support + +## How to file issues and get help + +This project uses GitHub issues to track bugs and feature requests. Please search the [existing issues][gh-issues] before filing new ones to avoid duplicates. For new issues, file your bug or feature request as a new issue using corresponding template. + +For help and questions about using this project, please see the [docs site for Playwright][docs]. + +Join our community [Discord Server][discord-server] to connect with other developers using Playwright and ask questions in our 'help-playwright' forum. + +## Microsoft Support Policy + +Support for Playwright is limited to the resources listed above. + +[gh-issues]: https://github.com/microsoft/playwright/issues/ +[docs]: https://playwright.dev/ +[discord-server]: https://aka.ms/playwright/discord diff --git a/browser_patches/firefox/UPSTREAM_CONFIG.sh b/browser_patches/firefox/UPSTREAM_CONFIG.sh index aee7dca3d9..ff33363424 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="4c9a3f8e2db68ae0a8fcf6bbf0574e3c0549ff49" +BASE_REVISION="cf0397e3ba298868fdca53f894da5b0d239dc09e" diff --git a/browser_patches/firefox/juggler/NetworkObserver.js b/browser_patches/firefox/juggler/NetworkObserver.js index 8eb69d8133..aa6f866277 100644 --- a/browser_patches/firefox/juggler/NetworkObserver.js +++ b/browser_patches/firefox/juggler/NetworkObserver.js @@ -602,6 +602,8 @@ class NetworkObserver { proxyFilter.onProxyFilterResult(defaultProxyInfo); return; } + if (this._targetRegistry.shouldBustHTTPAuthCacheForProxy(proxy)) + Services.obs.notifyObservers(null, "net:clear-active-logins"); proxyFilter.onProxyFilterResult(protocolProxyService.newProxyInfo( proxy.type, proxy.host, diff --git a/browser_patches/firefox/juggler/TargetRegistry.js b/browser_patches/firefox/juggler/TargetRegistry.js index 8c23ff8c93..c40c831d01 100644 --- a/browser_patches/firefox/juggler/TargetRegistry.js +++ b/browser_patches/firefox/juggler/TargetRegistry.js @@ -116,6 +116,7 @@ class TargetRegistry { this._browserToTarget = new Map(); this._browserIdToTarget = new Map(); + this._proxiesWithClashingAuthCacheKeys = new Set(); this._browserProxy = null; // Cleanup containers from previous runs (if any) @@ -234,12 +235,50 @@ class TargetRegistry { onOpenWindow(win); } + // Firefox uses nsHttpAuthCache to cache authentication to the proxy. + // If we're provided with a single proxy with a multiple different authentications, then + // we should clear the nsHttpAuthCache on every request. + shouldBustHTTPAuthCacheForProxy(proxy) { + return this._proxiesWithClashingAuthCacheKeys.has(proxy); + } + + _updateProxiesWithSameAuthCacheAndDifferentCredentials() { + const proxyIdToCredentials = new Map(); + const allProxies = [...this._browserContextIdToBrowserContext.values()].map(bc => bc._proxy).filter(Boolean); + if (this._browserProxy) + allProxies.push(this._browserProxy); + const proxyAuthCacheKeyAndProxy = allProxies.map(proxy => [ + JSON.stringify({ + type: proxy.type, + host: proxy.host, + port: proxy.port, + }), + proxy, + ]); + this._proxiesWithClashingAuthCacheKeys.clear(); + + proxyAuthCacheKeyAndProxy.sort(([cacheKey1], [cacheKey2]) => cacheKey1 < cacheKey2 ? -1 : 1); + for (let i = 0; i < proxyAuthCacheKeyAndProxy.length - 1; ++i) { + const [cacheKey1, proxy1] = proxyAuthCacheKeyAndProxy[i]; + const [cacheKey2, proxy2] = proxyAuthCacheKeyAndProxy[i + 1]; + if (cacheKey1 !== cacheKey2) + continue; + if (proxy1.username === proxy2.username && proxy1.password === proxy2.password) + continue; + // `proxy1` and `proxy2` have the same caching key, but serve different credentials. + // We have to bust HTTP Auth Cache everytime there's a request that will use either of the proxies. + this._proxiesWithClashingAuthCacheKeys.add(proxy1); + this._proxiesWithClashingAuthCacheKeys.add(proxy2); + } + } + async cancelDownload(options) { this._downloadInterceptor.cancelDownload(options.uuid); } setBrowserProxy(proxy) { this._browserProxy = proxy; + this._updateProxiesWithSameAuthCacheAndDifferentCredentials(); } getProxyInfo(channel) { @@ -906,12 +945,14 @@ class BrowserContext { } this._registry._browserContextIdToBrowserContext.delete(this.browserContextId); this._registry._userContextIdToBrowserContext.delete(this.userContextId); + this._registry._updateProxiesWithSameAuthCacheAndDifferentCredentials(); } setProxy(proxy) { // Clear AuthCache. Services.obs.notifyObservers(null, "net:clear-active-logins"); this._proxy = proxy; + this._registry._updateProxiesWithSameAuthCacheAndDifferentCredentials(); } setIgnoreHTTPSErrors(ignoreHTTPSErrors) { diff --git a/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm b/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm index 47fcabbd81..05d91473d3 100644 --- a/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm +++ b/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm @@ -70,7 +70,7 @@ class JugglerFrameChild extends JSWindowActorChild { const agents = topBrowingContextToAgents.get(this.browsingContext); // The agents are already re-bound to a new actor. - if (agents.actor !== this) + if (agents?.actor !== this) return; topBrowingContextToAgents.delete(this.browsingContext); diff --git a/browser_patches/firefox/juggler/screencast/nsScreencastService.cpp b/browser_patches/firefox/juggler/screencast/nsScreencastService.cpp index 25f6171801..062a851429 100644 --- a/browser_patches/firefox/juggler/screencast/nsScreencastService.cpp +++ b/browser_patches/firefox/juggler/screencast/nsScreencastService.cpp @@ -129,7 +129,7 @@ class nsScreencastService::Session : public rtc::VideoSinkInterfaceStartCapture(capability); + int error = mCaptureModule->StartCaptureCounted(capability); if (error) { fprintf(stderr, "StartCapture error %d\n", error); return false; @@ -152,7 +152,7 @@ class nsScreencastService::Session : public rtc::VideoSinkInterfaceDeRegisterCaptureDataCallback(this); else mCaptureModule->DeRegisterRawFrameCallback(this); - mCaptureModule->StopCapture(); + mCaptureModule->StopCaptureCounted(); if (mEncoder) { mEncoder->finish([this, protect = RefPtr{this}] { NS_DispatchToMainThread(NS_NewRunnableFunction( diff --git a/browser_patches/firefox/patches/bootstrap.diff b/browser_patches/firefox/patches/bootstrap.diff index be98b14c2f..4344455a66 100644 --- a/browser_patches/firefox/patches/bootstrap.diff +++ b/browser_patches/firefox/patches/bootstrap.diff @@ -106,7 +106,7 @@ index f6d425f36a965f03ac82dbe3ab6cde06f12751ac..d60999ab2658b1e1e5f07a8aee530451 browser/chrome/browser/search-extensions/amazon/favicon.ico browser/chrome/browser/search-extensions/amazondotcn/favicon.ico diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in -index 725a63981ccb58c5e47c096f39fa686a5ba5a9e8..5fe548a9b6652c4407dad6364bf20833b7fa7f3e 100644 +index 3bf9d511555510414f39db7f99a6b5a2a743f178..bb0f71dd602193536c23f7b865ec5dce3ee02242 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -189,6 +189,9 @@ @@ -167,7 +167,7 @@ index d49c6fbf1bf83b832795fa674f6b41f223eef812..7ea3540947ff5f61b15f27fbf4b95564 const transportProvider = { setListener(upgradeListener) { diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp -index 6e1a1b689398fa6c3c73f2f243ae02c67a4476c8..9dcf71ce753cf11f295d83eb7653e940065c8e2c 100644 +index db5b5b990727aefcbaa47f89e0f53f4048e60038..bcd2321f46d9bca719fc530054984a2163c21f86 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp @@ -106,8 +106,15 @@ struct ParamTraits @@ -188,7 +188,7 @@ index 6e1a1b689398fa6c3c73f2f243ae02c67a4476c8..9dcf71ce753cf11f295d83eb7653e940 template <> struct ParamTraits -@@ -2804,6 +2811,40 @@ void BrowsingContext::DidSet(FieldIndex, +@@ -2807,6 +2814,40 @@ void BrowsingContext::DidSet(FieldIndex, PresContextAffectingFieldChanged(); } @@ -230,10 +230,10 @@ index 6e1a1b689398fa6c3c73f2f243ae02c67a4476c8..9dcf71ce753cf11f295d83eb7653e940 nsString&& aOldValue) { MOZ_ASSERT(IsTop()); diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h -index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad546dfbfddd 100644 +index 61135ab0d7894c500c3c5d80d107e283c01b6830..cc8eb043f1f78214843ec7b335dd9932587d9bbd 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h -@@ -199,10 +199,10 @@ struct EmbedderColorSchemes { +@@ -203,10 +203,10 @@ struct EmbedderColorSchemes { FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ /* ScreenOrientation-related APIs */ \ FIELD(CurrentOrientationAngle, float) \ @@ -246,7 +246,7 @@ index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad54 FIELD(EmbedderElementType, Maybe) \ FIELD(MessageManagerGroup, nsString) \ FIELD(MaxTouchPointsOverride, uint8_t) \ -@@ -240,6 +240,10 @@ struct EmbedderColorSchemes { +@@ -244,6 +244,10 @@ struct EmbedderColorSchemes { * embedder element. */ \ FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \ FIELD(DisplayMode, dom::DisplayMode) \ @@ -257,7 +257,7 @@ index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad54 /* The number of entries added to the session history because of this \ * browsing context. */ \ FIELD(HistoryEntryCount, uint32_t) \ -@@ -926,6 +930,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -937,6 +941,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { return GetPrefersColorSchemeOverride(); } @@ -272,7 +272,7 @@ index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad54 bool IsInBFCache() const; bool AllowJavascript() const { return GetAllowJavascript(); } -@@ -1090,6 +1102,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -1101,6 +1113,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { void WalkPresContexts(Callback&&); void PresContextAffectingFieldChanged(); @@ -297,10 +297,19 @@ index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad54 bool CanSet(FieldIndex, bool, ContentParent*) { diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp -index 4c92988c9b90503c95cbec63e4dc85b5f789c488..cd6eb741a432ef761bb07d8d8e97441ed111cb6e 100644 +index b59a70321b6c5801e4a4f916ee303c999747570b..1eded29480eb4b401327da9ed33a63a18e3297b9 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp -@@ -1593,6 +1593,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, +@@ -324,6 +324,8 @@ void CanonicalBrowsingContext::ReplacedBy( + txn.SetHasRestoreData(GetHasRestoreData()); + txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart()); + txn.SetForceOffline(GetForceOffline()); ++ txn.SetPrefersReducedMotionOverride(GetPrefersReducedMotionOverride()); ++ txn.SetForcedColorsOverride(GetForcedColorsOverride()); + + // Propagate some settings on BrowsingContext replacement so they're not lost + // on bfcached navigations. These are important for GeckoView (see bug +@@ -1594,6 +1596,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, return; } @@ -314,7 +323,7 @@ index 4c92988c9b90503c95cbec63e4dc85b5f789c488..cd6eb741a432ef761bb07d8d8e97441e } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp -index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161be28fee3c 100644 +index 354b2c0d66976fd7fd431902bfc7816131602496..7948aa03c8fd865bf7953faaeea2bda84ade04c8 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -15,6 +15,12 @@ @@ -330,15 +339,15 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" -@@ -64,6 +70,7 @@ - #include "mozilla/dom/ContentFrameMessageManager.h" +@@ -66,6 +72,7 @@ #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/Element.h" + #include "mozilla/dom/FragmentDirective.h" +#include "mozilla/dom/Geolocation.h" #include "mozilla/dom/HTMLAnchorElement.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/PerformanceNavigation.h" -@@ -88,6 +95,7 @@ +@@ -90,6 +97,7 @@ #include "mozilla/dom/JSWindowActorChild.h" #include "mozilla/dom/DocumentBinding.h" #include "mozilla/ipc/ProtocolUtils.h" @@ -346,7 +355,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b #include "mozilla/net/DocumentChannel.h" #include "mozilla/net/DocumentChannelChild.h" #include "mozilla/net/ParentChannelWrapper.h" -@@ -111,6 +119,7 @@ +@@ -113,6 +121,7 @@ #include "nsIDocumentViewer.h" #include "mozilla/dom/Document.h" #include "nsHTMLDocument.h" @@ -362,7 +371,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsObjectLoadingContent.h" -@@ -346,6 +356,13 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, +@@ -347,6 +357,13 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, mAllowDNSPrefetch(true), mAllowWindowControl(true), mCSSErrorReportingEnabled(false), @@ -376,7 +385,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b mAllowAuth(mItemType == typeContent), mAllowKeywordFixup(false), mDisableMetaRefreshWhenInactive(false), -@@ -3066,6 +3083,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { +@@ -3046,6 +3063,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { return NS_OK; } @@ -591,7 +600,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b NS_IMETHODIMP nsDocShell::GetIsNavigating(bool* aOut) { *aOut = mIsNavigating; -@@ -4754,7 +4979,7 @@ nsDocShell::GetVisibility(bool* aVisibility) { +@@ -4734,7 +4959,7 @@ nsDocShell::GetVisibility(bool* aVisibility) { } void nsDocShell::ActivenessMaybeChanged() { @@ -600,7 +609,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b if (RefPtr presShell = GetPresShell()) { presShell->ActivenessMaybeChanged(); } -@@ -6676,6 +6901,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, +@@ -6672,6 +6897,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, return false; // no entry to save into } @@ -611,7 +620,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b MOZ_ASSERT(!mozilla::SessionHistoryInParent(), "mOSHE cannot be non-null with SHIP"); nsCOMPtr viewer = mOSHE->GetDocumentViewer(); -@@ -8408,6 +8637,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { +@@ -8401,6 +8630,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { true, // aForceNoOpener getter_AddRefs(newBC)); MOZ_ASSERT(!newBC); @@ -624,7 +633,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b return rv; } -@@ -9528,6 +9763,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, +@@ -9533,6 +9768,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr); nsCOMPtr req; @@ -641,7 +650,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req)); if (NS_SUCCEEDED(rv)) { -@@ -12691,6 +12936,9 @@ class OnLinkClickEvent : public Runnable { +@@ -12710,6 +12955,9 @@ class OnLinkClickEvent : public Runnable { mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied, mTriggeringPrincipal); } @@ -651,7 +660,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b return NS_OK; } -@@ -12775,6 +13023,8 @@ nsresult nsDocShell::OnLinkClick( +@@ -12792,6 +13040,8 @@ nsresult nsDocShell::OnLinkClick( nsCOMPtr ev = new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied, aIsTrusted, aTriggeringPrincipal); @@ -661,7 +670,7 @@ index 87a34cf5b2f29bb3a7287327cc329974201856b6..014152f79069ee4196e2cda9374d161b } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h -index f01e3426c4bc3a2145629be1bc7cf1d9cfd4c8bf..d4d76e391034203a67d4f2def714ba6efb1bea7b 100644 +index 0ea84df4dde885fd8e7f7e6045db56a0fa6b2691..b00bc444a5a25e63f98e583959d5f6812caf1815 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -15,6 +15,7 @@ @@ -723,7 +732,7 @@ index f01e3426c4bc3a2145629be1bc7cf1d9cfd4c8bf..d4d76e391034203a67d4f2def714ba6e bool mAllowKeywordFixup : 1; bool mDisableMetaRefreshWhenInactive : 1; diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl -index 024c0f544c54b7cfb0eeb64e5dc39a0b5d2a5950..63adaf24fd6dfd75f559323ae3c07e265f9f8241 100644 +index fdc04f16c6f547077ad8c872f9357d85d4513c50..199f8fdb0670265c715f99f5cac1a2b2f22c963d 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -44,6 +44,7 @@ interface nsIURI; @@ -734,7 +743,7 @@ index 024c0f544c54b7cfb0eeb64e5dc39a0b5d2a5950..63adaf24fd6dfd75f559323ae3c07e26 interface nsIEditor; interface nsIEditingSession; interface nsIInputStream; -@@ -729,6 +730,36 @@ interface nsIDocShell : nsIDocShellTreeItem +@@ -719,6 +720,36 @@ interface nsIDocShell : nsIDocShellTreeItem */ void synchronizeLayoutHistoryState(); @@ -772,10 +781,10 @@ index 024c0f544c54b7cfb0eeb64e5dc39a0b5d2a5950..63adaf24fd6dfd75f559323ae3c07e26 * 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 8cbf8b8075488fe1edc33b558f90224bd992117c..72198d51ca3c74eb855c928319551505422d6ebd 100644 +index c6cb09e1955d371cd19f563b30b486bcc2318304..d836946872b8e32360a925be5084472191e04f05 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp -@@ -3680,6 +3680,9 @@ void Document::SendToConsole(nsCOMArray& aMessages) { +@@ -3674,6 +3674,9 @@ void Document::SendToConsole(nsCOMArray& aMessages) { } void Document::ApplySettingsFromCSP(bool aSpeculative) { @@ -785,7 +794,7 @@ index 8cbf8b8075488fe1edc33b558f90224bd992117c..72198d51ca3c74eb855c928319551505 nsresult rv = NS_OK; if (!aSpeculative) { // 1) apply settings from regular CSP -@@ -3737,6 +3740,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { +@@ -3731,6 +3734,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { MOZ_ASSERT(!mScriptGlobalObject, "CSP must be initialized before mScriptGlobalObject is set!"); @@ -797,7 +806,7 @@ index 8cbf8b8075488fe1edc33b558f90224bd992117c..72198d51ca3c74eb855c928319551505 // If this is a data document - no need to set CSP. if (mLoadedAsData) { return NS_OK; -@@ -4522,6 +4530,10 @@ bool Document::HasFocus(ErrorResult& rv) const { +@@ -4501,6 +4509,10 @@ bool Document::HasFocus(ErrorResult& rv) const { return false; } @@ -808,7 +817,7 @@ index 8cbf8b8075488fe1edc33b558f90224bd992117c..72198d51ca3c74eb855c928319551505 if (!fm->IsInActiveWindow(bc)) { return false; } -@@ -18892,6 +18904,66 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { +@@ -18878,6 +18890,66 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { return PreferenceSheet::PrefsFor(*this).mColorScheme; } @@ -876,7 +885,7 @@ index 8cbf8b8075488fe1edc33b558f90224bd992117c..72198d51ca3c74eb855c928319551505 if (!sLoadingForegroundTopLevelContentDocument) { return false; diff --git a/dom/base/Document.h b/dom/base/Document.h -index 0b0d0ca3d0fdb1d5c2b88b77e78cd164230e040a..82d340aba7e4f37fde2dae09bd831ccb24302a12 100644 +index 7eea29947d91f6b99363d7bf4c69f4e7b3276636..227314db13631b825b9b0701e8f9e5e630f78a72 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -4035,6 +4035,9 @@ class Document : public nsINode, @@ -890,10 +899,10 @@ index 0b0d0ca3d0fdb1d5c2b88b77e78cd164230e040a..82d340aba7e4f37fde2dae09bd831ccb static bool AutomaticStorageAccessPermissionCanBeGranted( diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp -index 14a00b8ed85f69312a89990acbb5e0f9755bd832..c97bb6cdd125e755333254a954777b06815352c7 100644 +index a7229fe412644212747646bee5e111cb427bab52..4fdefb186804ed39d4670cca32e495d95f3546d6 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp -@@ -338,14 +338,18 @@ void Navigator::GetAppName(nsAString& aAppName) const { +@@ -344,14 +344,18 @@ void Navigator::GetAppName(nsAString& aAppName) const { * for more detail. */ /* static */ @@ -914,7 +923,7 @@ index 14a00b8ed85f69312a89990acbb5e0f9755bd832..c97bb6cdd125e755333254a954777b06 // Split values on commas. for (nsDependentSubstring lang : -@@ -397,7 +401,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) { +@@ -403,7 +407,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) { } void Navigator::GetLanguages(nsTArray& aLanguages) { @@ -929,7 +938,7 @@ index 14a00b8ed85f69312a89990acbb5e0f9755bd832..c97bb6cdd125e755333254a954777b06 // The returned value is cached by the binding code. The window listens to the // accept languages change and will clear the cache when needed. It has to -@@ -2281,7 +2291,8 @@ bool Navigator::Webdriver() { +@@ -2308,7 +2318,8 @@ bool Navigator::Webdriver() { } #endif @@ -940,10 +949,10 @@ index 14a00b8ed85f69312a89990acbb5e0f9755bd832..c97bb6cdd125e755333254a954777b06 AutoplayPolicy Navigator::GetAutoplayPolicy(AutoplayPolicyMediaType aType) { diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h -index e559dc4d6aef61b7012a27f3d6c3186a12a15319..9798a50789ce972c4d9e94419e20a5cde4cd552a 100644 +index 4c400554f9b129f4482b513b46b90b780f2b8796..6efdca2363d83327562751757753abd602c80ddd 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h -@@ -215,7 +215,7 @@ class Navigator final : public nsISupports, public nsWrapperCache { +@@ -218,7 +218,7 @@ class Navigator final : public nsISupports, public nsWrapperCache { StorageManager* Storage(); @@ -953,10 +962,10 @@ index e559dc4d6aef61b7012a27f3d6c3186a12a15319..9798a50789ce972c4d9e94419e20a5cd dom::MediaCapabilities* MediaCapabilities(); dom::MediaSession* MediaSession(); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp -index d2c863bd6549a7e2fbbc09deb99c93cdde6a7199..2639a0e3160250d3cd3c8a8074147039405931a8 100644 +index 1edbffd5353a77fd84bc9abecb0628557512fa67..33376c1d44dbc0561c210e48401d6b173924067d 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp -@@ -8731,7 +8731,8 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8796,7 +8796,8 @@ nsresult nsContentUtils::SendMouseEvent( bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized, @@ -966,7 +975,7 @@ index d2c863bd6549a7e2fbbc09deb99c93cdde6a7199..2639a0e3160250d3cd3c8a8074147039 nsPoint offset; nsCOMPtr widget = GetWidget(aPresShell, &offset); if (!widget) return NS_ERROR_FAILURE; -@@ -8739,6 +8740,7 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8804,6 +8805,7 @@ nsresult nsContentUtils::SendMouseEvent( EventMessage msg; Maybe exitFrom; bool contextMenuKey = false; @@ -974,7 +983,7 @@ index d2c863bd6549a7e2fbbc09deb99c93cdde6a7199..2639a0e3160250d3cd3c8a8074147039 if (aType.EqualsLiteral("mousedown")) { msg = eMouseDown; } else if (aType.EqualsLiteral("mouseup")) { -@@ -8763,6 +8765,12 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8828,6 +8830,12 @@ nsresult nsContentUtils::SendMouseEvent( msg = eMouseHitTest; } else if (aType.EqualsLiteral("MozMouseExploreByTouch")) { msg = eMouseExploreByTouch; @@ -987,7 +996,7 @@ index d2c863bd6549a7e2fbbc09deb99c93cdde6a7199..2639a0e3160250d3cd3c8a8074147039 } else { return NS_ERROR_FAILURE; } -@@ -8771,12 +8779,21 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8836,12 +8844,21 @@ nsresult nsContentUtils::SendMouseEvent( aInputSourceArg = MouseEvent_Binding::MOZ_SOURCE_MOUSE; } @@ -1011,7 +1020,7 @@ index d2c863bd6549a7e2fbbc09deb99c93cdde6a7199..2639a0e3160250d3cd3c8a8074147039 event.pointerId = aIdentifier; event.mModifiers = GetWidgetModifiers(aModifiers); event.mButton = aButton; -@@ -8787,8 +8804,10 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8852,8 +8869,10 @@ nsresult nsContentUtils::SendMouseEvent( event.mPressure = aPressure; event.mInputSource = aInputSourceArg; event.mClickCount = aClickCount; @@ -1023,10 +1032,10 @@ index d2c863bd6549a7e2fbbc09deb99c93cdde6a7199..2639a0e3160250d3cd3c8a8074147039 nsPresContext* presContext = aPresShell->GetPresContext(); if (!presContext) return NS_ERROR_FAILURE; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h -index 4291d2c5d1e17b75b2a0f3f26c84b0fbea03b027..18b311c64f825977e2c01ccc9dbfd6cd97ed8778 100644 +index ef3c1fd7cbb3a6c457ec7d70a50fd412077f4279..bd4e6e5db6273f024684169439fd31e0095b45f4 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h -@@ -3065,7 +3065,8 @@ class nsContentUtils { +@@ -3078,7 +3078,8 @@ class nsContentUtils { int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, mozilla::PreventDefaultResult* aPreventDefault, @@ -1037,10 +1046,10 @@ index 4291d2c5d1e17b75b2a0f3f26c84b0fbea03b027..18b311c64f825977e2c01ccc9dbfd6cd static void FirePageShowEventForFrameLoaderSwap( nsIDocShellTreeItem* aItem, diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp -index 9bc8340b9009717e0feecd5c14ff02be07a03daf..70ea04c7f11e6ccfadf72a82ec1741fac10ef5fd 100644 +index 6d611b4a8485325435267c89c88b5511bb37d2f2..13640d6bd8fc34797f5f0088bf12ff016b4b3ae7 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp -@@ -685,6 +685,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) { +@@ -684,6 +684,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) { return NS_ERROR_FAILURE; } @@ -1067,7 +1076,7 @@ index 9bc8340b9009717e0feecd5c14ff02be07a03daf..70ea04c7f11e6ccfadf72a82ec1741fa NS_IMETHODIMP nsDOMWindowUtils::SendMouseEvent( const nsAString& aType, float aX, float aY, int32_t aButton, -@@ -699,7 +719,7 @@ nsDOMWindowUtils::SendMouseEvent( +@@ -698,7 +718,7 @@ nsDOMWindowUtils::SendMouseEvent( aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, false, aPreventDefault, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true, aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false, @@ -1076,7 +1085,7 @@ index 9bc8340b9009717e0feecd5c14ff02be07a03daf..70ea04c7f11e6ccfadf72a82ec1741fa } NS_IMETHODIMP -@@ -717,7 +737,7 @@ nsDOMWindowUtils::SendMouseEventToWindow( +@@ -716,7 +736,7 @@ nsDOMWindowUtils::SendMouseEventToWindow( aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, true, nullptr, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true, aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false, @@ -1085,7 +1094,7 @@ index 9bc8340b9009717e0feecd5c14ff02be07a03daf..70ea04c7f11e6ccfadf72a82ec1741fa } NS_IMETHODIMP -@@ -726,13 +746,13 @@ nsDOMWindowUtils::SendMouseEventCommon( +@@ -725,13 +745,13 @@ nsDOMWindowUtils::SendMouseEventCommon( int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aPointerId, bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized, @@ -1115,10 +1124,10 @@ index 63968c9b7a4e418e4c0de6e7a75fa215a36a9105..decf3ea3833ccdffd49a7aded2d600f9 MOZ_CAN_RUN_SCRIPT nsresult SendTouchEventCommon( diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp -index 4e2d60469346772d6dad49b38b06a37c9f819a79..5123a1b40a951f2aa10b1b018c4e72abf18d0d27 100644 +index 587f03849d72d72020e89f4456dec481c9ede9f6..d0a910d3ae25fd4f6545f6d9130c8be04a06ed0e 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp -@@ -1678,6 +1678,10 @@ Maybe nsFocusManager::SetFocusInner(Element* aNewContent, +@@ -1684,6 +1684,10 @@ Maybe nsFocusManager::SetFocusInner(Element* aNewContent, (GetActiveBrowsingContext() == newRootBrowsingContext); } @@ -1129,7 +1138,7 @@ index 4e2d60469346772d6dad49b38b06a37c9f819a79..5123a1b40a951f2aa10b1b018c4e72ab // Exit fullscreen if a website focuses another window if (StaticPrefs::full_screen_api_exit_on_windowRaise() && !isElementInActiveWindow && (aFlags & FLAG_RAISE)) { -@@ -2263,6 +2267,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, +@@ -2269,6 +2273,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, bool aIsLeavingDocument, bool aAdjustWidget, bool aRemainActive, Element* aElementToFocus, uint64_t aActionId) { @@ -1137,7 +1146,7 @@ index 4e2d60469346772d6dad49b38b06a37c9f819a79..5123a1b40a951f2aa10b1b018c4e72ab LOGFOCUS(("<>", aActionId)); // hold a reference to the focused content, which may be null -@@ -2309,6 +2314,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, +@@ -2315,6 +2320,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, return true; } @@ -1149,7 +1158,7 @@ index 4e2d60469346772d6dad49b38b06a37c9f819a79..5123a1b40a951f2aa10b1b018c4e72ab // Keep a ref to presShell since dispatching the DOM event may cause // the document to be destroyed. RefPtr presShell = docShell->GetPresShell(); -@@ -2986,7 +2996,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, +@@ -2992,7 +3002,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, } } @@ -1161,7 +1170,7 @@ index 4e2d60469346772d6dad49b38b06a37c9f819a79..5123a1b40a951f2aa10b1b018c4e72ab // 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 c678a0a94150e73c6f5cd086c91a9848d4d0cfb8..0b362540bab495bffcf8a336351650f5897e0af5 100644 +index 460ccc17f2cd34f172215aaf5616badaa44f8ca5..d294373ca9b8987dd8bf056f4dae72c27903dcd7 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -2514,10 +2514,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, @@ -1206,7 +1215,7 @@ index c678a0a94150e73c6f5cd086c91a9848d4d0cfb8..0b362540bab495bffcf8a336351650f5 void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) { diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h -index 3c26344c3d45c57606d498008946fc480cd2eb96..b02909587341860ad805072755761474029f6eec 100644 +index 0039d6d91b23953afbd6aec2b4d1f064db3c3b1c..7a6c5da16651d34ea60c69331365d94886da1993 100644 --- a/dom/base/nsGlobalWindowOuter.h +++ b/dom/base/nsGlobalWindowOuter.h @@ -314,6 +314,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget, @@ -1218,10 +1227,10 @@ index 3c26344c3d45c57606d498008946fc480cd2eb96..b02909587341860ad805072755761474 // Outer windows only. virtual void EnsureSizeAndPositionUpToDate() override; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp -index 6c77574df7973f12a5afd28bd9eb2bf5c4ae91ff..76feda136c7e39b0ebe3333714b41347ecc6aef2 100644 +index 600fce143a0e1e35a18b980211686436be08533f..ec6f7c60d0a3756dcf8892e4690281e1a65f9b6a 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp -@@ -1388,6 +1388,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions, +@@ -1387,6 +1387,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions, mozilla::GetBoxQuadsFromWindowOrigin(this, aOptions, aResult, aRv); } @@ -1284,10 +1293,10 @@ index 6c77574df7973f12a5afd28bd9eb2bf5c4ae91ff..76feda136c7e39b0ebe3333714b41347 DOMQuad& aQuad, const GeometryNode& aFrom, const ConvertCoordinateOptions& aOptions, CallerType aCallerType, diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h -index d2a2fd008d15b5aa4534df1d7298108980bff41d..df86f5d534b4718bf1912da305fa2548a04637dc 100644 +index 2906bbb56c86cd287620b4bd067366f6703299d7..06697f07c7544c816181fa9849ce178bf38303aa 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h -@@ -2265,6 +2265,10 @@ class nsINode : public mozilla::dom::EventTarget { +@@ -2282,6 +2282,10 @@ class nsINode : public mozilla::dom::EventTarget { nsTArray>& aResult, ErrorResult& aRv); @@ -1468,7 +1477,7 @@ index 7e1af00d05fbafa2d828e2c7e4dcc5c82d115f5b..e85af9718d064e4d2865bc944e9d4ba1 ~Geolocation(); diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp -index 958601616b8f5749ae704b0cdd8a0c5a397dc021..87fa7c434f852b4203beec320d7b27bbcfc108ef 100644 +index 30093e5d408caa054a04adddf63ce2bec384eed6..2852746b6f5b50981dba29a65ce25c1fd55390e3 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -57,6 +57,7 @@ @@ -1494,7 +1503,7 @@ index 958601616b8f5749ae704b0cdd8a0c5a397dc021..87fa7c434f852b4203beec320d7b27bb return NS_OK; } diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl -index 27b9ff5acf8b1100a3b55d55cc390a15b2a3e3a4..7e8e2264dd4bb814cca5b00de80fcd5ce5c4be4d 100644 +index 9d185e8e7edcde63f0d2e0c05a32dfddaf71609c..9d48d2e33575c7f214152c6f8140f9a3a3313b44 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -373,6 +373,26 @@ interface nsIDOMWindowUtils : nsISupports { @@ -1525,10 +1534,10 @@ index 27b9ff5acf8b1100a3b55d55cc390a15b2a3e3a4..7e8e2264dd4bb814cca5b00de80fcd5c * touchstart, touchend, touchmove, and touchcancel * diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp -index 3d1f399edb4feb0793c214917464be2599739e6e..1d4a176b1a111a9415710f1bf1db1323620e4ee0 100644 +index 27fb1239dbd2a635688d022602d4a49dfff0560a..39f9dd48eef038503a50632c5e1395fecea6cae3 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp -@@ -1652,6 +1652,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, +@@ -1639,6 +1639,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, if (postLayerization) { postLayerization->Register(); } @@ -1565,15 +1574,12 @@ index 5aa445d2e0a6169e57c44569974d557b3baf7064..671f71979b407f0ca17c66f13805e851 } diff --git a/dom/media/systemservices/video_engine/desktop_capture_impl.cc b/dom/media/systemservices/video_engine/desktop_capture_impl.cc -index a966ff06d4a52e2ff70ce71df3a6a607a67e3eda..eb6bb16413df43217ddd85048c02d41d15e8431f 100644 +index d4b40fda96ea759eb92e1351e1046a9e0b85689b..b2123a3be05b2622a5e07d5ee32752d0feaaa57f 100644 --- a/dom/media/systemservices/video_engine/desktop_capture_impl.cc +++ b/dom/media/systemservices/video_engine/desktop_capture_impl.cc -@@ -135,11 +135,12 @@ int32_t ScreenDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8, - return 0; - } +@@ -137,9 +137,10 @@ int32_t ScreenDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8, --VideoCaptureModule* DesktopCaptureImpl::Create(const int32_t aModuleId, -+VideoCaptureModuleEx* DesktopCaptureImpl::Create(const int32_t aModuleId, + DesktopCaptureImpl* DesktopCaptureImpl::Create(const int32_t aModuleId, const char* aUniqueId, - const CaptureDeviceType aType) { + const CaptureDeviceType aType, @@ -1653,7 +1659,7 @@ index a966ff06d4a52e2ff70ce71df3a6a607a67e3eda..eb6bb16413df43217ddd85048c02d41d MOZ_ASSERT(!capturer == !mCaptureThread); if (!capturer) { -@@ -654,6 +672,15 @@ void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult, +@@ -663,6 +681,15 @@ void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult, frameInfo.height = aFrame->size().height(); frameInfo.videoType = VideoType::kARGB; @@ -1670,18 +1676,18 @@ index a966ff06d4a52e2ff70ce71df3a6a607a67e3eda..eb6bb16413df43217ddd85048c02d41d frameInfo.width * frameInfo.height * DesktopFrame::kBytesPerPixel; diff --git a/dom/media/systemservices/video_engine/desktop_capture_impl.h b/dom/media/systemservices/video_engine/desktop_capture_impl.h -index 7292f6c8a70298d4bf103080804843fa9bba1d15..7a50fee0d2fcaad475302d010892800a6e3c4e75 100644 +index 9aebaa39321839eb3beb503fc4ed33e303bb0deb..1dd75f3cdb8078b01c4d43a0ac3d8a6ea3ec47ab 100644 --- a/dom/media/systemservices/video_engine/desktop_capture_impl.h +++ b/dom/media/systemservices/video_engine/desktop_capture_impl.h -@@ -24,6 +24,7 @@ - #include "api/video/video_sink_interface.h" +@@ -25,6 +25,7 @@ #include "modules/desktop_capture/desktop_capturer.h" #include "modules/video_capture/video_capture.h" + #include "rtc_base/synchronization/mutex.h" +#include "rtc_base/deprecated/recursive_critical_section.h" #include "desktop_device_info.h" - #include "mozilla/DataMutex.h" -@@ -43,6 +44,21 @@ namespace webrtc { + #include "MediaEngineSource.h" +@@ -45,6 +46,33 @@ namespace webrtc { class VideoCaptureEncodeInterface; @@ -1698,12 +1704,24 @@ index 7292f6c8a70298d4bf103080804843fa9bba1d15..7a50fee0d2fcaad475302d010892800a + + virtual void RegisterRawFrameCallback(RawFrameCallback* rawFrameCallback) = 0; + virtual void DeRegisterRawFrameCallback(RawFrameCallback* rawFrameCallback) = 0; ++ int32_t StartCaptureCounted(const VideoCaptureCapability& aCapability) { ++ ++capture_counter_; ++ return capture_counter_ == 1 ? StartCapture(aCapability) : 0; ++ } ++ ++ int32_t StopCaptureCounted() { ++ --capture_counter_; ++ return capture_counter_ == 0 ? StopCapture() : 0; ++ } ++ ++ private: ++ int32_t capture_counter_ = 0; +}; + // simulate deviceInfo interface for video engine, bridge screen/application and // real screen/application device info -@@ -158,13 +174,13 @@ class BrowserDeviceInfoImpl : public VideoCaptureModule::DeviceInfo { +@@ -160,13 +188,13 @@ class BrowserDeviceInfoImpl : public VideoCaptureModule::DeviceInfo { // As with video, DesktopCaptureImpl is a proxy for screen sharing // and follows the video pipeline design class DesktopCaptureImpl : public DesktopCapturer::Callback, @@ -1712,15 +1730,14 @@ index 7292f6c8a70298d4bf103080804843fa9bba1d15..7a50fee0d2fcaad475302d010892800a public: /* Create a screen capture modules object */ -- static VideoCaptureModule* Create( -+ static VideoCaptureModuleEx* Create( + static DesktopCaptureImpl* Create( const int32_t aModuleId, const char* aUniqueId, - const mozilla::camera::CaptureDeviceType aType); + const mozilla::camera::CaptureDeviceType aType, bool aCaptureCursor = true); [[nodiscard]] static std::shared_ptr CreateDeviceInfo(const int32_t aId, -@@ -178,6 +194,8 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, +@@ -180,6 +208,8 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, void DeRegisterCaptureDataCallback( rtc::VideoSinkInterface* aCallback) override; int32_t StopCaptureIfAllClientsClose() override; @@ -1729,7 +1746,7 @@ index 7292f6c8a70298d4bf103080804843fa9bba1d15..7a50fee0d2fcaad475302d010892800a int32_t SetCaptureRotation(VideoRotation aRotation) override; bool SetApplyRotation(bool aEnable) override; -@@ -200,7 +218,8 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, +@@ -203,7 +233,8 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, protected: DesktopCaptureImpl(const int32_t aId, const char* aUniqueId, @@ -1739,7 +1756,7 @@ index 7292f6c8a70298d4bf103080804843fa9bba1d15..7a50fee0d2fcaad475302d010892800a virtual ~DesktopCaptureImpl(); private: -@@ -208,6 +227,9 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, +@@ -211,6 +242,9 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, static constexpr uint32_t kMaxDesktopCaptureCpuUsage = 50; void InitOnThread(std::unique_ptr aCapturer, int aFramerate); void ShutdownOnThread(); @@ -1749,7 +1766,7 @@ index 7292f6c8a70298d4bf103080804843fa9bba1d15..7a50fee0d2fcaad475302d010892800a // DesktopCapturer::Callback interface. void OnCaptureResult(DesktopCapturer::Result aResult, std::unique_ptr aFrame) override; -@@ -215,6 +237,8 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, +@@ -218,6 +252,8 @@ class DesktopCaptureImpl : public DesktopCapturer::Callback, // Notifies all mCallbacks of OnFrame(). mCaptureThread only. void NotifyOnFrame(const VideoFrame& aFrame); @@ -1803,7 +1820,7 @@ index 3b39538e51840cd9b1685b2efd2ff2e9ec83608a..c7bf4f2d53b58bbacb22b3ebebf6f3fc return aGlobalOrNull; diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp -index 11d09909f73fee425fd0f50b384c396a52e02a36..b0e668881bcd3b850de709ebf2557ae8391b8fe8 100644 +index f4aecfaf44d40d651f816c56db4b46c605754132..ef017504972454c12de7d6a7ff38a76a8253a62d 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -22,6 +22,7 @@ @@ -1812,9 +1829,9 @@ index 11d09909f73fee425fd0f50b384c396a52e02a36..b0e668881bcd3b850de709ebf2557ae8 #include "nsWhitespaceTokenizer.h" +#include "nsDocShell.h" + #include "mozilla/Assertions.h" #include "mozilla/Components.h" - #include "mozilla/dom/CSPDictionariesBinding.h" -@@ -132,6 +133,11 @@ void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc, +@@ -133,6 +134,11 @@ void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc, return; } @@ -1850,10 +1867,10 @@ index 2f71b284ee5f7e11f117c447834b48355784448c..2640bd57123c2b03bf4b06a2419cd020 * 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 321895e700a3129febba908fd7f35664c56ff637..375596973393158c7ff4586105451c336f63b5a0 100644 +index 4a7ebb25233ce685e73d53085e22337e9ad8bc59..0b7b24a4da5511ff2fa6695eb55f5533b2e574ab 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp -@@ -1001,7 +1001,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { +@@ -998,7 +998,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsTArray languages; @@ -1862,7 +1879,7 @@ index 321895e700a3129febba908fd7f35664c56ff637..375596973393158c7ff4586105451c33 RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { -@@ -1188,8 +1188,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { +@@ -1185,8 +1185,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { } // The navigator overridden properties should have already been read. @@ -1886,7 +1903,7 @@ index 321895e700a3129febba908fd7f35664c56ff637..375596973393158c7ff4586105451c33 template void RuntimeService::BroadcastAllWorkers(const Func& aFunc) { AssertIsOnMainThread(); -@@ -2310,6 +2316,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers( +@@ -2314,6 +2320,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers( } } @@ -1928,7 +1945,7 @@ index d10dabb5c5ff8e17851edf2bd2efc08e74584d8e..53c4070c5fde43b27fb8fbfdcf4c23d8 bool IsWorkerGlobal(JSObject* global); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp -index df248acda417ae2f304a7f5fa5d067912e402431..5a5b07409c6bca2c8cd80f4595c4e524b56566ef 100644 +index 7fbfdb0eeed2fc9d9a6ba12192150d5bdeed40b3..c31ae2724d09036ec2ba0b71cd94f648e9b90868 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -682,6 +682,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable { @@ -1938,7 +1955,7 @@ index df248acda417ae2f304a7f5fa5d067912e402431..5a5b07409c6bca2c8cd80f4595c4e524 +class ResetDefaultLocaleRunnable final : public WorkerControlRunnable { + public: + explicit ResetDefaultLocaleRunnable(WorkerPrivate* aWorkerPrivate) -+ : WorkerControlRunnable(aWorkerPrivate, "ResetDefaultLocaleRunnable", WorkerThread) {} ++ : WorkerControlRunnable("ResetDefaultLocaleRunnable") {} + + virtual bool WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) override { @@ -1947,10 +1964,10 @@ index df248acda417ae2f304a7f5fa5d067912e402431..5a5b07409c6bca2c8cd80f4595c4e524 + } +}; + - class UpdateLanguagesRunnable final : public WorkerRunnable { + class UpdateLanguagesRunnable final : public WorkerThreadRunnable { nsTArray mLanguages; -@@ -1992,6 +2004,16 @@ void WorkerPrivate::UpdateContextOptions( +@@ -2091,6 +2103,16 @@ void WorkerPrivate::UpdateContextOptions( } } @@ -1959,7 +1976,7 @@ index df248acda417ae2f304a7f5fa5d067912e402431..5a5b07409c6bca2c8cd80f4595c4e524 + + RefPtr runnable = + new ResetDefaultLocaleRunnable(this); -+ if (!runnable->Dispatch()) { ++ if (!runnable->Dispatch(this)) { + NS_WARNING("Failed to reset default locale in worker!"); + } +} @@ -1967,7 +1984,7 @@ index df248acda417ae2f304a7f5fa5d067912e402431..5a5b07409c6bca2c8cd80f4595c4e524 void WorkerPrivate::UpdateLanguages(const nsTArray& aLanguages) { AssertIsOnParentThread(); -@@ -5509,6 +5531,15 @@ void WorkerPrivate::UpdateContextOptionsInternal( +@@ -5667,6 +5689,15 @@ void WorkerPrivate::UpdateContextOptionsInternal( } } @@ -1984,10 +2001,10 @@ index df248acda417ae2f304a7f5fa5d067912e402431..5a5b07409c6bca2c8cd80f4595c4e524 const nsTArray& aLanguages) { WorkerGlobalScope* globalScope = GlobalScope(); diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h -index ce754ba9f6c23544f9c4b0393e3a3d1f9dcd24c1..caa917248fdd6594792d17c23e75329f35ae1e75 100644 +index 57212e01fb75da52187195acfbe052b19464286a..bc75882ee661d5c987187cd11b388443227d59bc 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h -@@ -417,6 +417,8 @@ class WorkerPrivate final +@@ -418,6 +418,8 @@ class WorkerPrivate final void UpdateContextOptionsInternal(JSContext* aCx, const JS::ContextOptions& aContextOptions); @@ -1996,7 +2013,7 @@ index ce754ba9f6c23544f9c4b0393e3a3d1f9dcd24c1..caa917248fdd6594792d17c23e75329f void UpdateLanguagesInternal(const nsTArray& aLanguages); void UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, -@@ -1034,6 +1036,8 @@ class WorkerPrivate final +@@ -1045,6 +1047,8 @@ class WorkerPrivate final void UpdateContextOptions(const JS::ContextOptions& aContextOptions); @@ -2058,7 +2075,7 @@ index 523e84c8c93f4221701f90f2e8ee146ec8e1adbd..98d5b1176e5378431b859a2dbd4d4e77 inline ClippedTime TimeClip(double time); diff --git a/js/src/debugger/Object.cpp b/js/src/debugger/Object.cpp -index 17528b0fd99ce8274e702746ff5674de4c3d4f2d..37fa52ae07381bec3504136b9bec0aa1ca110d6b 100644 +index 6ca6c31830066f9677988daca8566e93a8335c26..3f963dbf6127c997810e380802e56b023b5db4c8 100644 --- a/js/src/debugger/Object.cpp +++ b/js/src/debugger/Object.cpp @@ -2468,7 +2468,11 @@ Maybe DebuggerObject::call(JSContext* cx, @@ -2074,7 +2091,7 @@ index 17528b0fd99ce8274e702746ff5674de4c3d4f2d..37fa52ae07381bec3504136b9bec0aa1 } diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp -index 21ecc2e9f50a16357ace3320335d31a4929fc146..c4b1444ce53b20a700d2ff9f18521bd67623e7d2 100644 +index 623a6863a54fb0d653ebe55fd83356f1a8c8be15..1c0ef7b0d3ee2f61de728a68dd704a5d09757b38 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -186,6 +186,11 @@ void js::DateTimeInfo::internalResetTimeZone(ResetTimeZoneMode mode) { @@ -2089,7 +2106,7 @@ index 21ecc2e9f50a16357ace3320335d31a4929fc146..c4b1444ce53b20a700d2ff9f18521bd6 void js::DateTimeInfo::updateTimeZone() { MOZ_ASSERT(timeZoneStatus_ != TimeZoneStatus::Valid); -@@ -527,10 +532,24 @@ void js::ResetTimeZoneInternal(ResetTimeZoneMode mode) { +@@ -529,10 +534,24 @@ void js::ResetTimeZoneInternal(ResetTimeZoneMode mode) { js::DateTimeInfo::resetTimeZone(mode); } @@ -2114,7 +2131,7 @@ index 21ecc2e9f50a16357ace3320335d31a4929fc146..c4b1444ce53b20a700d2ff9f18521bd6 #if JS_HAS_INTL_API # if defined(XP_WIN) static bool IsOlsonCompatibleWindowsTimeZoneId(std::string_view tz) { -@@ -748,6 +767,15 @@ static bool ReadTimeZoneLink(std::string_view tz, +@@ -750,6 +769,15 @@ static bool ReadTimeZoneLink(std::string_view tz, void js::DateTimeInfo::internalResyncICUDefaultTimeZone() { #if JS_HAS_INTL_API @@ -2130,7 +2147,7 @@ index 21ecc2e9f50a16357ace3320335d31a4929fc146..c4b1444ce53b20a700d2ff9f18521bd6 // In the future we should not be setting a default ICU time zone at all, // instead all accesses should go through the appropriate DateTimeInfo // instance depending on the resist fingerprinting status. For now we return -@@ -759,7 +787,6 @@ void js::DateTimeInfo::internalResyncICUDefaultTimeZone() { +@@ -761,7 +789,6 @@ void js::DateTimeInfo::internalResyncICUDefaultTimeZone() { if (const char* tzenv = std::getenv("TZ")) { std::string_view tz(tzenv); @@ -2178,7 +2195,7 @@ index fd6d7ae078b8f6b3cc46a4a993a1e044a7128c90..4743094e489122dd9ee8ab9a7a175dd7 void internalResyncICUDefaultTimeZone(); diff --git a/layout/base/GeometryUtils.cpp b/layout/base/GeometryUtils.cpp -index dac899f7558b26d6848da8b98ed8a93555c8751a..2a07d67fa1c2840b25085566e84dc3b2d9b789cf 100644 +index 0ec6ee3eb37c6493d8a25352fd0e54e1927bceab..885dba71bc5815e5f6f3ec2700c376aa119b30d0 100644 --- a/layout/base/GeometryUtils.cpp +++ b/layout/base/GeometryUtils.cpp @@ -23,6 +23,7 @@ @@ -2228,10 +2245,10 @@ index dac899f7558b26d6848da8b98ed8a93555c8751a..2a07d67fa1c2840b25085566e84dc3b2 // No boxes to return return; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp -index 7674abb3d07fb1958eed38e730be83a2b56f75f2..f88a1a3bd6f1df7ebbdd836d82c677de043c5f41 100644 +index 6e588cff05c8d6fdaec53a980fce1bc8d2141953..a173b1154e171d7fa5454b27baf85f72a09501a6 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp -@@ -11205,7 +11205,9 @@ bool PresShell::ComputeActiveness() const { +@@ -11063,7 +11063,9 @@ bool PresShell::ComputeActiveness() const { if (!browserChild->IsVisible()) { MOZ_LOG(gLog, LogLevel::Debug, (" > BrowserChild %p is not visible", browserChild)); @@ -2243,10 +2260,10 @@ index 7674abb3d07fb1958eed38e730be83a2b56f75f2..f88a1a3bd6f1df7ebbdd836d82c677de // 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 429ad335cc148353b4a094f8fb6eff0dfe48012a..8b45056e6f055aa00bb4e43f0f24e6cbf00d1335 100644 +index 2ed62888d70663f3560fcaa9bc29ff98cb44c323..f5540c38df6a064094e013c841d943c63049dd75 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp -@@ -713,6 +713,10 @@ bool nsLayoutUtils::AllowZoomingForDocument( +@@ -698,6 +698,10 @@ bool nsLayoutUtils::AllowZoomingForDocument( !aDocument->GetPresShell()->AsyncPanZoomEnabled()) { return false; } @@ -2257,7 +2274,7 @@ index 429ad335cc148353b4a094f8fb6eff0dfe48012a..8b45056e6f055aa00bb4e43f0f24e6cb // True if we allow zooming for all documents on this platform, or if we are // in RDM. BrowsingContext* bc = aDocument->GetBrowsingContext(); -@@ -9764,6 +9768,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont, +@@ -9794,6 +9798,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont, /* static */ bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) { @@ -2268,10 +2285,10 @@ index 429ad335cc148353b4a094f8fb6eff0dfe48012a..8b45056e6f055aa00bb4e43f0f24e6cb return StaticPrefs::dom_meta_viewport_enabled() || (bc && bc->InRDMPane()); } diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h -index 7bb839ae18835d128dc9285b7f9dc5b5e06335af..09e3979d07447522ace740daf2b818a6c551ceba 100644 +index d273793fc8d92b5c19ec0562730eab249cc41eb8..46b4078c6031318265a8338e01f52ab60bd9c0e8 100644 --- a/layout/style/GeckoBindings.h +++ b/layout/style/GeckoBindings.h -@@ -625,6 +625,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*); @@ -2280,7 +2297,7 @@ index 7bb839ae18835d128dc9285b7f9dc5b5e06335af..09e3979d07447522ace740daf2b818a6 const mozilla::dom::Document*); mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme( diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp -index fbdde4102dd7872d460760580e93657a6aeb07bd..d51a927f8d5c4788f10ae94543b1630e9dd4420a 100644 +index cc86d1abf6ccfe48530607c41cd675612cbe5582..8cce20c719fee8a0480ae6ea1fd53c6639d0bd7b 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp @@ -260,11 +260,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) { @@ -2301,10 +2318,10 @@ index fbdde4102dd7872d460760580e93657a6aeb07bd..d51a927f8d5c4788f10ae94543b1630e bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) { diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp -index 6be031113fb1197aa6eecbf6fc109147ca6ee5b1..f836dde97895d4b1177bfcebcaab6420abd855b8 100644 +index 5ff1c5ad8b265f25ab5a18a639e4e5b420d93443..a788218d4f281daee274d14b7dd15f4c19eeddce 100644 --- a/netwerk/base/LoadInfo.cpp +++ b/netwerk/base/LoadInfo.cpp -@@ -657,7 +657,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) +@@ -691,7 +691,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) mInterceptionInfo(rhs.mInterceptionInfo), mHasInjectedCookieForCookieBannerHandling( rhs.mHasInjectedCookieForCookieBannerHandling), @@ -2314,7 +2331,7 @@ index 6be031113fb1197aa6eecbf6fc109147ca6ee5b1..f836dde97895d4b1177bfcebcaab6420 } LoadInfo::LoadInfo( -@@ -2373,4 +2374,16 @@ LoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) { +@@ -2416,4 +2417,16 @@ LoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) { return NS_OK; } @@ -2332,10 +2349,10 @@ index 6be031113fb1197aa6eecbf6fc109147ca6ee5b1..f836dde97895d4b1177bfcebcaab6420 + } // namespace mozilla::net diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h -index a8631b09b26708ca10f683f1a9dd6b8467d3fe8e..a0bd72113e3539d815d32382946581ee62f39b6c 100644 +index e6badeeee816bc74af22fb9ef5f88b58f13ac5b7..994216ee9b26e7cbc85b948165051d5d2bc7efb1 100644 --- a/netwerk/base/LoadInfo.h +++ b/netwerk/base/LoadInfo.h -@@ -401,6 +401,8 @@ class LoadInfo final : public nsILoadInfo { +@@ -408,6 +408,8 @@ class LoadInfo final : public nsILoadInfo { bool mHasInjectedCookieForCookieBannerHandling = false; bool mWasSchemelessInput = false; @@ -2345,10 +2362,10 @@ index a8631b09b26708ca10f683f1a9dd6b8467d3fe8e..a0bd72113e3539d815d32382946581ee // 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 920e7623a7f912296fc23361f66ab35a30c35f1e..dfea0d0f7a72da9699615d7ff778e429e7ae40fb 100644 +index 48560a8b3be4ace3aab241373ff1eab0e5bb2187..b2114472b04b4e837b1c7b080ce8718f5f67f43b 100644 --- a/netwerk/base/TRRLoadInfo.cpp +++ b/netwerk/base/TRRLoadInfo.cpp -@@ -861,5 +861,15 @@ TRRLoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) { +@@ -870,5 +870,15 @@ TRRLoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -2365,10 +2382,10 @@ index 920e7623a7f912296fc23361f66ab35a30c35f1e..dfea0d0f7a72da9699615d7ff778e429 } // namespace net } // namespace mozilla diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl -index ddfcb223e6126c943b58e9d198f0e2fa767c3de1..4280b836c55e5778ad4c94226ea537facb19c436 100644 +index 8ff5e556c98689542297517a7bdf57e0a2ccf400..b1429dbe180cbc84cf467991bb24124f5857d62b 100644 --- a/netwerk/base/nsILoadInfo.idl +++ b/netwerk/base/nsILoadInfo.idl -@@ -1532,4 +1532,6 @@ interface nsILoadInfo : nsISupports +@@ -1544,4 +1544,6 @@ interface nsILoadInfo : nsISupports * Whether the load has gone through the URL bar, where the fixup had to add * the protocol scheme. */ [infallible] attribute boolean wasSchemelessInput; @@ -2376,7 +2393,7 @@ index ddfcb223e6126c943b58e9d198f0e2fa767c3de1..4280b836c55e5778ad4c94226ea537fa + [infallible] attribute unsigned long long jugglerLoadIdentifier; }; diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl -index 946cc95a8806644f88d38a7b1c7b9c0614e3cbff..a7b92019c5bbf356d1bbacfbff8fca9a041cf62a 100644 +index 7f91d2df6f8bb4020c75c132dc8f6bf26625fa1e..ba6569f4be8fc54ec96ee44d5de45a0904c077ba 100644 --- a/netwerk/base/nsINetworkInterceptController.idl +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -59,6 +59,7 @@ interface nsIInterceptedChannel : nsISupports @@ -2388,10 +2405,10 @@ index 946cc95a8806644f88d38a7b1c7b9c0614e3cbff..a7b92019c5bbf356d1bbacfbff8fca9a /** * Set the status and reason for the forthcoming synthesized response. diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp -index 32d7036ff18828ec8938a8dc0a98ad3297132706..65585e719dda8f62319a5fc34baf69c59e51162c 100644 +index dfd80e8867ec46464ddcfc30316236c824950cb1..a702bbe63cf56984519000854e9f487dcac3cee4 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp -@@ -167,6 +167,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, +@@ -168,6 +168,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, loadInfo->SetHasValidUserGestureActivation( aLoadState->HasValidUserGestureActivation()); loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); @@ -2400,7 +2417,7 @@ index 32d7036ff18828ec8938a8dc0a98ad3297132706..65585e719dda8f62319a5fc34baf69c5 return loadInfo.forget(); } diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp -index c58fbc96391f8dcb585bd00b5ae8cba9088abd82..c121c891b61c9d60df770020c4ad09521d2bbfe6 100644 +index 5695d46f924abe6b765f3645d746cc4248051c1c..d28ead55f6a8458f70ca43c693e7396c5dc53858 100644 --- a/netwerk/protocol/http/InterceptedHttpChannel.cpp +++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp @@ -727,6 +727,14 @@ NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor) @@ -2438,10 +2455,10 @@ index c58fbc96391f8dcb585bd00b5ae8cba9088abd82..c121c891b61c9d60df770020c4ad0952 if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { mPump->PeekStream(CallTypeSniffers, static_cast(this)); diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp -index f2c47c42a6b6448ede3a6fef1510a3982336d5af..9e35df57102c93238de2e4d548bbe1205d227f3b 100644 +index f25949e6cc907ff18a76d68fc2e8005bd40146ea..9be4cb34517b06b94c6e145aef8a8ea5d2687d97 100644 --- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp -@@ -1382,6 +1382,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( +@@ -1389,6 +1389,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); @@ -2453,10 +2470,10 @@ index f2c47c42a6b6448ede3a6fef1510a3982336d5af..9e35df57102c93238de2e4d548bbe120 nsCOMPtr preloadCsp = mDocument->GetPreloadCsp(); if (!preloadCsp) { diff --git a/security/manager/ssl/nsCertOverrideService.cpp b/security/manager/ssl/nsCertOverrideService.cpp -index b8d0bbc3a12f74c19284b26eadada361abeb7946..a50379c57158748684e2ea5065550af81c1e9b86 100644 +index fcc2a45e6de8eaeb1af2404a69bd3df58cf2aec8..d4c1df007bf5993cf9e0dadbe91aa2c38afc42ec 100644 --- a/security/manager/ssl/nsCertOverrideService.cpp +++ b/security/manager/ssl/nsCertOverrideService.cpp -@@ -438,7 +438,12 @@ nsCertOverrideService::HasMatchingOverride( +@@ -437,7 +437,12 @@ nsCertOverrideService::HasMatchingOverride( bool disableAllSecurityCheck = false; { MutexAutoLock lock(mMutex); @@ -2470,7 +2487,7 @@ index b8d0bbc3a12f74c19284b26eadada361abeb7946..a50379c57158748684e2ea5065550af8 } if (disableAllSecurityCheck) { *aIsTemporary = false; -@@ -650,14 +655,24 @@ static bool IsDebugger() { +@@ -649,14 +654,24 @@ static bool IsDebugger() { NS_IMETHODIMP nsCertOverrideService:: @@ -2573,7 +2590,7 @@ index df1c5e464b845b6a8bfedadb86d0e7aab7fd3ffc..34451e791bb59f635134de702d9e5f64 } diff --git a/toolkit/components/browser/nsIWebBrowserChrome.idl b/toolkit/components/browser/nsIWebBrowserChrome.idl -index 517c87a285dd93220cb2654d6ba7bb05c66cdeb7..e3010421d8dbe5ed5ed72feedb9f15805b32503e 100644 +index 217beda78edf31bab4c37209964d7a5bf5425195..7ba723410eb93328a8f078c58a96eefc2599feea 100644 --- a/toolkit/components/browser/nsIWebBrowserChrome.idl +++ b/toolkit/components/browser/nsIWebBrowserChrome.idl @@ -74,6 +74,9 @@ interface nsIWebBrowserChrome : nsISupports @@ -2603,6 +2620,19 @@ index 00a5381133f8cec0de452c31c7151801a1acc0b9..5d3e3d6f566dc724f257beaeb994ceda let provider = this._chooseProvider(); if (provider.failed) { +diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp +index 32b1ac481382dd6aa3dda5572f013c2447a1a004..808031fbeb9b99b67c13c99c66b1aa1aff41f48a 100644 +--- a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp ++++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp +@@ -525,7 +525,7 @@ void PopulateLanguages() { + // sufficient to only collect this information as the other properties are + // just reformats of Navigator::GetAcceptLanguages. + nsTArray languages; +- dom::Navigator::GetAcceptLanguages(languages); ++ dom::Navigator::GetAcceptLanguages(nullptr, languages); + nsCString output = "["_ns; + + for (const auto& language : languages) { diff --git a/toolkit/components/startup/nsAppStartup.cpp b/toolkit/components/startup/nsAppStartup.cpp index 3314cb813f6ceb67096eeda0864ad3b16c0616cb..5aac63649e186d624a9905a5d16513f8353f5515 100644 --- a/toolkit/components/startup/nsAppStartup.cpp @@ -2632,10 +2662,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 b482214d31fe2a50ad075b424bc3f97edbc5148b..bd93fd73f165ef1da6422c6b8f141f0a82d77f0f 100644 +index 0767cb1539f940e5f634b58de44d876606903a09..dc0d72b4ff36d5ba7808528aefecb33f05b6672c 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp -@@ -1853,7 +1853,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( +@@ -1861,7 +1861,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( // Open a minimal popup. *aIsPopupRequested = true; @@ -2649,20 +2679,20 @@ index b482214d31fe2a50ad075b424bc3f97edbc5148b..bd93fd73f165ef1da6422c6b8f141f0a /** diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs -index 34bf1b9e19ae54f78d134b023af96820bc13a905..b85793edcd1996833420a026cb08706d648ece14 100644 +index deaed885c759d8e53ebf0beb53c5b7c4d4bd82f0..8e01e16490ab063361220d363494dfdf00442342 100644 --- a/toolkit/mozapps/update/UpdateService.sys.mjs +++ b/toolkit/mozapps/update/UpdateService.sys.mjs -@@ -3852,6 +3852,8 @@ UpdateService.prototype = { - }, +@@ -3875,6 +3875,8 @@ export class UpdateService { + } get disabledForTesting() { + /* playwright */ + return true; - return ( - (Cu.isInAutomation || - lazy.Marionette.running || + return lazy.UpdateServiceStub.updateDisabledForTesting; + } + diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild -index b697eb1e3b02b0ffcc95a6e492dc23eb888488cc..0f4059341dbb3c2ecb2c46be0850e0d56e2a7453 100644 +index 8c2b2bf996bd889651dc7fac1dc351b4c47b567e..07d237eb17a657ce051fd0aa5e53449c0c3159f6 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -155,6 +155,7 @@ if CONFIG["ENABLE_WEBDRIVER"]: @@ -2726,7 +2756,7 @@ index fe72a2715da8846146377e719559c16e6ef1f7ff..a5959143bac8f62ee359fa3883a844f3 // 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 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295ea050b878 100644 +index b9120ededd25707c90f33f65d3cead26433efdac..4d5728a73786d804d6b32da4d42934da2864eda1 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -112,6 +112,7 @@ @@ -2750,7 +2780,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e nsresult nsExternalHelperAppService::GetFileTokenForPath( const char16_t* aPlatformAppPath, nsIFile** aFile) { nsDependentString platformAppPath(aPlatformAppPath); -@@ -1442,7 +1449,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) { +@@ -1441,7 +1448,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) { // Strip off the ".part" from mTempLeafName mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1); @@ -2763,7 +2793,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); -@@ -1631,7 +1643,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { +@@ -1630,7 +1642,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { return NS_OK; } @@ -2801,7 +2831,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e if (NS_FAILED(rv)) { nsresult transferError = rv; -@@ -1683,6 +1724,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { +@@ -1682,6 +1723,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { bool alwaysAsk = true; mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); @@ -2811,7 +2841,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e if (alwaysAsk) { // But we *don't* ask if this mimeInfo didn't come from // our user configuration datastore and the user has said -@@ -2199,6 +2243,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver, +@@ -2198,6 +2242,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver, NotifyTransfer(aStatus); } @@ -2828,7 +2858,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e return NS_OK; } -@@ -2680,6 +2734,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) { +@@ -2679,6 +2733,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) { } } @@ -2987,7 +3017,7 @@ diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings. index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce8050630a1aa 100644 --- a/widget/cocoa/NativeKeyBindings.mm +++ b/widget/cocoa/NativeKeyBindings.mm -@@ -528,6 +528,13 @@ void NativeKeyBindings::GetEditCommandsForTests( +@@ -528,6 +528,13 @@ break; case KEY_NAME_INDEX_ArrowLeft: if (aEvent.IsAlt()) { @@ -3001,7 +3031,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 break; } if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) { -@@ -550,6 +557,13 @@ void NativeKeyBindings::GetEditCommandsForTests( +@@ -550,6 +557,13 @@ break; case KEY_NAME_INDEX_ArrowRight: if (aEvent.IsAlt()) { @@ -3015,7 +3045,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 break; } if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) { -@@ -572,6 +586,10 @@ void NativeKeyBindings::GetEditCommandsForTests( +@@ -572,6 +586,10 @@ break; case KEY_NAME_INDEX_ArrowUp: if (aEvent.IsControl()) { @@ -3026,7 +3056,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 break; } if (aEvent.IsMeta()) { -@@ -582,7 +600,7 @@ void NativeKeyBindings::GetEditCommandsForTests( +@@ -582,7 +600,7 @@ !aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveToBeginningOfDocument:)) : ToObjcSelectorPtr( @@ -3035,7 +3065,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805 aCommands); break; } -@@ -609,6 +627,10 @@ void NativeKeyBindings::GetEditCommandsForTests( +@@ -609,6 +627,10 @@ break; case KEY_NAME_INDEX_ArrowDown: if (aEvent.IsControl()) { @@ -3258,10 +3288,10 @@ index 8ba46829357fc4acc47bf20842fd869902efa000..a1b5b2c5230d90981bd563d4df2d2bf1 } }; diff --git a/xpcom/reflect/xptinfo/xptinfo.h b/xpcom/reflect/xptinfo/xptinfo.h -index 2456c2c2b58b27cd595880b547ed20fb687a1835..e967c089b2331c7cd36d34e511543fbc84320b7d 100644 +index 787d30d881adedd57d2025ca57bff4bc6c57e803..ae1a0172c960ab16919133485722d2ae0cdbcbd4 100644 --- a/xpcom/reflect/xptinfo/xptinfo.h +++ b/xpcom/reflect/xptinfo/xptinfo.h -@@ -514,7 +514,7 @@ static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size"); +@@ -505,7 +505,7 @@ static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size"); #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) # define PARAM_BUFFER_COUNT 18 #else diff --git a/browser_patches/webkit/UPSTREAM_CONFIG.sh b/browser_patches/webkit/UPSTREAM_CONFIG.sh index 8127a24878..c90cb30712 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="a47deb713746fa2f228e8450a52ed0ecafc5309d" +BASE_REVISION="f371dbc2bb4292037ed394e2162150a16ef977fc" diff --git a/browser_patches/webkit/patches/bootstrap.diff b/browser_patches/webkit/patches/bootstrap.diff index 84061248f1..9b8aa596c4 100644 --- a/browser_patches/webkit/patches/bootstrap.diff +++ b/browser_patches/webkit/patches/bootstrap.diff @@ -1,8 +1,8 @@ diff --git a/Source/JavaScriptCore/CMakeLists.txt b/Source/JavaScriptCore/CMakeLists.txt -index 3c0295bc0b5f5d417beee189e208f3582b54fdb9..65176f0a49628933887a06690217bfcc5d34f395 100644 +index 3a1013bab702f71303ee800f6b3e9a65a08f4de6..9448f06498ea591e51516d5ef7b543f63655b6ae 100644 --- a/Source/JavaScriptCore/CMakeLists.txt +++ b/Source/JavaScriptCore/CMakeLists.txt -@@ -1389,22 +1389,27 @@ set(JavaScriptCore_INSPECTOR_DOMAINS +@@ -1392,22 +1392,27 @@ set(JavaScriptCore_INSPECTOR_DOMAINS ${JAVASCRIPTCORE_DIR}/inspector/protocol/CSS.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/Canvas.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/Console.json @@ -62,7 +62,7 @@ index b26ef7b2b8f732160ddee36697a61ca7776fc2c3..9a442a4cda7efd7f2bd4e225d8bcbfed $(PROJECT_DIR)/inspector/protocol/Security.json $(PROJECT_DIR)/inspector/protocol/ServiceWorker.json diff --git a/Source/JavaScriptCore/DerivedSources.make b/Source/JavaScriptCore/DerivedSources.make -index 15d6e32cd525905b116f25a5bdd50bd1ef390cee..74b1415d21dcddc49e842e077e87e06724af8365 100644 +index 3033ef7fc7e1db0e4b6fb85236997ce92b1946a5..87fc65714ad2caa21ac19aabf0a7d93badb4c848 100644 --- a/Source/JavaScriptCore/DerivedSources.make +++ b/Source/JavaScriptCore/DerivedSources.make @@ -298,22 +298,27 @@ INSPECTOR_DOMAINS := \ @@ -94,7 +94,7 @@ index 15d6e32cd525905b116f25a5bdd50bd1ef390cee..74b1415d21dcddc49e842e077e87e067 $(JavaScriptCore)/inspector/protocol/ServiceWorker.json \ $(JavaScriptCore)/inspector/protocol/Target.json \ diff --git a/Source/JavaScriptCore/inspector/IdentifiersFactory.cpp b/Source/JavaScriptCore/inspector/IdentifiersFactory.cpp -index 528cceee66a1b1c91a0d0e59d5f1a1770a050c17..1e12d22fd2ce5363068ce7f5725f6e76ef7e8ad8 100644 +index 9bc5d1fd8e2a7e576be046b3c6ae1266696cf552..610f810db1dd6865c500c0796386a8284f4178e9 100644 --- a/Source/JavaScriptCore/inspector/IdentifiersFactory.cpp +++ b/Source/JavaScriptCore/inspector/IdentifiersFactory.cpp @@ -32,14 +32,21 @@ @@ -133,10 +133,10 @@ index eb25aedee4cd9ebe007e06c2515b37ee095b06f4..badf6559595c8377db1089ca3c25008e static String requestId(unsigned long identifier); }; diff --git a/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp b/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp -index b9296c03d37fa75ec9b6349dca0dd1862df5d348..96e14d95cd24c80a40e9ce4a528db6fe1cfcc534 100644 +index 143135027999755614d5fae2b897d8dd5e62d611..a357963f3350d29ea0fd90a20ac2393ff6a0efa9 100644 --- a/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp +++ b/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp -@@ -84,7 +84,10 @@ static RefPtr jsToInspectorValue(JSC::JSGlobalObject* globalObject, +@@ -85,7 +85,10 @@ static RefPtr jsToInspectorValue(JSC::JSGlobalObject* globalObject, JSC::PropertyNameArray propertyNames(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); object.methodTable()->getOwnPropertyNames(&object, globalObject, propertyNames, JSC::DontEnumPropertiesMode::Exclude); for (auto& name : propertyNames) { @@ -149,10 +149,10 @@ index b9296c03d37fa75ec9b6349dca0dd1862df5d348..96e14d95cd24c80a40e9ce4a528db6fe return nullptr; inspectorObject->setValue(name.string(), inspectorValue.releaseNonNull()); diff --git a/Source/JavaScriptCore/inspector/InspectorBackendDispatcher.cpp b/Source/JavaScriptCore/inspector/InspectorBackendDispatcher.cpp -index 80f5a61e477022fae12df0ba4f727e7076012fe3..7e513ea23a9a9ca0800ea21a11e51fad3417f1d2 100644 +index 3b2056bf5a71301383e0895d77cc6f16842235a9..eaf73171ce70ac43abbe404122a263ce708bb41f 100644 --- a/Source/JavaScriptCore/inspector/InspectorBackendDispatcher.cpp +++ b/Source/JavaScriptCore/inspector/InspectorBackendDispatcher.cpp -@@ -99,7 +99,7 @@ void BackendDispatcher::registerDispatcherForDomain(const String& domain, Supple +@@ -100,7 +100,7 @@ void BackendDispatcher::registerDispatcherForDomain(const String& domain, Supple m_dispatchers.set(domain, dispatcher); } @@ -161,7 +161,7 @@ index 80f5a61e477022fae12df0ba4f727e7076012fe3..7e513ea23a9a9ca0800ea21a11e51fad { Ref protect(*this); -@@ -144,6 +144,9 @@ void BackendDispatcher::dispatch(const String& message) +@@ -145,6 +145,9 @@ void BackendDispatcher::dispatch(const String& message) requestId = *requestIdInt; } @@ -239,10 +239,10 @@ index 082dd93cb0505c5bc7a2d5a7cf78fa0306809082..3f50f49ef9e691b3dc57dafd66c1e7d3 bool m_isPaused { false }; }; diff --git a/Source/JavaScriptCore/inspector/JSGlobalObjectConsoleClient.cpp b/Source/JavaScriptCore/inspector/JSGlobalObjectConsoleClient.cpp -index 15d2592d36b402bf2210dc4970ff40957022e84a..99ac14dc75ff5296e1f6c42bdbf8c128f2d82254 100644 +index c33e236f5228e21c6d5e0ea9bd97d07cdcb70640..7f160aec0f13e8c936aa7dea769d4e160d716452 100644 --- a/Source/JavaScriptCore/inspector/JSGlobalObjectConsoleClient.cpp +++ b/Source/JavaScriptCore/inspector/JSGlobalObjectConsoleClient.cpp -@@ -221,6 +221,14 @@ void JSGlobalObjectConsoleClient::screenshot(JSGlobalObject*, Ref&&); diff --git a/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp b/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp -index 594584db96a3de67b92910f472f1403bc9b26ce2..8ba453f9ae8726ccfb26c55180371c20bed6962e 100644 +index 0cb6efeef2430faa5dbd812f71d4abfd5f6eb9df..787ec6a5f8413c0a9dc133cb0e51ccdab58d40d0 100644 --- a/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp +++ b/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp @@ -194,9 +194,8 @@ void InspectorRuntimeAgent::callFunctionOn(const Protocol::Runtime::RemoteObject @@ -598,10 +598,10 @@ index 0000000000000000000000000000000000000000..8377901cb3ad75c29532a1f0f547efb5 +} diff --git a/Source/JavaScriptCore/inspector/protocol/Input.json b/Source/JavaScriptCore/inspector/protocol/Input.json new file mode 100644 -index 0000000000000000000000000000000000000000..b9ab57a2b5739ed997231399b4bd4042a0cb0935 +index 0000000000000000000000000000000000000000..1c43b476603325fa412bcfded9163e7a00aebbfa --- /dev/null +++ b/Source/JavaScriptCore/inspector/protocol/Input.json -@@ -0,0 +1,223 @@ +@@ -0,0 +1,264 @@ +{ + "domain": "Input", + "availability": ["web"], @@ -610,6 +610,16 @@ index 0000000000000000000000000000000000000000..b9ab57a2b5739ed997231399b4bd4042 + "id": "TimeSinceEpoch", + "description": "UTC time in seconds, counted from January 1, 1970.", + "type": "number" ++ }, ++ { ++ "id": "TouchPoint", ++ "type": "object", ++ "description": "Touch point.", ++ "properties": [ ++ { "name": "x", "type": "integer", "description": "X coordinate of the event relative to the main frame's viewport in CSS pixels." }, ++ { "name": "y", "type": "integer", "description": "Y coordinate of the event relative to the main frame's viewport in CSS pixels." }, ++ { "name": "id", "type": "integer", "description": "Identifier used to track touch sources between events, must be unique within an event." } ++ ] + } + ], + "commands": [ @@ -822,6 +832,37 @@ index 0000000000000000000000000000000000000000..b9ab57a2b5739ed997231399b4bd4042 + "type": "integer" + } + ] ++ }, ++ { ++ "name": "dispatchTouchEvent", ++ "description": "Dispatches a touch event to the page.", ++ "async": true, ++ "parameters": [ ++ { ++ "name": "type", ++ "description": "Type of the touch event.", ++ "type": "string", ++ "enum": [ ++ "touchStart", ++ "touchMove", ++ "touchEnd", ++ "touchCancel" ++ ] ++ }, ++ { ++ "name": "modifiers", ++ "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).", ++ "optional": true, ++ "type": "integer" ++ }, ++ { ++ "name": "touchPoints", ++ "description": "List of touch points", ++ "type": "array", ++ "optional": true, ++ "items": { "$ref": "TouchPoint" } ++ } ++ ] + } + ] +} @@ -1660,18 +1701,10 @@ index 24891ad836086fd23024fcb4d08ca63f6974c812..29f4b6b1923383fec7a99d28a4e815dc private: enum ArgumentRequirement { ArgumentRequired, ArgumentNotRequired }; diff --git a/Source/ThirdParty/libwebrtc/CMakeLists.txt b/Source/ThirdParty/libwebrtc/CMakeLists.txt -index edfc7022920e6fd9300b5b1a58a1c161096c0aa5..7f68528deb95c970c030c9effd0bdea843c67624 100644 +index a17275db76e30bf2f42bc8faa6dea14383b2ab27..f630ddaddfa5a5b048e028c233e84e8bec1e0370 100644 --- a/Source/ThirdParty/libwebrtc/CMakeLists.txt +++ b/Source/ThirdParty/libwebrtc/CMakeLists.txt -@@ -60,7 +60,6 @@ set(webrtc_SOURCES - Source/third_party/abseil-cpp/absl/debugging/stacktrace.cc - Source/third_party/abseil-cpp/absl/debugging/symbolize.cc - Source/third_party/abseil-cpp/absl/flags/commandlineflag.cc -- Source/third_party/abseil-cpp/absl/flags/flag.cc - Source/third_party/abseil-cpp/absl/flags/flag_test_defs.cc - Source/third_party/abseil-cpp/absl/flags/internal/commandlineflag.cc - Source/third_party/abseil-cpp/absl/flags/internal/flag.cc -@@ -455,6 +454,7 @@ set(webrtc_SOURCES +@@ -453,6 +453,7 @@ set(webrtc_SOURCES Source/third_party/boringssl/src/crypto/x509/x_val.c Source/third_party/boringssl/src/crypto/x509/x_x509a.c Source/third_party/boringssl/src/crypto/x509/x_x509.c @@ -1679,7 +1712,7 @@ index edfc7022920e6fd9300b5b1a58a1c161096c0aa5..7f68528deb95c970c030c9effd0bdea8 Source/third_party/boringssl/src/decrepit/bio/base64_bio.c Source/third_party/boringssl/src/decrepit/blowfish/blowfish.c Source/third_party/boringssl/src/decrepit/cast/cast.c -@@ -533,6 +533,11 @@ set(webrtc_SOURCES +@@ -532,6 +533,11 @@ set(webrtc_SOURCES Source/third_party/crc32c/src/src/crc32c.cc Source/third_party/crc32c/src/src/crc32c_portable.cc Source/third_party/crc32c/src/src/crc32c_sse42.cc @@ -1691,7 +1724,7 @@ index edfc7022920e6fd9300b5b1a58a1c161096c0aa5..7f68528deb95c970c030c9effd0bdea8 Source/third_party/libyuv/source/compare.cc Source/third_party/libyuv/source/compare_common.cc Source/third_party/libyuv/source/compare_gcc.cc -@@ -2404,6 +2409,10 @@ set(webrtc_INCLUDE_DIRECTORIES PRIVATE +@@ -2402,6 +2408,10 @@ set(webrtc_INCLUDE_DIRECTORIES PRIVATE Source/third_party/libsrtp/config Source/third_party/libsrtp/crypto/include Source/third_party/libsrtp/include @@ -1703,26 +1736,25 @@ index edfc7022920e6fd9300b5b1a58a1c161096c0aa5..7f68528deb95c970c030c9effd0bdea8 Source/third_party/opus/src/celt Source/third_party/opus/src/include diff --git a/Source/ThirdParty/libwebrtc/Configurations/Base-libwebrtc.xcconfig b/Source/ThirdParty/libwebrtc/Configurations/Base-libwebrtc.xcconfig -index 5155536dddb0d3627a021a28e6d733fde5ffbf1b..02c8aad77cd735d7e3cc5f072d62cc50c5b9aace 100644 +index 6b20d97d3d46359b2b2f9b4e8454a65c2ddbe9e3..80883fe3659389a3c385fd46ecd905bc0923d3ef 100644 --- a/Source/ThirdParty/libwebrtc/Configurations/Base-libwebrtc.xcconfig +++ b/Source/ThirdParty/libwebrtc/Configurations/Base-libwebrtc.xcconfig -@@ -21,7 +21,7 @@ - // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +@@ -22,6 +22,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --HEADER_SEARCH_PATHS = Source Source/third_party/libsrtp/crypto/include Source/third_party/libsrtp/include Source/third_party/boringssl/src/include Source/third_party/libyuv/include Source/webrtc/sdk/objc/Framework/Headers Source/webrtc/common_audio/signal_processing/include Source/webrtc/modules/audio_coding/codecs/isac/main/include Source/third_party/opus/src/celt Source/third_party/opus/src/include Source/third_party/opus/src/src Source/webrtc/modules/audio_device/mac Source/webrtc/modules/audio_device/ios Source/webrtc Source/webrtc/sdk/objc Source/webrtc/sdk/objc/base Source/webrtc/sdk/objc/Framework/Classes Source/third_party/libsrtp/config Source/webrtc/sdk/objc/Framework/Classes/Common Source/webrtc/sdk/objc/Framework/Classes/Video Source/webrtc/sdk/objc/Framework/Classes/PeerConnection Source/third_party/abseil-cpp Source/third_party/libvpx/source/libvpx Source/third_party/libwebm/webm_parser/include Source/third_party/crc32c/config Source/third_party/crc32c/include Source/third_party/crc32c/src/include Source/third_party/libaom/source/libaom; -+HEADER_SEARCH_PATHS = Source Source/third_party/libsrtp/crypto/include Source/third_party/libsrtp/include Source/third_party/boringssl/src/include Source/third_party/libyuv/include Source/webrtc/sdk/objc/Framework/Headers Source/webrtc/common_audio/signal_processing/include Source/webrtc/modules/audio_coding/codecs/isac/main/include Source/third_party/opus/src/celt Source/third_party/opus/src/include Source/third_party/opus/src/src Source/webrtc/modules/audio_device/mac Source/webrtc/modules/audio_device/ios Source/webrtc Source/webrtc/sdk/objc Source/webrtc/sdk/objc/base Source/webrtc/sdk/objc/Framework/Classes Source/third_party/libsrtp/config Source/webrtc/sdk/objc/Framework/Classes/Common Source/webrtc/sdk/objc/Framework/Classes/Video Source/webrtc/sdk/objc/Framework/Classes/PeerConnection Source/third_party/abseil-cpp Source/third_party/libvpx/source/libvpx Source/third_party/libwebm/webm_parser/include Source/third_party/crc32c/config Source/third_party/crc32c/include Source/third_party/crc32c/src/include Source/third_party/libaom/source/libaom Source/third_party/libwebm/mkvmuxer Source/third_party/libvpx/source/libvpx/third_party/libwebm; + HEADER_SEARCH_PATHS = Source Source/third_party/libsrtp/crypto/include Source/third_party/libsrtp/include Source/third_party/boringssl/src/include Source/third_party/libyuv/include Source/webrtc/sdk/objc/Framework/Headers Source/webrtc/common_audio/signal_processing/include Source/webrtc/modules/audio_coding/codecs/isac/main/include Source/third_party/opus/src/celt Source/third_party/opus/src/include Source/third_party/opus/src/src Source/webrtc/modules/audio_device/mac Source/webrtc/modules/audio_device/ios Source/webrtc Source/webrtc/sdk/objc Source/webrtc/sdk/objc/base Source/webrtc/sdk/objc/Framework/Classes Source/third_party/libsrtp/config Source/webrtc/sdk/objc/Framework/Classes/Common Source/webrtc/sdk/objc/Framework/Classes/Video Source/webrtc/sdk/objc/Framework/Classes/PeerConnection Source/third_party/abseil-cpp Source/third_party/libvpx/source/libvpx Source/third_party/libwebm/webm_parser/include Source/third_party/crc32c/config Source/third_party/crc32c/include Source/third_party/crc32c/src/include Source/third_party/libaom/source/libaom Source/third_party/protobuf/src; ++HEADER_SEARCH_PATHS = ${HEADER_SEARCH_PATHS} Source/third_party/libwebm/mkvmuxer Source/third_party/libvpx/source/libvpx/third_party/libwebm; USE_HEADERMAP = NO; WARNING_CFLAGS = -Wno-deprecated-declarations $(inherited); diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp -index a01284369bedab3d44e7a3c2671590b3ca2b18fc..6a75fd2d366386e12dc612bdcfb914613c636478 100644 +index fca61ffe9f0563d87b364e0fa681ceab1d7bb26f..5c0d72f6c0bab0bc0011a123302c5654ec5664ba 100644 --- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp +++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp -@@ -402,3 +402,24 @@ __ZN3rtc7LogSink12OnLogMessageENSt3__117basic_string_viewIcNS1_11char_traitsIcEE - __ZN3rtc7LogSink12OnLogMessageERKNS_10LogLineRefE - __ZN3rtc7LogSink12OnLogMessageERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS_15LoggingSeverityEPKc - __ZTVN3rtc7LogSinkE +@@ -403,3 +403,24 @@ __ZN3rtc17AsyncPacketSocket20NotifyPacketReceivedERKNS_14ReceivedPacketE + __ZN3rtc17AsyncPacketSocket30RegisterReceivedPacketCallbackEN4absl12AnyInvocableIFvPS0_RKNS_14ReceivedPacketEEEE + __ZN3rtc17AsyncPacketSocket32DeregisterReceivedPacketCallbackEv + __ZN3rtc18NetworkManagerBaseC2Ev +__ZN8mkvmuxer11SegmentInfo15set_writing_appEPKc +__ZN8mkvmuxer11SegmentInfo4InitEv +__ZN8mkvmuxer7Segment10OutputCuesEb @@ -1769,7 +1801,7 @@ index f95c3b6c6b73a01974f26d88bcc533e5032ddb66..6a9368c60824cd32649c93286522d779 #include "api/array_view.h" diff --git a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj -index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6a981a2a0 100644 +index 5e185d2c3aae7eb99b72dbbb1a04af638a6dfc63..d2fbb40ee5d8349d617b287368ae1a47a131e4ee 100644 --- a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj +++ b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj @@ -35,6 +35,20 @@ @@ -1793,7 +1825,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 /* 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, ); }; }; -@@ -5135,6 +5149,9 @@ +@@ -5193,6 +5207,9 @@ DDF30D9127C5C725006A526F /* receive_side_congestion_controller.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF30D9027C5C725006A526F /* receive_side_congestion_controller.h */; }; DDF30D9527C5C756006A526F /* bwe_defines.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF30D9327C5C756006A526F /* bwe_defines.h */; }; DDF30D9627C5C756006A526F /* remote_bitrate_estimator.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF30D9427C5C756006A526F /* remote_bitrate_estimator.h */; }; @@ -1803,7 +1835,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ -@@ -5623,6 +5640,13 @@ +@@ -5681,6 +5698,13 @@ remoteGlobalIDString = DDF30D0527C5C003006A526F; remoteInfo = absl; }; @@ -1817,7 +1849,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ -@@ -11200,6 +11224,9 @@ +@@ -11465,6 +11489,9 @@ DDF30D9027C5C725006A526F /* receive_side_congestion_controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = receive_side_congestion_controller.h; sourceTree = ""; }; DDF30D9327C5C756006A526F /* bwe_defines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bwe_defines.h; sourceTree = ""; }; DDF30D9427C5C756006A526F /* remote_bitrate_estimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = remote_bitrate_estimator.h; sourceTree = ""; }; @@ -1827,7 +1859,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 FB39D0D11200F0E300088E69 /* libwebrtc.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libwebrtc.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ -@@ -20044,6 +20071,7 @@ +@@ -20596,6 +20623,7 @@ isa = PBXGroup; children = ( CDFD2F9224C4B2F90048DAC3 /* common */, @@ -1835,7 +1867,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 CDEBB19224C0191800ADBD44 /* webm_parser */, ); path = libwebm; -@@ -20471,6 +20499,16 @@ +@@ -21007,6 +21035,16 @@ path = include; sourceTree = ""; }; @@ -1852,7 +1884,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 FB39D06E1200ED9200088E69 = { isa = PBXGroup; children = ( -@@ -23761,6 +23799,7 @@ +@@ -24293,6 +24331,7 @@ ); dependencies = ( 410B3827292B73E90003E515 /* PBXTargetDependency */, @@ -1860,7 +1892,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 DD2E76E827C6B69A00F2A74C /* PBXTargetDependency */, CDEBB4CC24C01AB400ADBD44 /* PBXTargetDependency */, 411ED040212E0811004320BA /* PBXTargetDependency */, -@@ -23843,6 +23882,7 @@ +@@ -24375,6 +24414,7 @@ 4460B8B92B155B6A00392062 /* vp9_qp_parser_fuzzer */, 444A6EF02AEADFC9005FE121 /* vp9_replay_fuzzer */, 44945C512B9BA1C300447FFD /* webm_fuzzer */, @@ -1868,7 +1900,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 ); }; /* End PBXProject section */ -@@ -23926,6 +23966,23 @@ +@@ -24458,6 +24498,23 @@ shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Scripts/create-symlink-to-altroot.sh\"\n"; }; @@ -1892,7 +1924,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ -@@ -25901,6 +25958,9 @@ +@@ -26471,6 +26528,9 @@ 5CDD865E1E43B8B500621E92 /* min_max_operations.c in Sources */, 4189395B242A71F5007FDC41 /* min_video_bitrate_experiment.cc in Sources */, 41B8D8FB28CB85CB00E5FA37 /* missing_mandatory_parameter_cause.cc in Sources */, @@ -1902,7 +1934,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 4131C387234B957D0028A615 /* moving_average.cc in Sources */, 41FCBB1521B1F7AA00A5DF27 /* moving_average.cc in Sources */, 5CD286101E6A64C90094FDC8 /* moving_max.cc in Sources */, -@@ -26778,6 +26838,11 @@ +@@ -27372,6 +27432,11 @@ target = DDF30D0527C5C003006A526F /* absl */; targetProxy = DD2E76E727C6B69A00F2A74C /* PBXContainerItemProxy */; }; @@ -1914,7 +1946,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ -@@ -27353,6 +27418,27 @@ +@@ -27947,6 +28012,27 @@ }; name = Production; }; @@ -1942,7 +1974,7 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 FB39D0711200ED9200088E69 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D7C59C71208C68B001C873E /* DebugRelease.xcconfig */; -@@ -27655,6 +27741,16 @@ +@@ -28249,6 +28335,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Production; }; @@ -1960,10 +1992,10 @@ index c25fd16f75eacc283d613509f3232b8de6ddde40..01ec8f8556149cf22a5304771a0b61b6 isa = XCConfigurationList; buildConfigurations = ( diff --git a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml -index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfba0791949 100644 +index 4d6f1829d52f1a0ea19c1a0de58e86304360c177..59a7410430ecc8db82f5b0bfcc11ba045b1f4aec 100644 --- a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml +++ b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml -@@ -562,6 +562,7 @@ ApplePayEnabled: +@@ -563,6 +563,7 @@ ApplePayEnabled: default: false # FIXME: This is on by default in WebKit2 PLATFORM(COCOA). Perhaps we should consider turning it on for WebKitLegacy as well. @@ -1971,7 +2003,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb AsyncClipboardAPIEnabled: type: bool status: mature -@@ -572,7 +573,7 @@ AsyncClipboardAPIEnabled: +@@ -573,7 +574,7 @@ AsyncClipboardAPIEnabled: default: false WebKit: "PLATFORM(COCOA) || PLATFORM(GTK)" : true @@ -1980,28 +2012,15 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb WebCore: default: false -@@ -1781,9 +1782,10 @@ CrossOriginEmbedderPolicyEnabled: +@@ -1810,6 +1811,7 @@ CrossOriginEmbedderPolicyEnabled: WebCore: default: false +# Playwright: disable setting. CrossOriginOpenerPolicyEnabled: type: bool -- status: stable -+ status: preview - category: security - humanReadableName: "Cross-Origin-Opener-Policy (COOP) header" - humanReadableDescription: "Support for Cross-Origin-Opener-Policy (COOP) header" -@@ -1791,7 +1793,7 @@ CrossOriginOpenerPolicyEnabled: - WebKitLegacy: - default: false - WebKit: -- default: true -+ default: false - WebCore: - default: false - -@@ -1835,7 +1837,7 @@ CustomPasteboardDataEnabled: + status: stable +@@ -1864,7 +1866,7 @@ CustomPasteboardDataEnabled: WebKitLegacy: default: false WebKit: @@ -2010,7 +2029,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb default: false CustomStateSetEnabled: -@@ -1894,6 +1896,7 @@ DOMAudioSessionFullEnabled: +@@ -1923,6 +1925,7 @@ DOMAudioSessionFullEnabled: WebCore: default: false @@ -2018,7 +2037,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb DOMPasteAccessRequestsEnabled: type: bool status: internal -@@ -1905,7 +1908,7 @@ DOMPasteAccessRequestsEnabled: +@@ -1934,7 +1937,7 @@ DOMPasteAccessRequestsEnabled: default: false WebKit: "PLATFORM(IOS) || PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(VISION)": true @@ -2027,7 +2046,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb WebCore: default: false -@@ -2249,7 +2252,7 @@ DirectoryUploadEnabled: +@@ -2278,7 +2281,7 @@ DirectoryUploadEnabled: WebKitLegacy: default: false WebKit: @@ -2036,7 +2055,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb default: false WebCore: default: false -@@ -3261,6 +3264,7 @@ InspectorAttachmentSide: +@@ -3304,6 +3307,7 @@ InspectorAttachmentSide: WebKit: default: 0 @@ -2044,7 +2063,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb InspectorStartsAttached: type: bool status: embedder -@@ -3268,7 +3272,7 @@ InspectorStartsAttached: +@@ -3311,7 +3315,7 @@ InspectorStartsAttached: exposed: [ WebKit ] defaultValue: WebKit: @@ -2053,7 +2072,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb InspectorWindowFrame: type: String -@@ -3622,9 +3626,10 @@ LayoutViewportHeightExpansionFactor: +@@ -3650,9 +3654,10 @@ LayoutViewportHeightExpansionFactor: WebCore: default: 0 @@ -2065,7 +2084,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb category: html humanReadableName: "Lazy iframe loading" humanReadableDescription: "Enable lazy iframe loading support" -@@ -3632,9 +3637,9 @@ LazyIframeLoadingEnabled: +@@ -3660,9 +3665,9 @@ LazyIframeLoadingEnabled: WebKitLegacy: default: true WebKit: @@ -2077,7 +2096,16 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb LazyImageLoadingEnabled: type: bool -@@ -5084,6 +5089,19 @@ PitchCorrectionAlgorithm: +@@ -5084,7 +5089,7 @@ PermissionsAPIEnabled: + WebKitLegacy: + default: false + WebKit: +- "PLATFORM(COCOA) || PLATFORM(GTK) || PLATFORM(WPE)" : true ++ "PLATFORM(COCOA) || PLATFORM(GTK) || PLATFORM(WPE) || PLATFORM(WIN)" : true + default: false + WebCore: + default: false +@@ -5143,6 +5148,19 @@ PitchCorrectionAlgorithm: WebCore: default: MediaPlayerEnums::PitchCorrectionAlgorithm::BestAllAround @@ -2097,7 +2125,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb PointerLockOptionsEnabled: type: bool status: testable -@@ -5622,7 +5640,7 @@ ScreenOrientationAPIEnabled: +@@ -5697,7 +5715,7 @@ ScreenOrientationAPIEnabled: WebKitLegacy: default: false WebKit: @@ -2106,7 +2134,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb WebCore: default: false -@@ -6854,6 +6872,7 @@ UseCGDisplayListsForDOMRendering: +@@ -6946,6 +6964,7 @@ UseCGDisplayListsForDOMRendering: WebKit: default: true @@ -2114,7 +2142,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb UseGPUProcessForCanvasRenderingEnabled: type: bool status: stable -@@ -6866,7 +6885,7 @@ UseGPUProcessForCanvasRenderingEnabled: +@@ -6958,7 +6977,7 @@ UseGPUProcessForCanvasRenderingEnabled: defaultValue: WebKit: "ENABLE(GPU_PROCESS_BY_DEFAULT)": true @@ -2123,7 +2151,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb default: false UseGPUProcessForDOMRenderingEnabled: -@@ -6876,7 +6895,7 @@ UseGPUProcessForDOMRenderingEnabled: +@@ -6968,7 +6987,7 @@ UseGPUProcessForDOMRenderingEnabled: humanReadableName: "GPU Process: DOM Rendering" humanReadableDescription: "Enable DOM rendering in GPU Process" webcoreBinding: none @@ -2132,7 +2160,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb exposed: [ WebKit ] defaultValue: WebKit: -@@ -6908,6 +6927,7 @@ UseGPUProcessForMediaEnabled: +@@ -7000,6 +7019,7 @@ UseGPUProcessForMediaEnabled: "ENABLE(GPU_PROCESS_BY_DEFAULT)": true default: false @@ -2140,7 +2168,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb UseGPUProcessForWebGLEnabled: type: bool status: internal -@@ -6919,7 +6939,7 @@ UseGPUProcessForWebGLEnabled: +@@ -7011,7 +7031,7 @@ UseGPUProcessForWebGLEnabled: default: false WebKit: "ENABLE(GPU_PROCESS_BY_DEFAULT) && ENABLE(GPU_PROCESS_WEBGL_BY_DEFAULT)": true @@ -2150,7 +2178,7 @@ index 7dc92bd115c44d969d31491db7ce123587f4a7fe..c0628987487ee41f927fc1bb4aa73dfb 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 ad8e407924f61aec230fea626efc6fb01f21f9f7..65b4a862906752f53e9d95d3238271846290329f 100644 +index 48c40a6bfa8ae0a275bbd8f3ae62f4701916fa48..a32e26e01d83999c575649a9fc0d96c71b655259 100644 --- a/Source/WTF/wtf/PlatformEnable.h +++ b/Source/WTF/wtf/PlatformEnable.h @@ -401,7 +401,7 @@ @@ -2172,10 +2200,10 @@ index ad8e407924f61aec230fea626efc6fb01f21f9f7..65b4a862906752f53e9d95d323827184 #if !defined(ENABLE_TOUCH_ACTION_REGIONS) diff --git a/Source/WTF/wtf/PlatformEnableCocoa.h b/Source/WTF/wtf/PlatformEnableCocoa.h -index 119cfbc1e518ca18fd7fe925e433054fc071f4d6..e4111b7f6ed0c0245e8ca1f7d1a4b3f674312615 100644 +index 5fef3978fdfb0dc92609688fdf282ea26a0859ef..70190e180003951e48c4084a1788f504730171ca 100644 --- a/Source/WTF/wtf/PlatformEnableCocoa.h +++ b/Source/WTF/wtf/PlatformEnableCocoa.h -@@ -780,7 +780,7 @@ +@@ -781,7 +781,7 @@ #endif #if !defined(ENABLE_SEC_ITEM_SHIM) @@ -2185,10 +2213,10 @@ index 119cfbc1e518ca18fd7fe925e433054fc071f4d6..e4111b7f6ed0c0245e8ca1f7d1a4b3f6 #if !defined(ENABLE_SERVER_PRECONNECT) diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h -index 770c60d0bfdb4219917347d24e06f6076f447ebd..153e93b0ad35f4f3182c619fc25ecc1bd236a3e6 100644 +index 33d00e530b23f32e279214a88379041ec2a7c3b7..0d7b3f08bef8f010d96d85df5d1672dac87054ec 100644 --- a/Source/WTF/wtf/PlatformHave.h +++ b/Source/WTF/wtf/PlatformHave.h -@@ -418,7 +418,7 @@ +@@ -425,7 +425,7 @@ #define HAVE_FOUNDATION_WITH_SAME_SITE_COOKIE_SUPPORT 1 #endif @@ -2197,7 +2225,7 @@ index 770c60d0bfdb4219917347d24e06f6076f447ebd..153e93b0ad35f4f3182c619fc25ecc1b #define HAVE_OS_DARK_MODE_SUPPORT 1 #endif -@@ -1262,7 +1262,8 @@ +@@ -1248,7 +1248,8 @@ #endif #if PLATFORM(MAC) @@ -2223,13 +2251,13 @@ index 007b8fe3292f326504013be8198ae020f7aacf35..4439f901b4a9a92d881c7cee24ad9cd2 namespace Unicode { diff --git a/Source/WebCore/DerivedSources.make b/Source/WebCore/DerivedSources.make -index bad5e65c0f8eb201e0865430156c19501a6f357d..daa60c7b289870a77f3af4bbb24fa8c2632ccaa1 100644 +index 3c93bf646040ac804afc9c98b855829e98b87269..366b9c6714aa4f378a156577bd932a31ee5737a6 100644 --- a/Source/WebCore/DerivedSources.make +++ b/Source/WebCore/DerivedSources.make -@@ -1150,6 +1150,10 @@ JS_BINDING_IDLS := \ - $(WebCore)/dom/Slotable.idl \ - $(WebCore)/dom/StaticRange.idl \ - $(WebCore)/dom/StringCallback.idl \ +@@ -1156,6 +1156,10 @@ JS_BINDING_IDLS := \ + $(WebCore)/dom/SubscriberCallback.idl \ + $(WebCore)/dom/SubscriptionObserver.idl \ + $(WebCore)/dom/SubscriptionObserverCallback.idl \ + $(WebCore)/dom/Document+Touch.idl \ + $(WebCore)/dom/Touch.idl \ + $(WebCore)/dom/TouchEvent.idl \ @@ -2237,7 +2265,7 @@ index bad5e65c0f8eb201e0865430156c19501a6f357d..daa60c7b289870a77f3af4bbb24fa8c2 $(WebCore)/dom/Text.idl \ $(WebCore)/dom/TextDecoder.idl \ $(WebCore)/dom/TextDecoderStream.idl \ -@@ -1737,9 +1741,6 @@ JS_BINDING_IDLS := \ +@@ -1745,9 +1749,6 @@ JS_BINDING_IDLS := \ ADDITIONAL_BINDING_IDLS = \ DocumentTouch.idl \ GestureEvent.idl \ @@ -2248,10 +2276,10 @@ index bad5e65c0f8eb201e0865430156c19501a6f357d..daa60c7b289870a77f3af4bbb24fa8c2 vpath %.in $(WEBKITADDITIONS_HEADER_SEARCH_PATHS) diff --git a/Source/WebCore/Modules/geolocation/Geolocation.cpp b/Source/WebCore/Modules/geolocation/Geolocation.cpp -index 6cfaedf99ffdc456d0d0cc631636d1719981c598..682b822d464f084be76a5272f44b23adcd20ed21 100644 +index be20114bdebd713c5224893ac73078b03b22e392..4174bdf02631d421a0f1fdadfde21c308e4c7735 100644 --- a/Source/WebCore/Modules/geolocation/Geolocation.cpp +++ b/Source/WebCore/Modules/geolocation/Geolocation.cpp -@@ -359,8 +359,9 @@ bool Geolocation::shouldBlockGeolocationRequests() +@@ -360,8 +360,9 @@ bool Geolocation::shouldBlockGeolocationRequests() bool isSecure = SecurityOrigin::isSecure(document->url()) || document->isSecureContext(); bool hasMixedContent = !document->foundMixedContent().isEmpty(); bool isLocalOrigin = securityOrigin()->isLocal(); @@ -2311,10 +2339,10 @@ index c6a03b56d8358316c9ce422c1a11438bd216f80f..69fbd319b7cd084ca125a8db1b5d92ef set(CSS_VALUE_PLATFORM_DEFINES "HAVE_OS_DARK_MODE_SUPPORT=1") diff --git a/Source/WebCore/SourcesCocoa.txt b/Source/WebCore/SourcesCocoa.txt -index 9b508a64017aaba7038419d75b2026326c6b3408..79060874c56510b59c874b8969c816bfb0214b3a 100644 +index 8ca60190e2f9110b74e37350ae12de7ff324de0e..0ad2e7621561b0a0eb67dc39555a2b99169df9c2 100644 --- a/Source/WebCore/SourcesCocoa.txt +++ b/Source/WebCore/SourcesCocoa.txt -@@ -715,3 +715,9 @@ testing/cocoa/WebViewVisualIdentificationOverlay.mm +@@ -716,3 +716,9 @@ testing/cocoa/WebViewVisualIdentificationOverlay.mm platform/graphics/angle/GraphicsContextGLANGLE.cpp @no-unify platform/graphics/cocoa/GraphicsContextGLCocoa.mm @no-unify platform/graphics/cv/GraphicsContextGLCVCocoa.cpp @no-unify @@ -2371,10 +2399,10 @@ index 92f1879df295fc63a9194dc54d3f7499c5fe3041..67c40d056aee6a8149ed1ff16ce4c835 +JSSpeechSynthesisEventInit.cpp +// Playwright: end. diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj -index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb552e10cb91 100644 +index 2dd82a349b4c111364b9032a5e5a3d81e238798e..f18701e5aa5a6dd0bd2619a0cfb92316aee41ffd 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj -@@ -6220,6 +6220,13 @@ +@@ -6198,6 +6198,13 @@ EDE3A5000C7A430600956A37 /* ColorMac.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE3A4FF0C7A430600956A37 /* ColorMac.h */; settings = {ATTRIBUTES = (Private, ); }; }; EDEC98030AED7E170059137F /* WebCorePrefix.h in Headers */ = {isa = PBXBuildFile; fileRef = EDEC98020AED7E170059137F /* WebCorePrefix.h */; }; EFCC6C8F20FE914400A2321B /* CanvasActivityRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = EFCC6C8D20FE914000A2321B /* CanvasActivityRecord.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -2388,7 +2416,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 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, ); }; }; -@@ -20217,6 +20224,14 @@ +@@ -20153,6 +20160,14 @@ EDEC98020AED7E170059137F /* WebCorePrefix.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = WebCorePrefix.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 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 = ""; }; @@ -2403,7 +2431,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 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 = ""; }; -@@ -27846,6 +27861,11 @@ +@@ -27805,6 +27820,11 @@ BC4A5324256055590028C592 /* TextDirectionSubmenuInclusionBehavior.h */, 2D4F96F11A1ECC240098BF88 /* TextIndicator.cpp */, 2D4F96F21A1ECC240098BF88 /* TextIndicator.h */, @@ -2415,7 +2443,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 F48570A42644C76D00C05F71 /* TranslationContextMenuInfo.h */, F4E1965F21F26E4E00285078 /* UndoItem.cpp */, 2ECDBAD521D8906300F00ECD /* UndoItem.h */, -@@ -34246,6 +34266,8 @@ +@@ -34147,6 +34167,8 @@ 29E4D8DF16B0940F00C84704 /* PlatformSpeechSynthesizer.h */, 1AD8F81A11CAB9E900E93E54 /* PlatformStrategies.cpp */, 1AD8F81911CAB9E900E93E54 /* PlatformStrategies.h */, @@ -2424,7 +2452,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 0FD7C21D23CE41E30096D102 /* PlatformWheelEvent.cpp */, 935C476A09AC4D4F00A6AAB4 /* PlatformWheelEvent.h */, F491A66A2A9FEFA300F96146 /* PlatformWheelEvent.serialization.in */, -@@ -36916,6 +36938,7 @@ +@@ -36817,6 +36839,7 @@ AD6E71AB1668899D00320C13 /* DocumentSharedObjectPool.h */, 6BDB5DC1227BD3B800919770 /* DocumentStorageAccess.cpp */, 6BDB5DC0227BD3B800919770 /* DocumentStorageAccess.h */, @@ -2432,7 +2460,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 7CE7FA5B1EF882300060C9D6 /* DocumentTouch.cpp */, 7CE7FA591EF882300060C9D6 /* DocumentTouch.h */, A8185F3209765765005826D9 /* DocumentType.cpp */, -@@ -41657,6 +41680,8 @@ +@@ -41547,6 +41570,8 @@ F4E90A3C2B52038E002DA469 /* PlatformTextAlternatives.h in Headers */, 0F7D07331884C56C00B4AF86 /* PlatformTextTrack.h in Headers */, 074E82BB18A69F0E007EF54C /* PlatformTimeRanges.h in Headers */, @@ -2441,7 +2469,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 CDD08ABD277E542600EA3755 /* PlatformTrackConfiguration.h in Headers */, CD1F9B022700323D00617EB6 /* PlatformVideoColorPrimaries.h in Headers */, CD1F9B01270020B700617EB6 /* PlatformVideoColorSpace.h in Headers */, -@@ -42939,6 +42964,7 @@ +@@ -42826,6 +42851,7 @@ 0F54DD081881D5F5003EEDBB /* Touch.h in Headers */, 71B7EE0D21B5C6870031C1EF /* TouchAction.h in Headers */, 0F54DD091881D5F5003EEDBB /* TouchEvent.h in Headers */, @@ -2449,7 +2477,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 0F54DD0A1881D5F5003EEDBB /* TouchList.h in Headers */, 070334D71459FFD5008D8D45 /* TrackBase.h in Headers */, BE88E0C21715CE2600658D98 /* TrackListBase.h in Headers */, -@@ -44089,6 +44115,8 @@ +@@ -43980,6 +44006,8 @@ 2D22830323A8470700364B7E /* CursorMac.mm in Sources */, 5CBD59592280E926002B22AA /* CustomHeaderFields.cpp in Sources */, 07E4BDBF2A3A5FAB000D5509 /* DictationCaretAnimator.cpp in Sources */, @@ -2458,7 +2486,7 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 7CE6CBFD187F394900D46BF5 /* FormatConverter.cpp in Sources */, 4667EA3E2968D9DA00BAB1E2 /* GameControllerHapticEffect.mm in Sources */, 46FE73D32968E52000B8064C /* GameControllerHapticEngines.mm in Sources */, -@@ -44176,6 +44204,9 @@ +@@ -44068,6 +44096,9 @@ CE88EE262414467B007F29C2 /* TextAlternativeWithRange.mm in Sources */, BE39137129B267F500FA5D4F /* TextTransformCocoa.cpp in Sources */, 51DF6D800B92A18E00C2DC85 /* ThreadCheck.mm in Sources */, @@ -2469,10 +2497,10 @@ index d6bd3e93dbaf2ccf6171d38aadc56fd9bc0841d1..38a9c62b6b1106e6ccb705012d93eb55 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 a54b85c9d3a0fbd0e24c2b0398eafe44830d2cbc..e23ae4813a861b7c6fdfad06951f02bee76201db 100644 +index a934d32a2b01e6273e1d97a7b8fd7e0999495dc5..379eee720dda27b29b32f26ab5a10d83ce78f4b9 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp -@@ -66,6 +66,7 @@ +@@ -67,6 +67,7 @@ #include "HTMLSlotElement.h" #include "HTMLTextAreaElement.h" #include "HitTestResult.h" @@ -2480,7 +2508,7 @@ index a54b85c9d3a0fbd0e24c2b0398eafe44830d2cbc..e23ae4813a861b7c6fdfad06951f02be #include "LocalFrame.h" #include "LocalizedStrings.h" #include "MathMLNames.h" -@@ -3992,9 +3993,14 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const +@@ -3968,9 +3969,14 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const if (roleValue() == AccessibilityRole::ApplicationDialog) return AccessibilityObjectInclusion::IncludeObject; @@ -2497,8 +2525,30 @@ index a54b85c9d3a0fbd0e24c2b0398eafe44830d2cbc..e23ae4813a861b7c6fdfad06951f02be bool AccessibilityObject::accessibilityIsIgnored() const { AXComputedObjectAttributeCache* attributeCache = nullptr; +diff --git a/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp b/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp +index 7641906564fb1e480f56923343a8ee6149f60820..ed530124becb719e66b211f468c24376887c4cc4 100644 +--- a/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp ++++ b/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp +@@ -289,7 +289,7 @@ String AccessibilityObjectAtspi::text() const + if (!value.isNull()) + return value; + +- auto text = m_coreObject->textUnderElement(TextUnderElementMode(TextUnderElementMode::Children::IncludeAllChildren)); ++ auto text = m_coreObject->textUnderElement({ TextUnderElementMode::Children::IncludeAllChildren }); + if (auto* renderer = m_coreObject->renderer()) { + if (is(*renderer) && downcast(*renderer).markerRenderer()) { + if (renderer->style().direction() == TextDirection::LTR) { +@@ -315,7 +315,7 @@ unsigned AccessibilityObject::getLengthForTextRange() const + textLength = downcast(*renderer).text().length(); + + if (!textLength && allowsTextRanges()) +- textLength = textUnderElement(TextUnderElementMode(TextUnderElementMode::Children::IncludeAllChildren)).length(); ++ textLength = textUnderElement({ TextUnderElementMode::Children::IncludeAllChildren }).length(); + + return textLength; + } diff --git a/Source/WebCore/bindings/js/WebCoreBuiltinNames.h b/Source/WebCore/bindings/js/WebCoreBuiltinNames.h -index a5a6d8a0c426db0b465d3c932de52f0678d5f5e8..75bb28488a3790ff0bfda8af3f3b641d9b519bd5 100644 +index bec74918c24d17a88bb1583504d00e2a11a5e6c6..8c86fc1552c091e6751e87c60af728b4ce37f2a1 100644 --- a/Source/WebCore/bindings/js/WebCoreBuiltinNames.h +++ b/Source/WebCore/bindings/js/WebCoreBuiltinNames.h @@ -183,6 +183,8 @@ namespace WebCore { @@ -2511,10 +2561,10 @@ index a5a6d8a0c426db0b465d3c932de52f0678d5f5e8..75bb28488a3790ff0bfda8af3f3b641d macro(DynamicsCompressorNode) \ macro(ElementInternals) \ diff --git a/Source/WebCore/css/query/MediaQueryFeatures.cpp b/Source/WebCore/css/query/MediaQueryFeatures.cpp -index 9892fda4291cae0e0d338fac8b0f98cd0126807d..7ecfd659809ab30e82a9c00ec7710292a1bd5611 100644 +index 450c429a76a97ba079087eaecc0deca236f3a419..726765635432192654ee218874813a1d09a83af4 100644 --- a/Source/WebCore/css/query/MediaQueryFeatures.cpp +++ b/Source/WebCore/css/query/MediaQueryFeatures.cpp -@@ -368,7 +368,11 @@ const FeatureSchema& forcedColors() +@@ -364,7 +364,11 @@ const FeatureSchema& forcedColors() static MainThreadNeverDestroyed schema { "forced-colors"_s, FixedVector { CSSValueNone, CSSValueActive }, @@ -2527,7 +2577,7 @@ index 9892fda4291cae0e0d338fac8b0f98cd0126807d..7ecfd659809ab30e82a9c00ec7710292 return MatchingIdentifiers { CSSValueNone }; } }; -@@ -547,6 +551,9 @@ const FeatureSchema& prefersReducedMotion() +@@ -546,6 +550,9 @@ const FeatureSchema& prefersReducedMotion() [](auto& context) { bool userPrefersReducedMotion = [&] { Ref frame = *context.document->frame(); @@ -2631,7 +2681,7 @@ index 9b344003de17b96d8b9ca8c7f32143a27543b1ea..2208a3f2b7d930bcd291e65b474d4c30 ] partial interface Element { // Returns Promise if PointerLockOptionsEnabled Runtime Flag is set, otherwise returns undefined. diff --git a/Source/WebCore/dom/PointerEvent.cpp b/Source/WebCore/dom/PointerEvent.cpp -index c35c7851f168954a0c5265ea218a2173b7b079a8..500b267351d2e4ac9864129650b6c00627a8ea6f 100644 +index 204b5f08ba950ead5f7d853d3c7fc9274ce46a26..e2f117a2a3e221fc4ca14b02c82cda952f0cd63b 100644 --- a/Source/WebCore/dom/PointerEvent.cpp +++ b/Source/WebCore/dom/PointerEvent.cpp @@ -27,9 +27,11 @@ @@ -2646,9 +2696,9 @@ index c35c7851f168954a0c5265ea218a2173b7b079a8..500b267351d2e4ac9864129650b6c006 #include namespace WebCore { -@@ -122,4 +124,51 @@ PointerEvent::PointerEvent(const AtomString& type, PointerID pointerId, const St - - PointerEvent::~PointerEvent() = default; +@@ -133,4 +135,51 @@ Vector> PointerEvent::getCoalescedEvents() + return m_coalescedEvents; + } +#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS_FAMILY) && !PLATFORM(WPE) + @@ -2699,7 +2749,7 @@ index c35c7851f168954a0c5265ea218a2173b7b079a8..500b267351d2e4ac9864129650b6c006 + } // namespace WebCore diff --git a/Source/WebCore/dom/PointerEvent.h b/Source/WebCore/dom/PointerEvent.h -index 261a8c7e8946cac87b9ebc5fc5e3b697f326b7f4..02645d3bdce44f40a648e720e99a6b296f371fb2 100644 +index c54bbf8060253b9000f3da9be8ff327a2625ff86..6b05fc5f61444ea9dc7775491801c585ae44045e 100644 --- a/Source/WebCore/dom/PointerEvent.h +++ b/Source/WebCore/dom/PointerEvent.h @@ -34,6 +34,8 @@ @@ -2711,8 +2761,8 @@ index 261a8c7e8946cac87b9ebc5fc5e3b697f326b7f4..02645d3bdce44f40a648e720e99a6b29 #endif #if ENABLE(TOUCH_EVENTS) && PLATFORM(WPE) -@@ -86,7 +88,7 @@ public: - static Ref create(const AtomString& type, MouseButton, const MouseEvent&, PointerID, const String& pointerType); +@@ -88,7 +90,7 @@ public: + static Ref create(const AtomString& type, MouseButton, const MouseEvent&, PointerID, const String& pointerType, CanBubble, IsCancelable); static Ref create(const AtomString& type, PointerID, const String& pointerType, IsPrimary = IsPrimary::No); -#if ENABLE(TOUCH_EVENTS) && (PLATFORM(IOS_FAMILY) || PLATFORM(WPE)) @@ -2720,9 +2770,9 @@ index 261a8c7e8946cac87b9ebc5fc5e3b697f326b7f4..02645d3bdce44f40a648e720e99a6b29 static Ref create(const PlatformTouchEvent&, unsigned touchIndex, bool isPrimary, Ref&&, const IntPoint& touchDelta = { }); static Ref create(const AtomString& type, const PlatformTouchEvent&, unsigned touchIndex, bool isPrimary, Ref&&, const IntPoint& touchDelta = { }); #endif -@@ -140,7 +142,7 @@ private: +@@ -144,7 +146,7 @@ private: PointerEvent(const AtomString&, Init&&); - PointerEvent(const AtomString& type, MouseButton, const MouseEvent&, PointerID, const String& pointerType); + PointerEvent(const AtomString& type, MouseButton, const MouseEvent&, PointerID, const String& pointerType, CanBubble, IsCancelable); PointerEvent(const AtomString& type, PointerID, const String& pointerType, IsPrimary); -#if ENABLE(TOUCH_EVENTS) && (PLATFORM(IOS_FAMILY) || PLATFORM(WPE)) +#if ENABLE(TOUCH_EVENTS) @@ -2757,7 +2807,7 @@ index 7813532cc52d582c42aebc979a1ecd1137765f08..c01cbd53ad2430a6ffab9a80fc73e74a #endif // USE(LIBWPE) diff --git a/Source/WebCore/html/FileInputType.cpp b/Source/WebCore/html/FileInputType.cpp -index 67feb6c9b89bbbbffd343d535d990c682be6e737..c60ffff41ace3b711920d867ab4f8dde9446b487 100644 +index 7a26cfbbaf3d9583064a5eb30a049b5ac04847f0..1377da39ae4cc134b87358871fdb39431161244b 100644 --- a/Source/WebCore/html/FileInputType.cpp +++ b/Source/WebCore/html/FileInputType.cpp @@ -37,6 +37,7 @@ @@ -2768,7 +2818,7 @@ index 67feb6c9b89bbbbffd343d535d990c682be6e737..c60ffff41ace3b711920d867ab4f8dde #include "LocalFrame.h" #include "LocalizedStrings.h" #include "MIMETypeRegistry.h" -@@ -157,6 +158,11 @@ void FileInputType::handleDOMActivateEvent(Event& event) +@@ -158,6 +159,11 @@ void FileInputType::handleDOMActivateEvent(Event& event) if (input.isDisabledFormControl()) return; @@ -2780,7 +2830,7 @@ index 67feb6c9b89bbbbffd343d535d990c682be6e737..c60ffff41ace3b711920d867ab4f8dde if (!UserGestureIndicator::processingUserGesture()) return; -@@ -345,7 +351,9 @@ void FileInputType::setFiles(RefPtr&& files, RequestIcon shouldRequest +@@ -346,7 +352,9 @@ void FileInputType::setFiles(RefPtr&& files, RequestIcon shouldRequest pathsChanged = true; else { for (unsigned i = 0; i < length; ++i) { @@ -2873,7 +2923,7 @@ index 3a981b5bf5ca0bbf4d1c9f0b125564742cd8cad9..f8fc2ca6700461627933f149c5837075 } // namespace WebCore diff --git a/Source/WebCore/inspector/InspectorInstrumentation.cpp b/Source/WebCore/inspector/InspectorInstrumentation.cpp -index 79e5f20d096d724bcee0866175c0ceee1fb2ea12..94fbc44587f6a1c58cf81cf4a75cb42ca1e87161 100644 +index da310c6df500f80d2ae762c890ce9733d892ebd1..50db06b983b2eb4ac89942df94838e009bc82175 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.cpp +++ b/Source/WebCore/inspector/InspectorInstrumentation.cpp @@ -598,6 +598,12 @@ void InspectorInstrumentation::applyUserAgentOverrideImpl(InstrumentingAgents& i @@ -2965,7 +3015,20 @@ index 79e5f20d096d724bcee0866175c0ceee1fb2ea12..94fbc44587f6a1c58cf81cf4a75cb42c #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) void InspectorInstrumentation::defaultAppearanceDidChangeImpl(InstrumentingAgents& instrumentingAgents) { -@@ -1051,6 +1060,12 @@ void InspectorInstrumentation::consoleStopRecordingCanvasImpl(InstrumentingAgent +@@ -915,6 +924,12 @@ void InspectorInstrumentation::interceptResponseImpl(InstrumentingAgents& instru + networkAgent->interceptResponse(response, identifier, WTFMove(handler)); + } + ++void InspectorInstrumentation::setStoppingLoadingDueToProcessSwapImpl(InstrumentingAgents& instrumentingAgents, bool value) ++{ ++ if (auto* networkAgent = instrumentingAgents.enabledNetworkAgent()) ++ networkAgent->setStoppingLoadingDueToProcessSwap(value); ++} ++ + // JavaScriptCore InspectorDebuggerAgent should know Console MessageTypes. + static bool isConsoleAssertMessage(MessageSource source, MessageType type) + { +@@ -1051,6 +1066,12 @@ void InspectorInstrumentation::consoleStopRecordingCanvasImpl(InstrumentingAgent canvasAgent->consoleStopRecordingCanvas(context); } @@ -2978,7 +3041,7 @@ index 79e5f20d096d724bcee0866175c0ceee1fb2ea12..94fbc44587f6a1c58cf81cf4a75cb42c void InspectorInstrumentation::didOpenDatabaseImpl(InstrumentingAgents& instrumentingAgents, Database& database) { if (auto* databaseAgent = instrumentingAgents.enabledDatabaseAgent()) -@@ -1357,6 +1372,36 @@ void InspectorInstrumentation::renderLayerDestroyedImpl(InstrumentingAgents& ins +@@ -1357,6 +1378,36 @@ void InspectorInstrumentation::renderLayerDestroyedImpl(InstrumentingAgents& ins layerTreeAgent->renderLayerDestroyed(renderLayer); } @@ -3015,7 +3078,7 @@ index 79e5f20d096d724bcee0866175c0ceee1fb2ea12..94fbc44587f6a1c58cf81cf4a75cb42c InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(WorkerOrWorkletGlobalScope& globalScope) { return globalScope.inspectorController().m_instrumentingAgents; -@@ -1373,6 +1418,13 @@ InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(Page& page) +@@ -1373,6 +1424,13 @@ InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(Page& page) return page.inspectorController().m_instrumentingAgents.get(); } @@ -3030,7 +3093,7 @@ index 79e5f20d096d724bcee0866175c0ceee1fb2ea12..94fbc44587f6a1c58cf81cf4a75cb42c { // Using RefPtr makes us hit the m_inRemovedLastRefFunction assert. diff --git a/Source/WebCore/inspector/InspectorInstrumentation.h b/Source/WebCore/inspector/InspectorInstrumentation.h -index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f6873a81c 100644 +index 156e222e4383bee7448347d84cb052b0d9b9ae70..9bce3a168c314fc34043578c248d69cb89b48426 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.h +++ b/Source/WebCore/inspector/InspectorInstrumentation.h @@ -31,6 +31,7 @@ @@ -3080,7 +3143,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f - static void loaderDetachedFromFrame(LocalFrame&, DocumentLoader&); static void frameStartedLoading(LocalFrame&); static void frameStoppedLoading(LocalFrame&); - static void didCompleteRenderingFrame(LocalFrame&); + static void didCompleteRenderingFrame(Frame&); - static void frameScheduledNavigation(Frame&, Seconds delay); + static void frameScheduledNavigation(Frame&, Seconds delay, bool targetIsCurrentFrame); static void frameClearedScheduledNavigation(Frame&); @@ -3089,7 +3152,15 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) static void defaultAppearanceDidChange(Page&); #endif -@@ -269,6 +274,7 @@ public: +@@ -248,6 +253,7 @@ public: + static bool shouldInterceptResponse(const LocalFrame&, const ResourceResponse&); + static void interceptRequest(ResourceLoader&, Function&&); + static void interceptResponse(const LocalFrame&, const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); ++ static void setStoppingLoadingDueToProcessSwap(Page*, bool); + + static void addMessageToConsole(Page&, std::unique_ptr); + static void addMessageToConsole(WorkerOrWorkletGlobalScope&, std::unique_ptr); +@@ -269,6 +275,7 @@ public: static void stopProfiling(Page&, JSC::JSGlobalObject*, const String& title); static void consoleStartRecordingCanvas(CanvasRenderingContext&, JSC::JSGlobalObject&, JSC::JSObject* options); static void consoleStopRecordingCanvas(CanvasRenderingContext&); @@ -3097,7 +3168,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f static void performanceMark(ScriptExecutionContext&, const String&, std::optional, LocalFrame*); -@@ -327,6 +333,12 @@ public: +@@ -327,6 +334,12 @@ public: static void layerTreeDidChange(Page*); static void renderLayerDestroyed(Page*, const RenderLayer&); @@ -3110,7 +3181,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f static void frontendCreated(); static void frontendDeleted(); static bool hasFrontends() { return InspectorInstrumentationPublic::hasFrontends(); } -@@ -343,6 +355,8 @@ public: +@@ -343,6 +356,8 @@ public: static void registerInstrumentingAgents(InstrumentingAgents&); static void unregisterInstrumentingAgents(InstrumentingAgents&); @@ -3119,7 +3190,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f private: static void didClearWindowObjectInWorldImpl(InstrumentingAgents&, LocalFrame&, DOMWrapperWorld&); static bool isDebuggerPausedImpl(InstrumentingAgents&); -@@ -422,6 +436,7 @@ private: +@@ -422,6 +437,7 @@ private: static void didRecalculateStyleImpl(InstrumentingAgents&); static void didScheduleStyleRecalculationImpl(InstrumentingAgents&, Document&); static void applyUserAgentOverrideImpl(InstrumentingAgents&, String&); @@ -3127,7 +3198,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f static void applyEmulatedMediaImpl(InstrumentingAgents&, AtomString&); static void flexibleBoxRendererBeganLayoutImpl(InstrumentingAgents&, const RenderObject&); -@@ -436,6 +451,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&); @@ -3135,7 +3206,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f static void willLoadXHRSynchronouslyImpl(InstrumentingAgents&); static void didLoadXHRSynchronouslyImpl(InstrumentingAgents&); static void scriptImportedImpl(InstrumentingAgents&, ResourceLoaderIdentifier, const String& sourceString); -@@ -446,13 +462,13 @@ private: +@@ -446,13 +463,13 @@ private: static void frameDetachedFromParentImpl(InstrumentingAgents&, LocalFrame&); static void didCommitLoadImpl(InstrumentingAgents&, LocalFrame&, DocumentLoader*); static void frameDocumentUpdatedImpl(InstrumentingAgents&, LocalFrame&); @@ -3151,7 +3222,15 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) static void defaultAppearanceDidChangeImpl(InstrumentingAgents&); #endif -@@ -479,6 +495,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)>&&); ++ static void setStoppingLoadingDueToProcessSwapImpl(InstrumentingAgents&, bool); + + static void addMessageToConsoleImpl(InstrumentingAgents&, std::unique_ptr); + +@@ -479,6 +497,7 @@ private: static void stopProfilingImpl(InstrumentingAgents&, JSC::JSGlobalObject*, const String& title); static void consoleStartRecordingCanvasImpl(InstrumentingAgents&, CanvasRenderingContext&, JSC::JSGlobalObject&, JSC::JSObject* options); static void consoleStopRecordingCanvasImpl(InstrumentingAgents&, CanvasRenderingContext&); @@ -3159,7 +3238,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f static void performanceMarkImpl(InstrumentingAgents&, const String& label, std::optional, LocalFrame*); -@@ -537,6 +554,12 @@ private: +@@ -537,6 +556,12 @@ private: static void layerTreeDidChangeImpl(InstrumentingAgents&); static void renderLayerDestroyedImpl(InstrumentingAgents&, const RenderLayer&); @@ -3172,7 +3251,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f static InstrumentingAgents& instrumentingAgents(Page&); static InstrumentingAgents& instrumentingAgents(WorkerOrWorkletGlobalScope&); static InstrumentingAgents& instrumentingAgents(ServiceWorkerGlobalScope&); -@@ -1070,6 +1093,13 @@ inline void InspectorInstrumentation::applyUserAgentOverride(LocalFrame& frame, +@@ -1070,6 +1095,13 @@ inline void InspectorInstrumentation::applyUserAgentOverride(LocalFrame& frame, applyUserAgentOverrideImpl(*agents, userAgent); } @@ -3186,7 +3265,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f inline void InspectorInstrumentation::applyEmulatedMedia(LocalFrame& frame, AtomString& media) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1172,6 +1202,13 @@ inline void InspectorInstrumentation::didFailLoading(ServiceWorkerGlobalScope& g +@@ -1172,6 +1204,13 @@ inline void InspectorInstrumentation::didFailLoading(ServiceWorkerGlobalScope& g didFailLoadingImpl(instrumentingAgents(globalScope), identifier, nullptr, error); } @@ -3200,7 +3279,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f inline void InspectorInstrumentation::continueAfterXFrameOptionsDenied(LocalFrame& frame, ResourceLoaderIdentifier identifier, DocumentLoader& loader, const ResourceResponse& response) { // Treat the same as didReceiveResponse. -@@ -1262,13 +1299,6 @@ inline void InspectorInstrumentation::frameDocumentUpdated(LocalFrame& frame) +@@ -1262,13 +1301,6 @@ inline void InspectorInstrumentation::frameDocumentUpdated(LocalFrame& frame) frameDocumentUpdatedImpl(*agents, frame); } @@ -3214,7 +3293,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f inline void InspectorInstrumentation::frameStartedLoading(LocalFrame& frame) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1290,11 +1320,11 @@ inline void InspectorInstrumentation::frameStoppedLoading(LocalFrame& frame) +@@ -1290,11 +1322,11 @@ inline void InspectorInstrumentation::frameStoppedLoading(LocalFrame& frame) frameStoppedLoadingImpl(*agents, frame); } @@ -3228,7 +3307,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f } inline void InspectorInstrumentation::frameClearedScheduledNavigation(Frame& frame) -@@ -1310,6 +1340,13 @@ inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page) +@@ -1310,6 +1342,13 @@ inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page) accessibilitySettingsDidChangeImpl(instrumentingAgents(page)); } @@ -3242,7 +3321,21 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) inline void InspectorInstrumentation::defaultAppearanceDidChange(Page& page) { -@@ -1698,6 +1735,11 @@ inline void InspectorInstrumentation::performanceMark(ScriptExecutionContext& co +@@ -1362,6 +1401,13 @@ inline void InspectorInstrumentation::interceptResponse(const LocalFrame& frame, + interceptResponseImpl(*agents, response, identifier, WTFMove(handler)); + } + ++inline void InspectorInstrumentation::setStoppingLoadingDueToProcessSwap(Page* page, bool value) ++{ ++ ASSERT(InspectorInstrumentationPublic::hasFrontends()); ++ if (auto* agents = instrumentingAgents(page)) ++ setStoppingLoadingDueToProcessSwapImpl(*agents, value); ++} ++ + inline void InspectorInstrumentation::didOpenDatabase(Database& database) + { + FAST_RETURN_IF_NO_FRONTENDS(void()); +@@ -1698,6 +1744,11 @@ inline void InspectorInstrumentation::performanceMark(ScriptExecutionContext& co performanceMarkImpl(*agents, label, WTFMove(startTime), frame); } @@ -3254,7 +3347,7 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f inline void InspectorInstrumentation::didRequestAnimationFrame(Document& document, int callbackId) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1754,6 +1796,42 @@ inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const Ren +@@ -1754,6 +1805,42 @@ inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const Ren renderLayerDestroyedImpl(*agents, renderLayer); } @@ -3297,8 +3390,60 @@ index bc7a4a0839df63c54a2651feb02baa8aa554e886..3d137bfd99bdbbebbbf1cf983be6356f inline InstrumentingAgents* InspectorInstrumentation::instrumentingAgents(ScriptExecutionContext* context) { return context ? instrumentingAgents(*context) : nullptr; +diff --git a/Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp b/Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp +index a67a1244fa526ad5759068e97e0d220f59565d6e..0048589109fccb9472fe35a410337771b1063d72 100644 +--- a/Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp ++++ b/Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp +@@ -50,4 +50,9 @@ void InspectorInstrumentationWebKit::interceptResponseInternal(const LocalFrame& + InspectorInstrumentation::interceptResponse(frame, response, identifier, WTFMove(handler)); + } + ++void InspectorInstrumentationWebKit::setStoppingLoadingDueToProcessSwapInternal(Page* page, bool value) ++{ ++ InspectorInstrumentation::setStoppingLoadingDueToProcessSwap(page, value); ++} ++ + } // namespace WebCore +diff --git a/Source/WebCore/inspector/InspectorInstrumentationWebKit.h b/Source/WebCore/inspector/InspectorInstrumentationWebKit.h +index c028341e84e59a6b1b16107fd74feb21f70b12ab..d385418ac34e8f315f201801a2c65226c8f6fee2 100644 +--- a/Source/WebCore/inspector/InspectorInstrumentationWebKit.h ++++ b/Source/WebCore/inspector/InspectorInstrumentationWebKit.h +@@ -33,6 +33,7 @@ + namespace WebCore { + + class LocalFrame; ++class Page; + class ResourceLoader; + class ResourceRequest; + class ResourceResponse; +@@ -44,12 +45,14 @@ public: + static bool shouldInterceptResponse(const LocalFrame*, const ResourceResponse&); + static void interceptRequest(ResourceLoader&, Function&&); + static void interceptResponse(const LocalFrame*, const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); ++ static void setStoppingLoadingDueToProcessSwap(Page*, bool); + + private: + static bool shouldInterceptRequestInternal(const ResourceLoader&); + static bool shouldInterceptResponseInternal(const LocalFrame&, const ResourceResponse&); + static void interceptRequestInternal(ResourceLoader&, Function&&); + static void interceptResponseInternal(const LocalFrame&, const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); ++ static void setStoppingLoadingDueToProcessSwapInternal(Page*, bool); + }; + + inline bool InspectorInstrumentationWebKit::shouldInterceptRequest(const ResourceLoader& loader) +@@ -79,4 +82,10 @@ inline void InspectorInstrumentationWebKit::interceptResponse(const LocalFrame* + interceptResponseInternal(*frame, response, identifier, WTFMove(handler)); + } + ++inline void InspectorInstrumentationWebKit::setStoppingLoadingDueToProcessSwap(Page* page, bool value) ++{ ++ FAST_RETURN_IF_NO_FRONTENDS(void()); ++ setStoppingLoadingDueToProcessSwapInternal(page, value); ++} ++ + } diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp -index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69db64f9720 100644 +index ec5000cb553f244224ac2dc5be229c11c42e5d7f..cf6bc9545f04ad7fdba7c2dfaf46ac45e158f15e 100644 --- a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp @@ -55,6 +55,7 @@ @@ -3340,7 +3485,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d #include "StaticNodeList.h" #include "StyleProperties.h" #include "StyleResolver.h" -@@ -145,7 +153,8 @@ using namespace HTMLNames; +@@ -146,7 +154,8 @@ using namespace HTMLNames; static const size_t maxTextSize = 10000; static const UChar horizontalEllipsisUChar[] = { horizontalEllipsis, 0 }; @@ -3350,7 +3495,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d { if (!colorObject) return std::nullopt; -@@ -164,7 +173,7 @@ static std::optional parseColor(RefPtr&& colorObject) +@@ -165,7 +174,7 @@ static std::optional parseColor(RefPtr&& colorObject) static std::optional parseRequiredConfigColor(const String& fieldName, JSON::Object& configObject) { @@ -3359,7 +3504,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d } static Color parseOptionalConfigColor(const String& fieldName, JSON::Object& configObject) -@@ -192,6 +201,20 @@ static bool parseQuad(Ref&& quadArray, FloatQuad* quad) +@@ -193,6 +202,20 @@ static bool parseQuad(Ref&& quadArray, FloatQuad* quad) return true; } @@ -3380,7 +3525,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d class RevalidateStyleAttributeTask { WTF_MAKE_FAST_ALLOCATED; public: -@@ -466,6 +489,20 @@ Node* InspectorDOMAgent::assertNode(Inspector::Protocol::ErrorString& errorStrin +@@ -467,6 +490,20 @@ Node* InspectorDOMAgent::assertNode(Inspector::Protocol::ErrorString& errorStrin return node.get(); } @@ -3401,7 +3546,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d Document* InspectorDOMAgent::assertDocument(Inspector::Protocol::ErrorString& errorString, Inspector::Protocol::DOM::NodeId nodeId) { RefPtr node = assertNode(errorString, nodeId); -@@ -1540,16 +1577,7 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::highlightNode(std::o +@@ -1541,16 +1578,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; @@ -3419,7 +3564,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d if (!node) return makeUnexpected(errorString); -@@ -1804,15 +1832,155 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::setInspectedNode(Ins +@@ -1805,15 +1833,155 @@ Inspector::Protocol::ErrorStringOr InspectorDOMAgent::setInspectedNode(Ins return { }; } @@ -3578,7 +3723,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d if (!object) return makeUnexpected("Missing injected script for given nodeId"_s); -@@ -3078,7 +3246,7 @@ Inspector::Protocol::ErrorStringOr InspectorDO +@@ -3079,7 +3247,7 @@ Inspector::Protocol::ErrorStringOr InspectorDO return makeUnexpected("Missing node for given path"_s); } @@ -3587,7 +3732,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d { Document* document = &node->document(); if (auto* templateHost = document->templateDocumentHost()) -@@ -3087,12 +3255,18 @@ RefPtr InspectorDOMAgent::resolveNod +@@ -3088,12 +3256,18 @@ RefPtr InspectorDOMAgent::resolveNod if (!frame) return nullptr; @@ -3609,7 +3754,7 @@ index 129c6f01b48eaf4fb39914721a0909e25d6d9a97..d8636860918f3f8c24ae91897840f69d } Node* InspectorDOMAgent::scriptValueAsNode(JSC::JSValue value) -@@ -3200,4 +3374,89 @@ Inspector::Protocol::ErrorStringOr> In +@@ -3201,4 +3375,89 @@ Inspector::Protocol::ErrorStringOr> In #endif } @@ -3773,7 +3918,7 @@ index 5f1dba2bc4d5c2f113a88dcc9ba479679cb79233..73e49d699919b68cffff41f612e461e2 void discardBindings(); diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp -index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf72cdbbf8 100644 +index f8a58175bdaa13ae7d2bb722df119e140ec7618f..b601fdd3d16a3fc016ce3af50d416a0880990d7b 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp @@ -59,6 +59,7 @@ @@ -3784,7 +3929,7 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf #include "Page.h" #include "PlatformStrategies.h" #include "ProgressTracker.h" -@@ -339,8 +340,8 @@ static Ref buildObjectForResourceRequest( +@@ -340,8 +341,8 @@ static Ref buildObjectForResourceRequest( .release(); if (request.httpBody() && !request.httpBody()->isEmpty()) { @@ -3795,7 +3940,7 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf } if (resourceLoader) { -@@ -393,6 +394,8 @@ RefPtr InspectorNetworkAgent::buildObjec +@@ -394,6 +395,8 @@ RefPtr InspectorNetworkAgent::buildObjec .setSource(responseSource(response.source())) .release(); @@ -3804,7 +3949,25 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf if (resourceLoader) { auto* metrics = response.deprecatedNetworkLoadMetricsOrNull(); responseObject->setTiming(buildObjectForTiming(metrics ? *metrics : NetworkLoadMetrics::emptyMetrics(), *resourceLoader)); -@@ -960,6 +963,7 @@ void InspectorNetworkAgent::continuePendingResponses() +@@ -680,6 +683,9 @@ void InspectorNetworkAgent::didFailLoading(ResourceLoaderIdentifier identifier, + String requestId = IdentifiersFactory::requestId(identifier.toUInt64()); + + if (loader && m_resourcesData->resourceType(requestId) == InspectorPageAgent::DocumentResource) { ++ if (m_stoppingLoadingDueToProcessSwap) ++ return; ++ + auto* frame = loader->frame(); + if (frame && frame->loader().documentLoader() && frame->document()) { + m_resourcesData->addResourceSharedBuffer(requestId, +@@ -909,6 +915,7 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::disable() + m_instrumentingAgents.setEnabledNetworkAgent(nullptr); + m_resourcesData->clear(); + m_extraRequestHeaders.clear(); ++ m_stoppingLoadingDueToProcessSwap = false; + + continuePendingRequests(); + continuePendingResponses(); +@@ -961,6 +968,7 @@ void InspectorNetworkAgent::continuePendingResponses() Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::setExtraHTTPHeaders(Ref&& headers) { @@ -3812,7 +3975,19 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf for (auto& entry : headers.get()) { auto stringValue = entry.value->asString(); if (!!stringValue) -@@ -1238,6 +1242,9 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptWithReq +@@ -1210,6 +1218,11 @@ void InspectorNetworkAgent::interceptResponse(const ResourceResponse& response, + m_frontendDispatcher->responseIntercepted(requestId, resourceResponse.releaseNonNull()); + } + ++void InspectorNetworkAgent::setStoppingLoadingDueToProcessSwap(bool stopping) ++{ ++ m_stoppingLoadingDueToProcessSwap = stopping; ++} ++ + Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptContinue(const Inspector::Protocol::Network::RequestId& requestId, Inspector::Protocol::Network::NetworkStage networkStage) + { + switch (networkStage) { +@@ -1239,6 +1252,9 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptWithReq return makeUnexpected("Missing pending intercept request for given requestId"_s); auto& loader = *pendingRequest->m_loader; @@ -3822,7 +3997,7 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf ResourceRequest request = loader.request(); if (!!url) request.setURL(URL({ }, url)); -@@ -1333,14 +1340,23 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptRequest +@@ -1334,14 +1350,23 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::interceptRequest response.setHTTPStatusCode(status); response.setHTTPStatusText(String { statusText }); HTTPHeaderMap explicitHeaders; @@ -3848,7 +4023,7 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf if (loader->reachedTerminalState()) return; -@@ -1403,6 +1419,12 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::setEmulatedCondi +@@ -1404,6 +1429,12 @@ Inspector::Protocol::ErrorStringOr InspectorNetworkAgent::setEmulatedCondi #endif // ENABLE(INSPECTOR_NETWORK_THROTTLING) @@ -3862,7 +4037,7 @@ index f481bcb61f039dc5608e0c03ee6eb14e8cb19762..92cf076baeff2db243cf035cad036ccf { return startsWithLettersIgnoringASCIICase(mimeType, "text/"_s) diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.h b/Source/WebCore/inspector/agents/InspectorNetworkAgent.h -index dc7e574ee6e9256a1f75ea838d20ca7f5e9190de..5dd4464256e0f5d652fa51fd611286ddc1da6f5c 100644 +index dc7e574ee6e9256a1f75ea838d20ca7f5e9190de..03a28b599df29e82392b70cf6b83a700134c53a3 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.h +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.h @@ -34,6 +34,8 @@ @@ -3882,8 +4057,24 @@ index dc7e574ee6e9256a1f75ea838d20ca7f5e9190de..5dd4464256e0f5d652fa51fd611286dd // InspectorInstrumentation void willRecalculateStyle(); +@@ -132,6 +135,7 @@ public: + bool shouldInterceptResponse(const ResourceResponse&); + void interceptResponse(const ResourceResponse&, ResourceLoaderIdentifier, CompletionHandler)>&&); + void interceptRequest(ResourceLoader&, Function&&); ++ void setStoppingLoadingDueToProcessSwap(bool); + + 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>&); +@@ -258,6 +262,7 @@ private: + bool m_enabled { false }; + bool m_loadingXHRSynchronously { false }; + bool m_interceptionEnabled { false }; ++ bool m_stoppingLoadingDueToProcessSwap { false }; + }; + + } // namespace WebCore diff --git a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp -index 84eb4dc2127420db005063e0f60d5ad950081719..5f85bc7ba23b9488d03bf7dfa5da645fb2717734 100644 +index 97fa6f1ae18db5b4a1fc8f8f99ce0605fb76a793..fd761459048e7e976b7351eb8714acc63f42bf2d 100644 --- a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp @@ -32,19 +32,26 @@ @@ -3928,7 +4119,7 @@ index 84eb4dc2127420db005063e0f60d5ad950081719..5f85bc7ba23b9488d03bf7dfa5da645f #include "ScriptController.h" #include "ScriptSourceCode.h" #include "SecurityOrigin.h" -@@ -66,11 +76,18 @@ +@@ -66,14 +76,21 @@ #include "StyleScope.h" #include "Theme.h" #include @@ -3946,7 +4137,11 @@ index 84eb4dc2127420db005063e0f60d5ad950081719..5f85bc7ba23b9488d03bf7dfa5da645f +#include #include #include - #include +-#include ++#include + + #if ENABLE(APPLICATION_MANIFEST) + #include "CachedApplicationManifest.h" @@ -92,6 +109,11 @@ namespace WebCore { using namespace Inspector; @@ -4286,7 +4481,7 @@ index 84eb4dc2127420db005063e0f60d5ad950081719..5f85bc7ba23b9488d03bf7dfa5da645f -Inspector::Protocol::ErrorStringOr InspectorPageAgent::snapshotRect(int x, int y, int width, int height, Inspector::Protocol::Page::CoordinateSystem coordinateSystem) +Inspector::Protocol::ErrorStringOr InspectorPageAgent::snapshotRect(int x, int y, int width, int height, Inspector::Protocol::Page::CoordinateSystem coordinateSystem, std::optional&& omitDeviceScaleFactor) { - SnapshotOptions options { { }, PixelFormat::BGRA8, DestinationColorSpace::SRGB() }; + SnapshotOptions options { { }, ImageBufferPixelFormat::BGRA8, DestinationColorSpace::SRGB() }; if (coordinateSystem == Inspector::Protocol::Page::CoordinateSystem::Viewport) options.flags.add(SnapshotFlags::InViewCoordinates); + if (omitDeviceScaleFactor.has_value() && *omitDeviceScaleFactor) @@ -4322,7 +4517,7 @@ index 84eb4dc2127420db005063e0f60d5ad950081719..5f85bc7ba23b9488d03bf7dfa5da645f +{ + bool success = WTF::setTimeZoneOverride(timeZone); + if (!success) -+ return makeUnexpected("Invalid time zone "_s + timeZone); ++ return makeUnexpected(makeString("Invalid time zone "_s, timeZone)); + + return { }; +} @@ -5193,10 +5388,10 @@ index 2ca6ee01a341eefead66a92e2af77875263a9df3..131bbd8c268a748b43cac105370d7b73 protected: static SameSiteInfo sameSiteInfo(const Document&, IsForDOMCookieAccess = IsForDOMCookieAccess::No); diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp -index fa516b91382c4c784d30f38a6782e7a29007e7d4..89c56c793e97771f75b109a2fd04a0e671425aa1 100644 +index 22b83991d32085991eb502728333dc7648ab883f..6bd27284c1799448f387338738c7a7fbc8cc3690 100644 --- a/Source/WebCore/loader/DocumentLoader.cpp +++ b/Source/WebCore/loader/DocumentLoader.cpp -@@ -765,8 +765,10 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc +@@ -766,8 +766,10 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc if (!didReceiveRedirectResponse) return completionHandler(WTFMove(newRequest)); @@ -5228,7 +5423,7 @@ index fa516b91382c4c784d30f38a6782e7a29007e7d4..89c56c793e97771f75b109a2fd04a0e6 { ASSERT(navigationID); diff --git a/Source/WebCore/loader/DocumentLoader.h b/Source/WebCore/loader/DocumentLoader.h -index 05c8c4a42b9273c15889c0ab11199b67954dfe15..6947545eef196ef105c6292c8734b5b86d7dc8f2 100644 +index 3f7d86c6ba98d5d5a6ad716bd3d78885bb9411e0..94dbc77f454b70733d7f15db8fac00ac72fb8657 100644 --- a/Source/WebCore/loader/DocumentLoader.h +++ b/Source/WebCore/loader/DocumentLoader.h @@ -207,6 +207,8 @@ public: @@ -5241,10 +5436,10 @@ index 05c8c4a42b9273c15889c0ab11199b67954dfe15..6947545eef196ef105c6292c8734b5b8 CheckedPtr checkedFrameLoader() const; WEBCORE_EXPORT SubresourceLoader* mainResourceLoader() const; diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp -index 213b18eed87af84e1f36ccda4008e3d2ea24721f..b8d3567ef231c363a5b40caef6427657d4cdb73f 100644 +index 231a764c5b88f9986e324587e62f94abd119fff8..260b5368d67f75616e7aeba7c3170fb52af5f054 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp -@@ -1290,6 +1290,7 @@ void FrameLoader::loadInSameDocument(URL url, RefPtr stat +@@ -1322,6 +1322,7 @@ void FrameLoader::loadInSameDocument(URL url, RefPtr stat } m_client->dispatchDidNavigateWithinPage(); @@ -5252,16 +5447,15 @@ index 213b18eed87af84e1f36ccda4008e3d2ea24721f..b8d3567ef231c363a5b40caef6427657 document->statePopped(stateObject ? stateObject.releaseNonNull() : SerializedScriptValue::nullValue()); m_client->dispatchDidPopStateWithinPage(); -@@ -1781,6 +1782,8 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t - if (!dispatchNavigateEvent(newURL, type, loader->triggeringAction(), NavigationHistoryBehavior::Auto, true)) - return; +@@ -1821,6 +1822,7 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t + const String& httpMethod = loader->request().httpMethod(); + if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker().loadType(), newURL)) { + loader->replacedByFragmentNavigation(m_frame); -+ + RefPtr oldDocumentLoader = m_documentLoader; NavigationAction action { frame->protectedDocument().releaseNonNull(), loader->request(), InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), policyChecker().loadType(), isFormSubmission }; - oldDocumentLoader->setTriggeringAction(WTFMove(action)); -@@ -1814,7 +1817,9 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t +@@ -1857,7 +1859,9 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t } RELEASE_ASSERT(!isBackForwardLoadType(policyChecker().loadType()) || frame->history().provisionalItem()); @@ -5271,21 +5465,17 @@ index 213b18eed87af84e1f36ccda4008e3d2ea24721f..b8d3567ef231c363a5b40caef6427657 continueLoadAfterNavigationPolicy(request, RefPtr { weakFormState.get() }.get(), navigationPolicyDecision, allowNavigationToInvalidURL); completionHandler(); }, PolicyDecisionMode::Asynchronous); -@@ -3079,14 +3084,19 @@ String FrameLoader::userAgent(const URL& url) const +@@ -3126,10 +3130,15 @@ String FrameLoader::userAgent(const URL& url) const String FrameLoader::navigatorPlatform() const { + String platform; + - if (RefPtr localFrame = dynamicDowncast(m_frame->mainFrame())) { - if (RefPtr documentLoader = localFrame->loader().activeDocumentLoader()) { - auto& customNavigatorPlatform = documentLoader->customNavigatorPlatform(); - if (!customNavigatorPlatform.isEmpty()) -- return customNavigatorPlatform; -+ platform = customNavigatorPlatform; - } - } + auto customNavigatorPlatform = m_frame->mainFrame().customNavigatorPlatform(); + if (!customNavigatorPlatform.isEmpty()) +- return customNavigatorPlatform; - return String(); ++ platform = customNavigatorPlatform; + + InspectorInstrumentation::applyPlatformOverride(m_frame, platform); + @@ -5293,7 +5483,7 @@ index 213b18eed87af84e1f36ccda4008e3d2ea24721f..b8d3567ef231c363a5b40caef6427657 } void FrameLoader::dispatchOnloadEvents() -@@ -3544,6 +3554,8 @@ void FrameLoader::receivedMainResourceError(const ResourceError& error, LoadWill +@@ -3594,6 +3603,8 @@ void FrameLoader::receivedMainResourceError(const ResourceError& error, LoadWill checkCompleted(); if (frame->page()) checkLoadComplete(loadWillContinueInAnotherProcess); @@ -5302,7 +5492,7 @@ index 213b18eed87af84e1f36ccda4008e3d2ea24721f..b8d3567ef231c363a5b40caef6427657 } void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequest& request, const SecurityOrigin* requesterOrigin, bool shouldContinue, NavigationHistoryBehavior historyHandling) -@@ -4422,9 +4434,6 @@ String FrameLoader::referrer() const +@@ -4476,9 +4487,6 @@ String FrameLoader::referrer() const void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() { @@ -5312,7 +5502,7 @@ index 213b18eed87af84e1f36ccda4008e3d2ea24721f..b8d3567ef231c363a5b40caef6427657 Vector> worlds; ScriptController::getAllWorlds(worlds); for (auto& world : worlds) -@@ -4434,13 +4443,12 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() +@@ -4488,13 +4496,12 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld& world) { Ref frame = m_frame.get(); @@ -5345,10 +5535,10 @@ index 91340dc21042f545592b442bc42dbceed06219b2..f3591fe333761b10a25ddaf4a4f8d721 virtual bool shouldPerformSecurityChecks() const { return false; } virtual bool havePerformedSecurityChecks(const ResourceResponse&) const { return false; } diff --git a/Source/WebCore/loader/NavigationScheduler.cpp b/Source/WebCore/loader/NavigationScheduler.cpp -index 16e15b0565a75933a4f10828cca190f4b055a05b..0189a5b49bc593b63413abb16524027f24daacc6 100644 +index 9af8f29088f6ac3841fe68cd8629ef18a6ca5675..f28e41daee51b3d4fd6c185532c6acf941dd30ad 100644 --- a/Source/WebCore/loader/NavigationScheduler.cpp +++ b/Source/WebCore/loader/NavigationScheduler.cpp -@@ -695,7 +695,7 @@ void NavigationScheduler::startTimer() +@@ -703,7 +703,7 @@ void NavigationScheduler::startTimer() Seconds delay = 1_s * m_redirect->delay(); m_timer.startOneShot(delay); @@ -5392,10 +5582,10 @@ index b74c5258454b0df9f74aa8a5297674b733925685..b6c3999745368c7f7e2e6176bfca6dc0 void ProgressTracker::incrementProgress(ResourceLoaderIdentifier identifier, const ResourceResponse& response) diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp -index bf685913e33c7d5959c158b7735321682d5e7208..8c184ff01799f5172c9a774a115014c5f6f18270 100644 +index 0838ffc91ef3b9b3990bb05bb0c52dec1128f92e..6f536869db9c2aa0264d2e800bc9f3e7adb9a379 100644 --- a/Source/WebCore/loader/cache/CachedResourceLoader.cpp +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp -@@ -1068,8 +1068,11 @@ ResourceErrorOr> CachedResourceLoader::requ +@@ -1089,8 +1089,11 @@ ResourceErrorOr> CachedResourceLoader::requ request.updateReferrerPolicy(document() ? document()->referrerPolicy() : ReferrerPolicy::Default); @@ -5409,7 +5599,7 @@ index bf685913e33c7d5959c158b7735321682d5e7208..8c184ff01799f5172c9a774a115014c5 Ref page = *frame->page(); -@@ -1682,8 +1685,9 @@ Vector> CachedResourceLoader::allCachedSVGImages() const +@@ -1703,8 +1706,9 @@ Vector> CachedResourceLoader::allCachedSVGImages() const ResourceErrorOr> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request) { @@ -5422,10 +5612,10 @@ index bf685913e33c7d5959c158b7735321682d5e7208..8c184ff01799f5172c9a774a115014c5 ASSERT(m_document); if (request.charset().isEmpty() && m_document && (type == CachedResource::Type::Script || type == CachedResource::Type::CSSStyleSheet)) diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h -index 9225486c5aaf841df1d5de85c40928307b4d72dc..93bc7be141be1043a057b6ad6c297397a5c6cfa8 100644 +index 1712a19473ab6db4db50a1d746846a091dce7417..ed27724b92ba34e0f2a543f398701f31a6764bc3 100644 --- a/Source/WebCore/page/ChromeClient.h +++ b/Source/WebCore/page/ChromeClient.h -@@ -335,7 +335,7 @@ public: +@@ -339,7 +339,7 @@ public: #endif #if ENABLE(ORIENTATION_EVENTS) @@ -5435,10 +5625,10 @@ index 9225486c5aaf841df1d5de85c40928307b4d72dc..93bc7be141be1043a057b6ad6c297397 #if ENABLE(INPUT_TYPE_COLOR) diff --git a/Source/WebCore/page/EventHandler.cpp b/Source/WebCore/page/EventHandler.cpp -index ff5f86a7c24bdb06a7ae0d6015de450a323cdae0..1760ac36123b4543279d7278fd9d328637da9ca3 100644 +index 82f9dab5460c78f49a48adfb8c1fcaf19b0a0b9e..b3c8a054f7c6e0b9db2cdc50fd077c687ccdd62b 100644 --- a/Source/WebCore/page/EventHandler.cpp +++ b/Source/WebCore/page/EventHandler.cpp -@@ -4326,6 +4326,12 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr +@@ -4346,6 +4346,12 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr if (!document) return false; @@ -5451,7 +5641,7 @@ index ff5f86a7c24bdb06a7ae0d6015de450a323cdae0..1760ac36123b4543279d7278fd9d3286 dragState().dataTransfer = DataTransfer::createForDrag(*document); auto hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::No; -@@ -4952,7 +4958,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -4972,7 +4978,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; @@ -5460,7 +5650,7 @@ index ff5f86a7c24bdb06a7ae0d6015de450a323cdae0..1760ac36123b4543279d7278fd9d3286 bool pointerCancelled = false; #endif RefPtr touchTarget; -@@ -4999,7 +5005,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -5019,7 +5025,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve // we also remove it from the map. touchTarget = m_originatingTouchPointTargets.take(touchPointTargetKey); @@ -5469,7 +5659,7 @@ index ff5f86a7c24bdb06a7ae0d6015de450a323cdae0..1760ac36123b4543279d7278fd9d3286 HitTestResult result = hitTestResultAtPoint(pagePoint, hitType | HitTestRequest::Type::AllowChildFrameContent); pointerTarget = result.targetElement(); pointerCancelled = (pointerTarget != touchTarget); -@@ -5022,7 +5028,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve +@@ -5042,7 +5048,7 @@ HandleUserInputEventResult EventHandler::handleTouchEvent(const PlatformTouchEve if (!targetFrame) continue; @@ -5518,7 +5708,7 @@ index 084db825e36bd46126fea95fc7183bf2e931be7e..dac3caef67600f8c7c945fd857a91140 } diff --git a/Source/WebCore/page/FrameSnapshotting.h b/Source/WebCore/page/FrameSnapshotting.h -index 055e3d8b2821c366b403fcc6ea0f2c9b1bd8029b..0e1bb019ba0ad1f76ef62abb164b80ccf26755be 100644 +index 5b365008debe6b8d5a95a572a4c2725b0a7a519d..2c6ad49a45a1759f446aced179c0c5a7bfb4b07a 100644 --- a/Source/WebCore/page/FrameSnapshotting.h +++ b/Source/WebCore/page/FrameSnapshotting.h @@ -55,6 +55,7 @@ enum class SnapshotFlags : uint16_t { @@ -5530,7 +5720,7 @@ index 055e3d8b2821c366b403fcc6ea0f2c9b1bd8029b..0e1bb019ba0ad1f76ef62abb164b80cc struct SnapshotOptions { diff --git a/Source/WebCore/page/History.cpp b/Source/WebCore/page/History.cpp -index 2b5e1e58bad69cba3d4c6de1e0d35f01ad9bb5f2..098355b3f71ebc2ec51862671a860d2964850c88 100644 +index 097302d7502b8a28db7c7ab8d778d17365b03533..4ab5ee0e71b2010ea5297e8d1d4ec03b9fd27097 100644 --- a/Source/WebCore/page/History.cpp +++ b/Source/WebCore/page/History.cpp @@ -32,6 +32,7 @@ @@ -5541,16 +5731,17 @@ index 2b5e1e58bad69cba3d4c6de1e0d35f01ad9bb5f2..098355b3f71ebc2ec51862671a860d29 #include "LocalFrame.h" #include "LocalFrameLoaderClient.h" #include "Logging.h" -@@ -304,6 +305,7 @@ ExceptionOr History::stateObjectAdded(RefPtr&& data +@@ -304,6 +305,8 @@ ExceptionOr History::stateObjectAdded(RefPtr&& data - if (!urlString.isEmpty()) - frame->protectedDocument()->updateURLForPushOrReplaceState(fullURL); + auto historyBehavior = stateObjectType == StateObjectType::Replace ? NavigationHistoryBehavior::Replace : NavigationHistoryBehavior::Push; + frame->loader().updateURLAndHistory(fullURL, WTFMove(data), historyBehavior); + InspectorInstrumentation::didNavigateWithinPage(*frame); ++ + return { }; + } - if (stateObjectType == StateObjectType::Push) { - frame->checkedHistory()->pushState(WTFMove(data), fullURL.string()); diff --git a/Source/WebCore/page/LocalFrame.cpp b/Source/WebCore/page/LocalFrame.cpp -index 114dbb1c6838198c7240af78726dd58073051704..26a2eebe64e0d34710ddd02b3dbb350ddef53f3a 100644 +index 119ae91c675f4bf1d03b9fc4878c9449416583b8..f0a10738508ca82499b474b8f221fdaf18ec3efc 100644 --- a/Source/WebCore/page/LocalFrame.cpp +++ b/Source/WebCore/page/LocalFrame.cpp @@ -40,6 +40,7 @@ @@ -5594,7 +5785,7 @@ index 114dbb1c6838198c7240af78726dd58073051704..26a2eebe64e0d34710ddd02b3dbb350d return 0; } #endif // ENABLE(ORIENTATION_EVENTS) -@@ -1340,6 +1344,362 @@ String LocalFrame::customUserAgentAsSiteSpecificQuirks() const +@@ -1350,6 +1354,362 @@ OptionSet LocalFrame::advancedPrivacyProtections() c return { }; } @@ -5958,7 +6149,7 @@ index 114dbb1c6838198c7240af78726dd58073051704..26a2eebe64e0d34710ddd02b3dbb350d #undef FRAME_RELEASE_LOG_ERROR diff --git a/Source/WebCore/page/LocalFrame.h b/Source/WebCore/page/LocalFrame.h -index aef60e6fe0be63ec1aca391fde6a1bc961a9e768..fd43db619f60121d4a2dbadb08c4c8e1d4092226 100644 +index c2fc366ed759fb9e1d83821c4fff4b05a6b8fe35..d3eec8aa3129ae6567b1402968374015f11efaa2 100644 --- a/Source/WebCore/page/LocalFrame.h +++ b/Source/WebCore/page/LocalFrame.h @@ -28,8 +28,10 @@ @@ -6020,7 +6211,7 @@ index aef60e6fe0be63ec1aca391fde6a1bc961a9e768..fd43db619f60121d4a2dbadb08c4c8e1 void selfOnlyRef(); void selfOnlyDeref(); -@@ -353,7 +355,6 @@ private: +@@ -355,7 +357,6 @@ private: #if ENABLE(DATA_DETECTION) std::unique_ptr m_dataDetectionResults; #endif @@ -6028,7 +6219,7 @@ index aef60e6fe0be63ec1aca391fde6a1bc961a9e768..fd43db619f60121d4a2dbadb08c4c8e1 void betterApproximateNode(const IntPoint& testPoint, const NodeQualifier&, Node*& best, Node* failedNode, IntPoint& bestPoint, IntRect& bestRect, const IntRect& testRect); bool hitTestResultAtViewportLocation(const FloatPoint& viewportLocation, HitTestResult&, IntPoint& center); -@@ -361,6 +362,7 @@ private: +@@ -363,6 +364,7 @@ private: enum class ShouldFindRootEditableElement : bool { No, Yes }; Node* qualifyingNodeAtViewportLocation(const FloatPoint& viewportLocation, FloatPoint& adjustedViewportLocation, const NodeQualifier&, ShouldApproximate, ShouldFindRootEditableElement = ShouldFindRootEditableElement::Yes); @@ -6037,10 +6228,10 @@ index aef60e6fe0be63ec1aca391fde6a1bc961a9e768..fd43db619f60121d4a2dbadb08c4c8e1 ViewportArguments m_viewportArguments; diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp -index 34c1e3039b5db70d15493524c96e507c2ce95199..2eb25218240da0a7878d9db2be1e9d3301e5d990 100644 +index 72ffbbfd28edb23a93c8c8fba76ab25a7f7b0f72..8d787339e6b570e210765bb1cddec6c4705c2a25 100644 --- a/Source/WebCore/page/Page.cpp +++ b/Source/WebCore/page/Page.cpp -@@ -579,6 +579,45 @@ void Page::setOverrideViewportArguments(const std::optional& +@@ -592,6 +592,45 @@ void Page::setOverrideViewportArguments(const std::optional& document->updateViewportArguments(); } @@ -6086,7 +6277,7 @@ index 34c1e3039b5db70d15493524c96e507c2ce95199..2eb25218240da0a7878d9db2be1e9d33 ScrollingCoordinator* Page::scrollingCoordinator() { if (!m_scrollingCoordinator && m_settings->scrollingCoordinatorEnabled()) { -@@ -3868,6 +3907,26 @@ void Page::setUseDarkAppearanceOverride(std::optional valueOverride) +@@ -3873,6 +3912,26 @@ void Page::setUseDarkAppearanceOverride(std::optional valueOverride) #endif } @@ -6114,10 +6305,10 @@ index 34c1e3039b5db70d15493524c96e507c2ce95199..2eb25218240da0a7878d9db2be1e9d33 { if (insets == m_fullscreenInsets) diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h -index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd8a083b9d 100644 +index 67a128b27cdd46115158ec20e7ce38dd56fde3d6..8ec6a45348412257db2b027f6f15027a21725466 100644 --- a/Source/WebCore/page/Page.h +++ b/Source/WebCore/page/Page.h -@@ -321,6 +321,9 @@ public: +@@ -341,6 +341,9 @@ public: const std::optional& overrideViewportArguments() const { return m_overrideViewportArguments; } WEBCORE_EXPORT void setOverrideViewportArguments(const std::optional&); @@ -6127,7 +6318,7 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd static void refreshPlugins(bool reload); WEBCORE_EXPORT PluginData& pluginData(); void clearPluginData(); -@@ -385,6 +388,10 @@ public: +@@ -405,6 +408,10 @@ public: #if ENABLE(DRAG_SUPPORT) DragController& dragController() { return m_dragController.get(); } const DragController& dragController() const { return m_dragController.get(); } @@ -6138,7 +6329,7 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd #endif FocusController& focusController() const { return *m_focusController; } WEBCORE_EXPORT CheckedRef checkedFocusController() const; -@@ -568,6 +575,10 @@ public: +@@ -588,6 +595,10 @@ public: WEBCORE_EXPORT void effectiveAppearanceDidChange(bool useDarkAppearance, bool useElevatedUserInterfaceLevel); bool defaultUseDarkAppearance() const { return m_useDarkAppearance; } void setUseDarkAppearanceOverride(std::optional); @@ -6149,7 +6340,7 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd #if ENABLE(TEXT_AUTOSIZING) float textAutosizingWidth() const { return m_textAutosizingWidth; } -@@ -1015,6 +1026,11 @@ public: +@@ -1035,6 +1046,11 @@ public: WEBCORE_EXPORT void setInteractionRegionsEnabled(bool); #endif @@ -6161,7 +6352,7 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY) DeviceOrientationUpdateProvider* deviceOrientationUpdateProvider() const { return m_deviceOrientationUpdateProvider.get(); } #endif -@@ -1197,6 +1213,9 @@ private: +@@ -1248,6 +1264,9 @@ private: #if ENABLE(DRAG_SUPPORT) UniqueRef m_dragController; @@ -6171,7 +6362,7 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd #endif std::unique_ptr m_focusController; #if ENABLE(CONTEXT_MENUS) -@@ -1275,6 +1294,8 @@ private: +@@ -1327,6 +1346,8 @@ private: bool m_useElevatedUserInterfaceLevel { false }; bool m_useDarkAppearance { false }; std::optional m_useDarkAppearanceOverride; @@ -6180,7 +6371,7 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd #if ENABLE(TEXT_AUTOSIZING) float m_textAutosizingWidth { 0 }; -@@ -1454,6 +1475,11 @@ private: +@@ -1506,6 +1527,11 @@ private: #endif std::optional m_overrideViewportArguments; @@ -6193,10 +6384,10 @@ index 39c4f65e563afee7e7bb770a7b174eb64432f04c..2aae08e7fd481aeae907d0acaae595fd #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 18e3cde7b9ae368de4b87c88c2f8324b77cbd7c8..1ebc51b6d81fc6ae17124e7be7da039609df03d0 100644 +index 8fcc6f546882998be87d52c296c4637c6fd5af0f..73e3adc185923ba249f39f070a0b9fbb20418b0b 100644 --- a/Source/WebCore/page/PageConsoleClient.cpp +++ b/Source/WebCore/page/PageConsoleClient.cpp -@@ -439,4 +439,9 @@ Ref PageConsoleClient::protectedPage() const +@@ -434,4 +434,9 @@ Ref PageConsoleClient::protectedPage() const return m_page.get(); } @@ -6269,10 +6460,10 @@ index 91a9847a4083393e225f42e71c2dd590a88c0289..b0838c84e4ba24378db42f40a68856af #endif ; diff --git a/Source/WebCore/page/Screen.cpp b/Source/WebCore/page/Screen.cpp -index 4e4dfdebe954bf3f047d3a86a758dfd0913f732e..da2bcd0256cdfc2ba28fb07094abd0b6f9c5f754 100644 +index 706f67ca9d5b98f519080fad94d424a130b18a0e..c98147603680bd21c83a0b2b1593fa84a733f22f 100644 --- a/Source/WebCore/page/Screen.cpp +++ b/Source/WebCore/page/Screen.cpp -@@ -111,6 +111,9 @@ int Screen::availLeft() const +@@ -102,6 +102,9 @@ int Screen::availLeft() const if (fingerprintingProtectionsEnabled(*frame)) return 0; @@ -6282,7 +6473,7 @@ index 4e4dfdebe954bf3f047d3a86a758dfd0913f732e..da2bcd0256cdfc2ba28fb07094abd0b6 return static_cast(screenAvailableRect(frame->protectedView().get()).x()); } -@@ -126,6 +129,9 @@ int Screen::availTop() const +@@ -117,6 +120,9 @@ int Screen::availTop() const if (fingerprintingProtectionsEnabled(*frame)) return 0; @@ -6292,7 +6483,7 @@ index 4e4dfdebe954bf3f047d3a86a758dfd0913f732e..da2bcd0256cdfc2ba28fb07094abd0b6 return static_cast(screenAvailableRect(frame->protectedView().get()).y()); } -@@ -141,6 +147,9 @@ int Screen::availHeight() const +@@ -132,6 +138,9 @@ int Screen::availHeight() const if (fingerprintingProtectionsEnabled(*frame)) return static_cast(frame->screenSize().height()); @@ -6302,7 +6493,7 @@ index 4e4dfdebe954bf3f047d3a86a758dfd0913f732e..da2bcd0256cdfc2ba28fb07094abd0b6 return static_cast(screenAvailableRect(frame->protectedView().get()).height()); } -@@ -156,6 +165,9 @@ int Screen::availWidth() const +@@ -147,6 +156,9 @@ int Screen::availWidth() const if (fingerprintingProtectionsEnabled(*frame)) return static_cast(frame->screenSize().width()); @@ -6313,10 +6504,10 @@ index 4e4dfdebe954bf3f047d3a86a758dfd0913f732e..da2bcd0256cdfc2ba28fb07094abd0b6 } diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp -index 2b3cffe02206bf456a7c39ef66b3176f89a33b86..64bffe692552e7fbbbabd71e11de9c40c6ec3951 100644 +index 50cf04e63a7de97d508448f181429f5b7fa438a3..6c2a5c085436eac669bce5f1d9094764ba0c6250 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp -@@ -343,6 +343,8 @@ bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtoc +@@ -344,6 +344,8 @@ bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtoc template typename std::enable_if::value, bool>::type ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, Predicate&& predicate, Args&&... args) const { @@ -6325,7 +6516,7 @@ index 2b3cffe02206bf456a7c39ef66b3176f89a33b86..64bffe692552e7fbbbabd71e11de9c40 bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly; for (auto& policy : m_policies) { if (policy->isReportOnly() != isReportOnly) -@@ -356,6 +358,8 @@ typename std::enable_if bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const { @@ -6334,7 +6525,7 @@ index 2b3cffe02206bf456a7c39ef66b3176f89a33b86..64bffe692552e7fbbbabd71e11de9c40 bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly; bool isAllowed = true; for (auto& policy : m_policies) { -@@ -372,6 +376,8 @@ bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposit +@@ -373,6 +377,8 @@ bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposit template bool ContentSecurityPolicy::allPoliciesAllow(ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const { @@ -6475,7 +6666,7 @@ index 34f49853b1d5ac8d8409bbd36d203bf7dfbb08e8..6d2808d510413e89ae9851733437d15b bool m_disallowFileAccess { false }; }; diff --git a/Source/WebCore/platform/DragImage.cpp b/Source/WebCore/platform/DragImage.cpp -index dc894343a5d9ce0e45a370a83bebf97fdcbeccc5..00003bd9bada8f134b6cc49f47c8c5cb459d1ebd 100644 +index c359242a7967dab94b8dc3c276a6df5473527145..64b0c6a0bfdf27a0305c25e8b8e0cda637450aa4 100644 --- a/Source/WebCore/platform/DragImage.cpp +++ b/Source/WebCore/platform/DragImage.cpp @@ -280,7 +280,7 @@ DragImage::~DragImage() @@ -6488,10 +6679,10 @@ index dc894343a5d9ce0e45a370a83bebf97fdcbeccc5..00003bd9bada8f134b6cc49f47c8c5cb IntSize dragImageSize(DragImageRef) { diff --git a/Source/WebCore/platform/MIMETypeRegistry.cpp b/Source/WebCore/platform/MIMETypeRegistry.cpp -index a4e3064903c7bfb3d64244804828fe1c6d76c222..2dfa62b08f83ff5e576fcb29962fe885fdffec86 100644 +index 092649a26549e8fc7c5683ad47585b4d7b1a0eab..7944352d9218b6237cfb4baceb686fcfa056c0ef 100644 --- a/Source/WebCore/platform/MIMETypeRegistry.cpp +++ b/Source/WebCore/platform/MIMETypeRegistry.cpp -@@ -666,6 +666,9 @@ bool MIMETypeRegistry::canShowMIMEType(const String& mimeType) +@@ -663,6 +663,9 @@ bool MIMETypeRegistry::canShowMIMEType(const String& mimeType) if (startsWithLettersIgnoringASCIICase(mimeType, "text/"_s)) return !isUnsupportedTextMIMEType(mimeType); @@ -6634,7 +6825,7 @@ index ae46341ba71c7f6df7c607bd852338cdb7f83fe1..b318c0771192344a6891c1f097cb0b93 +} // namespace WebCore +#endif diff --git a/Source/WebCore/platform/PlatformScreen.h b/Source/WebCore/platform/PlatformScreen.h -index 9db6f2d77f14bd77f075a4c826dea1768452fcb9..7a7d48b016037d3865603619b2c4ca67e737bc42 100644 +index 6c64c7040eb190c3d67380070e884a8230029c26..d0f8341c538cbc2323ac0074a5ef3226d00a5fd6 100644 --- a/Source/WebCore/platform/PlatformScreen.h +++ b/Source/WebCore/platform/PlatformScreen.h @@ -151,13 +151,18 @@ WEBCORE_EXPORT float screenScaleFactor(UIScreen * = nullptr); @@ -6806,10 +6997,10 @@ index d137ffd1a8ed0b788bd28197c6d7e9f7d14e852f..dcf8bf3f7ee6b037a370712e2ac36b6e if (!image || !encodeImage(image, mimeType, &encodedImage)) return { }; diff --git a/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.h b/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.h -index a82b748682f984fcdd4f5413d0254e0f5573f043..2c3d4bba92c63235c124a400d89455499aa3a189 100644 +index 5b659c763b9754b025a63f89522954cc39915b9a..448b50a2b131361a75d3f816cdcbb6a102551280 100644 --- a/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.h +++ b/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.h -@@ -38,7 +38,7 @@ WEBCORE_EXPORT uint8_t verifyImageBufferIsBigEnough(const void* buffer, size_t b +@@ -38,7 +38,7 @@ WEBCORE_EXPORT uint8_t verifyImageBufferIsBigEnough(std::span buf RetainPtr utiFromImageBufferMIMEType(const String& mimeType); CFStringRef jpegUTI(); @@ -6830,8 +7021,21 @@ index 6f43c048cd8354c97097c8365b772b92a429b670..7bccf4f7921fb3b0848781252cd69b4b namespace WebCore { +diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp +index 6f4b01b50a2278bfd0d0a072f5d1b6b367128706..f258f5f3e769c8c3a78a7801ebde09234c0298f7 100644 +--- a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp ++++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp +@@ -860,7 +860,7 @@ MediaPlayerEnums::SupportsType GStreamerRegistryScanner::isContentTypeSupported( + return SupportsType::IsNotSupported; + } + #else +- if (!factories.hasElementForMediaType(ElementFactories::Type::Decryptor, "application/x-webm-enc")) ++ if (!factories.hasElementForMediaType(ElementFactories::Type::Decryptor, "application/x-webm-enc"_s)) + return SupportsType::IsNotSupported; + #endif // GST_CHECK_VERSION(1, 22, 0) + } diff --git a/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp b/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp -index f6d2e39b6a9f454067734b3cb5a03ed243450dfa..fd78c6b39e35b57677ff6da0f66e5a76998efa47 100644 +index eb9710f7d61121f2414c8aa6734dc27653e292bb..9c0c7b350a6140681242ce36a180d6866e3e0fc2 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, @@ -6849,7 +7053,7 @@ index f6d2e39b6a9f454067734b3cb5a03ed243450dfa..fd78c6b39e35b57677ff6da0f66e5a76 + if (!numItems) + return numItems; + -+ if (font->platformData().isSystemFont() || font->platformData().hasVariations()) ++ if (font->platformData().hasVariations()) + return numItems; + + bool allGoodCharacters = true; @@ -6878,20 +7082,21 @@ index f6d2e39b6a9f454067734b3cb5a03ed243450dfa..fd78c6b39e35b57677ff6da0f66e5a76 // Determine the string for this item. const UChar* str = cp.data() + items[i].iCharPos; diff --git a/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp b/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp -index 8185125ac6aa07d4ee99d676d82c79932c2d84fd..0b2a8da1463c905d1e6f1448cdc0c0f63228b30f 100644 +index 979ec06ecd698b60066dc6775bf1b647624baa56..14d06fdd5b7b7df2a1942ed975cebf117beb4d1f 100644 --- a/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp +++ b/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp -@@ -37,7 +37,9 @@ +@@ -37,8 +37,10 @@ #include #include #include +#include #include + #include +#include namespace WebCore { -@@ -1301,6 +1303,246 @@ int PlatformKeyboardEvent::windowsKeyCodeForGdkKeyCode(unsigned keycode) +@@ -1302,6 +1304,246 @@ int PlatformKeyboardEvent::windowsKeyCodeForGdkKeyCode(unsigned keycode) } @@ -7368,21 +7573,22 @@ index ae439e30f1fb239d18e1164e8896dfb272c75673..4cf29eda13d1f2dc2f03750c0ef8985b #endif // USE(LIBWPE) diff --git a/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp b/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp -index 76f1c37bb02952511a95331d2cc778eabc2375f5..c36cc0ca55e8797264100da69081dace30654393 100644 +index a8674916429908cabb86bd95dc5b3da217e1556b..75cbb518708ebd459b1ed8e3206cdb02e502067d 100644 --- a/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp +++ b/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp -@@ -30,8 +30,10 @@ +@@ -30,9 +30,11 @@ #include "WindowsKeyboardCodes.h" #include +#include #include + #include #include +#include namespace WebCore { -@@ -1302,6 +1304,246 @@ int PlatformKeyboardEvent::windowsKeyCodeForWPEKeyCode(unsigned keycode) +@@ -1303,6 +1305,246 @@ int PlatformKeyboardEvent::windowsKeyCodeForWPEKeyCode(unsigned keycode) return 0; } @@ -7746,23 +7952,11 @@ index 0552842dbe3f3a2c12a504178f5a8ca977e1c4db..2ef3b1b459d8a9b4e86b4556feeb4f07 namespace WebCore { class WEBCORE_EXPORT LibWebRTCProviderGStreamer : public LibWebRTCProvider { -diff --git a/Source/WebCore/platform/network/DNS.cpp b/Source/WebCore/platform/network/DNS.cpp -index 3d9581954d0d01ccbe66c8c45d1ff36e5a978e04..a4c95274cf97237cc0dbb25d0641c944aa684538 100644 ---- a/Source/WebCore/platform/network/DNS.cpp -+++ b/Source/WebCore/platform/network/DNS.cpp -@@ -30,6 +30,7 @@ - #include "DNSResolveQueue.h" - #include - #include -+#include - - #if OS(UNIX) - #include diff --git a/Source/WebCore/platform/network/HTTPHeaderMap.cpp b/Source/WebCore/platform/network/HTTPHeaderMap.cpp -index 80b35d678bb9f3cd3f34af6258785b0c8a088347..b72058db305a8e6228893c5a8ce3f578861dae89 100644 +index 65679251a5c66afcf60ed4d4267169eefed745f5..2cb9dc1f0e777fd172e52f5a6c8f4d6910c08c47 100644 --- a/Source/WebCore/platform/network/HTTPHeaderMap.cpp +++ b/Source/WebCore/platform/network/HTTPHeaderMap.cpp -@@ -235,8 +235,11 @@ void HTTPHeaderMap::add(HTTPHeaderName name, const String& value) +@@ -236,8 +236,11 @@ void HTTPHeaderMap::add(HTTPHeaderName name, const String& value) auto index = m_commonHeaders.findIf([&](auto& header) { return header.key == name; }); @@ -7789,10 +7983,10 @@ index cf43da22a5f7674a1b24c4d39b492e0b8c318f3a..524e671cf6959fc48aefe4ec5a0611cb WEBCORE_EXPORT void setCookie(const Cookie&); WEBCORE_EXPORT void setCookies(const Vector&, const URL&, const URL& mainDocumentURL); diff --git a/Source/WebCore/platform/network/ResourceResponseBase.cpp b/Source/WebCore/platform/network/ResourceResponseBase.cpp -index e1bf41ea3b643d3841167abca8a6bd55f5f161f1..d7df4847f04fe832196f269d32c1ce1fc54cc650 100644 +index 97e5f6f38b9c036e8fc2fe6f4d4108f458113816..1391de13e58d82fd0ab9bf66bbc922d5bffa0627 100644 --- a/Source/WebCore/platform/network/ResourceResponseBase.cpp +++ b/Source/WebCore/platform/network/ResourceResponseBase.cpp -@@ -74,6 +74,7 @@ ResourceResponseBase::ResourceResponseBase(std::optional d +@@ -75,6 +75,7 @@ ResourceResponseBase::ResourceResponseBase(std::optional d , m_httpStatusText(data ? data->httpStatusText : String { }) , m_httpVersion(data ? data->httpVersion : String { }) , m_httpHeaderFields(data ? data->httpHeaderFields : HTTPHeaderMap { }) @@ -7800,7 +7994,7 @@ index e1bf41ea3b643d3841167abca8a6bd55f5f161f1..d7df4847f04fe832196f269d32c1ce1f , m_networkLoadMetrics(data && data->networkLoadMetrics ? Box::create(*data->networkLoadMetrics) : Box { }) , m_certificateInfo(data ? data->certificateInfo : std::nullopt) , m_httpStatusCode(data ? data->httpStatusCode : 0) -@@ -892,6 +893,7 @@ std::optional ResourceResponseBase::getResponseData() cons +@@ -893,6 +894,7 @@ std::optional ResourceResponseBase::getResponseData() cons String { m_httpStatusText }, String { m_httpVersion }, HTTPHeaderMap { m_httpHeaderFields }, @@ -7808,7 +8002,7 @@ index e1bf41ea3b643d3841167abca8a6bd55f5f161f1..d7df4847f04fe832196f269d32c1ce1f m_networkLoadMetrics ? std::optional(*m_networkLoadMetrics) : std::nullopt, m_source, m_type, -@@ -965,6 +967,11 @@ std::optional Coder Coder httpStatusCode; decoder >> httpStatusCode; if (!httpStatusCode) -@@ -1019,6 +1026,7 @@ std::optional Coder Coder&&, size_t); diff --git a/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp b/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp -index 798c95d09029dd354980a227bf906c43d9b75ee3..83461976697da56c2c5e51f56fd3e6fe1a9e28c0 100644 +index 7567442a6bdccbe755b2cf5e3fa42265a065a9e1..4886cf0b5a6bc0b8a270d763ad481f5bc6f0d1c2 100644 --- a/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp +++ b/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp -@@ -134,6 +134,12 @@ void NetworkStorageSession::setCookieAcceptPolicy(CookieAcceptPolicy policy) con +@@ -135,6 +135,12 @@ void NetworkStorageSession::setCookieAcceptPolicy(CookieAcceptPolicy policy) con cookieDatabase().setAcceptPolicy(policy); } @@ -8038,10 +8232,10 @@ index 09ab1320beacc41ae92399f3320aaf805d9d81d1..e1caf6e7ebd61151439a9c86350e5712 { GUniquePtr targetCookie(cookie.toSoupCookie()); diff --git a/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp b/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp -index 602348fdd5c5d7aec9cb00fcc0512be0d791ba68..ded04cb4ac56cf906f4e6e62a2bc50d016363cf5 100644 +index 71c773f4532b6670c4f29496cc8c4bd3093304b1..5876dfa98bb9cdb78d0de807d4408ae661c3f288 100644 --- a/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp +++ b/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp -@@ -39,6 +39,7 @@ +@@ -40,6 +40,7 @@ #include #include #include @@ -8049,7 +8243,7 @@ index 602348fdd5c5d7aec9cb00fcc0512be0d791ba68..ded04cb4ac56cf906f4e6e62a2bc50d0 namespace WebCore { -@@ -689,7 +690,10 @@ template void getStringData(IDataObject* data, FORMATETC* format, Ve +@@ -690,7 +691,10 @@ template void getStringData(IDataObject* data, FORMATETC* format, Ve STGMEDIUM store; if (FAILED(data->GetData(format, &store))) return; @@ -8104,10 +8298,10 @@ index 0379437d84807e4a8d3846afac5ec8a70e743e70..1ae19e2b755e99c9f4c3e6d5dc0e4f8b if (!m_dragDataMap.isEmpty() || !m_platformDragData) return m_dragDataMap; diff --git a/Source/WebCore/platform/win/KeyEventWin.cpp b/Source/WebCore/platform/win/KeyEventWin.cpp -index d948c806e68a05a9899a67c00048435c4dc93134..9a5a045f8642ca403b1da59a0ac7e838c86fc4d7 100644 +index d450bf9d0fd1f0bf8f28db483ac9d3d60fa9d114..72a59403a0b5493aea4a8e28eb15eac24b652b09 100644 --- a/Source/WebCore/platform/win/KeyEventWin.cpp +++ b/Source/WebCore/platform/win/KeyEventWin.cpp -@@ -242,10 +242,16 @@ PlatformKeyboardEvent::PlatformKeyboardEvent(HWND, WPARAM code, LPARAM keyData, +@@ -243,10 +243,16 @@ PlatformKeyboardEvent::PlatformKeyboardEvent(HWND, WPARAM code, LPARAM keyData, { } @@ -8128,10 +8322,10 @@ index d948c806e68a05a9899a67c00048435c4dc93134..9a5a045f8642ca403b1da59a0ac7e838 OptionSet PlatformKeyboardEvent::currentStateOfModifierKeys() diff --git a/Source/WebCore/platform/win/PasteboardWin.cpp b/Source/WebCore/platform/win/PasteboardWin.cpp -index 6ead5f4dea594fac7fad10fe97ad5a1ce605804f..a0cd7169ee36a3bec67b6a224444078fab61b699 100644 +index 03e5dc0b6ff099c281c61f05b29cf155b96922af..9b4694bc816e0e62a37c57e64729f183baca9080 100644 --- a/Source/WebCore/platform/win/PasteboardWin.cpp +++ b/Source/WebCore/platform/win/PasteboardWin.cpp -@@ -1136,7 +1136,21 @@ void Pasteboard::writeCustomData(const Vector& data) +@@ -1137,7 +1137,21 @@ void Pasteboard::writeCustomData(const Vector& data) } clear(); @@ -8153,7 +8347,7 @@ index 6ead5f4dea594fac7fad10fe97ad5a1ce605804f..a0cd7169ee36a3bec67b6a224444078f if (::OpenClipboard(m_owner)) { const auto& customData = data.first(); customData.forEachPlatformStringOrBuffer([](auto& type, auto& stringOrBuffer) { -@@ -1175,4 +1189,25 @@ void Pasteboard::write(const Color&) +@@ -1176,4 +1190,25 @@ void Pasteboard::write(const Color&) { } @@ -8623,11 +8817,24 @@ index 0000000000000000000000000000000000000000..a76b583a1e65cd6999fab4784c22dd9c +}; + +} // namespace WebCore +diff --git a/Source/WebCore/rendering/AncestorSubgridIterator.cpp b/Source/WebCore/rendering/AncestorSubgridIterator.cpp +index 9e7a774c2e0e591e491e0cda3657ae02f4543453..3161944e8f455d4dd33f6bbfd81ae2bb54d0787a 100644 +--- a/Source/WebCore/rendering/AncestorSubgridIterator.cpp ++++ b/Source/WebCore/rendering/AncestorSubgridIterator.cpp +@@ -30,7 +30,7 @@ + + namespace WebCore { + +-AncestorSubgridIterator::AncestorSubgridIterator() = default; ++AncestorSubgridIterator::AncestorSubgridIterator() { }; + + AncestorSubgridIterator::AncestorSubgridIterator(SingleThreadWeakPtr firstAncestorSubgrid, GridTrackSizingDirection direction) + : m_firstAncestorSubgrid(firstAncestorSubgrid) diff --git a/Source/WebCore/rendering/RenderTextControl.cpp b/Source/WebCore/rendering/RenderTextControl.cpp -index 58574b4d9fb503918e2cac0993ffe778f256a953..23de6aecb4ea7f7a1a36af6a93611559f19cfc11 100644 +index 5d81c5942027e02222430036013e2a066a6bae06..0246502c570719cc9c9362726b9bc03ded002a0b 100644 --- a/Source/WebCore/rendering/RenderTextControl.cpp +++ b/Source/WebCore/rendering/RenderTextControl.cpp -@@ -222,13 +222,13 @@ void RenderTextControl::layoutExcludedChildren(bool relayoutChildren) +@@ -225,13 +225,13 @@ void RenderTextControl::layoutExcludedChildren(bool relayoutChildren) } } @@ -8681,7 +8888,7 @@ index 1d8488e0d36288e09cd5662bd7f770ade95dfee3..dee07f87b47d62d4ef8ede45824bdb2f WorkerOrWorkletGlobalScope& m_globalScope; }; diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp -index 53209e9de68b0da9c7251d2575fa17195488ae60..5629471032b6a26186e8abff73c6f80e99e13a65 100644 +index 5f283341f89b3bc3be0c7508a0d995144764bb45..dc618d18fcfe7ef819f3724847f3d4e4c6771fc2 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp @@ -96,6 +96,8 @@ @@ -8693,7 +8900,7 @@ index 53209e9de68b0da9c7251d2575fa17195488ae60..5629471032b6a26186e8abff73c6f80e #endif #if ENABLE(APPLE_PAY_REMOTE_UI) -@@ -1098,6 +1100,14 @@ void NetworkConnectionToWebProcess::clearPageSpecificData(PageIdentifier pageID) +@@ -1090,6 +1092,14 @@ void NetworkConnectionToWebProcess::clearPageSpecificData(PageIdentifier pageID) storageSession->clearPageSpecificDataForResourceLoadStatistics(pageID); } @@ -8709,10 +8916,10 @@ index 53209e9de68b0da9c7251d2575fa17195488ae60..5629471032b6a26186e8abff73c6f80e { if (auto* storageSession = networkProcess().storageSession(m_sessionID)) diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h -index 12fe210ef04c92edf4b44b70f9bb35d00cc8a7d1..1196d7e9192dc01cac4914ac10998613f48c231c 100644 +index 0b2641927c4f8b17381b8e8c5bfa5e666506e229..22073e29d1104d928e5ca2fa56ffe830ede1f8a8 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h -@@ -345,6 +345,8 @@ private: +@@ -343,6 +343,8 @@ private: void clearPageSpecificData(WebCore::PageIdentifier); @@ -8722,7 +8929,7 @@ index 12fe210ef04c92edf4b44b70f9bb35d00cc8a7d1..1196d7e9192dc01cac4914ac10998613 void logUserInteraction(RegistrableDomain&&); diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in -index eb27201d553e64146b8d36d21a49e3981d6f05c1..d49ea6c810dfd4f460b24d94681d6b9ca088b020 100644 +index 1edf0f0137dcf67dd425ce92180cdd5a1811475b..0af045974298fee7227b0cfa8b2b9e6c54779ec0 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in @@ -74,6 +74,8 @@ messages -> NetworkConnectionToWebProcess LegacyReceiver { @@ -8735,10 +8942,10 @@ index eb27201d553e64146b8d36d21a49e3981d6f05c1..d49ea6c810dfd4f460b24d94681d6b9c LogUserInteraction(WebCore::RegistrableDomain domain) ResourceLoadStatisticsUpdated(Vector statistics) -> () diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.cpp b/Source/WebKit/NetworkProcess/NetworkProcess.cpp -index f9b765f8732de5d51b093fc776920e55b760cba3..621ee3d381f62e2ae6d2c4a71e8a5803a4729d67 100644 +index 5d7274e691dbe4a9c4763b349abca7d94e878554..d58697dd16691e1caa53a5548eb68cff1309f163 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkProcess.cpp -@@ -658,6 +658,12 @@ void NetworkProcess::registrableDomainsExemptFromWebsiteDataDeletion(PAL::Sessio +@@ -659,6 +659,12 @@ void NetworkProcess::registrableDomainsExemptFromWebsiteDataDeletion(PAL::Sessio completionHandler({ }); } @@ -8752,7 +8959,7 @@ index f9b765f8732de5d51b093fc776920e55b760cba3..621ee3d381f62e2ae6d2c4a71e8a5803 { if (auto* session = networkSession(sessionID)) { diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.h b/Source/WebKit/NetworkProcess/NetworkProcess.h -index 6d41842684f3e0e8b1190359f87105b747cf9da9..279d5c033397afb120a082953c0112f34bf2878a 100644 +index 95572ae0054657f1f8f2840291d49f8d23340990..38f6df007d51c53792d270f3d50fa2f1b77b4ca1 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkProcess.h @@ -33,6 +33,7 @@ @@ -8782,10 +8989,10 @@ index 6d41842684f3e0e8b1190359f87105b747cf9da9..279d5c033397afb120a082953c0112f3 void clearUserInteraction(PAL::SessionID, RegistrableDomain&&, CompletionHandler&&); void deleteAndRestrictWebsiteDataForRegistrableDomains(PAL::SessionID, OptionSet, RegistrableDomainsToDeleteOrRestrictWebsiteDataFor&&, bool shouldNotifyPage, CompletionHandler&&)>&&); diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -index 5e590b5a09f8bd5d040e2e68d54c545c52d4b179..c58bb6270ab75026187fdda6c94822f6aa47a35a 100644 +index 9679dc2ceea7b085638c19c00ba9fd04e71507da..130f12138c427a90dfffb96d7e219a258e819d10 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -@@ -79,6 +79,8 @@ messages -> NetworkProcess LegacyReceiver { +@@ -82,6 +82,8 @@ messages -> NetworkProcess LegacyReceiver { SetInspectionForServiceWorkersAllowed(PAL::SessionID sessionID, bool inspectable) @@ -8795,7 +9002,7 @@ index 5e590b5a09f8bd5d040e2e68d54c545c52d4b179..c58bb6270ab75026187fdda6c94822f6 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 e086cbe963b9965c9203c0023a67250302a0d9c4..b9a10a505a48e0708b048a7af71fb69e5df5d6a4 100644 +index f5a62296667657a7f094627e0792cb4284c04ec4..93c34c61f1cb29f3ed4c1c787da6f3efe994cfb2 100644 --- a/Source/WebKit/NetworkProcess/NetworkSession.h +++ b/Source/WebKit/NetworkProcess/NetworkSession.h @@ -200,6 +200,9 @@ public: @@ -8817,10 +9024,10 @@ index e086cbe963b9965c9203c0023a67250302a0d9c4..b9a10a505a48e0708b048a7af71fb69e HashSet> m_keptAliveLoads; diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm -index dc841a54751eb8ffa12f62a0817174aaca29eea7..ab6e9cb48c00e4ede2b53322cca8693c02e69a44 100644 +index 6c4c0e37a75426d90667c9ec595f8fa1b19ca502..45d1e5916c8cc5ec0136cf3b3596e9efb5e4a1ec 100644 --- a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm +++ b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm -@@ -767,6 +767,8 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didRece +@@ -769,6 +769,8 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didRece if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { sessionCocoa->setClientAuditToken(challenge); @@ -8829,7 +9036,7 @@ index dc841a54751eb8ffa12f62a0817174aaca29eea7..ab6e9cb48c00e4ede2b53322cca8693c NSURLSessionTaskTransactionMetrics *metrics = task._incompleteTaskMetrics.transactionMetrics.lastObject; auto tlsVersion = (tls_protocol_version_t)metrics.negotiatedTLSProtocolVersion.unsignedShortValue; -@@ -1108,6 +1110,13 @@ ALLOW_DEPRECATED_DECLARATIONS_END +@@ -1113,6 +1115,13 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data resourceResponse.setDeprecatedNetworkLoadMetrics(WebCore::copyTimingData(taskMetrics, networkDataTask->networkLoadMetrics())); @@ -8988,10 +9195,10 @@ index 486849ef6f550a0f3caab311abf5743c6d38e5af..afeaac63a18d9e71d3afead23b7da4fe void NetworkSessionCurl::didReceiveChallenge(WebSocketTask& webSocketTask, WebCore::AuthenticationChallenge&& challenge, CompletionHandler&& challengeCompletionHandler) diff --git a/Source/WebKit/NetworkProcess/curl/WebSocketTaskCurl.cpp b/Source/WebKit/NetworkProcess/curl/WebSocketTaskCurl.cpp -index 60106d6125f85d0cf848e828fd4ed7a50005f105..021d7b6d12baf4671a2968753969ca4675044313 100644 +index a5198bfa752746dd83dc2617606a27194afcd86f..3ca6b32e93bdaff2baae22170c84efe9bfe875d3 100644 --- a/Source/WebKit/NetworkProcess/curl/WebSocketTaskCurl.cpp +++ b/Source/WebKit/NetworkProcess/curl/WebSocketTaskCurl.cpp -@@ -36,11 +36,12 @@ +@@ -37,11 +37,12 @@ namespace WebKit { @@ -9005,7 +9212,7 @@ index 60106d6125f85d0cf848e828fd4ed7a50005f105..021d7b6d12baf4671a2968753969ca46 , m_scheduler(WebCore::CurlContext::singleton().streamScheduler()) { // We use topOrigin in case of service worker websocket connections, for which pageID does not link to a real page. -@@ -52,7 +53,7 @@ WebSocketTask::WebSocketTask(NetworkSocketChannel& channel, WebPageProxyIdentifi +@@ -53,7 +54,7 @@ WebSocketTask::WebSocketTask(NetworkSocketChannel& channel, WebPageProxyIdentifi if (networkSession() && networkSession()->networkProcess().localhostAliasesForTesting().contains(m_request.url().host())) localhostAlias = WebCore::CurlStream::LocalhostAlias::Enable; @@ -9014,7 +9221,7 @@ index 60106d6125f85d0cf848e828fd4ed7a50005f105..021d7b6d12baf4671a2968753969ca46 m_channel.didSendHandshakeRequest(WebCore::ResourceRequest(m_request)); } -@@ -257,7 +258,7 @@ void WebSocketTask::tryServerTrustEvaluation(WebCore::AuthenticationChallenge&& +@@ -258,7 +259,7 @@ void WebSocketTask::tryServerTrustEvaluation(WebCore::AuthenticationChallenge&& if (networkSession() && networkSession()->networkProcess().localhostAliasesForTesting().contains(m_request.url().host())) localhostAlias = WebCore::CurlStream::LocalhostAlias::Enable; @@ -9064,10 +9271,10 @@ index 51f3fb7ae9a4e208bc11ac583b72e772eac5e4dc..386ec972eba86763b83407c322a971a3 ;; Except deny access to new-style iOS Keychain folders which are UUIDs. (deny file-read* file-write* diff --git a/Source/WebKit/NetworkProcess/soup/NetworkDataTaskSoup.cpp b/Source/WebKit/NetworkProcess/soup/NetworkDataTaskSoup.cpp -index 15fb15b528ae9177e69fd4b6b43b81b7216232ff..075499ebbc20845a80830cdce105fb590f0772f8 100644 +index 61d9c1d41fdc490faf800fb30d66eb4603950cbf..3dffffe74cd04b22e40ce0d94326760404fbbc6c 100644 --- a/Source/WebKit/NetworkProcess/soup/NetworkDataTaskSoup.cpp +++ b/Source/WebKit/NetworkProcess/soup/NetworkDataTaskSoup.cpp -@@ -460,6 +460,8 @@ void NetworkDataTaskSoup::didSendRequest(GRefPtr&& inputStream) +@@ -461,6 +461,8 @@ void NetworkDataTaskSoup::didSendRequest(GRefPtr&& inputStream) m_networkLoadMetrics.responseStart = MonotonicTime::now(); #endif @@ -9076,7 +9283,7 @@ index 15fb15b528ae9177e69fd4b6b43b81b7216232ff..075499ebbc20845a80830cdce105fb59 dispatchDidReceiveResponse(); } -@@ -562,6 +564,8 @@ bool NetworkDataTaskSoup::acceptCertificate(GTlsCertificate* certificate, GTlsCe +@@ -563,6 +565,8 @@ bool NetworkDataTaskSoup::acceptCertificate(GTlsCertificate* certificate, GTlsCe { ASSERT(m_soupMessage); URL url = soupURIToURL(soup_message_get_uri(m_soupMessage.get())); @@ -9125,10 +9332,10 @@ index 60e79ff683e280591d686468c42decf1ac109ed2..99707bc16644b88ff24a192029f3866e } diff --git a/Source/WebKit/PlatformGTK.cmake b/Source/WebKit/PlatformGTK.cmake -index b33c07849a629c7c5d4ea4b5cbb6788af3dd3023..aedd95cea9e6388cab32274967513224890287a5 100644 +index a509f56343f94f1bc30658ec9928ec1796a5a9b8..e2fcf6d35053f6982975d238f17b76f49e1bacdc 100644 --- a/Source/WebKit/PlatformGTK.cmake +++ b/Source/WebKit/PlatformGTK.cmake -@@ -323,6 +323,9 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES +@@ -324,6 +324,9 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES ${GSTREAMER_PBUTILS_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS} @@ -9138,7 +9345,7 @@ index b33c07849a629c7c5d4ea4b5cbb6788af3dd3023..aedd95cea9e6388cab32274967513224 ) list(APPEND WebKit_INTERFACE_INCLUDE_DIRECTORIES -@@ -353,6 +356,9 @@ if (USE_LIBWEBRTC) +@@ -354,6 +357,9 @@ if (USE_LIBWEBRTC) list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES "${THIRDPARTY_DIR}/libwebrtc/Source/" "${THIRDPARTY_DIR}/libwebrtc/Source/webrtc" @@ -9148,7 +9355,7 @@ index b33c07849a629c7c5d4ea4b5cbb6788af3dd3023..aedd95cea9e6388cab32274967513224 ) endif () -@@ -404,6 +410,12 @@ else () +@@ -405,6 +411,12 @@ else () set(WebKitGTK_ENUM_HEADER_TEMPLATE ${WEBKIT_DIR}/UIProcess/API/gtk/WebKitEnumTypesGtk3.h.in) endif () @@ -9162,7 +9369,7 @@ index b33c07849a629c7c5d4ea4b5cbb6788af3dd3023..aedd95cea9e6388cab32274967513224 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 43d58da592e9b0f0bd6ca5cef5cbca0c4ba39b31..1fd4a732ca871704bbb915e93cf62d206c9db46e 100644 +index aa5c183a4c0946270713840071cf0167533158f6..637365fbb7d91f99ba1478188291bedff9b1cc2b 100644 --- a/Source/WebKit/PlatformWPE.cmake +++ b/Source/WebKit/PlatformWPE.cmake @@ -111,6 +111,8 @@ list(APPEND WebKit_SERIALIZATION_IN_FILES @@ -9182,7 +9389,7 @@ index 43d58da592e9b0f0bd6ca5cef5cbca0c4ba39b31..1fd4a732ca871704bbb915e93cf62d20 ) if (ENABLE_2022_GLIB_API) -@@ -425,7 +428,16 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES +@@ -426,7 +429,16 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES ${GIO_UNIX_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS} @@ -9200,7 +9407,7 @@ index 43d58da592e9b0f0bd6ca5cef5cbca0c4ba39b31..1fd4a732ca871704bbb915e93cf62d20 list(APPEND WebKit_LIBRARIES WPE::libwpe diff --git a/Source/WebKit/PlatformWin.cmake b/Source/WebKit/PlatformWin.cmake -index 06a86d0cfd1ca90f383af2b079f60ce220f8eb02..9e21935463bf964ecb090be48e68b50ef29c049b 100644 +index 7da9a409b65a577566f143a26a9717d4ba687a3c..711b5e1c4c82dfdf9ac6139a6de0785d29c1cc20 100644 --- a/Source/WebKit/PlatformWin.cmake +++ b/Source/WebKit/PlatformWin.cmake @@ -56,8 +56,13 @@ list(APPEND WebKit_SOURCES @@ -9306,7 +9513,7 @@ index 06a86d0cfd1ca90f383af2b079f60ce220f8eb02..9e21935463bf964ecb090be48e68b50e + list(APPEND WebProcess_SOURCES WebProcess/EntryPoint/win/WebProcessMain.cpp - ) + diff --git a/Source/WebKit/Shared/API/c/wpe/WebKit.h b/Source/WebKit/Shared/API/c/wpe/WebKit.h index a9aa21f5589dec453db1713c8846e0d2e687f552..9b94469d078d92e4b9e0c8149122b19a4d3b5307 100644 --- a/Source/WebKit/Shared/API/c/wpe/WebKit.h @@ -9319,6 +9526,22 @@ index a9aa21f5589dec453db1713c8846e0d2e687f552..9b94469d078d92e4b9e0c8149122b19a #include #include #include +diff --git a/Source/WebKit/Shared/AuxiliaryProcess.h b/Source/WebKit/Shared/AuxiliaryProcess.h +index bc4f258d0970d21655a311ba72932296440111f8..21bf2a8dc680ead511ddb17a9a7ae891c835ee30 100644 +--- a/Source/WebKit/Shared/AuxiliaryProcess.h ++++ b/Source/WebKit/Shared/AuxiliaryProcess.h +@@ -210,6 +210,11 @@ struct AuxiliaryProcessInitializationParameters { + #if PLATFORM(COCOA) + SDKAlignedBehaviors clientSDKAlignedBehaviors; + #endif ++// Playwright begin ++#if !PLATFORM(COCOA) ++ bool shouldEnableSharedArrayBuffer { false }; ++#endif ++// Playwright end + }; + + } // namespace WebKit diff --git a/Source/WebKit/Shared/Cocoa/CompletionHandlerCallChecker.h b/Source/WebKit/Shared/Cocoa/CompletionHandlerCallChecker.h index b09b17a5bff38e3ba8d6bb53da9ef09d229bdb61..46aa1caa93402711a08f5980387a957f783038e5 100644 --- a/Source/WebKit/Shared/Cocoa/CompletionHandlerCallChecker.h @@ -9407,10 +9630,10 @@ index e33858caab024b20217304209d7bf428e3335653..4026f6244889e5a0ee85edb72696d0be NSEvent* nativeEvent() const { return m_nativeEvent.get(); } #elif PLATFORM(GTK) diff --git a/Source/WebKit/Shared/NativeWebWheelEvent.h b/Source/WebKit/Shared/NativeWebWheelEvent.h -index dfdebf37842c22c2d27f5cb39b22fa8aa09f5511..76b76a53abfe9f43c044fc29d0f16ac9969b7252 100644 +index f8e96218fd2671d1c0aca5e549efe0d8b94ef0f9..6cebd61bceb39c08e916fe991e4c3fc6f34b4704 100644 --- a/Source/WebKit/Shared/NativeWebWheelEvent.h +++ b/Source/WebKit/Shared/NativeWebWheelEvent.h -@@ -73,7 +73,8 @@ public: +@@ -74,7 +74,8 @@ public: #elif PLATFORM(WIN) NativeWebWheelEvent(HWND, UINT message, WPARAM, LPARAM, float deviceScaleFactor); #endif @@ -9434,10 +9657,10 @@ index ea1eb9f00feaaecf73bdddc37c904e88f43bfa85..8a631e5293a11abd650958baad4e9678 #endif }; diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in -index 4be677ef8090d21da5dedbc275ef58f2b7c41654..b538f2e810668aa1039601ccff94960fb2d00525 100644 +index 54bfa4555c6dd11f8ee8e3a75df6ba97c1032e84..c795925b062886a3eee42ef3b37e2f084ae55f7d 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in -@@ -2683,6 +2683,9 @@ class WebCore::AuthenticationChallenge { +@@ -2691,6 +2691,9 @@ class WebCore::AuthenticationChallenge { class WebCore::DragData { #if PLATFORM(COCOA) String pasteboardName(); @@ -9447,7 +9670,7 @@ index 4be677ef8090d21da5dedbc275ef58f2b7c41654..b538f2e810668aa1039601ccff94960f #endif WebCore::IntPoint clientPosition(); WebCore::IntPoint globalPosition(); -@@ -3246,6 +3249,7 @@ enum class WebCore::WasPrivateRelayed : bool; +@@ -3254,6 +3257,7 @@ enum class WebCore::WasPrivateRelayed : bool; String httpStatusText; String httpVersion; WebCore::HTTPHeaderMap httpHeaderFields; @@ -9668,11 +9891,57 @@ index 0000000000000000000000000000000000000000..f4f09d171ebf9774b3f8744751d220d3 + RefPtr customData() + bool canSmartReplace() +} +diff --git a/Source/WebKit/Shared/unix/AuxiliaryProcessMain.cpp b/Source/WebKit/Shared/unix/AuxiliaryProcessMain.cpp +index 5bae771f7b19ebeaea42edca80198a598f1b49e4..82fa8826f7bd505f596fd7f0d378d62ba1ac3f2f 100644 +--- a/Source/WebKit/Shared/unix/AuxiliaryProcessMain.cpp ++++ b/Source/WebKit/Shared/unix/AuxiliaryProcessMain.cpp +@@ -38,6 +38,15 @@ + + namespace WebKit { + ++static bool hasArgument(const char* argument, int argc, char** argv) ++{ ++ for (int i = 0; i < argc; ++i) { ++ if (!strcmp(argument, argv[i])) ++ return true; ++ } ++ return false; ++} ++ + AuxiliaryProcessMainCommon::AuxiliaryProcessMainCommon() + { + #if ENABLE(BREAKPAD) +@@ -57,6 +66,10 @@ bool AuxiliaryProcessMainCommon::parseCommandLine(int argc, char** argv) + if (argc > 3 && argv[3] && !strcmp(argv[3], "--configure-jsc-for-testing")) + JSC::Config::configureForTesting(); + #endif ++// Playwright begin ++ if (hasArgument("--enable-shared-array-buffer", argc, argv)) ++ m_parameters.shouldEnableSharedArrayBuffer = true; ++// Playwright end + return true; + } + +diff --git a/Source/WebKit/Shared/win/AuxiliaryProcessMainWin.cpp b/Source/WebKit/Shared/win/AuxiliaryProcessMainWin.cpp +index 9edb5fbcd103cd8d1b224dfd60ac88aabe9626d2..9ed392ae3809f8bda92a2765ffadc643f23fe856 100644 +--- a/Source/WebKit/Shared/win/AuxiliaryProcessMainWin.cpp ++++ b/Source/WebKit/Shared/win/AuxiliaryProcessMainWin.cpp +@@ -41,6 +41,10 @@ bool AuxiliaryProcessMainCommon::parseCommandLine(int argc, char** argv) + m_parameters.connectionIdentifier = IPC::Connection::Identifier { reinterpret_cast(parseIntegerAllowingTrailingJunk(StringView::fromLatin1(argv[++i])).value_or(0)) }; + else if (!strcmp(argv[i], "-processIdentifier") && i + 1 < argc) + m_parameters.processIdentifier = ObjectIdentifier(parseIntegerAllowingTrailingJunk(StringView::fromLatin1(argv[++i])).value_or(0)); ++// Playwright begin ++ else if (!strcmp(argv[i], "-enable-shared-array-buffer")) ++ m_parameters.shouldEnableSharedArrayBuffer = true; ++// Playwright end + 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 76e3fa1aa685906adfff43aed55c9902084be0af..0cf48a92e368005e843f08c4f4096fdeaff0b1a8 100644 +index 4d418e2bd7f970bc5bfebceb88adb172e5eb8540..e988f9011fa194224f7376e134d50fc553725289 100644 --- a/Source/WebKit/Shared/win/WebEventFactory.cpp +++ b/Source/WebKit/Shared/win/WebEventFactory.cpp -@@ -483,7 +483,7 @@ WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message +@@ -484,7 +484,7 @@ WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message #if ENABLE(TOUCH_EVENTS) WebTouchEvent WebEventFactory::createWebTouchEvent() { @@ -9682,7 +9951,7 @@ index 76e3fa1aa685906adfff43aed55c9902084be0af..0cf48a92e368005e843f08c4f4096fde #endif // ENABLE(TOUCH_EVENTS) diff --git a/Source/WebKit/Sources.txt b/Source/WebKit/Sources.txt -index 5a8ce0964def0adf03f875b70bfeebd0d7a3df6e..2a6a24d1247d9e18a0b978cbd4a700f2af485d6c 100644 +index 3844b4e93816ad5e4a1ac4156516bc29c0203eb2..922a99ed0e62d66e39eb20b05f757870da8be85e 100644 --- a/Source/WebKit/Sources.txt +++ b/Source/WebKit/Sources.txt @@ -377,6 +377,7 @@ Shared/XR/XRDeviceProxy.cpp @@ -9722,8 +9991,8 @@ index 5a8ce0964def0adf03f875b70bfeebd0d7a3df6e..2a6a24d1247d9e18a0b978cbd4a700f2 +UIProcess/WebPageInspectorInputAgent.cpp UIProcess/WebPageProxy.cpp UIProcess/WebPageProxyMessageReceiverRegistration.cpp - UIProcess/WebPasteboardProxy.cpp -@@ -573,7 +580,11 @@ UIProcess/Inspector/WebInspectorUtilities.cpp + UIProcess/WebPageProxyTesting.cpp +@@ -574,7 +581,11 @@ UIProcess/Inspector/WebInspectorUtilities.cpp UIProcess/Inspector/WebPageDebuggable.cpp UIProcess/Inspector/WebPageInspectorController.cpp @@ -9736,10 +10005,10 @@ index 5a8ce0964def0adf03f875b70bfeebd0d7a3df6e..2a6a24d1247d9e18a0b978cbd4a700f2 UIProcess/Media/AudioSessionRoutingArbitratorProxy.cpp UIProcess/Media/MediaUsageManager.cpp diff --git a/Source/WebKit/SourcesCocoa.txt b/Source/WebKit/SourcesCocoa.txt -index ff2daa513c7fd2791b3f28e47b521a8ad4ade968..d75ff772fd46b53c5734203ae4e617ffffde5657 100644 +index fe64c9af8d7f79b86941404d5aed66424a675bf7..0f1e5aa7a24ddc03c7462226312b4ffb354de632 100644 --- a/Source/WebKit/SourcesCocoa.txt +++ b/Source/WebKit/SourcesCocoa.txt -@@ -271,6 +271,7 @@ UIProcess/API/Cocoa/_WKArchiveExclusionRule.mm +@@ -270,6 +270,7 @@ UIProcess/API/Cocoa/_WKArchiveExclusionRule.mm UIProcess/API/Cocoa/_WKAttachment.mm UIProcess/API/Cocoa/_WKAutomationSession.mm UIProcess/API/Cocoa/_WKAutomationSessionConfiguration.mm @@ -9756,7 +10025,7 @@ index ff2daa513c7fd2791b3f28e47b521a8ad4ade968..d75ff772fd46b53c5734203ae4e617ff UIProcess/Inspector/mac/WKInspectorResourceURLSchemeHandler.mm UIProcess/Inspector/mac/WKInspectorViewController.mm diff --git a/Source/WebKit/SourcesGTK.txt b/Source/WebKit/SourcesGTK.txt -index 4ccf24056472cbaca8f61ed82ab6447f8bb45db7..4d283f3cb33cee9756252c929a8551b0d8595295 100644 +index 94d0f078de20208836e4a1a63f6dd0ce4e7ab556..fce80ee80cb3f2079b7405d49b5646af060c10be 100644 --- a/Source/WebKit/SourcesGTK.txt +++ b/Source/WebKit/SourcesGTK.txt @@ -130,6 +130,7 @@ UIProcess/API/glib/WebKitAutomationSession.cpp @no-unify @@ -9793,7 +10062,7 @@ index 4ccf24056472cbaca8f61ed82ab6447f8bb45db7..4d283f3cb33cee9756252c929a8551b0 UIProcess/gtk/WebPasteboardProxyGtk.cpp UIProcess/gtk/WebPopupMenuProxyGtk.cpp diff --git a/Source/WebKit/SourcesWPE.txt b/Source/WebKit/SourcesWPE.txt -index 3dfcab5bbd7918b57c5dcaf8c0548bb0c4da52f2..82d274947520fe70f91da62bf1e39f728a65a003 100644 +index e6c9ee4c1e8ee1ad95ae6595e061378b377a3501..71882922bff1ad74a96b0bca2ea5522f81ef70ed 100644 --- a/Source/WebKit/SourcesWPE.txt +++ b/Source/WebKit/SourcesWPE.txt @@ -132,6 +132,7 @@ UIProcess/API/glib/WebKitAuthenticationRequest.cpp @no-unify @@ -9820,7 +10089,7 @@ index 3dfcab5bbd7918b57c5dcaf8c0548bb0c4da52f2..82d274947520fe70f91da62bf1e39f72 UIProcess/API/wpe/WebKitInputMethodContextWPE.cpp @no-unify UIProcess/API/wpe/WebKitInputMethodContextImplWPE.cpp @no-unify UIProcess/API/wpe/WebKitPopupMenu.cpp @no-unify -@@ -225,6 +228,7 @@ UIProcess/glib/DisplayLinkGLib.cpp +@@ -227,6 +230,7 @@ UIProcess/glib/DisplayLinkGLib.cpp UIProcess/glib/DisplayVBlankMonitor.cpp UIProcess/glib/DisplayVBlankMonitorDRM.cpp UIProcess/glib/DisplayVBlankMonitorTimer.cpp @@ -9828,7 +10097,7 @@ index 3dfcab5bbd7918b57c5dcaf8c0548bb0c4da52f2..82d274947520fe70f91da62bf1e39f72 UIProcess/glib/ScreenManager.cpp UIProcess/glib/WebPageProxyGLib.cpp UIProcess/glib/WebProcessPoolGLib.cpp -@@ -255,7 +259,12 @@ UIProcess/linux/MemoryPressureMonitor.cpp +@@ -259,7 +263,12 @@ UIProcess/linux/MemoryPressureMonitor.cpp UIProcess/soup/WebProcessPoolSoup.cpp UIProcess/wpe/AcceleratedBackingStoreDMABuf.cpp @@ -9841,7 +10110,7 @@ index 3dfcab5bbd7918b57c5dcaf8c0548bb0c4da52f2..82d274947520fe70f91da62bf1e39f72 UIProcess/wpe/WebPageProxyWPE.cpp UIProcess/wpe/WebPreferencesWPE.cpp -@@ -279,6 +288,8 @@ WebProcess/WebCoreSupport/glib/WebEditorClientGLib.cpp +@@ -286,6 +295,8 @@ WebProcess/WebCoreSupport/glib/WebEditorClientGLib.cpp WebProcess/WebCoreSupport/soup/WebFrameNetworkingContext.cpp @@ -9923,7 +10192,7 @@ index 9ecfb4e61a015c97e3adaeccfcf52ce24735eeed..decae9b739c9692921305b87449f6557 virtual void setStatusText(WebKit::WebPageProxy*, const WTF::String&) { } virtual void mouseDidMoveOverElement(WebKit::WebPageProxy&, const WebKit::WebHitTestResultData&, OptionSet, Object*) { } diff --git a/Source/WebKit/UIProcess/API/C/WKInspector.cpp b/Source/WebKit/UIProcess/API/C/WKInspector.cpp -index 990b0e5ebad19fdaf1b0036585be2ed88bc125d2..9f1931dbdd8c70d3637a2d71406b5968896037f2 100644 +index 16229e649d69b812be84b487ec87941cb0986250..88a5fa4bd77136a2370175696d078c20e1d58edd 100644 --- a/Source/WebKit/UIProcess/API/C/WKInspector.cpp +++ b/Source/WebKit/UIProcess/API/C/WKInspector.cpp @@ -28,6 +28,11 @@ @@ -9966,10 +10235,10 @@ index 026121d114c5fcad84c1396be8d692625beaa3bd..edd6e5cae033124c589959a42522fde0 } #endif diff --git a/Source/WebKit/UIProcess/API/C/WKPage.cpp b/Source/WebKit/UIProcess/API/C/WKPage.cpp -index 47abb7de04be89f82e90f0a2197ef129b2d2f1cd..fb71b452a27d62af68165a778647c4b2e0236443 100644 +index 4abc7ac9b65c92e1f8ac122b97abf32c69631eb7..93e67e9e60ffa2536be7da1b70a7704d6eb3313f 100644 --- a/Source/WebKit/UIProcess/API/C/WKPage.cpp +++ b/Source/WebKit/UIProcess/API/C/WKPage.cpp -@@ -1780,6 +1780,13 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient +@@ -1781,6 +1781,13 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient completionHandler(String()); } @@ -9983,7 +10252,7 @@ index 47abb7de04be89f82e90f0a2197ef129b2d2f1cd..fb71b452a27d62af68165a778647c4b2 void setStatusText(WebPageProxy* page, const String& text) final { if (!m_client.setStatusText) -@@ -1809,6 +1816,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient +@@ -1810,6 +1817,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient { if (!m_client.didNotHandleKeyEvent) return; @@ -10074,7 +10343,7 @@ index 857afb1b892c2ee7327808f3dab0cff441c92c52..332bb2e687d6b97fd11f1366ade5b178 { return _preferences->inactiveMediaCaptureSteamRepromptIntervalInMinutes(); diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h -index 3ff86aaf450aaa15ebd57e6703927f1234c35f3f..e3914dc048bcd7fc595a21ef11d648a70c8c84d4 100644 +index 950a5587c9ed75292e6ad8b4f898b73de3dabc25..f315b1023c6910e23e88d18022a18b6710308a55 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) { @@ -10124,7 +10393,7 @@ index eff4cf557033561ab20762d93a58c2d71f5505f0..2fd5a2515c54d9edcab48fa3d993298f NS_ASSUME_NONNULL_END diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm -index 350914dd5352683024925bb820148ba2582cd0c7..ac9767ff4920e066b11da4556b2df77debd18a1f 100644 +index 2432da7fa381ba09f73f2126b978e4b454e42e4b..d3f1ce3d339b6ebd6d61100eb8d4c71d86258f00 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm @@ -51,6 +51,7 @@ @@ -10135,7 +10404,7 @@ index 350914dd5352683024925bb820148ba2582cd0c7..ac9767ff4920e066b11da4556b2df77d #import #import #import -@@ -446,6 +447,11 @@ - (void)removeDataOfTypes:(NSSet *)dataTypes modifiedSince:(NSDate *)date comple +@@ -450,6 +451,11 @@ - (void)removeDataOfTypes:(NSSet *)dataTypes modifiedSince:(NSDate *)date comple }); } @@ -10332,7 +10601,7 @@ index 4974e14214e2bb3e982325b885bab33e54f83998..cacdf8c71fab248d38d2faf03f7affdc typedef NS_ENUM(NSInteger, _WKUserStyleLevel) { _WKUserStyleUserLevel, diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKUserStyleSheet.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKUserStyleSheet.mm -index 383bd33cc0b53ea049d2e6fb1bf338d584caeb18..e29ba10dceced9d115b09e014cc086c5453e33fe 100644 +index 8f7d300c449fd3323f3a47630a5b87835e7b2350..8b2b098f0bc8d9ad27654c33f616c0b73330573c 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/_WKUserStyleSheet.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/_WKUserStyleSheet.mm @@ -35,6 +35,7 @@ @@ -10546,7 +10815,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 e0a36ef8b438626b97ca791c250868c15fd663ea..1af633927046fd388d7a7a9a4b4e503e31df2e65 100644 +index 13452bf008a5a5b8cce2367296c20df5b05ced59..2055a9f371e8b60d7702988bfd7ffd1ad0bd55c2 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp @@ -94,6 +94,10 @@ private: @@ -10561,10 +10830,10 @@ index e0a36ef8b438626b97ca791c250868c15fd663ea..1af633927046fd388d7a7a9a4b4e503e bool canRunBeforeUnloadConfirmPanel() const final { return true; } diff --git a/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp b/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp -index f2df10c56663a21420eafc06ace0d204eaae1747..dc2b805b52c202d4910633bf9c6db6edf07f3b5f 100644 +index 47eca6bcf0048acad8e4d213cae94edc03efbffc..b5b1e526b988be8c6ab5830d933c7becb52344f3 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp -@@ -417,10 +417,19 @@ static void webkitWebContextSetProperty(GObject* object, guint propID, const GVa +@@ -421,10 +421,19 @@ static void webkitWebContextSetProperty(GObject* object, guint propID, const GVa } } @@ -10584,7 +10853,7 @@ index f2df10c56663a21420eafc06ace0d204eaae1747..dc2b805b52c202d4910633bf9c6db6ed GUniquePtr bundleFilename(g_build_filename(injectedBundleDirectory(), INJECTED_BUNDLE_FILENAME, nullptr)); WebKitWebContext* webContext = WEBKIT_WEB_CONTEXT(object); -@@ -477,6 +486,8 @@ static void webkitWebContextConstructed(GObject* object) +@@ -481,6 +490,8 @@ static void webkitWebContextConstructed(GObject* object) static void webkitWebContextDispose(GObject* object) { @@ -10593,7 +10862,7 @@ index f2df10c56663a21420eafc06ace0d204eaae1747..dc2b805b52c202d4910633bf9c6db6ed WebKitWebContextPrivate* priv = WEBKIT_WEB_CONTEXT(object)->priv; if (!priv->clientsDetached) { priv->clientsDetached = true; -@@ -938,6 +949,11 @@ WebKitNetworkSession* webkit_web_context_get_network_session_for_automation(WebK +@@ -942,6 +953,11 @@ WebKitNetworkSession* webkit_web_context_get_network_session_for_automation(WebK return nullptr; #endif } @@ -10630,7 +10899,7 @@ 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 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d1abcd895 100644 +index 061a05bdd19eea75552cad40e78794dfd26f6668..d96b4efdb43131cfccff3ace107b3959eb6a4a1c 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp @@ -34,6 +34,7 @@ @@ -10657,7 +10926,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d #include "WebKitPrintOperationPrivate.h" #include "WebKitWebInspectorPrivate.h" #include "WebKitWebViewBasePrivate.h" -@@ -145,6 +146,7 @@ enum { +@@ -146,6 +147,7 @@ enum { CLOSE, SCRIPT_DIALOG, @@ -10665,7 +10934,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d DECIDE_POLICY, PERMISSION_REQUEST, -@@ -497,6 +499,16 @@ GRefPtr WebKitWebViewClient::showOptionMenu(WebKitPopupMenu& p +@@ -506,6 +508,16 @@ GRefPtr WebKitWebViewClient::showOptionMenu(WebKitPopupMenu& p void WebKitWebViewClient::frameDisplayed(WKWPE::View&) { @@ -10682,7 +10951,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d { SetForScope inFrameDisplayedGuard(m_webView->priv->inFrameDisplayed, true); for (const auto& callback : m_webView->priv->frameDisplayedCallbacks) { -@@ -513,6 +525,18 @@ void WebKitWebViewClient::frameDisplayed(WKWPE::View&) +@@ -522,6 +534,18 @@ void WebKitWebViewClient::frameDisplayed(WKWPE::View&) } } @@ -10701,7 +10970,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d void WebKitWebViewClient::willStartLoad(WKWPE::View&) { webkitWebViewWillStartLoad(m_webView); -@@ -599,7 +623,7 @@ static gboolean webkitWebViewDecidePolicy(WebKitWebView*, WebKitPolicyDecision* +@@ -608,7 +632,7 @@ static gboolean webkitWebViewDecidePolicy(WebKitWebView*, WebKitPolicyDecision* static gboolean webkitWebViewPermissionRequest(WebKitWebView*, WebKitPermissionRequest* request) { @@ -10710,7 +10979,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d if (WEBKIT_IS_POINTER_LOCK_PERMISSION_REQUEST(request)) { webkit_permission_request_allow(request); return TRUE; -@@ -913,6 +937,10 @@ static void webkitWebViewConstructed(GObject* object) +@@ -927,6 +951,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); @@ -10721,7 +10990,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d webkitWebViewCreatePage(webView, WTFMove(configuration)); webkitWebContextWebViewCreated(priv->context.get(), webView); -@@ -1942,6 +1970,15 @@ static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass) +@@ -1956,6 +1984,15 @@ static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass) G_TYPE_BOOLEAN, 1, WEBKIT_TYPE_SCRIPT_DIALOG); @@ -10737,7 +11006,7 @@ index 9c64c74cc0f7fbfbc8c63d7815dece46c2c3c6f4..23d807f87cf8beaf7f4b05d15b12055d /** * WebKitWebView::decide-policy: * @web_view: the #WebKitWebView on which the signal is emitted -@@ -2734,6 +2771,23 @@ void webkitWebViewRunJavaScriptBeforeUnloadConfirm(WebKitWebView* webView, const +@@ -2748,6 +2785,23 @@ void webkitWebViewRunJavaScriptBeforeUnloadConfirm(WebKitWebView* webView, const webkit_script_dialog_unref(webView->priv->currentScriptDialog); } @@ -10786,7 +11055,7 @@ index 805f9f638c1630b5e9310494ae2970262de001cc..add3e80896c2e82bdd12cee15c8014bf #include <@API_INCLUDE_PREFIX@/WebKitClipboardPermissionRequest.h> #include <@API_INCLUDE_PREFIX@/WebKitColorChooserRequest.h> diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp -index cea5882f6c455898e1b5a231e1213e6b71ff21b6..d82b93d3c759b1184869b3504e1c3c07f53cc8ce 100644 +index 36a0e39f8b13dcaa57ac1221d24e06a35eabe9b0..6a51f9653ccfb26c52405e44390f4aa3dd5f23f6 100644 --- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp +++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp @@ -269,6 +269,8 @@ void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool @@ -10811,7 +11080,7 @@ index cea5882f6c455898e1b5a231e1213e6b71ff21b6..d82b93d3c759b1184869b3504e1c3c07 void PageClientImpl::didChangeContentSize(const IntSize& size) diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h -index adf00c390a0bc584955be83d15baae5898a928eb..2a70aba8ef2d8d5882f6520425e1b9d06796b477 100644 +index 8b747ca183ddd04160b29cd9b89e5e40ad8161ab..6c99ff9824ea2db3719b84ee3f5766011213d998 100644 --- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h +++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h @@ -105,7 +105,7 @@ private: @@ -10924,10 +11193,10 @@ 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 44ca7ff8b13bf0b1d4b16d90b8488f596175f2a1..58371f574003fba1d280ad0464b7688cd99b5ebe 100644 +index f6690c7780dff32a043c51f7afaba00119a92c44..f48f5b67505176bac3406212e266a6addd6e226a 100644 --- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp +++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp -@@ -2946,6 +2946,11 @@ void webkitWebViewBaseResetClickCounter(WebKitWebViewBase* webkitWebViewBase) +@@ -2930,6 +2930,11 @@ void webkitWebViewBaseResetClickCounter(WebKitWebViewBase* webkitWebViewBase) #endif } @@ -10939,7 +11208,7 @@ index 44ca7ff8b13bf0b1d4b16d90b8488f596175f2a1..58371f574003fba1d280ad0464b7688c void webkitWebViewBaseEnterAcceleratedCompositingMode(WebKitWebViewBase* webkitWebViewBase, const LayerTreeContext& layerTreeContext) { ASSERT(webkitWebViewBase->priv->acceleratedBackingStore); -@@ -3002,12 +3007,12 @@ void webkitWebViewBasePageClosed(WebKitWebViewBase* webkitWebViewBase) +@@ -2986,12 +2991,12 @@ void webkitWebViewBasePageClosed(WebKitWebViewBase* webkitWebViewBase) webkitWebViewBase->priv->acceleratedBackingStore->update({ }); } @@ -11013,13 +11282,13 @@ index 26d1790017e528f26ae04dac635678d5494bfd04..b9832e9221edaa14af485d34ac6216ff 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 f498562d70a4652f6831ac6bc12ef86e537d3930..73b11a5904d32e28c469622f98f0d90659c40a40 100644 +index b9b6b379136653e9dc13313715e0d281a2c8cb01..7cb887f31dc381739b079be0ab2548075fbc0690 100644 --- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp +++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp -@@ -33,9 +33,13 @@ - #include "NativeWebWheelEvent.h" +@@ -34,9 +34,13 @@ #include "TouchGestureController.h" - #include "WPEWebView.h" + #include "WPEWebViewLegacy.h" + #include "WPEWebViewPlatform.h" +#include "WebColorPickerWPE.h" +#include "WebDateTimePickerWPE.h" #include "WebContextMenuProxy.h" @@ -11030,8 +11299,8 @@ index f498562d70a4652f6831ac6bc12ef86e537d3930..73b11a5904d32e28c469622f98f0d906 #include #include #include -@@ -45,6 +49,12 @@ - #include +@@ -50,6 +54,12 @@ + #include #endif +#if USE(SKIA) @@ -11043,7 +11312,7 @@ index f498562d70a4652f6831ac6bc12ef86e537d3930..73b11a5904d32e28c469622f98f0d906 namespace WebKit { PageClientImpl::PageClientImpl(WKWPE::View& view) -@@ -203,7 +213,7 @@ WebCore::IntPoint PageClientImpl::accessibilityScreenToRootView(const WebCore::I +@@ -208,7 +218,7 @@ WebCore::IntPoint PageClientImpl::accessibilityScreenToRootView(const WebCore::I WebCore::IntRect PageClientImpl::rootViewToAccessibilityScreen(const WebCore::IntRect& rect) { @@ -11052,7 +11321,7 @@ index f498562d70a4652f6831ac6bc12ef86e537d3930..73b11a5904d32e28c469622f98f0d906 } void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool) -@@ -465,6 +475,64 @@ void PageClientImpl::selectionDidChange() +@@ -487,6 +497,64 @@ void PageClientImpl::selectionDidChange() m_view.selectionDidChange(); } @@ -11117,7 +11386,7 @@ index f498562d70a4652f6831ac6bc12ef86e537d3930..73b11a5904d32e28c469622f98f0d906 WebKitWebResourceLoadManager* PageClientImpl::webResourceLoadManager() { return m_view.webResourceLoadManager(); -@@ -475,4 +543,23 @@ void PageClientImpl::callAfterNextPresentationUpdate(CompletionHandler&& +@@ -497,4 +565,23 @@ void PageClientImpl::callAfterNextPresentationUpdate(CompletionHandler&& m_view.callAfterNextPresentationUpdate(WTFMove(callback)); } @@ -11142,10 +11411,10 @@ index f498562d70a4652f6831ac6bc12ef86e537d3930..73b11a5904d32e28c469622f98f0d906 + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h -index 6c113e0349dea1d7457874a948d6e55ba2625730..0fcffc23a58401ac54cd6864011a3dd86fa0683b 100644 +index dc56a4f5af6ddf3ff3d557493482b5b25efcd24d..f1f69fb9341fa8bfff6e1ae045db5465a1b85eed 100644 --- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h +++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h -@@ -166,10 +166,25 @@ private: +@@ -166,9 +166,24 @@ private: void didChangeWebPageID() const override; void selectionDidChange() override; @@ -11156,7 +11425,6 @@ index 6c113e0349dea1d7457874a948d6e55ba2625730..0fcffc23a58401ac54cd6864011a3dd8 + RefPtr takeViewSnapshot(std::optional&&, bool nominalResolution) override; +#endif WebKitWebResourceLoadManager* webResourceLoadManager() override; - void didClearEditorStateAfterPageTransition() final { } +#if ENABLE(DATALIST_ELEMENT) + RefPtr createDataListSuggestionsDropdown(WebKit::WebPageProxy& page) override; @@ -11172,21 +11440,6 @@ index 6c113e0349dea1d7457874a948d6e55ba2625730..0fcffc23a58401ac54cd6864011a3dd8 WKWPE::View& m_view; }; -diff --git a/Source/WebKit/UIProcess/API/wpe/WPEWebView.cpp b/Source/WebKit/UIProcess/API/wpe/WPEWebView.cpp -index 8c1b77dd3c2c5dab2943647a845767e4c6086f9f..c9ae70281bab0404a525d82db9054b0db3d9eb78 100644 ---- a/Source/WebKit/UIProcess/API/wpe/WPEWebView.cpp -+++ b/Source/WebKit/UIProcess/API/wpe/WPEWebView.cpp -@@ -98,7 +98,9 @@ View::View(struct wpe_view_backend* backend, WPEDisplay* display, const API::Pag - auto& preferences = configuration->preferences(); - preferences.setAcceleratedCompositingEnabled(true); - preferences.setForceCompositingMode(true); -- preferences.setThreadedScrollingEnabled(true); -+ // Playwright override begin -+ preferences.setThreadedScrollingEnabled(false); -+ // Playwright override end - - auto& pool = configuration->processPool(); - m_pageProxy = pool.createWebPage(*m_pageClient, WTFMove(configuration)); diff --git a/Source/WebKit/UIProcess/API/wpe/WebKitBrowserInspector.h b/Source/WebKit/UIProcess/API/wpe/WebKitBrowserInspector.h new file mode 100644 index 0000000000000000000000000000000000000000..273c5105cdf1638955cea01128c9bbab3e64436c @@ -11514,10 +11767,10 @@ index 65bf3b71e451aad11039130d2d23a68f5fce499f..99a1402270bcd210107bcc4f02983789 // 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 15a56d77cdd54d17a38e71b4adf31c981e6407ee..6bc36ee8b7b762699283ab6cbb12b11cf243836a 100644 +index a889806fbc7d1329a6bd7a6d179d31318cc2f010..197f2fdc887eef0a25f82a5d4702e6d34a3a126e 100644 --- a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp +++ b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp -@@ -168,7 +168,11 @@ void AuxiliaryProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& lau +@@ -165,7 +165,11 @@ void AuxiliaryProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& lau launchOptions.processCmdPrefix = String::fromUTF8(processCmdPrefix); #endif // ENABLE(DEVELOPER_MODE) && (PLATFORM(GTK) || PLATFORM(WPE)) @@ -11530,7 +11783,7 @@ index 15a56d77cdd54d17a38e71b4adf31c981e6407ee..6bc36ee8b7b762699283ab6cbb12b11c platformGetLaunchOptions(launchOptions); } diff --git a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h -index f203c2ea1a77939b1acaf5352e4cf67a58817ab2..08db3dba0ab5c4e22207c1dced9530ddad67e976 100644 +index e792deb624dc217fd9502461c0f7cabf789d812c..43b2912a05eaea0911662934305e94a6cc1477d1 100644 --- a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h +++ b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h @@ -277,13 +277,16 @@ protected: @@ -11550,7 +11803,7 @@ index f203c2ea1a77939b1acaf5352e4cf67a58817ab2..08db3dba0ab5c4e22207c1dced9530dd - Vector platformOverrideLanguages() const; void platformStartConnectionTerminationWatchdog(); - ResponsivenessTimer m_responsivenessTimer; + // Connection::Client diff --git a/Source/WebKit/UIProcess/BackingStore.h b/Source/WebKit/UIProcess/BackingStore.h index e1d579d6d428c3575ddf83b9d78dae045beb218d..9a2830e50ec8ab965e4a5a2b5faa9285c99bcc99 100644 --- a/Source/WebKit/UIProcess/BackingStore.h @@ -11744,7 +11997,7 @@ index 3ac9df925437afb3b4d37107fc6c02dc9ee41fa6..eb0dc8703860ef5006d6df26a6ca6115 { if (!m_uiDelegate) diff --git a/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm -index 58b550ade47802818b3d5ed8027f757ec7b282ec..31cbc0362a1d991de69ebd040ea70700716e097f 100644 +index 4e64905585de45ffa75c2d5b2ff2c3748fa15d15..342549d7c38432d9304ebd0bc1b55e4154f54948 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm @@ -38,6 +38,7 @@ @@ -11755,7 +12008,7 @@ index 58b550ade47802818b3d5ed8027f757ec7b282ec..31cbc0362a1d991de69ebd040ea70700 #import "PlaybackSessionManagerProxy.h" #import "QuickLookThumbnailLoader.h" #import "RemoteLayerTreeTransaction.h" -@@ -293,10 +294,84 @@ bool WebPageProxy::scrollingUpdatesDisabledForTesting() +@@ -298,10 +299,84 @@ bool WebPageProxy::scrollingUpdatesDisabledForTesting() void WebPageProxy::startDrag(const DragItem& dragItem, ShareableBitmap::Handle&& dragImageHandle) { @@ -11811,9 +12064,9 @@ index 58b550ade47802818b3d5ed8027f757ec7b282ec..31cbc0362a1d991de69ebd040ea70700 + NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName]; + m_overrideDragPasteboardName = String([pasteboard name]); + } -+ send(Messages::WebPage::SetDragPasteboardName(m_overrideDragPasteboardName)); ++ legacyMainFrameProcess().send(Messages::WebPage::SetDragPasteboardName(m_overrideDragPasteboardName), webPageIDInMainFrameProcess()); + } else { -+ send(Messages::WebPage::SetDragPasteboardName(""_s)); ++ legacyMainFrameProcess().send(Messages::WebPage::SetDragPasteboardName(""_s), webPageIDInMainFrameProcess()); + } +} + @@ -11842,7 +12095,7 @@ index 58b550ade47802818b3d5ed8027f757ec7b282ec..31cbc0362a1d991de69ebd040ea70700 #if ENABLE(ATTACHMENT_ELEMENT) diff --git a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm -index ddef8d1c1f33c435baa3c5f3faa80dca2b01401b..9ec578895ed986ae786b45b2fe755a5848703567 100644 +index 36b1489cbe2de1698e6baebef5dc8f8e6d1f3acd..bdffd070443436f026b987aa86d1c22e61463dbf 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm @@ -434,7 +434,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END @@ -11854,7 +12107,7 @@ index ddef8d1c1f33c435baa3c5f3faa80dca2b01401b..9ec578895ed986ae786b45b2fe755a58 #endif #if (PLATFORM(IOS) || PLATFORM(VISION)) && HAVE(AGX_COMPILER_SERVICE) -@@ -840,8 +840,8 @@ void WebProcessPool::registerNotificationObservers() +@@ -790,8 +790,8 @@ void WebProcessPool::registerNotificationObservers() }]; m_scrollerStyleNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSPreferredScrollerStyleDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *notification) { @@ -12025,7 +12278,7 @@ index 555814f9771f8f16d3572cd7007817ba4296d6d3..8850e4adb182e7f0b23e5cde45f14ad8 } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp b/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp -index f9123661bf8e84307bfe55a78999d4e35c70f165..897c488a2cef712613e53cd0c502f5e48b201341 100644 +index cc5e0d803a2069c6a5539fe1bccb67c5c9b1b3aa..7841be001cb5bc6db99b3d71f881c6e9b298e5fd 100644 --- a/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp +++ b/Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp @@ -40,8 +40,10 @@ @@ -12064,7 +12317,7 @@ index f9123661bf8e84307bfe55a78999d4e35c70f165..897c488a2cef712613e53cd0c502f5e4 m_downloadProxyMap.downloadFinished(*this); }); } else -@@ -168,6 +176,21 @@ void DownloadProxy::decideDestinationWithSuggestedFilename(const WebCore::Resour +@@ -153,6 +161,21 @@ void DownloadProxy::decideDestinationWithSuggestedFilename(const WebCore::Resour suggestedFilename = m_suggestedFilename; suggestedFilename = MIMETypeRegistry::appendFileExtensionIfNecessary(suggestedFilename, response.mimeType()); @@ -12086,7 +12339,7 @@ index f9123661bf8e84307bfe55a78999d4e35c70f165..897c488a2cef712613e53cd0c502f5e4 m_client->decideDestinationWithSuggestedFilename(*this, response, ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename), [this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)] (AllowOverwrite allowOverwrite, String destination) mutable { SandboxExtension::Handle sandboxExtensionHandle; if (!destination.isNull()) { -@@ -216,6 +239,8 @@ void DownloadProxy::didFinish() +@@ -201,6 +224,8 @@ void DownloadProxy::didFinish() updateQuarantinePropertiesIfPossible(); #endif m_client->didFinish(*this); @@ -12095,7 +12348,7 @@ index f9123661bf8e84307bfe55a78999d4e35c70f165..897c488a2cef712613e53cd0c502f5e4 // This can cause the DownloadProxy object to be deleted. m_downloadProxyMap.downloadFinished(*this); -@@ -226,6 +251,8 @@ void DownloadProxy::didFail(const ResourceError& error, std::span +@@ -211,6 +236,8 @@ void DownloadProxy::didFail(const ResourceError& error, std::span m_legacyResumeData = createData(resumeData); m_client->didFail(*this, error, m_legacyResumeData.get()); @@ -12117,7 +12370,7 @@ index 6f0d076b1b1cb0ec3e1c7fdc5f3a6dfffe9ee63d..656d4259dbf9ab97a8b0408c061c0fd2 } // namespace WebKit diff --git a/Source/WebKit/UIProcess/DrawingAreaProxy.h b/Source/WebKit/UIProcess/DrawingAreaProxy.h -index a11762ba4969c3b5a38b01ba3eb831940e48c9ab..e4df189df7b5246dd39a485f735743f81cdc3041 100644 +index 77d47a91327391c2b8bc3a3fc12f512c25160649..3173e2545ae0a79b90342234b008fd26a0ad72a7 100644 --- a/Source/WebKit/UIProcess/DrawingAreaProxy.h +++ b/Source/WebKit/UIProcess/DrawingAreaProxy.h @@ -98,6 +98,7 @@ public: @@ -12128,7 +12381,7 @@ index a11762ba4969c3b5a38b01ba3eb831940e48c9ab..e4df189df7b5246dd39a485f735743f8 virtual void minimumSizeForAutoLayoutDidChange() { } virtual void sizeToContentAutoSizeMaximumSizeDidChange() { } -@@ -181,6 +182,10 @@ private: +@@ -184,6 +185,10 @@ private: virtual void update(uint64_t /* backingStoreStateID */, UpdateInfo&&) { } virtual void exitAcceleratedCompositingMode(uint64_t /* backingStoreStateID */, UpdateInfo&&) { } #endif @@ -12153,7 +12406,7 @@ index fc5c53f18ad2dee2c2f40cdbb86ca7f24b262d8d..d93d804d3a8ebaa30856710df544f3db } diff --git a/Source/WebKit/UIProcess/Inspector/Agents/CairoJpegEncoder.cpp b/Source/WebKit/UIProcess/Inspector/Agents/CairoJpegEncoder.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..f9af359a8b81babaf855132ec168feb22ef5799b +index 0000000000000000000000000000000000000000..8d20e2aa36ba0f7996c20a6a02792c7f151bbed5 --- /dev/null +++ b/Source/WebKit/UIProcess/Inspector/Agents/CairoJpegEncoder.cpp @@ -0,0 +1,246 @@ @@ -12200,7 +12453,7 @@ index 0000000000000000000000000000000000000000..f9af359a8b81babaf855132ec168feb2 + +#include "config.h" + -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) + +#include "CairoJpegEncoder.h" + @@ -12441,10 +12694,10 @@ 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..41c52ae52e31fb90721be7cab85677745577f9a7 +index 0000000000000000000000000000000000000000..b0527d43bf28c7b6f25a5dc3f2b9ff42e0a96190 --- /dev/null +++ b/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.cpp -@@ -0,0 +1,393 @@ +@@ -0,0 +1,387 @@ +/* + * Copyright (C) 2020 Microsoft Corporation. + * @@ -12497,7 +12750,7 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 +#include +#endif + -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) +#include "CairoJpegEncoder.h" +#include "DrawingAreaProxyCoordinatedGraphics.h" +#include "DrawingAreaProxy.h" @@ -12543,7 +12796,7 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 + m_encoder = nullptr; +} + -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) +void InspectorScreencastAgent::didPaint(sk_sp&& surface) +{ + sk_sp image(surface); @@ -12564,16 +12817,15 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 + m_encoder->encodeFrame(sk_sp(image), displaySize); + if (m_screencast) { + { -+ SkBitmap bitmap; -+ bitmap.setInfo(SkImageInfo::Make(image->width(), image->height(), kRGBA_8888_SkColorType, kPremul_SkAlphaType)); -+ if (!bitmap.tryAllocPixels() || !image->readPixels(bitmap.pixmap(), 0, 0)) { -+ fprintf(stderr, "Failed to read pixels from SkImage\n"); ++ SkPixmap pixmap; ++ if (!image->peekPixels(&pixmap)) { ++ fprintf(stderr, "Failed to peek pixels from SkImage to compute hash\n"); + return; + } + // Do not send the same frame over and over. -+ size_t len = bitmap.computeByteSize(); ++ size_t len = pixmap.computeByteSize(); + auto cryptoDigest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_1); -+ cryptoDigest->addBytes(std::span(reinterpret_cast(bitmap.getPixels()), len)); ++ cryptoDigest->addBytes(std::span(reinterpret_cast(pixmap.addr()), len)); + auto digest = cryptoDigest->computeHash(); + if (m_lastFrameDigest == digest) + return; @@ -12585,13 +12837,8 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 + // Scale image to fit width / height + double scale = std::min(m_screencastWidth / displaySize.width(), m_screencastHeight / displaySize.height()); + if (scale < 1) { -+ // Create a destination bitmap with the desired size -+ SkImageInfo dstInfo = SkImageInfo::MakeN32Premul(displaySize.width() * scale, displaySize.height() * scale); + SkBitmap dstBitmap; -+ if (!dstBitmap.allocPixels(dstInfo)) { -+ fprintf(stderr, "Failed to allocate dstBitmap\n"); -+ return; -+ } ++ dstBitmap.allocPixels(SkImageInfo::MakeN32Premul(displaySize.width() * scale, displaySize.height() * scale)); + SkCanvas canvas(dstBitmap); + canvas.scale(scale, scale); + canvas.drawImage(image, 0, 0); @@ -12600,7 +12847,7 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 + + SkPixmap pixmap; + if (!image->peekPixels(&pixmap)) { -+ fprintf(stderr, "Failed to peek pixels from SkImage\n"); ++ fprintf(stderr, "Failed to peek pixels from SkImage for JPEG encoding\n"); + return; + } + @@ -12619,7 +12866,7 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 +} +#endif + -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) +void InspectorScreencastAgent::didPaint(cairo_surface_t* surface) +{ +#if PLATFORM(WPE) @@ -12826,7 +13073,7 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 +} +#endif + -+#if USE(CAIRO) && !PLATFORM(WPE) ++#if (USE(CAIRO) && !PLATFORM(WPE)) || PLATFORM(GTK) +void InspectorScreencastAgent::encodeFrame() +{ + if (!m_encoder && !m_screencast) @@ -12840,7 +13087,7 @@ index 0000000000000000000000000000000000000000..41c52ae52e31fb90721be7cab8567774 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.h b/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.h new file mode 100644 -index 0000000000000000000000000000000000000000..72399da38336232f93776e8c6e8b342ad51711ba +index 0000000000000000000000000000000000000000..f1322caa318ca408731697b2eb6349e0dc9c8537 --- /dev/null +++ b/Source/WebKit/UIProcess/Inspector/Agents/InspectorScreencastAgent.h @@ -0,0 +1,109 @@ @@ -12914,10 +13161,10 @@ index 0000000000000000000000000000000000000000..72399da38336232f93776e8c6e8b342a + void didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) override; + void willDestroyFrontendAndBackend(Inspector::DisconnectReason) override; + -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) + void didPaint(sk_sp&& surface); +#endif -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) + void didPaint(cairo_surface_t*); +#endif + @@ -12955,7 +13202,7 @@ index 0000000000000000000000000000000000000000..72399da38336232f93776e8c6e8b342a +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/Agents/ScreencastEncoder.cpp b/Source/WebKit/UIProcess/Inspector/Agents/ScreencastEncoder.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3f2fe93e6 +index 0000000000000000000000000000000000000000..8690b1a245d463cd29c0196ab609ab69c80cc0a9 --- /dev/null +++ b/Source/WebKit/UIProcess/Inspector/Agents/ScreencastEncoder.cpp @@ -0,0 +1,437 @@ @@ -13000,14 +13247,14 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 +#include +#include + -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) +#include +#include +#include +#include +#endif + -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) +#include +#endif + @@ -13095,11 +13342,11 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 + WTF_MAKE_NONCOPYABLE(VPXFrame); + WTF_MAKE_FAST_ALLOCATED; +public: -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) + explicit VPXFrame(sk_sp&& surface) + : m_surface(WTFMove(surface)) + { } -+#elif USE(CAIRO) ++#elif USE(CAIRO) || PLATFORM(GTK) + explicit VPXFrame(RefPtr&& surface) + : m_surface(WTFMove(surface)) + { } @@ -13115,7 +13362,7 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 + + void convertToVpxImage(vpx_image_t* image) + { -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) + // Convert the updated region to YUV ready for encoding. + SkImageInfo info = SkImageInfo::Make(m_surface->width(), m_surface->height(), kN32_SkColorType, kPremul_SkAlphaType); + int argb_stride = info.minRowBytes(); @@ -13124,7 +13371,7 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 + uint8_t* argb_data = buffer.get(); + if (!m_surface->readPixels(info, argb_data, argb_stride, 0, 0)) + fprintf(stderr, "Read SkImage to ARGB buffer\n"); -+#elif USE(CAIRO) ++#elif USE(CAIRO) || PLATFORM(GTK) + // Convert the updated region to YUV ready for encoding. + const uint8_t* argb_data = cairo_image_surface_get_data(m_surface.get()); + int argb_stride = cairo_image_surface_get_stride(m_surface.get()); @@ -13150,9 +13397,9 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 + } + +private: -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) + sk_sp m_surface; -+#elif USE(CAIRO) ++#elif USE(CAIRO) || PLATFORM(GTK) + RefPtr m_surface; +#elif PLATFORM(MAC) + RetainPtr m_windowImage; @@ -13315,7 +13562,7 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 + m_lastFrameTimestamp = now; +} + -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) +void ScreencastEncoder::encodeFrame(sk_sp&& image, IntSize size) +{ + flushLastFrame(); @@ -13339,7 +13586,7 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 + canvas.drawImage(image, 0, 0); + m_lastFrame = makeUnique(surface.asImage()); +} -+#elif USE(CAIRO) ++#elif USE(CAIRO) || PLATFORM(GTK) +void ScreencastEncoder::encodeFrame(cairo_surface_t* drawingAreaSurface, IntSize size) +{ + flushLastFrame(); @@ -13398,7 +13645,7 @@ index 0000000000000000000000000000000000000000..026f5f7cbb9a81d83b9d4c307aecffe3 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/Agents/ScreencastEncoder.h b/Source/WebKit/UIProcess/Inspector/Agents/ScreencastEncoder.h new file mode 100644 -index 0000000000000000000000000000000000000000..987577f5d9c2bb21eec25bc0839cc3af7b84ce1e +index 0000000000000000000000000000000000000000..f8701329f574bfad15f0e5456360a3ee3bd21b48 --- /dev/null +++ b/Source/WebKit/UIProcess/Inspector/Agents/ScreencastEncoder.h @@ -0,0 +1,82 @@ @@ -13456,9 +13703,9 @@ index 0000000000000000000000000000000000000000..987577f5d9c2bb21eec25bc0839cc3af + ScreencastEncoder(std::unique_ptr&&, WebCore::IntSize); + ~ScreencastEncoder(); + -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) + void encodeFrame(sk_sp&&, WebCore::IntSize); -+#elif USE(CAIRO) ++#elif USE(CAIRO) || PLATFORM(GTK) + void encodeFrame(cairo_surface_t*, WebCore::IntSize); +#elif PLATFORM(MAC) + void encodeFrame(RetainPtr&&); @@ -13619,10 +13866,10 @@ index 0000000000000000000000000000000000000000..e2ce910f3fd7f587add552275b7e7176 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp b/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp -index fedef553cec70feb6c6475a5f752fbc6833dbb38..16e73d6a724dd80487b7b140aaaccfec253eb7aa 100644 +index 5cd89b25d3d87ec952d9a1a55351c9a7d76b9125..f43f2b64bcb6fffc672406f9eea9f7bda96918d6 100644 --- a/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp +++ b/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp -@@ -28,11 +28,10 @@ +@@ -28,7 +28,7 @@ #include "MessageSenderInlines.h" #include "ProvisionalPageProxy.h" @@ -13631,11 +13878,7 @@ index fedef553cec70feb6c6475a5f752fbc6833dbb38..16e73d6a724dd80487b7b140aaaccfec #include "WebPageInspectorTarget.h" #include "WebPageMessages.h" #include "WebPageProxy.h" --#include "WebProcessProxy.h" - - namespace WebKit { - -@@ -40,19 +39,17 @@ using namespace Inspector; +@@ -40,19 +40,17 @@ using namespace Inspector; std::unique_ptr InspectorTargetProxy::create(WebPageProxy& page, const String& targetId, Inspector::InspectorTargetType type) { @@ -13660,14 +13903,14 @@ index fedef553cec70feb6c6475a5f752fbc6833dbb38..16e73d6a724dd80487b7b140aaaccfec , m_identifier(targetId) , m_type(type) { -@@ -99,6 +96,31 @@ void InspectorTargetProxy::didCommitProvisionalTarget() +@@ -99,6 +97,31 @@ void InspectorTargetProxy::didCommitProvisionalTarget() m_provisionalPage = nullptr; } +void InspectorTargetProxy::willResume() +{ + if (m_page.hasRunningProcess()) -+ m_page.send(Messages::WebPage::ResumeInspectorIfPausedInNewWindow()); ++ m_page.legacyMainFrameProcess().send(Messages::WebPage::ResumeInspectorIfPausedInNewWindow(), m_page.webPageIDInMainFrameProcess()); +} + +void InspectorTargetProxy::activate(String& error) @@ -13733,7 +13976,7 @@ index a2239cec8e18850f35f7f88a9c4ebadc62bf4023..79f3ff84327dc075ec96983e04db4b10 } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp -index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0654b9679 100644 +index 96cfb0cbc8749708d263a79270746a0185bff348..c6a71685084ca322925c848a404e5d80db0b727e 100644 --- a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp +++ b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp @@ -26,13 +26,21 @@ @@ -13811,13 +14054,13 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 + // window.open will create page with already running process. + if (!m_inspectedPage->hasRunningProcess()) + return; - String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); + String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageIDInMainFrameProcess()); createInspectorTarget(pageTargetId, Inspector::InspectorTargetType::Page); } +void WebPageInspectorController::didFinishAttachingToWebProcess() +{ -+ String pageTargetID = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); ++ String pageTargetID = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageIDInMainFrameProcess()); + // Create target only after attaching to a Web Process first time. Before that + // we cannot event establish frontend connection. + if (m_targets.contains(pageTargetID)) @@ -13827,7 +14070,7 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 + void WebPageInspectorController::pageClosed() { -+ String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); ++ String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageIDInMainFrameProcess()); + destroyInspectorTarget(pageTargetId); + disconnectAllFrontends(); @@ -13842,7 +14085,7 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 +{ + if (reason != ProcessTerminationReason::Crash) + return false; -+ String targetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); ++ String targetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageIDInMainFrameProcess()); + auto it = m_targets.find(targetId); + if (it == m_targets.end()) + return false; @@ -13926,7 +14169,7 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 } #endif -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) +void WebPageInspectorController::didPaint(sk_sp&& surface) +{ + if (!m_frontendRouter->hasFrontends()) @@ -13935,7 +14178,7 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 + m_screecastAgent->didPaint(WTFMove(surface)); +} +#endif -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) +void WebPageInspectorController::didPaint(cairo_surface_t* surface) +{ + if (!m_frontendRouter->hasFrontends()) @@ -14016,14 +14259,14 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 + if (!m_inspectedPage->isPageOpenedByDOMShowingInitialEmptyDocument()) + return false; + -+ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID())); ++ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageIDInMainFrameProcess())); + ASSERT(target); + return target->isPaused(); +} + +void WebPageInspectorController::setContinueLoadingCallback(WTF::Function&& callback) +{ -+ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID())); ++ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageIDInMainFrameProcess())); + ASSERT(target); + target->setResumeCallback(WTFMove(callback)); +} @@ -14069,7 +14312,7 @@ index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..916464f24c583edeacca19cc1955cea0 + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h -index c6aafe0e9339c8ac02dc133754ddc23e1cb522ff..964fd0b69738771ec24e8da54bbe1aea583dfd90 100644 +index c6aafe0e9339c8ac02dc133754ddc23e1cb522ff..00b4ae5f9a7ac36f1fceab1c3ab39b20086302ae 100644 --- a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h +++ b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h @@ -26,6 +26,7 @@ @@ -14164,10 +14407,10 @@ index c6aafe0e9339c8ac02dc133754ddc23e1cb522ff..964fd0b69738771ec24e8da54bbe1aea #if ENABLE(REMOTE_INSPECTOR) void setIndicating(bool); #endif -+#if USE(SKIA) ++#if USE(SKIA) && !PLATFORM(GTK) + void didPaint(sk_sp&&); +#endif -+#if USE(CAIRO) ++#if USE(CAIRO) || PLATFORM(GTK) + void didPaint(cairo_surface_t*); +#endif + using NavigationHandler = Function; @@ -14454,10 +14697,10 @@ 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..bb5dcfc6234db1b5100e4aa98d678370a4dad12d +index 0000000000000000000000000000000000000000..f0675fcaf55625bce9b23a378d556030c27997f8 --- /dev/null +++ b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp -@@ -0,0 +1,1025 @@ +@@ -0,0 +1,1026 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -14529,6 +14772,7 @@ index 0000000000000000000000000000000000000000..bb5dcfc6234db1b5100e4aa98d678370 +#include +#include +#include ++#include + +using namespace Inspector; + @@ -14640,13 +14884,13 @@ index 0000000000000000000000000000000000000000..bb5dcfc6234db1b5100e4aa98d678370 +static Ref> getEnabledWindowFeatures(const WebCore::WindowFeatures& features) { + auto result = JSON::ArrayOf::create(); + if (features.x) -+ result->addItem("left="_s + String::number(*features.x)); ++ result->addItem(makeString("left="_s, String::number(*features.x))); + if (features.y) -+ result->addItem("top="_s + String::number(*features.y)); ++ result->addItem(makeString("top="_s, String::number(*features.y))); + if (features.width) -+ result->addItem("width="_s + String::number(*features.width)); ++ result->addItem(makeString("width="_s, String::number(*features.width))); + if (features.height) -+ result->addItem("height="_s + String::number(*features.height)); ++ result->addItem(makeString("height="_s, String::number(*features.height))); + if (features.menuBarVisible) + result->addItem("menubar"_s); + if (features.toolBarVisible) @@ -15191,7 +15435,7 @@ index 0000000000000000000000000000000000000000..bb5dcfc6234db1b5100e4aa98d678370 + } + + auto sandboxExtensionHandles = SandboxExtension::createReadOnlyHandlesForFiles("InspectorPlaywrightAgent::grantFileReadAccess"_s, files); -+ pageProxyChannel->page().send(Messages::WebPage::ExtendSandboxForFilesFromOpenPanel(WTFMove(sandboxExtensionHandles))); ++ pageProxyChannel->page().legacyMainFrameProcess().send(Messages::WebPage::ExtendSandboxForFilesFromOpenPanel(WTFMove(sandboxExtensionHandles)), pageProxyChannel->page().webPageIDInMainFrameProcess()); +#endif + return { }; +} @@ -15707,11 +15951,50 @@ index 0000000000000000000000000000000000000000..e7a3dcc533294bb6e12f65d79b5b716b +} // namespace WebKit + +#endif // ENABLE(REMOTE_INSPECTOR) +diff --git a/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp b/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp +index c5df687a8093a3ee9bdddbecb69629584c197012..f09af9804e7a587a3c1a444317ba1a16ca52fb7f 100644 +--- a/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp ++++ b/Source/WebKit/UIProcess/Launcher/glib/ProcessLauncherGLib.cpp +@@ -188,6 +188,13 @@ void ProcessLauncher::launchProcess() + nargs++; + } + #endif ++// Playwright begin ++ bool enableSharedArrayBuffer = false; ++ if (m_launchOptions.processType == ProcessLauncher::ProcessType::Web && m_client && m_client->shouldEnableSharedArrayBuffer()) { ++ enableSharedArrayBuffer = true; ++ nargs++; ++ } ++// Playwright end + + char** argv = g_newa(char*, nargs); + unsigned i = 0; +@@ -203,6 +210,10 @@ void ProcessLauncher::launchProcess() + if (configureJSCForTesting) + argv[i++] = const_cast("--configure-jsc-for-testing"); + #endif ++// Playwright begin ++ if (enableSharedArrayBuffer) ++ argv[i++] = const_cast("--enable-shared-array-buffer"); ++// Playwright end + argv[i++] = nullptr; + + // Warning: we want GIO to be able to spawn with posix_spawn() rather than fork()/exec(), in diff --git a/Source/WebKit/UIProcess/Launcher/win/ProcessLauncherWin.cpp b/Source/WebKit/UIProcess/Launcher/win/ProcessLauncherWin.cpp -index fac881d7c3d44758591d7a9f392a3992ce9f9a72..ad56113497f21d2916664ffe605489e939c736f4 100644 +index fac881d7c3d44758591d7a9f392a3992ce9f9a72..35eba5a0b31fc6e2d6e5c05c9f866c03d2e1c7d0 100644 --- a/Source/WebKit/UIProcess/Launcher/win/ProcessLauncherWin.cpp +++ b/Source/WebKit/UIProcess/Launcher/win/ProcessLauncherWin.cpp -@@ -97,8 +97,11 @@ void ProcessLauncher::launchProcess() +@@ -91,14 +91,21 @@ void ProcessLauncher::launchProcess() + commandLineBuilder.append(" -configure-jsc-for-testing"_s); + if (!m_client->isJITEnabled()) + commandLineBuilder.append(" -disable-jit"_s); ++// Playwright begin ++ if (m_launchOptions.processType == ProcessLauncher::ProcessType::Web && m_client->shouldEnableSharedArrayBuffer()) ++ commandLineBuilder.append(" -enable-shared-array-buffer"_s); ++// Playwright end + commandLineBuilder.append('\0'); + + auto commandLine = commandLineBuilder.toString().wideCharacters(); STARTUPINFO startupInfo { }; startupInfo.cb = sizeof(startupInfo); @@ -15725,7 +16008,7 @@ index fac881d7c3d44758591d7a9f392a3992ce9f9a72..ad56113497f21d2916664ffe605489e9 BOOL result = ::CreateProcess(0, commandLine.data(), 0, 0, true, 0, 0, 0, &startupInfo, &processInformation); diff --git a/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp b/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp -index 4dbbd6c6206b214541217f6b49f4ba441855f8f9..6b4b3d7448a64e9b0519f37ce75e364deba62b6f 100644 +index 919dede3e9653f6e16d6772a90a023223c0845a9..8e0a214d02468c900dedc803204d4704b4aa6d12 100644 --- a/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp +++ b/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp @@ -30,6 +30,7 @@ @@ -15737,7 +16020,7 @@ index 4dbbd6c6206b214541217f6b49f4ba441855f8f9..6b4b3d7448a64e9b0519f37ce75e364d #include "RemoteMediaSessionCoordinatorProxyMessages.h" #include "WebPageProxy.h" diff --git a/Source/WebKit/UIProcess/PageClient.h b/Source/WebKit/UIProcess/PageClient.h -index b368e79479e84e95eebbaaea5e3d0e0dd4c70d62..1ad4b36568d33e7fd52dbabfa1ef6e5bbe6a8433 100644 +index cc9598fc619fb1bf3f4070d52a7587e209d4a147..58d92b530a60ce327cad1b47566bbb57450e955e 100644 --- a/Source/WebKit/UIProcess/PageClient.h +++ b/Source/WebKit/UIProcess/PageClient.h @@ -72,6 +72,11 @@ @@ -15765,7 +16048,7 @@ index b368e79479e84e95eebbaaea5e3d0e0dd4c70d62..1ad4b36568d33e7fd52dbabfa1ef6e5b namespace WebKit { class PageClient; } -@@ -357,7 +368,20 @@ public: +@@ -373,7 +384,20 @@ public: virtual void selectionDidChange() = 0; #endif @@ -16251,7 +16534,7 @@ index d499ee31f32b9dcdb456ba0b476d211fba115673..43b349887d18e21162b59fa8174df32c namespace WebCore { class PlatformWheelEvent; diff --git a/Source/WebKit/UIProcess/RemotePageProxy.cpp b/Source/WebKit/UIProcess/RemotePageProxy.cpp -index b89ab7f35c6a3bfda89f1575b65d192f8237d1eb..d2bba95b349634674240c964511606e49cb11912 100644 +index f08f1ae907d39f75ec5043921c44dfeb1c581511..a892f5050b0f7a37c1231c9ca41dfb6030249aba 100644 --- a/Source/WebKit/UIProcess/RemotePageProxy.cpp +++ b/Source/WebKit/UIProcess/RemotePageProxy.cpp @@ -43,6 +43,7 @@ @@ -16263,7 +16546,7 @@ index b89ab7f35c6a3bfda89f1575b65d192f8237d1eb..d2bba95b349634674240c964511606e4 namespace WebKit { diff --git a/Source/WebKit/UIProcess/RemotePageProxy.h b/Source/WebKit/UIProcess/RemotePageProxy.h -index 34cd273d199470e42f3bfe152642d565b0d53485..53241e02fdbfb33d128ea4c7942a501b843c20fd 100644 +index b2019b6e792c58c9aa258d571f24dbe35c5eb926..bac988e367c158292f6e5dab957bdafcd23d91de 100644 --- a/Source/WebKit/UIProcess/RemotePageProxy.h +++ b/Source/WebKit/UIProcess/RemotePageProxy.h @@ -73,7 +73,6 @@ class WebProcessProxy; @@ -16275,13 +16558,13 @@ index 34cd273d199470e42f3bfe152642d565b0d53485..53241e02fdbfb33d128ea4c7942a501b class RemotePageProxy : public IPC::MessageReceiver { WTF_MAKE_FAST_ALLOCATED; diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp b/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp -index 777ab704b262e00208193dee7738e14d853b102a..12a66a3ff48565d6a5682207b94f39572545f786 100644 +index 40531b866fda1c35dbddb90f2ac1027688b2a09c..82003f78d7d2c9fbf2d393187636cf8b0bcf228f 100644 --- a/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp +++ b/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp @@ -37,6 +37,7 @@ #include #include - #include + #include +#include #define U2F_RELEASE_LOG(fmt, ...) RELEASE_LOG(WebAuthn, "%p [transport=%s] - U2fAuthenticator::" fmt, this, transportForDebugging().utf8().data(), ##__VA_ARGS__) @@ -16311,7 +16594,7 @@ index e2ded3c65b9680346be2534a3e970e2f425a83a8..b0c006c1dcbfae4b33530f8eae04f986 WebPageProxy* page() const { return m_page.get(); } diff --git a/Source/WebKit/UIProcess/WebFrameProxy.cpp b/Source/WebKit/UIProcess/WebFrameProxy.cpp -index 992ebbfcd3094576076084ef394dd4802c5dcc8e..2a1e09b4da5e67ba524f769e3de239b77043cf16 100644 +index 21a9f2d213db48f58881d0873cfd17ce962080b8..7932e3de12dc1af7f3aad4a9cdbb8bf0004a67ba 100644 --- a/Source/WebKit/UIProcess/WebFrameProxy.cpp +++ b/Source/WebKit/UIProcess/WebFrameProxy.cpp @@ -31,6 +31,7 @@ @@ -16599,10 +16882,10 @@ index 0000000000000000000000000000000000000000..b70bfe0411571f4d181a7fae3186aaae +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageInspectorInputAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorInputAgent.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..8e48dfabe2a520deec08b771cf6699b22e27c9da +index 0000000000000000000000000000000000000000..247c1817e85e5935eacef5e5ecdc07737dbf4a85 --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorInputAgent.cpp -@@ -0,0 +1,334 @@ +@@ -0,0 +1,394 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -16635,8 +16918,9 @@ index 0000000000000000000000000000000000000000..8e48dfabe2a520deec08b771cf6699b2 +#include "NativeWebKeyboardEvent.h" +#include "NativeWebMouseEvent.h" +#include "NativeWebWheelEvent.h" -+#include "WebWheelEvent.h" +#include "WebPageProxy.h" ++#include "WebTouchEvent.h" ++#include "WebWheelEvent.h" +#include +#include +#include @@ -16902,10 +17186,69 @@ index 0000000000000000000000000000000000000000..8e48dfabe2a520deec08b771cf6699b2 +#endif +} + -+void WebPageInspectorInputAgent::dispatchTapEvent(int x, int y, std::optional&& modifiers, Ref&& callback) { -+ m_page.sendWithAsyncReply(Messages::WebPage::FakeTouchTap(WebCore::IntPoint(x, y), modifiers ? *modifiers : 0), [callback]() { ++void WebPageInspectorInputAgent::dispatchTapEvent(int x, int y, std::optional&& modifiers, Ref&& callback) ++{ ++ m_page.legacyMainFrameProcess().sendWithAsyncReply(Messages::WebPage::FakeTouchTap(WebCore::IntPoint(x, y), modifiers ? *modifiers : 0), [callback]() { + callback->sendSuccess(); -+ }); ++ }, m_page.webPageIDInMainFrameProcess()); ++} ++ ++void WebPageInspectorInputAgent::dispatchTouchEvent(const String& type, std::optional&& modifiers, RefPtr&& in_touchPoints, Ref&& callback) ++{ ++ float rotationAngle = 0.0; ++ float force = 1.0; ++ const WebCore::IntSize radius(1, 1); ++ ++ uint8_t unsignedModifiers = modifiers ? static_cast(*modifiers) : 0; ++ OptionSet eventModifiers; ++ eventModifiers = eventModifiers.fromRaw(unsignedModifiers); ++ ++ WebPlatformTouchPoint::State state; ++ if (type == "touchStart"_s) ++ state = WebPlatformTouchPoint::State::Pressed; ++ else if (type == "touchMove"_s) ++ state = WebPlatformTouchPoint::State::Moved; ++ else if (type == "touchEnd"_s) ++ state = WebPlatformTouchPoint::State::Released; ++ else if (type == "touchCancel"_s) ++ state = WebPlatformTouchPoint::State::Cancelled; ++ else { ++ callback->sendFailure("Unsupported event type"_s); ++ return; ++ } ++ ++ Vector touchPoints; ++ for (unsigned i = 0; i < in_touchPoints->length(); ++i) { ++ RefPtr item = in_touchPoints->get(i); ++ RefPtr obj = item->asObject(); ++ if (!obj) { ++ callback->sendFailure("Invalid TouchPoint format"_s); ++ return; ++ } ++ std::optional x = obj->getInteger("x"_s); ++ if (!x) { ++ callback->sendFailure("TouchPoint does not have x"_s); ++ return; ++ } ++ std::optional y = obj->getInteger("y"_s); ++ if (!y) { ++ callback->sendFailure("TouchPoint does not have y"_s); ++ return; ++ } ++ std::optional optionalId = obj->getInteger("id"_s); ++ int id = optionalId ? *optionalId : 0; ++ const WebCore::IntPoint position(*x, *y); ++ touchPoints.append(WebPlatformTouchPoint(id, state, position, position, radius, rotationAngle, force)); ++ } ++ ++ WebTouchEvent touchEvent({WebEventType::TouchStart, eventModifiers, WallTime::now()}, WTFMove(touchPoints)); ++ m_page.legacyMainFrameProcess().sendWithAsyncReply(Messages::WebPage::TouchEvent(touchEvent), [callback] (std::optional eventType, bool) { ++ if (!eventType) { ++ callback->sendFailure("Failed to dispatch touch event."_s); ++ return; ++ } ++ callback->sendSuccess(); ++ }, m_page.webPageIDInMainFrameProcess()); +} + +void WebPageInspectorInputAgent::dispatchWheelEvent(int x, int y, std::optional&& modifiers, std::optional&& deltaX, std::optional&& deltaY, Ref&& callback) @@ -16939,10 +17282,10 @@ index 0000000000000000000000000000000000000000..8e48dfabe2a520deec08b771cf6699b2 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageInspectorInputAgent.h b/Source/WebKit/UIProcess/WebPageInspectorInputAgent.h new file mode 100644 -index 0000000000000000000000000000000000000000..3e87bf40ced2301f4fb145c6cb31f2cf7fa15dd6 +index 0000000000000000000000000000000000000000..26a2a3c0791c334f811ec99a630314f8e8521d02 --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorInputAgent.h -@@ -0,0 +1,86 @@ +@@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -17007,6 +17350,7 @@ index 0000000000000000000000000000000000000000..3e87bf40ced2301f4fb145c6cb31f2cf + void dispatchKeyEvent(const String& type, std::optional&& modifiers, const String& text, const String& unmodifiedText, const String& code, const String& key, std::optional&& windowsVirtualKeyCode, std::optional&& nativeVirtualKeyCode, std::optional&& autoRepeat, std::optional&& isKeypad, std::optional&& isSystemKey, RefPtr&&, Ref&& callback) override; + void dispatchMouseEvent(const String& type, int x, int y, std::optional&& modifiers, const String& button, std::optional&& buttons, std::optional&& clickCount, std::optional&& deltaX, std::optional&& deltaY, Ref&& callback) override; + void dispatchTapEvent(int x, int y, std::optional&& modifiers, Ref&& callback) override; ++ void dispatchTouchEvent(const String& type, std::optional&& modifiers, RefPtr&& touchPoints, Ref&& callback) override; + void dispatchWheelEvent(int x, int y, std::optional&& modifiers, std::optional&& deltaX, std::optional&& deltaY, Ref&& callback) override; + +private: @@ -17030,10 +17374,10 @@ index 0000000000000000000000000000000000000000..3e87bf40ced2301f4fb145c6cb31f2cf + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp -index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a4de493a3 100644 +index f96981fa78e5d08faeaa261cf4ccb64e1c9241f8..a112a30315ed7708ea140c876d10ce14e4716584 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp -@@ -186,12 +186,14 @@ +@@ -190,12 +190,14 @@ #include #include #include @@ -17048,7 +17392,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a #include #include #include -@@ -212,6 +214,7 @@ +@@ -216,6 +218,7 @@ #include #include #include @@ -17056,7 +17400,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a #include #include #include -@@ -219,12 +222,15 @@ +@@ -223,12 +226,15 @@ #include #include #include @@ -17072,7 +17416,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a #include #include #include -@@ -299,6 +305,9 @@ +@@ -310,6 +316,9 @@ #include "AcceleratedBackingStoreDMABuf.h" #endif #include "GtkSettingsManager.h" @@ -17082,7 +17426,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a #include #endif -@@ -421,6 +430,8 @@ static constexpr Seconds tryCloseTimeoutDelay = 50_ms; +@@ -432,6 +441,8 @@ static constexpr Seconds tryCloseTimeoutDelay = 50_ms; static constexpr Seconds audibleActivityClearDelay = 10_s; #endif @@ -17091,7 +17435,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, webPageProxyCounter, ("WebPageProxy")); #if PLATFORM(COCOA) -@@ -826,6 +837,10 @@ WebPageProxy::~WebPageProxy() +@@ -846,6 +857,10 @@ WebPageProxy::~WebPageProxy() if (preferences->mediaSessionCoordinatorEnabled()) GroupActivitiesSessionNotifier::sharedNotifier().removeWebPage(*this); #endif @@ -17102,7 +17446,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } void WebPageProxy::addAllMessageReceivers() -@@ -1364,6 +1379,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) +@@ -1414,6 +1429,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) protectedPageClient()->didRelaunchProcess(); internals().pageLoadState.didSwapWebProcesses(); @@ -17110,7 +17454,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } void WebPageProxy::didAttachToRunningProcess() -@@ -1372,7 +1388,7 @@ void WebPageProxy::didAttachToRunningProcess() +@@ -1422,7 +1438,7 @@ void WebPageProxy::didAttachToRunningProcess() #if ENABLE(FULLSCREEN_API) ASSERT(!m_fullScreenManager); @@ -17119,7 +17463,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a #endif #if ENABLE(VIDEO_PRESENTATION_MODE) ASSERT(!m_playbackSessionManager); -@@ -1767,6 +1783,21 @@ WebProcessProxy& WebPageProxy::ensureRunningProcess() +@@ -1823,6 +1839,21 @@ WebProcessProxy& WebPageProxy::ensureRunningProcess() return m_legacyMainFrameProcess; } @@ -17134,14 +17478,14 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a + loadParameters.request = WTFMove(request); + loadParameters.shouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow; + loadParameters.shouldTreatAsContinuingLoad = ShouldTreatAsContinuingLoad::No; -+ legacyMainFrameProcess().send(Messages::WebPage::LoadRequestInFrameForInspector(WTFMove(loadParameters), frame->frameID()), internals().webPageID); ++ m_legacyMainFrameProcess->send(Messages::WebPage::LoadRequestInFrameForInspector(WTFMove(loadParameters), frame->frameID()), internals().webPageID); + return navigation; +} + RefPtr WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData) { if (m_isClosed) -@@ -2365,6 +2396,61 @@ void WebPageProxy::setControlledByAutomation(bool controlled) +@@ -2422,6 +2453,61 @@ void WebPageProxy::setControlledByAutomation(bool controlled) websiteDataStore().protectedNetworkProcess()->send(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation), 0); } @@ -17172,7 +17516,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a + auto deviceOrientation = toScreenOrientationType(angle.value_or(0)); + if (m_screenOrientationManager) + m_screenOrientationManager->setCurrentOrientation(deviceOrientation); -+ legacyMainFrameProcess().send(Messages::WebPage::SetDeviceOrientation(angle.value_or(0)), webPageID()); ++ m_legacyMainFrameProcess->send(Messages::WebPage::SetDeviceOrientation(angle.value_or(0)), webPageIDInMainFrameProcess()); +} + +std::optional WebPageProxy::permissionForAutomation(const String& origin, const String& permission) const @@ -17200,10 +17544,10 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a + fprintf(stderr, "RENDERER: %s\n", str.utf8().data()); +} + - void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type) + void WebPageProxy::createInspectorTarget(IPC::Connection& connection, const String& targetId, Inspector::InspectorTargetType type) { - MESSAGE_CHECK(m_legacyMainFrameProcess, !targetId.isEmpty()); -@@ -2606,6 +2692,24 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpdate) + MESSAGE_CHECK_BASE(!targetId.isEmpty(), &connection); +@@ -2663,6 +2749,24 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpdate) bool wasVisible = isViewVisible(); Ref pageClient = this->pageClient(); internals().activityState.remove(flagsToUpdate); @@ -17228,7 +17572,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a if (flagsToUpdate & ActivityState::IsFocused && pageClient->isViewFocused()) internals().activityState.add(ActivityState::IsFocused); if (flagsToUpdate & ActivityState::WindowIsActive && pageClient->isViewWindowActive()) -@@ -3348,7 +3452,7 @@ void WebPageProxy::performDragOperation(DragData& dragData, const String& dragSt +@@ -3401,7 +3505,7 @@ void WebPageProxy::performDragOperation(DragData& dragData, const String& dragSt grantAccessToCurrentPasteboardData(dragStorageName); #endif @@ -17237,7 +17581,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a performDragControllerAction(DragControllerAction::PerformDragOperation, dragData); #else if (!hasRunningProcess()) -@@ -3365,6 +3469,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag +@@ -3418,6 +3522,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag if (!hasRunningProcess()) return; @@ -17246,16 +17590,16 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a 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 (!remoteUserInputEventData) { didPerformDragControllerAction(dragOperation, dragHandlingMethod, mouseIsOverFileInput, numberOfItemsToBeAccepted, insertionRect, editableElementRect); -@@ -3381,6 +3487,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag - protectedLegacyMainFrameProcess()->assumeReadAccessToBaseURL(*this, url); +@@ -3427,7 +3533,7 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag + performDragControllerAction(action, dragData, remoteUserInputEventData->targetFrameID); + }; - ASSERT(dragData.platformData()); -+#endif +-#if PLATFORM(GTK) +#if PLATFORM(GTK) || PLATFORM(WPE) - sendWithAsyncReply(Messages::WebPage::PerformDragControllerAction(action, dragData.clientPosition(), dragData.globalPosition(), dragData.draggingSourceOperationMask(), *dragData.platformData(), dragData.flags()), WTFMove(completionHandler)); - #else - sendToProcessContainingFrame(frameID, Messages::WebPage::PerformDragControllerAction(frameID, action, dragData), WTFMove(completionHandler)); -@@ -3396,14 +3504,34 @@ void WebPageProxy::didPerformDragControllerAction(std::optionaldidPerformDragControllerAction(); @@ -17293,7 +17637,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a didStartDrag(); } #endif -@@ -3424,6 +3552,24 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo +@@ -3477,6 +3603,24 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo setDragCaretRect({ }); } @@ -17318,7 +17662,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a void WebPageProxy::didStartDrag() { if (!hasRunningProcess()) -@@ -3431,6 +3577,16 @@ void WebPageProxy::didStartDrag() +@@ -3484,6 +3628,16 @@ void WebPageProxy::didStartDrag() discardQueuedMouseEvents(); send(Messages::WebPage::DidStartDrag()); @@ -17335,7 +17679,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } void WebPageProxy::dragCancelled() -@@ -3590,16 +3746,37 @@ void WebPageProxy::processNextQueuedMouseEvent() +@@ -3622,16 +3776,37 @@ void WebPageProxy::processNextQueuedMouseEvent() process->startResponsivenessTimer(); } @@ -17374,12 +17718,12 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a + m_dragSelectionData = std::nullopt; + dragEnded(event.position(), event.globalPosition(), m_dragSourceOperationMask); + } -+ didReceiveEvent(eventType, true); ++ didReceiveEvent(eventType, true, std::nullopt); + } } void WebPageProxy::doAfterProcessingAllPendingMouseEvents(WTF::Function&& action) -@@ -3770,6 +3947,8 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) +@@ -3802,6 +3977,8 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->wheelEventsFlushedForPage(*this); @@ -17388,7 +17732,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } void WebPageProxy::cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent& nativeWheelEvent) -@@ -3919,7 +4098,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) +@@ -3940,7 +4117,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent) { @@ -17397,7 +17741,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a for (auto& touchPoint : touchStartEvent.touchPoints()) { auto location = touchPoint.location(); auto update = [this, location](TrackingType& trackingType, EventTrackingRegions::EventType eventType) { -@@ -4529,6 +4708,7 @@ void WebPageProxy::receivedNavigationActionPolicyDecision(WebProcessProxy& proce +@@ -4547,6 +4724,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) { @@ -17405,19 +17749,20 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a if (!hasRunningProcess()) return completionHandler(PolicyDecision { }); -@@ -5460,6 +5640,11 @@ void WebPageProxy::pageScaleFactorDidChange(double scaleFactor) +@@ -5478,6 +5656,12 @@ void WebPageProxy::pageScaleFactorDidChange(IPC::Connection& connection, double m_pageScaleFactor = scaleFactor; } -+void WebPageProxy::viewScaleFactorDidChange(double scaleFactor) ++void WebPageProxy::viewScaleFactorDidChange(IPC::Connection& connection, double scaleFactor) +{ ++ MESSAGE_CHECK_BASE(scaleFactorIsValid(scaleFactor), &connection); + m_viewScaleFactor = scaleFactor; +} + - void WebPageProxy::pluginScaleFactorDidChange(double pluginScaleFactor) + void WebPageProxy::pluginScaleFactorDidChange(IPC::Connection& connection, double pluginScaleFactor) { - MESSAGE_CHECK(m_legacyMainFrameProcess, scaleFactorIsValid(pluginScaleFactor)); -@@ -6015,6 +6200,7 @@ void WebPageProxy::didDestroyNavigationShared(Ref&& process, ui + MESSAGE_CHECK_BASE(scaleFactorIsValid(pluginScaleFactor), &connection); +@@ -6033,6 +6217,7 @@ void WebPageProxy::didDestroyNavigationShared(Ref&& process, ui Ref protectedPageClient { pageClient() }; m_navigationState->didDestroyNavigation(process->coreProcessIdentifier(), navigationID); @@ -17425,7 +17770,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData) -@@ -6282,6 +6468,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p +@@ -6300,6 +6485,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p m_failingProvisionalLoadURL = { }; @@ -17434,7 +17779,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a // 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; -@@ -6942,7 +7130,14 @@ void WebPageProxy::beginSafeBrowsingCheck(const URL&, bool, WebFramePolicyListen +@@ -6967,7 +7154,14 @@ void WebPageProxy::beginSafeBrowsingCheck(const URL&, bool, WebFramePolicyListen void WebPageProxy::decidePolicyForNavigationActionAsync(NavigationActionData&& data, CompletionHandler&& completionHandler) { @@ -17450,7 +17795,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } void WebPageProxy::decidePolicyForNavigationActionAsyncShared(Ref&& process, NavigationActionData&& data, CompletionHandler&& completionHandler) -@@ -7618,6 +7813,7 @@ void WebPageProxy::createNewPage(WindowFeatures&& windowFeatures, NavigationActi +@@ -7631,6 +7825,7 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w if (RefPtr page = originatingFrameInfo->page()) openerAppInitiatedState = page->lastNavigationWasAppInitiated(); @@ -17458,7 +17803,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a auto completionHandler = [ this, protectedThis = Ref { *this }, -@@ -7690,6 +7886,7 @@ void WebPageProxy::createNewPage(WindowFeatures&& windowFeatures, NavigationActi +@@ -7703,6 +7898,7 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w void WebPageProxy::showPage() { m_uiClient->showPage(this); @@ -17466,7 +17811,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } bool WebPageProxy::hasOpenedPage() const -@@ -7776,6 +7973,10 @@ void WebPageProxy::closePage() +@@ -7813,6 +8009,10 @@ void WebPageProxy::closePage() if (isClosed()) return; @@ -17477,7 +17822,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a WEBPAGEPROXY_RELEASE_LOG(Process, "closePage:"); protectedPageClient()->clearAllEditCommands(); m_uiClient->close(this); -@@ -7812,6 +8013,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, FrameInfoData&& f +@@ -7849,6 +8049,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 { @@ -17486,7 +17831,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a page.m_uiClient->runJavaScriptAlert(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)]() mutable { reply(); completion(); -@@ -7833,6 +8036,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, FrameInfoData&& +@@ -7870,6 +8072,8 @@ void WebPageProxy::runJavaScriptConfirm(IPC::Connection& connection, FrameIdenti if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -17495,7 +17840,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a 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 { -@@ -7856,6 +8061,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, FrameInfoData&& +@@ -7893,6 +8097,8 @@ void WebPageProxy::runJavaScriptPrompt(IPC::Connection& connection, FrameIdentif if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -17504,7 +17849,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a 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 { -@@ -7972,6 +8179,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, FrameInf +@@ -8009,6 +8215,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(IPC::Connection& connection, Fram return; } } @@ -17513,7 +17858,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a // Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer. protectedLegacyMainFrameProcess()->stopResponsivenessTimer(); -@@ -8464,6 +8673,11 @@ void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, +@@ -8501,6 +8709,11 @@ void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, } #if ENABLE(FULLSCREEN_API) @@ -17525,7 +17870,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a WebFullScreenManagerProxy* WebPageProxy::fullScreenManager() { return m_fullScreenManager.get(); -@@ -8564,6 +8778,17 @@ void WebPageProxy::requestDOMPasteAccess(DOMPasteAccessCategory pasteAccessCateg +@@ -8606,6 +8819,17 @@ void WebPageProxy::requestDOMPasteAccess(DOMPasteAccessCategory pasteAccessCateg } } @@ -17540,10 +17885,10 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a + return; + } + - m_pageClient->requestDOMPasteAccess(pasteAccessCategory, elementRect, originIdentifier, WTFMove(completionHandler)); + m_pageClient->requestDOMPasteAccess(pasteAccessCategory, requiresInteraction, elementRect, originIdentifier, WTFMove(completionHandler)); } -@@ -9424,6 +9649,8 @@ void WebPageProxy::mouseEventHandlingCompleted(std::optional event +@@ -9461,6 +9685,8 @@ void WebPageProxy::mouseEventHandlingCompleted(std::optional event if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->mouseEventsFlushedForPage(*this); didFinishProcessingAllPendingMouseEvents(); @@ -17552,7 +17897,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } } -@@ -9458,6 +9685,7 @@ void WebPageProxy::keyEventHandlingCompleted(std::optional eventTy +@@ -9495,6 +9721,7 @@ void WebPageProxy::keyEventHandlingCompleted(std::optional eventTy if (!canProcessMoreKeyEvents) { if (RefPtr automationSession = configuration().processPool().automationSession()) automationSession->keyboardEventsFlushedForPage(*this); @@ -17560,7 +17905,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a } } -@@ -9863,7 +10091,10 @@ void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason) +@@ -9906,7 +10133,10 @@ void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason) { WEBPAGEPROXY_RELEASE_LOG_ERROR(Loading, "dispatchProcessDidTerminate: reason=%" PUBLIC_LOG_STRING, processTerminationReasonToString(reason).characters()); @@ -17572,7 +17917,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a if (m_loaderClient) handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this); else -@@ -10237,6 +10468,7 @@ bool WebPageProxy::useGPUProcessForDOMRenderingEnabled() const +@@ -10292,6 +10522,7 @@ bool WebPageProxy::useGPUProcessForDOMRenderingEnabled() const WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& process, DrawingAreaProxy& drawingArea, std::optional&& remotePageParameters, bool isProcessSwap, RefPtr&& websitePolicies, std::optional&& mainFrameIdentifier) { @@ -17580,7 +17925,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a WebPageCreationParameters parameters; parameters.processDisplayName = configuration().processDisplayName(); -@@ -10461,6 +10693,8 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc +@@ -10516,6 +10747,8 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc parameters.httpsUpgradeEnabled = preferences().upgradeKnownHostsToHTTPSEnabled() ? m_configuration->httpsUpgradeEnabled() : false; @@ -17589,7 +17934,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a #if PLATFORM(IOS) || PLATFORM(VISION) // FIXME: This is also being passed over the to WebProcess via the PreferencesStore. parameters.allowsDeprecatedSynchronousXMLHttpRequestDuringUnload = allowsDeprecatedSynchronousXMLHttpRequestDuringUnload(); -@@ -10590,8 +10824,42 @@ void WebPageProxy::gamepadsRecentlyAccessed() +@@ -10648,8 +10881,42 @@ void WebPageProxy::gamepadsRecentlyAccessed() #endif // ENABLE(GAMEPAD) @@ -17632,7 +17977,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) { m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = Ref { *this }, authenticationChallenge] (bool shouldAllowLegacyTLS) { if (shouldAllowLegacyTLS) -@@ -10686,6 +10954,12 @@ void WebPageProxy::requestGeolocationPermissionForFrame(GeolocationIdentifier ge +@@ -10744,6 +11011,12 @@ void WebPageProxy::requestGeolocationPermissionForFrame(IPC::Connection& connect request->deny(); }; @@ -17645,7 +17990,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a // 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 -@@ -10748,6 +11022,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi +@@ -10806,6 +11079,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi shouldChangeDeniedToPrompt = false; if (sessionID().isEphemeral()) { @@ -17658,7 +18003,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a completionHandler(shouldChangeDeniedToPrompt ? PermissionState::Prompt : PermissionState::Denied); return; } -@@ -10762,6 +11042,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi +@@ -10820,6 +11099,12 @@ void WebPageProxy::queryPermission(const ClientOrigin& clientOrigin, const Permi return; } @@ -17672,7 +18017,7 @@ index a9f735f67fb71479cce369e020529fe9dd5a1e67..58571b1025ac41d2349e64ba0ea58e0a completionHandler(shouldChangeDeniedToPrompt ? PermissionState::Prompt : PermissionState::Denied); return; diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h -index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb870731bbb871 100644 +index 1c4d43a5025741a37708e99c398f03836de80182..df2e31590e9936525cb5037ea4456237a938de91 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -26,6 +26,7 @@ @@ -17680,10 +18025,10 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 #include "APIObject.h" +#include "APIWebsitePolicies.h" - #include "IdentifierTypes.h" #include "MessageReceiver.h" - #include "MessageSender.h" -@@ -39,6 +40,20 @@ + #include + #include +@@ -37,6 +38,20 @@ #include #include #include @@ -17704,7 +18049,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 #if USE(DICTATION_ALTERNATIVES) #include -@@ -110,6 +125,7 @@ class DestinationColorSpace; +@@ -112,6 +127,7 @@ class DestinationColorSpace; class DragData; class FloatPoint; class FloatQuad; @@ -17712,7 +18057,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 class FloatRect; class FloatSize; class FontAttributeChanges; -@@ -426,6 +442,7 @@ class WebExtensionController; +@@ -446,6 +462,7 @@ class WebExtensionController; class WebFramePolicyListenerProxy; class WebFrameProxy; class WebFullScreenManagerProxy; @@ -17720,7 +18065,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 class WebInspectorUIProxy; class WebKeyboardEvent; class WebMouseEvent; -@@ -661,6 +678,8 @@ public: +@@ -674,6 +691,8 @@ public: void setControlledByAutomation(bool); WebPageInspectorController& inspectorController() { return *m_inspectorController; } @@ -17729,7 +18074,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 #if PLATFORM(IOS_FAMILY) void showInspectorIndication(); -@@ -694,6 +713,7 @@ public: +@@ -707,6 +726,7 @@ public: bool hasSleepDisabler() const; #if ENABLE(FULLSCREEN_API) @@ -17737,7 +18082,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 WebFullScreenManagerProxy* fullScreenManager(); API::FullscreenClient& fullscreenClient() const { return *m_fullscreenClient; } -@@ -782,6 +802,12 @@ public: +@@ -795,6 +815,12 @@ public: void setPageLoadStateObserver(std::unique_ptr&&); @@ -17750,7 +18095,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 void initializeWebPage(); void setDrawingArea(std::unique_ptr&&); -@@ -808,6 +834,7 @@ public: +@@ -821,6 +847,7 @@ public: void addPlatformLoadParameters(WebProcessProxy&, LoadParameters&); RefPtr loadRequest(WebCore::ResourceRequest&&); RefPtr loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy, API::Object* userData = nullptr); @@ -17758,23 +18103,23 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 RefPtr loadFile(const String& fileURL, const String& resourceDirectoryURL, bool isAppInitiated = true, API::Object* userData = nullptr); RefPtr loadData(std::span, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData = nullptr); RefPtr loadData(std::span, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData, WebCore::ShouldOpenExternalURLsPolicy); -@@ -874,6 +901,7 @@ public: - +@@ -888,6 +915,7 @@ public: PageClient& pageClient() const; Ref protectedPageClient() const; + RefPtr optionalProtectedPageClient() const; + bool hasPageClient() const { return !!m_pageClient; } void setViewNeedsDisplay(const WebCore::Region&); void requestScroll(const WebCore::FloatPoint& scrollPosition, const WebCore::IntPoint& scrollOrigin, WebCore::ScrollIsAnimated); -@@ -1400,6 +1428,7 @@ public: +@@ -1414,6 +1442,7 @@ public: #endif - void pageScaleFactorDidChange(double); -+ void viewScaleFactorDidChange(double); - void pluginScaleFactorDidChange(double); - void pluginZoomFactorDidChange(double); + void pageScaleFactorDidChange(IPC::Connection&, double); ++ void viewScaleFactorDidChange(IPC::Connection&, double); + void pluginScaleFactorDidChange(IPC::Connection&, double); + void pluginZoomFactorDidChange(IPC::Connection&, double); -@@ -1484,14 +1513,20 @@ public: +@@ -1498,14 +1527,20 @@ public: void didStartDrag(); void dragCancelled(); void setDragCaretRect(const WebCore::IntRect&); @@ -17782,7 +18127,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 + bool cancelDragIfNeeded(); #if PLATFORM(COCOA) void startDrag(const WebCore::DragItem&, WebCore::ShareableBitmapHandle&& dragImageHandle); - void setPromisedDataForImage(const String& pasteboardName, WebCore::SharedMemoryHandle&& imageHandle, const String& filename, const String& extension, + void setPromisedDataForImage(IPC::Connection&, const String& pasteboardName, WebCore::SharedMemoryHandle&& imageHandle, const String& filename, const String& extension, const String& title, const String& url, const String& visibleURL, WebCore::SharedMemoryHandle&& archiveHandle, const String& originIdentifier); + void releaseInspectorDragPasteboard(); #endif @@ -17796,7 +18141,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 #endif void processDidBecomeUnresponsive(); -@@ -1724,6 +1759,7 @@ public: +@@ -1739,6 +1774,7 @@ public: void setViewportSizeForCSSViewportUnits(const WebCore::FloatSize&); WebCore::FloatSize viewportSizeForCSSViewportUnits() const; @@ -17804,7 +18149,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 void didReceiveAuthenticationChallengeProxy(Ref&&, NegotiatedLegacyTLS); void negotiatedLegacyTLS(); void didNegotiateModernTLS(const URL&); -@@ -1758,6 +1794,8 @@ public: +@@ -1771,6 +1807,8 @@ public: #if PLATFORM(COCOA) || PLATFORM(GTK) RefPtr takeViewSnapshot(std::optional&&); @@ -17812,8 +18157,8 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 + RefPtr takeViewSnapshot(std::optional&&) { return nullptr; } #endif - void wrapCryptoKey(const Vector&, CompletionHandler>&&)>&&); -@@ -2666,6 +2704,7 @@ private: + void wrapCryptoKey(Vector&&, CompletionHandler>&&)>&&); +@@ -2672,6 +2710,7 @@ private: RefPtr launchProcessForReload(); void requestNotificationPermission(const String& originString, CompletionHandler&&); @@ -17821,7 +18166,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 void didChangeContentSize(const WebCore::IntSize&); void didChangeIntrinsicContentSize(const WebCore::IntSize&); -@@ -3178,8 +3217,10 @@ private: +@@ -3191,8 +3230,10 @@ private: String m_overrideContentSecurityPolicy; RefPtr m_inspector; @@ -17832,7 +18177,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 std::unique_ptr m_fullScreenManager; std::unique_ptr m_fullscreenClient; #endif -@@ -3371,6 +3412,22 @@ private: +@@ -3384,6 +3425,22 @@ private: std::optional m_currentDragOperation; bool m_currentDragIsOverFileInput { false }; unsigned m_currentDragNumberOfFilesToBeAccepted { 0 }; @@ -17855,7 +18200,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 #endif bool m_mainFrameHasHorizontalScrollbar { false }; -@@ -3542,6 +3599,10 @@ private: +@@ -3555,6 +3612,10 @@ private: RefPtr messageBody; }; Vector m_pendingInjectedBundleMessages; @@ -17867,7 +18212,7 @@ index c2ee994879f2f26a8ccbfccb330acdbc85b42898..7d5c35038d16bfaabbe55905b8fb8707 #if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION) std::unique_ptr m_webDeviceOrientationUpdateProviderProxy; diff --git a/Source/WebKit/UIProcess/WebPageProxy.messages.in b/Source/WebKit/UIProcess/WebPageProxy.messages.in -index cf1a795016100a37361cad1902723c11c571fdba..2462eb77a81b5a904e1ed9bf06927f839bc6f391 100644 +index 488af1f4066de0b8adaa595cb2f4028fbce4b177..529a1b8d2f2b0dd9face3ce38cf68758ce381d2f 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.messages.in +++ b/Source/WebKit/UIProcess/WebPageProxy.messages.in @@ -29,6 +29,7 @@ messages -> WebPageProxy { @@ -17877,7 +18222,7 @@ index cf1a795016100a37361cad1902723c11c571fdba..2462eb77a81b5a904e1ed9bf06927f83 + LogToStderr(String text) DidChangeViewportProperties(struct WebCore::ViewportAttributes attributes) - DidReceiveEvent(enum:uint8_t WebKit::WebEventType eventType, bool handled) + DidReceiveEvent(enum:uint8_t WebKit::WebEventType eventType, bool handled, struct std::optional remoteUserInputEventData) @@ -182,6 +183,7 @@ messages -> WebPageProxy { #endif @@ -17886,7 +18231,7 @@ index cf1a795016100a37361cad1902723c11c571fdba..2462eb77a81b5a904e1ed9bf06927f83 PluginScaleFactorDidChange(double zoomFactor) PluginZoomFactorDidChange(double zoomFactor) -@@ -310,10 +312,14 @@ messages -> WebPageProxy { +@@ -305,10 +307,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 @@ -17918,7 +18263,7 @@ index c909cd634d6acd72695de8372866691269ad6a04..ff5b37e3b4a17eab4bd3f8e9a2a6ef84 } diff --git a/Source/WebKit/UIProcess/WebProcessPool.cpp b/Source/WebKit/UIProcess/WebProcessPool.cpp -index 3a3655d43728126604dc246de49ba7c9210038b2..6d2552c13caadb09a5c8fed13e6034a087465ddb 100644 +index c3ff67230baf4dec44f5d7232a8351ec1f77b79c..3fed70aa8f568cd9a3a4fa460ed565658405f63f 100644 --- a/Source/WebKit/UIProcess/WebProcessPool.cpp +++ b/Source/WebKit/UIProcess/WebProcessPool.cpp @@ -426,10 +426,10 @@ void WebProcessPool::setAutomationClient(std::unique_ptr& @@ -17956,10 +18301,10 @@ index 3a3655d43728126604dc246de49ba7c9210038b2..6d2552c13caadb09a5c8fed13e6034a0 parameters.urlSchemesRegisteredAsEmptyDocument = copyToVector(m_schemesToRegisterAsEmptyDocument); diff --git a/Source/WebKit/UIProcess/WebProcessProxy.cpp b/Source/WebKit/UIProcess/WebProcessProxy.cpp -index bec37fb35fb6e2077f6ad3a73d06b1b7d105ffc4..a5764d995f015b7f343303b4896c44abd38e024d 100644 +index 9411228a77d83f24384a0b9cf57f44351b48d75e..3510caa0b246b9794977f4fc99efddc9de1aceee 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.cpp +++ b/Source/WebKit/UIProcess/WebProcessProxy.cpp -@@ -188,6 +188,11 @@ Vector> WebProcessProxy::allProcesses() +@@ -189,6 +189,11 @@ Vector> WebProcessProxy::allProcesses() }); } @@ -17971,7 +18316,7 @@ index bec37fb35fb6e2077f6ad3a73d06b1b7d105ffc4..a5764d995f015b7f343303b4896c44ab RefPtr WebProcessProxy::processForIdentifier(ProcessIdentifier identifier) { return allProcessMap().get(identifier); -@@ -561,6 +566,26 @@ void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOpt +@@ -562,6 +567,26 @@ void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOpt if (WebKit::isInspectorProcessPool(processPool())) launchOptions.extraInitializationData.add("inspector-process"_s, "1"_s); @@ -17999,10 +18344,10 @@ index bec37fb35fb6e2077f6ad3a73d06b1b7d105ffc4..a5764d995f015b7f343303b4896c44ab if (isPrewarmed()) diff --git a/Source/WebKit/UIProcess/WebProcessProxy.h b/Source/WebKit/UIProcess/WebProcessProxy.h -index d154105bcabd3fe11657b484712aee58deb2d450..111e6b272fc3b1700b82bf3df98c5e501126daeb 100644 +index 7ac34410441b471cfe9bc3d9a96a7f082283813d..7a39d5f45d7b422330b8cd444ebe78cfa145c692 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.h +++ b/Source/WebKit/UIProcess/WebProcessProxy.h -@@ -175,6 +175,7 @@ public: +@@ -178,6 +178,7 @@ public: static void forWebPagesWithOrigin(PAL::SessionID, const WebCore::SecurityOriginData&, const Function&); static Vector> allowedFirstPartiesForCookies(); @@ -18011,10 +18356,10 @@ index d154105bcabd3fe11657b484712aee58deb2d450..111e6b272fc3b1700b82bf3df98c5e50 void initializeWebProcess(WebProcessCreationParameters&&); diff --git a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp -index f3b3f0101a88eefff3b3dde49017d0d918758bfa..09f4b3d331d6f44bdce4ba4895da2e600183e6b8 100644 +index 3311783c3e193b7b3e6f4b1aeddc40560308859a..ceb002bdce785ac39ed86c4f809813c32148bfc8 100644 --- a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp +++ b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp -@@ -305,7 +305,8 @@ SOAuthorizationCoordinator& WebsiteDataStore::soAuthorizationCoordinator(const W +@@ -306,7 +306,8 @@ SOAuthorizationCoordinator& WebsiteDataStore::soAuthorizationCoordinator(const W static Ref networkProcessForSession(PAL::SessionID sessionID) { @@ -18024,7 +18369,7 @@ index f3b3f0101a88eefff3b3dde49017d0d918758bfa..09f4b3d331d6f44bdce4ba4895da2e60 if (sessionID.isEphemeral()) { // Reuse a previous persistent session network process for ephemeral sessions. for (auto& dataStore : allDataStores().values()) { -@@ -2278,6 +2279,12 @@ void WebsiteDataStore::originDirectoryForTesting(WebCore::ClientOrigin&& origin, +@@ -2279,6 +2280,12 @@ void WebsiteDataStore::originDirectoryForTesting(WebCore::ClientOrigin&& origin, protectedNetworkProcess()->websiteDataOriginDirectoryForTesting(m_sessionID, WTFMove(origin), type, WTFMove(completionHandler)); } @@ -18124,10 +18469,10 @@ index 165885137ca032390c6b55baacc30a8c4c423eb5..930ceeca56587636993f9a0c0e0a21d0 std::unique_ptr m_soAuthorizationCoordinator; #endif diff --git a/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp b/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp -index cfb4be2f7b34d1ecc97989d4bfefa6e8c1a3c864..f250d43ad30903ffa93cce52e1d3843f7c428ecf 100644 +index 7c6139c6b0d8c7c3cddd08164317794a519a7b53..027d05bdd0e4bf94d70797ab195e656f81541300 100644 --- a/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp +++ b/Source/WebKit/UIProcess/geoclue/GeoclueGeolocationProvider.cpp -@@ -101,6 +101,14 @@ void GeoclueGeolocationProvider::stop() +@@ -102,6 +102,14 @@ void GeoclueGeolocationProvider::stop() } m_sourceType = LocationProviderSource::Unknown; @@ -18142,7 +18487,7 @@ index cfb4be2f7b34d1ecc97989d4bfefa6e8c1a3c864..f250d43ad30903ffa93cce52e1d3843f } void GeoclueGeolocationProvider::setEnableHighAccuracy(bool enabled) -@@ -373,6 +381,8 @@ void GeoclueGeolocationProvider::createGeoclueClient(const char* clientPath) +@@ -374,6 +382,8 @@ void GeoclueGeolocationProvider::createGeoclueClient(const char* clientPath) return; } @@ -18461,10 +18806,10 @@ index b02c70d85fe1a93899640a8b909b0cf734d28b18..b1dc8e89eb265be81e083bf337109561 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 b71fb0a8ac0605edc6466ac1a5b12607fb8f211b..bcbfd33dfe367e1b04426f8162dd1552b4dbbcc1 100644 +index 66329bbc7f97ca577842dd93e28d4143b16e69dc..cd10bd6a29e64a1356e5a14a3a6339c4ca43327e 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp -@@ -662,4 +662,30 @@ RendererBufferFormat AcceleratedBackingStoreDMABuf::bufferFormat() const +@@ -675,4 +675,30 @@ RendererBufferFormat AcceleratedBackingStoreDMABuf::bufferFormat() const return buffer ? buffer->format() : RendererBufferFormat { }; } @@ -18496,10 +18841,10 @@ index b71fb0a8ac0605edc6466ac1a5b12607fb8f211b..bcbfd33dfe367e1b04426f8162dd1552 + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h -index cd95ceb063fc776b5d68ff45262c4d84e572c509..041c562551cd467c320b8f566264885d00d1434b 100644 +index 68d4bb329c2e116fe720c98d3e38b6672c88e389..a4a36dd35ee9330f8b9edfb09f607a12dbac7c0b 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h -@@ -88,6 +88,7 @@ private: +@@ -90,6 +90,7 @@ private: #else bool paint(cairo_t*, const WebCore::IntRect&) override; #endif @@ -18507,9 +18852,9 @@ index cd95ceb063fc776b5d68ff45262c4d84e572c509..041c562551cd467c320b8f566264885d void unrealize() override; void update(const LayerTreeContext&) override; RendererBufferFormat bufferFormat() const override; -@@ -243,6 +244,9 @@ private: - RefPtr m_pendingBuffer; +@@ -246,6 +247,9 @@ private: RefPtr m_committedBuffer; + std::optional m_pendingDamageRegion; HashMap> m_buffers; +// Playwright begin + RefPtr m_flippedSurface; @@ -18572,12 +18917,12 @@ index 0000000000000000000000000000000000000000..5a255b0389470c4fd1baaff5e8e4882e + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/gtk/WebDateTimePickerGtk.cpp b/Source/WebKit/UIProcess/gtk/WebDateTimePickerGtk.cpp -index 6029857cfcd6c4fea14de28243f1955138a62844..8a1bda8f2637670cf88baca998cbff09157d2a6f 100644 +index 97191083e1c5b5283599e394e56553fc5e5cd6a1..e8c7cbb6cd9431b1720aafb8dfd0abdc97f17a39 100644 --- a/Source/WebKit/UIProcess/gtk/WebDateTimePickerGtk.cpp +++ b/Source/WebKit/UIProcess/gtk/WebDateTimePickerGtk.cpp -@@ -34,6 +34,8 @@ - #include +@@ -35,6 +35,8 @@ #include + #include +using namespace WebCore; + @@ -18586,10 +18931,10 @@ index 6029857cfcd6c4fea14de28243f1955138a62844..8a1bda8f2637670cf88baca998cbff09 Ref WebDateTimePickerGtk::create(WebPageProxy& page) diff --git a/Source/WebKit/UIProcess/gtk/WebPageInspectorEmulationAgentGtk.cpp b/Source/WebKit/UIProcess/gtk/WebPageInspectorEmulationAgentGtk.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..a75642f1d3765efa6fcc7c07cc51beb2ddf450d2 +index 0000000000000000000000000000000000000000..7d9672b0f831b5b7f6acf14ede26e1e8e9a65389 --- /dev/null +++ b/Source/WebKit/UIProcess/gtk/WebPageInspectorEmulationAgentGtk.cpp -@@ -0,0 +1,115 @@ +@@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -18687,6 +19032,8 @@ index 0000000000000000000000000000000000000000..a75642f1d3765efa6fcc7c07cc51beb2 + } + callback("Failed to resize window"_s); +#else ++ UNUSED_PARAM(this); ++ UNUSED_PARAM(didNotHaveInitialAllocation); + UNUSED_PARAM(drawingArea); + callback(String()); +#endif @@ -18788,10 +19135,10 @@ index 0000000000000000000000000000000000000000..36ab6e9aec9f8d79fb13a8a49beadaaf + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/gtk/WebPasteboardProxyGtk.cpp b/Source/WebKit/UIProcess/gtk/WebPasteboardProxyGtk.cpp -index d18b3e777203ef5d0f33884f909bc598d3526831..aef80b47359d7a2e4805a006dc59cd60d499a60e 100644 +index 2a17b59c9be6ecc76b0ec0a16d9f4866dffa0bf4..0d5c58a88b0e5197254d0eb5bd6eee045d99d299 100644 --- a/Source/WebKit/UIProcess/gtk/WebPasteboardProxyGtk.cpp +++ b/Source/WebKit/UIProcess/gtk/WebPasteboardProxyGtk.cpp -@@ -77,8 +77,10 @@ void WebPasteboardProxy::setPrimarySelectionOwner(WebFrameProxy* frame) +@@ -78,8 +78,10 @@ void WebPasteboardProxy::setPrimarySelectionOwner(WebFrameProxy* frame) if (m_primarySelectionOwner == frame) return; @@ -18805,7 +19152,7 @@ index d18b3e777203ef5d0f33884f909bc598d3526831..aef80b47359d7a2e4805a006dc59cd60 m_primarySelectionOwner = frame; } diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm -index d45b06876bac4046a05a03fbaf734af33071191d..8c136ce12155bda85cc31d2422a0bd5ff7dfdea9 100644 +index f5290e15ec912a36766509dd2414444a8c97e06d..2563926ff72e0b8bcb35e250141549a69578a52f 100644 --- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm +++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm @@ -503,6 +503,8 @@ IntRect PageClientImpl::rootViewToAccessibilityScreen(const IntRect& rect) @@ -19027,7 +19374,7 @@ index 0000000000000000000000000000000000000000..721826c8c98fc85b68a4f45deaee69c1 + +#endif diff --git a/Source/WebKit/UIProcess/mac/PageClientImplMac.h b/Source/WebKit/UIProcess/mac/PageClientImplMac.h -index 708c5d11dec4797db15b51293a94089869024e8a..ee05508c06f739c27df3d5e6c6b5234bad8a82bf 100644 +index 4c99b44dbdd37e73c349ea8d5a3422075d2eff65..7c6dc33bf047706f13332c726a7e2364d834f7ae 100644 --- a/Source/WebKit/UIProcess/mac/PageClientImplMac.h +++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.h @@ -54,6 +54,8 @@ class PageClientImpl final : public PageClientImplCocoa @@ -19039,7 +19386,7 @@ index 708c5d11dec4797db15b51293a94089869024e8a..ee05508c06f739c27df3d5e6c6b5234b PageClientImpl(NSView *, WKWebView *); virtual ~PageClientImpl(); -@@ -171,6 +173,9 @@ private: +@@ -170,6 +172,9 @@ private: void updateAcceleratedCompositingMode(const LayerTreeContext&) override; void didFirstLayerFlush(const LayerTreeContext&) override; @@ -19049,7 +19396,7 @@ index 708c5d11dec4797db15b51293a94089869024e8a..ee05508c06f739c27df3d5e6c6b5234b RefPtr takeViewSnapshot(std::optional&&) override; void wheelEventWasNotHandledByWebCore(const NativeWebWheelEvent&) override; #if ENABLE(MAC_GESTURE_EVENTS) -@@ -225,6 +230,10 @@ private: +@@ -224,6 +229,10 @@ private: void beganExitFullScreen(const WebCore::IntRect& initialFrame, const WebCore::IntRect& finalFrame) override; #endif @@ -19061,18 +19408,10 @@ index 708c5d11dec4797db15b51293a94089869024e8a..ee05508c06f739c27df3d5e6c6b5234b 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 c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f3050926a9f 100644 +index 0116071f4359eac661cc6de71eee2d43ce2d56e0..c93c40e2064e15ccecf08a147f4189e98810a6d3 100644 --- a/Source/WebKit/UIProcess/mac/PageClientImplMac.mm +++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.mm -@@ -79,6 +79,7 @@ - #import - #import - #import -+#import - #import - #import - #import -@@ -105,6 +106,13 @@ namespace WebKit { +@@ -110,6 +110,13 @@ namespace WebKit { using namespace WebCore; @@ -19086,7 +19425,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 PageClientImpl::PageClientImpl(NSView *view, WKWebView *webView) : PageClientImplCocoa(webView) , m_view(view) -@@ -158,6 +166,9 @@ NSWindow *PageClientImpl::activeWindow() const +@@ -163,6 +170,9 @@ NSWindow *PageClientImpl::activeWindow() const bool PageClientImpl::isViewWindowActive() { @@ -19096,7 +19435,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); NSWindow *activeViewWindow = activeWindow(); return activeViewWindow.isKeyWindow || (activeViewWindow && [NSApp keyWindow] == activeViewWindow); -@@ -165,6 +176,9 @@ bool PageClientImpl::isViewWindowActive() +@@ -170,6 +180,9 @@ bool PageClientImpl::isViewWindowActive() bool PageClientImpl::isViewFocused() { @@ -19106,7 +19445,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 // 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) -@@ -188,6 +202,9 @@ void PageClientImpl::makeFirstResponder() +@@ -193,6 +206,9 @@ void PageClientImpl::makeFirstResponder() bool PageClientImpl::isViewVisible() { @@ -19116,7 +19455,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 NSView *activeView = this->activeView(); NSWindow *activeViewWindow = activeWindow(); -@@ -271,7 +288,8 @@ void PageClientImpl::didRelaunchProcess() +@@ -276,7 +292,8 @@ void PageClientImpl::didRelaunchProcess() void PageClientImpl::preferencesDidChange() { @@ -19126,7 +19465,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 } void PageClientImpl::toolTipChanged(const String& oldToolTip, const String& newToolTip) -@@ -474,6 +492,8 @@ IntRect PageClientImpl::rootViewToAccessibilityScreen(const IntRect& rect) +@@ -479,6 +496,8 @@ IntRect PageClientImpl::rootViewToAccessibilityScreen(const IntRect& rect) void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool eventWasHandled) { @@ -19135,7 +19474,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 m_impl->doneWithKeyEvent(event.nativeEvent(), eventWasHandled); } -@@ -493,6 +513,8 @@ void PageClientImpl::computeHasVisualSearchResults(const URL& imageURL, Shareabl +@@ -498,6 +517,8 @@ void PageClientImpl::computeHasVisualSearchResults(const URL& imageURL, Shareabl RefPtr PageClientImpl::createPopupMenuProxy(WebPageProxy& page) { @@ -19144,7 +19483,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 return WebPopupMenuProxyMac::create(m_view, page.popupMenuClient()); } -@@ -634,6 +656,12 @@ CALayer *PageClientImpl::footerBannerLayer() const +@@ -639,6 +660,12 @@ CALayer *PageClientImpl::footerBannerLayer() const return m_impl->footerBannerLayer(); } @@ -19157,7 +19496,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 RefPtr PageClientImpl::takeViewSnapshot(std::optional&&) { return m_impl->takeViewSnapshot(); -@@ -824,6 +852,13 @@ void PageClientImpl::beganExitFullScreen(const IntRect& initialFrame, const IntR +@@ -829,6 +856,13 @@ void PageClientImpl::beganExitFullScreen(const IntRect& initialFrame, const IntR #endif // ENABLE(FULLSCREEN_API) @@ -19171,7 +19510,7 @@ index c17d3e660de168d07772480800099730562675ed..6ebec657486366f970817f511b840f30 void PageClientImpl::navigationGestureDidBegin() { m_impl->dismissContentRelativeChildWindowsWithAnimation(true); -@@ -1011,6 +1046,9 @@ void PageClientImpl::requestScrollToRect(const WebCore::FloatRect& targetRect, c +@@ -1017,6 +1051,9 @@ void PageClientImpl::requestScrollToRect(const WebCore::FloatRect& targetRect, c bool PageClientImpl::windowIsFrontWindowUnderMouse(const NativeWebMouseEvent& event) { @@ -19199,11 +19538,23 @@ index 21c925bafb662dbe961baaad7f25bf4296236d76..5496a33c558a00a5ba96d10223e600aa } +#endif +diff --git a/Source/WebKit/UIProcess/mac/WKImmediateActionController.mm b/Source/WebKit/UIProcess/mac/WKImmediateActionController.mm +index 63c423a74cf983ab7a0be49f0376d227c49724e1..5818c786d5fb0fb4a60c549b68ec989656223e5c 100644 +--- a/Source/WebKit/UIProcess/mac/WKImmediateActionController.mm ++++ b/Source/WebKit/UIProcess/mac/WKImmediateActionController.mm +@@ -31,6 +31,7 @@ + #import "APIHitTestResult.h" + #import "MessageSenderInlines.h" + #import "WKNSURLExtras.h" ++#import "WebFrameProxy.h" + #import "WebPageMessages.h" + #import "WebPageProxy.h" + #import "WebPageProxyMessages.h" diff --git a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h -index 6ab7aacaebfda818e3010bb06db72c8552ac598a..3e19cba50d73084392f62f176ad4c3153803e579 100644 +index e34faa8ae2933154efdbf0492a2f17af7a46f83b..54b509837bb767ac3ab28d1d7059462ca7a1170b 100644 --- a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h +++ b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h -@@ -68,6 +68,7 @@ private: +@@ -78,6 +78,7 @@ private: void show() override; void showContextMenuWithItems(Vector>&&) override; void useContextMenuItems(Vector>&&) override; @@ -19212,10 +19563,10 @@ index 6ab7aacaebfda818e3010bb06db72c8552ac598a..3e19cba50d73084392f62f176ad4c315 bool showAfterPostProcessingContextData(); diff --git a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm -index 26db4b48e75b3e65febc8cbfb314a4c22e9bdfb3..138ea13ab34b83ed190436afea7d994cb4dfa70e 100644 +index bb4822a1ca6c0b209baed338f267f366424f61c6..aaffc9579deda3a1f5c8fe71747833a5d0e2e8bf 100644 --- a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm +++ b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm -@@ -478,6 +478,12 @@ void WebContextMenuProxyMac::getShareMenuItem(CompletionHandlersetIntrinsicDeviceScaleFactor(deviceScaleFactorForWindow(hwnd)); +- // If there are no m_page, use intrinsic device scale factor. +- float deviceScaleFactor = m_page ? m_page->deviceScaleFactor() : deviceScaleFactorForWindow(hwnd); +- m_viewSize = expandedIntSize(FloatSize(LOWORD(lParam), HIWORD(lParam)) / deviceScaleFactor); ++ ++ m_viewSize = expandedIntSize(FloatSize(LOWORD(lParam), HIWORD(lParam))); + + if (m_page && m_page->drawingArea()) { + // FIXME specify correctly layerPosition. diff --git a/Source/WebKit/UIProcess/wpe/InspectorTargetProxyWPE.cpp b/Source/WebKit/UIProcess/wpe/InspectorTargetProxyWPE.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7453194ca6f032ba86a4c67f5bf12688ab6ec1be @@ -20223,22 +20590,37 @@ index 0000000000000000000000000000000000000000..a7d88f8c745f95af21db71dcfce368ba + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/wpe/WebPageProxyWPE.cpp b/Source/WebKit/UIProcess/wpe/WebPageProxyWPE.cpp -index b6536b74452c26a4d661bcf6039af54824f258d0..ade7c4ad1cef7bc69c452bef404eb38437c93088 100644 +index ff2377628995b6095d7cd75f447d904847da0dc0..345aebe075a5b710303eeb8d46f94cf9dc901706 100644 --- a/Source/WebKit/UIProcess/wpe/WebPageProxyWPE.cpp +++ b/Source/WebKit/UIProcess/wpe/WebPageProxyWPE.cpp -@@ -29,6 +29,7 @@ - #include "EditorState.h" +@@ -30,6 +30,7 @@ #include "InputMethodState.h" #include "PageClientImpl.h" + #include "WebProcessProxy.h" +#include #include #if USE(ATK) +diff --git a/Source/WebKit/UIProcess/wpe/WebPreferencesWPE.cpp b/Source/WebKit/UIProcess/wpe/WebPreferencesWPE.cpp +index 41307f5fe61b92785a493f68aeca475521708d55..f02e9d93c96ac6c0abedba9ced97b02f9250ac82 100644 +--- a/Source/WebKit/UIProcess/wpe/WebPreferencesWPE.cpp ++++ b/Source/WebKit/UIProcess/wpe/WebPreferencesWPE.cpp +@@ -34,6 +34,10 @@ void WebPreferences::platformInitializeStore() + setForceCompositingMode(true); + setThreadedScrollingEnabled(true); + ++ // Playwright override begin ++ setThreadedScrollingEnabled(false); ++ // Playwright override end ++ + #if USE(SKIA) + // FIXME: Expose this as a setting when we switch to Skia. + static const char* disableAccelerated2DCanvas = getenv("WEBKIT_DISABLE_ACCELERATED_2D_CANVAS"); diff --git a/Source/WebKit/WPEPlatform/CMakeLists.txt b/Source/WebKit/WPEPlatform/CMakeLists.txt -index 74f395b50b2d4668c521a8d72a5581113cf16698..75496b3c36a58c8da1bd8382cecddc8f81c62119 100644 +index 2b64d1b5b013d53b18b7757fe3b3f3d9a0501571..e8f28808f5ef0532319a4462fd285c0770d7ce52 100644 --- a/Source/WebKit/WPEPlatform/CMakeLists.txt +++ b/Source/WebKit/WPEPlatform/CMakeLists.txt -@@ -89,6 +89,7 @@ set(WPEPlatform_SYSTEM_INCLUDE_DIRECTORIES +@@ -96,6 +96,7 @@ set(WPEPlatform_SYSTEM_INCLUDE_DIRECTORIES set(WPEPlatform_LIBRARIES Epoxy::Epoxy @@ -20247,10 +20629,10 @@ index 74f395b50b2d4668c521a8d72a5581113cf16698..75496b3c36a58c8da1bd8382cecddc8f ${GLIB_GIO_LIBRARIES} ${GLIB_GOBJECT_LIBRARIES} diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce659aeb8a 100644 +index 3cf46d518a1920a5c6c8e9537f71b1b0b25b00a0..3c7e8c09640d82630864f41569f3229076088a93 100644 --- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj +++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -@@ -1540,6 +1540,7 @@ +@@ -1547,6 +1547,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 */; }; @@ -20258,7 +20640,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 */; }; -@@ -2370,6 +2371,18 @@ +@@ -2379,6 +2380,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 */; }; @@ -20277,7 +20659,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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, ); }; }; -@@ -2458,6 +2471,8 @@ +@@ -2467,6 +2480,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 */; }; @@ -20286,7 +20668,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 */; }; -@@ -2478,6 +2493,9 @@ +@@ -2487,6 +2502,9 @@ EBA8D3B627A5E33F00CB7900 /* MockPushServiceConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = EBA8D3B027A5E33F00CB7900 /* MockPushServiceConnection.mm */; }; EBA8D3B727A5E33F00CB7900 /* PushServiceConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = EBA8D3B127A5E33F00CB7900 /* PushServiceConnection.mm */; }; ED82A7F2128C6FAF004477B3 /* WKBundlePageOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A22F0FF1289FCD90085E74F /* WKBundlePageOverlay.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -20296,7 +20678,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 */; }; F41795A62AC61B78007F5F12 /* CompactContextMenuPresenter.h in Headers */ = {isa = PBXBuildFile; fileRef = F41795A42AC619A2007F5F12 /* CompactContextMenuPresenter.h */; }; -@@ -6210,6 +6228,7 @@ +@@ -6230,6 +6248,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 = ""; }; @@ -20304,7 +20686,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 = ""; }; -@@ -7908,6 +7927,19 @@ +@@ -7930,6 +7949,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 = ""; }; @@ -20324,7 +20706,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 = ""; }; -@@ -8061,6 +8093,8 @@ +@@ -8082,6 +8114,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 = ""; }; @@ -20333,7 +20715,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 = ""; }; -@@ -8085,6 +8119,14 @@ +@@ -8111,6 +8145,14 @@ ECA680D31E6904B500731D20 /* ExtraPrivateSymbolsForTAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtraPrivateSymbolsForTAPI.h; sourceTree = ""; }; ECBFC1DB1E6A4D66000300C7 /* ExtraPublicSymbolsForTAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExtraPublicSymbolsForTAPI.h; sourceTree = ""; }; F036978715F4BF0500C3A80E /* WebColorPicker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebColorPicker.cpp; sourceTree = ""; }; @@ -20348,7 +20730,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 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 = ""; }; -@@ -8389,6 +8431,7 @@ +@@ -8415,6 +8457,7 @@ 3766F9EE189A1241003CF19B /* JavaScriptCore.framework in Frameworks */, 3766F9F1189A1254003CF19B /* libicucore.dylib in Frameworks */, 7B9FC5BB28A5233B007570E7 /* libWebKitPlatform.a in Frameworks */, @@ -20356,7 +20738,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 3766F9EF189A1244003CF19B /* QuartzCore.framework in Frameworks */, 37694525184FC6B600CDE21F /* Security.framework in Frameworks */, 37BEC4DD1948FC6A008B4286 /* WebCore.framework in Frameworks */, -@@ -11262,6 +11305,7 @@ +@@ -11296,6 +11339,7 @@ 99788ACA1F421DCA00C08000 /* _WKAutomationSessionConfiguration.mm */, 990D28A81C6404B000986977 /* _WKAutomationSessionDelegate.h */, 990D28AF1C65203900986977 /* _WKAutomationSessionInternal.h */, @@ -20364,7 +20746,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 5C4609E222430E4C009943C2 /* _WKContentRuleListAction.h */, 5C4609E322430E4D009943C2 /* _WKContentRuleListAction.mm */, 5C4609E422430E4D009943C2 /* _WKContentRuleListActionInternal.h */, -@@ -12572,6 +12616,7 @@ +@@ -12603,6 +12647,7 @@ E34B110C27C46BC6006D2F2E /* libWebCoreTestShim.dylib */, E34B110F27C46D09006D2F2E /* libWebCoreTestSupport.dylib */, DDE992F4278D06D900F60D26 /* libWebKitAdditions.a */, @@ -20372,7 +20754,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 57A9FF15252C6AEF006A2040 /* libWTF.a */, 5750F32A2032D4E500389347 /* LocalAuthentication.framework */, 570DAAB0230273D200E8FC04 /* NearField.framework */, -@@ -13142,6 +13187,12 @@ +@@ -13173,6 +13218,12 @@ children = ( 9197940423DBC4BB00257892 /* InspectorBrowserAgent.cpp */, 9197940323DBC4BB00257892 /* InspectorBrowserAgent.h */, @@ -20385,7 +20767,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce ); path = Agents; sourceTree = ""; -@@ -13150,6 +13201,7 @@ +@@ -13181,6 +13232,7 @@ isa = PBXGroup; children = ( A5D3504D1D78F0D2005124A9 /* RemoteWebInspectorUIProxyMac.mm */, @@ -20393,7 +20775,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 1CA8B935127C774E00576C2B /* WebInspectorUIProxyMac.mm */, 99A7ACE326012919006D57FD /* WKInspectorResourceURLSchemeHandler.h */, 99A7ACE42601291A006D57FD /* WKInspectorResourceURLSchemeHandler.mm */, -@@ -13866,6 +13918,7 @@ +@@ -13898,6 +13950,7 @@ E1513C65166EABB200149FCB /* AuxiliaryProcessProxy.h */, 46A2B6061E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.cpp */, 46A2B6071E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.h */, @@ -20401,7 +20783,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 5C6D69352AC3935D0099BDAF /* BrowsingContextGroup.cpp */, 5C6D69362AC3935D0099BDAF /* BrowsingContextGroup.h */, 07297F9C1C1711EA003F0735 /* DeviceIdHashSaltStorage.cpp */, -@@ -13889,6 +13942,8 @@ +@@ -13921,6 +13974,8 @@ BC06F43912DBCCFB002D78DE /* GeolocationPermissionRequestProxy.cpp */, BC06F43812DBCCFB002D78DE /* GeolocationPermissionRequestProxy.h */, 2DD5A72A1EBF09A7009BA597 /* HiddenPageThrottlingAutoIncreasesCounter.h */, @@ -20410,7 +20792,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 5CEABA2B2333251400797797 /* LegacyGlobalSettings.cpp */, 5CEABA2A2333247700797797 /* LegacyGlobalSettings.h */, 31607F3819627002009B87DA /* LegacySessionStateCoding.h */, -@@ -13922,6 +13977,7 @@ +@@ -13954,6 +14009,7 @@ 1A0C227D2451130A00ED614D /* QuickLookThumbnailingSoftLink.mm */, 1AEE57232409F142002005D6 /* QuickLookThumbnailLoader.h */, 1AEE57242409F142002005D6 /* QuickLookThumbnailLoader.mm */, @@ -20418,7 +20800,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 5CCB54DC2A4FEA6A0005FAA8 /* RemotePageDrawingAreaProxy.cpp */, 5CCB54DB2A4FEA6A0005FAA8 /* RemotePageDrawingAreaProxy.h */, 5C907E9A294D507100B3402D /* RemotePageProxy.cpp */, -@@ -14024,6 +14080,8 @@ +@@ -14056,6 +14112,8 @@ BC7B6204129A0A6700D174A4 /* WebPageGroup.h */, 2D9EA3101A96D9EB002D2807 /* WebPageInjectedBundleClient.cpp */, 2D9EA30E1A96CBFF002D2807 /* WebPageInjectedBundleClient.h */, @@ -20427,7 +20809,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce BC111B0B112F5E4F00337BAB /* WebPageProxy.cpp */, BC032DCB10F4389F0058C15A /* WebPageProxy.h */, BCBD38FA125BAB9A00D2C29F /* WebPageProxy.messages.in */, -@@ -14190,6 +14248,7 @@ +@@ -14224,6 +14282,7 @@ BC646C1911DD399F006455B0 /* WKBackForwardListItemRef.h */, BC646C1611DD399F006455B0 /* WKBackForwardListRef.cpp */, BC646C1711DD399F006455B0 /* WKBackForwardListRef.h */, @@ -20435,17 +20817,17 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce BCB9E24A1120E15C00A137E0 /* WKContext.cpp */, BCB9E2491120E15C00A137E0 /* WKContext.h */, 1AE52F9319201F6B00A1FA37 /* WKContextConfigurationRef.cpp */, -@@ -14771,6 +14830,9 @@ - 7AFA6F682A9F57C50055322A /* DisplayLinkMac.mm */, +@@ -14805,6 +14864,9 @@ + 7AFA6F682A9F57C50055322A /* DisplayLinkMac.cpp */, 31ABA79C215AF9E000C90E31 /* HighPerformanceGPUManager.h */, 31ABA79D215AF9E000C90E31 /* HighPerformanceGPUManager.mm */, + D71A94302370E025002C4D9E /* InspectorPlaywrightAgentClientMac.h */, + D7EB04E62372A73B00F744CE /* InspectorPlaywrightAgentClientMac.mm */, + D79902AF236E9404005D6F7E /* InspectorTargetProxyMac.mm */, - 7139CA3F2BF670BC00CA613A /* LegacyDisplayLinkMac.cpp */, 1AFDE65B1954E8D500C48FFA /* LegacySessionStateCoding.cpp */, 0FCB4E5818BBE3D9000FCFC9 /* PageClientImplMac.h */, -@@ -14795,6 +14857,8 @@ + 0FCB4E5918BBE3D9000FCFC9 /* PageClientImplMac.mm */, +@@ -14828,6 +14890,8 @@ E568B92120A3AC6A00E3C856 /* WebDataListSuggestionsDropdownMac.mm */, E55CD20124D09F1F0042DB9C /* WebDateTimePickerMac.h */, E55CD20224D09F1F0042DB9C /* WebDateTimePickerMac.mm */, @@ -20454,7 +20836,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce BC857E8512B71EBB00EDEB2E /* WebPageProxyMac.mm */, BC5750951268F3C6006F0F12 /* WebPopupMenuProxyMac.h */, BC5750961268F3C6006F0F12 /* WebPopupMenuProxyMac.mm */, -@@ -15797,6 +15861,7 @@ +@@ -15857,6 +15921,7 @@ 99788ACB1F421DDA00C08000 /* _WKAutomationSessionConfiguration.h in Headers */, 990D28AC1C6420CF00986977 /* _WKAutomationSessionDelegate.h in Headers */, 990D28B11C65208D00986977 /* _WKAutomationSessionInternal.h in Headers */, @@ -20462,7 +20844,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 5C4609E7224317B4009943C2 /* _WKContentRuleListAction.h in Headers */, 5C4609E8224317BB009943C2 /* _WKContentRuleListActionInternal.h in Headers */, 1A5704F81BE01FF400874AF1 /* _WKContextMenuElementInfo.h in Headers */, -@@ -16108,6 +16173,7 @@ +@@ -16168,6 +16233,7 @@ E170876C16D6CA6900F99226 /* BlobRegistryProxy.h in Headers */, 4F601432155C5AA2001FBDE0 /* BlockingResponseMap.h in Headers */, 1A5705111BE410E600874AF1 /* BlockSPI.h in Headers */, @@ -20470,7 +20852,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce A7E69BCC2B2117A100D43D3F /* BufferAndBackendInfo.h in Headers */, BC3065FA1259344E00E71278 /* CacheModel.h in Headers */, 935BF7FC2936BF1A00B41326 /* CacheStorageCache.h in Headers */, -@@ -16288,7 +16354,11 @@ +@@ -16348,7 +16414,11 @@ BC14DF77120B5B7900826C0C /* InjectedBundleScriptWorld.h in Headers */, CE550E152283752200D28791 /* InsertTextOptions.h in Headers */, 9197940523DBC4BB00257892 /* InspectorBrowserAgent.h in Headers */, @@ -20482,7 +20864,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce A5E391FD2183C1F800C8FB31 /* InspectorTargetProxy.h in Headers */, C5BCE5DF1C50766A00CDE3FA /* InteractionInformationAtPosition.h in Headers */, 2D4D2C811DF60BF3002EB10C /* InteractionInformationRequest.h in Headers */, -@@ -16547,6 +16617,7 @@ +@@ -16610,6 +16680,7 @@ CDAC20C923FC2F750021DEE3 /* RemoteCDMInstanceSessionIdentifier.h in Headers */, F451C0FE2703B263002BA03B /* RemoteDisplayListRecorderProxy.h in Headers */, A78A5FE42B0EB39E005036D3 /* RemoteImageBufferSetIdentifier.h in Headers */, @@ -20490,7 +20872,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 2D47B56D1810714E003A3AEE /* RemoteLayerBackingStore.h in Headers */, 2DDF731518E95060004F5A66 /* RemoteLayerBackingStoreCollection.h in Headers */, 1AB16AEA164B3A8800290D62 /* RemoteLayerTreeContext.h in Headers */, -@@ -16601,6 +16672,7 @@ +@@ -16664,6 +16735,7 @@ E1E552C516AE065F004ED653 /* SandboxInitializationParameters.h in Headers */, E36FF00327F36FBD004BE21A /* SandboxStateVariables.h in Headers */, 7BAB111025DD02B3008FC479 /* ScopedActiveMessageReceiveQueue.h in Headers */, @@ -20498,7 +20880,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 463BB93A2B9D08D80098C5C3 /* ScriptMessageHandlerIdentifier.h in Headers */, E4D54D0421F1D72D007E3C36 /* ScrollingTreeFrameScrollingNodeRemoteIOS.h in Headers */, 0F931C1C18C5711900DBA7C3 /* ScrollingTreeOverflowScrollingNodeIOS.h in Headers */, -@@ -16947,6 +17019,8 @@ +@@ -17011,6 +17083,8 @@ 939EF87029D112EE00F23AEE /* WebPageInlines.h in Headers */, 9197940823DBC4CB00257892 /* WebPageInspectorAgentBase.h in Headers */, A513F5402154A5D700662841 /* WebPageInspectorController.h in Headers */, @@ -20507,7 +20889,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce A543E30C215C8A8D00279CD9 /* WebPageInspectorTarget.h in Headers */, A543E30D215C8A9000279CD9 /* WebPageInspectorTargetController.h in Headers */, A543E307215AD13700279CD9 /* WebPageInspectorTargetFrontendChannel.h in Headers */, -@@ -19338,6 +19412,8 @@ +@@ -19422,6 +19496,8 @@ 522F792928D50EBB0069B45B /* HidService.mm in Sources */, 2749F6442146561B008380BF /* InjectedBundleNodeHandle.cpp in Sources */, 2749F6452146561E008380BF /* InjectedBundleRangeHandle.cpp in Sources */, @@ -20516,7 +20898,7 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce 1CC94E532AC92F190045F269 /* JSWebExtensionAPIAction.mm in Sources */, 1C2B4D4B2A819D0D00C528A1 /* JSWebExtensionAPIAlarms.mm in Sources */, 1C8ECFEA2AFC7DCB007BAA62 /* JSWebExtensionAPICommands.mm in Sources */, -@@ -19777,6 +19853,8 @@ +@@ -19864,6 +19940,8 @@ E3816B3D27E2463A005EAFC0 /* WebMockContentFilterManager.cpp in Sources */, 31BA924D148831260062EDB5 /* WebNotificationManagerMessageReceiver.cpp in Sources */, 2DF6FE52212E110900469030 /* WebPage.cpp in Sources */, @@ -20524,9 +20906,9 @@ index 72fd5ec5a539c5375f4c9203166470d80170c3a0..8921f3a62e95ea9bc944bbbdf84953ce + D79902B3236E9404005D6F7E /* WebPageInspectorInputAgentMac.mm in Sources */, C0CE72A01247E71D00BC0EC4 /* WebPageMessageReceiver.cpp in Sources */, BCBD3914125BB1A800D2C29F /* WebPageProxyMessageReceiver.cpp in Sources */, - 7CE9CE101FA0767A000177DE /* WebPageUpdatePreferences.cpp in Sources */, + C0CE72A01B47E71D00BC0EC4 /* WebPageTestingMessageReceiver.cpp in Sources */, diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp -index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748b64ce144 100644 +index a3444c240790680684a09a929f5ba07d6e68e793..6167b8d1fb42bef261aed6bb3e9581447decb1a9 100644 --- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp +++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp @@ -233,6 +233,11 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou @@ -20541,7 +20923,30 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 #if ENABLE(PDFJS) if (tryLoadingUsingPDFJSHandler(resourceLoader, trackingParameters)) return; -@@ -366,7 +371,8 @@ static void addParametersShared(const LocalFrame* frame, NetworkResourceLoadPara +@@ -242,12 +247,16 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou + return; + + if (InspectorInstrumentationWebKit::shouldInterceptRequest(resourceLoader)) { +- InspectorInstrumentationWebKit::interceptRequest(resourceLoader, [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)); +- }); +- 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) { ++ auto& resourceLoader = protectedResourceLoader.get(); ++ WEBLOADERSTRATEGY_RELEASE_LOG("scheduleLoad: intercepted URL will be scheduled with the NetworkProcess"); ++ scheduleLoadFromNetworkProcess(resourceLoader, request, trackingParameters, shouldClearReferrerOnHTTPSToHTTPRedirect, maximumBufferingTime(resource)); ++ }); ++ return; ++ } + } + + WEBLOADERSTRATEGY_RELEASE_LOG("scheduleLoad: URL will be scheduled with the NetworkProcess"); +@@ -366,7 +375,8 @@ static void addParametersShared(const LocalFrame* frame, NetworkResourceLoadPara parameters.linkPreconnectEarlyHintsEnabled = mainFrame->settings().linkPreconnectEarlyHintsEnabled(); } @@ -20551,7 +20956,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 { auto identifier = resourceLoader.identifier(); ASSERT(identifier); -@@ -382,7 +388,7 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -382,7 +392,7 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL RunLoop::main().dispatch([resourceLoader = Ref { resourceLoader }, error = blockedError(request)] { resourceLoader->didFail(error); }); @@ -20560,7 +20965,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 } } -@@ -392,7 +398,6 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -392,7 +402,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); @@ -20568,7 +20973,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 loadParameters.identifier = identifier; loadParameters.webPageProxyID = trackingParameters.webPageProxyID; loadParameters.webPageID = trackingParameters.pageID; -@@ -482,14 +487,11 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -482,14 +491,11 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL if (loadParameters.options.mode != FetchOptions::Mode::Navigate) { ASSERT(loadParameters.sourceOrigin); @@ -20586,7 +20991,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 loadParameters.isMainFrameNavigation = isMainFrameNavigation; if (loadParameters.isMainFrameNavigation && document) -@@ -529,6 +531,17 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -529,6 +535,17 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL } ASSERT((loadParameters.webPageID && loadParameters.webFrameID) || loadParameters.clientCredentialPolicy == ClientCredentialPolicy::CannotAskClientForCredentials); @@ -20604,7 +21009,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 std::optional existingNetworkResourceLoadIdentifierToResume; if (loadParameters.isMainFrameNavigation) -@@ -544,7 +557,7 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL +@@ -544,7 +561,7 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL } auto loader = WebResourceLoader::create(resourceLoader, trackingParameters); @@ -20613,7 +21018,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 } void WebLoaderStrategy::scheduleInternallyFailedLoad(WebCore::ResourceLoader& resourceLoader) -@@ -954,7 +967,7 @@ void WebLoaderStrategy::didFinishPreconnection(WebCore::ResourceLoaderIdentifier +@@ -954,7 +971,7 @@ void WebLoaderStrategy::didFinishPreconnection(WebCore::ResourceLoaderIdentifier bool WebLoaderStrategy::isOnLine() const { @@ -20622,7 +21027,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 } void WebLoaderStrategy::addOnlineStateChangeListener(Function&& listener) -@@ -981,6 +994,11 @@ void WebLoaderStrategy::isResourceLoadFinished(CachedResource& resource, Complet +@@ -981,6 +998,11 @@ void WebLoaderStrategy::isResourceLoadFinished(CachedResource& resource, Complet void WebLoaderStrategy::setOnLineState(bool isOnLine) { @@ -20634,7 +21039,7 @@ index a3444c240790680684a09a929f5ba07d6e68e793..4b27fb80a9b81d91eb4e661c743f5748 if (m_isOnLine == isOnLine) return; -@@ -989,6 +1007,12 @@ void WebLoaderStrategy::setOnLineState(bool isOnLine) +@@ -989,6 +1011,12 @@ void WebLoaderStrategy::setOnLineState(bool isOnLine) listener(isOnLine); } @@ -20678,10 +21083,10 @@ index 3ef86cc236b8acee2fbe5d0b9c3fd755fcc9f06f..75951fc0fc5e4ef566582c0a49482793 } // namespace WebKit diff --git a/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp b/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp -index 1707b38220552075313bac33a735756abad53ced..f7bd60794a52bb64c37a51ac34af379dfab0e976 100644 +index 17a0fe5d7a30883febe90e9984d36d8ea222872a..c3804fd499bdd3663e32318012f507af6b434aa7 100644 --- a/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp +++ b/Source/WebKit/WebProcess/Network/WebResourceLoader.cpp -@@ -189,9 +189,6 @@ void WebResourceLoader::didReceiveResponse(ResourceResponse&& response, PrivateR +@@ -190,9 +190,6 @@ void WebResourceLoader::didReceiveResponse(ResourceResponse&& response, PrivateR } m_coreLoader->didReceiveResponse(inspectorResponse, [this, protectedThis = WTFMove(protectedThis), interceptedRequestIdentifier, policyDecisionCompletionHandler = WTFMove(policyDecisionCompletionHandler), overrideData = WTFMove(overrideData)]() mutable { @@ -20691,7 +21096,7 @@ index 1707b38220552075313bac33a735756abad53ced..f7bd60794a52bb64c37a51ac34af379d if (!m_coreLoader || !m_coreLoader->identifier()) { m_interceptController.continueResponse(interceptedRequestIdentifier); return; -@@ -209,6 +206,8 @@ void WebResourceLoader::didReceiveResponse(ResourceResponse&& response, PrivateR +@@ -210,6 +207,8 @@ void WebResourceLoader::didReceiveResponse(ResourceResponse&& response, PrivateR } }); }); @@ -20701,7 +21106,7 @@ index 1707b38220552075313bac33a735756abad53ced..f7bd60794a52bb64c37a51ac34af379d } diff --git a/Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.cpp b/Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.cpp -index ee9c3c4f48c328daaa015e2122235e51349bd999..5b3a4d3e742147195e0ff9e88176759df1b799db 100644 +index e314c2987e348a0abee8b655caff3a1c3b3c4564..882746d581bd8db6f2fad5944f09ee9fe9e0e55b 100644 --- a/Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.cpp +++ b/Source/WebKit/WebProcess/Notifications/NotificationPermissionRequestManager.cpp @@ -85,7 +85,7 @@ void NotificationPermissionRequestManager::startRequest(const SecurityOriginData @@ -20714,10 +21119,10 @@ index ee9c3c4f48c328daaa015e2122235e51349bd999..5b3a4d3e742147195e0ff9e88176759d auto permissionHandlers = m_requestsPerOrigin.take(securityOrigin); diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp -index d07821c8847c094bbce9e742f7dd4048b9d4d319..ab301c87d8ec84580e3e502427037440092aece4 100644 +index e22824697734a6fc65de8bdcf37afee5d5b1d508..1385b96bef6ab891d359e022974c77fc734ff18d 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp -@@ -471,6 +471,8 @@ void WebChromeClient::addMessageToConsole(MessageSource source, MessageLevel lev +@@ -472,6 +472,8 @@ void WebChromeClient::addMessageToConsole(MessageSource source, MessageLevel lev { // Notify the bundle client. auto page = protectedPage(); @@ -20750,13 +21155,13 @@ index 2eb0886f13ed035a53b8eaa60605de4dfe53fbe3..c3a216415ab588cde1f1e524e0a232ef { } diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp -index 2c6c166b8f287a66e914722fe46e5f6400a4c1e2..980a5a9fb54c355d1766e8b7d4f944bb6792bb53 100644 +index 1fbb60e23b8d338f05ff0daa284724afafe29282..a15410b5b492254634e1aa300af405ba12ae8e80 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp -@@ -1586,14 +1586,6 @@ void WebLocalFrameLoaderClient::transitionToCommittedForNewPage(InitializingIfra +@@ -1588,14 +1588,6 @@ void WebLocalFrameLoaderClient::transitionToCommittedForNewPage(InitializingIfra if (initializingIframe == InitializingIframe::No) - webPage->clearEditorStateAfterPageTransition(); + webPage->scheduleFullEditorStateUpdate(); - -#if USE(COORDINATED_GRAPHICS) - if (shouldUseFixedLayout) { @@ -20962,12 +21367,12 @@ index c0dd11d1a720907b1e2d863302a483eea1d39765..a4ad1b5acc545d98aea99c58fc5a5ca3 void DrawingAreaCoordinatedGraphics::scheduleDisplay() diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp -index 9792a2310ecae603e83eefd602beab20c47d696c..2744b289afd8341f05d72a6c205d347fbdff4dd1 100644 +index d81997b98996b258f43ce13496ec06c6dc06d467..16e191b5ed4cf0e3b223fb9547699a05ede2f6d6 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp -@@ -193,8 +193,16 @@ void LayerTreeHost::setViewOverlayRootLayer(GraphicsLayer* viewOverlayRootLayer) - void LayerTreeHost::scrollNonCompositedContents(const IntRect& rect) - { +@@ -204,8 +204,16 @@ void LayerTreeHost::scrollNonCompositedContents(const IntRect& rect) + m_scrolledSinceLastFrame = true; + auto* frameView = m_webPage.localMainFrameView(); + +// Playwright begin @@ -20982,7 +21387,7 @@ index 9792a2310ecae603e83eefd602beab20c47d696c..2744b289afd8341f05d72a6c205d347f m_viewportController.didScroll(rect.location()); didChangeViewport(); -@@ -313,6 +321,10 @@ void LayerTreeHost::didChangeViewport() +@@ -325,6 +333,10 @@ void LayerTreeHost::didChangeViewport() if (!view->useFixedLayout()) view->notifyScrollPositionChanged(m_lastScrollPosition); @@ -20994,10 +21399,10 @@ index 9792a2310ecae603e83eefd602beab20c47d696c..2744b289afd8341f05d72a6c205d347f if (m_lastPageScaleFactor != pageScale) { diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h -index d2630055562da015318b5863de64693161bb1b58..a537e2d6db6a49698c5646ca7f9d2c20c6872692 100644 +index 468d6f8c574ea3298e502ce5776b624e295d863d..7039d3b26db1586e8c2a693b8cf5daa36088dd33 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h -@@ -115,6 +115,13 @@ public: +@@ -116,6 +116,13 @@ public: #if PLATFORM(WPE) && USE(GBM) && ENABLE(WPE_PLATFORM) void preferredBufferFormatsDidChange(); #endif @@ -21090,10 +21495,18 @@ index b6e5283f51db82b60091320df44ef0bbf20c33c6..0d82fbb98b93e760cecf5fa7a41327d0 WebCookieJar(); diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp -index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f079f3489 100644 +index 8ed6c5c3f4bb342435f1ac6bb40020ba0fdd9364..2bbf3db029ab95cfda418fa39f2654db966dc2af 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp -@@ -1047,6 +1047,9 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) +@@ -233,6 +233,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1053,6 +1054,9 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) #endif #endif // HAVE(SANDBOX_STATE_FLAGS) @@ -21103,7 +21516,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f updateThrottleState(); #if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL) updateImageAnimationEnabled(); -@@ -2027,6 +2030,22 @@ void WebPage::loadDidCommitInAnotherProcess(WebCore::FrameIdentifier frameID, st +@@ -2018,6 +2022,22 @@ void WebPage::loadDidCommitInAnotherProcess(WebCore::FrameIdentifier frameID, st frame->loadDidCommitInAnotherProcess(layerHostingContextIdentifier); } @@ -21126,7 +21539,17 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f void WebPage::loadRequest(LoadParameters&& loadParameters) { WEBPAGE_RELEASE_LOG(Loading, "loadRequest: navigationID=%" PRIu64 ", shouldTreatAsContinuingLoad=%u, lastNavigationWasAppInitiated=%d, existingNetworkResourceLoadIdentifierToResume=%" PRIu64, loadParameters.navigationID, static_cast(loadParameters.shouldTreatAsContinuingLoad), loadParameters.request.isAppInitiated(), valueOrDefault(loadParameters.existingNetworkResourceLoadIdentifierToResume).toUInt64()); -@@ -2312,17 +2331,14 @@ void WebPage::setSize(const WebCore::IntSize& viewSize) +@@ -2200,7 +2220,9 @@ void WebPage::stopLoading() + void WebPage::stopLoadingDueToProcessSwap() + { + SetForScope isStoppingLoadingDueToProcessSwap(m_isStoppingLoadingDueToProcessSwap, true); ++ InspectorInstrumentationWebKit::setStoppingLoadingDueToProcessSwap(m_page.get(), true); + stopLoading(); ++ InspectorInstrumentationWebKit::setStoppingLoadingDueToProcessSwap(m_page.get(), false); + } + + bool WebPage::defersLoading() const +@@ -2301,17 +2323,14 @@ void WebPage::setSize(const WebCore::IntSize& viewSize) view->resize(viewSize); m_drawingArea->setNeedsDisplay(); @@ -21144,7 +21567,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArguments) { RefPtr localMainFrame = dynamicDowncast(m_page->mainFrame()); -@@ -2347,20 +2363,18 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg +@@ -2336,20 +2355,18 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg ViewportAttributes attr = computeViewportAttributes(viewportArguments, minimumLayoutFallbackWidth, deviceWidth, deviceHeight, 1, m_viewSize); @@ -21172,7 +21595,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f #if USE(COORDINATED_GRAPHICS) m_drawingArea->didChangeViewportAttributes(WTFMove(attr)); -@@ -2368,7 +2382,6 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg +@@ -2357,7 +2374,6 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg send(Messages::WebPageProxy::DidChangeViewportProperties(attr)); #endif } @@ -21180,7 +21603,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f void WebPage::scrollMainFrameIfNotAtMaxScrollPosition(const IntSize& scrollOffset) { -@@ -2670,6 +2683,7 @@ void WebPage::scaleView(double scale) +@@ -2659,6 +2675,7 @@ void WebPage::scaleView(double scale) } m_page->setViewScaleFactor(scale); @@ -21188,7 +21611,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f scalePage(pageScale, scrollPositionAtNewScale); } -@@ -2849,18 +2863,14 @@ void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArgum +@@ -2838,18 +2855,14 @@ void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArgum viewportConfigurationChanged(); #endif @@ -21208,7 +21631,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f } #if !PLATFORM(IOS_FAMILY) -@@ -3578,6 +3588,13 @@ void WebPage::setLastKnownMousePosition(WebCore::FrameIdentifier frameID, IntPoi +@@ -3571,6 +3584,13 @@ void WebPage::setLastKnownMousePosition(WebCore::FrameIdentifier frameID, IntPoi frame->coreLocalFrame()->eventHandler().setLastKnownMousePosition(eventPoint, globalPoint); } @@ -21221,8 +21644,8 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f + void WebPage::flushDeferredDidReceiveMouseEvent() { - if (auto info = std::exchange(m_deferredMouseEventCompletionHandler, std::nullopt)) -@@ -3862,6 +3879,97 @@ void WebPage::touchEvent(const WebTouchEvent& touchEvent, CompletionHandlersendMessageToTargetBackend(targetId, message); } @@ -21332,7 +21755,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f void WebPage::insertNewlineInQuotedContent() { RefPtr frame = m_page->checkedFocusController()->focusedOrMainFrame(); -@@ -4185,6 +4298,7 @@ void WebPage::didCompletePageTransition() +@@ -4178,6 +4294,7 @@ void WebPage::didCompletePageTransition() void WebPage::show() { send(Messages::WebPageProxy::ShowPage()); @@ -21340,7 +21763,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f } void WebPage::setIsTakingSnapshotsForApplicationSuspension(bool isTakingSnapshotsForApplicationSuspension) -@@ -5296,7 +5410,7 @@ NotificationPermissionRequestManager* WebPage::notificationPermissionRequestMana +@@ -5378,7 +5495,7 @@ NotificationPermissionRequestManager* WebPage::notificationPermissionRequestMana #if ENABLE(DRAG_SUPPORT) @@ -21349,7 +21772,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f 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) -@@ -7646,6 +7760,10 @@ void WebPage::didCommitLoad(WebFrame* frame) +@@ -7748,6 +7865,10 @@ void WebPage::didCommitLoad(WebFrame* frame) #endif flushDeferredDidReceiveMouseEvent(); @@ -21360,7 +21783,7 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f } void WebPage::didFinishDocumentLoad(WebFrame& frame) -@@ -7915,6 +8033,9 @@ Ref WebPage::createDocumentLoader(LocalFrame& frame, const Resou +@@ -8028,6 +8149,9 @@ Ref WebPage::createDocumentLoader(LocalFrame& frame, const Resou WebsitePoliciesData::applyToDocumentLoader(WTFMove(*m_pendingWebsitePolicies), documentLoader); m_pendingWebsitePolicies = std::nullopt; } @@ -21371,18 +21794,18 @@ index 302b6fc3639e3bfe5a3f7624c21c518a6e142781..2a799f3454f6d51712f27cbc0b86036f return documentLoader; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h -index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57c664d616 100644 +index dade474046440584b2579c3c0e72d7daf9aafc68..35be816bd0ba41b6f6c31e4dec1906fbfe0533ee 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit/WebProcess/WebPage/WebPage.h -@@ -70,6 +70,7 @@ - #include +@@ -71,6 +71,7 @@ #include #include + #include +#include #include #include #include -@@ -1155,11 +1156,11 @@ public: +@@ -1173,11 +1174,11 @@ public: void clearSelection(); void restoreSelectionInFocusedEditableElement(); @@ -21396,7 +21819,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 void performDragControllerAction(std::optional, DragControllerAction, WebCore::DragData&&, CompletionHandler, WebCore::DragHandlingMethod, bool, unsigned, WebCore::IntRect, WebCore::IntRect, std::optional)>&&); void performDragOperation(WebCore::DragData&&, SandboxExtension::Handle&&, Vector&&, CompletionHandler&&); #endif -@@ -1174,6 +1175,9 @@ public: +@@ -1192,6 +1193,9 @@ public: void didStartDrag(); void dragCancelled(); OptionSet allowedDragSourceActions() const { return m_allowedDragSourceActions; } @@ -21406,7 +21829,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 #endif void beginPrinting(WebCore::FrameIdentifier, const PrintInfo&); -@@ -1249,8 +1253,11 @@ public: +@@ -1267,8 +1271,11 @@ public: void gestureEvent(WebCore::FrameIdentifier, const WebGestureEvent&, CompletionHandler, bool, std::optional)>&&); #endif @@ -21419,7 +21842,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 void dynamicViewportSizeUpdate(const DynamicViewportSizeUpdate&); bool scaleWasSetByUIProcess() const { return m_scaleWasSetByUIProcess; } void willStartUserTriggeredZooming(); -@@ -1397,6 +1404,7 @@ public: +@@ -1415,6 +1422,7 @@ public: void connectInspector(const String& targetId, Inspector::FrontendChannel::ConnectionType); void disconnectInspector(const String& targetId); void sendMessageToTargetBackend(const String& targetId, const String& message); @@ -21427,7 +21850,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 void insertNewlineInQuotedContent(); -@@ -1883,6 +1891,7 @@ private: +@@ -1921,6 +1929,7 @@ private: void createProvisionalFrame(ProvisionalFrameCreationParameters&&, WebCore::FrameIdentifier); void destroyProvisionalFrame(WebCore::FrameIdentifier); void loadDidCommitInAnotherProcess(WebCore::FrameIdentifier, std::optional); @@ -21435,7 +21858,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 void loadRequest(LoadParameters&&); [[noreturn]] void loadRequestWaitingForProcessLaunch(LoadParameters&&, URL&&, WebPageProxyIdentifier, bool); void loadData(LoadParameters&&); -@@ -1924,6 +1933,7 @@ private: +@@ -1962,6 +1971,7 @@ private: void updatePotentialTapSecurityOrigin(const WebTouchEvent&, bool wasHandled); #elif ENABLE(TOUCH_EVENTS) void touchEvent(const WebTouchEvent&, CompletionHandler, bool)>&&); @@ -21443,7 +21866,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 #endif void cancelPointer(WebCore::PointerID, const WebCore::IntPoint&); -@@ -2070,9 +2080,7 @@ private: +@@ -2108,9 +2118,7 @@ private: void addLayerForFindOverlay(CompletionHandler&&); void removeLayerForFindOverlay(CompletionHandler&&); @@ -21453,7 +21876,7 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 void didChangeSelectedIndexForActivePopupMenu(int32_t newIndex); void setTextForActivePopupMenu(int32_t index); -@@ -2676,6 +2684,7 @@ private: +@@ -2713,6 +2721,7 @@ private: UserActivity m_userActivity; uint64_t m_pendingNavigationID { 0 }; @@ -21462,12 +21885,12 @@ index 1cb204697117b143bd031cfd1a0ff8e7116864ba..1f8abf4d4dada0543b5aefc0b2640d57 bool m_mainFrameProgressCompleted { false }; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in -index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0ddb1f9b0a8 100644 +index 148cbc222395ba878b51a233331e45e0e2d1604f..0bb93d61e68587cbd27e24fbef0eb875783ff8ff 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in +++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in -@@ -55,10 +55,13 @@ messages -> WebPage LegacyReceiver { - ClearNotificationPermissionState() - #endif +@@ -51,10 +51,13 @@ messages -> WebPage LegacyReceiver { + MouseEvent(WebCore::FrameIdentifier frameID, WebKit::WebMouseEvent event, std::optional> sandboxExtensions) + SetLastKnownMousePosition(WebCore::FrameIdentifier frameID, WebCore::IntPoint eventPoint, WebCore::IntPoint globalPoint); +#if ENABLE(ORIENTATION_EVENTS) + SetDeviceOrientation(WebCore::IntDegrees deviceOrientation) @@ -21480,7 +21903,7 @@ index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0dd SetOverrideViewportArguments(std::optional arguments) DynamicViewportSizeUpdate(struct WebKit::DynamicViewportSizeUpdate target) -@@ -148,6 +151,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -144,6 +147,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType ConnectInspector(String targetId, Inspector::FrontendChannel::ConnectionType connectionType) DisconnectInspector(String targetId) SendMessageToTargetBackend(String targetId, String message) @@ -21488,7 +21911,7 @@ index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0dd #if ENABLE(REMOTE_INSPECTOR) SetIndicating(bool indicating); -@@ -158,6 +162,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -154,6 +158,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType #endif #if !ENABLE(IOS_TOUCH_EVENTS) && ENABLE(TOUCH_EVENTS) TouchEvent(WebKit::WebTouchEvent event) -> (std::optional eventType, bool handled) @@ -21496,7 +21919,7 @@ index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0dd #endif CancelPointer(WebCore::PointerID pointerId, WebCore::IntPoint documentPoint) -@@ -191,6 +196,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -187,6 +192,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType CreateProvisionalFrame(struct WebKit::ProvisionalFrameCreationParameters creationParameters, WebCore::FrameIdentifier frameID) DestroyProvisionalFrame(WebCore::FrameIdentifier frameID); LoadDidCommitInAnotherProcess(WebCore::FrameIdentifier frameID, std::optional layerHostingContextIdentifier) @@ -21504,7 +21927,7 @@ index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0dd 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) -@@ -355,10 +361,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -351,10 +357,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType RemoveLayerForFindOverlay() -> () # Drag and drop. @@ -21517,7 +21940,7 @@ index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0dd 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) PerformDragOperation(WebCore::DragData dragData, WebKit::SandboxExtensionHandle sandboxExtensionHandle, Vector sandboxExtensionsForUpload) -> (bool handled) #endif -@@ -368,6 +374,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -364,6 +370,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType DragCancelled() #endif @@ -21529,7 +21952,7 @@ index e1925f00bdbcabf1d9509a4dac169490000ef48d..75c54167747fc391179449ba22dda0dd 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/mac/WebPageMac.mm b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm -index a95a86568072d78053ccd8cf758aad9ba3f1bcc4..661923b01c15d9b3ec4b351d657c2e650642762d 100644 +index 678294ec1a7b6a05ed91730a723210a66af2f918..aafa6cfaff690bcbf6a69870576844f8894b8613 100644 --- a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm +++ b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm @@ -802,21 +802,37 @@ String WebPage::platformUserAgent(const URL&) const @@ -21621,7 +22044,7 @@ index f17f5d719d892309ed9c7093384945866b5117b9..1dba47bbf0dbd0362548423a74b38034 } diff --git a/Source/WebKit/WebProcess/WebProcess.cpp b/Source/WebKit/WebProcess/WebProcess.cpp -index fb283adec78aec2bc00f4db13f243bb814b84d5a..5e13f718081dc6da8c9105bcde23408c21bbed14 100644 +index df3c9d0ebbc3665c3ec4fcac259bee402ef19f45..9ff1d77c609c6b483eb3b9c620ef5742f5477069 100644 --- a/Source/WebKit/WebProcess/WebProcess.cpp +++ b/Source/WebKit/WebProcess/WebProcess.cpp @@ -88,6 +88,7 @@ @@ -21632,7 +22055,22 @@ index fb283adec78aec2bc00f4db13f243bb814b84d5a..5e13f718081dc6da8c9105bcde23408c #include #include #include -@@ -371,6 +372,8 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter +@@ -364,6 +365,14 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter + { + JSC::Options::AllowUnfinalizedAccessScope scope; + JSC::Options::allowNonSPTagging() = false; ++ // Playwright begin ++ // SharedBufferArray is enabled only on Mac via XPC sercvice "enable-shared-array-buffer" option. ++ // For other platforms, enable it here. ++#if !PLATFORM(COCOA) ++ if (parameters.shouldEnableSharedArrayBuffer) ++ JSC::Options::useSharedArrayBuffer() = true; ++#endif ++ // Playwright end + JSC::Options::notifyOptionsChanged(); + } + +@@ -371,6 +380,8 @@ void WebProcess::initializeProcess(const AuxiliaryProcessInitializationParameter platformInitializeProcess(parameters); updateCPULimit(); @@ -21657,10 +22095,10 @@ index 8987c3964a9308f2454759de7f8972215a3ae416..bcac0afeb94ed8123d1f9fb0b932c849 SetProcessDPIAware(); return true; diff --git a/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm b/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm -index 7c01c6e5509f448a3ebdf10376ef2974403e37c0..7a9c9e3a197dc723e7a94dce5ddbddf05ba5111b 100644 +index d694170887445e2eed73340666bc4847aef4f9a6..6ced22d0953a39582285201e0946d68f954644ad 100644 --- a/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm +++ b/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm -@@ -4214,7 +4214,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END +@@ -4223,7 +4223,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END _private->handlingMouseDownEvent = NO; } @@ -21723,7 +22161,7 @@ index 0000000000000000000000000000000000000000..dd6a53e2d57318489b7e49dd7373706d + LIBVPX_LIBRARIES +) diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake -index fa9c8766ea53612b9651e03375eb8b6bf4f15e76..621fb9f05c620f486feed36c4f23f66188a7ee88 100644 +index 7528273e7832716ef8cb900f89728d4003130d92..32826feac62d3d4132df79febd01b8a8b9ce16ff 100644 --- a/Source/cmake/OptionsGTK.cmake +++ b/Source/cmake/OptionsGTK.cmake @@ -11,8 +11,13 @@ if (${CMAKE_VERSION} VERSION_LESS "3.20" AND NOT ${CMAKE_GENERATOR} STREQUAL "Ni @@ -21780,14 +22218,14 @@ index fa9c8766ea53612b9651e03375eb8b6bf4f15e76..621fb9f05c620f486feed36c4f23f661 WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_STREAM PRIVATE ON) @@ -131,7 +140,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETWORK_CACHE_SPECULATIVE_REVALIDATION P WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETWORK_CACHE_STALE_WHILE_REVALIDATE PRIVATE ON) - WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) - WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS_IN_WORKERS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) + WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS PRIVATE ON) + WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS_IN_WORKERS PRIVATE ON) -WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_THUNDER PRIVATE ${ENABLE_DEVELOPER_MODE}) +WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_THUNDER PRIVATE OFF) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_PERIODIC_MEMORY_MONITOR PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_POINTER_LOCK PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_SHAREABLE_RESOURCE PRIVATE ON) -@@ -147,6 +156,14 @@ else () +@@ -149,6 +158,14 @@ else () WEBKIT_OPTION_DEFAULT_PORT_VALUE(USE_SKIA PRIVATE OFF) endif () @@ -21803,7 +22241,7 @@ index fa9c8766ea53612b9651e03375eb8b6bf4f15e76..621fb9f05c620f486feed36c4f23f661 # 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 8d5c7b97d0b717e4da818baaef1a681629332edb..df125a4c00c79daa73e9959a6b8b8fe0e3929e99 100644 +index a70706b67e0313e084eed2d1682ef85b296c80a6..21a610a7c0ddd838ed152bc1de4440ae4e6ff7ea 100644 --- a/Source/cmake/OptionsWPE.cmake +++ b/Source/cmake/OptionsWPE.cmake @@ -9,6 +9,8 @@ if (${CMAKE_VERSION} VERSION_LESS "3.20" AND NOT ${CMAKE_GENERATOR} STREQUAL "Ni @@ -21848,7 +22286,7 @@ index 8d5c7b97d0b717e4da818baaef1a681629332edb..df125a4c00c79daa73e9959a6b8b8fe0 WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_SESSION PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_SESSION_PLAYLIST PRIVATE OFF) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_STREAM PRIVATE ON) -@@ -72,7 +77,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS_IN_WORKERS PRIVATE ${EN +@@ -72,7 +77,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS_IN_WORKERS PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_PERIODIC_MEMORY_MONITOR PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_SHAREABLE_RESOURCE PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_SPEECH_SYNTHESIS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) @@ -21857,7 +22295,7 @@ index 8d5c7b97d0b717e4da818baaef1a681629332edb..df125a4c00c79daa73e9959a6b8b8fe0 WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_TOUCH_EVENTS PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_VARIATION_FONTS PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_CODECS PRIVATE ON) -@@ -89,6 +94,23 @@ if (WPE_VERSION VERSION_GREATER_EQUAL 1.13.90) +@@ -91,6 +96,23 @@ if (WPE_VERSION VERSION_GREATER_EQUAL 1.13.90) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_GAMEPAD PUBLIC ON) endif () @@ -21881,7 +22319,7 @@ index 8d5c7b97d0b717e4da818baaef1a681629332edb..df125a4c00c79daa73e9959a6b8b8fe0 # 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. -@@ -98,7 +120,7 @@ WEBKIT_OPTION_DEFINE(ENABLE_JOURNALD_LOG "Whether to enable journald logging" PU +@@ -100,7 +122,7 @@ WEBKIT_OPTION_DEFINE(ENABLE_JOURNALD_LOG "Whether to enable journald logging" PU WEBKIT_OPTION_DEFINE(ENABLE_WPE_PLATFORM_DRM "Whether to enable support for DRM platform" PUBLIC ON) WEBKIT_OPTION_DEFINE(ENABLE_WPE_PLATFORM_HEADLESS "Whether to enable support for headless platform" PUBLIC ON) WEBKIT_OPTION_DEFINE(ENABLE_WPE_PLATFORM_WAYLAND "Whether to enable support for Wayland platform" PUBLIC ON) @@ -21891,10 +22329,10 @@ index 8d5c7b97d0b717e4da818baaef1a681629332edb..df125a4c00c79daa73e9959a6b8b8fe0 WEBKIT_OPTION_DEFINE(USE_ATK "Whether to enable usage of ATK." PUBLIC ON) WEBKIT_OPTION_DEFINE(USE_GBM "Whether to enable usage of GBM." PUBLIC ON) diff --git a/Source/cmake/OptionsWin.cmake b/Source/cmake/OptionsWin.cmake -index 92171930d24d199ebe5e7ff13252a6561ce57379..4d0b67710fa8a68dd604fb5c41ae16dd1a29e797 100644 +index 75b07257a0f116511fafc947e64f3f98f9086a82..5e65d3e455c9fb1f03551c4561e74247952a68e4 100644 --- a/Source/cmake/OptionsWin.cmake +++ b/Source/cmake/OptionsWin.cmake -@@ -86,6 +86,29 @@ find_package(ZLIB 1.2.11 REQUIRED) +@@ -67,6 +67,29 @@ find_package(ZLIB 1.2.11 REQUIRED) find_package(LibPSL 0.20.2 REQUIRED) find_package(WebP REQUIRED COMPONENTS demux) @@ -21924,7 +22362,7 @@ index 92171930d24d199ebe5e7ff13252a6561ce57379..4d0b67710fa8a68dd604fb5c41ae16dd WEBKIT_OPTION_BEGIN() # FIXME: Most of these options should not be public. -@@ -156,6 +179,14 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_FTPDIR PRIVATE OFF) +@@ -133,6 +156,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) @@ -21940,7 +22378,7 @@ index 92171930d24d199ebe5e7ff13252a6561ce57379..4d0b67710fa8a68dd604fb5c41ae16dd set(USE_ANGLE_EGL ON) diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake -index 4db77baa1f6f1b66a6c84e7577ee4169797b474b..cc57a3a95dcf7863aeeda489b25554f4172ca10b 100644 +index edac4df57e4da23a7cdf234eefc3879c35c607c2..df575fa5d0c8574a8b248362167e160704bab41c 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -122,7 +122,7 @@ macro(WEBKIT_ADD_TARGET_CXX_FLAGS _target) @@ -21953,7 +22391,7 @@ index 4db77baa1f6f1b66a6c84e7577ee4169797b474b..cc57a3a95dcf7863aeeda489b25554f4 if (MSVC) set(FATAL_WARNINGS_FLAG /WX) diff --git a/Tools/DumpRenderTree/DerivedSources.make b/Tools/DumpRenderTree/DerivedSources.make -index 57aae19f1a16d08ba1579562eeff264c8768af4d..6ce36f40cc954bd02b86f84bd9a0bd2928459938 100644 +index 576835410df6deac60f0158f1d2d1ef1e5f4c78d..9b492cfe5fef8de340a80f2af70a7d68672ef2e4 100644 --- a/Tools/DumpRenderTree/DerivedSources.make +++ b/Tools/DumpRenderTree/DerivedSources.make @@ -73,8 +73,8 @@ $(IDL_FILE_NAMES_LIST) : $(UICONTEXT_INTERFACES:%=%.idl) @@ -22022,7 +22460,7 @@ index 61616b96e2f4e21aa6d098445e0f1a933e512a9c..33732da18013679a869ff8eb2b445434 } diff --git a/Tools/MiniBrowser/gtk/BrowserWindow.c b/Tools/MiniBrowser/gtk/BrowserWindow.c -index f8f78a9b2e711d8250e770d73f0f7af6dc1520ed..101313a9ba3c1ddcfa10561d582ce485f0212765 100644 +index d39b809a879babcdbbf4b5f7687204df5ccc43f3..2862876cb515da09db653e71659d64215a636758 100644 --- a/Tools/MiniBrowser/gtk/BrowserWindow.c +++ b/Tools/MiniBrowser/gtk/BrowserWindow.c @@ -73,7 +73,7 @@ struct _BrowserWindowClass { @@ -22055,7 +22493,7 @@ index f8f78a9b2e711d8250e770d73f0f7af6dc1520ed..101313a9ba3c1ddcfa10561d582ce485 gtk_window_set_title(GTK_WINDOW(window), privateTitle ? privateTitle : title); g_free(privateTitle); } -@@ -520,8 +514,12 @@ static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision +@@ -524,8 +518,12 @@ static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision return FALSE; WebKitNavigationAction *navigationAction = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); @@ -22070,7 +22508,7 @@ index f8f78a9b2e711d8250e770d73f0f7af6dc1520ed..101313a9ba3c1ddcfa10561d582ce485 return FALSE; /* Multiple tabs are not allowed in editor mode. */ -@@ -1498,6 +1496,28 @@ static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event) +@@ -1502,6 +1500,28 @@ static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event) } #endif @@ -22099,7 +22537,7 @@ index f8f78a9b2e711d8250e770d73f0f7af6dc1520ed..101313a9ba3c1ddcfa10561d582ce485 static void browser_window_class_init(BrowserWindowClass *klass) { GObjectClass *gobjectClass = G_OBJECT_CLASS(klass); -@@ -1511,6 +1531,19 @@ static void browser_window_class_init(BrowserWindowClass *klass) +@@ -1515,6 +1535,19 @@ static void browser_window_class_init(BrowserWindowClass *klass) GtkWidgetClass *widgetClass = GTK_WIDGET_CLASS(klass); widgetClass->delete_event = browserWindowDeleteEvent; #endif @@ -22333,10 +22771,10 @@ index 0dd4a639e69f52e674c6bbe257e35daabd25d077..1c0af52edd19d19d30136158f25ede26 g_clear_object(&interfaceSettings); diff --git a/Tools/MiniBrowser/wpe/main.cpp b/Tools/MiniBrowser/wpe/main.cpp -index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1f03a8746 100644 +index d340d90d8d6650b8499cf5593df481e06a3178a3..8534bc30f863e4eafdb1cc7b205034d35bd3c0ff 100644 --- a/Tools/MiniBrowser/wpe/main.cpp +++ b/Tools/MiniBrowser/wpe/main.cpp -@@ -45,6 +45,9 @@ static gboolean headlessMode; +@@ -49,6 +49,9 @@ static gboolean headlessMode; static gboolean privateMode; static gboolean automationMode; static gboolean ignoreTLSErrors; @@ -22346,9 +22784,9 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 static const char* contentFilter; static const char* cookiesFile; static const char* cookiesPolicy; -@@ -78,6 +81,9 @@ static const GOptionEntry commandLineOptions[] = - { "use-wpe-platform-api", 0, 0, G_OPTION_ARG_NONE, &useWPEPlatformAPI, "Use the WPE platform API", nullptr }, +@@ -125,6 +128,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 }, + { "inspector-pipe", 'v', 0, G_OPTION_ARG_NONE, &inspectorPipe, "Expose remote debugging protocol over pipe", nullptr }, + { "user-data-dir", 0, 0, G_OPTION_ARG_STRING, &userDataDir, "Default profile persistence folder location", "FILE" }, @@ -22356,7 +22794,7 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &uriArguments, nullptr, "[URL]" }, { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } }; -@@ -220,15 +226,38 @@ static void filterSavedCallback(WebKitUserContentFilterStore *store, GAsyncResul +@@ -276,15 +282,38 @@ static void filterSavedCallback(WebKitUserContentFilterStore *store, GAsyncResul g_main_loop_quit(data->mainLoop); } @@ -22395,9 +22833,9 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 + +static WebKitWebView* createWebViewImpl(WebKitWebView* webView, WebKitWebContext *webContext, gpointer user_data) { - - auto backend = createViewBackend(1280, 720); -@@ -244,18 +273,37 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi + auto backend = createViewBackend(defaultWindowWidthLegacyAPI, defaultWindowHeightLegacyAPI); + WebKitWebViewBackend* viewBackend = nullptr; +@@ -299,12 +328,27 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi }, backend.release()); } @@ -22429,8 +22867,9 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 + nullptr)); + } - g_signal_connect(newWebView, "create", G_CALLBACK(createWebView), user_data); - g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), user_data); + #if ENABLE_WPE_PLATFORM + if (auto* wpeView = webkit_web_view_get_wpe_view(newWebView)) { +@@ -319,6 +363,10 @@ static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationActi g_hash_table_add(openViews, newWebView); @@ -22441,7 +22880,7 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 return newWebView; } -@@ -269,13 +317,89 @@ static WebKitFeature* findFeature(WebKitFeatureList* featureList, const char* id +@@ -376,13 +424,89 @@ static WebKitFeature* findFeature(WebKitFeatureList* featureList, const char* id return nullptr; } @@ -22532,7 +22971,7 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 webkit_network_session_set_itp_enabled(networkSession, enableITP); if (proxy) { -@@ -302,10 +426,18 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -409,10 +533,18 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesFile, storageType); } } @@ -22553,7 +22992,7 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 webkit_website_data_manager_set_itp_enabled(manager, enableITP); if (proxy) { -@@ -336,6 +468,7 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -443,6 +575,7 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* } #endif @@ -22561,7 +23000,7 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 WebKitUserContentManager* userContentManager = nullptr; if (contentFilter) { GFile* contentFilterFile = g_file_new_for_commandline_arg(contentFilter); -@@ -408,6 +541,15 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* +@@ -521,6 +654,15 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* "autoplay", WEBKIT_AUTOPLAY_ALLOW, nullptr); @@ -22577,16 +23016,16 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 auto* webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, "backend", viewBackend, "web-context", webContext, -@@ -436,8 +578,6 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* - g_signal_connect(wpeView, "event", G_CALLBACK(wpeViewEventCallback), webView); +@@ -565,8 +707,6 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* + } #endif - openViews = g_hash_table_new_full(nullptr, nullptr, g_object_unref, nullptr); - - webkit_web_context_set_automation_allowed(webContext, automationMode); g_signal_connect(webContext, "automation-started", G_CALLBACK(automationStartedCallback), webView); g_signal_connect(webView, "permission-request", G_CALLBACK(decidePermissionRequest), nullptr); -@@ -450,16 +590,9 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* + g_signal_connect(webView, "create", G_CALLBACK(createWebView), application); +@@ -578,16 +718,11 @@ static void activate(GApplication* application, WPEToolingBackends::ViewBackend* webkit_web_view_set_background_color(webView, &color); if (uriArguments) { @@ -22599,14 +23038,16 @@ index 3c2476471ef67299da6312d0f995fc0738aa96c9..bdbc645f41d254e75ae8aba6eb2850c1 - g_object_unref(file); - webkit_web_view_load_uri(webView, url); - g_free(url); -- } else if (automationMode) +- } else if (!automationMode) + // Playwright: avoid weird url transformation like http://trac.webkit.org/r240840 + webkit_web_view_load_uri(webView, uriArguments[0]); + } else if (automationMode || inspectorPipe) - webkit_web_view_load_uri(webView, "about:blank"); - else ++ webkit_web_view_load_uri(webView, "about:blank"); ++ else webkit_web_view_load_uri(webView, "https://wpewebkit.org"); -@@ -533,8 +666,14 @@ int main(int argc, char *argv[]) + + g_object_unref(webContext); +@@ -683,8 +818,14 @@ int main(int argc, char *argv[]) } } @@ -22634,7 +23075,7 @@ index 1067b31bc989748dfcc5502209d36d001b9b239e..7629263fb8bc93dca6dfc01c75eed8d2 + add_subdirectory(Playwright/win) +endif () diff --git a/Tools/Scripts/build-webkit b/Tools/Scripts/build-webkit -index 39c351abbdd7b1b7f7016c5fd071ec2ae59b4a42..dc804e1d5284b15da6fa0679932d6cca8ece499a 100755 +index 39f91de9501cf38331ce567d30c9cefa25b28510..af2eeec87bb43f9ddfc6d583c35a8e7a87d5127c 100755 --- a/Tools/Scripts/build-webkit +++ b/Tools/Scripts/build-webkit @@ -273,7 +273,7 @@ if (isAppleCocoaWebKit()) { @@ -22662,7 +23103,7 @@ 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 02656db352f2d52dd24e7886e9119edb562be1c1..21366de86e0c2caeb9c040cd9f9f5b966b29438b 100644 +index 076ad1f7005f732f8529955cfe3b6a8dd5212949..dee6f57eb5fff12de9cdaad230e894b0b9b15b88 100644 --- a/Tools/WebKitTestRunner/TestController.cpp +++ b/Tools/WebKitTestRunner/TestController.cpp @@ -964,6 +964,7 @@ void TestController::createWebViewWithOptions(const TestOptions& options) diff --git a/docs/src/api/class-apirequestcontext.md b/docs/src/api/class-apirequestcontext.md index a6333d6b0a..1d3e728235 100644 --- a/docs/src/api/class-apirequestcontext.md +++ b/docs/src/api/class-apirequestcontext.md @@ -141,7 +141,7 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.delete.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.delete.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.delete.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.delete.params = %%-python-fetch-option-params-%% @@ -150,6 +150,9 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.delete.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.delete.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.delete.headers = %%-js-python-csharp-fetch-option-headers-%% * since: v1.16 @@ -303,7 +306,7 @@ Target URL or Request to get all parameters from. ### option: APIRequestContext.fetch.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.fetch.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.fetch.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.fetch.params = %%-python-fetch-option-params-%% @@ -312,6 +315,9 @@ Target URL or Request to get all parameters from. ### option: APIRequestContext.fetch.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.fetch.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.fetch.method * since: v1.16 * langs: js, python, csharp @@ -418,7 +424,7 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query ### option: APIRequestContext.get.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.get.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.get.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.get.params = %%-python-fetch-option-params-%% @@ -427,6 +433,9 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query ### option: APIRequestContext.get.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.get.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.get.headers = %%-js-python-csharp-fetch-option-headers-%% * since: v1.16 @@ -477,7 +486,7 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.head.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.head.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.head.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.head.params = %%-python-fetch-option-params-%% @@ -486,6 +495,9 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.head.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.head.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.head.headers = %%-js-python-csharp-fetch-option-headers-%% * since: v1.16 @@ -536,7 +548,7 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.patch.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.patch.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.patch.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.patch.params = %%-python-fetch-option-params-%% @@ -545,6 +557,9 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.patch.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.patch.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.patch.headers = %%-js-python-csharp-fetch-option-headers-%% * since: v1.16 @@ -716,7 +731,7 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar ### option: APIRequestContext.post.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.post.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.post.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.post.params = %%-python-fetch-option-params-%% @@ -725,6 +740,9 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar ### option: APIRequestContext.post.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.post.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.post.headers = %%-js-python-csharp-fetch-option-headers-%% * since: v1.16 @@ -775,7 +793,7 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.put.params = %%-js-fetch-option-params-%% * since: v1.16 -### param: APIRequestContext.put.params = %%-java-csharp-fetch-params-%% +### param: APIRequestContext.put.params = %%-java-fetch-params-%% * since: v1.18 ### option: APIRequestContext.put.params = %%-python-fetch-option-params-%% @@ -784,6 +802,9 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.put.params = %%-csharp-fetch-option-params-%% * since: v1.16 +### option: APIRequestContext.put.paramsString = %%-csharp-fetch-option-paramsString-%% +* since: v1.47 + ### option: APIRequestContext.put.headers = %%-js-python-csharp-fetch-option-headers-%% * since: v1.16 diff --git a/docs/src/api/class-apiresponse.md b/docs/src/api/class-apiresponse.md index 1297d2d4fa..5a901b76ba 100644 --- a/docs/src/api/class-apiresponse.md +++ b/docs/src/api/class-apiresponse.md @@ -60,7 +60,7 @@ An object with all the response HTTP headers associated with this response. - `name` <[string]> Name of the header. - `value` <[string]> Value of the header. -An array with all the request HTTP headers associated with this response. Header names are not lower-cased. +An array with all the response HTTP headers associated with this response. Header names are not lower-cased. Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times. ## async method: APIResponse.json diff --git a/docs/src/api/class-browser.md b/docs/src/api/class-browser.md index 0c6fd67160..59cf4c99c0 100644 --- a/docs/src/api/class-browser.md +++ b/docs/src/api/class-browser.md @@ -297,8 +297,10 @@ testing frameworks should explicitly create [`method: Browser.newContext`] follo ## async method: Browser.removeAllListeners * since: v1.47 +* langs: js -Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. +Removes all the listeners of the given type (or all registered listeners if no type given). +Allows to wait for async listeners to complete or to ignore subsequent errors from these listeners. ### param: Browser.removeAllListeners.type * since: v1.47 diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 7b6c6aab1d..43396f4957 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -6,7 +6,7 @@ BrowserContexts provide a way to operate multiple independent browser sessions. If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser context. -Playwright allows creating "incognito" browser contexts with [`method: Browser.newContext`] method. "Incognito" browser +Playwright allows creating isolated non-persistent browser contexts with [`method: Browser.newContext`] method. Non-persistent browser contexts don't write any browsing data to disk. ```js @@ -415,42 +415,13 @@ The order of evaluation of multiple scripts installed via [`method: BrowserConte [`method: Page.addInitScript`] is not defined. ::: -**Bundling** - -If you have a complex script split into several files, it needs to be bundled into a single file first. We recommend running [`esbuild`](https://esbuild.github.io/) or [`webpack`](https://webpack.js.org/) to produce a commonjs module and pass [`option: path`] and [`option: arg`]. - -```js browser title="mocks/mockRandom.ts" -// This script can import other files. -import { defaultValue } from './defaultValue'; - -export default function(value?: number) { - window.Math.random = () => value ?? defaultValue; -} -``` - -```sh -# bundle with esbuild -esbuild mocks/mockRandom.ts --bundle --format=cjs --outfile=mocks/mockRandom.js -``` - -```js title="tests/example.spec.ts" -const mockPath = { path: path.resolve(__dirname, '../mocks/mockRandom.js') }; - -// Passing 42 as an argument to the default export function. -await context.addInitScript({ path: mockPath }, 42); - -// Make sure to pass undefined even if you do not need to pass an argument. -// This instructs Playwright to treat the file as a commonjs module. -await context.addInitScript({ path: mockPath }, undefined); -``` - ### param: BrowserContext.addInitScript.script * since: v1.8 * langs: js - `script` <[function]|[string]|[Object]> - `path` ?<[path]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to the - current working directory. - - `content` ?<[string]> Raw script content. + current working directory. Optional. + - `content` ?<[string]> Raw script content. Optional. Script to be evaluated in all pages in the browser context. @@ -466,9 +437,7 @@ Script to be evaluated in all pages in the browser context. * langs: js - `arg` ?<[Serializable]> -Optional JSON-serializable argument to pass to [`param: script`]. -* When `script` is a function, the argument is passed to it directly. -* When `script` is a file path, the file is assumed to be a commonjs module. The default export, either `module.exports` or `module.exports.default`, should be a function that's going to be executed with this argument. +Optional argument to pass to [`param: script`] (only supported when passing a function). ### param: BrowserContext.addInitScript.path * since: v1.8 @@ -1048,8 +1017,10 @@ Returns all open pages in the context. ## async method: BrowserContext.removeAllListeners * since: v1.47 +* langs: js -Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. +Removes all the listeners of the given type (or all registered listeners if no type given). +Allows to wait for async listeners to complete or to ignore subsequent errors from these listeners. ### param: BrowserContext.removeAllListeners.type * since: v1.47 diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index 1793798c8c..c8f54c7380 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -866,7 +866,7 @@ await handle.SelectOptionAsync(new[] { ### option: ElementHandle.selectOption.force = %%-input-force-%% * since: v1.13 -### option: ElementHandle.selectOption.noWaitAfter = %%-input-no-wait-after-%% +### option: ElementHandle.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% * since: v1.8 ### option: ElementHandle.selectOption.timeout = %%-input-timeout-%% diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index 4da22fe989..f3f308622f 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -1543,7 +1543,7 @@ await frame.SelectOptionAsync("select#colors", new[] { "red", "green", "blue" }) ### option: Frame.selectOption.force = %%-input-force-%% * since: v1.13 -### option: Frame.selectOption.noWaitAfter = %%-input-no-wait-after-%% +### option: Frame.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% * since: v1.8 ### option: Frame.selectOption.strict = %%-input-strict-%% diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 8a148ddf0b..4df0035098 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -2055,7 +2055,7 @@ await element.SelectOptionAsync(new[] { "red", "green", "blue" }); ### option: Locator.selectOption.force = %%-input-force-%% * since: v1.14 -### option: Locator.selectOption.noWaitAfter = %%-input-no-wait-after-%% +### option: Locator.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% * since: v1.14 ### option: Locator.selectOption.timeout = %%-input-timeout-%% diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 11d7cbdd74..e1aa908041 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -619,42 +619,13 @@ The order of evaluation of multiple scripts installed via [`method: BrowserConte [`method: Page.addInitScript`] is not defined. ::: -**Bundling** - -If you have a complex script split into several files, it needs to be bundled into a single file first. We recommend running [`esbuild`](https://esbuild.github.io/) or [`webpack`](https://webpack.js.org/) to produce a commonjs module and pass [`option: path`] and [`option: arg`]. - -```js browser title="mocks/mockRandom.ts" -// This script can import other files. -import { defaultValue } from './defaultValue'; - -export default function(value?: number) { - window.Math.random = () => value ?? defaultValue; -} -``` - -```sh -# bundle with esbuild -esbuild mocks/mockRandom.ts --bundle --format=cjs --outfile=mocks/mockRandom.js -``` - -```js title="tests/example.spec.ts" -const mockPath = { path: path.resolve(__dirname, '../mocks/mockRandom.js') }; - -// Passing 42 as an argument to the default export function. -await page.addInitScript({ path: mockPath }, 42); - -// Make sure to pass undefined even if you do not need to pass an argument. -// This instructs Playwright to treat the file as a commonjs module. -await page.addInitScript({ path: mockPath }, undefined); -``` - ### param: Page.addInitScript.script * since: v1.8 * langs: js - `script` <[function]|[string]|[Object]> - `path` ?<[path]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to the - current working directory. - - `content` ?<[string]> Raw script content. + current working directory. Optional. + - `content` ?<[string]> Raw script content. Optional. Script to be evaluated in the page. @@ -670,9 +641,7 @@ Script to be evaluated in all pages in the browser context. * langs: js - `arg` ?<[Serializable]> -Optional JSON-serializable argument to pass to [`param: script`]. -* When `script` is a function, the argument is passed to it directly. -* When `script` is a file path, the file is assumed to be a commonjs module. The default export, either `module.exports` or `module.exports.default`, should be a function that's going to be executed with this argument. +Optional argument to pass to [`param: script`] (only supported when passing a function). ### param: Page.addInitScript.path * since: v1.8 @@ -3372,8 +3341,23 @@ By default, after calling the handler Playwright will wait until the overlay bec ## async method: Page.removeAllListeners * since: v1.47 +* langs: js -Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. +Removes all the listeners of the given type (or all registered listeners if no type given). +Allows to wait for async listeners to complete or to ignore subsequent errors from these listeners. + +**Usage** + +```js +page.on('request', async request => { + const response = await request.response(); + const body = await response.body(); + console.log(body.byteLength); +}); +await page.goto('https://playwright.dev', { waitUntil: 'domcontentloaded' }); +// Waits for all the reported 'request' events to resolve. +await page.removeAllListeners('request', { behavior: 'wait' }); +``` ### param: Page.removeAllListeners.type * since: v1.47 @@ -3742,7 +3726,7 @@ await page.SelectOptionAsync("select#colors", new[] { "red", "green", "blue" }); ### option: Page.selectOption.force = %%-input-force-%% * since: v1.13 -### option: Page.selectOption.noWaitAfter = %%-input-no-wait-after-%% +### option: Page.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% * since: v1.8 ### option: Page.selectOption.strict = %%-input-strict-%% diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index 2dcd17b3ac..5e56907656 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -83,7 +83,7 @@ assertThat(page).not().hasURL("error"); ``` ```csharp -await Expect(Page).Not.ToHaveURL("error"); +await Expect(Page).Not.ToHaveURLAsync("error"); ``` ## async method: PageAssertions.NotToHaveTitle @@ -271,7 +271,7 @@ expect(page).to_have_title(re.compile(r".*checkout")) ``` ```csharp -await Expect(Page).ToHaveTitle("Playwright"); +await Expect(Page).ToHaveTitleAsync("Playwright"); ``` ### param: PageAssertions.toHaveTitle.titleOrRegExp @@ -320,7 +320,7 @@ expect(page).to_have_url(re.compile(".*checkout")) ``` ```csharp -await Expect(Page).ToHaveURL(new Regex(".*checkout")); +await Expect(Page).ToHaveURLAsync(new Regex(".*checkout")); ``` ### param: PageAssertions.toHaveURL.urlOrRegExp diff --git a/docs/src/api/class-request.md b/docs/src/api/class-request.md index db878f9995..e13d9de69f 100644 --- a/docs/src/api/class-request.md +++ b/docs/src/api/class-request.md @@ -288,10 +288,15 @@ Returns the matching [Response] object, or `null` if the response was not receiv ## method: Request.serviceWorker * since: v1.24 * langs: js -* deprecated: Requests made by a Service Worker are not reported in Playwright. - returns: <[null]|[Worker]> -This method will always return `null`. +The Service [Worker] that is performing the request. + +**Details** + +This method is Chromium only. It's safe to call when using other browsers, but it will always be `null`. + +Requests originated in a Service Worker do not have a [`method: Request.frame`] available. ## async method: Request.sizes * since: v1.15 diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 5aa9c6a5ab..cbec1a5e25 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -364,7 +364,7 @@ Query parameters to be sent with the URL. ## python-fetch-option-params * langs: python -- `params` <[Object]<[string], [string]|[float]|[boolean]>> +- `params` <[Object]<[string], [string]|[float]|[boolean]>|[string]> Query parameters to be sent with the URL. @@ -374,7 +374,13 @@ Query parameters to be sent with the URL. Query parameters to be sent with the URL. -## java-csharp-fetch-params +## csharp-fetch-option-paramsString +* langs: csharp +- `paramsString` <[string]> + +Query parameters to be sent with the URL. + +## java-fetch-params * langs: java - `options` ?<[RequestOptions]> @@ -769,12 +775,6 @@ Actual picture of each page will be scaled down if necessary to fit the specifie Network proxy settings to use with this context. Defaults to none. -:::note -For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all -contexts override the proxy, global proxy will be never used and can be any string, for example -`launch({ proxy: { server: 'http://per-context' } })`. -::: - ## context-option-strict - `strictSelectors` <[boolean]> diff --git a/docs/src/browsers.md b/docs/src/browsers.md index 3b9e76d006..1b12ff534c 100644 --- a/docs/src/browsers.md +++ b/docs/src/browsers.md @@ -459,9 +459,13 @@ Google Chrome and Microsoft Edge respect enterprise policies, which include limi Playwright's Firefox version matches the recent [Firefox Stable](https://www.mozilla.org/en-US/firefox/new/) build. Playwright doesn't work with the branded version of Firefox since it relies on patches. +Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems. For example, available media codecs vary substantially between Linux, macOS and Windows. + ### WebKit -Playwright's WebKit is derived from the latest WebKit main branch sources, often before these updates are incorporated into Apple Safari and other WebKit-based browsers. This gives a lot of lead time to react on the potential browser update issues. Playwright doesn't work with the branded version of Safari since it relies on patches. Instead, you can test using the most recent WebKit build. Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems. +Playwright's WebKit is derived from the latest WebKit main branch sources, often before these updates are incorporated into Apple Safari and other WebKit-based browsers. This gives a lot of lead time to react on the potential browser update issues. Playwright doesn't work with the branded version of Safari since it relies on patches. Instead, you can test using the most recent WebKit build. + +Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems. For example, available media codecs vary substantially between Linux, macOS and Windows. While running WebKit on Linux CI is usually the most affordable option, for the closest-to-Safari experience you should run WebKit on mac, for example if you do video playback. ## Install behind a firewall or a proxy diff --git a/docs/src/ci.md b/docs/src/ci.md index 7a08a00b4a..97ea4ad8d2 100644 --- a/docs/src/ci.md +++ b/docs/src/ci.md @@ -432,6 +432,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # Force a non-shallow checkout, so that we can reference $GITHUB_BASE_REF. + # See https://github.com/actions/checkout for more details. + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 18 @@ -1044,4 +1048,4 @@ xvfb-run mvn test ``` ```bash csharp xvfb-run dotnet test -``` \ No newline at end of file +``` diff --git a/docs/src/mock.md b/docs/src/mock.md index bd0c4e5c54..87ddf2ec96 100644 --- a/docs/src/mock.md +++ b/docs/src/mock.md @@ -288,7 +288,7 @@ await Expect(page.GetByText("Strawberry")).ToBeVisibleAsync(); ```java // Get the response from the HAR file -page.routeFromHAR("./hars/fruit.har", new RouteFromHAROptions() +page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions() .setUrl("*/**/api/v1/fruits") .setUpdate(true) ); @@ -386,7 +386,7 @@ await page.ExpectByTextAsync("Playwright", new() { Exact = true }).ToBeVisibleAs // Replay API requests from HAR. // Either use a matching response from the HAR, // or abort the request if nothing matches. -page.routeFromHAR("./hars/fruit.har", new RouteFromHAROptions() +page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions() .setUrl("*/**/api/v1/fruits") .setUpdate(false) ); diff --git a/docs/src/network.md b/docs/src/network.md index f1fbc620b3..152231556e 100644 --- a/docs/src/network.md +++ b/docs/src/network.md @@ -147,7 +147,7 @@ const browser = await chromium.launch({ Browser browser = chromium.launch(new BrowserType.LaunchOptions() .setProxy(new Proxy("http://myproxy.com:3128") .setUsername('usr') - .setPassword('pwd')); + .setPassword('pwd'))); ``` ```python async @@ -179,60 +179,48 @@ await using var browser = await BrowserType.LaunchAsync(new() }); ``` -When specifying proxy for each context individually, **Chromium on Windows** needs a hint that proxy will be set. This is done via passing a non-empty proxy server to the browser itself. Here is an example of a context-specific proxy: +Its also possible to specify it per context: -```js tab=js-test title="playwright.config.ts" -import { defineConfig } from '@playwright/test'; -export default defineConfig({ - use: { - launchOptions: { - // Browser proxy option is required for Chromium on Windows. - proxy: { server: 'per-context' } - }, +```js tab=js-test title="example.spec.ts" +import { test, expect } from '@playwright/test'; + +test('should use custom proxy on a new context', async ({ browser }) => { + const context = await browser.newContext({ proxy: { server: 'http://myproxy.com:3128', } - } + }); + const page = await context.newPage(); + + await context.close(); }); ``` ```js tab=js-library -const browser = await chromium.launch({ - // Browser proxy option is required for Chromium on Windows. - proxy: { server: 'per-context' } -}); +const browser = await chromium.launch(); const context = await browser.newContext({ proxy: { server: 'http://myproxy.com:3128' } }); ``` ```java -Browser browser = chromium.launch(new BrowserType.LaunchOptions() - // Browser proxy option is required for Chromium on Windows. - .setProxy(new Proxy("per-context")); -BrowserContext context = chromium.launch(new Browser.NewContextOptions() - .setProxy(new Proxy("http://myproxy.com:3128")); +Browser browser = chromium.launch(); +BrowserContext context = browser.newContext(new Browser.NewContextOptions() + .setProxy(new Proxy("http://myproxy.com:3128"))); ``` ```python async -# Browser proxy option is required for Chromium on Windows. -browser = await chromium.launch(proxy={"server": "per-context"}) +browser = await chromium.launch() context = await browser.new_context(proxy={"server": "http://myproxy.com:3128"}) ``` ```python sync -# Browser proxy option is required for Chromium on Windows. -browser = chromium.launch(proxy={"server": "per-context"}) +browser = chromium.launch() context = browser.new_context(proxy={"server": "http://myproxy.com:3128"}) ``` ```csharp -var proxy = new Proxy { Server = "per-context" }; -await using var browser = await BrowserType.LaunchAsync(new() -{ - // Browser proxy option is required for Chromium on Windows. - Proxy = proxy -}); +await using var browser = await BrowserType.LaunchAsync(); await using var context = await browser.NewContextAsync(new() { Proxy = new Proxy { Server = "http://myproxy.com:3128" }, diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index 697ce6641f..b6a249b0eb 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -4,6 +4,38 @@ title: "Release notes" toc_max_heading_level: 2 --- +## Version 1.47 + +### Network Tab improvements + +The Network tab in the trace viewer has several nice improvements: + +- filtering by asset type and URL +- better display of query string parameters +- preview of font assets + +![Network tab now has filters](https://github.com/user-attachments/assets/4bd1b67d-90bd-438b-a227-00b9e86872e2) + +### Miscellaneous + +- The `mcr.microsoft.com/playwright/dotnet:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble. + To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright/dotnet:v1.47.0-jammy` instead. +- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility. +- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as byte arrays instead of file paths. +- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated. +- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`. + +### Browser Versions + +- Chromium 129.0.6668.29 +- Mozilla Firefox 130.0 +- WebKit 18.0 + +This version was also tested against the following stable channels: + +- Google Chrome 128 +- Microsoft Edge 128 + ## Version 1.46 ### TLS Client Certificates diff --git a/docs/src/release-notes-java.md b/docs/src/release-notes-java.md index cb948b15da..89a49f7451 100644 --- a/docs/src/release-notes-java.md +++ b/docs/src/release-notes-java.md @@ -4,6 +4,38 @@ title: "Release notes" toc_max_heading_level: 2 --- +## Version 1.47 + +### Network Tab improvements + +The Network tab in the trace viewer has several nice improvements: + +- filtering by asset type and URL +- better display of query string parameters +- preview of font assets + +![Network tab now has filters](https://github.com/user-attachments/assets/4bd1b67d-90bd-438b-a227-00b9e86872e2) + +### Miscellaneous + +- The `mcr.microsoft.com/playwright/java:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble. + To use the 22.02 jammy-based image, please use `mcr.microsoft.com/playwright/java:v1.47.0-jammy` instead. +- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility. +- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as byte arrays instead of file paths. +- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated. +- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`. + +### Browser Versions + +- Chromium 129.0.6668.29 +- Mozilla Firefox 130.0 +- WebKit 18.0 + +This version was also tested against the following stable channels: + +- Google Chrome 128 +- Microsoft Edge 128 + ## Version 1.46 ### TLS Client Certificates diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index c4434d60f5..a89b79c5ce 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -6,6 +6,67 @@ toc_max_heading_level: 2 import LiteYouTube from '@site/src/components/LiteYouTube'; +## Version 1.47 + +### Network Tab improvements + +The Network tab in the UI mode and trace viewer has several nice improvements: + +- filtering by asset type and URL +- better display of query string parameters +- preview of font assets + +![Network tab now has filters](https://github.com/user-attachments/assets/4bd1b67d-90bd-438b-a227-00b9e86872e2) + + +### `--tsconfig` CLI option + +By default, Playwright will look up the closest tsconfig for each imported file using a heuristic. You can now specify a single tsconfig file in the command line, and Playwright will use it for all imported files, not only test files: + +```sh +# Pass a specific tsconfig +npx playwright test --tsconfig tsconfig.test.json +``` + +### [APIRequestContext] now accepts [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) and `string` as query parameters + +You can now pass [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) and `string` as query parameters to [APIRequestContext]: + +```ts +test('query params', async ({ request }) => { + const searchParams = new URLSearchParams(); + searchParams.set('userId', 1); + const response = await request.get( + 'https://jsonplaceholder.typicode.com/posts', + { + params: searchParams // or as a string: 'userId=1' + } + ); + // ... +}); +``` + +### Miscellaneous + +- The `mcr.microsoft.com/playwright:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble. + To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright:v1.47.0-jammy` instead. +- New option [`option: behavior`] in [`method: Page.removeAllListeners`], [`method: Browser.removeAllListeners`] and [`method: BrowserContext.removeAllListeners`] to wait for ongoing listeners to complete. +- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as buffers instead of file paths. +- Attachments with a `text/html` content type can now be opened in a new tab in the HTML report. This is useful for including third-party reports or other HTML content in the Playwright test report and distributing it to your team. +- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated. +- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`. + +### Browser Versions + +- Chromium 129.0.6668.29 +- Mozilla Firefox 130.0 +- WebKit 18.0 + +This version was also tested against the following stable channels: + +- Google Chrome 128 +- Microsoft Edge 128 + ## Version 1.46 > -The full title path starting with the project. +The full title path starting with the test file name. ## property: TestInfo.workerIndex * since: v1.10 diff --git a/docs/src/test-components-js.md b/docs/src/test-components-js.md index e0bdc000bf..91d1860a26 100644 --- a/docs/src/test-components-js.md +++ b/docs/src/test-components-js.md @@ -113,12 +113,10 @@ component is mounted using this script. It can be either a `.js`, `.ts`, `.jsx` }> -```js +```js title="app.spec.tsx" import { test, expect } from '@playwright/experimental-ct-react'; import App from './App'; -test.use({ viewport: { width: 500, height: 500 } }); - test('should work', async ({ mount }) => { const component = await mount(); await expect(component).toContainText('Learn React'); @@ -129,18 +127,25 @@ test('should work', async ({ mount }) => { -```js +```js title="app.spec.ts" import { test, expect } from '@playwright/experimental-ct-vue'; import App from './App.vue'; -test.use({ viewport: { width: 500, height: 500 } }); - test('should work', async ({ mount }) => { const component = await mount(App); - await expect(component).toContainText('Vite + Vue'); + await expect(component).toContainText('Learn Vue'); }); ``` +```js title="app.spec.tsx" +import { test, expect } from '@playwright/experimental-ct-vue'; +import App from './App.vue'; + +test('should work', async ({ mount }) => { + const component = await mount(); + await expect(component).toContainText('Learn Vue'); +}); +``` If using TypeScript and Vue make sure to add a `vue.d.ts` file to your project: ```js @@ -151,15 +156,13 @@ declare module '*.vue'; -```js +```js title="app.spec.ts" import { test, expect } from '@playwright/experimental-ct-svelte'; import App from './App.svelte'; -test.use({ viewport: { width: 500, height: 500 } }); - test('should work', async ({ mount }) => { const component = await mount(App); - await expect(component).toContainText('Vite + Svelte'); + await expect(component).toContainText('Learn Svelte'); }); ``` @@ -167,12 +170,10 @@ test('should work', async ({ mount }) => { -```js +```js title="app.spec.tsx" import { test, expect } from '@playwright/experimental-ct-solid'; import App from './App'; -test.use({ viewport: { width: 500, height: 500 } }); - test('should work', async ({ mount }) => { const component = await mount(); await expect(component).toContainText('Learn Solid'); @@ -261,7 +262,10 @@ export function InputMediaForTest(props: InputMediaForTestProps) { Then test the component via testing the story: -```js title="input-media.test.spec.tsx" +```js title="input-media.spec.tsx" +import { test, expect } from '@playwright/experimental-ct-react'; +import { InputMediaForTest } from './input-media.story.tsx'; + test('changes the image', async ({ mount }) => { let mediaSelected: string | null = null; @@ -313,7 +317,9 @@ Provide props to a component when mounted. -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-react'; + test('props', async ({ mount }) => { const component = await mount(); }); @@ -322,7 +328,9 @@ test('props', async ({ mount }) => { -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-solid'; + test('props', async ({ mount }) => { const component = await mount(); }); @@ -331,7 +339,9 @@ test('props', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-svelte'; + test('props', async ({ mount }) => { const component = await mount(Component, { props: { msg: 'greetings' } }); }); @@ -340,12 +350,23 @@ test('props', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-vue'; + test('props', async ({ mount }) => { const component = await mount(Component, { props: { msg: 'greetings' } }); }); ``` +```js title="component.spec.tsx" +// Or alternatively, using the `jsx` style +import { test } from '@playwright/experimental-ct-vue'; + +test('props', async ({ mount }) => { + const component = await mount(); +}); +``` + @@ -366,36 +387,53 @@ Provide callbacks/events to a component when mounted. -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-react'; + test('callback', async ({ mount }) => { - const component = await mount( {}} />); + const component = await mount( {}} />); }); ``` -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-solid'; + test('callback', async ({ mount }) => { - const component = await mount( {}} />); + const component = await mount( {}} />); }); ``` -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-svelte'; + test('event', async ({ mount }) => { - const component = await mount(Component, { on: { callback() {} } }); + const component = await mount(Component, { on: { click() {} } }); }); ``` -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-vue'; + test('event', async ({ mount }) => { - const component = await mount(Component, { on: { callback() {} } }); + const component = await mount(Component, { on: { click() {} } }); +}); +``` + +```js title="component.spec.tsx" +// Or alternatively, using the `jsx` style +import { test } from '@playwright/experimental-ct-vue'; + +test('event', async ({ mount }) => { + const component = await mount( {}} />); }); ``` @@ -419,7 +457,9 @@ Provide children/slots to a component when mounted. -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-react'; + test('children', async ({ mount }) => { const component = await mount(Child); }); @@ -428,7 +468,9 @@ test('children', async ({ mount }) => { -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-solid'; + test('children', async ({ mount }) => { const component = await mount(Child); }); @@ -437,7 +479,9 @@ test('children', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-svelte'; + test('slot', async ({ mount }) => { const component = await mount(Component, { slots: { default: 'Slot' } }); }); @@ -446,12 +490,23 @@ test('slot', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-vue'; + test('slot', async ({ mount }) => { const component = await mount(Component, { slots: { default: 'Slot' } }); }); ``` +```js title="component.spec.tsx" +// Or alternatively, using the `jsx` style +import { test } from '@playwright/experimental-ct-vue'; + +test('children', async ({ mount }) => { + const component = await mount(Child); +}); +``` + @@ -614,7 +669,9 @@ Unmount the mounted component from the DOM. This is useful for testing the compo -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-react'; + test('unmount', async ({ mount }) => { const component = await mount(); await component.unmount(); @@ -624,7 +681,9 @@ test('unmount', async ({ mount }) => { -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-solid'; + test('unmount', async ({ mount }) => { const component = await mount(); await component.unmount(); @@ -634,7 +693,9 @@ test('unmount', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-svelte'; + test('unmount', async ({ mount }) => { const component = await mount(Component); await component.unmount(); @@ -644,13 +705,24 @@ test('unmount', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-vue'; + test('unmount', async ({ mount }) => { const component = await mount(Component); await component.unmount(); }); ``` +```js title="component.spec.tsx" +// Or alternatively, using the `jsx` style +import { test } from '@playwright/experimental-ct-vue'; + +test('unmount', async ({ mount }) => { + const component = await mount(); + await component.unmount(); +}); +``` @@ -671,11 +743,13 @@ Update props, slots/children, and/or events/callbacks of a mounted component. Th -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-react'; + test('update', async ({ mount }) => { const component = await mount(); await component.update( - {}}>Child + {}}>Child ); }); ``` @@ -683,11 +757,13 @@ test('update', async ({ mount }) => { -```js +```js title="component.spec.tsx" +import { test } from '@playwright/experimental-ct-solid'; + test('update', async ({ mount }) => { const component = await mount(); await component.update( - {}}>Child + {}}>Child ); }); ``` @@ -695,12 +771,14 @@ test('update', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-svelte'; + test('update', async ({ mount }) => { const component = await mount(Component); await component.update({ props: { msg: 'greetings' }, - on: { callback: () => {} }, + on: { click() {} }, slots: { default: 'Child' } }); }); @@ -709,17 +787,31 @@ test('update', async ({ mount }) => { -```js +```js title="component.spec.ts" +import { test } from '@playwright/experimental-ct-vue'; + test('update', async ({ mount }) => { const component = await mount(Component); await component.update({ props: { msg: 'greetings' }, - on: { callback: () => {} }, + on: { click() {} }, slots: { default: 'Child' } }); }); ``` +```js title="component.spec.tsx" +// Or alternatively, using the `jsx` style +import { test } from '@playwright/experimental-ct-vue'; + +test('update', async ({ mount }) => { + const component = await mount(); + await component.update( + {}}>Child + ); +}); +``` + diff --git a/docs/src/test-fixtures-js.md b/docs/src/test-fixtures-js.md index 60c9219a95..69c080297f 100644 --- a/docs/src/test-fixtures-js.md +++ b/docs/src/test-fixtures-js.md @@ -454,10 +454,6 @@ test('example test', async ({ slowFixture }) => { ## Fixtures-options -:::note -Overriding custom fixtures in the config file has changed in version 1.18. [Learn more](./release-notes#breaking-change-custom-config-options). -::: - Playwright Test supports running multiple test projects that can be separately configured. You can use "option" fixtures to make your configuration options declarative and type-checked. Learn more about [parametrizing tests](./test-parameterize.md). Below we'll create a `defaultItem` option in addition to the `todoPage` fixture from other examples. This option will be set in configuration file. Note the tuple syntax and `{ option: true }` argument. @@ -555,6 +551,30 @@ export default defineConfig({ }); ``` +**Array as an option value** + +If the value of your option is an array, for example `[{ name: 'Alice' }, { name: 'Bob' }]`, you'll need to wrap it into an extra array when providing the value. This is best illustrated with an example. + +```js +type Person = { name: string }; +const test = base.extend<{ persons: Person[] }>({ + // Declare the option, default value is an empty array. + persons: [[], { option: true }], +}); + +// Option value is an array of persons. +const actualPersons = [{ name: 'Alice' }, { name: 'Bob' }]; +test.use({ + // CORRECT: Wrap the value into an array and pass the scope. + persons: [actualPersons, { scope: 'test' }], +}); + +test.use({ + // WRONG: passing an array value directly will not work. + persons: actualPersons, +}); +``` + ## Execution order Each fixture has a setup and teardown phase separated by the `await use()` call in the fixture. Setup is executed before the fixture is used by the test/hook, and teardown is executed when the fixture will not be used by the test/hook anymore. @@ -702,3 +722,64 @@ export const test = base.extend({ }, { title: 'my fixture' }], }); ``` + +## Adding global beforeEach/afterEach hooks + +[`method: Test.beforeEach`] and [`method: Test.afterEach`] hooks run before/after each test declared in the same file and same [`method: Test.describe`] block (if any). If you want to declare hooks that run before/after each test globally, you can declare them as auto fixtures like this: + +```ts title="fixtures.ts" +import { test as base } from '@playwright/test'; + +export const test = base.extend<{ forEachTest: void }>({ + forEachTest: [async ({ page }, use) => { + // This code runs before every test. + await page.goto('http://localhost:8000'); + await use(); + // This code runs after every test. + console.log('Last URL:', page.url()); + }, { auto: true }], // automatically starts for every test. +}); +``` + +And then import the fixtures in all your tests: + +```ts title="mytest.spec.ts" +import { test } from './fixtures'; +import { expect } from '@playwright/test'; + +test('basic', async ({ page }) => { + expect(page).toHaveURL('http://localhost:8000'); + await page.goto('https://playwright.dev'); +}); +``` + +## Adding global beforeAll/afterAll hooks + +[`method: Test.beforeAll`] and [`method: Test.afterAll`] hooks run before/after all tests declared in the same file and same [`method: Test.describe`] block (if any), once per worker process. If you want to declare hooks +that run before/after all tests in every file, you can declare them as auto fixtures with `scope: 'worker'` as follows: + +```ts title="fixtures.ts" +import { test as base } from '@playwright/test'; + +export const test = base.extend<{}, { forEachWorker: void }>({ + forEachWorker: [async ({}, use) => { + // This code runs before all the tests in the worker process. + console.log(`Starting test worker ${test.info().workerIndex}`); + await use(); + // This code runs after all the tests in the worker process. + console.log(`Stopping test worker ${test.info().workerIndex}`); + }, { scope: 'worker', auto: true }], // automatically starts for every worker. +}); +``` + +And then import the fixtures in all your tests: + +```ts title="mytest.spec.ts" +import { test } from './fixtures'; +import { expect } from '@playwright/test'; + +test('basic', async ({ }) => { + // ... +}); +``` +Note that the fixtures will still run once per [worker process](./test-parallel.md#worker-processes), but you don't need to redeclare them in every file. diff --git a/docs/src/test-typescript-js.md b/docs/src/test-typescript-js.md index 5eaa3670a5..6e18b3c615 100644 --- a/docs/src/test-typescript-js.md +++ b/docs/src/test-typescript-js.md @@ -80,14 +80,14 @@ By default, Playwright will look up a closest tsconfig for each imported file by ```sh # Playwright will choose tsconfig automatically -npx playwrigh test +npx playwright test ``` Alternatively, you can specify a single tsconfig file to use in the command line, and Playwright will use it for all imported files, not only test files. ```sh # Pass a specific tsconfig -npx playwrigh test --tsconfig=tsconfig.test.json +npx playwright test --tsconfig=tsconfig.test.json ``` ## Manually compile tests with TypeScript diff --git a/package-lock.json b/package-lock.json index 1c48c213d7..b7cdad125e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "playwright-internal", - "version": "1.47.0-next", + "version": "1.48.0-next", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "playwright-internal", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "workspaces": [ "packages/*" @@ -26,6 +26,7 @@ "@types/babel__core": "^7.20.2", "@types/codemirror": "^5.60.7", "@types/formidable": "^2.0.4", + "@types/immutable": "^3.8.7", "@types/node": "^18.19.39", "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", @@ -37,11 +38,13 @@ "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react": "^4.2.1", "@zip.js/zip.js": "^2.7.29", + "ansi-styles": "^4.3.0", "chokidar": "^3.5.3", + "chromium-bidi": "^0.6.4", "colors": "^1.4.0", "concurrently": "^6.2.1", "cross-env": "^7.0.3", - "dotenv": "^16.0.0", + "dotenv": "^16.4.5", "electron": "^30.1.2", "esbuild": "^0.18.11", "eslint": "^8.55.0", @@ -50,6 +53,7 @@ "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.2", "formidable": "^2.1.1", + "immutable": "^4.3.7", "license-checker": "^25.0.1", "mime": "^3.0.0", "node-stream-zip": "^1.15.0", @@ -1831,6 +1835,16 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, + "node_modules/@types/immutable": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/immutable/-/immutable-3.8.7.tgz", + "integrity": "sha512-nsHFDX48Tl3RaP4BF47HHe5njx40Pcp+0a8CIqzJata80Fp7JzkcuGB7UhZBGjH9aA1fMEahIqvPQQNmro5YLg==", + "deprecated": "This is a stub types definition for Facebook's Immutable (https://github.com/facebook/immutable-js). Facebook's Immutable provides its own type definitions, so you don't need @types/immutable installed!", + "dev": true, + "dependencies": { + "immutable": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -2358,16 +2372,38 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ansi-styles/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/ansi-to-html": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz", @@ -2801,6 +2837,17 @@ "node": ">=4" } }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2828,6 +2875,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/chromium-bidi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.5.tgz", + "integrity": "sha512-RuLrmzYrxSb0s9SgpB+QN5jJucPduZQ/9SIe76MDxYJuecPW5mxMdacJ1f4EtgiV+R0p3sCkznTMvH0MPGFqjA==", + "dev": true, + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -2927,21 +2988,6 @@ "node": ">=10.0.0" } }, - "node_modules/concurrently/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/concurrently/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2970,24 +3016,6 @@ "node": ">=8" } }, - "node_modules/concurrently/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/concurrently/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/concurrently/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3258,6 +3286,13 @@ "dev": true, "optional": true }, + "node_modules/devtools-protocol": { + "version": "0.0.1349977", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1349977.tgz", + "integrity": "sha512-5JcwlDKinshGSm+4AVLFCkokJUAKTgjmiorNmrGgYYKix1h8Ts9/fplQeK1xg/rACYw1JlEM2PwIEvny5QswKQ==", + "dev": true, + "peer": true + }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -3293,15 +3328,15 @@ } }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/electron": { @@ -3757,21 +3792,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3798,24 +3818,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4613,6 +4615,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5454,6 +5462,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6820,9 +6834,9 @@ } }, "node_modules/svelte": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.9.tgz", - "integrity": "sha512-hsoB/WZGEPFXeRRLPhPrbRz67PhP6sqYgvwcAs+gWdSQSvNDw+/lTeUJSWe5h2xC97Fz/8QxAOqItwBzNJPU8w==", + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", + "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", @@ -7120,6 +7134,12 @@ "punycode": "^2.1.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/util-extend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", @@ -7738,39 +7758,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7905,6 +7892,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "packages/html-reporter": { "version": "0.0.0", "dependencies": { @@ -7912,10 +7908,10 @@ } }, "packages/playwright": { - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "bin": { "playwright": "cli.js" @@ -7929,11 +7925,11 @@ }, "packages/playwright-browser-chromium": { "name": "@playwright/browser-chromium", - "version": "1.47.0-next", + "version": "1.48.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "engines": { "node": ">=18" @@ -7941,11 +7937,11 @@ }, "packages/playwright-browser-firefox": { "name": "@playwright/browser-firefox", - "version": "1.47.0-next", + "version": "1.48.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "engines": { "node": ">=18" @@ -7953,22 +7949,22 @@ }, "packages/playwright-browser-webkit": { "name": "@playwright/browser-webkit", - "version": "1.47.0-next", + "version": "1.48.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "engines": { "node": ">=18" } }, "packages/playwright-chromium": { - "version": "1.47.0-next", + "version": "1.48.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "bin": { "playwright": "cli.js" @@ -7978,7 +7974,7 @@ } }, "packages/playwright-core": { - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -7989,11 +7985,11 @@ }, "packages/playwright-ct-core": { "name": "@playwright/experimental-ct-core", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "playwright": "1.47.0-next", - "playwright-core": "1.47.0-next", + "playwright": "1.48.0-next", + "playwright-core": "1.48.0-next", "vite": "^5.2.8" }, "engines": { @@ -8002,10 +7998,10 @@ }, "packages/playwright-ct-react": { "name": "@playwright/experimental-ct-react", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-react": "^4.2.1" }, "bin": { @@ -8017,10 +8013,10 @@ }, "packages/playwright-ct-react17": { "name": "@playwright/experimental-ct-react17", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-react": "^4.2.1" }, "bin": { @@ -8032,10 +8028,10 @@ }, "packages/playwright-ct-solid": { "name": "@playwright/experimental-ct-solid", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "vite-plugin-solid": "^2.7.0" }, "bin": { @@ -8050,17 +8046,17 @@ }, "packages/playwright-ct-svelte": { "name": "@playwright/experimental-ct-svelte", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@sveltejs/vite-plugin-svelte": "^3.0.1" }, "bin": { "playwright": "cli.js" }, "devDependencies": { - "svelte": "^4.2.8" + "svelte": "^4.2.19" }, "engines": { "node": ">=18" @@ -8068,10 +8064,10 @@ }, "packages/playwright-ct-vue": { "name": "@playwright/experimental-ct-vue", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-vue": "^4.2.1" }, "bin": { @@ -8083,10 +8079,10 @@ }, "packages/playwright-ct-vue2": { "name": "@playwright/experimental-ct-vue2", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-vue2": "^2.2.0" }, "bin": { @@ -8135,11 +8131,11 @@ } }, "packages/playwright-firefox": { - "version": "1.47.0-next", + "version": "1.48.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "bin": { "playwright": "cli.js" @@ -8150,10 +8146,10 @@ }, "packages/playwright-test": { "name": "@playwright/test", - "version": "1.47.0-next", + "version": "1.48.0-next", "license": "Apache-2.0", "dependencies": { - "playwright": "1.47.0-next" + "playwright": "1.48.0-next" }, "bin": { "playwright": "cli.js" @@ -8163,11 +8159,11 @@ } }, "packages/playwright-webkit": { - "version": "1.47.0-next", + "version": "1.48.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "bin": { "playwright": "cli.js" diff --git a/package.json b/package.json index 930fac8a80..c095546c7d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "playwright-internal", "private": true, - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", @@ -24,6 +24,7 @@ "webview2test": "playwright test --config=tests/webview2/playwright.config.ts", "itest": "playwright test --config=tests/installation/playwright.config.ts", "stest": "playwright test --config=tests/stress/playwright.config.ts", + "biditest": "playwright test --config=tests/bidi/playwright.config.ts", "test-html-reporter": "playwright test --config=packages/html-reporter", "test-web": "playwright test --config=packages/web", "ttest": "node ./tests/playwright-test/stable-test-runner/node_modules/@playwright/test/cli test --config=tests/playwright-test/playwright.config.ts", @@ -64,6 +65,7 @@ "@types/babel__core": "^7.20.2", "@types/codemirror": "^5.60.7", "@types/formidable": "^2.0.4", + "@types/immutable": "^3.8.7", "@types/node": "^18.19.39", "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", @@ -75,11 +77,13 @@ "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react": "^4.2.1", "@zip.js/zip.js": "^2.7.29", + "ansi-styles": "^4.3.0", "chokidar": "^3.5.3", + "chromium-bidi": "^0.6.4", "colors": "^1.4.0", "concurrently": "^6.2.1", "cross-env": "^7.0.3", - "dotenv": "^16.0.0", + "dotenv": "^16.4.5", "electron": "^30.1.2", "esbuild": "^0.18.11", "eslint": "^8.55.0", @@ -88,6 +92,7 @@ "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.2", "formidable": "^2.1.1", + "immutable": "^4.3.7", "license-checker": "^25.0.1", "mime": "^3.0.0", "node-stream-zip": "^1.15.0", diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index 419a8725ac..8c1dcc85dc 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -75,11 +75,16 @@ export const AttachmentLink: React.FunctionComponent<{ attachment: TestAttachment, href?: string, linkName?: string, -}> = ({ attachment, href, linkName }) => { + openInNewTab?: boolean, +}> = ({ attachment, href, linkName, openInNewTab }) => { return {attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()} {attachment.path && {linkName || attachment.name}} - {!attachment.path && {linkifyText(attachment.name)}} + {!attachment.path && ( + openInNewTab + ? e.stopPropagation()}>{attachment.name} + : {linkifyText(attachment.name)} + )} } loadChildren={attachment.body ? () => { return [
{linkifyText(attachment.body!)}
]; } : undefined} depth={0} style={{ lineHeight: '32px' }}>
; diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index 1ec8c65a1e..8ee36d0cda 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -67,15 +67,16 @@ export const TestResultView: React.FC<{ anchor: 'video' | 'diff' | '', }> = ({ result, anchor }) => { - const { screenshots, videos, traces, otherAttachments, diffs } = React.useMemo(() => { + const { screenshots, videos, traces, otherAttachments, diffs, htmls } = React.useMemo(() => { const attachments = result?.attachments || []; const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/'))); const videos = attachments.filter(a => a.name === 'video'); const traces = attachments.filter(a => a.name === 'trace'); + const htmls = attachments.filter(a => a.contentType.startsWith('text/html')); const otherAttachments = new Set(attachments); - [...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a)); + [...screenshots, ...videos, ...traces, ...htmls].forEach(a => otherAttachments.delete(a)); const diffs = groupImageDiffs(screenshots); - return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs }; + return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, htmls }; }, [result]); const videoRef = React.useRef(null); @@ -135,7 +136,10 @@ export const TestResultView: React.FC<{ )} } - {!!otherAttachments.size && + {!!(otherAttachments.size + htmls.length) && + {[...htmls].map((a, i) => ( + ) + )} {[...otherAttachments].map((a, i) => )} } ; diff --git a/packages/playwright-browser-chromium/package.json b/packages/playwright-browser-chromium/package.json index 5a3d0761cc..ac4869cb7a 100644 --- a/packages/playwright-browser-chromium/package.json +++ b/packages/playwright-browser-chromium/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/browser-chromium", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright package that automatically installs Chromium", "repository": { "type": "git", @@ -27,6 +27,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" } } diff --git a/packages/playwright-browser-firefox/package.json b/packages/playwright-browser-firefox/package.json index 86778490f2..974c76ac0c 100644 --- a/packages/playwright-browser-firefox/package.json +++ b/packages/playwright-browser-firefox/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/browser-firefox", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright package that automatically installs Firefox", "repository": { "type": "git", @@ -27,6 +27,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" } } diff --git a/packages/playwright-browser-webkit/package.json b/packages/playwright-browser-webkit/package.json index 1e108574c5..58e819b0d3 100644 --- a/packages/playwright-browser-webkit/package.json +++ b/packages/playwright-browser-webkit/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/browser-webkit", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright package that automatically installs WebKit", "repository": { "type": "git", @@ -27,6 +27,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" } } diff --git a/packages/playwright-chromium/package.json b/packages/playwright-chromium/package.json index 55897f465e..00f5299d3e 100644 --- a/packages/playwright-chromium/package.json +++ b/packages/playwright-chromium/package.json @@ -1,6 +1,6 @@ { "name": "playwright-chromium", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate Chromium", "repository": { "type": "git", @@ -30,6 +30,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" } } diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index 3c5a71e20f..0a3ca6a5f4 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -16,6 +16,7 @@ This project incorporates components from the projects listed below. The origina - concat-map@0.0.1 (https://github.com/substack/node-concat-map) - debug@4.3.4 (https://github.com/debug-js/debug) - define-lazy-prop@2.0.0 (https://github.com/sindresorhus/define-lazy-prop) +- dotenv@16.4.5 (https://github.com/motdotla/dotenv) - end-of-stream@1.4.4 (https://github.com/mafintosh/end-of-stream) - escape-string-regexp@2.0.0 (https://github.com/sindresorhus/escape-string-regexp) - extract-zip@2.0.1 (https://github.com/maxogden/extract-zip) @@ -472,6 +473,34 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ========================================= END OF define-lazy-prop@2.0.0 AND INFORMATION +%% dotenv@16.4.5 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2015, Scott Motte +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF dotenv@16.4.5 AND INFORMATION + %% end-of-stream@1.4.4 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -1514,6 +1543,6 @@ END OF yazl@2.5.1 AND INFORMATION SUMMARY BEGIN HERE ========================================= -Total Packages: 45 +Total Packages: 46 ========================================= END OF SUMMARY \ No newline at end of file diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index dd9fd60588..23974d6523 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,31 +3,31 @@ "browsers": [ { "name": "chromium", - "revision": "1131", + "revision": "1135", "installByDefault": true, - "browserVersion": "128.0.6613.36" + "browserVersion": "129.0.6668.42" }, { "name": "chromium-tip-of-tree", - "revision": "1250", + "revision": "1259", "installByDefault": false, - "browserVersion": "129.0.6658.0" + "browserVersion": "130.0.6713.0" }, { "name": "firefox", - "revision": "1462", + "revision": "1463", "installByDefault": true, - "browserVersion": "129.0" + "browserVersion": "130.0" }, { "name": "firefox-beta", - "revision": "1462", + "revision": "1463", "installByDefault": false, - "browserVersion": "130.0b2" + "browserVersion": "131.0b2" }, { "name": "webkit", - "revision": "2062", + "revision": "2073", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", @@ -42,7 +42,11 @@ { "name": "ffmpeg", "revision": "1010", - "installByDefault": true + "installByDefault": true, + "revisionOverrides": { + "mac12": "1010", + "mac12-arm64": "1010" + } }, { "name": "android", diff --git a/packages/playwright-core/bundles/utils/package-lock.json b/packages/playwright-core/bundles/utils/package-lock.json index 66c4cdae12..eef68ef8ee 100644 --- a/packages/playwright-core/bundles/utils/package-lock.json +++ b/packages/playwright-core/bundles/utils/package-lock.json @@ -11,6 +11,7 @@ "colors": "1.4.0", "commander": "8.3.0", "debug": "^4.3.4", + "dotenv": "^16.4.5", "graceful-fs": "4.2.10", "https-proxy-agent": "5.0.0", "jpeg-js": "0.4.4", @@ -198,6 +199,17 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -560,6 +572,11 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" }, + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" + }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", diff --git a/packages/playwright-core/bundles/utils/package.json b/packages/playwright-core/bundles/utils/package.json index 8ac0c112fe..a7c66192e0 100644 --- a/packages/playwright-core/bundles/utils/package.json +++ b/packages/playwright-core/bundles/utils/package.json @@ -12,6 +12,7 @@ "colors": "1.4.0", "commander": "8.3.0", "debug": "^4.3.4", + "dotenv": "^16.4.5", "graceful-fs": "4.2.10", "https-proxy-agent": "5.0.0", "jpeg-js": "0.4.4", diff --git a/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts b/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts index 49dc61a05c..dcb3790629 100644 --- a/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts +++ b/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts @@ -20,6 +20,9 @@ export const colors = colorsLibrary; import debugLibrary from 'debug'; export const debug = debugLibrary; +import dotenvLibrary from 'dotenv'; +export const dotenv = dotenvLibrary; + export { getProxyForUrl } from 'proxy-from-env'; export { HttpsProxyAgent } from 'https-proxy-agent'; diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index 155f526eae..3a931d4b9a 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -1,6 +1,6 @@ { "name": "playwright-core", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index ad943c049e..d8fa8230c6 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -20,7 +20,7 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; import type { Command } from '../utilsBundle'; -import { program } from '../utilsBundle'; +import { program, dotenv } from '../utilsBundle'; export { program } from '../utilsBundle'; import { runDriver, runServer, printApiJson, launchBrowserServer } from './driver'; import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer'; @@ -348,10 +348,10 @@ type CaptureOptions = { fullPage: boolean; }; -async function launchContext(options: Options, headless: boolean, executablePath?: string): Promise<{ browser: Browser, browserName: string, launchOptions: LaunchOptions, contextOptions: BrowserContextOptions, context: BrowserContext }> { +async function launchContext(options: Options, extraOptions: LaunchOptions): Promise<{ browser: Browser, browserName: string, launchOptions: LaunchOptions, contextOptions: BrowserContextOptions, context: BrowserContext }> { validateOptions(options); const browserType = lookupBrowserType(options); - const launchOptions: LaunchOptions = { headless, executablePath }; + const launchOptions: LaunchOptions = extraOptions; if (options.channel) launchOptions.channel = options.channel as any; launchOptions.handleSIGINT = false; @@ -363,7 +363,7 @@ async function launchContext(options: Options, headless: boolean, executablePath // In headful mode, use host device scale factor for things to look nice. // In headless, keep things the way it works in Playwright by default. // Assume high-dpi on MacOS. TODO: this is not perfect. - if (!headless) + if (!extraOptions.headless) contextOptions.deviceScaleFactor = os.platform() === 'darwin' ? 2 : 1; // Work around the WebKit GTK scrolling issue. @@ -547,7 +547,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi } async function open(options: Options, url: string | undefined, language: string) { - const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS, process.env.PWTEST_CLI_EXECUTABLE_PATH); + const { context, launchOptions, contextOptions } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH }); await context._enableRecorder({ language, launchOptions, @@ -560,7 +560,17 @@ async function open(options: Options, url: string | undefined, language: string) async function codegen(options: Options & { target: string, output?: string, testIdAttribute?: string }, url: string | undefined) { const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options; - const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS, process.env.PWTEST_CLI_EXECUTABLE_PATH); + const tracesDir = path.join(os.tmpdir(), `recorder-trace-${Date.now()}`); + const { context, launchOptions, contextOptions } = await launchContext(options, { + headless: !!process.env.PWTEST_CLI_HEADLESS, + executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH, + tracesDir, + }); + dotenv.config({ path: 'playwright.env' }); + if (process.env.PW_RECORDER_IS_TRACE_VIEWER) { + await fs.promises.mkdir(tracesDir, { recursive: true }); + await context.tracing.start({ name: 'trace', _live: true }); + } await context._enableRecorder({ language, launchOptions, @@ -570,7 +580,6 @@ async function codegen(options: Options & { target: string, output?: string, tes mode: 'recording', testIdAttributeName, outputFile: outputFile ? path.resolve(outputFile) : undefined, - handleSIGINT: false, }); await openPage(context, url); } @@ -587,7 +596,7 @@ async function waitForPage(page: Page, captureOptions: CaptureOptions) { } async function screenshot(options: Options, captureOptions: CaptureOptions, url: string, path: string) { - const { context } = await launchContext(options, true); + const { context } = await launchContext(options, { headless: true }); console.log('Navigating to ' + url); const page = await openPage(context, url); await waitForPage(page, captureOptions); @@ -600,7 +609,7 @@ async function screenshot(options: Options, captureOptions: CaptureOptions, url: async function pdf(options: Options, captureOptions: CaptureOptions, url: string, path: string) { if (options.browser !== 'chromium') throw new Error('PDF creation is only working with Chromium'); - const { context } = await launchContext({ ...options, browser: 'chromium' }, true); + const { context } = await launchContext({ ...options, browser: 'chromium' }, { headless: true }); console.log('Navigating to ' + url); const page = await openPage(context, url); await waitForPage(page, captureOptions); diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index b0b72917cf..ef222136dd 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -308,7 +308,7 @@ export class BrowserContext extends ChannelOwner } async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise { - const source = await evaluationScript(script, arg, arguments.length > 1); + const source = await evaluationScript(script, arg); await this._channel.addInitScript({ source }); } @@ -481,7 +481,6 @@ export class BrowserContext extends ChannelOwner mode?: 'recording' | 'inspecting', testIdAttributeName?: string, outputFile?: string, - handleSIGINT?: boolean, }) { await this._channel.recorderSupplementEnable(params); } diff --git a/packages/playwright-core/src/client/clientHelper.ts b/packages/playwright-core/src/client/clientHelper.ts index fcc785b71b..540230a4fc 100644 --- a/packages/playwright-core/src/client/clientHelper.ts +++ b/packages/playwright-core/src/client/clientHelper.ts @@ -28,37 +28,20 @@ export function envObjectToArray(env: types.Env): { name: string, value: string return result; } -export async function evaluationScript(fun: Function | string | { path?: string, content?: string }, arg: any, hasArg: boolean, addSourceUrl: boolean = true): Promise { +export async function evaluationScript(fun: Function | string | { path?: string, content?: string }, arg?: any, addSourceUrl: boolean = true): Promise { if (typeof fun === 'function') { const source = fun.toString(); const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg); return `(${source})(${argString})`; } - if (isString(fun)) { - if (arg !== undefined) - throw new Error('Cannot evaluate a string with arguments'); + if (arg !== undefined) + throw new Error('Cannot evaluate a string with arguments'); + if (isString(fun)) return fun; - } - if (fun.content !== undefined) { - if (arg !== undefined) - throw new Error('Cannot evaluate a string with arguments'); + if (fun.content !== undefined) return fun.content; - } if (fun.path !== undefined) { let source = await fs.promises.readFile(fun.path, 'utf8'); - if (hasArg) { - // Assume a CJS module that has a function default export. - source = `(() => { - var exports = {}; var module = { exports }; - ${source} - let __pw_result__ = module.exports; - if (__pw_result__ && typeof __pw_result__ === 'object' && ('default' in __pw_result__)) - __pw_result__ = __pw_result__['default']; - if (typeof __pw_result__ !== 'function') - return __pw_result__; - return __pw_result__(${JSON.stringify(arg)}); - })()`; - } if (addSourceUrl) source = addSourceUrlToScript(source, fun.path); return source; diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 7aaa5069c2..87c31579b5 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -175,8 +175,12 @@ export class APIRequestContext extends ChannelOwner= 0, `'maxRedirects' must be greater than or equal to '0'`); assert(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`); const url = options.url !== undefined ? options.url : options.request!.url(); - const params = mapParamsToArray(options.params); const method = options.method || options.request?.method(); + let encodedParams = undefined; + if (typeof options.params === 'string') + encodedParams = options.params; + else if (options.params instanceof URLSearchParams) + encodedParams = options.params.toString(); // Cannot call allHeaders() here as the request may be paused inside route handler. const headersObj = options.headers || options.request?.headers(); const headers = headersObj ? headersObjectToArray(headersObj) : undefined; @@ -228,7 +232,8 @@ export class APIRequestContext extends ChannelOwner implements api.Page } async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { - const source = await evaluationScript(script, arg, arguments.length > 1); + const source = await evaluationScript(script, arg); await this._channel.addInitScript({ source }); } diff --git a/packages/playwright-core/src/client/playwright.ts b/packages/playwright-core/src/client/playwright.ts index 593e0bd49f..9933ce15de 100644 --- a/packages/playwright-core/src/client/playwright.ts +++ b/packages/playwright-core/src/client/playwright.ts @@ -26,6 +26,8 @@ import { Selectors, SelectorsOwner } from './selectors'; export class Playwright extends ChannelOwner { readonly _android: Android; readonly _electron: Electron; + readonly _bidiChromium: BrowserType; + readonly _bidiFirefox: BrowserType; readonly chromium: BrowserType; readonly firefox: BrowserType; readonly webkit: BrowserType; @@ -45,6 +47,10 @@ export class Playwright extends ChannelOwner { this.webkit._playwright = this; this._android = Android.from(initializer.android); this._electron = Electron.from(initializer.electron); + this._bidiChromium = BrowserType.from(initializer.bidiChromium); + this._bidiChromium._playwright = this; + this._bidiFirefox = BrowserType.from(initializer.bidiFirefox); + this._bidiFirefox._playwright = this; this.devices = this._connection.localUtils()?.devices ?? {}; this.selectors = new Selectors(); this.errors = { TimeoutError }; diff --git a/packages/playwright-core/src/client/selectors.ts b/packages/playwright-core/src/client/selectors.ts index c7a7967559..2739be0e8d 100644 --- a/packages/playwright-core/src/client/selectors.ts +++ b/packages/playwright-core/src/client/selectors.ts @@ -26,7 +26,7 @@ export class Selectors implements api.Selectors { private _registrations: channels.SelectorsRegisterParams[] = []; async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise { - const source = await evaluationScript(script, undefined, false, false); + const source = await evaluationScript(script, undefined, false); const params = { ...options, name, source }; for (const channel of this._channels) await channel._channel.register(params); diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 37d374e3ec..2e7f7e4107 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -33,7 +33,7 @@ export type WaitForEventOptions = Function | { predicate?: Function, timeout?: n export type WaitForFunctionOptions = { timeout?: number, polling?: 'raf' | number }; export type SelectOption = { value?: string, label?: string, index?: number, valueOrLabel?: string }; -export type SelectOptionOptions = { force?: boolean, timeout?: number, noWaitAfter?: boolean }; +export type SelectOptionOptions = { force?: boolean, timeout?: number }; export type FilePayload = { name: string, mimeType: string, buffer: Buffer }; export type StorageState = { cookies: channels.NetworkCookie[], diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 81755c79bc..b67edcbca8 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -176,6 +176,7 @@ scheme.APIRequestContextInitializer = tObject({ }); scheme.APIRequestContextFetchParams = tObject({ url: tString, + encodedParams: tOptional(tString), params: tOptional(tArray(tType('NameValue'))), method: tOptional(tString), headers: tOptional(tArray(tType('NameValue'))), @@ -323,6 +324,8 @@ scheme.PlaywrightInitializer = tObject({ chromium: tChannel(['BrowserType']), firefox: tChannel(['BrowserType']), webkit: tChannel(['BrowserType']), + bidiChromium: tChannel(['BrowserType']), + bidiFirefox: tChannel(['BrowserType']), android: tChannel(['Android']), electron: tChannel(['Electron']), utils: tOptional(tChannel(['LocalUtils'])), @@ -961,7 +964,6 @@ scheme.BrowserContextRecorderSupplementEnableParams = tObject({ device: tOptional(tString), saveStorage: tOptional(tString), outputFile: tOptional(tString), - handleSIGINT: tOptional(tBoolean), omitCallTracking: tOptional(tBoolean), }); scheme.BrowserContextRecorderSupplementEnableResult = tOptional(tObject({})); @@ -1637,7 +1639,6 @@ scheme.FrameSelectOptionParams = tObject({ }))), force: tOptional(tBoolean), timeout: tOptional(tNumber), - noWaitAfter: tOptional(tBoolean), }); scheme.FrameSelectOptionResult = tObject({ values: tArray(tString), @@ -2001,7 +2002,6 @@ scheme.ElementHandleSelectOptionParams = tObject({ }))), force: tOptional(tBoolean), timeout: tOptional(tNumber), - noWaitAfter: tOptional(tBoolean), }); scheme.ElementHandleSelectOptionResult = tObject({ values: tArray(tString), diff --git a/packages/playwright-core/src/server/DEPS.list b/packages/playwright-core/src/server/DEPS.list index bc32bb8486..4446d36a24 100644 --- a/packages/playwright-core/src/server/DEPS.list +++ b/packages/playwright-core/src/server/DEPS.list @@ -16,6 +16,7 @@ [playwright.ts] ./android/ +./bidi/ ./chromium/ ./electron/ ./firefox/ diff --git a/packages/playwright-core/src/server/bidi/DEPS.list b/packages/playwright-core/src/server/bidi/DEPS.list new file mode 100644 index 0000000000..9be31302c5 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/DEPS.list @@ -0,0 +1,11 @@ +[*] +../../utils/ +../ +../isomorphic/ +./third_party/ + +[bidiOverCdp.ts] +*** + +[bidiChromium.ts] +../chromium/chromiumSwitches.ts diff --git a/packages/playwright-core/src/server/bidi/bidiBrowser.ts b/packages/playwright-core/src/server/bidi/bidiBrowser.ts new file mode 100644 index 0000000000..0c658a82b4 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiBrowser.ts @@ -0,0 +1,335 @@ +/** + * 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 channels from '@protocol/channels'; +import type { RegisteredListener } from '../../utils/eventsHelper'; +import { eventsHelper } from '../../utils/eventsHelper'; +import type { BrowserOptions } from '../browser'; +import { Browser } from '../browser'; +import { assertBrowserContextIsNotOwned, BrowserContext } from '../browserContext'; +import type { SdkObject } from '../instrumentation'; +import * as network from '../network'; +import type { InitScript, Page, PageDelegate } from '../page'; +import type { ConnectionTransport } from '../transport'; +import type * as types from '../types'; +import type { BidiSession } from './bidiConnection'; +import { BidiConnection } from './bidiConnection'; +import { bidiBytesValueToString } from './bidiNetworkManager'; +import { BidiPage } from './bidiPage'; +import * as bidi from './third_party/bidiProtocol'; + +export class BidiBrowser extends Browser { + private readonly _connection: BidiConnection; + readonly _browserSession: BidiSession; + private _bidiSessionInfo!: bidi.Session.NewResult; + readonly _contexts = new Map(); + readonly _bidiPages = new Map(); + private readonly _eventListeners: RegisteredListener[]; + + static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions): Promise { + const browser = new BidiBrowser(parent, transport, options); + if ((options as any).__testHookOnConnectToBrowser) + await (options as any).__testHookOnConnectToBrowser(); + + let proxy: bidi.Session.ManualProxyConfiguration | undefined; + if (options.proxy) { + proxy = { + proxyType: 'manual', + }; + const url = new URL(options.proxy.server); // Validate proxy server. + switch (url.protocol) { + case 'http:': + proxy.httpProxy = url.host; + break; + case 'https:': + proxy.httpsProxy = url.host; + break; + case 'socks4:': + proxy.socksProxy = url.host; + proxy.socksVersion = 4; + break; + case 'socks5:': + proxy.socksProxy = url.host; + proxy.socksVersion = 5; + break; + default: + throw new Error('Invalid proxy server protocol: ' + options.proxy.server); + } + if (options.proxy.bypass) + proxy.noProxy = options.proxy.bypass.split(','); + // TODO: support authentication. + } + + browser._bidiSessionInfo = await browser._browserSession.send('session.new', { + capabilities: { + alwaysMatch: { + acceptInsecureCerts: false, + proxy, + unhandledPromptBehavior: { + default: bidi.Session.UserPromptHandlerType.Ignore, + }, + webSocketUrl: true + }, + } + }); + + await browser._browserSession.send('session.subscribe', { + events: [ + 'browsingContext', + 'network', + 'log', + 'script', + ], + }); + return browser; + } + + constructor(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions) { + super(parent, options); + this._connection = new BidiConnection(transport, this._onDisconnect.bind(this), options.protocolLogger, options.browserLogsCollector); + this._browserSession = this._connection.browserSession; + this._eventListeners = [ + eventsHelper.addEventListener(this._browserSession, 'browsingContext.contextCreated', this._onBrowsingContextCreated.bind(this)), + eventsHelper.addEventListener(this._browserSession, 'script.realmDestroyed', this._onScriptRealmDestroyed.bind(this)), + ]; + } + + _onDisconnect() { + this._didClose(); + } + + async doCreateNewContext(options: channels.BrowserNewContextParams): Promise { + const { userContext } = await this._browserSession.send('browser.createUserContext', {}); + const context = new BidiBrowserContext(this, userContext, options); + await context._initialize(); + this._contexts.set(userContext, context); + return context; + } + + contexts(): BrowserContext[] { + return Array.from(this._contexts.values()); + } + + version(): string { + return this._bidiSessionInfo.capabilities.browserVersion; + } + + userAgent(): string { + return this._bidiSessionInfo.capabilities.userAgent; + } + + isConnected(): boolean { + return !this._connection.isClosed(); + } + + private _onBrowsingContextCreated(event: bidi.BrowsingContext.Info) { + if (event.parent) { + const parentFrameId = event.parent; + for (const page of this._bidiPages.values()) { + const parentFrame = page._page._frameManager.frame(parentFrameId); + if (!parentFrame) + continue; + page._session.addFrameBrowsingContext(event.context); + page._page._frameManager.frameAttached(event.context, parentFrameId); + return; + } + return; + } + let context = this._contexts.get(event.userContext); + if (!context) + context = this._defaultContext as BidiBrowserContext; + if (!context) + return; + const session = this._connection.createMainFrameBrowsingContextSession(event.context); + const opener = event.originalOpener && this._bidiPages.get(event.originalOpener); + const page = new BidiPage(context, session, opener || null); + this._bidiPages.set(event.context, page); + } + + _onBrowsingContextDestroyed(event: bidi.BrowsingContext.Info) { + if (event.parent) { + this._browserSession.removeFrameBrowsingContext(event.context); + const parentFrameId = event.parent; + for (const page of this._bidiPages.values()) { + const parentFrame = page._page._frameManager.frame(parentFrameId); + if (!parentFrame) + continue; + page._page._frameManager.frameDetached(event.context); + return; + } + return; + } + const bidiPage = this._bidiPages.get(event.context); + if (!bidiPage) + return; + bidiPage.didClose(); + this._bidiPages.delete(event.context); + } + + private _onScriptRealmDestroyed(event: bidi.Script.RealmDestroyedParameters) { + for (const page of this._bidiPages.values()) { + if (page._onRealmDestroyed(event)) + return; + } + } +} + +export class BidiBrowserContext extends BrowserContext { + declare readonly _browser: BidiBrowser; + + constructor(browser: BidiBrowser, browserContextId: string | undefined, options: channels.BrowserNewContextParams) { + super(browser, options, browserContextId); + this._authenticateProxyViaHeader(); + } + + private _bidiPages() { + return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this); + } + + pages(): Page[] { + return this._bidiPages().map(bidiPage => bidiPage._initializedPage).filter(Boolean) as Page[]; + } + + async newPageDelegate(): Promise { + assertBrowserContextIsNotOwned(this); + const { context } = await this._browser._browserSession.send('browsingContext.create', { + type: bidi.BrowsingContext.CreateType.Window, + userContext: this._browserContextId, + }); + return this._browser._bidiPages.get(context)!; + } + + async doGetCookies(urls: string[]): Promise { + const { cookies } = await this._browser._browserSession.send('storage.getCookies', + { partition: { type: 'storageKey', userContext: this._browserContextId } }); + return network.filterCookies(cookies.map((c: bidi.Network.Cookie) => { + const copy: channels.NetworkCookie = { + name: c.name, + value: bidiBytesValueToString(c.value), + domain: c.domain, + path: c.path, + httpOnly: c.httpOnly, + secure: c.secure, + expires: c.expiry ?? -1, + sameSite: c.sameSite ? fromBidiSameSite(c.sameSite) : 'None', + }; + return copy; + }), urls); + } + + async addCookies(cookies: channels.SetNetworkCookie[]) { + cookies = network.rewriteCookies(cookies); + const promises = cookies.map((c: channels.SetNetworkCookie) => { + const cookie: bidi.Storage.PartialCookie = { + name: c.name, + value: { type: 'string', value: c.value }, + domain: c.domain!, + path: c.path, + httpOnly: c.httpOnly, + secure: c.secure, + sameSite: c.sameSite && toBidiSameSite(c.sameSite), + expiry: (c.expires === -1 || c.expires === undefined) ? undefined : Math.round(c.expires), + }; + return this._browser._browserSession.send('storage.setCookie', + { cookie, partition: { type: 'storageKey', userContext: this._browserContextId } }); + }); + await Promise.all(promises); + } + + async doClearCookies() { + await this._browser._browserSession.send('storage.deleteCookies', + { partition: { type: 'storageKey', userContext: this._browserContextId } }); + } + + async doGrantPermissions(origin: string, permissions: string[]) { + } + + async doClearPermissions() { + } + + async setGeolocation(geolocation?: types.Geolocation): Promise { + } + + async setExtraHTTPHeaders(headers: types.HeadersArray): Promise { + } + + async setUserAgent(userAgent: string | undefined): Promise { + } + + async setOffline(offline: boolean): Promise { + } + + async doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise { + this._options.httpCredentials = httpCredentials; + for (const page of this.pages()) + await (page._delegate as BidiPage).updateHttpCredentials(); + } + + async doAddInitScript(initScript: InitScript) { + await Promise.all(this.pages().map(page => (page._delegate as BidiPage).addInitScript(initScript))); + } + + async doRemoveNonInternalInitScripts() { + } + + async doUpdateRequestInterception(): Promise { + } + + onClosePersistent() {} + + override async clearCache(): Promise { + } + + async doClose(reason: string | undefined) { + // TODO: implement for persistent context + if (!this._browserContextId) + return; + + await this._browser._browserSession.send('browser.removeUserContext', { + userContext: this._browserContextId + }); + this._browser._contexts.delete(this._browserContextId); + } + + async cancelDownload(uuid: string) { + } +} + +function fromBidiSameSite(sameSite: bidi.Network.SameSite): channels.NetworkCookie['sameSite'] { + switch (sameSite) { + case 'strict': return 'Strict'; + case 'lax': return 'Lax'; + case 'none': return 'None'; + } + return 'None'; +} + +function toBidiSameSite(sameSite: channels.SetNetworkCookie['sameSite']): bidi.Network.SameSite { + switch (sameSite) { + case 'Strict': return bidi.Network.SameSite.Strict; + case 'Lax': return bidi.Network.SameSite.Lax; + case 'None': return bidi.Network.SameSite.None; + } + return bidi.Network.SameSite.None; +} + +export namespace Network { + export const enum SameSite { + Strict = 'strict', + Lax = 'lax', + None = 'none', + } +} diff --git a/packages/playwright-core/src/server/bidi/bidiChromium.ts b/packages/playwright-core/src/server/bidi/bidiChromium.ts new file mode 100644 index 0000000000..e94dabf072 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiChromium.ts @@ -0,0 +1,161 @@ +/** + * 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 os from 'os'; +import { assert, wrapInASCIIBox } from '../../utils'; +import type { Env } from '../../utils/processLauncher'; +import type { BrowserOptions } from '../browser'; +import { BrowserReadyState, BrowserType, kNoXServerRunningError } from '../browserType'; +import { chromiumSwitches } from '../chromium/chromiumSwitches'; +import type { SdkObject } from '../instrumentation'; +import type { ProtocolError } from '../protocolError'; +import type { ConnectionTransport } from '../transport'; +import type * as types from '../types'; +import { BidiBrowser } from './bidiBrowser'; +import { kBrowserCloseMessageId } from './bidiConnection'; + +export class BidiChromium extends BrowserType { + constructor(parent: SdkObject) { + super(parent, 'bidi'); + this._useBidi = true; + } + + override async connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { + // Chrome doesn't support Bidi, we create Bidi over CDP which is used by Chrome driver. + // bidiOverCdp depends on chromium-bidi which we only have in devDependencies, so + // we load bidiOverCdp dynamically. + const bidiTransport = await require('./bidiOverCdp').connectBidiOverCdp(transport); + (transport as any)[kBidiOverCdpWrapper] = bidiTransport; + return BidiBrowser.connect(this.attribution.playwright, bidiTransport, options); + } + + override doRewriteStartupLog(error: ProtocolError): ProtocolError { + if (!error.logs) + return error; + if (error.logs.includes('Missing X server')) + error.logs = '\n' + wrapInASCIIBox(kNoXServerRunningError, 1); + // These error messages are taken from Chromium source code as of July, 2020: + // https://github.com/chromium/chromium/blob/70565f67e79f79e17663ad1337dc6e63ee207ce9/content/browser/zygote_host/zygote_host_impl_linux.cc + if (!error.logs.includes('crbug.com/357670') && !error.logs.includes('No usable sandbox!') && !error.logs.includes('crbug.com/638180')) + return error; + error.logs = [ + `Chromium sandboxing failed!`, + `================================`, + `To avoid the sandboxing issue, do either of the following:`, + ` - (preferred): Configure your environment to support sandboxing`, + ` - (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option`, + `================================`, + ``, + ].join('\n'); + return error; + } + + override amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { + return env; + } + + override attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void { + const bidiTransport = (transport as any)[kBidiOverCdpWrapper]; + if (bidiTransport) + transport = bidiTransport; + transport.send({ method: 'browser.close', params: {}, id: kBrowserCloseMessageId }); + } + + override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] { + const chromeArguments = this._innerDefaultArgs(options); + chromeArguments.push(`--user-data-dir=${userDataDir}`); + chromeArguments.push('--remote-debugging-port=0'); + if (isPersistent) + chromeArguments.push('about:blank'); + else + chromeArguments.push('--no-startup-window'); + return chromeArguments; + } + + override readyState(options: types.LaunchOptions): BrowserReadyState | undefined { + assert(options.useWebSocket); + return new ChromiumReadyState(); + } + + private _innerDefaultArgs(options: types.LaunchOptions): string[] { + const { args = [], proxy } = options; + const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); + if (userDataDirArg) + throw this._createUserDataDirArgMisuseError('--user-data-dir'); + if (args.find(arg => arg.startsWith('--remote-debugging-pipe'))) + throw new Error('Playwright manages remote debugging connection itself.'); + if (args.find(arg => !arg.startsWith('-'))) + throw new Error('Arguments can not specify page to be opened'); + const chromeArguments = [...chromiumSwitches]; + + if (os.platform() === 'darwin') { + // See https://github.com/microsoft/playwright/issues/7362 + chromeArguments.push('--enable-use-zoom-for-dsf=false'); + // See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025. + if (options.headless) + chromeArguments.push('--use-angle'); + } + + if (options.devtools) + chromeArguments.push('--auto-open-devtools-for-tabs'); + if (options.headless) { + if (process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW) + chromeArguments.push('--headless=new'); + else + chromeArguments.push('--headless=old'); + + chromeArguments.push( + '--hide-scrollbars', + '--mute-audio', + '--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4', + ); + } + if (options.chromiumSandbox !== true) + chromeArguments.push('--no-sandbox'); + if (proxy) { + const proxyURL = new URL(proxy.server); + const isSocks = proxyURL.protocol === 'socks5:'; + // https://www.chromium.org/developers/design-documents/network-settings + if (isSocks && !this.attribution.playwright.options.socksProxyPort) { + // https://www.chromium.org/developers/design-documents/network-stack/socks-proxy + chromeArguments.push(`--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE ${proxyURL.hostname}"`); + } + chromeArguments.push(`--proxy-server=${proxy.server}`); + const proxyBypassRules = []; + // https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578 + if (this.attribution.playwright.options.socksProxyPort) + proxyBypassRules.push('<-loopback>'); + if (proxy.bypass) + proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t)); + if (!process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK && !proxyBypassRules.includes('<-loopback>')) + proxyBypassRules.push('<-loopback>'); + if (proxyBypassRules.length > 0) + chromeArguments.push(`--proxy-bypass-list=${proxyBypassRules.join(';')}`); + } + chromeArguments.push(...args); + return chromeArguments; + } +} + +class ChromiumReadyState extends BrowserReadyState { + override onBrowserOutput(message: string): void { + const match = message.match(/DevTools listening on (.*)/); + if (match) + this._wsEndpoint.resolve(match[1]); + } +} + +const kBidiOverCdpWrapper = Symbol('kBidiConnectionWrapper'); diff --git a/packages/playwright-core/src/server/bidi/bidiConnection.ts b/packages/playwright-core/src/server/bidi/bidiConnection.ts new file mode 100644 index 0000000000..7138f2e06a --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiConnection.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 { EventEmitter } from 'events'; +import { assert } from '../../utils'; +import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; +import type { RecentLogsCollector } from '../../utils/debugLogger'; +import { debugLogger } from '../../utils/debugLogger'; +import type { ProtocolLogger } from '../types'; +import { helper } from '../helper'; +import { ProtocolError } from '../protocolError'; +import type * as bidi from './third_party/bidiProtocol'; +import type * as bidiCommands from './third_party/bidiCommands'; + +// BidiPlaywright uses this special id to issue Browser.close command which we +// should ignore. +export const kBrowserCloseMessageId = 0; + +export class BidiConnection { + private readonly _transport: ConnectionTransport; + private readonly _onDisconnect: () => void; + private readonly _protocolLogger: ProtocolLogger; + private readonly _browserLogsCollector: RecentLogsCollector; + _browserDisconnectedLogs: string | undefined; + private _lastId = 0; + private _closed = false; + readonly browserSession: BidiSession; + readonly _browsingContextToSession = new Map(); + + constructor(transport: ConnectionTransport, onDisconnect: () => void, protocolLogger: ProtocolLogger, browserLogsCollector: RecentLogsCollector) { + this._transport = transport; + this._onDisconnect = onDisconnect; + this._protocolLogger = protocolLogger; + this._browserLogsCollector = browserLogsCollector; + this.browserSession = new BidiSession(this, '', (message: any) => { + this.rawSend(message); + }); + this._transport.onmessage = this._dispatchMessage.bind(this); + // onclose should be set last, since it can be immediately called. + this._transport.onclose = this._onClose.bind(this); + } + + nextMessageId(): number { + return ++this._lastId; + } + + rawSend(message: ProtocolRequest) { + this._protocolLogger('send', message); + this._transport.send(message); + } + + private _dispatchMessage(message: ProtocolResponse) { + this._protocolLogger('receive', message); + const object = message as bidi.Message; + // Bidi messages do not have a common session identifier, so we + // route them based on BrowsingContext. + if (object.type === 'event') { + // Route page events to the right session. + let context; + if ('context' in object.params) + context = object.params.context; + else if (object.method === 'log.entryAdded') + context = object.params.source?.context; + if (context) { + const session = this._browsingContextToSession.get(context); + if (session) { + session.dispatchMessage(message); + return; + } + } + } else if (message.id) { + // Find caller session. + for (const session of this._browsingContextToSession.values()) { + if (session.hasCallback(message.id)) { + session.dispatchMessage(message); + return; + } + } + } + this.browserSession.dispatchMessage(message); + } + + _onClose(reason?: string) { + this._closed = true; + this._transport.onmessage = undefined; + this._transport.onclose = undefined; + this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason); + this.browserSession.dispose(); + this._onDisconnect(); + } + + isClosed() { + return this._closed; + } + + close() { + if (!this._closed) + this._transport.close(); + } + + createMainFrameBrowsingContextSession(bowsingContextId: bidi.BrowsingContext.BrowsingContext): BidiSession { + const result = new BidiSession(this, bowsingContextId, message => this.rawSend(message)); + this._browsingContextToSession.set(bowsingContextId, result); + return result; + } +} + +type BidiEvents = { + [K in bidi.Event['method']]: Extract; +}; + +export class BidiSession extends EventEmitter { + readonly connection: BidiConnection; + readonly sessionId: string; + + private _disposed = false; + private readonly _rawSend: (message: any) => void; + private readonly _callbacks = new Map void, reject: (e: ProtocolError) => void, error: ProtocolError }>(); + private _crashed: boolean = false; + private readonly _browsingContexts = new Set(); + + override on: (event: T, listener: (payload: T extends symbol ? any : BidiEvents[T extends keyof BidiEvents ? T : never]['params']) => void) => this; + override addListener: (event: T, listener: (payload: T extends symbol ? any : BidiEvents[T extends keyof BidiEvents ? T : never]['params']) => void) => this; + override off: (event: T, listener: (payload: T extends symbol ? any : BidiEvents[T extends keyof BidiEvents ? T : never]['params']) => void) => this; + override removeListener: (event: T, listener: (payload: T extends symbol ? any : BidiEvents[T extends keyof BidiEvents ? T : never]['params']) => void) => this; + override once: (event: T, listener: (payload: T extends symbol ? any : BidiEvents[T extends keyof BidiEvents ? T : never]['params']) => void) => this; + + constructor(connection: BidiConnection, sessionId: string, rawSend: (message: any) => void) { + super(); + this.setMaxListeners(0); + this.connection = connection; + this.sessionId = sessionId; + this._rawSend = rawSend; + + this.on = super.on; + this.off = super.removeListener; + this.addListener = super.addListener; + this.removeListener = super.removeListener; + this.once = super.once; + } + + addFrameBrowsingContext(context: string) { + this._browsingContexts.add(context); + this.connection._browsingContextToSession.set(context, this); + } + + removeFrameBrowsingContext(context: string) { + this._browsingContexts.delete(context); + this.connection._browsingContextToSession.delete(context); + } + + async send( + method: T, + params?: bidiCommands.Commands[T]['params'] + ): Promise { + if (this._crashed || this._disposed || this.connection._browserDisconnectedLogs) + throw new ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this.connection._browserDisconnectedLogs); + const id = this.connection.nextMessageId(); + const messageObj = { id, method, params }; + this._rawSend(messageObj); + return new Promise((resolve, reject) => { + this._callbacks.set(id, { resolve, reject, error: new ProtocolError('error', method) }); + }); + } + + sendMayFail(method: T, params?: bidiCommands.Commands[T]['params']): Promise { + return this.send(method, params).catch(error => debugLogger.log('error', error)); + } + + markAsCrashed() { + this._crashed = true; + } + + isDisposed(): boolean { + return this._disposed; + } + + dispose() { + this._disposed = true; + this.connection._browsingContextToSession.delete(this.sessionId); + for (const context of this._browsingContexts) + this.connection._browsingContextToSession.delete(context); + this._browsingContexts.clear(); + for (const callback of this._callbacks.values()) { + callback.error.type = this._crashed ? 'crashed' : 'closed'; + callback.error.logs = this.connection._browserDisconnectedLogs; + callback.reject(callback.error); + } + this._callbacks.clear(); + } + + hasCallback(id: number): boolean { + return this._callbacks.has(id); + } + + dispatchMessage(message: any) { + const object = message as bidi.Message; + if (object.id === kBrowserCloseMessageId) + return; + if (object.id && this._callbacks.has(object.id)) { + const callback = this._callbacks.get(object.id)!; + this._callbacks.delete(object.id); + if (object.type === 'error') { + callback.error.setMessage(object.error + '\nMessage: ' + object.message); + callback.reject(callback.error); + } else if (object.type === 'success') { + callback.resolve(object.result); + } else { + callback.error.setMessage('Internal error, unexpected response type: ' + JSON.stringify(object)); + callback.reject(callback.error); + } + } else if (object.id) { + // Response might come after session has been disposed and rejected all callbacks. + assert(this.isDisposed()); + } else { + Promise.resolve().then(() => this.emit(object.method, object.params)); + } + } +} diff --git a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts new file mode 100644 index 0000000000..eaacb629e6 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts @@ -0,0 +1,167 @@ +/** + * 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 { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; +import * as js from '../javascript'; +import type { BidiSession } from './bidiConnection'; +import { BidiDeserializer } from './third_party/bidiDeserializer'; +import * as bidi from './third_party/bidiProtocol'; +import { BidiSerializer } from './third_party/bidiSerializer'; + +export class BidiExecutionContext implements js.ExecutionContextDelegate { + private readonly _session: BidiSession; + private readonly _target: bidi.Script.Target; + + constructor(session: BidiSession, realmInfo: bidi.Script.RealmInfo) { + this._session = session; + if (realmInfo.type === 'window') { + // Simple realm does not seem to work for Window contexts. + this._target = { + context: realmInfo.context, + sandbox: realmInfo.sandbox, + }; + } else { + this._target = { + realm: realmInfo.realm + }; + } + } + + async rawEvaluateJSON(expression: string): Promise { + const response = await this._session.send('script.evaluate', { + expression, + target: this._target, + serializationOptions: { + maxObjectDepth: 10, + maxDomDepth: 10, + }, + awaitPromise: true, + userActivation: true, + }); + if (response.type === 'success') + return BidiDeserializer.deserialize(response.result); + if (response.type === 'exception') + throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails)); + throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)); + } + + async rawEvaluateHandle(expression: string): Promise { + const response = await this._session.send('script.evaluate', { + expression, + target: this._target, + resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned. + serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 }, + awaitPromise: true, + userActivation: true, + }); + if (response.type === 'success') { + if ('handle' in response.result) + return response.result.handle!; + throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result)); + } + if (response.type === 'exception') + throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails)); + throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)); + } + + rawCallFunctionNoReply(func: Function, ...args: any[]) { + throw new Error('Method not implemented.'); + } + + async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], objectIds: string[]): Promise { + const response = await this._session.send('script.callFunction', { + functionDeclaration, + target: this._target, + arguments: [ + { handle: utilityScript._objectId! }, + ...values.map(BidiSerializer.serialize), + ...objectIds.map(handle => ({ handle })), + ], + resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned. + serializationOptions: returnByValue ? {} : { maxObjectDepth: 0, maxDomDepth: 0 }, + awaitPromise: true, + userActivation: true, + }); + if (response.type === 'exception') + throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails)); + if (response.type === 'success') { + if (returnByValue) + return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result)); + const objectId = 'handle' in response.result ? response.result.handle : undefined ; + return utilityScript._context.createHandle({ objectId, ...response.result }); + } + throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)); + } + + async getProperties(context: js.ExecutionContext, objectId: js.ObjectId): Promise> { + throw new Error('Method not implemented.'); + } + + createHandle(context: js.ExecutionContext, jsRemoteObject: js.RemoteObject): js.JSHandle { + const remoteObject: bidi.Script.RemoteValue = jsRemoteObject as bidi.Script.RemoteValue; + return new js.JSHandle(context, remoteObject.type, renderPreview(remoteObject), jsRemoteObject.objectId, remoteObjectValue(remoteObject)); + } + + async releaseHandle(objectId: js.ObjectId): Promise { + await this._session.send('script.disown', { + target: this._target, + handles: [objectId], + }); + } + + objectCount(objectId: js.ObjectId): Promise { + throw new Error('Method not implemented.'); + } + + async rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise { + const response = await this._session.send('script.callFunction', { + functionDeclaration, + target: this._target, + arguments: [arg], + resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned. + serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 }, + awaitPromise: true, + userActivation: true, + }); + if (response.type === 'exception') + throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails)); + if (response.type === 'success') + return response.result; + throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)); + } +} + +function renderPreview(remoteObject: bidi.Script.RemoteValue): string | undefined { + if (remoteObject.type === 'undefined') + return 'undefined'; + if (remoteObject.type === 'null') + return 'null'; + if ('value' in remoteObject) + return String(remoteObject.value); + return `<${remoteObject.type}>`; +} + +function remoteObjectValue(remoteObject: bidi.Script.RemoteValue): any { + if (remoteObject.type === 'undefined') + return undefined; + if (remoteObject.type === 'null') + return null; + if (remoteObject.type === 'number' && typeof remoteObject.value === 'string') + return js.parseUnserializableValue(remoteObject.value); + if ('value' in remoteObject) + return remoteObject.value; + return undefined; +} diff --git a/packages/playwright-core/src/server/bidi/bidiFirefox.ts b/packages/playwright-core/src/server/bidi/bidiFirefox.ts new file mode 100644 index 0000000000..3a2da48c25 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiFirefox.ts @@ -0,0 +1,101 @@ +/** + * 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 os from 'os'; +import path from 'path'; +import { assert, wrapInASCIIBox } from '../../utils'; +import type { Env } from '../../utils/processLauncher'; +import type { BrowserOptions } from '../browser'; +import { BrowserReadyState, BrowserType, kNoXServerRunningError } from '../browserType'; +import type { SdkObject } from '../instrumentation'; +import type { ProtocolError } from '../protocolError'; +import type { ConnectionTransport } from '../transport'; +import type * as types from '../types'; +import { BidiBrowser } from './bidiBrowser'; +import { kBrowserCloseMessageId } from './bidiConnection'; + +export class BidiFirefox extends BrowserType { + constructor(parent: SdkObject) { + super(parent, 'bidi'); + this._useBidi = true; + } + + override async connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { + return BidiBrowser.connect(this.attribution.playwright, transport, options); + } + + override doRewriteStartupLog(error: ProtocolError): ProtocolError { + if (!error.logs) + return error; + // https://github.com/microsoft/playwright/issues/6500 + if (error.logs.includes(`as root in a regular user's session is not supported.`)) + error.logs = '\n' + wrapInASCIIBox(`Firefox is unable to launch if the $HOME folder isn't owned by the current user.\nWorkaround: Set the HOME=/root environment variable${process.env.GITHUB_ACTION ? ' in your GitHub Actions workflow file' : ''} when running Playwright.`, 1); + if (error.logs.includes('no DISPLAY environment variable specified')) + error.logs = '\n' + wrapInASCIIBox(kNoXServerRunningError, 1); + return error; + } + + override amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { + if (!path.isAbsolute(os.homedir())) + throw new Error(`Cannot launch Firefox with relative home directory. Did you set ${os.platform() === 'win32' ? 'USERPROFILE' : 'HOME'} to a relative path?`); + if (os.platform() === 'linux') { + // Always remove SNAP_NAME and SNAP_INSTANCE_NAME env variables since they + // confuse Firefox: in our case, builds never come from SNAP. + // See https://github.com/microsoft/playwright/issues/20555 + return { ...env, SNAP_NAME: undefined, SNAP_INSTANCE_NAME: undefined }; + } + return env; + } + + override attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void { + transport.send({ method: 'browser.close', params: {}, id: kBrowserCloseMessageId }); + } + + override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] { + const { args = [], headless } = options; + const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); + if (userDataDirArg) + throw this._createUserDataDirArgMisuseError('--profile'); + const firefoxArguments = ['--remote-debugging-port=0']; + if (headless) + firefoxArguments.push('--headless'); + else + firefoxArguments.push('--foreground'); + firefoxArguments.push(`--profile`, userDataDir); + firefoxArguments.push(...args); + // TODO: make ephemeral context work without this argument. + firefoxArguments.push('about:blank'); + // if (isPersistent) + // firefoxArguments.push('about:blank'); + // else + // firefoxArguments.push('-silent'); + return firefoxArguments; + } + + override readyState(options: types.LaunchOptions): BrowserReadyState | undefined { + assert(options.useWebSocket); + return new FirefoxReadyState(); + } +} + +class FirefoxReadyState extends BrowserReadyState { + override onBrowserOutput(message: string): void { + // Bidi WebSocket in Firefox. + const match = message.match(/WebDriver BiDi listening on (ws:\/\/.*)$/); + if (match) + this._wsEndpoint.resolve(match[1] + '/session'); + } +} diff --git a/packages/playwright-core/src/server/bidi/bidiInput.ts b/packages/playwright-core/src/server/bidi/bidiInput.ts new file mode 100644 index 0000000000..3550051a6a --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiInput.ts @@ -0,0 +1,144 @@ +/** + * 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 input from '../input'; +import type * as types from '../types'; +import type { BidiSession } from './bidiConnection'; +import * as bidi from './third_party/bidiProtocol'; +import { getBidiKeyValue } from './third_party/bidiKeyboard'; + +export class RawKeyboardImpl implements input.RawKeyboard { + private _session: BidiSession; + + constructor(session: BidiSession) { + this._session = session; + } + + setSession(session: BidiSession) { + this._session = session; + } + + async keydown(modifiers: Set, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise { + const actions: bidi.Input.KeySourceAction[] = []; + actions.push({ type: 'keyDown', value: getBidiKeyValue(key) }); + // TODO: add modifiers? + await this._performActions(actions); + } + + async keyup(modifiers: Set, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise { + const actions: bidi.Input.KeySourceAction[] = []; + actions.push({ type: 'keyUp', value: getBidiKeyValue(key) }); + await this._performActions(actions); + } + + async sendText(text: string): Promise { + const actions: bidi.Input.KeySourceAction[] = []; + for (const char of text) { + const value = getBidiKeyValue(char); + actions.push({ type: 'keyDown', value }); + actions.push({ type: 'keyUp', value }); + } + await this._performActions(actions); + } + + private async _performActions(actions: bidi.Input.KeySourceAction[]) { + await this._session.send('input.performActions', { + context: this._session.sessionId, + actions: [ + { + type: 'key', + id: 'pw_keyboard', + actions, + } + ] + }); + } +} + +export class RawMouseImpl implements input.RawMouse { + private readonly _session: BidiSession; + + constructor(session: BidiSession) { + this._session = session; + } + + async move(x: number, y: number, button: types.MouseButton | 'none', buttons: Set, modifiers: Set, forClick: boolean): Promise { + // Bidi throws when x/y are not integers. + x = Math.round(x); + y = Math.round(y); + await this._performActions([{ type: 'pointerMove', x, y }]); + } + + async down(x: number, y: number, button: types.MouseButton, buttons: Set, modifiers: Set, clickCount: number): Promise { + await this._performActions([{ type: 'pointerDown', button: toBidiButton(button) }]); + } + + async up(x: number, y: number, button: types.MouseButton, buttons: Set, modifiers: Set, clickCount: number): Promise { + await this._performActions([{ type: 'pointerUp', button: toBidiButton(button) }]); + } + + async wheel(x: number, y: number, buttons: Set, modifiers: Set, deltaX: number, deltaY: number): Promise { + // Bidi throws when x/y are not integers. + x = Math.round(x); + y = Math.round(y); + await this._session.send('input.performActions', { + context: this._session.sessionId, + actions: [ + { + type: 'wheel', + id: 'pw_mouse_wheel', + actions: [{ type: 'scroll', x, y, deltaX, deltaY }], + } + ] + }); + } + + private async _performActions(actions: bidi.Input.PointerSourceAction[]) { + await this._session.send('input.performActions', { + context: this._session.sessionId, + actions: [ + { + type: 'pointer', + id: 'pw_mouse', + parameters: { + pointerType: bidi.Input.PointerType.Mouse, + }, + actions, + } + ] + }); + } +} + +export class RawTouchscreenImpl implements input.RawTouchscreen { + private readonly _session: BidiSession; + + constructor(session: BidiSession) { + this._session = session; + } + + async tap(x: number, y: number, modifiers: Set) { + } +} + +function toBidiButton(button: string): number { + switch (button) { + case 'left': return 0; + case 'right': return 2; + case 'middle': return 1; + } + throw new Error('Unknown button: ' + button); +} diff --git a/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts b/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts new file mode 100644 index 0000000000..00846b124a --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts @@ -0,0 +1,316 @@ +/** + * 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 { RegisteredListener } from '../../utils/eventsHelper'; +import { eventsHelper } from '../../utils/eventsHelper'; +import type { Page } from '../page'; +import * as network from '../network'; +import type * as frames from '../frames'; +import type * as types from '../types'; +import * as bidi from './third_party/bidiProtocol'; +import type { BidiSession } from './bidiConnection'; + + +export class BidiNetworkManager { + private readonly _session: BidiSession; + private readonly _requests: Map; + private readonly _page: Page; + private readonly _eventListeners: RegisteredListener[]; + private readonly _onNavigationResponseStarted: (params: bidi.Network.ResponseStartedParameters) => void; + private _userRequestInterceptionEnabled: boolean = false; + private _protocolRequestInterceptionEnabled: boolean = false; + private _credentials: types.Credentials | undefined; + private _intercepId: bidi.Network.Intercept | undefined; + + constructor(bidiSession: BidiSession, page: Page, onNavigationResponseStarted: (params: bidi.Network.ResponseStartedParameters) => void) { + this._session = bidiSession; + this._requests = new Map(); + this._page = page; + this._onNavigationResponseStarted = onNavigationResponseStarted; + this._eventListeners = [ + eventsHelper.addEventListener(bidiSession, 'network.beforeRequestSent', this._onBeforeRequestSent.bind(this)), + eventsHelper.addEventListener(bidiSession, 'network.responseStarted', this._onResponseStarted.bind(this)), + eventsHelper.addEventListener(bidiSession, 'network.responseCompleted', this._onResponseCompleted.bind(this)), + eventsHelper.addEventListener(bidiSession, 'network.fetchError', this._onFetchError.bind(this)), + eventsHelper.addEventListener(bidiSession, 'network.authRequired', this._onAuthRequired.bind(this)), + ]; + } + + dispose() { + eventsHelper.removeEventListeners(this._eventListeners); + } + + private _onBeforeRequestSent(param: bidi.Network.BeforeRequestSentParameters) { + if (param.request.url.startsWith('data:')) + return; + const redirectedFrom = param.redirectCount ? (this._requests.get(param.request.request) || null) : null; + const frame = redirectedFrom ? redirectedFrom.request.frame() : (param.context ? this._page._frameManager.frame(param.context) : null); + if (!frame) + return; + if (redirectedFrom) + this._requests.delete(redirectedFrom._id); + let route; + if (param.intercepts) { + // We do not support intercepting redirects. + if (redirectedFrom) { + this._session.sendMayFail('network.continueRequest', { + request: param.request.request, + headers: redirectedFrom._originalRequestRoute?._alreadyContinuedHeaders, + }); + } else { + route = new BidiRouteImpl(this._session, param.request.request); + } + } + const request = new BidiRequest(frame, redirectedFrom, param, route); + this._requests.set(request._id, request); + this._page._frameManager.requestStarted(request.request, route); + } + + private _onResponseStarted(params: bidi.Network.ResponseStartedParameters) { + const request = this._requests.get(params.request.request); + if (!request) + return; + const getResponseBody = async () => { + throw new Error(`Response body is not available for requests in Bidi`); + }; + const timings = params.request.timings; + const startTime = timings.requestTime; + function relativeToStart(time: number): number { + if (!time) + return -1; + return (time - startTime) / 1000; + } + const timing: network.ResourceTiming = { + startTime: startTime / 1000, + requestStart: relativeToStart(timings.requestStart), + responseStart: relativeToStart(timings.responseStart), + domainLookupStart: relativeToStart(timings.dnsStart), + domainLookupEnd: relativeToStart(timings.dnsEnd), + connectStart: relativeToStart(timings.connectStart), + secureConnectionStart: relativeToStart(timings.tlsStart), + connectEnd: relativeToStart(timings.connectEnd), + }; + const response = new network.Response(request.request, params.response.status, params.response.statusText, fromBidiHeaders(params.response.headers), timing, getResponseBody, false); + response._serverAddrFinished(); + response._securityDetailsFinished(); + // "raw" headers are the same as "provisional" headers in Bidi. + response.setRawResponseHeaders(null); + response.setResponseHeadersSize(params.response.headersSize); + this._page._frameManager.requestReceivedResponse(response); + if (params.navigation) + this._onNavigationResponseStarted(params); + } + + private _onResponseCompleted(params: bidi.Network.ResponseCompletedParameters) { + const request = this._requests.get(params.request.request); + if (!request) + return; + const response = request.request._existingResponse()!; + // TODO: body size is the encoded size + response.setTransferSize(params.response.bodySize); + response.setEncodedBodySize(params.response.bodySize); + + // Keep redirected requests in the map for future reference as redirectedFrom. + const isRedirected = response.status() >= 300 && response.status() <= 399; + const responseEndTime = params.request.timings.responseEnd / 1000 - response.timing().startTime; + if (isRedirected) { + response._requestFinished(responseEndTime); + } else { + this._requests.delete(request._id); + response._requestFinished(responseEndTime); + } + response._setHttpVersion(params.response.protocol); + this._page._frameManager.reportRequestFinished(request.request, response); + + } + + private _onFetchError(params: bidi.Network.FetchErrorParameters) { + const request = this._requests.get(params.request.request); + if (!request) + return; + this._requests.delete(request._id); + const response = request.request._existingResponse(); + if (response) { + response.setTransferSize(null); + response.setEncodedBodySize(null); + response._requestFinished(-1); + } + request.request._setFailureText(params.errorText); + // TODO: support canceled flag + this._page._frameManager.requestFailed(request.request, params.errorText === 'NS_BINDING_ABORTED'); + } + + private _onAuthRequired(params: bidi.Network.AuthRequiredParameters) { + const isBasic = params.response.authChallenges?.some(challenge => challenge.scheme.startsWith('Basic')); + const credentials = this._page._browserContext._options.httpCredentials; + if (isBasic && credentials) { + this._session.sendMayFail('network.continueWithAuth', { + request: params.request.request, + action: 'provideCredentials', + credentials: { + type: 'password', + username: credentials.username, + password: credentials.password, + } + }); + } else { + this._session.sendMayFail('network.continueWithAuth', { + request: params.request.request, + action: 'default', + }); + } + } + + async setRequestInterception(value: boolean) { + this._userRequestInterceptionEnabled = value; + await this._updateProtocolRequestInterception(); + } + + async setCredentials(credentials: types.Credentials | undefined) { + this._credentials = credentials; + await this._updateProtocolRequestInterception(); + } + + async _updateProtocolRequestInterception(initial?: boolean) { + const enabled = this._userRequestInterceptionEnabled || !!this._credentials; + if (enabled === this._protocolRequestInterceptionEnabled) + return; + this._protocolRequestInterceptionEnabled = enabled; + if (initial && !enabled) + return; + const cachePromise = this._session.send('network.setCacheBehavior', { cacheBehavior: enabled ? 'bypass' : 'default' }); + let interceptPromise = Promise.resolve(undefined); + if (enabled) { + interceptPromise = this._session.send('network.addIntercept', { + phases: [bidi.Network.InterceptPhase.AuthRequired, bidi.Network.InterceptPhase.BeforeRequestSent], + urlPatterns: [{ type: 'pattern' }], + // urlPatterns: [{ type: 'string', pattern: '*' }], + }).then(r => { + this._intercepId = r.intercept; + }); + } else if (this._intercepId) { + interceptPromise = this._session.send('network.removeIntercept', { intercept: this._intercepId }); + this._intercepId = undefined; + } + await Promise.all([cachePromise, interceptPromise]); + } +} + + +class BidiRequest { + readonly request: network.Request; + readonly _id: string; + private _redirectedTo: BidiRequest | undefined; + // Only first request in the chain can be intercepted, so this will + // store the first and only Route in the chain (if any). + _originalRequestRoute: BidiRouteImpl | undefined; + + constructor(frame: frames.Frame, redirectedFrom: BidiRequest | null, payload: bidi.Network.BeforeRequestSentParameters, route: BidiRouteImpl | undefined) { + this._id = payload.request.request; + if (redirectedFrom) + redirectedFrom._redirectedTo = this; + // TODO: missing in the spec? + const postDataBuffer = null; + this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigation ?? undefined, + payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers)); + // "raw" headers are the same as "provisional" headers in Bidi. + this.request.setRawRequestHeaders(null); + this.request._setBodySize(payload.request.bodySize || 0); + this._originalRequestRoute = route ?? redirectedFrom?._originalRequestRoute; + route?._setRequest(this.request); + } + + _finalRequest(): BidiRequest { + let request: BidiRequest = this; + while (request._redirectedTo) + request = request._redirectedTo; + return request; + } +} + +class BidiRouteImpl implements network.RouteDelegate { + private _requestId: bidi.Network.Request; + private _session: BidiSession; + private _request!: network.Request; + _alreadyContinuedHeaders: bidi.Network.Header[] | undefined; + + constructor(session: BidiSession, requestId: bidi.Network.Request) { + this._session = session; + this._requestId = requestId; + } + + _setRequest(request: network.Request) { + this._request = request; + } + + async continue(overrides: types.NormalizedContinueOverrides) { + // Firefox does not update content-length header. + let headers = overrides.headers || this._request.headers(); + if (overrides.postData && headers) { + headers = headers.map(header => { + if (header.name.toLowerCase() === 'content-length') + return { name: header.name, value: overrides.postData!.byteLength.toString() }; + return header; + }); + } + this._alreadyContinuedHeaders = toBidiHeaders(headers); + await this._session.sendMayFail('network.continueRequest', { + request: this._requestId, + url: overrides.url, + method: overrides.method, + // TODO: cookies! + headers: this._alreadyContinuedHeaders, + body: overrides.postData ? { type: 'base64', value: Buffer.from(overrides.postData).toString('base64') } : undefined, + }); + } + + async fulfill(response: types.NormalizedFulfillResponse) { + const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64'); + await this._session.sendMayFail('network.provideResponse', { + request: this._requestId, + statusCode: response.status, + reasonPhrase: network.statusText(response.status), + headers: toBidiHeaders(response.headers), + body: { type: 'base64', value: base64body }, + }); + } + + async abort(errorCode: string) { + await this._session.sendMayFail('network.failRequest', { + request: this._requestId + }); + } +} + +function fromBidiHeaders(bidiHeaders: bidi.Network.Header[]): types.HeadersArray { + const result: types.HeadersArray = []; + for (const { name, value } of bidiHeaders) + result.push({ name, value: bidiBytesValueToString(value) }); + return result; +} + +function toBidiHeaders(headers: types.HeadersArray): bidi.Network.Header[] { + return headers.map(({ name, value }) => ({ name, value: { type: 'string', value } })); +} + +export function bidiBytesValueToString(value: bidi.Network.BytesValue): string { + if (value.type === 'string') + return value.value; + if (value.type === 'base64') + return Buffer.from(value.type, 'base64').toString('binary'); + return 'unknown value type: ' + (value as any).type; + +} diff --git a/packages/playwright-core/src/server/bidi/bidiOverCdp.ts b/packages/playwright-core/src/server/bidi/bidiOverCdp.ts new file mode 100644 index 0000000000..1d01317c1e --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiOverCdp.ts @@ -0,0 +1,100 @@ +/** + * 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 * as bidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/BidiMapper'; +import * as bidiCdpConnection from 'chromium-bidi/lib/cjs/cdp/CdpConnection'; +import type * as bidiTransport from 'chromium-bidi/lib/cjs/utils/transport'; +import type { ChromiumBidi } from 'chromium-bidi/lib/cjs/protocol/protocol'; +import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; +import { debugLogger } from '../../utils/debugLogger'; + +const bidiServerLogger = (prefix: string, ...args: unknown[]): void => { + debugLogger.log(prefix as any, args); +}; + +export async function connectBidiOverCdp(cdp: ConnectionTransport): Promise { + let server: bidiMapper.BidiServer | undefined = undefined; + const bidiTransport = new BidiTransportImpl(); + const bidiConnection = new BidiConnection(bidiTransport, () => server?.close()); + const cdpTransportImpl = new CdpTransportImpl(cdp); + const cdpConnection = new bidiCdpConnection.MapperCdpConnection(cdpTransportImpl, bidiServerLogger); + // Make sure onclose event is propagated. + cdp.onclose = () => bidiConnection.onclose?.(); + server = await bidiMapper.BidiServer.createAndStart( + bidiTransport, + cdpConnection, + await cdpConnection.createBrowserSession(), + /* selfTargetId= */ '', + undefined, + bidiServerLogger); + return bidiConnection; +} + +class BidiTransportImpl implements bidiMapper.BidiTransport { + _handler?: (message: ChromiumBidi.Command) => Promise | void; + _bidiConnection!: BidiConnection; + + setOnMessage(handler: (message: ChromiumBidi.Command) => Promise | void) { + this._handler = handler; + } + sendMessage(message: ChromiumBidi.Message): Promise | void { + return this._bidiConnection.onmessage?.(message as any); + } + close() { + this._bidiConnection.onclose?.(); + } +} + +class BidiConnection implements ConnectionTransport { + private _bidiTransport: BidiTransportImpl; + private _closeCallback: () => void; + + constructor(bidiTransport: BidiTransportImpl, closeCallback: () => void) { + this._bidiTransport = bidiTransport; + this._bidiTransport._bidiConnection = this; + this._closeCallback = closeCallback; + } + send(s: ProtocolRequest): void { + this._bidiTransport._handler?.(s as any); + } + close(): void { + this._closeCallback(); + } + onmessage?: ((message: ProtocolResponse) => void) | undefined; + onclose?: ((reason?: string) => void) | undefined; +} + +class CdpTransportImpl implements bidiTransport.Transport { + private _connection: ConnectionTransport; + private _handler?: (message: string) => Promise | void; + _bidiConnection!: BidiConnection; + + constructor(connection: ConnectionTransport) { + this._connection = connection; + this._connection.onmessage = message => { + this._handler?.(JSON.stringify(message)); + }; + } + setOnMessage(handler: (message: string) => Promise | void) { + this._handler = handler; + } + sendMessage(message: string): Promise | void { + return this._connection.send(JSON.parse(message)); + } + close(): void { + this._connection.close(); + } +} diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts new file mode 100644 index 0000000000..f06924d70f --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -0,0 +1,525 @@ +/** + * 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 { RegisteredListener } from '../../utils/eventsHelper'; +import { eventsHelper } from '../../utils/eventsHelper'; +import { assert } from '../../utils'; +import type * as accessibility from '../accessibility'; +import * as dom from '../dom'; +import * as dialog from '../dialog'; +import type * as frames from '../frames'; +import { type InitScript, Page, type PageDelegate } from '../page'; +import type { Progress } from '../progress'; +import type * as types from '../types'; +import type { BidiBrowserContext } from './bidiBrowser'; +import type { BidiSession } from './bidiConnection'; +import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './bidiInput'; +import * as bidi from './third_party/bidiProtocol'; +import { BidiExecutionContext } from './bidiExecutionContext'; +import { BidiNetworkManager } from './bidiNetworkManager'; +import { BrowserContext } from '../browserContext'; + +const UTILITY_WORLD_NAME = '__playwright_utility_world__'; + +export class BidiPage implements PageDelegate { + readonly rawMouse: RawMouseImpl; + readonly rawKeyboard: RawKeyboardImpl; + readonly rawTouchscreen: RawTouchscreenImpl; + readonly _page: Page; + private readonly _pagePromise: Promise; + readonly _session: BidiSession; + readonly _opener: BidiPage | null; + private readonly _realmToContext: Map; + private _sessionListeners: RegisteredListener[] = []; + readonly _browserContext: BidiBrowserContext; + readonly _networkManager: BidiNetworkManager; + _initializedPage: Page | null = null; + private _initScriptIds: string[] = []; + + constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) { + this._session = bidiSession; + this._opener = opener; + this.rawKeyboard = new RawKeyboardImpl(bidiSession); + this.rawMouse = new RawMouseImpl(bidiSession); + this.rawTouchscreen = new RawTouchscreenImpl(bidiSession); + this._realmToContext = new Map(); + this._page = new Page(this, browserContext); + this._browserContext = browserContext; + this._networkManager = new BidiNetworkManager(this._session, this._page, this._onNavigationResponseStarted.bind(this)); + this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false)); + this._sessionListeners = [ + eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationFailed', this._onNavigationFailed.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.fragmentNavigated', this._onFragmentNavigated.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.domContentLoaded', this._onDomContentLoaded.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.load', this._onLoad.bind(this)), + eventsHelper.addEventListener(bidiSession, 'browsingContext.userPromptOpened', this._onUserPromptOpened.bind(this)), + eventsHelper.addEventListener(bidiSession, 'log.entryAdded', this._onLogEntryAdded.bind(this)), + ]; + + // Initialize main frame. + this._pagePromise = this._initialize().finally(async () => { + await this._page.initOpener(this._opener); + }).then(() => { + this._initializedPage = this._page; + this._page.reportAsNew(); + return this._page; + }).catch(e => { + this._page.reportAsNew(e); + return e; + }); + } + + private async _initialize() { + // Initialize main frame. + this._onFrameAttached(this._session.sessionId, null); + await Promise.all([ + this.updateHttpCredentials(), + this.updateRequestInterception(), + this._updateViewport(), + this._addAllInitScripts(), + ]); + } + + private async _addAllInitScripts() { + return Promise.all(this._page.allInitScripts().map(initScript => this.addInitScript(initScript))); + } + + potentiallyUninitializedPage(): Page { + return this._page; + } + + didClose() { + this._session.dispose(); + eventsHelper.removeEventListeners(this._sessionListeners); + this._page._didClose(); + } + + async pageOrError(): Promise { + // TODO: Wait for first execution context to be created and maybe about:blank navigated. + return this._pagePromise; + } + + private _onFrameAttached(frameId: string, parentFrameId: string | null): frames.Frame { + return this._page._frameManager.frameAttached(frameId, parentFrameId); + } + + private _removeContextsForFrame(frame: frames.Frame, notifyFrame: boolean) { + for (const [contextId, context] of this._realmToContext) { + if (context.frame === frame) { + this._realmToContext.delete(contextId); + if (notifyFrame) + frame._contextDestroyed(context); + } + } + } + + private _onRealmCreated(realmInfo: bidi.Script.RealmInfo) { + if (this._realmToContext.has(realmInfo.realm)) + return; + if (realmInfo.type !== 'window') + return; + const frame = this._page._frameManager.frame(realmInfo.context); + if (!frame) + return; + const delegate = new BidiExecutionContext(this._session, realmInfo); + let worldName: types.World; + if (!realmInfo.sandbox) { + worldName = 'main'; + // Force creating utility world every time the main world is created (e.g. due to navigation). + this._touchUtilityWorld(realmInfo.context); + } else if (realmInfo.sandbox === UTILITY_WORLD_NAME) { + worldName = 'utility'; + } else { + return; + } + const context = new dom.FrameExecutionContext(delegate, frame, worldName); + (context as any)[contextDelegateSymbol] = delegate; + frame._contextCreated(worldName, context); + this._realmToContext.set(realmInfo.realm, context); + } + + private async _touchUtilityWorld(context: bidi.BrowsingContext.BrowsingContext) { + await this._session.sendMayFail('script.evaluate', { + expression: '1 + 1', + target: { + context, + sandbox: UTILITY_WORLD_NAME, + }, + serializationOptions: { + maxObjectDepth: 10, + maxDomDepth: 10, + }, + awaitPromise: true, + userActivation: true, + }); + } + + _onRealmDestroyed(params: bidi.Script.RealmDestroyedParameters): boolean { + const context = this._realmToContext.get(params.realm); + if (!context) + return false; + this._realmToContext.delete(params.realm); + context.frame._contextDestroyed(context); + return true; + } + + // TODO: route the message directly to the browser + private _onBrowsingContextDestroyed(params: bidi.BrowsingContext.Info) { + this._browserContext._browser._onBrowsingContextDestroyed(params); + } + + private _onNavigationStarted(params: bidi.BrowsingContext.NavigationInfo) { + const frameId = params.context; + this._page._frameManager.frameRequestedNavigation(frameId, params.navigation!); + + const url = params.url.toLowerCase(); + if (url.startsWith('file:') || url.startsWith('data:') || url === 'about:blank') { + // Navigation to file urls doesn't emit network events, so we fire 'commit' event right when navigation is started. + // Doing it in domcontentload would be too late as we'd clear frame tree. + const frame = this._page._frameManager.frame(frameId)!; + if (frame) + this._page._frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, '', params.navigation!, /* initial */ false); + } + } + + // TODO: there is no separate event for committed navigation, so we approximate it with responseStarted. + private _onNavigationResponseStarted(params: bidi.Network.ResponseStartedParameters) { + const frameId = params.context!; + const frame = this._page._frameManager.frame(frameId); + assert(frame); + this._page._frameManager.frameCommittedNewDocumentNavigation(frameId, params.response.url, '', params.navigation!, /* initial */ false); + // if (!initial) + // this._firstNonInitialNavigationCommittedFulfill(); + } + + private _onDomContentLoaded(params: bidi.BrowsingContext.NavigationInfo) { + const frameId = params.context; + this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded'); + } + + private _onLoad(params: bidi.BrowsingContext.NavigationInfo) { + this._page._frameManager.frameLifecycleEvent(params.context, 'load'); + } + + private _onNavigationAborted(params: bidi.BrowsingContext.NavigationInfo) { + this._page._frameManager.frameAbortedNavigation(params.context, 'Navigation aborted', params.navigation || undefined); + } + + private _onNavigationFailed(params: bidi.BrowsingContext.NavigationInfo) { + this._page._frameManager.frameAbortedNavigation(params.context, 'Navigation failed', params.navigation || undefined); + } + + private _onFragmentNavigated(params: bidi.BrowsingContext.NavigationInfo) { + this._page._frameManager.frameCommittedSameDocumentNavigation(params.context, params.url); + } + + private _onUserPromptOpened(event: bidi.BrowsingContext.UserPromptOpenedParameters) { + this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( + this._page, + event.type as dialog.DialogType, + event.message, + async (accept: boolean, userText?: string) => { + await this._session.send('browsingContext.handleUserPrompt', { context: event.context, accept, userText }); + }, + event.defaultValue)); + } + + private _onLogEntryAdded(params: bidi.Log.Entry) { + if (params.type !== 'console') + return; + const entry: bidi.Log.ConsoleLogEntry = params as bidi.Log.ConsoleLogEntry; + const context = this._realmToContext.get(params.source.realm); + if (!context) + return; + const callFrame = params.stackTrace?.callFrames[0]; + const location = callFrame ?? { url: '', lineNumber: 1, columnNumber: 1 }; + this._page._addConsoleMessage(entry.method, entry.args.map(arg => context.createHandle({ objectId: (arg as any).handle, ...arg })), location, params.text || undefined); + } + + async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise { + const { navigation } = await this._session.send('browsingContext.navigate', { + context: frame._id, + url, + }); + return { newDocumentId: navigation || undefined }; + } + + async updateExtraHTTPHeaders(): Promise { + } + + async updateEmulateMedia(): Promise { + } + + async updateEmulatedViewportSize(): Promise { + await this._updateViewport(); + } + + async updateUserAgent(): Promise { + } + + async bringToFront(): Promise { + } + + private async _updateViewport(): Promise { + const options = this._browserContext._options; + const deviceSize = this._page.emulatedSize(); + if (deviceSize === null) + return; + const viewportSize = deviceSize.viewport; + await this._session.send('browsingContext.setViewport', { + context: this._session.sessionId, + viewport: { + width: viewportSize.width, + height: viewportSize.height, + }, + devicePixelRatio: options.deviceScaleFactor || 1 + }); + } + + async updateRequestInterception(): Promise { + await this._networkManager.setRequestInterception(this._page.needsRequestInterception()); + } + + async updateOffline() { + } + + async updateHttpCredentials() { + await this._networkManager.setCredentials(this._browserContext._options.httpCredentials); + } + + async updateFileChooserInterception() { + } + + async reload(): Promise { + await this._session.send('browsingContext.reload', { + context: this._session.sessionId, + // ignoreCache: true, + wait: bidi.BrowsingContext.ReadinessState.Interactive, + }); + } + + goBack(): Promise { + throw new Error('Method not implemented.'); + } + + goForward(): Promise { + throw new Error('Method not implemented.'); + } + + async addInitScript(initScript: InitScript): Promise { + const { script } = await this._session.send('script.addPreloadScript', { + // TODO: remove function call from the source. + functionDeclaration: `() => { return ${initScript.source} }`, + // TODO: push to iframes? + contexts: [this._session.sessionId], + }); + if (!initScript.internal) + this._initScriptIds.push(script); + } + + async removeNonInternalInitScripts() { + const promises = this._initScriptIds.map(script => this._session.send('script.removePreloadScript', { script })); + this._initScriptIds = []; + await Promise.all(promises); + } + + async closePage(runBeforeUnload: boolean): Promise { + await this._session.send('browsingContext.close', { + context: this._session.sessionId, + promptUnload: runBeforeUnload, + }); + } + + async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise { + } + + async takeScreenshot(progress: Progress, format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise { + throw new Error('Method not implemented.'); + } + + async getContentFrame(handle: dom.ElementHandle): Promise { + const executionContext = toBidiExecutionContext(handle._context); + const contentWindow = await executionContext.rawCallFunction('e => e.contentWindow', { handle: handle._objectId }); + if (contentWindow.type === 'window') { + const frameId = contentWindow.value.context; + const result = this._page._frameManager.frame(frameId); + return result; + } + return null; + } + + async getOwnerFrame(handle: dom.ElementHandle): Promise { + throw new Error('Method not implemented.'); + } + + isElementHandle(remoteObject: bidi.Script.RemoteValue): boolean { + return remoteObject.type === 'node'; + } + + async getBoundingBox(handle: dom.ElementHandle): Promise { + const box = await handle.evaluate(element => { + if (!(element instanceof Element)) + return null; + const rect = element.getBoundingClientRect(); + return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; + }); + if (!box) + return null; + const position = await this._framePosition(handle._frame); + if (!position) + return null; + box.x += position.x; + box.y += position.y; + return box; + } + + // TODO: move to Frame. + private async _framePosition(frame: frames.Frame): Promise { + if (frame === this._page.mainFrame()) + return { x: 0, y: 0 }; + const element = await frame.frameElement(); + const box = await element.boundingBox(); + if (!box) + return null; + const style = await element.evaluateInUtility(([injected, iframe]) => injected.describeIFrameStyle(iframe as Element), {}).catch(e => 'error:notconnected' as const); + if (style === 'error:notconnected' || style === 'transformed') + return null; + // Content box is offset by border and padding widths. + box.x += style.left; + box.y += style.top; + return box; + } + + async scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'> { + return await handle.evaluateInUtility(([injected, node]) => { + node.scrollIntoView({ + block: 'center', + inline: 'center', + behavior: 'instant', + }); + }, null).then(() => 'done' as const).catch(e => { + if (e instanceof Error && e.message.includes('Node is detached from document')) + return 'error:notconnected'; + if (e instanceof Error && e.message.includes('Node does not have a layout object')) + return 'error:notvisible'; + throw e; + }); + } + + async setScreencastOptions(options: { width: number, height: number, quality: number } | null): Promise { + } + + rafCountForStablePosition(): number { + return 1; + } + + async getContentQuads(handle: dom.ElementHandle): Promise { + const quads = await handle.evaluateInUtility(([injected, node]) => { + if (!node.isConnected) + return 'error:notconnected'; + const rects = node.getClientRects(); + if (!rects) + return null; + return [...rects].map(rect => [ + { x: rect.left, y: rect.top }, + { x: rect.right, y: rect.top }, + { x: rect.right, y: rect.bottom }, + { x: rect.left, y: rect.bottom }, + ]); + }, null); + if (!quads || quads === 'error:notconnected') + return quads; + // TODO: consider transforming quads to support clicks in iframes. + const position = await this._framePosition(handle._frame); + if (!position) + return null; + quads.forEach(quad => quad.forEach(point => { + point.x += position.x; + point.y += position.y; + })); + return quads as types.Quad[]; + } + + async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { + throw new Error('Method not implemented.'); + } + + async setInputFilePaths(handle: dom.ElementHandle, paths: string[]): Promise { + throw new Error('Method not implemented.'); + } + + async adoptElementHandle(handle: dom.ElementHandle, to: dom.FrameExecutionContext): Promise> { + const fromContext = toBidiExecutionContext(handle._context); + const shared = await fromContext.rawCallFunction('x => x', { handle: handle._objectId }); + // TODO: store sharedId in the handle. + if (!('sharedId' in shared)) + throw new Error('Element is not a node'); + const sharedId = shared.sharedId!; + const executionContext = toBidiExecutionContext(to); + const result = await executionContext.rawCallFunction('x => x', { sharedId }); + if ('handle' in result) + return to.createHandle({ objectId: result.handle!, ...result }) as dom.ElementHandle; + throw new Error('Failed to adopt element handle.'); + } + + async getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> { + throw new Error('Method not implemented.'); + } + + async inputActionEpilogue(): Promise { + } + + async resetForReuse(): Promise { + } + + async getFrameElement(frame: frames.Frame): Promise { + const parent = frame.parentFrame(); + if (!parent) + throw new Error('Frame has been detached.'); + const parentContext = await parent._mainContext(); + const list = await parentContext.evaluateHandle(() => { return [...document.querySelectorAll('iframe,frame')]; }); + const length = await list.evaluate(list => list.length); + let foundElement = null; + for (let i = 0; i < length; i++) { + const element = await list.evaluateHandle((list, i) => list[i], i); + const candidate = await element.contentFrame(); + if (frame === candidate) { + foundElement = element; + break; + } else { + element.dispose(); + } + } + list.dispose(); + if (!foundElement) + throw new Error('Frame has been detached.'); + return foundElement; + } + + shouldToggleStyleSheetToSyncAnimations(): boolean { + return true; + } +} + +function toBidiExecutionContext(executionContext: dom.FrameExecutionContext): BidiExecutionContext { + return (executionContext as any)[contextDelegateSymbol] as BidiExecutionContext; +} + +const contextDelegateSymbol = Symbol('delegate'); diff --git a/packages/playwright-core/src/server/bidi/third_party/LICENSE b/packages/playwright-core/src/server/bidi/third_party/LICENSE new file mode 100644 index 0000000000..d2c171df74 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/third_party/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Google Inc. + + 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 + + https://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. diff --git a/packages/playwright-core/src/server/bidi/third_party/bidiCommands.d.ts b/packages/playwright-core/src/server/bidi/third_party/bidiCommands.d.ts new file mode 100644 index 0000000000..9242cc061d --- /dev/null +++ b/packages/playwright-core/src/server/bidi/third_party/bidiCommands.d.ts @@ -0,0 +1,176 @@ +/** + * @license + * Copyright 2024 Google Inc. + * Modifications copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Bidi from './bidiProtocol'; + +export interface Commands { + 'script.evaluate': { + params: Bidi.Script.EvaluateParameters; + returnType: Bidi.Script.EvaluateResult; + }; + 'script.callFunction': { + params: Bidi.Script.CallFunctionParameters; + returnType: Bidi.Script.EvaluateResult; + }; + 'script.disown': { + params: Bidi.Script.DisownParameters; + returnType: Bidi.EmptyResult; + }; + 'script.addPreloadScript': { + params: Bidi.Script.AddPreloadScriptParameters; + returnType: Bidi.Script.AddPreloadScriptResult; + }; + 'script.removePreloadScript': { + params: Bidi.Script.RemovePreloadScriptParameters; + returnType: Bidi.EmptyResult; + }; + + 'browser.close': { + params: Bidi.EmptyParams; + returnType: Bidi.EmptyResult; + }; + + 'browser.createUserContext': { + params: Bidi.EmptyParams; + returnType: Bidi.Browser.CreateUserContextResult; + }; + 'browser.getUserContexts': { + params: Bidi.EmptyParams; + returnType: Bidi.Browser.GetUserContextsResult; + }; + 'browser.removeUserContext': { + params: { + userContext: Bidi.Browser.UserContext; + }; + returnType: Bidi.Browser.RemoveUserContext; + }; + + 'browsingContext.activate': { + params: Bidi.BrowsingContext.ActivateParameters; + returnType: Bidi.EmptyResult; + }; + 'browsingContext.create': { + params: Bidi.BrowsingContext.CreateParameters; + returnType: Bidi.BrowsingContext.CreateResult; + }; + 'browsingContext.close': { + params: Bidi.BrowsingContext.CloseParameters; + returnType: Bidi.EmptyResult; + }; + 'browsingContext.getTree': { + params: Bidi.BrowsingContext.GetTreeParameters; + returnType: Bidi.BrowsingContext.GetTreeResult; + }; + 'browsingContext.locateNodes': { + params: Bidi.BrowsingContext.LocateNodesParameters; + returnType: Bidi.BrowsingContext.LocateNodesResult; + }; + 'browsingContext.navigate': { + params: Bidi.BrowsingContext.NavigateParameters; + returnType: Bidi.BrowsingContext.NavigateResult; + }; + 'browsingContext.reload': { + params: Bidi.BrowsingContext.ReloadParameters; + returnType: Bidi.BrowsingContext.NavigateResult; + }; + 'browsingContext.print': { + params: Bidi.BrowsingContext.PrintParameters; + returnType: Bidi.BrowsingContext.PrintResult; + }; + 'browsingContext.captureScreenshot': { + params: Bidi.BrowsingContext.CaptureScreenshotParameters; + returnType: Bidi.BrowsingContext.CaptureScreenshotResult; + }; + 'browsingContext.handleUserPrompt': { + params: Bidi.BrowsingContext.HandleUserPromptParameters; + returnType: Bidi.EmptyResult; + }; + 'browsingContext.setViewport': { + params: Bidi.BrowsingContext.SetViewportParameters; + returnType: Bidi.EmptyResult; + }; + 'browsingContext.traverseHistory': { + params: Bidi.BrowsingContext.TraverseHistoryParameters; + returnType: Bidi.EmptyResult; + }; + + 'input.performActions': { + params: Bidi.Input.PerformActionsParameters; + returnType: Bidi.EmptyResult; + }; + 'input.releaseActions': { + params: Bidi.Input.ReleaseActionsParameters; + returnType: Bidi.EmptyResult; + }; + 'input.setFiles': { + params: Bidi.Input.SetFilesParameters; + returnType: Bidi.EmptyResult; + }; + + 'session.end': { + params: Bidi.EmptyParams; + returnType: Bidi.EmptyResult; + }; + 'session.new': { + params: Bidi.Session.NewParameters; + returnType: Bidi.Session.NewResult; + }; + 'session.status': { + params: object; + returnType: Bidi.Session.StatusResult; + }; + 'session.subscribe': { + params: Bidi.Session.SubscriptionRequest; + returnType: Bidi.EmptyResult; + }; + 'session.unsubscribe': { + params: Bidi.Session.SubscriptionRequest; + returnType: Bidi.EmptyResult; + }; + + 'storage.deleteCookies': { + params: Bidi.Storage.DeleteCookiesParameters; + returnType: Bidi.Storage.DeleteCookiesResult; + }; + 'storage.getCookies': { + params: Bidi.Storage.GetCookiesParameters; + returnType: Bidi.Storage.GetCookiesResult; + }; + 'network.setCacheBehavior': { + params: Bidi.Network.SetCacheBehaviorParameters; + returnType: Bidi.EmptyResult; + }; + 'storage.setCookie': { + params: Bidi.Storage.SetCookieParameters; + returnType: Bidi.Storage.SetCookieParameters; + }; + + 'network.addIntercept': { + params: Bidi.Network.AddInterceptParameters; + returnType: Bidi.Network.AddInterceptResult; + }; + 'network.removeIntercept': { + params: Bidi.Network.RemoveInterceptParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueRequest': { + params: Bidi.Network.ContinueRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueWithAuth': { + params: Bidi.Network.ContinueWithAuthParameters; + returnType: Bidi.EmptyResult; + }; + 'network.failRequest': { + params: Bidi.Network.FailRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.provideResponse': { + params: Bidi.Network.ProvideResponseParameters; + returnType: Bidi.EmptyResult; + }; +} diff --git a/packages/playwright-core/src/server/bidi/third_party/bidiDeserializer.ts b/packages/playwright-core/src/server/bidi/third_party/bidiDeserializer.ts new file mode 100644 index 0000000000..3637b4af36 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/third_party/bidiDeserializer.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2024 Google Inc. + * Modifications copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 + */ + + +import type * as Bidi from './bidiProtocol'; + +/* eslint-disable object-curly-spacing */ + +/** + * @internal + */ +export class BidiDeserializer { + static deserialize(result: Bidi.Script.RemoteValue): any { + if (!result) + return undefined; + + switch (result.type) { + case 'array': + return result.value?.map(value => { + return BidiDeserializer.deserialize(value); + }); + case 'set': + return result.value?.reduce((acc: Set, value) => { + return acc.add(BidiDeserializer.deserialize(value)); + }, new Set()); + case 'object': + return result.value?.reduce((acc: Record, tuple) => { + const {key, value} = BidiDeserializer._deserializeTuple(tuple); + acc[key as any] = value; + return acc; + }, {}); + case 'map': + return result.value?.reduce((acc: Map, tuple) => { + const {key, value} = BidiDeserializer._deserializeTuple(tuple); + return acc.set(key, value); + }, new Map()); + case 'promise': + return {}; + case 'regexp': + return new RegExp(result.value.pattern, result.value.flags); + case 'date': + return new Date(result.value); + case 'undefined': + return undefined; + case 'null': + return null; + case 'number': + return BidiDeserializer._deserializeNumber(result.value); + case 'bigint': + return BigInt(result.value); + case 'boolean': + return Boolean(result.value); + case 'string': + return result.value; + } + + throw new Error(`Deserialization of type ${result.type} not supported.`); + } + + static _deserializeNumber(value: Bidi.Script.SpecialNumber | number): number { + switch (value) { + case '-0': + return -0; + case 'NaN': + return NaN; + case 'Infinity': + return Infinity; + case '-Infinity': + return -Infinity; + default: + return value; + } + } + + static _deserializeTuple([serializedKey, serializedValue]: [ + Bidi.Script.RemoteValue | string, + Bidi.Script.RemoteValue, + ]): {key: unknown; value: unknown} { + const key = + typeof serializedKey === 'string' + ? serializedKey + : BidiDeserializer.deserialize(serializedKey); + const value = BidiDeserializer.deserialize(serializedValue); + + return {key, value}; + } +} diff --git a/packages/playwright-core/src/server/bidi/third_party/bidiKeyboard.ts b/packages/playwright-core/src/server/bidi/third_party/bidiKeyboard.ts new file mode 100644 index 0000000000..307d83fb87 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/third_party/bidiKeyboard.ts @@ -0,0 +1,231 @@ +/** + * @license + * Copyright 2024 Google Inc. + * Modifications copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable curly */ + +export const getBidiKeyValue = (key: string) => { + switch (key) { + case '\r': + case '\n': + key = 'Enter'; + break; + } + // Measures the number of code points rather than UTF-16 code units. + if ([...key].length === 1) { + return key; + } + switch (key) { + case 'Cancel': + return '\uE001'; + case 'Help': + return '\uE002'; + case 'Backspace': + return '\uE003'; + case 'Tab': + return '\uE004'; + case 'Clear': + return '\uE005'; + case 'Enter': + return '\uE007'; + case 'Shift': + case 'ShiftLeft': + return '\uE008'; + case 'Control': + case 'ControlLeft': + return '\uE009'; + case 'Alt': + case 'AltLeft': + return '\uE00A'; + case 'Pause': + return '\uE00B'; + case 'Escape': + return '\uE00C'; + case 'PageUp': + return '\uE00E'; + case 'PageDown': + return '\uE00F'; + case 'End': + return '\uE010'; + case 'Home': + return '\uE011'; + case 'ArrowLeft': + return '\uE012'; + case 'ArrowUp': + return '\uE013'; + case 'ArrowRight': + return '\uE014'; + case 'ArrowDown': + return '\uE015'; + case 'Insert': + return '\uE016'; + case 'Delete': + return '\uE017'; + case 'NumpadEqual': + return '\uE019'; + case 'Numpad0': + return '\uE01A'; + case 'Numpad1': + return '\uE01B'; + case 'Numpad2': + return '\uE01C'; + case 'Numpad3': + return '\uE01D'; + case 'Numpad4': + return '\uE01E'; + case 'Numpad5': + return '\uE01F'; + case 'Numpad6': + return '\uE020'; + case 'Numpad7': + return '\uE021'; + case 'Numpad8': + return '\uE022'; + case 'Numpad9': + return '\uE023'; + case 'NumpadMultiply': + return '\uE024'; + case 'NumpadAdd': + return '\uE025'; + case 'NumpadSubtract': + return '\uE027'; + case 'NumpadDecimal': + return '\uE028'; + case 'NumpadDivide': + return '\uE029'; + case 'F1': + return '\uE031'; + case 'F2': + return '\uE032'; + case 'F3': + return '\uE033'; + case 'F4': + return '\uE034'; + case 'F5': + return '\uE035'; + case 'F6': + return '\uE036'; + case 'F7': + return '\uE037'; + case 'F8': + return '\uE038'; + case 'F9': + return '\uE039'; + case 'F10': + return '\uE03A'; + case 'F11': + return '\uE03B'; + case 'F12': + return '\uE03C'; + case 'Meta': + case 'MetaLeft': + return '\uE03D'; + case 'ShiftRight': + return '\uE050'; + case 'ControlRight': + return '\uE051'; + case 'AltRight': + return '\uE052'; + case 'MetaRight': + return '\uE053'; + case 'Digit0': + return '0'; + case 'Digit1': + return '1'; + case 'Digit2': + return '2'; + case 'Digit3': + return '3'; + case 'Digit4': + return '4'; + case 'Digit5': + return '5'; + case 'Digit6': + return '6'; + case 'Digit7': + return '7'; + case 'Digit8': + return '8'; + case 'Digit9': + return '9'; + case 'KeyA': + return 'a'; + case 'KeyB': + return 'b'; + case 'KeyC': + return 'c'; + case 'KeyD': + return 'd'; + case 'KeyE': + return 'e'; + case 'KeyF': + return 'f'; + case 'KeyG': + return 'g'; + case 'KeyH': + return 'h'; + case 'KeyI': + return 'i'; + case 'KeyJ': + return 'j'; + case 'KeyK': + return 'k'; + case 'KeyL': + return 'l'; + case 'KeyM': + return 'm'; + case 'KeyN': + return 'n'; + case 'KeyO': + return 'o'; + case 'KeyP': + return 'p'; + case 'KeyQ': + return 'q'; + case 'KeyR': + return 'r'; + case 'KeyS': + return 's'; + case 'KeyT': + return 't'; + case 'KeyU': + return 'u'; + case 'KeyV': + return 'v'; + case 'KeyW': + return 'w'; + case 'KeyX': + return 'x'; + case 'KeyY': + return 'y'; + case 'KeyZ': + return 'z'; + case 'Semicolon': + return ';'; + case 'Equal': + return '='; + case 'Comma': + return ','; + case 'Minus': + return '-'; + case 'Period': + return '.'; + case 'Slash': + return '/'; + case 'Backquote': + return '`'; + case 'BracketLeft': + return '['; + case 'Backslash': + return '\\'; + case 'BracketRight': + return ']'; + case 'Quote': + return '"'; + default: + throw new Error(`Unknown key: "${key}"`); + } +}; diff --git a/packages/playwright-core/src/server/bidi/third_party/bidiProtocol.ts b/packages/playwright-core/src/server/bidi/third_party/bidiProtocol.ts new file mode 100644 index 0000000000..c349e58337 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/third_party/bidiProtocol.ts @@ -0,0 +1,2204 @@ +/** + * @license + * Copyright 2024 Google Inc. + * Modifications copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * THIS FILE IS AUTOGENERATED by cddlconv 0.1.5. + * Run `node tools/generate-bidi-types.mjs` to regenerate. + * @see https://github.com/w3c/webdriver-bidi/blob/master/index.bs + */ + +export type Event = { + type: 'event'; +} & EventData & + Extensible; +export type Command = { + id: JsUint; +} & CommandData & + Extensible; +export type CommandResponse = { + type: 'success'; + id: JsUint; + result: ResultData; +} & Extensible; +export type EventData = + | BrowsingContextEvent + | LogEvent + | NetworkEvent + | ScriptEvent; +export type CommandData = + | BrowserCommand + | BrowsingContextCommand + | InputCommand + | NetworkCommand + | ScriptCommand + | SessionCommand + | StorageCommand; +export type ResultData = + | BrowsingContextResult + | EmptyResult + | NetworkResult + | ScriptResult + | SessionResult + | StorageResult; +export type EmptyParams = Extensible; +export type Message = CommandResponse | ErrorResponse | Event; +export type ErrorResponse = { + type: 'error'; + id: JsUint | null; + error: ErrorCode; + message: string; + stacktrace?: string; +} & Extensible; +export type EmptyResult = Extensible; +export type Extensible = { + [key: string]: any; +}; + +/** + * Must be between `-9007199254740991` and `9007199254740991`, inclusive. + */ +export type JsInt = number; + +/** + * Must be between `0` and `9007199254740991`, inclusive. + */ +export type JsUint = number; +export const enum ErrorCode { + InvalidArgument = 'invalid argument', + InvalidSelector = 'invalid selector', + InvalidSessionId = 'invalid session id', + MoveTargetOutOfBounds = 'move target out of bounds', + NoSuchAlert = 'no such alert', + NoSuchElement = 'no such element', + NoSuchFrame = 'no such frame', + NoSuchHandle = 'no such handle', + NoSuchHistoryEntry = 'no such history entry', + NoSuchIntercept = 'no such intercept', + NoSuchNode = 'no such node', + NoSuchRequest = 'no such request', + NoSuchScript = 'no such script', + NoSuchStoragePartition = 'no such storage partition', + NoSuchUserContext = 'no such user context', + SessionNotCreated = 'session not created', + UnableToCaptureScreen = 'unable to capture screen', + UnableToCloseBrowser = 'unable to close browser', + UnableToSetCookie = 'unable to set cookie', + UnableToSetFileInput = 'unable to set file input', + UnderspecifiedStoragePartition = 'underspecified storage partition', + UnknownCommand = 'unknown command', + UnknownError = 'unknown error', + UnsupportedOperation = 'unsupported operation', +} +export type SessionCommand = + | Session.End + | Session.New + | Session.Status + | Session.Subscribe + | Session.Unsubscribe; +export namespace Session { + export type ProxyConfiguration = + | Session.AutodetectProxyConfiguration + | Session.DirectProxyConfiguration + | Session.ManualProxyConfiguration + | Session.PacProxyConfiguration + | Session.SystemProxyConfiguration + | Record; +} +export type SessionResult = Session.NewResult | Session.StatusResult; +export namespace Session { + export type CapabilitiesRequest = { + alwaysMatch?: Session.CapabilityRequest; + firstMatch?: [...Session.CapabilityRequest[]]; + }; +} +export namespace Session { + export type CapabilityRequest = { + acceptInsecureCerts?: boolean; + browserName?: string; + browserVersion?: string; + platformName?: string; + proxy?: Session.ProxyConfiguration; + unhandledPromptBehavior?: Session.UserPromptHandler; + } & Extensible; +} +export namespace Session { + export type AutodetectProxyConfiguration = { + proxyType: 'autodetect'; + } & Extensible; +} +export namespace Session { + export type DirectProxyConfiguration = { + proxyType: 'direct'; + } & Extensible; +} +export namespace Session { + export type ManualProxyConfiguration = { + proxyType: 'manual'; + ftpProxy?: string; + httpProxy?: string; + sslProxy?: string; + } & ({} | Session.SocksProxyConfiguration) & { + noProxy?: [...string[]]; + } & Extensible; +} +export namespace Session { + export type SocksProxyConfiguration = { + socksProxy: string; + /** + * Must be between `0` and `255`, inclusive. + */ + socksVersion: number; + }; +} +export namespace Session { + export type PacProxyConfiguration = { + proxyType: 'pac'; + proxyAutoconfigUrl: string; + } & Extensible; +} +export namespace Session { + export type SystemProxyConfiguration = { + proxyType: 'system'; + } & Extensible; +} +export namespace Session { + export type UserPromptHandler = { + alert?: Session.UserPromptHandlerType; + beforeUnload?: Session.UserPromptHandlerType; + confirm?: Session.UserPromptHandlerType; + default?: Session.UserPromptHandlerType; + prompt?: Session.UserPromptHandlerType; + }; +} +export namespace Session { + export const enum UserPromptHandlerType { + Accept = 'accept', + Dismiss = 'dismiss', + Ignore = 'ignore', + } +} +export namespace Session { + export type SubscriptionRequest = { + events: [string, ...string[]]; + contexts?: [ + BrowsingContext.BrowsingContext, + ...BrowsingContext.BrowsingContext[], + ]; + }; +} +export namespace Session { + export type Status = { + method: 'session.status'; + params: EmptyParams; + }; +} +export namespace Session { + export type StatusResult = { + ready: boolean; + message: string; + }; +} +export namespace Session { + export type New = { + method: 'session.new'; + params: Session.NewParameters; + }; +} +export namespace Session { + export type NewParameters = { + capabilities: Session.CapabilitiesRequest; + }; +} +export namespace Session { + export type NewResult = { + sessionId: string; + capabilities: { + acceptInsecureCerts: boolean; + browserName: string; + browserVersion: string; + platformName: string; + setWindowRect: boolean; + userAgent: string; + proxy?: Session.ProxyConfiguration; + unhandledPromptBehavior?: Session.UserPromptHandler; + webSocketUrl?: string; + } & Extensible; + }; +} +export namespace Session { + export type End = { + method: 'session.end'; + params: EmptyParams; + }; +} +export namespace Session { + export type Subscribe = { + method: 'session.subscribe'; + params: Session.SubscriptionRequest; + }; +} +export namespace Session { + export type Unsubscribe = { + method: 'session.unsubscribe'; + params: Session.SubscriptionRequest; + }; +} +export type BrowserCommand = + | Browser.Close + | Browser.CreateUserContext + | Browser.GetUserContexts + | Browser.RemoveUserContext; +export type BrowserResult = + | Browser.CreateUserContextResult + | Browser.GetUserContextsResult; +export namespace Browser { + export type UserContext = string; +} +export namespace Browser { + export type UserContextInfo = { + userContext: Browser.UserContext; + }; +} +export namespace Browser { + export type Close = { + method: 'browser.close'; + params: EmptyParams; + }; +} +export namespace Browser { + export type CreateUserContext = { + method: 'browser.createUserContext'; + params: EmptyParams; + }; +} +export namespace Browser { + export type CreateUserContextResult = Browser.UserContextInfo; +} +export namespace Browser { + export type GetUserContexts = { + method: 'browser.getUserContexts'; + params: EmptyParams; + }; +} +export namespace Browser { + export type GetUserContextsResult = { + userContexts: [Browser.UserContextInfo, ...Browser.UserContextInfo[]]; + }; +} +export namespace Browser { + export type RemoveUserContext = { + method: 'browser.removeUserContext'; + params: Browser.RemoveUserContextParameters; + }; +} +export namespace Browser { + export type RemoveUserContextParameters = { + userContext: Browser.UserContext; + }; +} +export type BrowsingContextCommand = + | BrowsingContext.Activate + | BrowsingContext.CaptureScreenshot + | BrowsingContext.Close + | BrowsingContext.Create + | BrowsingContext.GetTree + | BrowsingContext.HandleUserPrompt + | BrowsingContext.LocateNodes + | BrowsingContext.Navigate + | BrowsingContext.Print + | BrowsingContext.Reload + | BrowsingContext.SetViewport + | BrowsingContext.TraverseHistory; +export type BrowsingContextEvent = + | BrowsingContext.ContextCreated + | BrowsingContext.ContextDestroyed + | BrowsingContext.DomContentLoaded + | BrowsingContext.DownloadWillBegin + | BrowsingContext.FragmentNavigated + | BrowsingContext.Load + | BrowsingContext.NavigationAborted + | BrowsingContext.NavigationFailed + | BrowsingContext.NavigationStarted + | BrowsingContext.UserPromptClosed + | BrowsingContext.UserPromptOpened; +export type BrowsingContextResult = + | BrowsingContext.CaptureScreenshotResult + | BrowsingContext.CreateResult + | BrowsingContext.GetTreeResult + | BrowsingContext.LocateNodesResult + | BrowsingContext.NavigateResult + | BrowsingContext.PrintResult + | BrowsingContext.TraverseHistoryResult; +export namespace BrowsingContext { + export type BrowsingContext = string; +} +export namespace BrowsingContext { + export type InfoList = [...BrowsingContext.Info[]]; +} +export namespace BrowsingContext { + export type Info = { + children: BrowsingContext.InfoList | null; + context: BrowsingContext.BrowsingContext; + originalOpener: BrowsingContext.BrowsingContext | null; + url: string; + userContext: Browser.UserContext; + parent?: BrowsingContext.BrowsingContext | null; + }; +} +export namespace BrowsingContext { + export type Locator = + | BrowsingContext.AccessibilityLocator + | BrowsingContext.CssLocator + | BrowsingContext.InnerTextLocator + | BrowsingContext.XPathLocator; +} +export namespace BrowsingContext { + export type AccessibilityLocator = { + type: 'accessibility'; + value: { + name?: string; + role?: string; + }; + }; +} +export namespace BrowsingContext { + export type CssLocator = { + type: 'css'; + value: string; + }; +} +export namespace BrowsingContext { + export type InnerTextLocator = { + type: 'innerText'; + value: string; + ignoreCase?: boolean; + matchType?: 'full' | 'partial'; + maxDepth?: JsUint; + }; +} +export namespace BrowsingContext { + export type XPathLocator = { + type: 'xpath'; + value: string; + }; +} +export namespace BrowsingContext { + export type Navigation = string; +} +export namespace BrowsingContext { + export type NavigationInfo = { + context: BrowsingContext.BrowsingContext; + navigation: BrowsingContext.Navigation | null; + timestamp: JsUint; + url: string; + }; +} +export namespace BrowsingContext { + export const enum ReadinessState { + None = 'none', + Interactive = 'interactive', + Complete = 'complete', + } +} +export namespace BrowsingContext { + export const enum UserPromptType { + Alert = 'alert', + Beforeunload = 'beforeunload', + Confirm = 'confirm', + Prompt = 'prompt', + } +} +export namespace BrowsingContext { + export type Activate = { + method: 'browsingContext.activate'; + params: BrowsingContext.ActivateParameters; + }; +} +export namespace BrowsingContext { + export type ActivateParameters = { + context: BrowsingContext.BrowsingContext; + }; +} +export namespace BrowsingContext { + export type CaptureScreenshotParameters = { + context: BrowsingContext.BrowsingContext; + /** + * @defaultValue `"viewport"` + */ + origin?: 'viewport' | 'document'; + format?: BrowsingContext.ImageFormat; + clip?: BrowsingContext.ClipRectangle; + }; +} +export namespace BrowsingContext { + export type CaptureScreenshot = { + method: 'browsingContext.captureScreenshot'; + params: BrowsingContext.CaptureScreenshotParameters; + }; +} +export namespace BrowsingContext { + export type ImageFormat = { + type: string; + /** + * Must be between `0` and `1`, inclusive. + */ + quality?: number; + }; +} +export namespace BrowsingContext { + export type ClipRectangle = + | BrowsingContext.BoxClipRectangle + | BrowsingContext.ElementClipRectangle; +} +export namespace BrowsingContext { + export type ElementClipRectangle = { + type: 'element'; + element: Script.SharedReference; + }; +} +export namespace BrowsingContext { + export type BoxClipRectangle = { + type: 'box'; + x: number; + y: number; + width: number; + height: number; + }; +} +export namespace BrowsingContext { + export type CaptureScreenshotResult = { + data: string; + }; +} +export namespace BrowsingContext { + export type Close = { + method: 'browsingContext.close'; + params: BrowsingContext.CloseParameters; + }; +} +export namespace BrowsingContext { + export type CloseParameters = { + context: BrowsingContext.BrowsingContext; + /** + * @defaultValue `false` + */ + promptUnload?: boolean; + }; +} +export namespace BrowsingContext { + export type Create = { + method: 'browsingContext.create'; + params: BrowsingContext.CreateParameters; + }; +} +export namespace BrowsingContext { + export const enum CreateType { + Tab = 'tab', + Window = 'window', + } +} +export namespace BrowsingContext { + export type CreateParameters = { + type: BrowsingContext.CreateType; + referenceContext?: BrowsingContext.BrowsingContext; + /** + * @defaultValue `false` + */ + background?: boolean; + userContext?: Browser.UserContext; + }; +} +export namespace BrowsingContext { + export type CreateResult = { + context: BrowsingContext.BrowsingContext; + }; +} +export namespace BrowsingContext { + export type GetTree = { + method: 'browsingContext.getTree'; + params: BrowsingContext.GetTreeParameters; + }; +} +export namespace BrowsingContext { + export type GetTreeParameters = { + maxDepth?: JsUint; + root?: BrowsingContext.BrowsingContext; + }; +} +export namespace BrowsingContext { + export type GetTreeResult = { + contexts: BrowsingContext.InfoList; + }; +} +export namespace BrowsingContext { + export type HandleUserPrompt = { + method: 'browsingContext.handleUserPrompt'; + params: BrowsingContext.HandleUserPromptParameters; + }; +} +export namespace BrowsingContext { + export type HandleUserPromptParameters = { + context: BrowsingContext.BrowsingContext; + accept?: boolean; + userText?: string; + }; +} +export namespace BrowsingContext { + export type LocateNodesParameters = { + context: BrowsingContext.BrowsingContext; + locator: BrowsingContext.Locator; + /** + * Must be greater than or equal to `1`. + */ + maxNodeCount?: JsUint; + serializationOptions?: Script.SerializationOptions; + startNodes?: [Script.SharedReference, ...Script.SharedReference[]]; + }; +} +export namespace BrowsingContext { + export type LocateNodes = { + method: 'browsingContext.locateNodes'; + params: BrowsingContext.LocateNodesParameters; + }; +} +export namespace BrowsingContext { + export type LocateNodesResult = { + nodes: [...Script.NodeRemoteValue[]]; + }; +} +export namespace BrowsingContext { + export type Navigate = { + method: 'browsingContext.navigate'; + params: BrowsingContext.NavigateParameters; + }; +} +export namespace BrowsingContext { + export type NavigateParameters = { + context: BrowsingContext.BrowsingContext; + url: string; + wait?: BrowsingContext.ReadinessState; + }; +} +export namespace BrowsingContext { + export type NavigateResult = { + navigation: BrowsingContext.Navigation | null; + url: string; + }; +} +export namespace BrowsingContext { + export type Print = { + method: 'browsingContext.print'; + params: BrowsingContext.PrintParameters; + }; +} +export namespace BrowsingContext { + export type PrintParameters = { + context: BrowsingContext.BrowsingContext; + /** + * @defaultValue `false` + */ + background?: boolean; + margin?: BrowsingContext.PrintMarginParameters; + /** + * @defaultValue `"portrait"` + */ + orientation?: 'portrait' | 'landscape'; + page?: BrowsingContext.PrintPageParameters; + pageRanges?: [...(JsUint | string)[]]; + /** + * Must be between `0.1` and `2`, inclusive. + * + * @defaultValue `1` + */ + scale?: number; + /** + * @defaultValue `true` + */ + shrinkToFit?: boolean; + }; +} +export namespace BrowsingContext { + export type PrintMarginParameters = { + /** + * Must be greater than or equal to `0`. + * + * @defaultValue `1` + */ + bottom?: number; + /** + * Must be greater than or equal to `0`. + * + * @defaultValue `1` + */ + left?: number; + /** + * Must be greater than or equal to `0`. + * + * @defaultValue `1` + */ + right?: number; + /** + * Must be greater than or equal to `0`. + * + * @defaultValue `1` + */ + top?: number; + }; +} +export namespace BrowsingContext { + export type PrintPageParameters = { + /** + * Must be greater than or equal to `0.0352`. + * + * @defaultValue `27.94` + */ + height?: number; + /** + * Must be greater than or equal to `0.0352`. + * + * @defaultValue `21.59` + */ + width?: number; + }; +} +export namespace BrowsingContext { + export type PrintResult = { + data: string; + }; +} +export namespace BrowsingContext { + export type Reload = { + method: 'browsingContext.reload'; + params: BrowsingContext.ReloadParameters; + }; +} +export namespace BrowsingContext { + export type ReloadParameters = { + context: BrowsingContext.BrowsingContext; + ignoreCache?: boolean; + wait?: BrowsingContext.ReadinessState; + }; +} +export namespace BrowsingContext { + export type SetViewport = { + method: 'browsingContext.setViewport'; + params: BrowsingContext.SetViewportParameters; + }; +} +export namespace BrowsingContext { + export type SetViewportParameters = { + context: BrowsingContext.BrowsingContext; + viewport?: BrowsingContext.Viewport | null; + /** + * Must be greater than `0`. + */ + devicePixelRatio?: number | null; + }; +} +export namespace BrowsingContext { + export type Viewport = { + width: JsUint; + height: JsUint; + }; +} +export namespace BrowsingContext { + export type TraverseHistory = { + method: 'browsingContext.traverseHistory'; + params: BrowsingContext.TraverseHistoryParameters; + }; +} +export namespace BrowsingContext { + export type TraverseHistoryParameters = { + context: BrowsingContext.BrowsingContext; + delta: JsInt; + }; +} +export namespace BrowsingContext { + export type TraverseHistoryResult = Record; +} +export namespace BrowsingContext { + export type ContextCreated = { + method: 'browsingContext.contextCreated'; + params: BrowsingContext.Info; + }; +} +export namespace BrowsingContext { + export type ContextDestroyed = { + method: 'browsingContext.contextDestroyed'; + params: BrowsingContext.Info; + }; +} +export namespace BrowsingContext { + export type NavigationStarted = { + method: 'browsingContext.navigationStarted'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type FragmentNavigated = { + method: 'browsingContext.fragmentNavigated'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type DomContentLoaded = { + method: 'browsingContext.domContentLoaded'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type Load = { + method: 'browsingContext.load'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type DownloadWillBegin = { + method: 'browsingContext.downloadWillBegin'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type NavigationAborted = { + method: 'browsingContext.navigationAborted'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type NavigationFailed = { + method: 'browsingContext.navigationFailed'; + params: BrowsingContext.NavigationInfo; + }; +} +export namespace BrowsingContext { + export type UserPromptClosed = { + method: 'browsingContext.userPromptClosed'; + params: BrowsingContext.UserPromptClosedParameters; + }; +} +export namespace BrowsingContext { + export type UserPromptClosedParameters = { + context: BrowsingContext.BrowsingContext; + accepted: boolean; + type: BrowsingContext.UserPromptType; + userText?: string; + }; +} +export namespace BrowsingContext { + export type UserPromptOpened = { + method: 'browsingContext.userPromptOpened'; + params: BrowsingContext.UserPromptOpenedParameters; + }; +} +export namespace BrowsingContext { + export type UserPromptOpenedParameters = { + context: BrowsingContext.BrowsingContext; + handler: Session.UserPromptHandlerType; + message: string; + type: BrowsingContext.UserPromptType; + defaultValue?: string; + }; +} +export type NetworkCommand = + | Network.AddIntercept + | Network.ContinueRequest + | Network.ContinueResponse + | Network.ContinueWithAuth + | Network.FailRequest + | Network.ProvideResponse + | Network.RemoveIntercept + | Network.SetCacheBehavior; +export type NetworkEvent = + | Network.AuthRequired + | Network.BeforeRequestSent + | Network.FetchError + | Network.ResponseCompleted + | Network.ResponseStarted; +export type NetworkResult = Network.AddInterceptResult; +export namespace Network { + export type AuthChallenge = { + scheme: string; + realm: string; + }; +} +export namespace Network { + export type AuthCredentials = { + type: 'password'; + username: string; + password: string; + }; +} +export namespace Network { + export type BaseParameters = { + context: BrowsingContext.BrowsingContext | null; + isBlocked: boolean; + navigation: BrowsingContext.Navigation | null; + redirectCount: JsUint; + request: Network.RequestData; + timestamp: JsUint; + intercepts?: [Network.Intercept, ...Network.Intercept[]]; + }; +} +export namespace Network { + export type BytesValue = Network.StringValue | Network.Base64Value; +} +export namespace Network { + export type StringValue = { + type: 'string'; + value: string; + }; +} +export namespace Network { + export type Base64Value = { + type: 'base64'; + value: string; + }; +} +export namespace Network { + export const enum SameSite { + Strict = 'strict', + Lax = 'lax', + None = 'none', + } +} +export namespace Network { + export type Cookie = { + name: string; + value: Network.BytesValue; + domain: string; + path: string; + size: JsUint; + httpOnly: boolean; + secure: boolean; + sameSite: Network.SameSite; + expiry?: JsUint; + } & Extensible; +} +export namespace Network { + export type CookieHeader = { + name: string; + value: Network.BytesValue; + }; +} +export namespace Network { + export type FetchTimingInfo = { + timeOrigin: number; + requestTime: number; + redirectStart: number; + redirectEnd: number; + fetchStart: number; + dnsStart: number; + dnsEnd: number; + connectStart: number; + connectEnd: number; + tlsStart: number; + requestStart: number; + responseStart: number; + responseEnd: number; + }; +} +export namespace Network { + export type Header = { + name: string; + value: Network.BytesValue; + }; +} +export namespace Network { + export type Initiator = { + type: 'parser' | 'script' | 'preflight' | 'other'; + columnNumber?: JsUint; + lineNumber?: JsUint; + stackTrace?: Script.StackTrace; + request?: Network.Request; + }; +} +export namespace Network { + export type Intercept = string; +} +export namespace Network { + export type Request = string; +} +export namespace Network { + export type RequestData = { + request: Network.Request; + url: string; + method: string; + headers: [...Network.Header[]]; + cookies: [...Network.Cookie[]]; + headersSize: JsUint; + bodySize: JsUint | null; + timings: Network.FetchTimingInfo; + }; +} +export namespace Network { + export type ResponseContent = { + size: JsUint; + }; +} +export namespace Network { + export type ResponseData = { + url: string; + protocol: string; + status: JsUint; + statusText: string; + fromCache: boolean; + headers: [...Network.Header[]]; + mimeType: string; + bytesReceived: JsUint; + headersSize: JsUint | null; + bodySize: JsUint | null; + content: Network.ResponseContent; + authChallenges?: [...Network.AuthChallenge[]]; + }; +} +export namespace Network { + export type SetCookieHeader = { + name: string; + value: Network.BytesValue; + domain?: string; + httpOnly?: boolean; + expiry?: string; + maxAge?: JsInt; + path?: string; + sameSite?: Network.SameSite; + secure?: boolean; + }; +} +export namespace Network { + export type UrlPattern = Network.UrlPatternPattern | Network.UrlPatternString; +} +export namespace Network { + export type UrlPatternPattern = { + type: 'pattern'; + protocol?: string; + hostname?: string; + port?: string; + pathname?: string; + search?: string; + }; +} +export namespace Network { + export type UrlPatternString = { + type: 'string'; + pattern: string; + }; +} +export namespace Network { + export type AddInterceptParameters = { + phases: [Network.InterceptPhase, ...Network.InterceptPhase[]]; + contexts?: [ + BrowsingContext.BrowsingContext, + ...BrowsingContext.BrowsingContext[], + ]; + urlPatterns?: [...Network.UrlPattern[]]; + }; +} +export namespace Network { + export type AddIntercept = { + method: 'network.addIntercept'; + params: Network.AddInterceptParameters; + }; +} +export namespace Network { + export const enum InterceptPhase { + BeforeRequestSent = 'beforeRequestSent', + ResponseStarted = 'responseStarted', + AuthRequired = 'authRequired', + } +} +export namespace Network { + export type AddInterceptResult = { + intercept: Network.Intercept; + }; +} +export namespace Network { + export type ContinueRequest = { + method: 'network.continueRequest'; + params: Network.ContinueRequestParameters; + }; +} +export namespace Network { + export type ContinueRequestParameters = { + request: Network.Request; + body?: Network.BytesValue; + cookies?: [...Network.CookieHeader[]]; + headers?: [...Network.Header[]]; + method?: string; + url?: string; + }; +} +export namespace Network { + export type ContinueResponse = { + method: 'network.continueResponse'; + params: Network.ContinueResponseParameters; + }; +} +export namespace Network { + export type ContinueResponseParameters = { + request: Network.Request; + cookies?: [...Network.SetCookieHeader[]]; + credentials?: Network.AuthCredentials; + headers?: [...Network.Header[]]; + reasonPhrase?: string; + statusCode?: JsUint; + }; +} +export namespace Network { + export type ContinueWithAuth = { + method: 'network.continueWithAuth'; + params: Network.ContinueWithAuthParameters; + }; +} +export namespace Network { + export type ContinueWithAuthParameters = { + request: Network.Request; + } & ( + | Network.ContinueWithAuthCredentials + | Network.ContinueWithAuthNoCredentials + ); +} +export namespace Network { + export type ContinueWithAuthCredentials = { + action: 'provideCredentials'; + credentials: Network.AuthCredentials; + }; +} +export namespace Network { + export type ContinueWithAuthNoCredentials = { + action: 'default' | 'cancel'; + }; +} +export namespace Network { + export type FailRequest = { + method: 'network.failRequest'; + params: Network.FailRequestParameters; + }; +} +export namespace Network { + export type FailRequestParameters = { + request: Network.Request; + }; +} +export namespace Network { + export type ProvideResponse = { + method: 'network.provideResponse'; + params: Network.ProvideResponseParameters; + }; +} +export namespace Network { + export type ProvideResponseParameters = { + request: Network.Request; + body?: Network.BytesValue; + cookies?: [...Network.SetCookieHeader[]]; + headers?: [...Network.Header[]]; + reasonPhrase?: string; + statusCode?: JsUint; + }; +} +export namespace Network { + export type RemoveIntercept = { + method: 'network.removeIntercept'; + params: Network.RemoveInterceptParameters; + }; +} +export namespace Network { + export type RemoveInterceptParameters = { + intercept: Network.Intercept; + }; +} +export namespace Network { + export type SetCacheBehavior = { + method: 'network.setCacheBehavior'; + params: Network.SetCacheBehaviorParameters; + }; +} +export namespace Network { + export type SetCacheBehaviorParameters = { + cacheBehavior: 'default' | 'bypass'; + contexts?: [ + BrowsingContext.BrowsingContext, + ...BrowsingContext.BrowsingContext[], + ]; + }; +} +export type ScriptEvent = + | Script.Message + | Script.RealmCreated + | Script.RealmDestroyed; +export namespace Network { + export type AuthRequiredParameters = Network.BaseParameters & { + response: Network.ResponseData; + }; +} +export namespace Network { + export type BeforeRequestSentParameters = Network.BaseParameters & { + initiator: Network.Initiator; + }; +} +export namespace Network { + export type FetchErrorParameters = Network.BaseParameters & { + errorText: string; + }; +} +export namespace Network { + export type ResponseCompletedParameters = Network.BaseParameters & { + response: Network.ResponseData; + }; +} +export namespace Network { + export type ResponseStartedParameters = Network.BaseParameters & { + response: Network.ResponseData; + }; +} +export type ScriptCommand = + | Script.AddPreloadScript + | Script.CallFunction + | Script.Disown + | Script.Evaluate + | Script.GetRealms + | Script.RemovePreloadScript; +export type ScriptResult = + | Script.AddPreloadScriptResult + | Script.EvaluateResult + | Script.GetRealmsResult; +export namespace Network { + export type AuthRequired = { + method: 'network.authRequired'; + params: Network.AuthRequiredParameters; + }; +} +export namespace Network { + export type BeforeRequestSent = { + method: 'network.beforeRequestSent'; + params: Network.BeforeRequestSentParameters; + }; +} +export namespace Network { + export type FetchError = { + method: 'network.fetchError'; + params: Network.FetchErrorParameters; + }; +} +export namespace Network { + export type ResponseCompleted = { + method: 'network.responseCompleted'; + params: Network.ResponseCompletedParameters; + }; +} +export namespace Network { + export type ResponseStarted = { + method: 'network.responseStarted'; + params: Network.ResponseStartedParameters; + }; +} +export namespace Script { + export type Channel = string; +} +export namespace Script { + export type EvaluateResultSuccess = { + type: 'success'; + result: Script.RemoteValue; + realm: Script.Realm; + }; +} +export namespace Script { + export type ExceptionDetails = { + columnNumber: JsUint; + exception: Script.RemoteValue; + lineNumber: JsUint; + stackTrace: Script.StackTrace; + text: string; + }; +} +export namespace Script { + export type ChannelValue = { + type: 'channel'; + value: Script.ChannelProperties; + }; +} +export namespace Script { + export type ChannelProperties = { + channel: Script.Channel; + serializationOptions?: Script.SerializationOptions; + ownership?: Script.ResultOwnership; + }; +} +export namespace Script { + export type EvaluateResult = + | Script.EvaluateResultSuccess + | Script.EvaluateResultException; +} +export namespace Script { + export type EvaluateResultException = { + type: 'exception'; + exceptionDetails: Script.ExceptionDetails; + realm: Script.Realm; + }; +} +export namespace Script { + export type Handle = string; +} +export namespace Script { + export type InternalId = string; +} +export namespace Script { + export type ListLocalValue = [...Script.LocalValue[]]; +} +export namespace Script { + export type LocalValue = + | Script.RemoteReference + | Script.PrimitiveProtocolValue + | Script.ChannelValue + | Script.ArrayLocalValue + | Script.DateLocalValue + | Script.MapLocalValue + | Script.ObjectLocalValue + | Script.RegExpLocalValue + | Script.SetLocalValue; +} +export namespace Script { + export type ArrayLocalValue = { + type: 'array'; + value: Script.ListLocalValue; + }; +} +export namespace Script { + export type DateLocalValue = { + type: 'date'; + value: string; + }; +} +export namespace Script { + export type MappingLocalValue = [ + ...[Script.LocalValue | string, Script.LocalValue][], + ]; +} +export namespace Script { + export type MapLocalValue = { + type: 'map'; + value: Script.MappingLocalValue; + }; +} +export namespace Script { + export type ObjectLocalValue = { + type: 'object'; + value: Script.MappingLocalValue; + }; +} +export namespace Script { + export type RegExpValue = { + pattern: string; + flags?: string; + }; +} +export namespace Script { + export type RegExpLocalValue = { + type: 'regexp'; + value: Script.RegExpValue; + }; +} +export namespace Script { + export type SetLocalValue = { + type: 'set'; + value: Script.ListLocalValue; + }; +} +export namespace Script { + export type PreloadScript = string; +} +export namespace Script { + export type Realm = string; +} +export namespace Script { + export type PrimitiveProtocolValue = + | Script.UndefinedValue + | Script.NullValue + | Script.StringValue + | Script.NumberValue + | Script.BooleanValue + | Script.BigIntValue; +} +export namespace Script { + export type UndefinedValue = { + type: 'undefined'; + }; +} +export namespace Script { + export type NullValue = { + type: 'null'; + }; +} +export namespace Script { + export type StringValue = { + type: 'string'; + value: string; + }; +} +export namespace Script { + export type SpecialNumber = 'NaN' | '-0' | 'Infinity' | '-Infinity'; +} +export namespace Script { + export type NumberValue = { + type: 'number'; + value: number | Script.SpecialNumber; + }; +} +export namespace Script { + export type BooleanValue = { + type: 'boolean'; + value: boolean; + }; +} +export namespace Script { + export type BigIntValue = { + type: 'bigint'; + value: string; + }; +} +export namespace Script { + export type RealmInfo = + | Script.WindowRealmInfo + | Script.DedicatedWorkerRealmInfo + | Script.SharedWorkerRealmInfo + | Script.ServiceWorkerRealmInfo + | Script.WorkerRealmInfo + | Script.PaintWorkletRealmInfo + | Script.AudioWorkletRealmInfo + | Script.WorkletRealmInfo; +} +export namespace Script { + export type BaseRealmInfo = { + realm: Script.Realm; + origin: string; + }; +} +export namespace Script { + export type WindowRealmInfo = Script.BaseRealmInfo & { + type: 'window'; + context: BrowsingContext.BrowsingContext; + sandbox?: string; + }; +} +export namespace Script { + export type DedicatedWorkerRealmInfo = Script.BaseRealmInfo & { + type: 'dedicated-worker'; + owners: [Script.Realm]; + }; +} +export namespace Script { + export type SharedWorkerRealmInfo = Script.BaseRealmInfo & { + type: 'shared-worker'; + }; +} +export namespace Script { + export type ServiceWorkerRealmInfo = Script.BaseRealmInfo & { + type: 'service-worker'; + }; +} +export namespace Script { + export type WorkerRealmInfo = Script.BaseRealmInfo & { + type: 'worker'; + }; +} +export namespace Script { + export type PaintWorkletRealmInfo = Script.BaseRealmInfo & { + type: 'paint-worklet'; + }; +} +export namespace Script { + export type AudioWorkletRealmInfo = Script.BaseRealmInfo & { + type: 'audio-worklet'; + }; +} +export namespace Script { + export type WorkletRealmInfo = Script.BaseRealmInfo & { + type: 'worklet'; + }; +} +export namespace Script { + export type RealmType = + | 'window' + | 'dedicated-worker' + | 'shared-worker' + | 'service-worker' + | 'worker' + | 'paint-worklet' + | 'audio-worklet' + | 'worklet'; +} +export namespace Script { + export type ListRemoteValue = [...Script.RemoteValue[]]; +} +export namespace Script { + export type MappingRemoteValue = [ + ...[Script.RemoteValue | string, Script.RemoteValue][], + ]; +} +export namespace Script { + export type RemoteValue = + | Script.PrimitiveProtocolValue + | Script.SymbolRemoteValue + | Script.ArrayRemoteValue + | Script.ObjectRemoteValue + | Script.FunctionRemoteValue + | Script.RegExpRemoteValue + | Script.DateRemoteValue + | Script.MapRemoteValue + | Script.SetRemoteValue + | Script.WeakMapRemoteValue + | Script.WeakSetRemoteValue + | Script.GeneratorRemoteValue + | Script.ErrorRemoteValue + | Script.ProxyRemoteValue + | Script.PromiseRemoteValue + | Script.TypedArrayRemoteValue + | Script.ArrayBufferRemoteValue + | Script.NodeListRemoteValue + | Script.HtmlCollectionRemoteValue + | Script.NodeRemoteValue + | Script.WindowProxyRemoteValue; +} +export namespace Script { + export type RemoteReference = + | Script.SharedReference + | Script.RemoteObjectReference; +} +export namespace Script { + export type SharedReference = { + sharedId: Script.SharedId; + handle?: Script.Handle; + } & Extensible; +} +export namespace Script { + export type RemoteObjectReference = { + handle: Script.Handle; + sharedId?: Script.SharedId; + } & Extensible; +} +export namespace Script { + export type SymbolRemoteValue = { + type: 'symbol'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type ArrayRemoteValue = { + type: 'array'; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.ListRemoteValue; + }; +} +export namespace Script { + export type ObjectRemoteValue = { + type: 'object'; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.MappingRemoteValue; + }; +} +export namespace Script { + export type FunctionRemoteValue = { + type: 'function'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type RegExpRemoteValue = { + handle?: Script.Handle; + internalId?: Script.InternalId; + } & Script.RegExpLocalValue; +} +export namespace Script { + export type DateRemoteValue = { + handle?: Script.Handle; + internalId?: Script.InternalId; + } & Script.DateLocalValue; +} +export namespace Script { + export type MapRemoteValue = { + type: 'map'; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.MappingRemoteValue; + }; +} +export namespace Script { + export type SetRemoteValue = { + type: 'set'; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.ListRemoteValue; + }; +} +export namespace Script { + export type WeakMapRemoteValue = { + type: 'weakmap'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type WeakSetRemoteValue = { + type: 'weakset'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type GeneratorRemoteValue = { + type: 'generator'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type ErrorRemoteValue = { + type: 'error'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type ProxyRemoteValue = { + type: 'proxy'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type PromiseRemoteValue = { + type: 'promise'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type TypedArrayRemoteValue = { + type: 'typedarray'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type ArrayBufferRemoteValue = { + type: 'arraybuffer'; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type NodeListRemoteValue = { + type: 'nodelist'; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.ListRemoteValue; + }; +} +export namespace Script { + export type HtmlCollectionRemoteValue = { + type: 'htmlcollection'; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.ListRemoteValue; + }; +} +export namespace Script { + export type NodeRemoteValue = { + type: 'node'; + sharedId?: Script.SharedId; + handle?: Script.Handle; + internalId?: Script.InternalId; + value?: Script.NodeProperties; + }; +} +export namespace Script { + export type NodeProperties = { + nodeType: JsUint; + childNodeCount: JsUint; + attributes?: { + [key: string]: string; + }; + children?: [...Script.NodeRemoteValue[]]; + localName?: string; + mode?: 'open' | 'closed'; + namespaceURI?: string; + nodeValue?: string; + shadowRoot?: Script.NodeRemoteValue | null; + }; +} +export namespace Script { + export type WindowProxyRemoteValue = { + type: 'window'; + value: Script.WindowProxyProperties; + handle?: Script.Handle; + internalId?: Script.InternalId; + }; +} +export namespace Script { + export type WindowProxyProperties = { + context: BrowsingContext.BrowsingContext; + }; +} +export namespace Script { + export const enum ResultOwnership { + Root = 'root', + None = 'none', + } +} +export namespace Script { + export type SerializationOptions = { + /** + * @defaultValue `0` + */ + maxDomDepth?: JsUint | null; + /** + * @defaultValue `null` + */ + maxObjectDepth?: JsUint | null; + /** + * @defaultValue `"none"` + */ + includeShadowTree?: 'none' | 'open' | 'all'; + }; +} +export namespace Script { + export type SharedId = string; +} +export namespace Script { + export type StackFrame = { + columnNumber: JsUint; + functionName: string; + lineNumber: JsUint; + url: string; + }; +} +export namespace Script { + export type StackTrace = { + callFrames: [...Script.StackFrame[]]; + }; +} +export namespace Script { + export type Source = { + realm: Script.Realm; + context?: BrowsingContext.BrowsingContext; + }; +} +export namespace Script { + export type RealmTarget = { + realm: Script.Realm; + }; +} +export namespace Script { + export type ContextTarget = { + context: BrowsingContext.BrowsingContext; + sandbox?: string; + }; +} +export namespace Script { + export type Target = Script.ContextTarget | Script.RealmTarget; +} +export namespace Script { + export type AddPreloadScript = { + method: 'script.addPreloadScript'; + params: Script.AddPreloadScriptParameters; + }; +} +export namespace Script { + export type AddPreloadScriptParameters = { + functionDeclaration: string; + arguments?: [...Script.ChannelValue[]]; + contexts?: [ + BrowsingContext.BrowsingContext, + ...BrowsingContext.BrowsingContext[], + ]; + sandbox?: string; + }; +} +export namespace Script { + export type AddPreloadScriptResult = { + script: Script.PreloadScript; + }; +} +export namespace Script { + export type Disown = { + method: 'script.disown'; + params: Script.DisownParameters; + }; +} +export namespace Script { + export type DisownParameters = { + handles: [...Script.Handle[]]; + target: Script.Target; + }; +} +export namespace Script { + export type CallFunctionParameters = { + functionDeclaration: string; + awaitPromise: boolean; + target: Script.Target; + arguments?: [...Script.LocalValue[]]; + resultOwnership?: Script.ResultOwnership; + serializationOptions?: Script.SerializationOptions; + this?: Script.LocalValue; + /** + * @defaultValue `false` + */ + userActivation?: boolean; + }; +} +export namespace Script { + export type CallFunction = { + method: 'script.callFunction'; + params: Script.CallFunctionParameters; + }; +} +export namespace Script { + export type Evaluate = { + method: 'script.evaluate'; + params: Script.EvaluateParameters; + }; +} +export namespace Script { + export type EvaluateParameters = { + expression: string; + target: Script.Target; + awaitPromise: boolean; + resultOwnership?: Script.ResultOwnership; + serializationOptions?: Script.SerializationOptions; + /** + * @defaultValue `false` + */ + userActivation?: boolean; + }; +} +export namespace Script { + export type GetRealms = { + method: 'script.getRealms'; + params: Script.GetRealmsParameters; + }; +} +export namespace Script { + export type GetRealmsParameters = { + context?: BrowsingContext.BrowsingContext; + type?: Script.RealmType; + }; +} +export namespace Script { + export type GetRealmsResult = { + realms: [...Script.RealmInfo[]]; + }; +} +export namespace Script { + export type RemovePreloadScript = { + method: 'script.removePreloadScript'; + params: Script.RemovePreloadScriptParameters; + }; +} +export namespace Script { + export type RemovePreloadScriptParameters = { + script: Script.PreloadScript; + }; +} +export namespace Script { + export type MessageParameters = { + channel: Script.Channel; + data: Script.RemoteValue; + source: Script.Source; + }; +} +export namespace Script { + export type RealmCreated = { + method: 'script.realmCreated'; + params: Script.RealmInfo; + }; +} +export namespace Script { + export type Message = { + method: 'script.message'; + params: Script.MessageParameters; + }; +} +export namespace Script { + export type RealmDestroyed = { + method: 'script.realmDestroyed'; + params: Script.RealmDestroyedParameters; + }; +} +export namespace Script { + export type RealmDestroyedParameters = { + realm: Script.Realm; + }; +} +export type StorageCommand = + | Storage.DeleteCookies + | Storage.GetCookies + | Storage.SetCookie; +export type StorageResult = + | Storage.DeleteCookiesResult + | Storage.GetCookiesResult + | Storage.SetCookieResult; +export namespace Storage { + export type PartitionKey = { + userContext?: string; + sourceOrigin?: string; + } & Extensible; +} +export namespace Storage { + export type GetCookies = { + method: 'storage.getCookies'; + params: Storage.GetCookiesParameters; + }; +} +export namespace Storage { + export type CookieFilter = { + name?: string; + value?: Network.BytesValue; + domain?: string; + path?: string; + size?: JsUint; + httpOnly?: boolean; + secure?: boolean; + sameSite?: Network.SameSite; + expiry?: JsUint; + } & Extensible; +} +export namespace Storage { + export type BrowsingContextPartitionDescriptor = { + type: 'context'; + context: BrowsingContext.BrowsingContext; + }; +} +export namespace Storage { + export type StorageKeyPartitionDescriptor = { + type: 'storageKey'; + userContext?: string; + sourceOrigin?: string; + } & Extensible; +} +export namespace Storage { + export type PartitionDescriptor = + | Storage.BrowsingContextPartitionDescriptor + | Storage.StorageKeyPartitionDescriptor; +} +export namespace Storage { + export type GetCookiesParameters = { + filter?: Storage.CookieFilter; + partition?: Storage.PartitionDescriptor; + }; +} +export namespace Storage { + export type GetCookiesResult = { + cookies: [...Network.Cookie[]]; + partitionKey: Storage.PartitionKey; + }; +} +export namespace Storage { + export type SetCookie = { + method: 'storage.setCookie'; + params: Storage.SetCookieParameters; + }; +} +export namespace Storage { + export type PartialCookie = { + name: string; + value: Network.BytesValue; + domain: string; + path?: string; + httpOnly?: boolean; + secure?: boolean; + sameSite?: Network.SameSite; + expiry?: JsUint; + } & Extensible; +} +export namespace Storage { + export type SetCookieParameters = { + cookie: Storage.PartialCookie; + partition?: Storage.PartitionDescriptor; + }; +} +export namespace Storage { + export type SetCookieResult = { + partitionKey: Storage.PartitionKey; + }; +} +export namespace Storage { + export type DeleteCookies = { + method: 'storage.deleteCookies'; + params: Storage.DeleteCookiesParameters; + }; +} +export namespace Storage { + export type DeleteCookiesParameters = { + filter?: Storage.CookieFilter; + partition?: Storage.PartitionDescriptor; + }; +} +export namespace Storage { + export type DeleteCookiesResult = { + partitionKey: Storage.PartitionKey; + }; +} +export type LogEvent = Log.EntryAdded; +export namespace Log { + export const enum Level { + Debug = 'debug', + Info = 'info', + Warn = 'warn', + Error = 'error', + } +} +export namespace Log { + export type Entry = + | Log.GenericLogEntry + | Log.ConsoleLogEntry + | Log.JavascriptLogEntry; +} +export namespace Log { + export type BaseLogEntry = { + level: Log.Level; + source: Script.Source; + text: string | null; + timestamp: JsUint; + stackTrace?: Script.StackTrace; + }; +} +export namespace Log { + export type GenericLogEntry = Log.BaseLogEntry & { + type: string; + }; +} +export namespace Log { + export type ConsoleLogEntry = Log.BaseLogEntry & { + type: 'console'; + method: string; + args: [...Script.RemoteValue[]]; + }; +} +export namespace Log { + export type JavascriptLogEntry = Log.BaseLogEntry & { + type: 'javascript'; + }; +} +export namespace Log { + export type EntryAdded = { + method: 'log.entryAdded'; + params: Log.Entry; + }; +} +export type InputCommand = + | Input.PerformActions + | Input.ReleaseActions + | Input.SetFiles; +export namespace Input { + export type ElementOrigin = { + type: 'element'; + element: Script.SharedReference; + }; +} +export namespace Input { + export type PerformActionsParameters = { + context: BrowsingContext.BrowsingContext; + actions: [...Input.SourceActions[]]; + }; +} +export namespace Input { + export type NoneSourceActions = { + type: 'none'; + id: string; + actions: [...Input.NoneSourceAction[]]; + }; +} +export namespace Input { + export type KeySourceActions = { + type: 'key'; + id: string; + actions: [...Input.KeySourceAction[]]; + }; +} +export namespace Input { + export type PointerSourceActions = { + type: 'pointer'; + id: string; + parameters?: Input.PointerParameters; + actions: [...Input.PointerSourceAction[]]; + }; +} +export namespace Input { + export type PerformActions = { + method: 'input.performActions'; + params: Input.PerformActionsParameters; + }; +} +export namespace Input { + export type SourceActions = + | Input.NoneSourceActions + | Input.KeySourceActions + | Input.PointerSourceActions + | Input.WheelSourceActions; +} +export namespace Input { + export type NoneSourceAction = Input.PauseAction; +} +export namespace Input { + export type KeySourceAction = + | Input.PauseAction + | Input.KeyDownAction + | Input.KeyUpAction; +} +export namespace Input { + export const enum PointerType { + Mouse = 'mouse', + Pen = 'pen', + Touch = 'touch', + } +} +export namespace Input { + export type PointerParameters = { + /** + * @defaultValue `"mouse"` + */ + pointerType?: Input.PointerType; + }; +} +export namespace Input { + export type WheelSourceActions = { + type: 'wheel'; + id: string; + actions: [...Input.WheelSourceAction[]]; + }; +} +export namespace Input { + export type PointerSourceAction = + | Input.PauseAction + | Input.PointerDownAction + | Input.PointerUpAction + | Input.PointerMoveAction; +} +export namespace Input { + export type WheelSourceAction = Input.PauseAction | Input.WheelScrollAction; +} +export namespace Input { + export type PauseAction = { + type: 'pause'; + duration?: JsUint; + }; +} +export namespace Input { + export type KeyDownAction = { + type: 'keyDown'; + value: string; + }; +} +export namespace Input { + export type KeyUpAction = { + type: 'keyUp'; + value: string; + }; +} +export namespace Input { + export type PointerUpAction = { + type: 'pointerUp'; + button: JsUint; + }; +} +export namespace Input { + export type PointerDownAction = { + type: 'pointerDown'; + button: JsUint; + } & Input.PointerCommonProperties; +} +export namespace Input { + export type PointerMoveAction = { + type: 'pointerMove'; + x: JsInt; + y: JsInt; + duration?: JsUint; + origin?: Input.Origin; + } & Input.PointerCommonProperties; +} +export namespace Input { + export type WheelScrollAction = { + type: 'scroll'; + x: JsInt; + y: JsInt; + deltaX: JsInt; + deltaY: JsInt; + duration?: JsUint; + /** + * @defaultValue `"viewport"` + */ + origin?: Input.Origin; + }; +} +export namespace Input { + export type PointerCommonProperties = { + /** + * @defaultValue `1` + */ + width?: JsUint; + /** + * @defaultValue `1` + */ + height?: JsUint; + /** + * @defaultValue `0` + */ + pressure?: number; + /** + * @defaultValue `0` + */ + tangentialPressure?: number; + /** + * Must be between `0` and `359`, inclusive. + * + * @defaultValue `0` + */ + twist?: number; + /** + * Must be between `0` and `1.5707963267948966`, inclusive. + * + * @defaultValue `0` + */ + altitudeAngle?: number; + /** + * Must be between `0` and `6.283185307179586`, inclusive. + * + * @defaultValue `0` + */ + azimuthAngle?: number; + }; +} +export namespace Input { + export type Origin = 'viewport' | 'pointer' | Input.ElementOrigin; +} +export namespace Input { + export type ReleaseActions = { + method: 'input.releaseActions'; + params: Input.ReleaseActionsParameters; + }; +} +export namespace Input { + export type ReleaseActionsParameters = { + context: BrowsingContext.BrowsingContext; + }; +} +export namespace Input { + export type SetFiles = { + method: 'input.setFiles'; + params: Input.SetFilesParameters; + }; +} +export namespace Input { + export type SetFilesParameters = { + context: BrowsingContext.BrowsingContext; + element: Script.SharedReference; + files: [...string[]]; + }; +} diff --git a/packages/playwright-core/src/server/bidi/third_party/bidiSerializer.ts b/packages/playwright-core/src/server/bidi/third_party/bidiSerializer.ts new file mode 100644 index 0000000000..97e8381328 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/third_party/bidiSerializer.ts @@ -0,0 +1,148 @@ +/** + * @license + * Copyright 2024 Google Inc. + * Modifications copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type * as Bidi from './bidiProtocol'; + +/* eslint-disable curly, indent */ + +/** + * @internal + */ +class UnserializableError extends Error {} + +/** + * @internal + */ +export class BidiSerializer { + static serialize(arg: unknown): Bidi.Script.LocalValue { + switch (typeof arg) { + case 'symbol': + case 'function': + throw new UnserializableError(`Unable to serializable ${typeof arg}`); + case 'object': + return BidiSerializer._serializeObject(arg); + + case 'undefined': + return { + type: 'undefined', + }; + case 'number': + return BidiSerializer._serializeNumber(arg); + case 'bigint': + return { + type: 'bigint', + value: arg.toString(), + }; + case 'string': + return { + type: 'string', + value: arg, + }; + case 'boolean': + return { + type: 'boolean', + value: arg, + }; + } + } + + static _serializeNumber(arg: number): Bidi.Script.LocalValue { + let value: Bidi.Script.SpecialNumber | number; + if (Object.is(arg, -0)) { + value = '-0'; + } else if (Object.is(arg, Infinity)) { + value = 'Infinity'; + } else if (Object.is(arg, -Infinity)) { + value = '-Infinity'; + } else if (Object.is(arg, NaN)) { + value = 'NaN'; + } else { + value = arg; + } + return { + type: 'number', + value, + }; + } + + static _serializeObject(arg: object | null): Bidi.Script.LocalValue { + if (arg === null) { + return { + type: 'null', + }; + } else if (Array.isArray(arg)) { + const parsedArray = arg.map(subArg => { + return BidiSerializer.serialize(subArg); + }); + + return { + type: 'array', + value: parsedArray, + }; + } else if (isPlainObject(arg)) { + try { + JSON.stringify(arg); + } catch (error) { + if ( + error instanceof TypeError && + error.message.startsWith('Converting circular structure to JSON') + ) { + error.message += ' Recursive objects are not allowed.'; + } + throw error; + } + + const parsedObject: Bidi.Script.MappingLocalValue = []; + for (const key in arg) { + parsedObject.push([BidiSerializer.serialize(key), BidiSerializer.serialize(arg[key])]); + } + + return { + type: 'object', + value: parsedObject, + }; + } else if (isRegExp(arg)) { + return { + type: 'regexp', + value: { + pattern: arg.source, + flags: arg.flags, + }, + }; + } else if (isDate(arg)) { + return { + type: 'date', + value: arg.toISOString(), + }; + } + + throw new UnserializableError( + 'Custom object serialization not possible. Use plain objects instead.' + ); + } +} + +/** + * @internal + */ +export const isPlainObject = (obj: unknown): obj is Record => { + return typeof obj === 'object' && obj?.constructor === Object; +}; + +/** + * @internal + */ +export const isRegExp = (obj: unknown): obj is RegExp => { + return typeof obj === 'object' && obj?.constructor === RegExp; +}; + +/** + * @internal + */ +export const isDate = (obj: unknown): obj is Date => { + return typeof obj === 'object' && obj?.constructor === Date; +}; diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 80681130ef..8ddbe68f89 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import * as os from 'os'; import { TimeoutSettings } from '../common/timeoutSettings'; import { createGuid, debugMode } from '../utils'; import { mkdirIfNeeded } from '../utils/fileUtils'; @@ -44,6 +43,7 @@ import { BrowserContextAPIRequestContext } from './fetch'; import type { Artifact } from './artifact'; import { Clock } from './clock'; import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; +import { RecorderApp } from './recorder/recorderApp'; export abstract class BrowserContext extends SdkObject { static Events = { @@ -131,19 +131,21 @@ export abstract class BrowserContext extends SdkObject { // When PWDEBUG=1, show inspector for each context. if (debugMode() === 'inspector') - await Recorder.show(this, { pauseOnNextStatement: true }); + await Recorder.show(this, RecorderApp.factory(this), { pauseOnNextStatement: true }); // When paused, show inspector. if (this._debugger.isPaused()) - Recorder.showInspector(this); + Recorder.showInspector(this, RecorderApp.factory(this)); + this._debugger.on(Debugger.Events.PausedStateChanged, () => { - Recorder.showInspector(this); + if (this._debugger.isPaused()) + Recorder.showInspector(this, RecorderApp.factory(this)); }); if (debugMode() === 'console') await this.extendInjectedScript(consoleApiSource.source); if (this._options.serviceWorkers === 'block') - await this.addInitScript(`\nnavigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); };\n`); + await this.addInitScript(`\nif (navigator.serviceWorker) navigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); };\n`); if (this._options.permissions) await this.grantPermissions(this._options.permissions); @@ -700,11 +702,8 @@ export function validateBrowserContextOptions(options: channels.BrowserNewContex options.recordVideo.size!.width &= ~1; options.recordVideo.size!.height &= ~1; } - if (options.proxy) { - if (!browserOptions.proxy && browserOptions.isChromium && os.platform() === 'win32') - throw new Error(`Browser needs to be launched with the global proxy. If all contexts override the proxy, global proxy will be never used and can be any string, for example "launch({ proxy: { server: 'http://per-context' } })"`); + if (options.proxy) options.proxy = normalizeProxySettings(options.proxy); - } verifyGeolocation(options.geolocation); } diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index 6e154b7e66..d0c3174a59 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -32,7 +32,7 @@ import { ProgressController } from './progress'; import type * as types from './types'; import type * as channels from '@protocol/channels'; import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings'; -import { debugMode } from '../utils'; +import { debugMode, ManualPromise } from '../utils'; import { existsAsync } from '../utils/fileUtils'; import { helper } from './helper'; import { RecentLogsCollector } from '../utils/debugLogger'; @@ -44,14 +44,24 @@ export const kNoXServerRunningError = 'Looks like you launched a headed browser 'Set either \'headless: true\' or use \'xvfb-run \' before running Playwright.\n\n<3 Playwright Team'; -export interface BrowserReadyState { - onBrowserOutput(message: string): void; - onBrowserExit(): void; - waitUntilReady(): Promise<{ wsEndpoint?: string }>; +export abstract class BrowserReadyState { + protected readonly _wsEndpoint = new ManualPromise(); + + onBrowserExit(): void { + // Unblock launch when browser prematurely exits. + this._wsEndpoint.resolve(undefined); + } + async waitUntilReady(): Promise<{ wsEndpoint?: string }> { + const wsEndpoint = await this._wsEndpoint; + return { wsEndpoint }; + } + + abstract onBrowserOutput(message: string): void; } export abstract class BrowserType extends SdkObject { private _name: BrowserName; + _useBidi: boolean = false; constructor(parent: SdkObject, browserName: BrowserName) { super(parent, 'browser-type'); @@ -69,6 +79,8 @@ export abstract class BrowserType extends SdkObject { async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise { options = this._validateLaunchOptions(options); + if (this._useBidi) + options.useWebSocket = true; const controller = new ProgressController(metadata, this); controller.setLogName('browser'); const browser = await controller.run(progress => { @@ -82,6 +94,8 @@ export abstract class BrowserType extends SdkObject { async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise { options = this._validateLaunchOptions(options); + if (this._useBidi) + options.useWebSocket = true; const controller = new ProgressController(metadata, this); const persistent: channels.BrowserNewContextParams = { ...options }; controller.setLogName('browser'); diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index a30be361e7..d84ae3952e 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -24,7 +24,7 @@ import type { Env } from '../../utils/processLauncher'; import { gracefullyCloseSet } from '../../utils/processLauncher'; import { kBrowserCloseMessageId } from './crConnection'; import { BrowserType, kNoXServerRunningError } from '../browserType'; -import type { BrowserReadyState } from '../browserType'; +import { BrowserReadyState } from '../browserType'; import type { ConnectionTransport, ProtocolRequest } from '../transport'; import { WebSocketTransport } from '../transport'; import { CRDevTools } from './crDevTools'; @@ -110,12 +110,6 @@ export class Chromium extends BrowserType { artifactsDir, downloadsPath: options.downloadsPath || artifactsDir, tracesDir: options.tracesDir || artifactsDir, - // On Windows context level proxies only work, if there isn't a global proxy - // set. This is currently a bug in the CR/Windows networking stack. By - // passing an arbitrary value we disable the check in PW land which warns - // users in normal (launch/launchServer) mode since otherwise connectOverCDP - // does not work at all with proxies on Windows. - proxy: { server: 'per-context' }, originalLaunchOptions: {}, }; validateBrowserContextOptions(persistent, browserOptions); @@ -358,21 +352,12 @@ export class Chromium extends BrowserType { } } -class ChromiumReadyState implements BrowserReadyState { - private readonly _wsEndpoint = new ManualPromise(); - - onBrowserOutput(message: string): void { +class ChromiumReadyState extends BrowserReadyState { + override onBrowserOutput(message: string): void { const match = message.match(/DevTools listening on (.*)/); if (match) this._wsEndpoint.resolve(match[1]); } - onBrowserExit(): void { - this._wsEndpoint.resolve(undefined); - } - async waitUntilReady(): Promise<{ wsEndpoint?: string }> { - const wsEndpoint = await this._wsEndpoint; - return { wsEndpoint }; - } } async function urlToWSEndpoint(progress: Progress, endpointURL: string, headers: { [key: string]: string; }) { diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index 850f466afa..a8ff5a08dc 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -609,7 +609,7 @@ class RouteImpl implements network.RouteDelegate { this._interceptionId = interceptionId; } - async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise { + async continue(overrides: types.NormalizedContinueOverrides): Promise { this._alreadyContinuedParams = { requestId: this._interceptionId!, url: overrides.url, diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 904ed5a479..002dfa09be 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -690,15 +690,16 @@ class FrameSession { if (!frame || this._eventBelongsToStaleFrame(frame._id)) return; const delegate = new CRExecutionContext(this._client, contextPayload); - let worldName: types.World|null = null; + let worldName: types.World; if (contextPayload.auxData && !!contextPayload.auxData.isDefault) worldName = 'main'; else if (contextPayload.name === UTILITY_WORLD_NAME) worldName = 'utility'; + else + return; const context = new dom.FrameExecutionContext(delegate, frame, worldName); (context as any)[contextDelegateSymbol] = delegate; - if (worldName) - frame._contextCreated(worldName, context); + frame._contextCreated(worldName, context); this._contextIdToContext.set(contextPayload.id, context); } @@ -897,7 +898,7 @@ class FrameSession { const buffer = Buffer.from(payload.data, 'base64'); this._page.emit(Page.Events.ScreencastFrame, { buffer, - timestamp: payload.metadata.timestamp, + frameSwapWallTime: payload.metadata.timestamp ? payload.metadata.timestamp * 1000 : undefined, width: payload.metadata.deviceWidth, height: payload.metadata.deviceHeight, }); diff --git a/packages/playwright-core/src/server/chromium/protocol.d.ts b/packages/playwright-core/src/server/chromium/protocol.d.ts index 99ce1d3a6a..caadb2a577 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -1131,17 +1131,21 @@ using Audits.issueAdded event. } /** - * Defines commands and events for browser extensions. Available if the client -is connected using the --remote-debugging-pipe flag and -the --enable-unsafe-extension-debugging flag is set. + * Defines commands and events for browser extensions. */ export module Extensions { + /** + * Storage areas. + */ + export type StorageArea = "session"|"local"|"sync"|"managed"; /** * Installs an unpacked extension from the filesystem similar to --load-extension CLI flags. Returns extension ID once the extension -has been installed. +has been installed. Available if the client is connected using the +--remote-debugging-pipe flag and the --enable-unsafe-extension-debugging +flag is set. */ export type loadUnpackedParameters = { /** @@ -1155,6 +1159,81 @@ has been installed. */ id: string; } + /** + * Gets data from extension storage in the given `storageArea`. If `keys` is +specified, these are used to filter the result. + */ + export type getStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to retrieve data from. + */ + storageArea: StorageArea; + /** + * Keys to retrieve. + */ + keys?: string[]; + } + export type getStorageItemsReturnValue = { + data: { [key: string]: string }; + } + /** + * Removes `keys` from extension storage in the given `storageArea`. + */ + export type removeStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to remove data from. + */ + storageArea: StorageArea; + /** + * Keys to remove. + */ + keys: string[]; + } + export type removeStorageItemsReturnValue = { + } + /** + * Clears extension storage in the given `storageArea`. + */ + export type clearStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to remove data from. + */ + storageArea: StorageArea; + } + export type clearStorageItemsReturnValue = { + } + /** + * Sets `values` in extension storage in the given `storageArea`. The provided `values` +will be merged with existing values in the storage area. + */ + export type setStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to set data in. + */ + storageArea: StorageArea; + /** + * Values to set. + */ + values: { [key: string]: string }; + } + export type setStorageItemsReturnValue = { + } } /** @@ -2532,16 +2611,6 @@ stylesheet rules) this rule came from. */ style: CSSStyle; } - /** - * CSS position-fallback rule representation. - */ - export interface CSSPositionFallbackRule { - name: Value; - /** - * List of keyframes. - */ - tryRules: CSSTryRule[]; - } /** * CSS @position-try rule representation. */ @@ -2888,10 +2957,6 @@ attributes) for a DOM node identified by `nodeId`. * A list of CSS keyframed animations matching this node. */ cssKeyframesRules?: CSSKeyframesRule[]; - /** - * A list of CSS position fallbacks matching this node. - */ - cssPositionFallbackRules?: CSSPositionFallbackRule[]; /** * A list of CSS @position-try rules matching this node, based on the position-try-fallbacks property. */ @@ -3496,7 +3561,7 @@ front-end. /** * Pseudo element type. */ - export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"; + export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-next-button"|"scroll-prev-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"; /** * Shadow root type. */ @@ -3646,6 +3711,13 @@ The property is always undefined now. compatibilityMode?: CompatibilityMode; assignedSlot?: BackendNode; } + /** + * A structure to hold the top-level node of a detached tree and an array of its retained descendants. + */ + export interface DetachedElementInfo { + treeNode: Node; + retainedNodeIds: NodeId[]; + } /** * A structure holding an RGBA color. */ @@ -4693,6 +4765,17 @@ File wrapper. export type getFileInfoReturnValue = { path: string; } + /** + * Returns list of detached nodes + */ + export type getDetachedDomNodesParameters = { + } + export type getDetachedDomNodesReturnValue = { + /** + * The list of detached nodes + */ + detachedNodes: DetachedElementInfo[]; + } /** * Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). @@ -11369,7 +11452,7 @@ as an ad. * All Permissions Policy features. This enum should match the one defined in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5. */ - export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -11784,7 +11867,7 @@ Example URLs: http://www.google.com/file.html -> "google.com" */ fixed?: number; } - export type ClientNavigationReason = "formSubmissionGet"|"formSubmissionPost"|"httpHeaderRefresh"|"scriptInitiated"|"metaTagRefresh"|"pageBlockInterstitial"|"reload"|"anchorClick"; + export type ClientNavigationReason = "anchorClick"|"formSubmissionGet"|"formSubmissionPost"|"httpHeaderRefresh"|"initialFrameNavigation"|"metaTagRefresh"|"other"|"pageBlockInterstitial"|"reload"|"scriptInitiated"; export type ClientNavigationDisposition = "currentTab"|"newTab"|"newWindow"|"download"; export interface InstallabilityErrorArgument { /** @@ -12298,6 +12381,10 @@ when bfcache navigation fails. * Frame's new url. */ url: string; + /** + * Navigation type + */ + navigationType: "fragment"|"historyApi"|"other"; } /** * Compressed image data requested by the `startScreencast`. @@ -16922,7 +17009,7 @@ possible for multiple rule sets and links to trigger a single attempt. /** * List of FinalStatus reasons for Prerender2. */ - export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"|"WindowClosed"; + export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"|"WindowClosed"|"SlowNetwork"|"OtherPrerenderedPageActivated"; /** * Preloading status values, see also PreloadingTriggeringOutcome. This status is shared by prefetchStatusUpdated and prerenderStatusUpdated. @@ -17270,6 +17357,101 @@ supported yet. } } + /** + * This domain allows configuring virtual Bluetooth devices to test +the web-bluetooth API. + */ + export module BluetoothEmulation { + /** + * Indicates the various states of Central. + */ + export type CentralState = "absent"|"powered-off"|"powered-on"; + /** + * Stores the manufacturer data + */ + export interface ManufacturerData { + /** + * Company identifier +https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml +https://usb.org/developers + */ + key: number; + /** + * Manufacturer-specific data + */ + data: binary; + } + /** + * Stores the byte data of the advertisement packet sent by a Bluetooth device. + */ + export interface ScanRecord { + name?: string; + uuids?: string[]; + /** + * Stores the external appearance description of the device. + */ + appearance?: number; + /** + * Stores the transmission power of a broadcasting device. + */ + txPower?: number; + /** + * Key is the company identifier and the value is an array of bytes of +manufacturer specific data. + */ + manufacturerData?: ManufacturerData[]; + } + /** + * Stores the advertisement packet information that is sent by a Bluetooth device. + */ + export interface ScanEntry { + deviceAddress: string; + rssi: number; + scanRecord: ScanRecord; + } + + + /** + * Enable the BluetoothEmulation domain. + */ + export type enableParameters = { + /** + * State of the simulated central. + */ + state: CentralState; + } + export type enableReturnValue = { + } + /** + * Disable the BluetoothEmulation domain. + */ + export type disableParameters = { + } + export type disableReturnValue = { + } + /** + * Simulates a peripheral with |address|, |name| and |knownServiceUuids| +that has already been connected to the system. + */ + export type simulatePreconnectedPeripheralParameters = { + address: string; + name: string; + manufacturerData: ManufacturerData[]; + knownServiceUuids: string[]; + } + export type simulatePreconnectedPeripheralReturnValue = { + } + /** + * Simulates an advertisement packet described in |entry| being received by +the central. + */ + export type simulateAdvertisementParameters = { + entry: ScanEntry; + } + export type simulateAdvertisementReturnValue = { + } + } + /** * This domain is deprecated - use Runtime or Log instead. */ @@ -20122,6 +20304,10 @@ Error was thrown. "Audits.checkContrast": Audits.checkContrastParameters; "Audits.checkFormsIssues": Audits.checkFormsIssuesParameters; "Extensions.loadUnpacked": Extensions.loadUnpackedParameters; + "Extensions.getStorageItems": Extensions.getStorageItemsParameters; + "Extensions.removeStorageItems": Extensions.removeStorageItemsParameters; + "Extensions.clearStorageItems": Extensions.clearStorageItemsParameters; + "Extensions.setStorageItems": Extensions.setStorageItemsParameters; "Autofill.trigger": Autofill.triggerParameters; "Autofill.setAddresses": Autofill.setAddressesParameters; "Autofill.disable": Autofill.disableParameters; @@ -20232,6 +20418,7 @@ Error was thrown. "DOM.setNodeStackTracesEnabled": DOM.setNodeStackTracesEnabledParameters; "DOM.getNodeStackTraces": DOM.getNodeStackTracesParameters; "DOM.getFileInfo": DOM.getFileInfoParameters; + "DOM.getDetachedDomNodes": DOM.getDetachedDomNodesParameters; "DOM.setInspectedNode": DOM.setInspectedNodeParameters; "DOM.setNodeName": DOM.setNodeNameParameters; "DOM.setNodeValue": DOM.setNodeValueParameters; @@ -20616,6 +20803,10 @@ Error was thrown. "PWA.launchFilesInApp": PWA.launchFilesInAppParameters; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters; "PWA.changeAppUserSettings": PWA.changeAppUserSettingsParameters; + "BluetoothEmulation.enable": BluetoothEmulation.enableParameters; + "BluetoothEmulation.disable": BluetoothEmulation.disableParameters; + "BluetoothEmulation.simulatePreconnectedPeripheral": BluetoothEmulation.simulatePreconnectedPeripheralParameters; + "BluetoothEmulation.simulateAdvertisement": BluetoothEmulation.simulateAdvertisementParameters; "Console.clearMessages": Console.clearMessagesParameters; "Console.disable": Console.disableParameters; "Console.enable": Console.enableParameters; @@ -20722,6 +20913,10 @@ Error was thrown. "Audits.checkContrast": Audits.checkContrastReturnValue; "Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue; "Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue; + "Extensions.getStorageItems": Extensions.getStorageItemsReturnValue; + "Extensions.removeStorageItems": Extensions.removeStorageItemsReturnValue; + "Extensions.clearStorageItems": Extensions.clearStorageItemsReturnValue; + "Extensions.setStorageItems": Extensions.setStorageItemsReturnValue; "Autofill.trigger": Autofill.triggerReturnValue; "Autofill.setAddresses": Autofill.setAddressesReturnValue; "Autofill.disable": Autofill.disableReturnValue; @@ -20832,6 +21027,7 @@ Error was thrown. "DOM.setNodeStackTracesEnabled": DOM.setNodeStackTracesEnabledReturnValue; "DOM.getNodeStackTraces": DOM.getNodeStackTracesReturnValue; "DOM.getFileInfo": DOM.getFileInfoReturnValue; + "DOM.getDetachedDomNodes": DOM.getDetachedDomNodesReturnValue; "DOM.setInspectedNode": DOM.setInspectedNodeReturnValue; "DOM.setNodeName": DOM.setNodeNameReturnValue; "DOM.setNodeValue": DOM.setNodeValueReturnValue; @@ -21216,6 +21412,10 @@ Error was thrown. "PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue; "PWA.changeAppUserSettings": PWA.changeAppUserSettingsReturnValue; + "BluetoothEmulation.enable": BluetoothEmulation.enableReturnValue; + "BluetoothEmulation.disable": BluetoothEmulation.disableReturnValue; + "BluetoothEmulation.simulatePreconnectedPeripheral": BluetoothEmulation.simulatePreconnectedPeripheralReturnValue; + "BluetoothEmulation.simulateAdvertisement": BluetoothEmulation.simulateAdvertisementReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue; "Console.disable": Console.disableReturnValue; "Console.enable": Console.enableReturnValue; diff --git a/packages/playwright-core/src/server/chromium/videoRecorder.ts b/packages/playwright-core/src/server/chromium/videoRecorder.ts index 25199413f8..68bfbea037 100644 --- a/packages/playwright-core/src/server/chromium/videoRecorder.ts +++ b/packages/playwright-core/src/server/chromium/videoRecorder.ts @@ -53,7 +53,7 @@ export class VideoRecorder { private constructor(page: Page, ffmpegPath: string, progress: Progress) { this._progress = progress; this._ffmpegPath = ffmpegPath; - page.on(Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.timestamp)); + page.on(Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.frameSwapWallTime / 1000)); } private async _launch(options: types.PageScreencastOptions) { diff --git a/packages/playwright-core/src/server/codegen/DEPS.list b/packages/playwright-core/src/server/codegen/DEPS.list new file mode 100644 index 0000000000..58432390fd --- /dev/null +++ b/packages/playwright-core/src/server/codegen/DEPS.list @@ -0,0 +1,3 @@ +[*] +../../utils/ +../deviceDescriptors.ts diff --git a/packages/playwright-core/src/server/recorder/csharp.ts b/packages/playwright-core/src/server/codegen/csharp.ts similarity index 90% rename from packages/playwright-core/src/server/recorder/csharp.ts rename to packages/playwright-core/src/server/codegen/csharp.ts index 52460f8121..2244a372fc 100644 --- a/packages/playwright-core/src/server/recorder/csharp.ts +++ b/packages/playwright-core/src/server/codegen/csharp.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import type { BrowserContextOptions } from '../../..'; -import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; -import { sanitizeDeviceOptions, toSignalMap } from './language'; -import type { ActionInContext } from './codeGenerator'; -import type { Action } from './recorderActions'; -import type { MouseClickOptions } from './utils'; -import { toModifiers } from './utils'; +import type { BrowserContextOptions } from '../../../types/types'; +import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import { sanitizeDeviceOptions, toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { escapeWithQuotes, asLocator } from '../../utils'; import { deviceDescriptors } from '../deviceDescriptors'; @@ -72,14 +68,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator { return formatter.format(); } - let subject: string; - if (actionInContext.frame.isMainFrame) { - subject = pageAlias; - } else { - const locators = actionInContext.frame.selectorsChain.map(selector => `.FrameLocator(${quote(selector)})`); - subject = `${pageAlias}${locators.join('')}`; - } - + const locators = actionInContext.frame.framePath.map(selector => `.${this._asLocator(selector)}.ContentFrame()`); + const subject = `${pageAlias}${locators.join('')}`; const signals = toSignalMap(action); if (signals.dialog) { @@ -93,7 +83,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator { } const lines: string[] = []; - lines.push(this._generateActionCall(subject, action)); + lines.push(this._generateActionCall(subject, actionInContext)); if (signals.download) { lines.unshift(`var download${signals.download.downloadAlias} = await ${pageAlias}.RunAndWaitForDownloadAsync(async () =>\n{`); @@ -111,7 +101,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, action: Action): string { + private _generateActionCall(subject: string, actionInContext: ActionInContext): string { + const action = actionInContext.action; switch (action.name) { case 'openPage': throw Error('Not reached'); @@ -121,16 +112,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator { let method = 'Click'; if (action.clickCount === 2) method = 'DblClick'; - const modifiers = toModifiers(action.modifiers); - const options: MouseClickOptions = {}; - if (action.button !== 'left') - options.button = action.button; - if (modifiers.length) - options.modifiers = modifiers; - if (action.clickCount > 2) - options.clickCount = action.clickCount; - if (action.position) - options.position = action.position; + const options = toClickOptionsForSourceCode(action); if (!Object.entries(options).length) return `await ${subject}.${this._asLocator(action.selector)}.${method}Async();`; const optionsString = formatObject(options, ' ', 'Locator' + method + 'Options'); @@ -145,7 +127,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator { case 'setInputFiles': return `await ${subject}.${this._asLocator(action.selector)}.SetInputFilesAsync(${formatObject(action.files)});`; case 'press': { - const modifiers = toModifiers(action.modifiers); + const modifiers = toKeyboardModifiers(action.modifiers); const shortcut = [...modifiers, action.key].join('+'); return `await ${subject}.${this._asLocator(action.selector)}.PressAsync(${quote(shortcut)});`; } diff --git a/packages/playwright-core/src/server/recorder/java.ts b/packages/playwright-core/src/server/codegen/java.ts similarity index 88% rename from packages/playwright-core/src/server/recorder/java.ts rename to packages/playwright-core/src/server/codegen/java.ts index 72d5d9a995..3a640d36e2 100644 --- a/packages/playwright-core/src/server/recorder/java.ts +++ b/packages/playwright-core/src/server/codegen/java.ts @@ -14,13 +14,10 @@ * limitations under the License. */ -import type { BrowserContextOptions } from '../../..'; -import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; -import { toSignalMap } from './language'; -import type { ActionInContext } from './codeGenerator'; -import type { Action } from './recorderActions'; -import type { MouseClickOptions } from './utils'; -import { toModifiers } from './utils'; +import type { BrowserContextOptions } from '../../../types/types'; +import type * as types from '../types'; +import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import { toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import { JavaScriptFormatter } from './javascript'; import { escapeWithQuotes, asLocator } from '../../utils'; @@ -63,16 +60,8 @@ export class JavaLanguageGenerator implements LanguageGenerator { return formatter.format(); } - let subject: string; - let inFrameLocator = false; - if (actionInContext.frame.isMainFrame) { - subject = pageAlias; - } else { - const locators = actionInContext.frame.selectorsChain.map(selector => `.frameLocator(${quote(selector)})`); - subject = `${pageAlias}${locators.join('')}`; - inFrameLocator = true; - } - + const locators = actionInContext.frame.framePath.map(selector => `.${this._asLocator(selector, false)}.contentFrame()`); + const subject = `${pageAlias}${locators.join('')}`; const signals = toSignalMap(action); if (signals.dialog) { @@ -82,7 +71,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { });`); } - let code = this._generateActionCall(subject, action, inFrameLocator); + let code = this._generateActionCall(subject, actionInContext, !!actionInContext.frame.framePath.length); if (signals.popup) { code = `Page ${signals.popup.popupAlias} = ${pageAlias}.waitForPopup(() -> { @@ -101,7 +90,8 @@ export class JavaLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, action: Action, inFrameLocator: boolean): string { + private _generateActionCall(subject: string, actionInContext: ActionInContext, inFrameLocator: boolean): string { + const action = actionInContext.action; switch (action.name) { case 'openPage': throw Error('Not reached'); @@ -111,16 +101,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { let method = 'click'; if (action.clickCount === 2) method = 'dblclick'; - const modifiers = toModifiers(action.modifiers); - const options: MouseClickOptions = {}; - if (action.button !== 'left') - options.button = action.button; - if (modifiers.length) - options.modifiers = modifiers; - if (action.clickCount > 2) - options.clickCount = action.clickCount; - if (action.position) - options.position = action.position; + const options = toClickOptionsForSourceCode(action); const optionsText = formatClickOptions(options); return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.${method}(${optionsText});`; } @@ -133,7 +114,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { case 'setInputFiles': return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)});`; case 'press': { - const modifiers = toModifiers(action.modifiers); + const modifiers = toKeyboardModifiers(action.modifiers); const shortcut = [...modifiers, action.key].join('+'); return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.press(${quote(shortcut)});`; } @@ -279,7 +260,7 @@ function formatContextOptions(contextOptions: BrowserContextOptions, deviceName: return lines.join('\n'); } -function formatClickOptions(options: MouseClickOptions) { +function formatClickOptions(options: types.MouseClickOptions) { const lines = []; if (options.button) lines.push(` .setButton(MouseButton.${options.button.toUpperCase()})`); diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/codegen/javascript.ts similarity index 88% rename from packages/playwright-core/src/server/recorder/javascript.ts rename to packages/playwright-core/src/server/codegen/javascript.ts index 104b3bcd53..c3ed05d4d4 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/codegen/javascript.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import type { BrowserContextOptions } from '../../..'; -import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; -import { sanitizeDeviceOptions, toSignalMap } from './language'; -import type { ActionInContext } from './codeGenerator'; -import type { Action } from './recorderActions'; -import type { MouseClickOptions } from './utils'; -import { toModifiers } from './utils'; +import type { BrowserContextOptions } from '../../../types/types'; +import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import { sanitizeDeviceOptions, toSignalMap, toKeyboardModifiers, toClickOptionsForSourceCode } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import { escapeWithQuotes, asLocator } from '../../utils'; @@ -52,14 +48,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { return formatter.format(); } - let subject: string; - if (actionInContext.frame.isMainFrame) { - subject = pageAlias; - } else { - const locators = actionInContext.frame.selectorsChain.map(selector => `.frameLocator(${quote(selector)})`); - subject = `${pageAlias}${locators.join('')}`; - } - + const locators = actionInContext.frame.framePath.map(selector => `.${this._asLocator(selector)}.contentFrame()`); + const subject = `${pageAlias}${locators.join('')}`; const signals = toSignalMap(action); if (signals.dialog) { @@ -74,7 +64,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { if (signals.download) formatter.add(`const download${signals.download.downloadAlias}Promise = ${pageAlias}.waitForEvent('download');`); - formatter.add(this._generateActionCall(subject, action)); + formatter.add(wrapWithStep(actionInContext.description, this._generateActionCall(subject, actionInContext))); if (signals.popup) formatter.add(`const ${signals.popup.popupAlias} = await ${signals.popup.popupAlias}Promise;`); @@ -84,7 +74,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, action: Action): string { + private _generateActionCall(subject: string, actionInContext: ActionInContext): string { + const action = actionInContext.action; switch (action.name) { case 'openPage': throw Error('Not reached'); @@ -94,16 +85,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { let method = 'click'; if (action.clickCount === 2) method = 'dblclick'; - const modifiers = toModifiers(action.modifiers); - const options: MouseClickOptions = {}; - if (action.button !== 'left') - options.button = action.button; - if (modifiers.length) - options.modifiers = modifiers; - if (action.clickCount > 2) - options.clickCount = action.clickCount; - if (action.position) - options.position = action.position; + const options = toClickOptionsForSourceCode(action); const optionsString = formatOptions(options, false); return `await ${subject}.${this._asLocator(action.selector)}.${method}(${optionsString});`; } @@ -116,7 +98,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { case 'setInputFiles': return `await ${subject}.${this._asLocator(action.selector)}.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)});`; case 'press': { - const modifiers = toModifiers(action.modifiers); + const modifiers = toKeyboardModifiers(action.modifiers); const shortcut = [...modifiers, action.key].join('+'); return `await ${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)});`; } @@ -276,3 +258,9 @@ export class JavaScriptFormatter { function quote(text: string) { return escapeWithQuotes(text, '\''); } + +function wrapWithStep(description: string | undefined, body: string) { + return description ? `await test.step(\`${description}\`, async () => { +${body} +});` : body; +} diff --git a/packages/playwright-core/src/server/recorder/jsonl.ts b/packages/playwright-core/src/server/codegen/jsonl.ts similarity index 90% rename from packages/playwright-core/src/server/recorder/jsonl.ts rename to packages/playwright-core/src/server/codegen/jsonl.ts index 108d5eadc6..78485297b6 100644 --- a/packages/playwright-core/src/server/recorder/jsonl.ts +++ b/packages/playwright-core/src/server/codegen/jsonl.ts @@ -15,8 +15,7 @@ */ import { asLocator } from '../../utils'; -import type { ActionInContext } from './codeGenerator'; -import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; +import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; export class JsonlLanguageGenerator implements LanguageGenerator { id = 'jsonl'; diff --git a/packages/playwright-core/src/server/codegen/language.ts b/packages/playwright-core/src/server/codegen/language.ts new file mode 100644 index 0000000000..3e0c8f71e5 --- /dev/null +++ b/packages/playwright-core/src/server/codegen/language.ts @@ -0,0 +1,85 @@ +/** + * 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 { BrowserContextOptions } from '../../..'; +import type * as actions from '../recorder/recorderActions'; +import type * as types from '../types'; +import type { ActionInContext, LanguageGenerator, LanguageGeneratorOptions } from './types'; + +export function generateCode(actions: ActionInContext[], languageGenerator: LanguageGenerator, options: LanguageGeneratorOptions) { + const header = languageGenerator.generateHeader(options); + const footer = languageGenerator.generateFooter(options.saveStorage); + const actionTexts = actions.map(a => languageGenerator.generateAction(a)).filter(Boolean); + const text = [header, ...actionTexts, footer].join('\n'); + return { header, footer, actionTexts, text }; +} + +export function sanitizeDeviceOptions(device: any, options: BrowserContextOptions): BrowserContextOptions { + // Filter out all the properties from the device descriptor. + const cleanedOptions: Record = {}; + for (const property in options) { + if (JSON.stringify(device[property]) !== JSON.stringify((options as any)[property])) + cleanedOptions[property] = (options as any)[property]; + } + return cleanedOptions; +} + +export function toSignalMap(action: actions.Action) { + let popup: actions.PopupSignal | undefined; + let download: actions.DownloadSignal | undefined; + let dialog: actions.DialogSignal | undefined; + for (const signal of action.signals) { + if (signal.name === 'popup') + popup = signal; + else if (signal.name === 'download') + download = signal; + else if (signal.name === 'dialog') + dialog = signal; + } + return { + popup, + download, + dialog, + }; +} + +export function toKeyboardModifiers(modifiers: number): types.SmartKeyboardModifier[] { + const result: types.SmartKeyboardModifier[] = []; + if (modifiers & 1) + result.push('Alt'); + if (modifiers & 2) + result.push('ControlOrMeta'); + if (modifiers & 4) + result.push('ControlOrMeta'); + if (modifiers & 8) + result.push('Shift'); + return result; +} + +export function toClickOptionsForSourceCode(action: actions.ClickAction): types.MouseClickOptions { + const modifiers = toKeyboardModifiers(action.modifiers); + const options: types.MouseClickOptions = {}; + if (action.button !== 'left') + options.button = action.button; + if (modifiers.length) + options.modifiers = modifiers; + // Do not render clickCount === 2 for dblclick. + if (action.clickCount > 2) + options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; + return options; +} diff --git a/packages/playwright-core/src/server/codegen/languages.ts b/packages/playwright-core/src/server/codegen/languages.ts new file mode 100644 index 0000000000..d379be6be7 --- /dev/null +++ b/packages/playwright-core/src/server/codegen/languages.ts @@ -0,0 +1,37 @@ +/** + * 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 { JavaLanguageGenerator } from './java'; +import { JavaScriptLanguageGenerator } from './javascript'; +import { JsonlLanguageGenerator } from './jsonl'; +import { CSharpLanguageGenerator } from './csharp'; +import { PythonLanguageGenerator } from './python'; + +export function languageSet() { + return new Set([ + new JavaLanguageGenerator('junit'), + new JavaLanguageGenerator('library'), + new JavaScriptLanguageGenerator(/* isPlaywrightTest */false), + new JavaScriptLanguageGenerator(/* isPlaywrightTest */true), + new PythonLanguageGenerator(/* isAsync */false, /* isPytest */true), + new PythonLanguageGenerator(/* isAsync */false, /* isPytest */false), + new PythonLanguageGenerator(/* isAsync */true, /* isPytest */false), + new CSharpLanguageGenerator('mstest'), + new CSharpLanguageGenerator('nunit'), + new CSharpLanguageGenerator('library'), + new JsonlLanguageGenerator(), + ]); +} diff --git a/packages/playwright-core/src/server/recorder/python.ts b/packages/playwright-core/src/server/codegen/python.ts similarity index 89% rename from packages/playwright-core/src/server/recorder/python.ts rename to packages/playwright-core/src/server/codegen/python.ts index d393fb38e4..6c2b60dc70 100644 --- a/packages/playwright-core/src/server/recorder/python.ts +++ b/packages/playwright-core/src/server/codegen/python.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import type { BrowserContextOptions } from '../../..'; -import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; -import { sanitizeDeviceOptions, toSignalMap } from './language'; -import type { ActionInContext } from './codeGenerator'; -import type { Action } from './recorderActions'; -import type { MouseClickOptions } from './utils'; -import { toModifiers } from './utils'; +import type { BrowserContextOptions } from '../../../types/types'; +import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import { sanitizeDeviceOptions, toSignalMap, toKeyboardModifiers, toClickOptionsForSourceCode } from './language'; import { escapeWithQuotes, toSnakeCase, asLocator } from '../../utils'; import { deviceDescriptors } from '../deviceDescriptors'; @@ -59,20 +55,14 @@ export class PythonLanguageGenerator implements LanguageGenerator { return formatter.format(); } - let subject: string; - if (actionInContext.frame.isMainFrame) { - subject = pageAlias; - } else { - const locators = actionInContext.frame.selectorsChain.map(selector => `.frame_locator(${quote(selector)})`); - subject = `${pageAlias}${locators.join('')}`; - } - + const locators = actionInContext.frame.framePath.map(selector => `.${this._asLocator(selector)}.content_frame()`); + const subject = `${pageAlias}${locators.join('')}`; const signals = toSignalMap(action); if (signals.dialog) formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`); - let code = `${this._awaitPrefix}${this._generateActionCall(subject, action)}`; + let code = `${this._awaitPrefix}${this._generateActionCall(subject, actionInContext)}`; if (signals.popup) { code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info { @@ -93,7 +83,8 @@ export class PythonLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, action: Action): string { + private _generateActionCall(subject: string, actionInContext: ActionInContext): string { + const action = actionInContext.action; switch (action.name) { case 'openPage': throw Error('Not reached'); @@ -103,16 +94,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { let method = 'click'; if (action.clickCount === 2) method = 'dblclick'; - const modifiers = toModifiers(action.modifiers); - const options: MouseClickOptions = {}; - if (action.button !== 'left') - options.button = action.button; - if (modifiers.length) - options.modifiers = modifiers; - if (action.clickCount > 2) - options.clickCount = action.clickCount; - if (action.position) - options.position = action.position; + const options = toClickOptionsForSourceCode(action); const optionsString = formatOptions(options, false); return `${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`; } @@ -125,7 +107,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { case 'setInputFiles': return `${subject}.${this._asLocator(action.selector)}.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`; case 'press': { - const modifiers = toModifiers(action.modifiers); + const modifiers = toKeyboardModifiers(action.modifiers); const shortcut = [...modifiers, action.key].join('+'); return `${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`; } diff --git a/packages/playwright-core/src/server/codegen/types.ts b/packages/playwright-core/src/server/codegen/types.ts new file mode 100644 index 0000000000..96f2aa85d1 --- /dev/null +++ b/packages/playwright-core/src/server/codegen/types.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 type { BrowserContextOptions, LaunchOptions } from '../../../types/types'; +import type * as actions from '../recorder/recorderActions'; +import type { Language } from '../../utils'; +export type { Language } from '../../utils'; + +export type LanguageGeneratorOptions = { + browserName: string; + launchOptions: LaunchOptions; + contextOptions: BrowserContextOptions; + deviceName?: string; + saveStorage?: string; +}; + +export type FrameDescription = { + pageAlias: string; + framePath: string[]; +}; + +export type ActionInContext = { + frame: FrameDescription; + description?: string; + action: actions.Action; + committed?: boolean; +}; + +export interface LanguageGenerator { + id: string; + groupName: string; + name: string; + highlighter: Language; + generateHeader(options: LanguageGeneratorOptions): string; + generateAction(actionInContext: ActionInContext): string; + generateFooter(saveStorage: string | undefined): string; +} diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 1d87485571..2a950d7c6a 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -52,7 +52,6 @@ export class DebugController extends SdkObject { initialize(codegenId: string, sdkLanguage: Language) { this._codegenId = codegenId; this._sdkLanguage = sdkLanguage; - Recorder.setAppFactory(async () => new InspectingRecorderApp(this)); } setAutoCloseAllowed(allowed: boolean) { @@ -62,7 +61,6 @@ export class DebugController extends SdkObject { dispose() { this.setReportStateChanged(false); this.setAutoCloseAllowed(false); - Recorder.setAppFactory(undefined); } setReportStateChanged(enabled: boolean) { @@ -199,7 +197,7 @@ export class DebugController extends SdkObject { const contexts = new Set(); for (const page of this._playwright.allPages()) contexts.add(page.context()); - const result = await Promise.all([...contexts].map(c => Recorder.show(c, { omitCallTracking: true }))); + const result = await Promise.all([...contexts].map(c => Recorder.show(c, () => Promise.resolve(new InspectingRecorderApp(this)), { omitCallTracking: true }))); return result.filter(Boolean) as Recorder[]; } diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index c25989baf5..efb2801f2c 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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -187,7 +187,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/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -1098,7 +1098,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1109,7 +1109,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1120,7 +1120,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1131,7 +1131,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1142,7 +1142,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1153,7 +1153,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1164,7 +1164,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/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1175,7 +1175,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/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1186,7 +1186,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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1197,7 +1197,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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1219,7 +1219,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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1230,7 +1230,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1241,7 +1241,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1252,7 +1252,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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1263,7 +1263,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/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1274,7 +1274,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1285,7 +1285,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1296,7 +1296,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/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1307,7 +1307,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/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1362,7 +1362,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1373,7 +1373,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1384,7 +1384,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1395,7 +1395,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1406,7 +1406,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1417,7 +1417,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/128.0.6613.36 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/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Safari/537.36 Edg/128.0.6613.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36 Edg/129.0.6668.42", "screen": { "width": 1792, "height": 1120 @@ -1592,7 +1592,7 @@ "defaultBrowserType": "chromium" }, "Desktop Firefox HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0", "screen": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.36 Safari/537.36 Edg/128.0.6613.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.42 Safari/537.36 Edg/129.0.6668.42", "screen": { "width": 1920, "height": 1080 @@ -1652,7 +1652,7 @@ "defaultBrowserType": "chromium" }, "Desktop Firefox": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0", "screen": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 5654950360..5c8fa550a7 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -39,6 +39,8 @@ import type { Dialog } from '../dialog'; import type { ConsoleMessage } from '../console'; import { serializeError } from '../errors'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; +import { RecorderInTraceViewer } from '../recorder/recorderInTraceViewer'; +import { RecorderApp } from '../recorder/recorderApp'; export class BrowserContextDispatcher extends Dispatcher implements channels.BrowserContextChannel { _type_EventTarget = true; @@ -291,7 +293,8 @@ export class BrowserContextDispatcher extends Dispatcher { - await Recorder.show(this._context, params); + const factory = process.env.PW_RECORDER_IS_TRACE_VIEWER ? RecorderInTraceViewer.factory(this._context) : RecorderApp.factory(this._context); + await Recorder.show(this._context, factory, params); } async pause(params: channels.BrowserContextPauseParams, metadata: CallMetadata) { diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index 2122f064c9..c411f93198 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -46,6 +46,8 @@ export class PlaywrightDispatcher extends Dispatcher; - readonly world: types.World | null; + readonly world: types.World; - constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame, world: types.World|null) { + constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame, world: types.World) { super(frame, delegate, world || 'content-script'); this.frame = frame; this.world = world; @@ -233,7 +233,7 @@ export class ElementHandle extends js.JSHandle { this._page._timeoutSettings.timeout(options)); } - private async _clickablePoint(): Promise { + private async _clickablePoint(): Promise { const intersectQuadWithViewport = (quad: types.Quad): types.Quad => { return quad.map(point => ({ x: Math.min(Math.max(point.x, 0), metrics.width), @@ -257,6 +257,8 @@ export class ElementHandle extends js.JSHandle { this._page._delegate.getContentQuads(this), this._page.mainFrame()._utilityContext().then(utility => utility.evaluate(() => ({ width: innerWidth, height: innerHeight }))), ] as const); + if (quads === 'error:notconnected') + return quads; if (!quads || !quads.length) return 'error:notvisible'; @@ -536,7 +538,7 @@ export class ElementHandle extends js.JSHandle { return this._retryPointerAction(progress, 'tap', true /* waitForEnabled */, point => this._page.touchscreen.tap(point.x, point.y), { ...options, waitAfter: 'disabled' }); } - async selectOption(metadata: CallMetadata, elements: ElementHandle[], values: types.SelectOption[], options: { noWaitAfter?: boolean } & types.CommonActionOptions): Promise { + async selectOption(metadata: CallMetadata, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { const controller = new ProgressController(metadata, this); return controller.run(async progress => { const result = await this._selectOption(progress, elements, values, options); @@ -544,7 +546,7 @@ export class ElementHandle extends js.JSHandle { }, this._page._timeoutSettings.timeout(options)); } - async _selectOption(progress: Progress, elements: ElementHandle[], values: types.SelectOption[], options: { noWaitAfter?: boolean } & types.CommonActionOptions): Promise { + async _selectOption(progress: Progress, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { let resultingOptions: string[] = []; await this._retryAction(progress, 'select option', async () => { await progress.beforeInputAction(this); diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index a4d9cd9ee2..7a6102a50d 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -155,7 +155,9 @@ export abstract class APIRequestContext extends SdkObject { } const requestUrl = new URL(params.url, defaults.baseURL); - if (params.params) { + if (params.encodedParams) { + requestUrl.search = params.encodedParams; + } else if (params.params) { for (const { name, value } of params.params) requestUrl.searchParams.append(name, value); } diff --git a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts index 266f5bcb83..978eb30bd4 100644 --- a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts +++ b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts @@ -226,7 +226,7 @@ class FFRouteImpl implements network.RouteDelegate { this._request = request; } - async continue(request: network.Request, overrides: types.NormalizedContinueOverrides) { + async continue(overrides: types.NormalizedContinueOverrides) { await this._session.sendMayFail('Network.resumeInterceptedRequest', { requestId: this._request._id, url: overrides.url, diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 6778a777f2..d1066876eb 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -163,15 +163,16 @@ export class FFPage implements PageDelegate { if (!frame) return; const delegate = new FFExecutionContext(this._session, executionContextId); - let worldName: types.World|null = null; + let worldName: types.World; if (auxData.name === UTILITY_WORLD_NAME) worldName = 'utility'; else if (!auxData.name) worldName = 'main'; + else + return; const context = new dom.FrameExecutionContext(delegate, frame, worldName); (context as any)[contextDelegateSymbol] = delegate; - if (worldName) - frame._contextCreated(worldName, context); + frame._contextCreated(worldName, context); this._contextIdToContext.set(executionContextId, context); } diff --git a/packages/playwright-core/src/server/firefox/firefox.ts b/packages/playwright-core/src/server/firefox/firefox.ts index ed2709b685..9fbc409a56 100644 --- a/packages/playwright-core/src/server/firefox/firefox.ts +++ b/packages/playwright-core/src/server/firefox/firefox.ts @@ -20,12 +20,12 @@ import path from 'path'; import { FFBrowser } from './ffBrowser'; import { kBrowserCloseMessageId } from './ffConnection'; import { BrowserType, kNoXServerRunningError } from '../browserType'; -import type { BrowserReadyState } from '../browserType'; +import { BrowserReadyState } from '../browserType'; import type { Env } from '../../utils/processLauncher'; import type { ConnectionTransport } from '../transport'; import type { BrowserOptions } from '../browser'; import type * as types from '../types'; -import { ManualPromise, wrapInASCIIBox } from '../../utils'; +import { wrapInASCIIBox } from '../../utils'; import type { SdkObject } from '../instrumentation'; import type { ProtocolError } from '../protocolError'; @@ -95,20 +95,10 @@ export class Firefox extends BrowserType { } } -class JugglerReadyState implements BrowserReadyState { - private readonly _jugglerPromise = new ManualPromise(); - - onBrowserOutput(message: string): void { +class JugglerReadyState extends BrowserReadyState { + override onBrowserOutput(message: string): void { if (message.includes('Juggler listening to the pipe')) - this._jugglerPromise.resolve(); - } - onBrowserExit(): void { - // Unblock launch when browser prematurely exits. - this._jugglerPromise.resolve(); - } - async waitUntilReady(): Promise<{ wsEndpoint?: string }> { - await this._jugglerPromise; - return { }; + this._wsEndpoint.resolve(undefined); } } diff --git a/packages/playwright-core/src/server/frameSelectors.ts b/packages/playwright-core/src/server/frameSelectors.ts index 66f2e2e514..4be2a9c285 100644 --- a/packages/playwright-core/src/server/frameSelectors.ts +++ b/packages/playwright-core/src/server/frameSelectors.ts @@ -160,7 +160,7 @@ export class FrameSelectors { async function adoptIfNeeded(handle: ElementHandle, context: FrameExecutionContext): Promise> { if (handle._context === context) return handle; - const adopted = handle._page._delegate.adoptElementHandle(handle, context); + const adopted = await handle._page._delegate.adoptElementHandle(handle, context); handle.dispose(); return adopted; } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 931ba8ef73..3b952ea02a 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -291,8 +291,7 @@ export class FrameManager { if (request._documentId) frame.setPendingDocument({ documentId: request._documentId, request }); if (request._isFavicon) { - if (route) - route.continue(request, { isFallback: true }).catch(() => {}); + route?.continue({ isFallback: true }).catch(() => {}); return; } this._page.emitOnContext(BrowserContext.Events.Request, request); @@ -800,7 +799,7 @@ export class Frame extends SdkObject { const result = await resolved.injected.evaluateHandle((injected, { info, root }) => { const elements = injected.querySelectorAll(info.parsed, root || document); const element: Element | undefined = elements[0]; - const visible = element ? injected.isVisible(element) : false; + const visible = element ? injected.utils.isElementVisible(element) : false; let log = ''; if (elements.length > 1) { if (info.strict) @@ -1344,7 +1343,7 @@ export class Frame extends SdkObject { }, this._page._timeoutSettings.timeout(options)); } - async selectOption(metadata: CallMetadata, selector: string, elements: dom.ElementHandle[], values: types.SelectOption[], options: { noWaitAfter?: boolean } & types.CommonActionOptions = {}): Promise { + async selectOption(metadata: CallMetadata, selector: string, elements: dom.ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions = {}): Promise { const controller = new ProgressController(metadata, this); return controller.run(async progress => { return await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force /* performLocatorHandlersCheckpoint */, handle => handle._selectOption(progress, elements, values, options)); diff --git a/packages/playwright-core/src/server/injected/.eslintrc.js b/packages/playwright-core/src/server/injected/.eslintrc.js index e96e2a9f80..eccd5b787d 100644 --- a/packages/playwright-core/src/server/injected/.eslintrc.js +++ b/packages/playwright-core/src/server/injected/.eslintrc.js @@ -1,10 +1,21 @@ +const path = require('path'); + module.exports = { - rules: { - "no-restricted-globals": [ - "error", - { "name": "window" }, - { "name": "document" }, - { "name": "globalThis" }, - ] - } + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint", "notice"], + parserOptions: { + ecmaVersion: 9, + sourceType: "module", + project: path.join(__dirname, '../../../../../tsconfig.json'), + }, + rules: { + "no-restricted-globals": [ + "error", + { "name": "window" }, + { "name": "document" }, + { "name": "globalThis" }, + ], + '@typescript-eslint/no-floating-promises': 'error', + "@typescript-eslint/no-unnecessary-boolean-literal-compare": 2, + }, }; diff --git a/packages/playwright-core/src/server/injected/clock.ts b/packages/playwright-core/src/server/injected/clock.ts index 48cc9276a2..b2daf190f3 100644 --- a/packages/playwright-core/src/server/injected/clock.ts +++ b/packages/playwright-core/src/server/injected/clock.ts @@ -216,7 +216,7 @@ export class ClockController { const sinceLastSync = now - this._realTime!.lastSyncTicks; this._realTime!.lastSyncTicks = now; // eslint-disable-next-line no-console - this._runTo(shiftTicks(this._now.ticks, sinceLastSync)).catch(e => console.error(e)).then(() => this._updateRealTimeTimer()); + void this._runTo(shiftTicks(this._now.ticks, sinceLastSync)).catch(e => console.error(e)).then(() => this._updateRealTimeTimer()); }, callAt - this._now.ticks), }; } @@ -239,7 +239,12 @@ export class ClockController { addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: any[] }): number { this._replayLogOnce(); - if (options.func === undefined) + + if (options.type === TimerType.AnimationFrame && !options.func) + throw new Error('Callback must be provided to requestAnimationFrame calls'); + if (options.type === TimerType.IdleCallback && !options.func) + throw new Error('Callback must be provided to requestIdleCallback calls'); + if ([TimerType.Timeout, TimerType.Interval].includes(options.type) && !options.func && options.delay === undefined) throw new Error('Callback must be provided to timer calls'); let delay = options.delay ? +options.delay : 0; diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 2323648bae..69fe959f81 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -29,11 +29,13 @@ import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser'; import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator'; import type * as channels from '@protocol/channels'; import { Highlight } from './highlight'; -import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription } from './roleUtils'; +import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription, beginAriaCaches, endAriaCaches } from './roleUtils'; import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; -import { cacheNormalizedWhitespaces, normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils'; +import { cacheNormalizedWhitespaces, escapeHTML, escapeHTMLAttribute, normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils'; +import { selectorForSimpleDomNodeId, generateSimpleDomNode } from './simpleDom'; +import type { SimpleDomNode } from './simpleDom'; export type FrameExpectParams = Omit & { expectedValue?: any }; @@ -66,7 +68,25 @@ export class InjectedScript { // eslint-disable-next-line no-restricted-globals readonly window: Window & typeof globalThis; readonly document: Document; - readonly utils = { isInsideScope, elementText, asLocator, normalizeWhiteSpace, cacheNormalizedWhitespaces }; + + // Recorder must use any external dependencies through InjectedScript. + // Otherwise it will end up with a copy of all modules it uses, and any + // module-level globals will be duplicated, which leads to subtle bugs. + readonly utils = { + asLocator, + beginAriaCaches, + cacheNormalizedWhitespaces, + elementText, + endAriaCaches, + escapeHTML, + escapeHTMLAttribute, + getAriaRole, + getElementAccessibleDescription, + getElementAccessibleName, + isElementVisible, + isInsideScope, + normalizeWhiteSpace, + }; // eslint-disable-next-line no-restricted-globals constructor(window: Window & typeof globalThis, isUnderTest: boolean, sdkLanguage: Language, testIdAttributeNameForStrictErrorAndConsoleCodegen: string, stableRafCount: number, browserName: string, customEngines: { name: string, engine: SelectorEngine }[]) { @@ -426,10 +446,6 @@ export class InjectedScript { return new constrFunction(this, params); } - isVisible(element: Element): boolean { - return isElementVisible(element); - } - async viewportRatio(element: Element): Promise { return await new Promise(resolve => { const observer = new IntersectionObserver(entries => { @@ -567,9 +583,9 @@ export class InjectedScript { } if (state === 'visible') - return this.isVisible(element); + return isElementVisible(element); if (state === 'hidden') - return !this.isVisible(element); + return !isElementVisible(element); const disabled = getAriaDisabled(element); if (state === 'disabled') @@ -1297,16 +1313,15 @@ export class InjectedScript { throw this.createStacklessError('Unknown expect matcher: ' + expression); } - getElementAccessibleName(element: Element, includeHidden?: boolean): string { - return getElementAccessibleName(element, !!includeHidden); + generateSimpleDomNode(selector: string): SimpleDomNode | undefined { + const element = this.querySelector(this.parseSelector(selector), this.document.documentElement, true); + if (!element) + return; + return generateSimpleDomNode(this, element); } - getElementAccessibleDescription(element: Element, includeHidden?: boolean): string { - return getElementAccessibleDescription(element, !!includeHidden); - } - - getAriaRole(element: Element) { - return getAriaRole(element); + selectorForSimpleDomNodeId(nodeId: string) { + return selectorForSimpleDomNodeId(this, nodeId); } } diff --git a/packages/playwright-core/src/server/injected/recorder/DEPS.list b/packages/playwright-core/src/server/injected/recorder/DEPS.list index ee39467fea..1f58b3d5d0 100644 --- a/packages/playwright-core/src/server/injected/recorder/DEPS.list +++ b/packages/playwright-core/src/server/injected/recorder/DEPS.list @@ -1,4 +1,4 @@ -# Recorder must use any external dependencies through InjectedScript. +# Recorder must use any external dependencies through injectedScript.utils. # Otherwise it will end up with a copy of all modules it uses, and any # module-level globals will be duplicated, which leads to subtle bugs. [*] diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index 3432f159dd..48639fefc8 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -21,9 +21,10 @@ import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; import type { ElementText } from '../selectorUtils'; import type { Highlight, HighlightOptions } from '../highlight'; import clipPaths from './clipPaths'; +import type { SimpleDomNode } from '../simpleDom'; interface RecorderDelegate { - performAction?(action: actions.Action): Promise; + performAction?(action: actions.PerformOnRecordAction): Promise; recordAction?(action: actions.Action): Promise; setSelector?(selector: string): Promise; setMode?(mode: Mode): Promise; @@ -35,6 +36,7 @@ interface RecorderTool { cursor(): string; cleanup?(): void; onClick?(event: MouseEvent): void; + onDblClick?(event: MouseEvent): void; onContextMenu?(event: MouseEvent): void; onDragStart?(event: DragEvent): void; onInput?(event: Event): void; @@ -168,7 +170,7 @@ class InspectTool implements RecorderTool { if (this._hoveredModel?.tooltipListItemSelected) this._reset(true); else if (this._assertVisibility) - this._recorder.delegate.setMode?.('recording'); + this._recorder.setMode('recording'); } } @@ -182,15 +184,15 @@ class InspectTool implements RecorderTool { private _commit(selector: string) { if (this._assertVisibility) { - this._recorder.delegate.recordAction?.({ + this._recorder.recordAction({ name: 'assertVisible', selector, signals: [], }); - this._recorder.delegate.setMode?.('recording'); + this._recorder.setMode('recording'); this._recorder.overlay?.flashToolSucceeded('assertingVisibility'); } else { - this._recorder.delegate.setSelector?.(selector); + this._recorder.setSelector(selector); } } @@ -209,6 +211,7 @@ class RecordActionTool implements RecorderTool { private _hoveredElement: HTMLElement | null = null; private _activeModel: HighlightModel | null = null; private _expectProgrammaticKeyUp = false; + private _pendingClickAction: { action: actions.ClickAction, timeout: NodeJS.Timeout } | undefined; constructor(recorder: Recorder) { this._recorder = recorder; @@ -251,6 +254,38 @@ class RecordActionTool implements RecorderTool { return; } + this._cancelPendingClickAction(); + + // Stall click in case we are observing double-click. + if (event.detail === 1) { + this._pendingClickAction = { + action: { + name: 'click', + selector: this._hoveredModel!.selector, + position: positionForEvent(event), + signals: [], + button: buttonForEvent(event), + modifiers: modifiersForEvent(event), + clickCount: event.detail + }, + timeout: setTimeout(() => this._commitPendingClickAction(), 200) + }; + } + } + + onDblClick(event: MouseEvent) { + if (isRangeInput(this._hoveredElement)) + return; + if (this._shouldIgnoreMouseEvent(event)) + return; + // Only allow double click dispatch while action is in progress. + if (this._actionInProgress(event)) + return; + if (this._consumedDueToNoModel(event, this._hoveredModel)) + return; + + this._cancelPendingClickAction(); + this._performAction({ name: 'click', selector: this._hoveredModel!.selector, @@ -262,6 +297,18 @@ class RecordActionTool implements RecorderTool { }); } + private _commitPendingClickAction() { + if (this._pendingClickAction) + this._performAction(this._pendingClickAction.action); + this._cancelPendingClickAction(); + } + + private _cancelPendingClickAction() { + if (this._pendingClickAction) + clearTimeout(this._pendingClickAction.timeout); + this._pendingClickAction = undefined; + } + onContextMenu(event: MouseEvent) { // the 'contextmenu' event is triggered by a right-click or equivalent action, // and it prevents the click event from firing for that action, so we always @@ -338,7 +385,7 @@ class RecordActionTool implements RecorderTool { const target = this._recorder.deepEventTarget(event); if (target.nodeName === 'INPUT' && (target as HTMLInputElement).type.toLowerCase() === 'file') { - this._recorder.delegate.recordAction?.({ + this._recorder.recordAction({ name: 'setInputFiles', selector: this._activeModel!.selector, signals: [], @@ -348,7 +395,7 @@ class RecordActionTool implements RecorderTool { } if (isRangeInput(target)) { - this._recorder.delegate.recordAction?.({ + this._recorder.recordAction({ name: 'fill', // must use hoveredModel instead of activeModel for it to work in webkit selector: this._hoveredModel!.selector, @@ -367,7 +414,7 @@ class RecordActionTool implements RecorderTool { // Non-navigating actions are simply recorded by Playwright. if (this._consumedDueWrongTarget(event)) return; - this._recorder.delegate.recordAction?.({ + this._recorder.recordAction({ name: 'fill', selector: this._activeModel!.selector, signals: [], @@ -483,26 +530,27 @@ class RecordActionTool implements RecorderTool { return true; } - private async _performAction(action: actions.Action) { + private _performAction(action: actions.PerformOnRecordAction) { this._hoveredElement = null; this._hoveredModel = null; this._activeModel = null; this._recorder.updateHighlight(null, false); this._performingAction = true; - await this._recorder.delegate.performAction?.(action).catch(() => {}); - this._performingAction = false; + void this._recorder.performAction(action).then(() => { + this._performingAction = false; - // If that was a keyboard action, it similarly requires new selectors for active model. - this._onFocus(false); + // If that was a keyboard action, it similarly requires new selectors for active model. + this._onFocus(false); - if (this._recorder.injectedScript.isUnderTest) { - // Serialize all to string as we cannot attribute console message to isolated world - // in Firefox. - console.error('Action performed for test: ' + JSON.stringify({ // eslint-disable-line no-console - hovered: this._hoveredModel ? (this._hoveredModel as any).selector : null, - active: this._activeModel ? (this._activeModel as any).selector : null, - })); - } + if (this._recorder.injectedScript.isUnderTest) { + // Serialize all to string as we cannot attribute console message to isolated world + // in Firefox. + console.error('Action performed for test: ' + JSON.stringify({ // eslint-disable-line no-console + hovered: this._hoveredModel ? (this._hoveredModel as any).selector : null, + active: this._activeModel ? (this._activeModel as any).selector : null, + })); + } + }); } private _shouldGenerateKeyPressFor(event: KeyboardEvent): boolean { @@ -613,7 +661,7 @@ class TextAssertionTool implements RecorderTool { onKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') - this._recorder.delegate.setMode?.('recording'); + this._recorder.setMode('recording'); consumeEvent(event); } @@ -680,8 +728,8 @@ class TextAssertionTool implements RecorderTool { if (!this._action || !this._dialog.isShowing()) return; this._dialog.close(); - this._recorder.delegate.recordAction?.(this._action); - this._recorder.delegate.setMode?.('recording'); + this._recorder.recordAction(this._action); + this._recorder.setMode('recording'); } private _showDialog() { @@ -726,8 +774,8 @@ class TextAssertionTool implements RecorderTool { const action = this._generateAction(); if (!action) return; - this._recorder.delegate.recordAction?.(action); - this._recorder.delegate.setMode?.('recording'); + this._recorder.recordAction(action); + this._recorder.setMode('recording'); this._recorder.overlay?.flashToolSucceeded('assertingValue'); } } @@ -799,7 +847,7 @@ class Overlay { this._dragState = { offsetX: this._offsetX, dragStart: { x: (event as MouseEvent).clientX, y: 0 } }; }), addEventListener(this._recordToggle, 'click', () => { - this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'standby' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'standby'); + this._recorder.setMode(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'standby' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'standby'); }), addEventListener(this._pickLocatorToggle, 'click', () => { const newMode: Record = { @@ -812,19 +860,19 @@ class Overlay { 'assertingVisibility': 'recording-inspecting', 'assertingValue': 'recording-inspecting', }; - this._recorder.delegate.setMode?.(newMode[this._recorder.state.mode]); + this._recorder.setMode(newMode[this._recorder.state.mode]); }), addEventListener(this._assertVisibilityToggle, 'click', () => { if (!this._assertVisibilityToggle.classList.contains('disabled')) - this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingVisibility' ? 'recording' : 'assertingVisibility'); + this._recorder.setMode(this._recorder.state.mode === 'assertingVisibility' ? 'recording' : 'assertingVisibility'); }), addEventListener(this._assertTextToggle, 'click', () => { if (!this._assertTextToggle.classList.contains('disabled')) - this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText'); + this._recorder.setMode(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText'); }), addEventListener(this._assertValuesToggle, 'click', () => { if (!this._assertValuesToggle.classList.contains('disabled')) - this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingValue' ? 'recording' : 'assertingValue'); + this._recorder.setMode(this._recorder.state.mode === 'assertingValue' ? 'recording' : 'assertingValue'); }), ]; } @@ -890,7 +938,7 @@ class Overlay { const halfGapSize = (this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 - 10; this._offsetX = Math.max(-halfGapSize, Math.min(halfGapSize, this._offsetX)); this._updateVisualPosition(); - this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX }); + this._recorder.setOverlayState({ offsetX: this._offsetX }); consumeEvent(event); return true; } @@ -913,6 +961,10 @@ class Overlay { } return false; } + + onDblClick(event: MouseEvent) { + return false; + } } export class Recorder { @@ -924,9 +976,14 @@ export class Recorder { readonly highlight: Highlight; readonly overlay: Overlay | undefined; private _stylesheet: CSSStyleSheet; - state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } }; + state: UIState = { + mode: 'none', + testIdAttributeName: 'data-testid', + language: 'javascript', + overlay: { offsetX: 0 }, + }; readonly document: Document; - delegate: RecorderDelegate = {}; + private _delegate: RecorderDelegate = {}; constructor(injectedScript: InjectedScript) { this.document = injectedScript.document; @@ -963,6 +1020,7 @@ export class Recorder { this._listeners = [ addEventListener(this.document, 'click', event => this._onClick(event as MouseEvent), true), addEventListener(this.document, 'auxclick', event => this._onClick(event as MouseEvent), true), + addEventListener(this.document, 'dblclick', event => this._onDblClick(event as MouseEvent), true), addEventListener(this.document, 'contextmenu', event => this._onContextMenu(event as MouseEvent), true), addEventListener(this.document, 'dragstart', event => this._onDragStart(event as DragEvent), true), addEventListener(this.document, 'input', event => this._onInput(event), true), @@ -994,7 +1052,7 @@ export class Recorder { } setUIState(state: UIState, delegate: RecorderDelegate) { - this.delegate = delegate; + this._delegate = delegate; if (state.actionPoint && this.state.actionPoint && state.actionPoint.x === this.state.actionPoint.x && state.actionPoint.y === this.state.actionPoint.y) { // All good. @@ -1036,6 +1094,16 @@ export class Recorder { this._currentTool.onClick?.(event); } + private _onDblClick(event: MouseEvent) { + if (!event.isTrusted) + return; + if (this.overlay?.onDblClick(event)) + return; + if (this._ignoreOverlayEvent(event)) + return; + this._currentTool.onDblClick?.(event); + } + private _onContextMenu(event: MouseEvent) { if (!event.isTrusted) return; @@ -1155,7 +1223,7 @@ export class Recorder { tooltipText = this.injectedScript.utils.asLocator(this.state.language, model.selector); this.highlight.updateHighlight(model?.elements || [], { ...model, tooltipText }); if (userGesture) - this.delegate.highlightUpdated?.(); + this._delegate.highlightUpdated?.(); } private _ignoreOverlayEvent(event: Event) { @@ -1172,6 +1240,26 @@ export class Recorder { } return event.composedPath()[0] as HTMLElement; } + + setMode(mode: Mode) { + void this._delegate.setMode?.(mode); + } + + async performAction(action: actions.PerformOnRecordAction) { + await this._delegate.performAction?.(action).catch(() => {}); + } + + recordAction(action: actions.Action) { + void this._delegate.recordAction?.(action); + } + + setOverlayState(state: { offsetX: number; }) { + void this._delegate.setOverlayState?.(state); + } + + setSelector(selector: string) { + void this._delegate.setSelector?.(selector); + } } class Dialog { @@ -1361,8 +1449,8 @@ function createSvgElement(doc: Document, { tagName, attrs, children }: SvgJson): } interface Embedder { - __pw_recorderPerformAction(action: actions.Action): Promise; - __pw_recorderRecordAction(action: actions.Action): Promise; + __pw_recorderPerformAction(action: actions.PerformOnRecordAction, simpleDomNode?: SimpleDomNode): Promise; + __pw_recorderRecordAction(action: actions.Action, simpleDomNode?: SimpleDomNode): Promise; __pw_recorderState(): Promise; __pw_recorderSetSelector(selector: string): Promise; __pw_recorderSetMode(mode: Mode): Promise; @@ -1407,12 +1495,12 @@ export class PollingRecorder implements RecorderDelegate { this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod); } - async performAction(action: actions.Action) { - await this._embedder.__pw_recorderPerformAction(action); + async performAction(action: actions.PerformOnRecordAction, simpleDomNode?: SimpleDomNode) { + await this._embedder.__pw_recorderPerformAction(action, simpleDomNode); } - async recordAction(action: actions.Action): Promise { - await this._embedder.__pw_recorderRecordAction(action); + async recordAction(action: actions.Action, simpleDomNode?: SimpleDomNode): Promise { + await this._embedder.__pw_recorderRecordAction(action, simpleDomNode); } async setSelector(selector: string): Promise { diff --git a/packages/playwright-core/src/server/injected/simpleDom.ts b/packages/playwright-core/src/server/injected/simpleDom.ts new file mode 100644 index 0000000000..c31862cd6c --- /dev/null +++ b/packages/playwright-core/src/server/injected/simpleDom.ts @@ -0,0 +1,120 @@ +/** + * 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 { InjectedScript } from './injectedScript'; + +const leafRoles = new Set([ + 'button', + 'checkbox', + 'combobox', + 'link', + 'textbox', +]); + +export type SimpleDom = { + markup: string; + elements: Map; +}; + +export type SimpleDomNode = { + dom: SimpleDom; + id: string; + tag: string; +}; + +let lastDom: SimpleDom | undefined; + +export function generateSimpleDom(injectedScript: InjectedScript): SimpleDom { + return generate(injectedScript).dom; +} + +export function generateSimpleDomNode(injectedScript: InjectedScript, target: Element): SimpleDomNode { + return generate(injectedScript, target).node!; +} + +export function selectorForSimpleDomNodeId(injectedScript: InjectedScript, id: string): string { + const element = lastDom?.elements.get(id); + if (!element) + throw new Error(`Internal error: element with id "${id}" not found`); + return injectedScript.generateSelectorSimple(element); +} + +function generate(injectedScript: InjectedScript, target?: Element): { dom: SimpleDom, node?: SimpleDomNode } { + const normalizeWhitespace = (text: string) => text.replace(/[\s\n]+/g, match => match.includes('\n') ? '\n' : ' '); + const tokens: string[] = []; + const elements = new Map(); + let lastId = 0; + let resultTarget: { tag: string, id: string } | undefined; + const visit = (node: Node) => { + if (node.nodeType === Node.TEXT_NODE) { + tokens.push(node.nodeValue!); + return; + } + + if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as Element; + if (element.nodeName === 'SCRIPT' || element.nodeName === 'STYLE' || element.nodeName === 'NOSCRIPT') + return; + if (injectedScript.utils.isElementVisible(element)) { + const role = injectedScript.utils.getAriaRole(element) as string; + if (role && leafRoles.has(role)) { + let value: string | undefined; + if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') + value = (element as HTMLInputElement | HTMLTextAreaElement).value; + const name = injectedScript.utils.getElementAccessibleName(element, false); + const structuralId = String(++lastId); + elements.set(structuralId, element); + tokens.push(renderTag(injectedScript, role, name, structuralId, { value })); + if (element === target) { + const tagNoValue = renderTag(injectedScript, role, name, structuralId); + resultTarget = { tag: tagNoValue, id: structuralId }; + } + return; + } + } + for (let child = element.firstChild; child; child = child.nextSibling) + visit(child); + } + }; + injectedScript.utils.beginAriaCaches(); + try { + visit(injectedScript.document.body); + } finally { + injectedScript.utils.endAriaCaches(); + } + const dom = { + markup: normalizeWhitespace(tokens.join(' ')), + elements + }; + + if (target && !resultTarget) + throw new Error('Target element is not in the simple DOM'); + + lastDom = dom; + + return { dom, node: resultTarget ? { dom, ...resultTarget } : undefined }; +} + +function renderTag(injectedScript: InjectedScript, role: string, name: string, id: string, params?: { value?: string }): string { + const escapedTextContent = injectedScript.utils.escapeHTML(name); + const escapedValue = injectedScript.utils.escapeHTMLAttribute(params?.value || ''); + switch (role) { + case 'button': return ``; + case 'link': return `${escapedTextContent}`; + case 'textbox': return ``; + } + return `
${escapedTextContent}
`; +} diff --git a/packages/playwright-core/src/server/input.ts b/packages/playwright-core/src/server/input.ts index f09f91b86f..4e4c95a8f3 100644 --- a/packages/playwright-core/src/server/input.ts +++ b/packages/playwright-core/src/server/input.ts @@ -215,7 +215,7 @@ export class Mouse { async click(x: number, y: number, options: { delay?: number, button?: types.MouseButton, clickCount?: number } = {}, metadata?: CallMetadata) { if (metadata) - metadata.point = { x: this._x, y: this._y }; + metadata.point = { x, y }; const { delay = null, clickCount = 1 } = options; if (delay) { this.move(x, y, { forClick: true }); diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index fd62e1751b..42c94fe97b 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -108,6 +108,7 @@ export class Request extends SdkObject { private _waitForResponsePromise = new ManualPromise(); _responseEndTiming = -1; private _overrides: NormalizedContinueOverrides | undefined; + private _bodySize: number | undefined; constructor(context: contexts.BrowserContext, frame: frames.Frame | null, serviceWorker: pages.Worker | null, redirectedFrom: Request | null, documentId: string | undefined, url: string, resourceType: string, method: string, postData: Buffer | null, headers: HeadersArray) { @@ -223,8 +224,13 @@ export class Request extends SdkObject { }; } + // TODO(bidi): remove once post body is available. + _setBodySize(size: number) { + this._bodySize = size; + } + bodySize(): number { - return this.postDataBuffer()?.length || 0; + return this._bodySize || this.postDataBuffer()?.length || 0; } async requestHeadersSize(): Promise { @@ -324,7 +330,7 @@ export class Route extends SdkObject { this._request._setOverrides(overrides); if (!overrides.isFallback) this._request._context.emit(BrowserContext.Events.RequestContinued, this._request); - await this._delegate.continue(this._request, overrides); + await this._delegate.continue(overrides); this._endHandling(); } @@ -612,7 +618,7 @@ export class WebSocket extends SdkObject { export interface RouteDelegate { abort(errorCode: string): Promise; fulfill(response: types.NormalizedFulfillResponse): Promise; - continue(request: Request, overrides: types.NormalizedContinueOverrides): Promise; + continue(overrides: types.NormalizedContinueOverrides): Promise; } // List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes. diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index e0436968ec..f2e59aa56f 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -76,7 +76,7 @@ export interface PageDelegate { adoptElementHandle(handle: dom.ElementHandle, to: dom.FrameExecutionContext): Promise>; getContentFrame(handle: dom.ElementHandle): Promise; // Only called for frame owner elements. getOwnerFrame(handle: dom.ElementHandle): Promise; // Returns frameId. - getContentQuads(handle: dom.ElementHandle): Promise; + getContentQuads(handle: dom.ElementHandle): Promise; setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise; setInputFilePaths(handle: dom.ElementHandle, files: string[]): Promise; getBoundingBox(handle: dom.ElementHandle): Promise; @@ -846,6 +846,8 @@ export class PageBinding { const handle = await context.evaluateHandle(takeHandle, { name, seq }).catch(e => null); result = await binding.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, handle); } else { + if (!Array.isArray(serializedArgs)) + throw new Error(`serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly`); const args = serializedArgs!.map(a => parseEvaluationResultValue(a)); result = await binding.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args); } diff --git a/packages/playwright-core/src/server/playwright.ts b/packages/playwright-core/src/server/playwright.ts index f33c2b3699..722400b6fc 100644 --- a/packages/playwright-core/src/server/playwright.ts +++ b/packages/playwright-core/src/server/playwright.ts @@ -28,6 +28,8 @@ import { debugLogger, type Language } from '../utils'; import type { Page } from './page'; import { DebugController } from './debugController'; import type { BrowserType } from './browserType'; +import { BidiChromium } from './bidi/bidiChromium'; +import { BidiFirefox } from './bidi/bidiFirefox'; type PlaywrightOptions = { socksProxyPort?: number; @@ -43,6 +45,8 @@ export class Playwright extends SdkObject { readonly electron: Electron; readonly firefox: BrowserType; readonly webkit: BrowserType; + readonly bidiChromium: BrowserType; + readonly bidiFirefox: BrowserType; readonly options: PlaywrightOptions; readonly debugController: DebugController; private _allPages = new Set(); @@ -62,6 +66,8 @@ export class Playwright extends SdkObject { } }, null); this.chromium = new Chromium(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/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 93f706581f..5e197f871f 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -14,43 +14,24 @@ * limitations under the License. */ -import * as fs from 'fs'; -import type * as actions from './recorder/recorderActions'; import type * as channels from '@protocol/channels'; -import type { ActionInContext } from './recorder/codeGenerator'; -import { CodeGenerator } from './recorder/codeGenerator'; -import { toClickOptions, toModifiers } from './recorder/utils'; -import { Page } from './page'; -import { Frame } from './frames'; -import { BrowserContext } from './browserContext'; -import { JavaLanguageGenerator } from './recorder/java'; -import { JavaScriptLanguageGenerator } from './recorder/javascript'; -import { JsonlLanguageGenerator } from './recorder/jsonl'; -import { CSharpLanguageGenerator } from './recorder/csharp'; -import { PythonLanguageGenerator } from './recorder/python'; -import * as recorderSource from '../generated/recorderSource'; -import * as consoleApiSource from '../generated/consoleApiSource'; -import { EmptyRecorderApp } from './recorder/recorderApp'; -import type { IRecorderApp } from './recorder/recorderApp'; -import { RecorderApp } from './recorder/recorderApp'; -import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; -import type { Point } from '../common/types'; import type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes'; -import { createGuid, isUnderTest, monotonicTime } from '../utils'; -import { metadataToCallLog } from './recorder/recorderUtils'; -import { Debugger } from './debugger'; -import { EventEmitter } from 'events'; -import { raceAgainstDeadline } from '../utils/timeoutRunner'; -import type { Language, LanguageGenerator } from './recorder/language'; +import * as fs from 'fs'; +import type { Point } from '../common/types'; +import * as consoleApiSource from '../generated/consoleApiSource'; +import { isUnderTest } from '../utils'; import { locatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser'; -import { quoteCSSAttributeValue, eventsHelper, type RegisteredListener } from '../utils'; -import type { Dialog } from './dialog'; - -type BindingSource = { frame: Frame, page: Page }; +import { BrowserContext } from './browserContext'; +import { type Language } from './codegen/types'; +import { Debugger } from './debugger'; +import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; +import { ContextRecorder, generateFrameSelector } from './recorder/contextRecorder'; +import type { IRecorderAppFactory, IRecorderApp, IRecorder } from './recorder/recorderFrontend'; +import { buildFullSelector, metadataToCallLog } from './recorder/recorderUtils'; const recorderSymbol = Symbol('recorderSymbol'); -export class Recorder implements InstrumentationListener { +export class Recorder implements InstrumentationListener, IRecorder { private _context: BrowserContext; private _mode: Mode; private _highlightedSelector = ''; @@ -61,40 +42,38 @@ export class Recorder implements InstrumentationListener { private _userSources = new Map(); private _debugger: Debugger; private _contextRecorder: ContextRecorder; - private _handleSIGINT: boolean | undefined; private _omitCallTracking = false; private _currentLanguage: Language; - private static recorderAppFactory: ((recorder: Recorder) => Promise) | undefined; - - static setAppFactory(recorderAppFactory: ((recorder: Recorder) => Promise) | undefined) { - Recorder.recorderAppFactory = recorderAppFactory; - } - - static showInspector(context: BrowserContext) { + static showInspector(context: BrowserContext, recorderAppFactory: IRecorderAppFactory) { const params: channels.BrowserContextRecorderSupplementEnableParams = {}; if (isUnderTest()) params.language = process.env.TEST_INSPECTOR_LANGUAGE; - Recorder.show(context, params).catch(() => {}); + Recorder.show(context, recorderAppFactory, params).catch(() => {}); } - static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise { + static show(context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise { let recorderPromise = (context as any)[recorderSymbol] as Promise; if (!recorderPromise) { - const recorder = new Recorder(context, params); - recorderPromise = recorder.install().then(() => recorder); + recorderPromise = Recorder._create(context, recorderAppFactory, params); (context as any)[recorderSymbol] = recorderPromise; } return recorderPromise; } + private static async _create(context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise { + const recorder = new Recorder(context, params); + const recorderApp = await recorderAppFactory(recorder); + await recorder._install(recorderApp); + return recorder; + } + constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) { this._mode = params.mode || 'none'; - this._contextRecorder = new ContextRecorder(context, params); + this._contextRecorder = new ContextRecorder(context, params, {}); this._context = context; this._omitCallTracking = !!params.omitCallTracking; this._debugger = context.debugger(); - this._handleSIGINT = params.handleSIGINT; context.instrumentation.addListener(this, context); this._currentLanguage = this._contextRecorder.languageName(); @@ -104,14 +83,7 @@ export class Recorder implements InstrumentationListener { } } - private static async defaultRecorderAppFactory(recorder: Recorder) { - if (process.env.PW_CODEGEN_NO_INSPECTOR) - return new EmptyRecorderApp(); - return await RecorderApp.open(recorder, recorder._context, recorder._handleSIGINT); - } - - async install() { - const recorderApp = await (Recorder.recorderAppFactory || Recorder.defaultRecorderAppFactory)(this); + private async _install(recorderApp: IRecorderApp) { this._recorderApp = recorderApp; recorderApp.once('close', () => { this._debugger.resume(false); @@ -158,7 +130,7 @@ export class Recorder implements InstrumentationListener { this._context.once(BrowserContext.Events.Close, () => { this._contextRecorder.dispose(); this._context.instrumentation.removeListener(this); - recorderApp.close().catch(() => {}); + this._recorderApp?.close().catch(() => {}); }); this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], primaryFileName: string }) => { this._recorderSources = data.sources; @@ -191,15 +163,8 @@ export class Recorder implements InstrumentationListener { }); await this._context.exposeBinding('__pw_recorderSetSelector', false, async ({ frame }, selector: string) => { - const selectorPromises: Promise[] = []; - let currentFrame: Frame | null = frame; - while (currentFrame) { - selectorPromises.push(findFrameSelector(currentFrame)); - currentFrame = currentFrame.parentFrame(); - } - const fullSelector = (await Promise.all(selectorPromises)).filter(Boolean); - fullSelector.push(selector); - await this._recorderApp?.setSelector(fullSelector.join(' >> internal:control=enter-frame >> '), true); + const selectorChain = await generateFrameSelector(frame); + await this._recorderApp?.setSelector(buildFullSelector(selectorChain, selector), true); }); await this._context.exposeBinding('__pw_recorderSetMode', false, async ({ frame }, mode: Mode) => { @@ -225,7 +190,7 @@ export class Recorder implements InstrumentationListener { this._pausedStateChanged(); this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged()); - (this._context as any).recorderAppForTest = recorderApp; + (this._context as any).recorderAppForTest = this._recorderApp; } _pausedStateChanged() { @@ -369,329 +334,8 @@ export class Recorder implements InstrumentationListener { } } -class ContextRecorder extends EventEmitter { - static Events = { - Change: 'change' - }; - - private _generator: CodeGenerator; - private _pageAliases = new Map(); - private _lastPopupOrdinal = 0; - private _lastDialogOrdinal = -1; - private _lastDownloadOrdinal = -1; - private _timers = new Set(); - private _context: BrowserContext; - private _params: channels.BrowserContextRecorderSupplementEnableParams; - private _recorderSources: Source[]; - private _throttledOutputFile: ThrottledFile | null = null; - private _orderedLanguages: LanguageGenerator[] = []; - private _listeners: RegisteredListener[] = []; - - constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) { - super(); - this._context = context; - this._params = params; - this._recorderSources = []; - const language = params.language || context.attribution.playwright.options.sdkLanguage; - this.setOutput(language, params.outputFile); - const generator = new CodeGenerator(context._browser.options.name, params.mode === 'recording', params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage); - generator.on('change', () => { - this._recorderSources = []; - for (const languageGenerator of this._orderedLanguages) { - const { header, footer, actions, text } = generator.generateStructure(languageGenerator); - const source: Source = { - isRecorded: true, - label: languageGenerator.name, - group: languageGenerator.groupName, - id: languageGenerator.id, - text, - header, - footer, - actions, - language: languageGenerator.highlighter, - highlight: [] - }; - source.revealLine = text.split('\n').length - 1; - this._recorderSources.push(source); - if (languageGenerator === this._orderedLanguages[0]) - this._throttledOutputFile?.setContent(source.text); - } - this.emit(ContextRecorder.Events.Change, { - sources: this._recorderSources, - primaryFileName: this._orderedLanguages[0].id - }); - }); - context.on(BrowserContext.Events.BeforeClose, () => { - this._throttledOutputFile?.flush(); - }); - this._listeners.push(eventsHelper.addEventListener(process, 'exit', () => { - this._throttledOutputFile?.flush(); - })); - this._generator = generator; - } - - setOutput(codegenId: string, outputFile?: string) { - const languages = new Set([ - new JavaLanguageGenerator('junit'), - new JavaLanguageGenerator('library'), - new JavaScriptLanguageGenerator(/* isPlaywrightTest */false), - new JavaScriptLanguageGenerator(/* isPlaywrightTest */true), - new PythonLanguageGenerator(/* isAsync */false, /* isPytest */true), - new PythonLanguageGenerator(/* isAsync */false, /* isPytest */false), - new PythonLanguageGenerator(/* isAsync */true, /* isPytest */false), - new CSharpLanguageGenerator('mstest'), - new CSharpLanguageGenerator('nunit'), - new CSharpLanguageGenerator('library'), - new JsonlLanguageGenerator(), - ]); - const primaryLanguage = [...languages].find(l => l.id === codegenId); - if (!primaryLanguage) - throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`); - languages.delete(primaryLanguage); - this._orderedLanguages = [primaryLanguage, ...languages]; - this._throttledOutputFile = outputFile ? new ThrottledFile(outputFile) : null; - this._generator?.restart(); - } - - languageName(id?: string): Language { - for (const lang of this._orderedLanguages) { - if (!id || lang.id === id) - return lang.highlighter; - } - return 'javascript'; - } - - async install() { - this._context.on(BrowserContext.Events.Page, (page: Page) => this._onPage(page)); - for (const page of this._context.pages()) - this._onPage(page); - this._context.on(BrowserContext.Events.Dialog, (dialog: Dialog) => this._onDialog(dialog.page())); - - // Input actions that potentially lead to navigation are intercepted on the page and are - // performed by the Playwright. - await this._context.exposeBinding('__pw_recorderPerformAction', false, - (source: BindingSource, action: actions.Action) => this._performAction(source.frame, action)); - - // Other non-essential actions are simply being recorded. - await this._context.exposeBinding('__pw_recorderRecordAction', false, - (source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action)); - - await this._context.extendInjectedScript(recorderSource.source); - } - - setEnabled(enabled: boolean) { - this._generator.setEnabled(enabled); - } - - dispose() { - for (const timer of this._timers) - clearTimeout(timer); - this._timers.clear(); - eventsHelper.removeEventListeners(this._listeners); - } - - private async _onPage(page: Page) { - // First page is called page, others are called popup1, popup2, etc. - const frame = page.mainFrame(); - page.on('close', () => { - this._generator.addAction({ - frame: this._describeMainFrame(page), - committed: true, - action: { - name: 'closePage', - signals: [], - } - }); - this._pageAliases.delete(page); - }); - frame.on(Frame.Events.InternalNavigation, event => { - if (event.isPublic) - this._onFrameNavigated(frame, page); - }); - page.on(Page.Events.Download, () => this._onDownload(page)); - const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : ''; - const pageAlias = 'page' + suffix; - this._pageAliases.set(page, pageAlias); - - if (page.opener()) { - this._onPopup(page.opener()!, page); - } else { - this._generator.addAction({ - frame: this._describeMainFrame(page), - committed: true, - action: { - name: 'openPage', - url: page.mainFrame().url(), - signals: [], - } - }); - } - } - - clearScript(): void { - this._generator.restart(); - if (this._params.mode === 'recording') { - for (const page of this._context.pages()) - this._onFrameNavigated(page.mainFrame(), page); - } - } - - private _describeMainFrame(page: Page): actions.FrameDescription { - return { - pageAlias: this._pageAliases.get(page)!, - isMainFrame: true, - }; - } - - private async _describeFrame(frame: Frame): Promise { - const page = frame._page; - const pageAlias = this._pageAliases.get(page)!; - const chain: Frame[] = []; - for (let ancestor: Frame | null = frame; ancestor; ancestor = ancestor.parentFrame()) - chain.push(ancestor); - chain.reverse(); - - if (chain.length === 1) - return this._describeMainFrame(page); - - const selectorPromises: Promise[] = []; - for (let i = 0; i < chain.length - 1; i++) - selectorPromises.push(findFrameSelector(chain[i + 1])); - - const result = await raceAgainstDeadline(() => Promise.all(selectorPromises), monotonicTime() + 2000); - if (!result.timedOut && result.result.every(selector => !!selector)) { - return { - pageAlias, - isMainFrame: false, - selectorsChain: result.result as string[], - }; - } - // Best effort to find a selector for the frame. - const selectorsChain = []; - for (let i = 0; i < chain.length - 1; i++) { - if (chain[i].name()) - selectorsChain.push(`iframe[name=${quoteCSSAttributeValue(chain[i].name())}]`); - else - selectorsChain.push(`iframe[src=${quoteCSSAttributeValue(chain[i].url())}]`); - } - return { - pageAlias, - isMainFrame: false, - selectorsChain, - }; - } - - testIdAttributeName(): string { - return this._params.testIdAttributeName || this._context.selectors().testIdAttributeName() || 'data-testid'; - } - - private async _performAction(frame: Frame, action: actions.Action) { - // Commit last action so that no further signals are added to it. - this._generator.commitLastAction(); - - const frameDescription = await this._describeFrame(frame); - const actionInContext: ActionInContext = { - frame: frameDescription, - action - }; - - const perform = async (action: string, params: any, cb: (callMetadata: CallMetadata) => Promise) => { - const callMetadata: CallMetadata = { - id: `call@${createGuid()}`, - apiName: 'frame.' + action, - objectId: frame.guid, - pageId: frame._page.guid, - frameId: frame.guid, - startTime: monotonicTime(), - endTime: 0, - type: 'Frame', - method: action, - params, - log: [], - }; - this._generator.willPerformAction(actionInContext); - - try { - await frame.instrumentation.onBeforeCall(frame, callMetadata); - await cb(callMetadata); - } catch (e) { - callMetadata.endTime = monotonicTime(); - await frame.instrumentation.onAfterCall(frame, callMetadata); - this._generator.performedActionFailed(actionInContext); - return; - } - - callMetadata.endTime = monotonicTime(); - await frame.instrumentation.onAfterCall(frame, callMetadata); - - this._setCommittedAfterTimeout(actionInContext); - this._generator.didPerformAction(actionInContext); - }; - - const kActionTimeout = 5000; - if (action.name === 'click') { - const { options } = toClickOptions(action); - await perform('click', { selector: action.selector }, callMetadata => frame.click(callMetadata, action.selector, { ...options, timeout: kActionTimeout, strict: true })); - } - if (action.name === 'press') { - const modifiers = toModifiers(action.modifiers); - const shortcut = [...modifiers, action.key].join('+'); - await perform('press', { selector: action.selector, key: shortcut }, callMetadata => frame.press(callMetadata, action.selector, shortcut, { timeout: kActionTimeout, strict: true })); - } - if (action.name === 'check') - await perform('check', { selector: action.selector }, callMetadata => frame.check(callMetadata, action.selector, { timeout: kActionTimeout, strict: true })); - if (action.name === 'uncheck') - await perform('uncheck', { selector: action.selector }, callMetadata => frame.uncheck(callMetadata, action.selector, { timeout: kActionTimeout, strict: true })); - if (action.name === 'select') { - const values = action.options.map(value => ({ value })); - await perform('selectOption', { selector: action.selector, values }, callMetadata => frame.selectOption(callMetadata, action.selector, [], values, { timeout: kActionTimeout, strict: true })); - } - } - - private async _recordAction(frame: Frame, action: actions.Action) { - // Commit last action so that no further signals are added to it. - this._generator.commitLastAction(); - - const frameDescription = await this._describeFrame(frame); - const actionInContext: ActionInContext = { - frame: frameDescription, - action - }; - this._setCommittedAfterTimeout(actionInContext); - this._generator.addAction(actionInContext); - } - - private _setCommittedAfterTimeout(actionInContext: ActionInContext) { - const timer = setTimeout(() => { - // Commit the action after 5 seconds so that no further signals are added to it. - actionInContext.committed = true; - this._timers.delete(timer); - }, isUnderTest() ? 500 : 5000); - this._timers.add(timer); - } - - private _onFrameNavigated(frame: Frame, page: Page) { - const pageAlias = this._pageAliases.get(page); - this._generator.signal(pageAlias!, frame, { name: 'navigation', url: frame.url() }); - } - - private _onPopup(page: Page, popup: Page) { - const pageAlias = this._pageAliases.get(page)!; - const popupAlias = this._pageAliases.get(popup)!; - this._generator.signal(pageAlias, page.mainFrame(), { name: 'popup', popupAlias }); - } - - private _onDownload(page: Page) { - const pageAlias = this._pageAliases.get(page)!; - ++this._lastDownloadOrdinal; - this._generator.signal(pageAlias, page.mainFrame(), { name: 'download', downloadAlias: this._lastDownloadOrdinal ? String(this._lastDownloadOrdinal) : '' }); - } - - private _onDialog(page: Page) { - const pageAlias = this._pageAliases.get(page)!; - ++this._lastDialogOrdinal; - this._generator.signal(pageAlias, page.mainFrame(), { name: 'dialog', dialogAlias: this._lastDialogOrdinal ? String(this._lastDialogOrdinal) : '' }); - } +function isScreenshotCommand(metadata: CallMetadata) { + return metadata.method.toLowerCase().includes('screenshot'); } function languageForFile(file: string) { @@ -703,49 +347,3 @@ function languageForFile(file: string) { return 'csharp'; return 'javascript'; } - -class ThrottledFile { - private _file: string; - private _timer: NodeJS.Timeout | undefined; - private _text: string | undefined; - - constructor(file: string) { - this._file = file; - } - - setContent(text: string) { - this._text = text; - if (!this._timer) - this._timer = setTimeout(() => this.flush(), 250); - } - - flush(): void { - if (this._timer) { - clearTimeout(this._timer); - this._timer = undefined; - } - if (this._text) - fs.writeFileSync(this._file, this._text); - this._text = undefined; - } -} - -function isScreenshotCommand(metadata: CallMetadata) { - return metadata.method.toLowerCase().includes('screenshot'); -} - -async function findFrameSelector(frame: Frame): Promise { - try { - const parent = frame.parentFrame(); - const frameElement = await frame.frameElement(); - if (!frameElement || !parent) - return; - const utility = await parent._utilityContext(); - const injected = await utility.injectedScript(); - const selector = await injected.evaluate((injected, element) => { - return injected.generateSelectorSimple(element as Element, { testIdAttributeName: '', omitInternalEngines: true }); - }, frameElement); - return selector; - } catch (e) { - } -} diff --git a/packages/playwright-core/src/server/recorder/DEPS.list b/packages/playwright-core/src/server/recorder/DEPS.list index 69c4226c68..f3bbfc23bf 100644 --- a/packages/playwright-core/src/server/recorder/DEPS.list +++ b/packages/playwright-core/src/server/recorder/DEPS.list @@ -1,9 +1,15 @@ [*] ../ +../codegen/language.ts +../codegen/languages.ts ../isomorphic/** ../registry/** ../../common/ +../../generated/recorderSource.ts ../../protocol/ ../../utils/** ../../utilsBundle.ts ../../zipBundle.ts + +[recorderInTraceViewer.ts] +../trace/viewer/traceViewer.ts diff --git a/packages/playwright-core/src/server/recorder/codeGenerator.ts b/packages/playwright-core/src/server/recorder/codeGenerator.ts deleted file mode 100644 index d3bb5f86d9..0000000000 --- a/packages/playwright-core/src/server/recorder/codeGenerator.ts +++ /dev/null @@ -1,168 +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. - */ - -import { EventEmitter } from 'events'; -import type { BrowserContextOptions, LaunchOptions } from '../../..'; -import type { Frame } from '../frames'; -import type { LanguageGenerator, LanguageGeneratorOptions } from './language'; -import type { Action, Signal, FrameDescription } from './recorderActions'; - -export type ActionInContext = { - frame: FrameDescription; - action: Action; - committed?: boolean; -}; - -export class CodeGenerator extends EventEmitter { - private _currentAction: ActionInContext | null = null; - private _lastAction: ActionInContext | null = null; - private _actions: ActionInContext[] = []; - private _enabled: boolean; - private _options: LanguageGeneratorOptions; - - constructor(browserName: string, enabled: boolean, launchOptions: LaunchOptions, contextOptions: BrowserContextOptions, deviceName: string | undefined, saveStorage: string | undefined) { - super(); - - // Make a copy of options to modify them later. - launchOptions = { headless: false, ...launchOptions }; - contextOptions = { ...contextOptions }; - this._enabled = enabled; - this._options = { browserName, launchOptions, contextOptions, deviceName, saveStorage }; - this.restart(); - } - - restart() { - this._currentAction = null; - this._lastAction = null; - this._actions = []; - this.emit('change'); - } - - setEnabled(enabled: boolean) { - this._enabled = enabled; - } - - addAction(action: ActionInContext) { - if (!this._enabled) - return; - this.willPerformAction(action); - this.didPerformAction(action); - } - - willPerformAction(action: ActionInContext) { - if (!this._enabled) - return; - this._currentAction = action; - } - - performedActionFailed(action: ActionInContext) { - if (!this._enabled) - return; - if (this._currentAction === action) - this._currentAction = null; - } - - didPerformAction(actionInContext: ActionInContext) { - if (!this._enabled) - return; - const action = actionInContext.action; - let eraseLastAction = false; - if (this._lastAction && this._lastAction.frame.pageAlias === actionInContext.frame.pageAlias) { - const lastAction = this._lastAction.action; - // We augment last action based on the type. - if (this._lastAction && action.name === 'fill' && lastAction.name === 'fill') { - if (action.selector === lastAction.selector) - eraseLastAction = true; - } - if (lastAction && action.name === 'click' && lastAction.name === 'click') { - if (action.selector === lastAction.selector && action.clickCount > lastAction.clickCount) - eraseLastAction = true; - } - if (lastAction && action.name === 'navigate' && lastAction.name === 'navigate') { - if (action.url === lastAction.url) { - // Already at a target URL. - this._currentAction = null; - return; - } - } - // Check and uncheck erase click. - if (lastAction && (action.name === 'check' || action.name === 'uncheck') && lastAction.name === 'click') { - if (action.selector === lastAction.selector) - eraseLastAction = true; - } - } - - this._lastAction = actionInContext; - this._currentAction = null; - if (eraseLastAction) - this._actions.pop(); - this._actions.push(actionInContext); - this.emit('change'); - } - - commitLastAction() { - if (!this._enabled) - return; - const action = this._lastAction; - if (action) - action.committed = true; - } - - signal(pageAlias: string, frame: Frame, signal: Signal) { - if (!this._enabled) - return; - - // Signal either arrives while action is being performed or shortly after. - if (this._currentAction) { - this._currentAction.action.signals.push(signal); - return; - } - - if (this._lastAction && (!this._lastAction.committed || signal.name !== 'navigation')) { - const signals = this._lastAction.action.signals; - if (signal.name === 'navigation' && signals.length && signals[signals.length - 1].name === 'download') - return; - if (signal.name === 'download' && signals.length && signals[signals.length - 1].name === 'navigation') - signals.length = signals.length - 1; - this._lastAction.action.signals.push(signal); - this.emit('change'); - return; - } - - if (signal.name === 'navigation' && frame._page.mainFrame() === frame) { - this.addAction({ - frame: { - pageAlias, - isMainFrame: true, - }, - committed: true, - action: { - name: 'navigate', - url: frame.url(), - signals: [], - }, - }); - } - } - - generateStructure(languageGenerator: LanguageGenerator) { - const header = languageGenerator.generateHeader(this._options); - const footer = languageGenerator.generateFooter(this._options.saveStorage); - const actions = this._actions.map(a => languageGenerator.generateAction(a)).filter(Boolean); - const text = [header, ...actions, footer].join('\n'); - return { header, footer, actions, text }; - } -} diff --git a/packages/playwright-core/src/server/recorder/contextRecorder.ts b/packages/playwright-core/src/server/recorder/contextRecorder.ts new file mode 100644 index 0000000000..9b4efb9e65 --- /dev/null +++ b/packages/playwright-core/src/server/recorder/contextRecorder.ts @@ -0,0 +1,335 @@ +/** + * 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 channels from '@protocol/channels'; +import type { Source } from '@recorder/recorderTypes'; +import { EventEmitter } from 'events'; +import * as recorderSource from '../../generated/recorderSource'; +import { eventsHelper, isUnderTest, monotonicTime, quoteCSSAttributeValue, type RegisteredListener } from '../../utils'; +import { raceAgainstDeadline } from '../../utils/timeoutRunner'; +import { BrowserContext } from '../browserContext'; +import type { ActionInContext, FrameDescription, LanguageGeneratorOptions, Language, LanguageGenerator } from '../codegen/types'; +import { languageSet } from '../codegen/languages'; +import type { Dialog } from '../dialog'; +import { Frame } from '../frames'; +import { Page } from '../page'; +import type * as actions from './recorderActions'; +import { performAction } from './recorderRunner'; +import { ThrottledFile } from './throttledFile'; +import { RecorderCollection } from './recorderCollection'; +import { generateCode } from '../codegen/language'; + +type BindingSource = { frame: Frame, page: Page }; + +export interface ContextRecorderDelegate { + rewriteActionInContext?(pageAliases: Map, actionInContext: ActionInContext): Promise; +} + +export class ContextRecorder extends EventEmitter { + static Events = { + Change: 'change' + }; + + private _collection: RecorderCollection; + private _pageAliases = new Map(); + private _lastPopupOrdinal = 0; + private _lastDialogOrdinal = -1; + private _lastDownloadOrdinal = -1; + private _timers = new Set(); + private _context: BrowserContext; + private _params: channels.BrowserContextRecorderSupplementEnableParams; + private _delegate: ContextRecorderDelegate; + private _recorderSources: Source[]; + private _throttledOutputFile: ThrottledFile | null = null; + private _orderedLanguages: LanguageGenerator[] = []; + private _listeners: RegisteredListener[] = []; + + constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams, delegate: ContextRecorderDelegate) { + super(); + this._context = context; + this._params = params; + this._delegate = delegate; + this._recorderSources = []; + const language = params.language || context.attribution.playwright.options.sdkLanguage; + this.setOutput(language, params.outputFile); + + // Make a copy of options to modify them later. + const languageGeneratorOptions: LanguageGeneratorOptions = { + browserName: context._browser.options.name, + launchOptions: { headless: false, ...params.launchOptions, tracesDir: undefined }, + contextOptions: { ...params.contextOptions }, + deviceName: params.device, + saveStorage: params.saveStorage, + }; + + const collection = new RecorderCollection(this._pageAliases, params.mode === 'recording'); + collection.on('change', () => { + this._recorderSources = []; + for (const languageGenerator of this._orderedLanguages) { + const { header, footer, actionTexts, text } = generateCode(collection.actions(), languageGenerator, languageGeneratorOptions); + const source: Source = { + isRecorded: true, + label: languageGenerator.name, + group: languageGenerator.groupName, + id: languageGenerator.id, + text, + header, + footer, + actions: actionTexts, + language: languageGenerator.highlighter, + highlight: [] + }; + source.revealLine = text.split('\n').length - 1; + this._recorderSources.push(source); + if (languageGenerator === this._orderedLanguages[0]) + this._throttledOutputFile?.setContent(source.text); + } + this.emit(ContextRecorder.Events.Change, { + sources: this._recorderSources, + primaryFileName: this._orderedLanguages[0].id + }); + }); + context.on(BrowserContext.Events.BeforeClose, () => { + this._throttledOutputFile?.flush(); + }); + this._listeners.push(eventsHelper.addEventListener(process, 'exit', () => { + this._throttledOutputFile?.flush(); + })); + this._collection = collection; + } + + setOutput(codegenId: string, outputFile?: string) { + const languages = languageSet(); + const primaryLanguage = [...languages].find(l => l.id === codegenId); + if (!primaryLanguage) + throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`); + languages.delete(primaryLanguage); + this._orderedLanguages = [primaryLanguage, ...languages]; + this._throttledOutputFile = outputFile ? new ThrottledFile(outputFile) : null; + this._collection?.restart(); + } + + languageName(id?: string): Language { + for (const lang of this._orderedLanguages) { + if (!id || lang.id === id) + return lang.highlighter; + } + return 'javascript'; + } + + async install() { + this._context.on(BrowserContext.Events.Page, (page: Page) => this._onPage(page)); + for (const page of this._context.pages()) + this._onPage(page); + this._context.on(BrowserContext.Events.Dialog, (dialog: Dialog) => this._onDialog(dialog.page())); + + // Input actions that potentially lead to navigation are intercepted on the page and are + // performed by the Playwright. + await this._context.exposeBinding('__pw_recorderPerformAction', false, + (source: BindingSource, action: actions.PerformOnRecordAction) => this._performAction(source.frame, action)); + + // Other non-essential actions are simply being recorded. + await this._context.exposeBinding('__pw_recorderRecordAction', false, + (source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action)); + + await this._context.extendInjectedScript(recorderSource.source); + } + + setEnabled(enabled: boolean) { + this._collection.setEnabled(enabled); + } + + dispose() { + for (const timer of this._timers) + clearTimeout(timer); + this._timers.clear(); + eventsHelper.removeEventListeners(this._listeners); + } + + private async _onPage(page: Page) { + // First page is called page, others are called popup1, popup2, etc. + const frame = page.mainFrame(); + page.on('close', () => { + this._collection.addRecordedAction({ + frame: this._describeMainFrame(page), + committed: true, + action: { + name: 'closePage', + signals: [], + } + }); + this._pageAliases.delete(page); + }); + frame.on(Frame.Events.InternalNavigation, event => { + if (event.isPublic) + this._onFrameNavigated(frame, page); + }); + page.on(Page.Events.Download, () => this._onDownload(page)); + const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : ''; + const pageAlias = 'page' + suffix; + this._pageAliases.set(page, pageAlias); + + if (page.opener()) { + this._onPopup(page.opener()!, page); + } else { + this._collection.addRecordedAction({ + frame: this._describeMainFrame(page), + committed: true, + action: { + name: 'openPage', + url: page.mainFrame().url(), + signals: [], + } + }); + } + } + + clearScript(): void { + this._collection.restart(); + if (this._params.mode === 'recording') { + for (const page of this._context.pages()) + this._onFrameNavigated(page.mainFrame(), page); + } + } + + private _describeMainFrame(page: Page): FrameDescription { + return { + pageAlias: this._pageAliases.get(page)!, + framePath: [], + }; + } + + private async _describeFrame(frame: Frame): Promise { + return { + pageAlias: this._pageAliases.get(frame._page)!, + framePath: await generateFrameSelector(frame), + }; + } + + testIdAttributeName(): string { + return this._params.testIdAttributeName || this._context.selectors().testIdAttributeName() || 'data-testid'; + } + + private async _performAction(frame: Frame, action: actions.PerformOnRecordAction) { + // Commit last action so that no further signals are added to it. + this._collection.commitLastAction(); + + const frameDescription = await this._describeFrame(frame); + const actionInContext: ActionInContext = { + frame: frameDescription, + action, + description: undefined, + }; + + await this._delegate.rewriteActionInContext?.(this._pageAliases, actionInContext); + + const callMetadata = await this._collection.willPerformAction(actionInContext); + if (!callMetadata) + return; + const error = await performAction(callMetadata, this._pageAliases, actionInContext).then(() => undefined).catch((e: Error) => e); + await this._collection.didPerformAction(callMetadata, actionInContext, error); + if (error) + actionInContext.committed = true; + else + this._setCommittedAfterTimeout(actionInContext); + } + + private async _recordAction(frame: Frame, action: actions.Action) { + // Commit last action so that no further signals are added to it. + this._collection.commitLastAction(); + + const frameDescription = await this._describeFrame(frame); + const actionInContext: ActionInContext = { + frame: frameDescription, + action, + description: undefined, + }; + + await this._delegate.rewriteActionInContext?.(this._pageAliases, actionInContext); + + this._setCommittedAfterTimeout(actionInContext); + this._collection.addRecordedAction(actionInContext); + } + + private _setCommittedAfterTimeout(actionInContext: ActionInContext) { + const timer = setTimeout(() => { + // Commit the action after 5 seconds so that no further signals are added to it. + actionInContext.committed = true; + this._timers.delete(timer); + }, isUnderTest() ? 500 : 5000); + this._timers.add(timer); + } + + private _onFrameNavigated(frame: Frame, page: Page) { + const pageAlias = this._pageAliases.get(page); + this._collection.signal(pageAlias!, frame, { name: 'navigation', url: frame.url() }); + } + + private _onPopup(page: Page, popup: Page) { + const pageAlias = this._pageAliases.get(page)!; + const popupAlias = this._pageAliases.get(popup)!; + this._collection.signal(pageAlias, page.mainFrame(), { name: 'popup', popupAlias }); + } + + private _onDownload(page: Page) { + const pageAlias = this._pageAliases.get(page)!; + ++this._lastDownloadOrdinal; + this._collection.signal(pageAlias, page.mainFrame(), { name: 'download', downloadAlias: this._lastDownloadOrdinal ? String(this._lastDownloadOrdinal) : '' }); + } + + private _onDialog(page: Page) { + const pageAlias = this._pageAliases.get(page)!; + ++this._lastDialogOrdinal; + this._collection.signal(pageAlias, page.mainFrame(), { name: 'dialog', dialogAlias: this._lastDialogOrdinal ? String(this._lastDialogOrdinal) : '' }); + } +} + +export async function generateFrameSelector(frame: Frame): Promise { + const selectorPromises: Promise[] = []; + while (frame) { + const parent = frame.parentFrame(); + if (!parent) + break; + selectorPromises.push(generateFrameSelectorInParent(parent, frame)); + frame = parent; + } + const result = await Promise.all(selectorPromises); + return result.reverse(); +} + +async function generateFrameSelectorInParent(parent: Frame, frame: Frame): Promise { + const result = await raceAgainstDeadline(async () => { + try { + const frameElement = await frame.frameElement(); + if (!frameElement || !parent) + return; + const utility = await parent._utilityContext(); + const injected = await utility.injectedScript(); + const selector = await injected.evaluate((injected, element) => { + return injected.generateSelectorSimple(element as Element); + }, frameElement); + return selector; + } catch (e) { + return e.toString(); + } + }, monotonicTime() + 2000); + if (!result.timedOut && result.result) + return result.result; + + if (frame.name()) + return `iframe[name=${quoteCSSAttributeValue(frame.name())}]`; + return `iframe[src=${quoteCSSAttributeValue(frame.url())}]`; +} diff --git a/packages/playwright-core/src/server/recorder/language.ts b/packages/playwright-core/src/server/recorder/language.ts deleted file mode 100644 index cee2b22163..0000000000 --- a/packages/playwright-core/src/server/recorder/language.ts +++ /dev/null @@ -1,71 +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. - */ - -import type { BrowserContextOptions, LaunchOptions } from '../../..'; -import type { Language } from '../../utils'; -import type { ActionInContext } from './codeGenerator'; -import type { Action, DialogSignal, DownloadSignal, PopupSignal } from './recorderActions'; -export type { Language } from '../../utils'; - -export type LanguageGeneratorOptions = { - browserName: string; - launchOptions: LaunchOptions; - contextOptions: BrowserContextOptions; - deviceName?: string; - saveStorage?: string; -}; - -export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text'; -export type LocatorBase = 'page' | 'locator' | 'frame-locator'; - -export interface LanguageGenerator { - id: string; - groupName: string; - name: string; - highlighter: Language; - generateHeader(options: LanguageGeneratorOptions): string; - generateAction(actionInContext: ActionInContext): string; - generateFooter(saveStorage: string | undefined): string; -} - -export function sanitizeDeviceOptions(device: any, options: BrowserContextOptions): BrowserContextOptions { - // Filter out all the properties from the device descriptor. - const cleanedOptions: Record = {}; - for (const property in options) { - if (JSON.stringify(device[property]) !== JSON.stringify((options as any)[property])) - cleanedOptions[property] = (options as any)[property]; - } - return cleanedOptions; -} - -export function toSignalMap(action: Action) { - let popup: PopupSignal | undefined; - let download: DownloadSignal | undefined; - let dialog: DialogSignal | undefined; - for (const signal of action.signals) { - if (signal.name === 'popup') - popup = signal; - else if (signal.name === 'download') - download = signal; - else if (signal.name === 'dialog') - dialog = signal; - } - return { - popup, - download, - dialog, - }; -} diff --git a/packages/playwright-core/src/server/recorder/recorderActions.ts b/packages/playwright-core/src/server/recorder/recorderActions.ts index 3c9720cbc4..9447f32457 100644 --- a/packages/playwright-core/src/server/recorder/recorderActions.ts +++ b/packages/playwright-core/src/server/recorder/recorderActions.ts @@ -37,28 +37,28 @@ export type ActionBase = { signals: Signal[], }; -export type ClickAction = ActionBase & { - name: 'click', +export type ActionWithSelector = ActionBase & { selector: string, +}; + +export type ClickAction = ActionWithSelector & { + name: 'click', button: 'left' | 'middle' | 'right', modifiers: number, clickCount: number, position?: Point, }; -export type CheckAction = ActionBase & { +export type CheckAction = ActionWithSelector & { name: 'check', - selector: string, }; -export type UncheckAction = ActionBase & { +export type UncheckAction = ActionWithSelector & { name: 'uncheck', - selector: string, }; -export type FillAction = ActionBase & { +export type FillAction = ActionWithSelector & { name: 'fill', - selector: string, text: string, }; @@ -83,44 +83,39 @@ export type PressAction = ActionBase & { modifiers: number, }; -export type SelectAction = ActionBase & { +export type SelectAction = ActionWithSelector & { name: 'select', - selector: string, options: string[], }; -export type SetInputFilesAction = ActionBase & { +export type SetInputFilesAction = ActionWithSelector & { name: 'setInputFiles', - selector: string, files: string[], }; -export type AssertTextAction = ActionBase & { +export type AssertTextAction = ActionWithSelector & { name: 'assertText', - selector: string, text: string, substring: boolean, }; -export type AssertValueAction = ActionBase & { +export type AssertValueAction = ActionWithSelector & { name: 'assertValue', - selector: string, value: string, }; -export type AssertCheckedAction = ActionBase & { +export type AssertCheckedAction = ActionWithSelector & { name: 'assertChecked', - selector: string, checked: boolean, }; -export type AssertVisibleAction = ActionBase & { +export type AssertVisibleAction = ActionWithSelector & { name: 'assertVisible', - selector: string, }; export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction; export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction | AssertVisibleAction; +export type PerformOnRecordAction = ClickAction | CheckAction | UncheckAction | PressAction | SelectAction; // Signals. @@ -148,14 +143,3 @@ export type DialogSignal = BaseSignal & { }; export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal; - -type FrameDescriptionMainFrame = { - isMainFrame: true; -}; - -type FrameDescriptionChildFrame = { - isMainFrame: false; - selectorsChain: string[]; -}; - -export type FrameDescription = { pageAlias: string } & (FrameDescriptionMainFrame | FrameDescriptionChildFrame); diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 7f9166ae73..8044fadf41 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -24,9 +24,9 @@ import type { CallLog, EventData, Mode, Source } from '@recorder/recorderTypes'; import { isUnderTest } from '../../utils'; import { mime } from '../../utilsBundle'; import { syncLocalStorageWithSettings } from '../launchApp'; -import type { Recorder } from '../recorder'; import type { BrowserContext } from '../browserContext'; import { launchApp } from '../launchApp'; +import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFrontend'; declare global { interface Window { @@ -42,16 +42,6 @@ declare global { } } -export interface IRecorderApp extends EventEmitter { - close(): Promise; - setPaused(paused: boolean): Promise; - setMode(mode: Mode): Promise; - setFileIfNeeded(file: string): Promise; - setSelector(selector: string, userGesture?: boolean): Promise; - updateCallLogs(callLogs: CallLog[]): Promise; - setSources(sources: Source[]): Promise; -} - export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { async close(): Promise {} async setPaused(paused: boolean): Promise {} @@ -65,9 +55,9 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { export class RecorderApp extends EventEmitter implements IRecorderApp { private _page: Page; readonly wsEndpoint: string | undefined; - private _recorder: Recorder; + private _recorder: IRecorder; - constructor(recorder: Recorder, page: Page, wsEndpoint: string | undefined) { + constructor(recorder: IRecorder, page: Page, wsEndpoint: string | undefined) { super(); this.setMaxListeners(0); this._recorder = recorder; @@ -113,7 +103,15 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html'); } - static async open(recorder: Recorder, inspectedContext: BrowserContext, handleSIGINT: boolean | undefined): Promise { + static factory(context: BrowserContext): IRecorderAppFactory { + return async recorder => { + if (process.env.PW_CODEGEN_NO_INSPECTOR) + return new EmptyRecorderApp(); + return await RecorderApp._open(recorder, context); + }; + } + + private static async _open(recorder: IRecorder, inspectedContext: BrowserContext): Promise { const sdkLanguage = inspectedContext.attribution.playwright.options.sdkLanguage; const headed = !!inspectedContext._browser.options.headful; const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)({ sdkLanguage: 'javascript', isInternalPlaywright: true }); @@ -125,7 +123,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { noDefaultViewport: true, headless: !!process.env.PWTEST_CLI_HEADLESS || (isUnderTest() && !headed), useWebSocket: !!process.env.PWTEST_RECORDER_PORT, - handleSIGINT, + handleSIGINT: false, args: process.env.PWTEST_RECORDER_PORT ? [`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`] : [], executablePath: inspectedContext._browser.options.isChromium ? inspectedContext._browser.options.customExecutablePath : undefined, } @@ -170,11 +168,11 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { async setSelector(selector: string, userGesture?: boolean): Promise { if (userGesture) { - if (this._recorder.mode() === 'inspecting') { + if (this._recorder?.mode() === 'inspecting') { this._recorder.setMode('standby'); this._page.bringToFront(); } else { - this._recorder.setMode('recording'); + this._recorder?.setMode('recording'); } } await this._page.mainFrame().evaluateExpression(((data: { selector: string, userGesture?: boolean }) => { diff --git a/packages/playwright-core/src/server/recorder/recorderCollection.ts b/packages/playwright-core/src/server/recorder/recorderCollection.ts new file mode 100644 index 0000000000..fbfbf8f26e --- /dev/null +++ b/packages/playwright-core/src/server/recorder/recorderCollection.ts @@ -0,0 +1,148 @@ +/** + * 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 { EventEmitter } from 'events'; +import type { Frame } from '../frames'; +import type { Page } from '../page'; +import type { Signal } from './recorderActions'; +import type { ActionInContext } from '../codegen/types'; +import type { CallMetadata } from '@protocol/callMetadata'; +import { createGuid } from '../../utils/crypto'; +import { monotonicTime } from '../../utils/time'; +import { mainFrameForAction, traceParamsForAction } from './recorderUtils'; + +export class RecorderCollection extends EventEmitter { + private _lastAction: ActionInContext | null = null; + private _actions: ActionInContext[] = []; + private _enabled: boolean; + private _pageAliases: Map; + + constructor(pageAliases: Map, enabled: boolean) { + super(); + this._enabled = enabled; + this._pageAliases = pageAliases; + this.restart(); + } + + restart() { + this._lastAction = null; + this._actions = []; + this.emit('change'); + } + + actions() { + return this._actions; + } + + setEnabled(enabled: boolean) { + this._enabled = enabled; + } + + async willPerformAction(actionInContext: ActionInContext): Promise { + if (!this._enabled) + return null; + const { callMetadata, mainFrame } = this._callMetadataForAction(actionInContext); + await mainFrame.instrumentation.onBeforeCall(mainFrame, callMetadata); + this._lastAction = actionInContext; + return callMetadata; + } + + private _callMetadataForAction(actionInContext: ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } { + const mainFrame = mainFrameForAction(this._pageAliases, actionInContext); + const { action } = actionInContext; + const callMetadata: CallMetadata = { + id: `call@${createGuid()}`, + apiName: 'frame.' + action.name, + objectId: mainFrame.guid, + pageId: mainFrame._page.guid, + frameId: mainFrame.guid, + startTime: monotonicTime(), + endTime: 0, + type: 'Frame', + method: action.name, + params: traceParamsForAction(actionInContext), + log: [], + }; + return { callMetadata, mainFrame }; + } + + async didPerformAction(callMetadata: CallMetadata, actionInContext: ActionInContext, error?: Error) { + if (!this._enabled) + return; + + if (!error) + this._actions.push(actionInContext); + + const mainFrame = mainFrameForAction(this._pageAliases, actionInContext); + callMetadata.endTime = monotonicTime(); + await mainFrame.instrumentation.onAfterCall(mainFrame, callMetadata); + + this.emit('change'); + } + + addRecordedAction(actionInContext: ActionInContext) { + if (!this._enabled) + return; + const action = actionInContext.action; + + const lastAction = this._lastAction && this._lastAction.frame.pageAlias === actionInContext.frame.pageAlias ? this._lastAction.action : undefined; + if (lastAction && action.name === 'navigate' && lastAction.name === 'navigate' && action.url === lastAction.url) { + // Already at a target URL. + return; + } + + if (lastAction && action.name === 'fill' && lastAction.name === 'fill' && action.selector === lastAction.selector) + this._actions.pop(); + + this._lastAction = actionInContext; + this._actions.push(actionInContext); + this.emit('change'); + } + + commitLastAction() { + if (!this._enabled) + return; + const action = this._lastAction; + if (action) + action.committed = true; + } + + signal(pageAlias: string, frame: Frame, signal: Signal) { + if (!this._enabled) + return; + + if (this._lastAction && !this._lastAction.committed) { + this._lastAction.action.signals.push(signal); + this.emit('change'); + return; + } + + if (signal.name === 'navigation' && frame._page.mainFrame() === frame) { + this.addRecordedAction({ + frame: { + pageAlias, + framePath: [], + }, + committed: true, + action: { + name: 'navigate', + url: frame.url(), + signals: [], + }, + }); + } + } +} diff --git a/packages/playwright-core/src/server/recorder/recorderFrontend.ts b/packages/playwright-core/src/server/recorder/recorderFrontend.ts new file mode 100644 index 0000000000..161aa71eca --- /dev/null +++ b/packages/playwright-core/src/server/recorder/recorderFrontend.ts @@ -0,0 +1,35 @@ +/** + * 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 { CallLog, Mode, Source } from '@recorder/recorderTypes'; +import type { EventEmitter } from 'events'; + +export interface IRecorder { + setMode(mode: Mode): void; + mode(): Mode; +} + +export interface IRecorderApp extends EventEmitter { + close(): Promise; + setPaused(paused: boolean): Promise; + setMode(mode: Mode): Promise; + setFileIfNeeded(file: string): Promise; + setSelector(selector: string, userGesture?: boolean): Promise; + updateCallLogs(callLogs: CallLog[]): Promise; + setSources(sources: Source[]): Promise; +} + +export type IRecorderAppFactory = (recorder: IRecorder) => Promise; diff --git a/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts b/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts new file mode 100644 index 0000000000..f7613ffc54 --- /dev/null +++ b/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts @@ -0,0 +1,94 @@ +/** + * 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 path from 'path'; +import type { CallLog, Mode, Source } from '@recorder/recorderTypes'; +import { EventEmitter } from 'events'; +import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFrontend'; +import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer'; +import type { TraceViewerServerOptions } from '../trace/viewer/traceViewer'; +import type { BrowserContext } from '../browserContext'; +import { gracefullyProcessExitDoNotHang } from '../../utils/processLauncher'; +import type { Transport } from '../../utils/httpServer'; + +export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp { + private _recorder: IRecorder; + private _transport: Transport; + + static factory(context: BrowserContext): IRecorderAppFactory { + return async (recorder: IRecorder) => { + const transport = new RecorderTransport(); + const trace = path.join(context._browser.options.tracesDir, 'trace'); + await openApp(trace, { transport }); + return new RecorderInTraceViewer(context, recorder, transport); + }; + } + + constructor(context: BrowserContext, recorder: IRecorder, transport: Transport) { + super(); + this._recorder = recorder; + this._transport = transport; + } + + async close(): Promise { + this._transport.sendEvent?.('close', {}); + } + + async setPaused(paused: boolean): Promise { + this._transport.sendEvent?.('setPaused', { paused }); + } + + async setMode(mode: Mode): Promise { + this._transport.sendEvent?.('setMode', { mode }); + } + + async setFileIfNeeded(file: string): Promise { + this._transport.sendEvent?.('setFileIfNeeded', { file }); + } + + async setSelector(selector: string, userGesture?: boolean): Promise { + this._transport.sendEvent?.('setSelector', { selector, userGesture }); + } + + async updateCallLogs(callLogs: CallLog[]): Promise { + this._transport.sendEvent?.('updateCallLogs', { callLogs }); + } + + async setSources(sources: Source[]): Promise { + this._transport.sendEvent?.('setSources', { sources }); + } +} + +async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }) { + const server = await startTraceViewerServer(options); + await installRootRedirect(server, [trace], { ...options, webApp: 'recorder.html' }); + const page = await openTraceViewerApp(server.urlPrefix('precise'), 'chromium', options); + page.on('close', () => gracefullyProcessExitDoNotHang(0)); +} + +class RecorderTransport implements Transport { + constructor() { + } + + async dispatch(method: string, params: any) { + } + + onclose() { + } + + sendEvent?: (method: string, params: any) => void; + close?: () => void; +} diff --git a/packages/playwright-core/src/server/recorder/recorderRunner.ts b/packages/playwright-core/src/server/recorder/recorderRunner.ts new file mode 100644 index 0000000000..f5358d6097 --- /dev/null +++ b/packages/playwright-core/src/server/recorder/recorderRunner.ts @@ -0,0 +1,143 @@ +/** + * 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 { serializeExpectedTextValues } from '../../utils'; +import { toKeyboardModifiers } from '../codegen/language'; +import type { ActionInContext } from '../codegen/types'; +import type { CallMetadata } from '../instrumentation'; +import type { Page } from '../page'; +import type * as actions from './recorderActions'; +import type * as types from '../types'; +import { buildFullSelector, mainFrameForAction } from './recorderUtils'; + +export async function performAction(callMetadata: CallMetadata, pageAliases: Map, actionInContext: ActionInContext) { + const mainFrame = mainFrameForAction(pageAliases, actionInContext); + const { action } = actionInContext; + + const kActionTimeout = 5000; + + if (action.name === 'navigate') { + await mainFrame.goto(callMetadata, action.url, { timeout: kActionTimeout }); + return; + } + + if (action.name === 'openPage') + throw Error('Not reached'); + + if (action.name === 'closePage') { + await mainFrame._page.close(callMetadata); + return; + } + + const selector = buildFullSelector(actionInContext.frame.framePath, action.selector); + + if (action.name === 'click') { + const options = toClickOptions(action); + await mainFrame.click(callMetadata, selector, { ...options, timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'press') { + const modifiers = toKeyboardModifiers(action.modifiers); + const shortcut = [...modifiers, action.key].join('+'); + await mainFrame.press(callMetadata, selector, shortcut, { timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'fill') { + await mainFrame.fill(callMetadata, selector, action.text, { timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'setInputFiles') { + await mainFrame.setInputFiles(callMetadata, selector, { selector, payloads: [], timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'check') { + await mainFrame.check(callMetadata, selector, { timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'uncheck') { + await mainFrame.uncheck(callMetadata, selector, { timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'select') { + const values = action.options.map(value => ({ value })); + await mainFrame.selectOption(callMetadata, selector, [], values, { timeout: kActionTimeout, strict: true }); + return; + } + + if (action.name === 'assertChecked') { + await mainFrame.expect(callMetadata, selector, { + selector, + expression: 'to.be.checked', + isNot: !action.checked, + timeout: kActionTimeout, + }); + return; + } + + if (action.name === 'assertText') { + await mainFrame.expect(callMetadata, selector, { + selector, + expression: 'to.have.text', + expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }), + isNot: false, + timeout: kActionTimeout, + }); + return; + } + + if (action.name === 'assertValue') { + await mainFrame.expect(callMetadata, selector, { + selector, + expression: 'to.have.value', + expectedValue: action.value, + isNot: false, + timeout: kActionTimeout, + }); + return; + } + + if (action.name === 'assertVisible') { + await mainFrame.expect(callMetadata, selector, { + selector, + expression: 'to.be.visible', + isNot: false, + timeout: kActionTimeout, + }); + return; + } + + throw new Error('Internal error: unexpected action ' + (action as any).name); +} + +export function toClickOptions(action: actions.ClickAction): types.MouseClickOptions { + const modifiers = toKeyboardModifiers(action.modifiers); + const options: types.MouseClickOptions = {}; + if (action.button !== 'left') + options.button = action.button; + if (modifiers.length) + options.modifiers = modifiers; + if (action.clickCount > 1) + options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; + return options; +} diff --git a/packages/playwright-core/src/server/recorder/recorderUtils.ts b/packages/playwright-core/src/server/recorder/recorderUtils.ts index d6237b4899..234fc79a0f 100644 --- a/packages/playwright-core/src/server/recorder/recorderUtils.ts +++ b/packages/playwright-core/src/server/recorder/recorderUtils.ts @@ -16,6 +16,12 @@ import type { CallMetadata } from '../instrumentation'; import type { CallLog, CallLogStatus } from '@recorder/recorderTypes'; +import type { Page } from '../page'; +import type { ActionInContext } from '../codegen/types'; +import type { Frame } from '../frames'; +import type * as actions from './recorderActions'; +import { toKeyboardModifiers } from '../codegen/language'; +import { serializeExpectedTextValues } from '../../utils/expectUtils'; export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog { let title = metadata.apiName || metadata.method; @@ -44,3 +50,82 @@ export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus) }; return callLog; } + +export function buildFullSelector(framePath: string[], selector: string) { + return [...framePath, selector].join(' >> internal:control=enter-frame >> '); +} + +export function mainFrameForAction(pageAliases: Map, actionInContext: ActionInContext): Frame { + const pageAlias = actionInContext.frame.pageAlias; + const page = [...pageAliases.entries()].find(([, alias]) => pageAlias === alias)?.[0]; + if (!page) + throw new Error('Internal error: page not found'); + return page.mainFrame(); +} + +export async function frameForAction(pageAliases: Map, actionInContext: ActionInContext, action: actions.ActionWithSelector): Promise { + const pageAlias = actionInContext.frame.pageAlias; + const page = [...pageAliases.entries()].find(([, alias]) => pageAlias === alias)?.[0]; + if (!page) + throw new Error('Internal error: page not found'); + const fullSelector = buildFullSelector(actionInContext.frame.framePath, action.selector); + const result = await page.mainFrame().selectors.resolveFrameForSelector(fullSelector); + if (!result) + throw new Error('Internal error: frame not found'); + return result.frame; +} + +export function traceParamsForAction(actionInContext: ActionInContext) { + const { action } = actionInContext; + + switch (action.name) { + case 'navigate': return { url: action.url }; + case 'openPage': return {}; + case 'closePage': return {}; + } + + const selector = buildFullSelector(actionInContext.frame.framePath, action.selector); + switch (action.name) { + case 'click': return { selector, clickCount: action.clickCount }; + case 'press': { + const modifiers = toKeyboardModifiers(action.modifiers); + const shortcut = [...modifiers, action.key].join('+'); + return { selector, key: shortcut }; + } + case 'fill': return { selector, text: action.text }; + case 'setInputFiles': return { selector, files: action.files }; + case 'check': return { selector }; + case 'uncheck': return { selector }; + case 'select': return { selector, values: action.options.map(value => ({ value })) }; + case 'assertChecked': { + return { + selector, + expression: 'to.be.checked', + isNot: !action.checked, + }; + } + case 'assertText': { + return { + selector, + expression: 'to.have.text', + expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }), + isNot: false, + }; + } + case 'assertValue': { + return { + selector, + expression: 'to.have.value', + expectedValue: action.value, + isNot: false, + }; + } + case 'assertVisible': { + return { + selector, + expression: 'to.be.visible', + isNot: false, + }; + } + } +} diff --git a/packages/playwright-core/src/server/recorder/throttledFile.ts b/packages/playwright-core/src/server/recorder/throttledFile.ts new file mode 100644 index 0000000000..4a34f41a0c --- /dev/null +++ b/packages/playwright-core/src/server/recorder/throttledFile.ts @@ -0,0 +1,43 @@ +/** + * 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 * as fs from 'fs'; + +export class ThrottledFile { + private _file: string; + private _timer: NodeJS.Timeout | undefined; + private _text: string | undefined; + + constructor(file: string) { + this._file = file; + } + + setContent(text: string) { + this._text = text; + if (!this._timer) + this._timer = setTimeout(() => this.flush(), 250); + } + + flush(): void { + if (this._timer) { + clearTimeout(this._timer); + this._timer = undefined; + } + if (this._text) + fs.writeFileSync(this._file, this._text); + this._text = undefined; + } +} diff --git a/packages/playwright-core/src/server/recorder/utils.ts b/packages/playwright-core/src/server/recorder/utils.ts deleted file mode 100644 index 883a8ab129..0000000000 --- a/packages/playwright-core/src/server/recorder/utils.ts +++ /dev/null @@ -1,51 +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. - */ - -import type { Frame } from '../frames'; -import type { SmartKeyboardModifier } from '../types'; -import type * as actions from './recorderActions'; - -export type MouseClickOptions = Parameters[2]; - -export function toClickOptions(action: actions.ClickAction): { method: 'click' | 'dblclick', options: MouseClickOptions } { - let method: 'click' | 'dblclick' = 'click'; - if (action.clickCount === 2) - method = 'dblclick'; - const modifiers = toModifiers(action.modifiers); - const options: MouseClickOptions = {}; - if (action.button !== 'left') - options.button = action.button; - if (modifiers.length) - options.modifiers = modifiers; - if (action.clickCount > 2) - options.clickCount = action.clickCount; - if (action.position) - options.position = action.position; - return { method, options }; -} - -export function toModifiers(modifiers: number): SmartKeyboardModifier[] { - const result: SmartKeyboardModifier[] = []; - if (modifiers & 1) - result.push('Alt'); - if (modifiers & 2) - result.push('ControlOrMeta'); - if (modifiers & 4) - result.push('ControlOrMeta'); - if (modifiers & 8) - result.push('Shift'); - return result; -} diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index c7ce3a2e7e..4e942967a4 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -264,6 +264,9 @@ const DOWNLOAD_PATHS: Record = { 'mac14-arm64': 'builds/android/%s/android.zip', 'win64': 'builds/android/%s/android.zip', }, + // TODO(bidi): implement downloads. + 'bidi': { + } as DownloadPaths, }; export const registryDirectory = (() => { @@ -349,14 +352,15 @@ function readDescriptors(browsersJSON: BrowsersJSON) { }); } -export type BrowserName = 'chromium' | 'firefox' | 'webkit'; +export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi'; type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'android'; +type BidiChannel = 'bidi-firefox-stable' | 'bidi-firefox-beta' | 'bidi-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'; const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree']; export interface Executable { type: 'browser' | 'tool' | 'channel'; - name: BrowserName | InternalTool | ChromiumChannel; + name: BrowserName | InternalTool | ChromiumChannel | BidiChannel; browserName: BrowserName | undefined; installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none'; directory: string | undefined; @@ -521,6 +525,48 @@ export class Registry { 'win32': `\\Microsoft\\Edge SxS\\Application\\msedge.exe`, })); + this._executables.push(this._createBidiFirefoxChannel('bidi-firefox-stable', { + 'linux': '/firefox/firefox', + 'darwin': '/Firefox.app/Contents/MacOS/firefox', + 'win32': '\\core\\firefox.exe', + })); + this._executables.push(this._createBidiFirefoxChannel('bidi-firefox-beta', { + 'linux': '/firefox/firefox', + 'darwin': '/Firefox.app/Contents/MacOS/firefox', + 'win32': '\\core\\firefox.exe', + })); + this._executables.push(this._createBidiFirefoxChannel('bidi-firefox-nightly', { + 'linux': '/firefox/firefox', + 'darwin': '/Firefox Nightly.app/Contents/MacOS/firefox', + 'win32': '\\firefox\\firefox.exe', + })); + + this._executables.push(this._createBidiChannel('bidi-chrome-stable', { + 'linux': '/opt/google/chrome/chrome', + 'darwin': '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + 'win32': `\\Google\\Chrome\\Application\\chrome.exe`, + })); + this._executables.push(this._createBidiChannel('bidi-chrome-canary', { + 'linux': '', + 'darwin': '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', + 'win32': `\\Google\\Chrome SxS\\Application\\chrome.exe`, + })); + this._executables.push({ + type: 'browser', + name: 'bidi-chromium', + browserName: 'bidi', + directory: chromium.dir, + executablePath: () => chromiumExecutable, + executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage), + installType: 'download-on-demand', + _validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']), + downloadURLs: this._downloadURLs(chromium), + browserVersion: chromium.browserVersion, + _install: () => this._downloadExecutable(chromium, chromiumExecutable), + _dependencyGroup: 'chromium', + _isHermeticInstallation: true, + }); + const firefox = descriptors.find(d => d.name === 'firefox')!; const firefoxExecutable = findExecutablePath(firefox.dir, 'firefox'); this._executables.push({ @@ -616,6 +662,21 @@ 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 { @@ -656,6 +717,86 @@ export class Registry { }; } + private _createBidiFirefoxChannel(name: BidiChannel, lookAt: Record<'linux' | 'darwin' | 'win32', string>, install?: () => Promise): ExecutableImpl { + const executablePath = (sdkLanguage: string, shouldThrow: boolean) => { + 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}`); + return undefined; + } + const folder = path.resolve('firefox'); + let channelName = 'stable'; + if (name.includes('beta')) + channelName = 'beta'; + else if (name.includes('nightly')) + channelName = 'nightly'; + const installedVersions = fs.readdirSync(folder); + const found = installedVersions.filter(e => e.includes(channelName)); + if (found.length === 1) + return path.join(folder, found[0], suffix); + if (found.length > 1) { + if (shouldThrow) + throw new Error(`Multiple Firefox installations found for channel '${name}': ${found.join(', ')}`); + else + return undefined; + } + if (shouldThrow) + throw new Error(`Cannot find Firefox installation for channel '${name}' under ${folder}`); + return undefined; + }; + return { + type: 'channel', + name, + browserName: 'bidi', + directory: undefined, + executablePath: (sdkLanguage: string) => executablePath(sdkLanguage, false), + executablePathOrDie: (sdkLanguage: string) => executablePath(sdkLanguage, true)!, + installType: 'none', + _validateHostRequirements: () => Promise.resolve(), + _isHermeticInstallation: true, + _install: install, + }; + } + + private _createBidiChannel(name: BidiChannel, lookAt: Record<'linux' | 'darwin' | 'win32', string>, install?: () => Promise): ExecutableImpl { + const executablePath = (sdkLanguage: string, shouldThrow: boolean) => { + 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}`); + return undefined; + } + const prefixes = (process.platform === 'win32' ? [ + process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)'] + ].filter(Boolean) : ['']) as string[]; + + for (const prefix of prefixes) { + const executablePath = path.join(prefix, suffix); + if (canAccessFile(executablePath)) + return executablePath; + } + if (!shouldThrow) + return undefined; + + const location = prefixes.length ? ` at ${path.join(prefixes[0], suffix)}` : ``; + const installation = install ? `\nRun "${buildPlaywrightCLICommand(sdkLanguage, 'install ' + name)}"` : ''; + throw new Error(`Firefox distribution '${name}' is not found${location}${installation}`); + }; + return { + type: 'channel', + name, + browserName: 'bidi', + directory: undefined, + executablePath: (sdkLanguage: string) => executablePath(sdkLanguage, false), + executablePathOrDie: (sdkLanguage: string) => executablePath(sdkLanguage, true)!, + installType: install ? 'install-script' : 'none', + _validateHostRequirements: () => Promise.resolve(), + _isHermeticInstallation: false, + _install: install, + }; + } + executables(): Executable[] { return this._executables; } diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 32a7b1cebd..2dd900bf89 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -169,6 +169,10 @@ class SocksProxyConnection { this.target.removeListener('close', this._targetCloseEventListener); // @ts-expect-error const session: http2.ServerHttp2Session = http2.performServerHandshake(internalTLS); + session.on('error', () => { + this.target.destroy(); + this._targetCloseEventListener(); + }); session.once('stream', (stream: http2.ServerHttp2Stream) => { stream.respond({ 'content-type': 'text/html', diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts index 48bfea7302..d552c397e0 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts @@ -137,6 +137,7 @@ export class Snapshotter { html: data.html, viewport: data.viewport, timestamp: monotonicTime(), + wallTime: data.wallTime, collectionTime: data.collectionTime, resourceOverrides: [], isMainFrame: page.mainFrame() === frame diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index 14e34c8aee..e881d52313 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -27,7 +27,7 @@ export type SnapshotData = { }[], viewport: { width: number, height: number }, url: string, - timestamp: number, + wallTime: number, collectionTime: number, }; @@ -572,7 +572,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: height: window.innerHeight, }, url: location.href, - timestamp, + wallTime: Date.now(), collectionTime: 0, }; @@ -589,7 +589,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: result.resourceOverrides.push({ url, content, contentType: 'text/css' },); } - result.collectionTime = performance.now() - result.timestamp; + result.collectionTime = performance.now() - timestamp; return result; } } diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 1dec4f536a..b09bbe3134 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -472,7 +472,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps sha1, width: params.width, height: params.height, - timestamp: monotonicTime() + timestamp: monotonicTime(), + frameSwapWallTime: params.frameSwapWallTime, }; // Make sure to write the screencast frame before adding a reference to it. this._appendResource(sha1, params.buffer); diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index d053a3e643..6ca0319aa3 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -109,6 +109,8 @@ export async function startTraceViewerServer(options?: TraceViewerServerOptions) export async function installRootRedirect(server: HttpServer, traceUrls: string[], options: TraceViewerRedirectOptions) { const params = new URLSearchParams(); + if (path.sep !== path.posix.sep) + params.set('pathSeparator', path.sep); for (const traceUrl of traceUrls) params.append('trace', traceUrl); if (server.wsGuid()) diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index 80b30f28fb..3ddfda4627 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -536,7 +536,7 @@ export module Protocol { /** * Pseudo-style identifier (see enum PseudoId in RenderStyleConstants.h). */ - export type PseudoId = "first-line"|"first-letter"|"grammar-error"|"highlight"|"marker"|"before"|"after"|"selection"|"backdrop"|"spelling-error"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"|"-webkit-scrollbar"|"-webkit-resizer"|"-webkit-scrollbar-thumb"|"-webkit-scrollbar-button"|"-webkit-scrollbar-track"|"-webkit-scrollbar-track-piece"|"-webkit-scrollbar-corner"; + export type PseudoId = "first-line"|"first-letter"|"grammar-error"|"highlight"|"marker"|"before"|"after"|"selection"|"backdrop"|"spelling-error"|"target-text"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"|"-webkit-scrollbar"|"-webkit-resizer"|"-webkit-scrollbar-thumb"|"-webkit-scrollbar-button"|"-webkit-scrollbar-track"|"-webkit-scrollbar-track-piece"|"-webkit-scrollbar-corner"; /** * Pseudo-style identifier (see enum PseudoId in RenderStyleConstants.h). */ diff --git a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts index a450127789..93367726ed 100644 --- a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts @@ -141,7 +141,7 @@ export class WKRouteImpl implements network.RouteDelegate { }); } - async continue(request: network.Request, overrides: types.NormalizedContinueOverrides) { + async continue(overrides: types.NormalizedContinueOverrides) { // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. await this._session.sendMayFail('Network.interceptWithRequest', { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 26ffd2dbab..c3954b4882 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -502,15 +502,16 @@ export class WKPage implements PageDelegate { if (!frame) return; const delegate = new WKExecutionContext(this._session, contextPayload.id); - let worldName: types.World|null = null; + let worldName: types.World; if (contextPayload.type === 'normal') worldName = 'main'; else if (contextPayload.type === 'user' && contextPayload.name === UTILITY_WORLD_NAME) worldName = 'utility'; + else + return; const context = new dom.FrameExecutionContext(delegate, frame, worldName); (context as any)[contextDelegateSymbol] = delegate; - if (worldName) - frame._contextCreated(worldName, context); + frame._contextCreated(worldName, context); this._contextIdToContext.set(contextPayload.id, context); } diff --git a/packages/playwright-core/src/utils/expectUtils.ts b/packages/playwright-core/src/utils/expectUtils.ts new file mode 100644 index 0000000000..0ae21e8602 --- /dev/null +++ b/packages/playwright-core/src/utils/expectUtils.ts @@ -0,0 +1,29 @@ +/** + * 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 { ExpectedTextValue } from '@protocol/channels'; +import { isRegExp, isString } from './rtti'; + +export function serializeExpectedTextValues(items: (string | RegExp)[], options: { matchSubstring?: boolean, normalizeWhiteSpace?: boolean, ignoreCase?: boolean } = {}): ExpectedTextValue[] { + return items.map(i => ({ + string: isString(i) ? i : undefined, + regexSource: isRegExp(i) ? i.source : undefined, + regexFlags: isRegExp(i) ? i.flags : undefined, + matchSubstring: options.matchSubstring, + ignoreCase: options.ignoreCase, + normalizeWhiteSpace: options.normalizeWhiteSpace, + })); +} diff --git a/packages/playwright-core/src/utils/index.ts b/packages/playwright-core/src/utils/index.ts index 372922ec59..0bc7a75b08 100644 --- a/packages/playwright-core/src/utils/index.ts +++ b/packages/playwright-core/src/utils/index.ts @@ -21,6 +21,7 @@ export * from './debug'; export * from './debugLogger'; export * from './env'; export * from './eventsHelper'; +export * from './expectUtils'; export * from './fileUtils'; export * from './headers'; export * from './hostPlatform'; diff --git a/packages/playwright-core/src/utilsBundle.ts b/packages/playwright-core/src/utilsBundle.ts index bb037a1ca7..a2a62be867 100644 --- a/packages/playwright-core/src/utilsBundle.ts +++ b/packages/playwright-core/src/utilsBundle.ts @@ -19,6 +19,7 @@ import path from 'path'; export const colors: typeof import('../bundles/utils/node_modules/colors/safe') = require('./utilsBundleImpl').colors; export const debug: typeof import('../bundles/utils/node_modules/@types/debug') = require('./utilsBundleImpl').debug; +export const dotenv: typeof import('../bundles/utils/node_modules/dotenv') = require('./utilsBundleImpl').dotenv; export const getProxyForUrl: typeof import('../bundles/utils/node_modules/@types/proxy-from-env').getProxyForUrl = require('./utilsBundleImpl').getProxyForUrl; export const HttpsProxyAgent: typeof import('../bundles/utils/node_modules/https-proxy-agent').HttpsProxyAgent = require('./utilsBundleImpl').HttpsProxyAgent; export const jpegjs: typeof import('../bundles/utils/node_modules/jpeg-js') = require('./utilsBundleImpl').jpegjs; diff --git a/packages/playwright-core/types/protocol.d.ts b/packages/playwright-core/types/protocol.d.ts index 99ce1d3a6a..caadb2a577 100644 --- a/packages/playwright-core/types/protocol.d.ts +++ b/packages/playwright-core/types/protocol.d.ts @@ -1131,17 +1131,21 @@ using Audits.issueAdded event. } /** - * Defines commands and events for browser extensions. Available if the client -is connected using the --remote-debugging-pipe flag and -the --enable-unsafe-extension-debugging flag is set. + * Defines commands and events for browser extensions. */ export module Extensions { + /** + * Storage areas. + */ + export type StorageArea = "session"|"local"|"sync"|"managed"; /** * Installs an unpacked extension from the filesystem similar to --load-extension CLI flags. Returns extension ID once the extension -has been installed. +has been installed. Available if the client is connected using the +--remote-debugging-pipe flag and the --enable-unsafe-extension-debugging +flag is set. */ export type loadUnpackedParameters = { /** @@ -1155,6 +1159,81 @@ has been installed. */ id: string; } + /** + * Gets data from extension storage in the given `storageArea`. If `keys` is +specified, these are used to filter the result. + */ + export type getStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to retrieve data from. + */ + storageArea: StorageArea; + /** + * Keys to retrieve. + */ + keys?: string[]; + } + export type getStorageItemsReturnValue = { + data: { [key: string]: string }; + } + /** + * Removes `keys` from extension storage in the given `storageArea`. + */ + export type removeStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to remove data from. + */ + storageArea: StorageArea; + /** + * Keys to remove. + */ + keys: string[]; + } + export type removeStorageItemsReturnValue = { + } + /** + * Clears extension storage in the given `storageArea`. + */ + export type clearStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to remove data from. + */ + storageArea: StorageArea; + } + export type clearStorageItemsReturnValue = { + } + /** + * Sets `values` in extension storage in the given `storageArea`. The provided `values` +will be merged with existing values in the storage area. + */ + export type setStorageItemsParameters = { + /** + * ID of extension. + */ + id: string; + /** + * StorageArea to set data in. + */ + storageArea: StorageArea; + /** + * Values to set. + */ + values: { [key: string]: string }; + } + export type setStorageItemsReturnValue = { + } } /** @@ -2532,16 +2611,6 @@ stylesheet rules) this rule came from. */ style: CSSStyle; } - /** - * CSS position-fallback rule representation. - */ - export interface CSSPositionFallbackRule { - name: Value; - /** - * List of keyframes. - */ - tryRules: CSSTryRule[]; - } /** * CSS @position-try rule representation. */ @@ -2888,10 +2957,6 @@ attributes) for a DOM node identified by `nodeId`. * A list of CSS keyframed animations matching this node. */ cssKeyframesRules?: CSSKeyframesRule[]; - /** - * A list of CSS position fallbacks matching this node. - */ - cssPositionFallbackRules?: CSSPositionFallbackRule[]; /** * A list of CSS @position-try rules matching this node, based on the position-try-fallbacks property. */ @@ -3496,7 +3561,7 @@ front-end. /** * Pseudo element type. */ - export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"; + export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-next-button"|"scroll-prev-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"; /** * Shadow root type. */ @@ -3646,6 +3711,13 @@ The property is always undefined now. compatibilityMode?: CompatibilityMode; assignedSlot?: BackendNode; } + /** + * A structure to hold the top-level node of a detached tree and an array of its retained descendants. + */ + export interface DetachedElementInfo { + treeNode: Node; + retainedNodeIds: NodeId[]; + } /** * A structure holding an RGBA color. */ @@ -4693,6 +4765,17 @@ File wrapper. export type getFileInfoReturnValue = { path: string; } + /** + * Returns list of detached nodes + */ + export type getDetachedDomNodesParameters = { + } + export type getDetachedDomNodesReturnValue = { + /** + * The list of detached nodes + */ + detachedNodes: DetachedElementInfo[]; + } /** * Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). @@ -11369,7 +11452,7 @@ as an ad. * All Permissions Policy features. This enum should match the one defined in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5. */ - export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -11784,7 +11867,7 @@ Example URLs: http://www.google.com/file.html -> "google.com" */ fixed?: number; } - export type ClientNavigationReason = "formSubmissionGet"|"formSubmissionPost"|"httpHeaderRefresh"|"scriptInitiated"|"metaTagRefresh"|"pageBlockInterstitial"|"reload"|"anchorClick"; + export type ClientNavigationReason = "anchorClick"|"formSubmissionGet"|"formSubmissionPost"|"httpHeaderRefresh"|"initialFrameNavigation"|"metaTagRefresh"|"other"|"pageBlockInterstitial"|"reload"|"scriptInitiated"; export type ClientNavigationDisposition = "currentTab"|"newTab"|"newWindow"|"download"; export interface InstallabilityErrorArgument { /** @@ -12298,6 +12381,10 @@ when bfcache navigation fails. * Frame's new url. */ url: string; + /** + * Navigation type + */ + navigationType: "fragment"|"historyApi"|"other"; } /** * Compressed image data requested by the `startScreencast`. @@ -16922,7 +17009,7 @@ possible for multiple rule sets and links to trigger a single attempt. /** * List of FinalStatus reasons for Prerender2. */ - export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"|"WindowClosed"; + export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"|"WindowClosed"|"SlowNetwork"|"OtherPrerenderedPageActivated"; /** * Preloading status values, see also PreloadingTriggeringOutcome. This status is shared by prefetchStatusUpdated and prerenderStatusUpdated. @@ -17270,6 +17357,101 @@ supported yet. } } + /** + * This domain allows configuring virtual Bluetooth devices to test +the web-bluetooth API. + */ + export module BluetoothEmulation { + /** + * Indicates the various states of Central. + */ + export type CentralState = "absent"|"powered-off"|"powered-on"; + /** + * Stores the manufacturer data + */ + export interface ManufacturerData { + /** + * Company identifier +https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml +https://usb.org/developers + */ + key: number; + /** + * Manufacturer-specific data + */ + data: binary; + } + /** + * Stores the byte data of the advertisement packet sent by a Bluetooth device. + */ + export interface ScanRecord { + name?: string; + uuids?: string[]; + /** + * Stores the external appearance description of the device. + */ + appearance?: number; + /** + * Stores the transmission power of a broadcasting device. + */ + txPower?: number; + /** + * Key is the company identifier and the value is an array of bytes of +manufacturer specific data. + */ + manufacturerData?: ManufacturerData[]; + } + /** + * Stores the advertisement packet information that is sent by a Bluetooth device. + */ + export interface ScanEntry { + deviceAddress: string; + rssi: number; + scanRecord: ScanRecord; + } + + + /** + * Enable the BluetoothEmulation domain. + */ + export type enableParameters = { + /** + * State of the simulated central. + */ + state: CentralState; + } + export type enableReturnValue = { + } + /** + * Disable the BluetoothEmulation domain. + */ + export type disableParameters = { + } + export type disableReturnValue = { + } + /** + * Simulates a peripheral with |address|, |name| and |knownServiceUuids| +that has already been connected to the system. + */ + export type simulatePreconnectedPeripheralParameters = { + address: string; + name: string; + manufacturerData: ManufacturerData[]; + knownServiceUuids: string[]; + } + export type simulatePreconnectedPeripheralReturnValue = { + } + /** + * Simulates an advertisement packet described in |entry| being received by +the central. + */ + export type simulateAdvertisementParameters = { + entry: ScanEntry; + } + export type simulateAdvertisementReturnValue = { + } + } + /** * This domain is deprecated - use Runtime or Log instead. */ @@ -20122,6 +20304,10 @@ Error was thrown. "Audits.checkContrast": Audits.checkContrastParameters; "Audits.checkFormsIssues": Audits.checkFormsIssuesParameters; "Extensions.loadUnpacked": Extensions.loadUnpackedParameters; + "Extensions.getStorageItems": Extensions.getStorageItemsParameters; + "Extensions.removeStorageItems": Extensions.removeStorageItemsParameters; + "Extensions.clearStorageItems": Extensions.clearStorageItemsParameters; + "Extensions.setStorageItems": Extensions.setStorageItemsParameters; "Autofill.trigger": Autofill.triggerParameters; "Autofill.setAddresses": Autofill.setAddressesParameters; "Autofill.disable": Autofill.disableParameters; @@ -20232,6 +20418,7 @@ Error was thrown. "DOM.setNodeStackTracesEnabled": DOM.setNodeStackTracesEnabledParameters; "DOM.getNodeStackTraces": DOM.getNodeStackTracesParameters; "DOM.getFileInfo": DOM.getFileInfoParameters; + "DOM.getDetachedDomNodes": DOM.getDetachedDomNodesParameters; "DOM.setInspectedNode": DOM.setInspectedNodeParameters; "DOM.setNodeName": DOM.setNodeNameParameters; "DOM.setNodeValue": DOM.setNodeValueParameters; @@ -20616,6 +20803,10 @@ Error was thrown. "PWA.launchFilesInApp": PWA.launchFilesInAppParameters; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters; "PWA.changeAppUserSettings": PWA.changeAppUserSettingsParameters; + "BluetoothEmulation.enable": BluetoothEmulation.enableParameters; + "BluetoothEmulation.disable": BluetoothEmulation.disableParameters; + "BluetoothEmulation.simulatePreconnectedPeripheral": BluetoothEmulation.simulatePreconnectedPeripheralParameters; + "BluetoothEmulation.simulateAdvertisement": BluetoothEmulation.simulateAdvertisementParameters; "Console.clearMessages": Console.clearMessagesParameters; "Console.disable": Console.disableParameters; "Console.enable": Console.enableParameters; @@ -20722,6 +20913,10 @@ Error was thrown. "Audits.checkContrast": Audits.checkContrastReturnValue; "Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue; "Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue; + "Extensions.getStorageItems": Extensions.getStorageItemsReturnValue; + "Extensions.removeStorageItems": Extensions.removeStorageItemsReturnValue; + "Extensions.clearStorageItems": Extensions.clearStorageItemsReturnValue; + "Extensions.setStorageItems": Extensions.setStorageItemsReturnValue; "Autofill.trigger": Autofill.triggerReturnValue; "Autofill.setAddresses": Autofill.setAddressesReturnValue; "Autofill.disable": Autofill.disableReturnValue; @@ -20832,6 +21027,7 @@ Error was thrown. "DOM.setNodeStackTracesEnabled": DOM.setNodeStackTracesEnabledReturnValue; "DOM.getNodeStackTraces": DOM.getNodeStackTracesReturnValue; "DOM.getFileInfo": DOM.getFileInfoReturnValue; + "DOM.getDetachedDomNodes": DOM.getDetachedDomNodesReturnValue; "DOM.setInspectedNode": DOM.setInspectedNodeReturnValue; "DOM.setNodeName": DOM.setNodeNameReturnValue; "DOM.setNodeValue": DOM.setNodeValueReturnValue; @@ -21216,6 +21412,10 @@ Error was thrown. "PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue; "PWA.changeAppUserSettings": PWA.changeAppUserSettingsReturnValue; + "BluetoothEmulation.enable": BluetoothEmulation.enableReturnValue; + "BluetoothEmulation.disable": BluetoothEmulation.disableReturnValue; + "BluetoothEmulation.simulatePreconnectedPeripheral": BluetoothEmulation.simulatePreconnectedPeripheralReturnValue; + "BluetoothEmulation.simulateAdvertisement": BluetoothEmulation.simulateAdvertisementReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue; "Console.disable": Console.disableReturnValue; "Console.enable": Console.enableReturnValue; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 0d018dcfd3..765c84c36e 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -288,41 +288,8 @@ export interface Page { * [browserContext.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script) * and [page.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-page#page-add-init-script) is not * defined. - * - * **Bundling** - * - * If you have a complex script split into several files, it needs to be bundled into a single file first. We - * recommend running [`esbuild`](https://esbuild.github.io/) or [`webpack`](https://webpack.js.org/) to produce a - * commonjs module and pass `path` and `arg`. - * - * ```js - * // mocks/mockRandom.ts - * // This script can import other files. - * import { defaultValue } from './defaultValue'; - * - * export default function(value?: number) { - * window.Math.random = () => value ?? defaultValue; - * } - * ``` - * - * ```js - * // tests/example.spec.ts - * const mockPath = { path: path.resolve(__dirname, '../mocks/mockRandom.js') }; - * - * // Passing 42 as an argument to the default export function. - * await page.addInitScript({ path: mockPath }, 42); - * - * // Make sure to pass undefined even if you do not need to pass an argument. - * // This instructs Playwright to treat the file as a commonjs module. - * await page.addInitScript({ path: mockPath }, undefined); - * ``` - * * @param script Script to be evaluated in the page. - * @param arg Optional JSON-serializable argument to pass to `script`. - * - When `script` is a function, the argument is passed to it directly. - * - When `script` is a file path, the file is assumed to be a commonjs module. The default export, either - * `module.exports` or `module.exports.default`, should be a function that's going to be executed with this - * argument. + * @param arg Optional argument to pass to `script` (only supported when passing a function). */ addInitScript(script: PageFunction | { path?: string, content?: string }, arg?: Arg): Promise; @@ -898,17 +865,55 @@ export interface Page { exposeBinding(name: string, playwrightBinding: (source: BindingSource, ...args: any[]) => any, options?: { handle?: boolean }): Promise; /** - * Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. + * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for + * async listeners to complete or to ignore subsequent errors from these listeners. + * + * **Usage** + * + * ```js + * page.on('request', async request => { + * const response = await request.response(); + * const body = await response.body(); + * console.log(body.byteLength); + * }); + * await page.goto('https://playwright.dev', { waitUntil: 'domcontentloaded' }); + * // Waits for all the reported 'request' events to resolve. + * await page.removeAllListeners('request', { behavior: 'wait' }); + * ``` + * * @param type * @param options */ removeAllListeners(type?: string): this; /** - * Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. + * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for + * async listeners to complete or to ignore subsequent errors from these listeners. + * + * **Usage** + * + * ```js + * page.on('request', async request => { + * const response = await request.response(); + * const body = await response.body(); + * console.log(body.byteLength); + * }); + * await page.goto('https://playwright.dev', { waitUntil: 'domcontentloaded' }); + * // Waits for all the reported 'request' events to resolve. + * await page.removeAllListeners('request', { behavior: 'wait' }); + * ``` + * * @param type * @param options */ - removeAllListeners(type: string | undefined, options: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise; + removeAllListeners(type: string | undefined, options: { + /** + * Specifies whether to wait for already running listeners and what to do if they throw errors: + * - `'default'` - do not wait for current listener calls (if any) to finish, if the listener throws, it may result in unhandled error + * - `'wait'` - wait for current listener calls (if any) to finish + * - `'ignoreErrors'` - do not wait for current listener calls (if any) to finish, all errors thrown by the listeners after removal are silently caught + */ + behavior?: 'wait'|'ignoreErrors'|'default' + }): Promise; /** * Emitted when the page closes. */ @@ -3897,10 +3902,8 @@ export interface Page { force?: boolean; /** - * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You - * can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as - * navigating to inaccessible pages. Defaults to `false`. - * @deprecated This option will default to `true` in the future. + * This option has no effect. + * @deprecated This option has no effect. */ noWaitAfter?: boolean; @@ -7023,10 +7026,8 @@ export interface Frame { force?: boolean; /** - * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You - * can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as - * navigating to inaccessible pages. Defaults to `false`. - * @deprecated This option will default to `true` in the future. + * This option has no effect. + * @deprecated This option has no effect. */ noWaitAfter?: boolean; @@ -7571,9 +7572,9 @@ export interface Frame { * If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser * context. * - * Playwright allows creating "incognito" browser contexts with + * Playwright allows creating isolated non-persistent browser contexts with * [browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context) method. - * "Incognito" browser contexts don't write any browsing data to disk. + * Non-persistent browser contexts don't write any browsing data to disk. * * ```js * // Create a new incognito browser context @@ -7699,56 +7700,33 @@ export interface BrowserContext { * [browserContext.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script) * and [page.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-page#page-add-init-script) is not * defined. - * - * **Bundling** - * - * If you have a complex script split into several files, it needs to be bundled into a single file first. We - * recommend running [`esbuild`](https://esbuild.github.io/) or [`webpack`](https://webpack.js.org/) to produce a - * commonjs module and pass `path` and `arg`. - * - * ```js - * // mocks/mockRandom.ts - * // This script can import other files. - * import { defaultValue } from './defaultValue'; - * - * export default function(value?: number) { - * window.Math.random = () => value ?? defaultValue; - * } - * ``` - * - * ```js - * // tests/example.spec.ts - * const mockPath = { path: path.resolve(__dirname, '../mocks/mockRandom.js') }; - * - * // Passing 42 as an argument to the default export function. - * await context.addInitScript({ path: mockPath }, 42); - * - * // Make sure to pass undefined even if you do not need to pass an argument. - * // This instructs Playwright to treat the file as a commonjs module. - * await context.addInitScript({ path: mockPath }, undefined); - * ``` - * * @param script Script to be evaluated in all pages in the browser context. - * @param arg Optional JSON-serializable argument to pass to `script`. - * - When `script` is a function, the argument is passed to it directly. - * - When `script` is a file path, the file is assumed to be a commonjs module. The default export, either - * `module.exports` or `module.exports.default`, should be a function that's going to be executed with this - * argument. + * @param arg Optional argument to pass to `script` (only supported when passing a function). */ addInitScript(script: PageFunction | { path?: string, content?: string }, arg?: Arg): Promise; /** - * Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. + * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for + * async listeners to complete or to ignore subsequent errors from these listeners. * @param type * @param options */ removeAllListeners(type?: string): this; /** - * Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. + * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for + * async listeners to complete or to ignore subsequent errors from these listeners. * @param type * @param options */ - removeAllListeners(type: string | undefined, options: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise; + removeAllListeners(type: string | undefined, options: { + /** + * Specifies whether to wait for already running listeners and what to do if they throw errors: + * - `'default'` - do not wait for current listener calls (if any) to finish, if the listener throws, it may result in unhandled error + * - `'wait'` - wait for current listener calls (if any) to finish + * - `'ignoreErrors'` - do not wait for current listener calls (if any) to finish, all errors thrown by the listeners after removal are silently caught + */ + behavior?: 'wait'|'ignoreErrors'|'default' + }): Promise; /** * **NOTE** Only works with Chromium browser's persistent context. * @@ -9022,17 +9000,27 @@ export interface BrowserContext { */ export interface Browser { /** - * Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. + * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for + * async listeners to complete or to ignore subsequent errors from these listeners. * @param type * @param options */ removeAllListeners(type?: string): this; /** - * Removes all the listeners of the given type if the type is given. Otherwise removes all the listeners. + * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for + * async listeners to complete or to ignore subsequent errors from these listeners. * @param type * @param options */ - removeAllListeners(type: string | undefined, options: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise; + removeAllListeners(type: string | undefined, options: { + /** + * Specifies whether to wait for already running listeners and what to do if they throw errors: + * - `'default'` - do not wait for current listener calls (if any) to finish, if the listener throws, it may result in unhandled error + * - `'wait'` - wait for current listener calls (if any) to finish + * - `'ignoreErrors'` - do not wait for current listener calls (if any) to finish, all errors thrown by the listeners after removal are silently caught + */ + behavior?: 'wait'|'ignoreErrors'|'default' + }): Promise; /** * Emitted when Browser gets disconnected from the browser application. This might happen because of one of the * following: @@ -9372,10 +9360,6 @@ export interface Browser { /** * Network proxy settings to use with this context. Defaults to none. - * - * **NOTE** For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If - * all contexts override the proxy, global proxy will be never used and can be any string, for example `launch({ - * proxy: { server: 'http://per-context' } })`. */ proxy?: { /** @@ -11140,10 +11124,8 @@ export interface ElementHandle extends JSHandle { force?: boolean; /** - * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You - * can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as - * navigating to inaccessible pages. Defaults to `false`. - * @deprecated This option will default to `true` in the future. + * This option has no effect. + * @deprecated This option has no effect. */ noWaitAfter?: boolean; @@ -13335,10 +13317,8 @@ export interface Locator { force?: boolean; /** - * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You - * can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as - * navigating to inaccessible pages. Defaults to `false`. - * @deprecated This option will default to `true` in the future. + * This option has no effect. + * @deprecated This option has no effect. */ noWaitAfter?: boolean; @@ -15155,6 +15135,8 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; +export const _bidiChromium: BrowserType; +export const _bidiFirefox: BrowserType; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; @@ -17340,8 +17322,8 @@ export interface APIResponse { headers(): { [key: string]: string; }; /** - * An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers - * with multiple entries, such as `Set-Cookie`, appear in the array multiple times. + * An array with all the response HTTP headers associated with this response. Header names are not lower-cased. + * Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times. */ headersArray(): Array<{ /** @@ -19257,8 +19239,14 @@ export interface Request { response(): Promise; /** - * This method will always return `null`. - * @deprecated Requests made by a Service Worker are not reported in Playwright. + * The Service {@link Worker} that is performing the request. + * + * **Details** + * + * This method is Chromium only. It's safe to call when using other browsers, but it will always be `null`. + * + * Requests originated in a Service Worker do not have a + * [request.frame()](https://playwright.dev/docs/api/class-request#request-frame) available. */ serviceWorker(): null|Worker; @@ -20847,10 +20835,6 @@ export interface BrowserContextOptions { /** * Network proxy settings to use with this context. Defaults to none. - * - * **NOTE** For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If - * all contexts override the proxy, global proxy will be never used and can be any string, for example `launch({ - * proxy: { server: 'http://per-context' } })`. */ proxy?: { /** diff --git a/packages/playwright-ct-core/index.js b/packages/playwright-ct-core/index.js index cda6e7ad55..1a4f185a31 100644 --- a/packages/playwright-ct-core/index.js +++ b/packages/playwright-ct-core/index.js @@ -16,7 +16,6 @@ const { test: baseTest, expect, devices, defineConfig: originalDefineConfig } = require('playwright/test'); const { fixtures } = require('./lib/mount'); -const { clearCacheCommand, runDevServerCommand, findRelatedTestFilesCommand } = require('./lib/cliOverrides'); const { createPlugin } = require('./lib/vitePlugin'); const defineConfig = (...configs) => { @@ -29,11 +28,6 @@ const defineConfig = (...configs) => { babelPlugins: [ [require.resolve('./lib/tsxTransform')] ], - cli: { - 'clear-cache': clearCacheCommand, - 'dev-server': runDevServerCommand, - 'find-related-test-files': findRelatedTestFilesCommand, - }, } }; }; diff --git a/packages/playwright-ct-core/package.json b/packages/playwright-ct-core/package.json index 8705546806..ef959ebeb5 100644 --- a/packages/playwright-ct-core/package.json +++ b/packages/playwright-ct-core/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-core", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing Helpers", "repository": { "type": "git", @@ -26,8 +26,8 @@ } }, "dependencies": { - "playwright-core": "1.47.0-next", + "playwright-core": "1.48.0-next", "vite": "^5.2.8", - "playwright": "1.47.0-next" + "playwright": "1.48.0-next" } } diff --git a/packages/playwright-ct-core/src/cliOverrides.ts b/packages/playwright-ct-core/src/cliOverrides.ts deleted file mode 100644 index d57014f636..0000000000 --- a/packages/playwright-ct-core/src/cliOverrides.ts +++ /dev/null @@ -1,39 +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. - */ - -import { affectedTestFiles, cacheDir } from 'playwright/lib/transform/compilationCache'; -import { buildBundle } from './vitePlugin'; -import { resolveDirs } from './viteUtils'; -import { runDevServer } from './devServer'; -import type { FullConfigInternal } from 'playwright/lib/common/config'; -import { removeFolderAndLogToConsole } from 'playwright/lib/runner/testServer'; - -export async function clearCacheCommand(config: FullConfigInternal) { - const dirs = await resolveDirs(config.configDir, config.config); - if (dirs) - await removeFolderAndLogToConsole(dirs.outDir); - await removeFolderAndLogToConsole(cacheDir); -} - -export async function findRelatedTestFilesCommand(files: string[], config: FullConfigInternal) { - await buildBundle(config.config, config.configDir); - return { testFiles: affectedTestFiles(files) }; -} - -export async function runDevServerCommand(config: FullConfigInternal) { - return await runDevServer(config); -} diff --git a/packages/playwright-ct-core/src/devServer.ts b/packages/playwright-ct-core/src/devServer.ts index 3e833fcd9c..2152a672b3 100644 --- a/packages/playwright-ct-core/src/devServer.ts +++ b/packages/playwright-ct-core/src/devServer.ts @@ -17,28 +17,26 @@ import fs from 'fs'; import path from 'path'; import { Watcher } from 'playwright/lib/fsWatcher'; -import { Runner } from 'playwright/lib/runner/runner'; import type { PluginContext } from 'rollup'; import { source as injectedSource } from './generated/indexSource'; import { createConfig, populateComponentsFromTests, resolveDirs, transformIndexFile, frameworkConfig } from './viteUtils'; import type { ComponentRegistry } from './viteUtils'; -import type { FullConfigInternal } from 'playwright/lib/common/config'; +import type { FullConfig } from 'playwright/test'; -export async function runDevServer(config: FullConfigInternal): Promise<() => Promise> { - const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config.config); - const runner = new Runner(config); - await runner.loadAllTests(); +export async function runDevServer(config: FullConfig): Promise<() => Promise> { + const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config); const componentRegistry: ComponentRegistry = new Map(); await populateComponentsFromTests(componentRegistry); - const dirs = await resolveDirs(config.configDir, config.config); + const configDir = config.configFile ? path.dirname(config.configFile) : config.rootDir; + const dirs = await resolveDirs(configDir, config); if (!dirs) { // eslint-disable-next-line no-console console.log(`Template file playwright/index.html is missing.`); return async () => {}; } const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8'); - const viteConfig = await createConfig(dirs, config.config, frameworkPluginFactory, false); + const viteConfig = await createConfig(dirs, config, frameworkPluginFactory, false); viteConfig.plugins.push({ name: 'playwright:component-index', @@ -57,8 +55,8 @@ export async function runDevServer(config: FullConfigInternal): Promise<() => Pr const projectDirs = new Set(); const projectOutputs = new Set(); for (const p of config.projects) { - projectDirs.add(p.project.testDir); - projectOutputs.add(p.project.outputDir); + projectDirs.add(p.testDir); + projectOutputs.add(p.outputDir); } const globalWatcher = new Watcher(async () => { diff --git a/packages/playwright-ct-core/src/injected/serializers.ts b/packages/playwright-ct-core/src/injected/serializers.ts index d07be7c1f8..be083e27db 100644 --- a/packages/playwright-ct-core/src/injected/serializers.ts +++ b/packages/playwright-ct-core/src/injected/serializers.ts @@ -66,6 +66,13 @@ export function transformObject(value: any, mapping: (v: any) => { result: any } result.push(transformObject(item, mapping)); return result; } + if (value?.__pw_type === 'jsx' && typeof value.type === 'function') { + throw new Error([ + `Component "${value.type.name}" cannot be mounted.`, + `Most likely, this component is defined in the test file. Create a test story instead.`, + `For more information, see https://playwright.dev/docs/test-components#test-stories.`, + ].join('\n')); + } const result2: any = {}; for (const [key, prop] of Object.entries(value)) result2[key] = transformObject(prop, mapping); diff --git a/packages/playwright-ct-core/src/vitePlugin.ts b/packages/playwright-ct-core/src/vitePlugin.ts index 80f4d21774..ec738c8700 100644 --- a/packages/playwright-ct-core/src/vitePlugin.ts +++ b/packages/playwright-ct-core/src/vitePlugin.ts @@ -31,6 +31,8 @@ import type { ImportInfo } from './tsxTransform'; import type { ComponentRegistry } from './viteUtils'; import { createConfig, frameworkConfig, hasJSComponents, populateComponentsFromTests, resolveDirs, resolveEndpoint, transformIndexFile } from './viteUtils'; import { resolveHook } from 'playwright/lib/transform/transform'; +import { runDevServer } from './devServer'; +import { removeDirAndLogToConsole } from 'playwright/lib/util'; const log = debug('pw:vite'); @@ -73,6 +75,17 @@ export function createPlugin(): TestRunnerPlugin { populateDependencies: async () => { await buildBundle(config, configDir); }, + + startDevServer: async () => { + return await runDevServer(config); + }, + + clearCache: async () => { + const configDir = config.configFile ? path.dirname(config.configFile) : config.rootDir; + const dirs = await resolveDirs(configDir, config); + if (dirs) + await removeDirAndLogToConsole(dirs.outDir); + }, }; } @@ -249,7 +262,7 @@ function vitePlugin(registerSource: string, templateDir: string, buildInfo: Buil async writeBundle(this: PluginContext) { for (const importInfo of importInfos.values()) { - const importPath = resolveHook(importInfo.filename, importInfo.importSource, true); + const importPath = resolveHook(importInfo.filename, importInfo.importSource); if (!importPath) continue; const deps = new Set(); diff --git a/packages/playwright-ct-core/src/viteUtils.ts b/packages/playwright-ct-core/src/viteUtils.ts index d1160c16f0..a9f7020999 100644 --- a/packages/playwright-ct-core/src/viteUtils.ts +++ b/packages/playwright-ct-core/src/viteUtils.ts @@ -147,13 +147,13 @@ export async function populateComponentsFromTests(componentRegistry: ComponentRe for (const importInfo of importList) componentRegistry.set(importInfo.id, importInfo); if (componentsByImportingFile) - componentsByImportingFile.set(file, importList.map(i => resolveHook(i.filename, i.importSource, true)).filter(Boolean) as string[]); + componentsByImportingFile.set(file, importList.map(i => resolveHook(i.filename, i.importSource)).filter(Boolean) as string[]); } } export function hasJSComponents(components: ImportInfo[]): boolean { for (const component of components) { - const importPath = resolveHook(component.filename, component.importSource, true); + const importPath = resolveHook(component.filename, component.importSource); const extname = importPath ? path.extname(importPath) : ''; if (extname === '.js' || (importPath && !extname && fs.existsSync(importPath + '.js'))) return true; @@ -183,7 +183,7 @@ export function transformIndexFile(id: string, content: string, templateDir: str lines.push(registerSource); for (const value of importInfos.values()) { - const importPath = resolveHook(value.filename, value.importSource, true) || value.importSource; + const importPath = resolveHook(value.filename, value.importSource) || value.importSource; lines.push(`const ${value.id} = () => import('${importPath?.replaceAll(path.sep, '/')}').then((mod) => mod.${value.remoteName || 'default'});`); } diff --git a/packages/playwright-ct-react/package.json b/packages/playwright-ct-react/package.json index d788859d3e..b024d75855 100644 --- a/packages/playwright-ct-react/package.json +++ b/packages/playwright-ct-react/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-react", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing for React", "repository": { "type": "git", @@ -30,7 +30,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-react": "^4.2.1" }, "bin": { diff --git a/packages/playwright-ct-react17/package.json b/packages/playwright-ct-react17/package.json index d60e249184..46e1c80fff 100644 --- a/packages/playwright-ct-react17/package.json +++ b/packages/playwright-ct-react17/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-react17", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing for React", "repository": { "type": "git", @@ -30,7 +30,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-react": "^4.2.1" }, "bin": { diff --git a/packages/playwright-ct-solid/package.json b/packages/playwright-ct-solid/package.json index b99cd395b6..5fa614a43b 100644 --- a/packages/playwright-ct-solid/package.json +++ b/packages/playwright-ct-solid/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-solid", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing for Solid", "repository": { "type": "git", @@ -30,7 +30,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "vite-plugin-solid": "^2.7.0" }, "devDependencies": { diff --git a/packages/playwright-ct-svelte/package.json b/packages/playwright-ct-svelte/package.json index 3f287315c2..1b3cb47789 100644 --- a/packages/playwright-ct-svelte/package.json +++ b/packages/playwright-ct-svelte/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-svelte", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing for Svelte", "repository": { "type": "git", @@ -30,11 +30,11 @@ "./package.json": "./package.json" }, "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@sveltejs/vite-plugin-svelte": "^3.0.1" }, "devDependencies": { - "svelte": "^4.2.8" + "svelte": "^4.2.19" }, "bin": { "playwright": "cli.js" diff --git a/packages/playwright-ct-vue/package.json b/packages/playwright-ct-vue/package.json index 35b340902b..f648cf2aca 100644 --- a/packages/playwright-ct-vue/package.json +++ b/packages/playwright-ct-vue/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-vue", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing for Vue", "repository": { "type": "git", @@ -30,7 +30,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-vue": "^4.2.1" }, "bin": { diff --git a/packages/playwright-ct-vue2/package.json b/packages/playwright-ct-vue2/package.json index 10be77c26b..c104e44ca6 100644 --- a/packages/playwright-ct-vue2/package.json +++ b/packages/playwright-ct-vue2/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-vue2", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "Playwright Component Testing for Vue2", "repository": { "type": "git", @@ -30,7 +30,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@playwright/experimental-ct-core": "1.47.0-next", + "@playwright/experimental-ct-core": "1.48.0-next", "@vitejs/plugin-vue2": "^2.2.0" }, "devDependencies": { diff --git a/packages/playwright-firefox/package.json b/packages/playwright-firefox/package.json index dd528d0ef8..7fe7b929f1 100644 --- a/packages/playwright-firefox/package.json +++ b/packages/playwright-firefox/package.json @@ -1,6 +1,6 @@ { "name": "playwright-firefox", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate Firefox", "repository": { "type": "git", @@ -30,6 +30,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" } } diff --git a/packages/playwright-test/package.json b/packages/playwright-test/package.json index fed09fd22f..d70f9f0a18 100644 --- a/packages/playwright-test/package.json +++ b/packages/playwright-test/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/test", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", @@ -30,6 +30,6 @@ }, "scripts": {}, "dependencies": { - "playwright": "1.47.0-next" + "playwright": "1.48.0-next" } } diff --git a/packages/playwright-webkit/package.json b/packages/playwright-webkit/package.json index f65486ed3a..ff6c8e97b0 100644 --- a/packages/playwright-webkit/package.json +++ b/packages/playwright-webkit/package.json @@ -1,6 +1,6 @@ { "name": "playwright-webkit", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate WebKit", "repository": { "type": "git", @@ -30,6 +30,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" } } diff --git a/packages/playwright/ThirdPartyNotices.txt b/packages/playwright/ThirdPartyNotices.txt index 21bb223549..f2bb64d661 100644 --- a/packages/playwright/ThirdPartyNotices.txt +++ b/packages/playwright/ThirdPartyNotices.txt @@ -5,8 +5,8 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. - @ampproject/remapping@2.2.1 (https://github.com/ampproject/remapping) -- @babel/code-frame@7.22.5 (https://github.com/babel/babel) - @babel/code-frame@7.24.2 (https://github.com/babel/babel) +- @babel/code-frame@7.24.7 (https://github.com/babel/babel) - @babel/compat-data@7.23.5 (https://github.com/babel/babel) - @babel/core@7.24.4 (https://github.com/babel/babel) - @babel/generator@7.24.4 (https://github.com/babel/babel) @@ -27,11 +27,11 @@ This project incorporates components from the projects listed below. The origina - @babel/helper-split-export-declaration@7.22.6 (https://github.com/babel/babel) - @babel/helper-string-parser@7.23.4 (https://github.com/babel/babel) - @babel/helper-validator-identifier@7.22.20 (https://github.com/babel/babel) -- @babel/helper-validator-identifier@7.22.5 (https://github.com/babel/babel) +- @babel/helper-validator-identifier@7.24.7 (https://github.com/babel/babel) - @babel/helper-validator-option@7.23.5 (https://github.com/babel/babel) - @babel/helpers@7.24.4 (https://github.com/babel/babel) -- @babel/highlight@7.22.5 (https://github.com/babel/babel) - @babel/highlight@7.24.2 (https://github.com/babel/babel) +- @babel/highlight@7.24.7 (https://github.com/babel/babel) - @babel/parser@7.24.4 (https://github.com/babel/babel) - @babel/plugin-proposal-decorators@7.24.1 (https://github.com/babel/babel) - @babel/plugin-proposal-explicit-resource-management@7.24.1 (https://github.com/babel/babel) @@ -67,29 +67,28 @@ This project incorporates components from the projects listed below. The origina - @babel/template@7.24.0 (https://github.com/babel/babel) - @babel/traverse@7.24.1 (https://github.com/babel/babel) - @babel/types@7.24.0 (https://github.com/babel/babel) -- @jest/expect-utils@29.5.0 (https://github.com/facebook/jest) -- @jest/schemas@29.4.3 (https://github.com/facebook/jest) -- @jest/types@29.5.0 (https://github.com/facebook/jest) +- @jest/expect-utils@29.7.0 (https://github.com/jestjs/jest) +- @jest/schemas@29.6.3 (https://github.com/jestjs/jest) +- @jest/types@29.6.3 (https://github.com/jestjs/jest) - @jridgewell/gen-mapping@0.3.5 (https://github.com/jridgewell/gen-mapping) - @jridgewell/resolve-uri@3.1.1 (https://github.com/jridgewell/resolve-uri) - @jridgewell/set-array@1.2.1 (https://github.com/jridgewell/set-array) - @jridgewell/sourcemap-codec@1.4.15 (https://github.com/jridgewell/sourcemap-codec) - @jridgewell/trace-mapping@0.3.25 (https://github.com/jridgewell/trace-mapping) -- @sinclair/typebox@0.25.24 (https://github.com/sinclairzx81/typebox) -- @types/istanbul-lib-coverage@2.0.4 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- @types/istanbul-lib-report@3.0.0 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- @types/istanbul-reports@3.0.1 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- @types/node@20.2.5 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- @types/stack-utils@2.0.1 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- @types/yargs-parser@21.0.0 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- @types/yargs@17.0.24 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @sinclair/typebox@0.27.8 (https://github.com/sinclairzx81/typebox) +- @types/istanbul-lib-coverage@2.0.6 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @types/istanbul-lib-report@3.0.3 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @types/istanbul-reports@3.0.4 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @types/node@22.5.4 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @types/stack-utils@2.0.3 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @types/yargs-parser@21.0.3 (https://github.com/DefinitelyTyped/DefinitelyTyped) +- @types/yargs@17.0.33 (https://github.com/DefinitelyTyped/DefinitelyTyped) - ansi-colors@4.1.3 (https://github.com/doowb/ansi-colors) - ansi-styles@3.2.1 (https://github.com/chalk/ansi-styles) - ansi-styles@4.3.0 (https://github.com/chalk/ansi-styles) - ansi-styles@5.2.0 (https://github.com/chalk/ansi-styles) - anymatch@3.1.3 (https://github.com/micromatch/anymatch) - binary-extensions@2.2.0 (https://github.com/sindresorhus/binary-extensions) -- braces@3.0.2 (https://github.com/micromatch/braces) - braces@3.0.3 (https://github.com/micromatch/braces) - browserslist@4.22.2 (https://github.com/browserslist/browserslist) - buffer-from@1.1.2 (https://github.com/LinusU/buffer-from) @@ -97,7 +96,7 @@ This project incorporates components from the projects listed below. The origina - chalk@2.4.2 (https://github.com/chalk/chalk) - chalk@4.1.2 (https://github.com/chalk/chalk) - chokidar@3.6.0 (https://github.com/paulmillr/chokidar) -- ci-info@3.8.0 (https://github.com/watson/ci-info) +- ci-info@3.9.0 (https://github.com/watson/ci-info) - codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror) - color-convert@1.9.3 (https://github.com/Qix-/color-convert) - color-convert@2.0.1 (https://github.com/Qix-/color-convert) @@ -105,14 +104,12 @@ This project incorporates components from the projects listed below. The origina - color-name@1.1.4 (https://github.com/colorjs/color-name) - convert-source-map@2.0.0 (https://github.com/thlorenz/convert-source-map) - debug@4.3.4 (https://github.com/debug-js/debug) -- diff-sequences@29.4.3 (https://github.com/facebook/jest) +- diff-sequences@29.6.3 (https://github.com/jestjs/jest) - electron-to-chromium@1.4.638 (https://github.com/kilian/electron-to-chromium) - enquirer@2.3.6 (https://github.com/enquirer/enquirer) - escalade@3.1.1 (https://github.com/lukeed/escalade) - escape-string-regexp@1.0.5 (https://github.com/sindresorhus/escape-string-regexp) - escape-string-regexp@2.0.0 (https://github.com/sindresorhus/escape-string-regexp) -- expect@29.5.0 (https://github.com/facebook/jest) -- fill-range@7.0.1 (https://github.com/jonschlinkert/fill-range) - fill-range@7.1.1 (https://github.com/jonschlinkert/fill-range) - gensync@1.0.0-beta.2 (https://github.com/loganfsmyth/gensync) - glob-parent@5.1.2 (https://github.com/gulpjs/glob-parent) @@ -124,24 +121,26 @@ This project incorporates components from the projects listed below. The origina - is-extglob@2.1.1 (https://github.com/jonschlinkert/is-extglob) - is-glob@4.0.3 (https://github.com/micromatch/is-glob) - is-number@7.0.0 (https://github.com/jonschlinkert/is-number) -- jest-diff@29.5.0 (https://github.com/facebook/jest) -- jest-get-type@29.4.3 (https://github.com/facebook/jest) -- jest-matcher-utils@29.5.0 (https://github.com/facebook/jest) -- jest-message-util@29.5.0 (https://github.com/facebook/jest) -- jest-util@29.5.0 (https://github.com/facebook/jest) +- jest-diff@29.7.0 (https://github.com/jestjs/jest) +- jest-get-type@29.6.3 (https://github.com/jestjs/jest) +- jest-matcher-utils@29.7.0 (https://github.com/jestjs/jest) +- jest-message-util@29.7.0 (https://github.com/jestjs/jest) +- jest-mock@29.7.0 (https://github.com/jestjs/jest) +- jest-util@29.7.0 (https://github.com/jestjs/jest) - js-tokens@4.0.0 (https://github.com/lydell/js-tokens) - jsesc@2.5.2 (https://github.com/mathiasbynens/jsesc) - json5@2.2.3 (https://github.com/json5/json5) - lru-cache@5.1.1 (https://github.com/isaacs/node-lru-cache) -- micromatch@4.0.5 (https://github.com/micromatch/micromatch) +- micromatch@4.0.8 (https://github.com/micromatch/micromatch) - ms@2.1.2 (https://github.com/zeit/ms) - node-releases@2.0.14 (https://github.com/chicoxyzzy/node-releases) - normalize-path@3.0.0 (https://github.com/jonschlinkert/normalize-path) - picocolors@1.0.0 (https://github.com/alexeyraspopov/picocolors) +- picocolors@1.1.0 (https://github.com/alexeyraspopov/picocolors) - picomatch@2.3.1 (https://github.com/micromatch/picomatch) - pirates@4.0.4 (https://github.com/danez/pirates) -- pretty-format@29.5.0 (https://github.com/facebook/jest) -- react-is@18.2.0 (https://github.com/facebook/react) +- pretty-format@29.7.0 (https://github.com/jestjs/jest) +- react-is@18.3.1 (https://github.com/facebook/react) - readdirp@3.6.0 (https://github.com/paulmillr/readdirp) - semver@6.3.1 (https://github.com/npm/node-semver) - slash@3.0.0 (https://github.com/sindresorhus/slash) @@ -153,6 +152,7 @@ This project incorporates components from the projects listed below. The origina - supports-color@7.2.0 (https://github.com/chalk/supports-color) - to-fast-properties@2.0.0 (https://github.com/sindresorhus/to-fast-properties) - to-regex-range@5.0.1 (https://github.com/micromatch/to-regex-range) +- undici-types@6.19.8 (https://github.com/nodejs/undici) - update-browserslist-db@1.0.13 (https://github.com/browserslist/update-db) - yallist@3.1.1 (https://github.com/isaacs/yallist) @@ -362,33 +362,6 @@ Apache License ========================================= END OF @ampproject/remapping@2.2.1 AND INFORMATION -%% @babel/code-frame@7.22.5 NOTICES AND INFORMATION BEGIN HERE -========================================= -MIT License - -Copyright (c) 2014-present Sebastian McKenzie and other contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY 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 @babel/code-frame@7.22.5 AND INFORMATION - %% @babel/code-frame@7.24.2 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -416,6 +389,33 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/code-frame@7.24.2 AND INFORMATION +%% @babel/code-frame@7.24.7 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY 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 @babel/code-frame@7.24.7 AND INFORMATION + %% @babel/compat-data@7.23.5 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -956,7 +956,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/helper-validator-identifier@7.22.20 AND INFORMATION -%% @babel/helper-validator-identifier@7.22.5 NOTICES AND INFORMATION BEGIN HERE +%% @babel/helper-validator-identifier@7.24.7 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -981,7 +981,7 @@ LIABLE FOR ANY 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 @babel/helper-validator-identifier@7.22.5 AND INFORMATION +END OF @babel/helper-validator-identifier@7.24.7 AND INFORMATION %% @babel/helper-validator-option@7.23.5 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -1037,33 +1037,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/helpers@7.24.4 AND INFORMATION -%% @babel/highlight@7.22.5 NOTICES AND INFORMATION BEGIN HERE -========================================= -MIT License - -Copyright (c) 2014-present Sebastian McKenzie and other contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY 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 @babel/highlight@7.22.5 AND INFORMATION - %% @babel/highlight@7.24.2 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -1091,6 +1064,33 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/highlight@7.24.2 AND INFORMATION +%% @babel/highlight@7.24.7 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY 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 @babel/highlight@7.24.7 AND INFORMATION + %% @babel/parser@7.24.4 NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (C) 2012-2014 by various contributors (see AUTHORS) @@ -2033,7 +2033,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/types@7.24.0 AND INFORMATION -%% @jest/expect-utils@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% @jest/expect-utils@29.7.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2057,9 +2057,9 @@ 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 @jest/expect-utils@29.5.0 AND INFORMATION +END OF @jest/expect-utils@29.7.0 AND INFORMATION -%% @jest/schemas@29.4.3 NOTICES AND INFORMATION BEGIN HERE +%% @jest/schemas@29.6.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2083,9 +2083,9 @@ 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 @jest/schemas@29.4.3 AND INFORMATION +END OF @jest/schemas@29.6.3 AND INFORMATION -%% @jest/types@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% @jest/types@29.6.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2109,7 +2109,7 @@ 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 @jest/types@29.5.0 AND INFORMATION +END OF @jest/types@29.6.3 AND INFORMATION %% @jridgewell/gen-mapping@0.3.5 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -2233,7 +2233,7 @@ SOFTWARE. ========================================= END OF @jridgewell/trace-mapping@0.3.25 AND INFORMATION -%% @sinclair/typebox@0.25.24 NOTICES AND INFORMATION BEGIN HERE +%% @sinclair/typebox@0.27.8 NOTICES AND INFORMATION BEGIN HERE ========================================= TypeBox: JSON Schema Type Builder with Static Type Resolution for TypeScript @@ -2259,9 +2259,9 @@ 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 @sinclair/typebox@0.25.24 AND INFORMATION +END OF @sinclair/typebox@0.27.8 AND INFORMATION -%% @types/istanbul-lib-coverage@2.0.4 NOTICES AND INFORMATION BEGIN HERE +%% @types/istanbul-lib-coverage@2.0.6 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2285,35 +2285,9 @@ MIT License OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ========================================= -END OF @types/istanbul-lib-coverage@2.0.4 AND INFORMATION +END OF @types/istanbul-lib-coverage@2.0.6 AND INFORMATION -%% @types/istanbul-lib-report@3.0.0 NOTICES AND INFORMATION BEGIN HERE -========================================= -MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 @types/istanbul-lib-report@3.0.0 AND INFORMATION - -%% @types/istanbul-reports@3.0.1 NOTICES AND INFORMATION BEGIN HERE +%% @types/istanbul-lib-report@3.0.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2337,9 +2311,9 @@ MIT License OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ========================================= -END OF @types/istanbul-reports@3.0.1 AND INFORMATION +END OF @types/istanbul-lib-report@3.0.3 AND INFORMATION -%% @types/node@20.2.5 NOTICES AND INFORMATION BEGIN HERE +%% @types/istanbul-reports@3.0.4 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2363,9 +2337,9 @@ MIT License OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ========================================= -END OF @types/node@20.2.5 AND INFORMATION +END OF @types/istanbul-reports@3.0.4 AND INFORMATION -%% @types/stack-utils@2.0.1 NOTICES AND INFORMATION BEGIN HERE +%% @types/node@22.5.4 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2389,9 +2363,9 @@ MIT License OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ========================================= -END OF @types/stack-utils@2.0.1 AND INFORMATION +END OF @types/node@22.5.4 AND INFORMATION -%% @types/yargs-parser@21.0.0 NOTICES AND INFORMATION BEGIN HERE +%% @types/stack-utils@2.0.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2415,9 +2389,9 @@ MIT License OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ========================================= -END OF @types/yargs-parser@21.0.0 AND INFORMATION +END OF @types/stack-utils@2.0.3 AND INFORMATION -%% @types/yargs@17.0.24 NOTICES AND INFORMATION BEGIN HERE +%% @types/yargs-parser@21.0.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -2441,7 +2415,33 @@ MIT License OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ========================================= -END OF @types/yargs@17.0.24 AND INFORMATION +END OF @types/yargs-parser@21.0.3 AND INFORMATION + +%% @types/yargs@17.0.33 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 @types/yargs@17.0.33 AND INFORMATION %% ansi-colors@4.1.3 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -2545,32 +2545,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ========================================= END OF binary-extensions@2.2.0 AND INFORMATION -%% braces@3.0.2 NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - -Copyright (c) 2014-2018, Jon Schlinkert. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 braces@3.0.2 AND INFORMATION - %% braces@3.0.3 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -3102,11 +3076,11 @@ THE SOFTWARE. ========================================= END OF chokidar@3.6.0 AND INFORMATION -%% ci-info@3.8.0 NOTICES AND INFORMATION BEGIN HERE +%% ci-info@3.9.0 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) -Copyright (c) 2016-2023 Thomas Watson Steen +Copyright (c) 2016 Thomas Watson Steen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -3126,7 +3100,7 @@ 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 ci-info@3.8.0 AND INFORMATION +END OF ci-info@3.9.0 AND INFORMATION %% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -3282,7 +3256,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF debug@4.3.4 AND INFORMATION -%% diff-sequences@29.4.3 NOTICES AND INFORMATION BEGIN HERE +%% diff-sequences@29.6.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -3306,7 +3280,7 @@ 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 diff-sequences@29.4.3 AND INFORMATION +END OF diff-sequences@29.6.3 AND INFORMATION %% electron-to-chromium@1.4.638 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -3398,58 +3372,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ========================================= END OF escape-string-regexp@2.0.0 AND INFORMATION -%% expect@29.5.0 NOTICES AND INFORMATION BEGIN HERE -========================================= -MIT License - -Copyright (c) Meta Platforms, Inc. and affiliates. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 expect@29.5.0 AND INFORMATION - -%% fill-range@7.0.1 NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - -Copyright (c) 2014-present, Jon Schlinkert. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 fill-range@7.0.1 AND INFORMATION - %% fill-range@7.1.1 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -3662,7 +3584,7 @@ THE SOFTWARE. ========================================= END OF is-number@7.0.0 AND INFORMATION -%% jest-diff@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% jest-diff@29.7.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -3686,9 +3608,9 @@ 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 jest-diff@29.5.0 AND INFORMATION +END OF jest-diff@29.7.0 AND INFORMATION -%% jest-get-type@29.4.3 NOTICES AND INFORMATION BEGIN HERE +%% jest-get-type@29.6.3 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -3712,9 +3634,9 @@ 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 jest-get-type@29.4.3 AND INFORMATION +END OF jest-get-type@29.6.3 AND INFORMATION -%% jest-matcher-utils@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% jest-matcher-utils@29.7.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -3738,9 +3660,9 @@ 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 jest-matcher-utils@29.5.0 AND INFORMATION +END OF jest-matcher-utils@29.7.0 AND INFORMATION -%% jest-message-util@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% jest-message-util@29.7.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -3764,9 +3686,9 @@ 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 jest-message-util@29.5.0 AND INFORMATION +END OF jest-message-util@29.7.0 AND INFORMATION -%% jest-util@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% jest-mock@29.7.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -3790,7 +3712,33 @@ 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 jest-util@29.5.0 AND INFORMATION +END OF jest-mock@29.7.0 AND INFORMATION + +%% jest-util@29.7.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 jest-util@29.7.0 AND INFORMATION %% js-tokens@4.0.0 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -3891,7 +3839,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ========================================= END OF lru-cache@5.1.1 AND INFORMATION -%% micromatch@4.0.5 NOTICES AND INFORMATION BEGIN HERE +%% micromatch@4.0.8 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -3915,7 +3863,7 @@ 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 micromatch@4.0.5 AND INFORMATION +END OF micromatch@4.0.8 AND INFORMATION %% ms@2.1.2 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -4015,6 +3963,26 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ========================================= END OF picocolors@1.0.0 AND INFORMATION +%% picocolors@1.1.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +ISC License + +Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF picocolors@1.1.0 AND INFORMATION + %% picomatch@2.3.1 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -4067,7 +4035,7 @@ SOFTWARE. ========================================= END OF pirates@4.0.4 AND INFORMATION -%% pretty-format@29.5.0 NOTICES AND INFORMATION BEGIN HERE +%% pretty-format@29.7.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -4091,9 +4059,9 @@ 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 pretty-format@29.5.0 AND INFORMATION +END OF pretty-format@29.7.0 AND INFORMATION -%% react-is@18.2.0 NOTICES AND INFORMATION BEGIN HERE +%% react-is@18.3.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -4117,7 +4085,7 @@ 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 react-is@18.2.0 AND INFORMATION +END OF react-is@18.3.1 AND INFORMATION %% readdirp@3.6.0 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -4358,6 +4326,32 @@ THE SOFTWARE. ========================================= END OF to-regex-range@5.0.1 AND INFORMATION +%% undici-types@6.19.8 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Matteo Collina and Undici contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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 undici-types@6.19.8 AND INFORMATION + %% update-browserslist-db@1.0.13 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) diff --git a/packages/playwright/bundles/expect/package-lock.json b/packages/playwright/bundles/expect/package-lock.json index a6bf58d4d2..73375dd81b 100644 --- a/packages/playwright/bundles/expect/package-lock.json +++ b/packages/playwright/bundles/expect/package-lock.json @@ -8,37 +8,43 @@ "name": "expect-bundle", "version": "0.0.1", "dependencies": { - "expect": "29.5.0", - "jest-matcher-utils": "29.5.0" + "@jest/expect-utils": "29.7.0", + "jest-get-type": "29.6.3", + "jest-matcher-utils": "29.7.0", + "jest-message-util": "29.7.0", + "jest-mock": "29.7.0", + "jest-util": "29.7.0" } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -109,33 +115,33 @@ } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -147,53 +153,56 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/node": { - "version": "20.2.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", - "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/ansi-styles": { "version": "4.3.0", @@ -210,11 +219,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -236,9 +245,9 @@ } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "funding": [ { "type": "github", @@ -266,9 +275,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -281,25 +290,10 @@ "node": ">=8" } }, - "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", - "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -329,53 +323,53 @@ } }, "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -383,12 +377,25 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dependencies": { + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -405,17 +412,22 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -428,11 +440,11 @@ } }, "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -452,9 +464,9 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/slash": { "version": "3.0.0", @@ -496,30 +508,37 @@ "engines": { "node": ">=8.0" } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" } }, "dependencies": { "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "requires": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" } }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "dependencies": { "ansi-styles": { @@ -574,27 +593,27 @@ } }, "@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "requires": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" } }, "@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "requires": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" } }, "@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -603,53 +622,56 @@ } }, "@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "requires": { "@types/istanbul-lib-coverage": "*" } }, "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "requires": { "@types/istanbul-lib-report": "*" } }, "@types/node": { - "version": "20.2.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", - "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "requires": { + "undici-types": "~6.19.2" + } }, "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "ansi-styles": { "version": "4.3.0", @@ -660,11 +682,11 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "chalk": { @@ -677,9 +699,9 @@ } }, "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==" }, "color-convert": { "version": "2.0.1", @@ -695,31 +717,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==" + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==" }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" }, - "expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", - "requires": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - } - }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -740,54 +750,64 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "requires": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==" + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==" }, "jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "requires": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, - "jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "requires": { + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -801,25 +821,30 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, + "picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -832,9 +857,9 @@ } }, "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "slash": { "version": "3.0.0", @@ -864,6 +889,11 @@ "requires": { "is-number": "^7.0.0" } + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" } } } diff --git a/packages/playwright/bundles/expect/package.json b/packages/playwright/bundles/expect/package.json index 11ea4cc42e..d9f436755f 100644 --- a/packages/playwright/bundles/expect/package.json +++ b/packages/playwright/bundles/expect/package.json @@ -9,7 +9,11 @@ "generate-license": "node ../../../../utils/generate_third_party_notice.js" }, "dependencies": { - "expect": "29.5.0", - "jest-matcher-utils": "29.5.0" + "@jest/expect-utils": "29.7.0", + "jest-get-type": "29.6.3", + "jest-matcher-utils": "29.7.0", + "jest-message-util": "29.7.0", + "jest-mock": "29.7.0", + "jest-util": "29.7.0" } } diff --git a/packages/playwright/bundles/expect/src/expectBundleImpl.ts b/packages/playwright/bundles/expect/src/expectBundleImpl.ts index 3eddff1323..dbfd169353 100644 --- a/packages/playwright/bundles/expect/src/expectBundleImpl.ts +++ b/packages/playwright/bundles/expect/src/expectBundleImpl.ts @@ -14,8 +14,30 @@ * limitations under the License. */ -import expectLibrary from 'expect'; +import expectLibrary from '../third_party/index'; export const expect = expectLibrary; +export * as mock from 'jest-mock'; +import * as am from '../third_party/asymmetricMatchers'; +import * as mu from 'jest-matcher-utils'; + +export const asymmetricMatchers = { + any: am.any, + anything: am.anything, + arrayContaining: am.arrayContaining, + arrayNotContaining: am.arrayNotContaining, + closeTo: am.closeTo, + notCloseTo: am.notCloseTo, + objectContaining: am.objectContaining, + objectNotContaining: am.objectNotContaining, + stringContaining: am.stringContaining, + stringMatching: am.stringMatching, + stringNotContaining: am.stringNotContaining, + stringNotMatching: am.stringNotMatching, +}; + +export const matcherUtils = { + stringify: mu.stringify, +}; export { INVERTED_COLOR, diff --git a/packages/playwright/bundles/expect/third_party/LICENSE b/packages/playwright/bundles/expect/third_party/LICENSE new file mode 100644 index 0000000000..b93be90515 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 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. diff --git a/packages/playwright/bundles/expect/third_party/asymmetricMatchers.ts b/packages/playwright/bundles/expect/third_party/asymmetricMatchers.ts new file mode 100644 index 0000000000..4e7a3402c9 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/asymmetricMatchers.ts @@ -0,0 +1,362 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + equals, + getObjectKeys, + isA, + iterableEquality, + subsetEquality, +} from '@jest/expect-utils'; +import * as matcherUtils from 'jest-matcher-utils'; +import { pluralize } from 'jest-util'; +import { getCustomEqualityTesters, getState } from './jestMatchersObject'; +import type { + AsymmetricMatcher as AsymmetricMatcherInterface, + MatcherContext, + MatcherState, +} from './types'; + +const functionToString = Function.prototype.toString; + +function fnNameFor(func: () => unknown) { + if (func.name) + return func.name; + + const matches = functionToString + .call(func) + .match(/^(?:async)?\s*function\s*\*?\s*([\w$]+)\s*\(/); + return matches ? matches[1] : ''; +} + +const utils = Object.freeze({ + ...matcherUtils, + iterableEquality, + subsetEquality, +}); + +function getPrototype(obj: object) { + if (Object.getPrototypeOf) + return Object.getPrototypeOf(obj); + + if (obj.constructor.prototype === obj) + return null; + + return obj.constructor.prototype; +} + +export function hasProperty( + obj: object | null, + property: string | symbol, +): boolean { + if (!obj) + return false; + + if (Object.prototype.hasOwnProperty.call(obj, property)) + return true; + + return hasProperty(getPrototype(obj), property); +} + +export abstract class AsymmetricMatcher +implements AsymmetricMatcherInterface { + $$typeof = Symbol.for('jest.asymmetricMatcher'); + + constructor(protected sample: T, protected inverse = false) { } + + protected getMatcherContext(): MatcherContext { + return { + customTesters: getCustomEqualityTesters(), + + dontThrow: () => { }, + ...getState(), + equals, + isNot: this.inverse, + utils, + }; + } + + abstract asymmetricMatch(other: unknown): boolean; + abstract toString(): string; + getExpectedType?(): string; + toAsymmetricMatcher?(): string; +} + +class Any extends AsymmetricMatcher { + constructor(sample: unknown) { + if (typeof sample === 'undefined') { + throw new TypeError( + 'any() expects to be passed a constructor function. ' + + 'Please pass one or use anything() to match any object.', + ); + } + super(sample); + } + + asymmetricMatch(other: unknown) { + if (this.sample === String) + return typeof other === 'string' || other instanceof String; + + if (this.sample === Number) + return typeof other === 'number' || other instanceof Number; + + if (this.sample === Function) + return typeof other === 'function' || other instanceof Function; + + if (this.sample === Boolean) + return typeof other === 'boolean' || other instanceof Boolean; + + if (this.sample === BigInt) + return typeof other === 'bigint' || other instanceof BigInt; + + if (this.sample === Symbol) + return typeof other === 'symbol' || other instanceof Symbol; + + if (this.sample === Object) + return typeof other === 'object'; + + return other instanceof this.sample; + } + + toString() { + return 'Any'; + } + + override getExpectedType() { + if (this.sample === String) + return 'string'; + + if (this.sample === Number) + return 'number'; + + if (this.sample === Function) + return 'function'; + + if (this.sample === Object) + return 'object'; + + if (this.sample === Boolean) + return 'boolean'; + + return fnNameFor(this.sample); + } + + override toAsymmetricMatcher() { + return `Any<${fnNameFor(this.sample)}>`; + } +} + +class Anything extends AsymmetricMatcher { + asymmetricMatch(other: unknown) { + // eslint-disable-next-line eqeqeq + return other != null; + } + + toString() { + return 'Anything'; + } + + // No getExpectedType method, because it matches either null or undefined. + + override toAsymmetricMatcher() { + return 'Anything'; + } +} + +class ArrayContaining extends AsymmetricMatcher> { + constructor(sample: Array, inverse = false) { + super(sample, inverse); + } + + asymmetricMatch(other: unknown) { + if (!Array.isArray(this.sample)) { + throw new Error( + `You must provide an array to ${this.toString()}, not '${typeof this + .sample}'.`, + ); + } + + const matcherContext = this.getMatcherContext(); + const result = + this.sample.length === 0 || + (Array.isArray(other) && + this.sample.every(item => + other.some(another => + equals(item, another, matcherContext.customTesters), + ), + )); + + return this.inverse ? !result : result; + } + + toString() { + return `Array${this.inverse ? 'Not' : ''}Containing`; + } + + override getExpectedType() { + return 'array'; + } +} + +class ObjectContaining extends AsymmetricMatcher< + Record +> { + constructor(sample: Record, inverse = false) { + super(sample, inverse); + } + + asymmetricMatch(other: any) { + if (typeof this.sample !== 'object') { + throw new Error( + `You must provide an object to ${this.toString()}, not '${typeof this + .sample}'.`, + ); + } + + let result = true; + + const matcherContext = this.getMatcherContext(); + const objectKeys = getObjectKeys(this.sample); + + for (const key of objectKeys) { + if ( + !hasProperty(other, key) || + !equals(this.sample[key], other[key], matcherContext.customTesters) + ) { + result = false; + break; + } + } + + return this.inverse ? !result : result; + } + + toString() { + return `Object${this.inverse ? 'Not' : ''}Containing`; + } + + override getExpectedType() { + return 'object'; + } +} + +class StringContaining extends AsymmetricMatcher { + constructor(sample: string, inverse = false) { + if (!isA('String', sample)) + throw new Error('Expected is not a string'); + super(sample, inverse); + } + + asymmetricMatch(other: unknown) { + const result = isA('String', other) && other.includes(this.sample); + + return this.inverse ? !result : result; + } + + toString() { + return `String${this.inverse ? 'Not' : ''}Containing`; + } + + override getExpectedType() { + return 'string'; + } +} + +class StringMatching extends AsymmetricMatcher { + constructor(sample: string | RegExp, inverse = false) { + if (!isA('String', sample) && !isA('RegExp', sample)) + throw new Error('Expected is not a String or a RegExp'); + super(new RegExp(sample), inverse); + } + + asymmetricMatch(other: unknown) { + const result = isA('String', other) && this.sample.test(other); + + return this.inverse ? !result : result; + } + + toString() { + return `String${this.inverse ? 'Not' : ''}Matching`; + } + + override getExpectedType() { + return 'string'; + } +} + +class CloseTo extends AsymmetricMatcher { + private readonly precision: number; + + constructor(sample: number, precision = 2, inverse = false) { + if (!isA('Number', sample)) + throw new Error('Expected is not a Number'); + + if (!isA('Number', precision)) + throw new Error('Precision is not a Number'); + + super(sample); + this.inverse = inverse; + this.precision = precision; + } + + asymmetricMatch(other: unknown) { + if (!isA('Number', other)) + return false; + let result = false; + if (other === Infinity && this.sample === Infinity) { + result = true; // Infinity - Infinity is NaN + } else if (other === -Infinity && this.sample === -Infinity) { + result = true; // -Infinity - -Infinity is NaN + } else { + result = + Math.abs(this.sample - other) < Math.pow(10, -this.precision) / 2; + } + return this.inverse ? !result : result; + } + + toString() { + return `Number${this.inverse ? 'Not' : ''}CloseTo`; + } + + override getExpectedType() { + return 'number'; + } + + override toAsymmetricMatcher(): string { + return [ + this.toString(), + this.sample, + `(${pluralize('digit', this.precision)})`, + ].join(' '); + } +} + +export const any = (expectedObject: unknown): Any => new Any(expectedObject); +export const anything = (): Anything => new Anything(); +export const arrayContaining = (sample: Array): ArrayContaining => + new ArrayContaining(sample); +export const arrayNotContaining = (sample: Array): ArrayContaining => + new ArrayContaining(sample, true); +export const objectContaining = ( + sample: Record, +): ObjectContaining => new ObjectContaining(sample); +export const objectNotContaining = ( + sample: Record, +): ObjectContaining => new ObjectContaining(sample, true); +export const stringContaining = (expected: string): StringContaining => + new StringContaining(expected); +export const stringNotContaining = (expected: string): StringContaining => + new StringContaining(expected, true); +export const stringMatching = (expected: string | RegExp): StringMatching => + new StringMatching(expected); +export const stringNotMatching = (expected: string | RegExp): StringMatching => + new StringMatching(expected, true); +export const closeTo = (expected: number, precision?: number): CloseTo => + new CloseTo(expected, precision); +export const notCloseTo = (expected: number, precision?: number): CloseTo => + new CloseTo(expected, precision, true); diff --git a/packages/playwright/bundles/expect/third_party/extractExpectedAssertionsErrors.ts b/packages/playwright/bundles/expect/third_party/extractExpectedAssertionsErrors.ts new file mode 100644 index 0000000000..1be9b4dce9 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/extractExpectedAssertionsErrors.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + EXPECTED_COLOR, + RECEIVED_COLOR, + matcherHint, + pluralize, +} from 'jest-matcher-utils'; +import { getState, setState } from './jestMatchersObject'; +import type { Expect, ExpectedAssertionsErrors } from './types'; + +const resetAssertionsLocalState = () => { + setState({ + assertionCalls: 0, + expectedAssertionsNumber: null, + isExpectingAssertions: false, + numPassingAsserts: 0, + }); +}; + +// Create and format all errors related to the mismatched number of `expect` +// calls and reset the matcher's state. +const extractExpectedAssertionsErrors: Expect['extractExpectedAssertionsErrors'] = + () => { + const result: ExpectedAssertionsErrors = []; + const { + assertionCalls, + expectedAssertionsNumber, + expectedAssertionsNumberError, + isExpectingAssertions, + isExpectingAssertionsError, + } = getState(); + + resetAssertionsLocalState(); + + if ( + typeof expectedAssertionsNumber === 'number' && + assertionCalls !== expectedAssertionsNumber + ) { + const numOfAssertionsExpected = EXPECTED_COLOR( + pluralize('assertion', expectedAssertionsNumber), + ); + + expectedAssertionsNumberError!.message = + `${matcherHint('.assertions', '', expectedAssertionsNumber.toString(), { + isDirectExpectCall: true, + })}\n\n` + + `Expected ${numOfAssertionsExpected} to be called but received ${RECEIVED_COLOR( + pluralize('assertion call', assertionCalls || 0), + )}.`; + + result.push({ + actual: assertionCalls.toString(), + error: expectedAssertionsNumberError!, + expected: expectedAssertionsNumber.toString(), + }); + } + if (isExpectingAssertions && assertionCalls === 0) { + const expected = EXPECTED_COLOR('at least one assertion'); + const received = RECEIVED_COLOR('received none'); + + isExpectingAssertionsError!.message = `${matcherHint( + '.hasAssertions', + '', + '', + { isDirectExpectCall: true }, + )}\n\nExpected ${expected} to be called but ${received}.`; + + result.push({ + actual: 'none', + error: isExpectingAssertionsError!, + expected: 'at least one', + }); + } + + return result; + }; + +export default extractExpectedAssertionsErrors; diff --git a/packages/playwright/bundles/expect/third_party/index.ts b/packages/playwright/bundles/expect/third_party/index.ts new file mode 100644 index 0000000000..3935ab71e1 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/index.ts @@ -0,0 +1,463 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { equals, iterableEquality, subsetEquality } from '@jest/expect-utils'; +import * as matcherUtils from 'jest-matcher-utils'; +import { isPromise } from 'jest-util'; +import { + any, + anything, + arrayContaining, + arrayNotContaining, + closeTo, + notCloseTo, + objectContaining, + objectNotContaining, + stringContaining, + stringMatching, + stringNotContaining, + stringNotMatching, +} from './asymmetricMatchers'; +import extractExpectedAssertionsErrors from './extractExpectedAssertionsErrors'; +import { + INTERNAL_MATCHER_FLAG, + addCustomEqualityTesters, + getCustomEqualityTesters, + getMatchers, + getState, + setMatchers, + setState, +} from './jestMatchersObject'; +import matchers from './matchers'; +import spyMatchers from './spyMatchers'; +import toThrowMatchers, { + createMatcher as createThrowMatcher, +} from './toThrowMatchers'; +import type { + Expect, + ExpectationResult, + MatcherContext, + MatcherState, + MatcherUtils, + MatchersObject, + PromiseMatcherFn, + RawMatcherFn, + SyncExpectationResult, + ThrowingMatcherFn, +} from './types'; + +export type { Tester, TesterContext } from '@jest/expect-utils'; +export { AsymmetricMatcher } from './asymmetricMatchers'; +export type { + AsyncExpectationResult, + AsymmetricMatchers, + BaseExpect, + Expect, + ExpectationResult, + MatcherContext, + MatcherFunction, + MatcherFunctionWithContext, + MatcherState, + MatcherUtils, + Matchers, + SyncExpectationResult, +} from './types'; + +export class JestAssertionError extends Error { + matcherResult?: Omit & { message: string }; +} + +const createToThrowErrorMatchingSnapshotMatcher = function( + matcher: RawMatcherFn, +) { + return function( + this: MatcherContext, + received: any, + testNameOrInlineSnapshot?: string, + ) { + return matcher.apply(this, [received, testNameOrInlineSnapshot, true]); + }; +}; + +const getPromiseMatcher = (name: string, matcher: RawMatcherFn) => { + if (name === 'toThrow' || name === 'toThrowError') + return createThrowMatcher(name, true); + else if ( + name === 'toThrowErrorMatchingSnapshot' || + name === 'toThrowErrorMatchingInlineSnapshot' + ) + return createToThrowErrorMatchingSnapshotMatcher(matcher); + + + return null; +}; + +export const expect: Expect = (actual: any, ...rest: Array) => { + if (rest.length !== 0) + throw new Error('Expect takes at most one argument.'); + + + const allMatchers = getMatchers(); + const expectation: any = { + not: {}, + rejects: { not: {} }, + resolves: { not: {} }, + }; + + const err = new JestAssertionError(); + + Object.keys(allMatchers).forEach(name => { + const matcher = allMatchers[name]; + const promiseMatcher = getPromiseMatcher(name, matcher) || matcher; + expectation[name] = makeThrowingMatcher(matcher, false, '', actual); + expectation.not[name] = makeThrowingMatcher(matcher, true, '', actual); + + expectation.resolves[name] = makeResolveMatcher( + name, + promiseMatcher, + false, + actual, + err, + ); + expectation.resolves.not[name] = makeResolveMatcher( + name, + promiseMatcher, + true, + actual, + err, + ); + + expectation.rejects[name] = makeRejectMatcher( + name, + promiseMatcher, + false, + actual, + err, + ); + expectation.rejects.not[name] = makeRejectMatcher( + name, + promiseMatcher, + true, + actual, + err, + ); + }); + + return expectation; +}; + +const getMessage = (message?: () => string) => + (message && message()) || + matcherUtils.RECEIVED_COLOR('No message was specified for this matcher.'); + +const makeResolveMatcher = + ( + matcherName: string, + matcher: RawMatcherFn, + isNot: boolean, + actual: Promise, + outerErr: JestAssertionError, + ): PromiseMatcherFn => + (...args) => { + const options = { + isNot, + promise: 'resolves', + }; + + if (!isPromise(actual)) { + throw new JestAssertionError( + matcherUtils.matcherErrorMessage( + matcherUtils.matcherHint(matcherName, undefined, '', options), + `${matcherUtils.RECEIVED_COLOR('received')} value must be a promise`, + matcherUtils.printWithType( + 'Received', + actual, + matcherUtils.printReceived, + ), + ), + ); + } + + const innerErr = new JestAssertionError(); + + return actual.then( + result => + makeThrowingMatcher(matcher, isNot, 'resolves', result, innerErr).apply( + null, + args, + ), + reason => { + outerErr.message = + `${matcherUtils.matcherHint( + matcherName, + undefined, + '', + options, + )}\n\n` + + 'Received promise rejected instead of resolved\n' + + `Rejected to value: ${matcherUtils.printReceived(reason)}`; + return Promise.reject(outerErr); + }, + ); + }; + +const makeRejectMatcher = + ( + matcherName: string, + matcher: RawMatcherFn, + isNot: boolean, + actual: Promise | (() => Promise), + outerErr: JestAssertionError, + ): PromiseMatcherFn => + (...args) => { + const options = { + isNot, + promise: 'rejects', + }; + + const actualWrapper: Promise = + typeof actual === 'function' ? actual() : actual; + + if (!isPromise(actualWrapper)) { + throw new JestAssertionError( + matcherUtils.matcherErrorMessage( + matcherUtils.matcherHint(matcherName, undefined, '', options), + `${matcherUtils.RECEIVED_COLOR( + 'received', + )} value must be a promise or a function returning a promise`, + matcherUtils.printWithType( + 'Received', + actual, + matcherUtils.printReceived, + ), + ), + ); + } + + const innerErr = new JestAssertionError(); + + return actualWrapper.then( + result => { + outerErr.message = + `${matcherUtils.matcherHint( + matcherName, + undefined, + '', + options, + )}\n\n` + + 'Received promise resolved instead of rejected\n' + + `Resolved to value: ${matcherUtils.printReceived(result)}`; + return Promise.reject(outerErr); + }, + reason => + makeThrowingMatcher(matcher, isNot, 'rejects', reason, innerErr).apply( + null, + args, + ), + ); + }; + +const makeThrowingMatcher = ( + matcher: RawMatcherFn, + isNot: boolean, + promise: string, + actual: any, + err?: JestAssertionError, +): ThrowingMatcherFn => + function throwingMatcher(...args): any { + let throws = true; + const utils: MatcherUtils['utils'] = { + ...matcherUtils, + iterableEquality, + subsetEquality, + }; + + const matcherUtilsThing: MatcherUtils = { + customTesters: getCustomEqualityTesters(), + // When throws is disabled, the matcher will not throw errors during test + // execution but instead add them to the global matcher state. If a + // matcher throws, test execution is normally stopped immediately. The + // snapshot matcher uses it because we want to log all snapshot + // failures in a test. + dontThrow: () => (throws = false), + equals, + utils, + }; + + const matcherContext: MatcherContext = { + ...getState(), + ...matcherUtilsThing, + error: err, + isNot, + promise, + }; + + const processResult = ( + result: SyncExpectationResult, + asyncError?: JestAssertionError, + ) => { + _validateResult(result); + + getState().assertionCalls++; + + if ((result.pass && isNot) || (!result.pass && !isNot)) { + // XOR + const message = getMessage(result.message); + let error; + + if (err) { + error = err; + error.message = message; + } else if (asyncError) { + error = asyncError; + error.message = message; + } else { + error = new JestAssertionError(message); + + // Try to remove this function from the stack trace frame. + // Guard for some environments (browsers) that do not support this feature. + if (Error.captureStackTrace) + Error.captureStackTrace(error, throwingMatcher); + + } + // Passing the result of the matcher with the error so that a custom + // reporter could access the actual and expected objects of the result + // for example in order to display a custom visual diff + error.matcherResult = { ...result, message }; + + if (throws) + throw error; + else + getState().suppressedErrors.push(error); + + } else { + getState().numPassingAsserts++; + } + }; + + const handleError = (error: Error) => { + if ( + matcher[INTERNAL_MATCHER_FLAG] === true && + !(error instanceof JestAssertionError) && + error.name !== 'PrettyFormatPluginError' && + // Guard for some environments (browsers) that do not support this feature. + Error.captureStackTrace + ) { + // Try to remove this and deeper functions from the stack trace frame. + Error.captureStackTrace(error, throwingMatcher); + } + throw error; + }; + + let potentialResult: ExpectationResult; + + try { + potentialResult = + matcher[INTERNAL_MATCHER_FLAG] === true + ? matcher.call(matcherContext, actual, ...args) + : // It's a trap specifically for inline snapshot to capture this name + // in the stack trace, so that it can correctly get the custom matcher + // function call. + (function __EXTERNAL_MATCHER_TRAP__() { + return matcher.call(matcherContext, actual, ...args); + })(); + + if (isPromise(potentialResult)) { + const asyncError = new JestAssertionError(); + if (Error.captureStackTrace) + Error.captureStackTrace(asyncError, throwingMatcher); + + + return potentialResult + .then(aResult => processResult(aResult, asyncError)) + .catch(handleError); + } else { + return processResult(potentialResult); + } + } catch (error: any) { + return handleError(error); + } + }; + +expect.extend = (matchers: MatchersObject) => + setMatchers(matchers, false, expect); + +expect.addEqualityTesters = customTesters => + addCustomEqualityTesters(customTesters); + +expect.anything = anything; +expect.any = any; + +expect.not = { + arrayContaining: arrayNotContaining, + closeTo: notCloseTo, + objectContaining: objectNotContaining, + stringContaining: stringNotContaining, + stringMatching: stringNotMatching, +}; + +expect.arrayContaining = arrayContaining; +expect.closeTo = closeTo; +expect.objectContaining = objectContaining; +expect.stringContaining = stringContaining; +expect.stringMatching = stringMatching; + +const _validateResult = (result: any) => { + if ( + typeof result !== 'object' || + typeof result.pass !== 'boolean' || + (result.message && + typeof result.message !== 'string' && + typeof result.message !== 'function') + ) { + throw new Error( + 'Unexpected return from a matcher function.\n' + + 'Matcher functions should ' + + 'return an object in the following format:\n' + + ' {message?: string | function, pass: boolean}\n' + + `'${matcherUtils.stringify(result)}' was returned`, + ); + } +}; + +function assertions(expected: number): void { + const error = new Error(); + if (Error.captureStackTrace) + Error.captureStackTrace(error, assertions); + + + setState({ + expectedAssertionsNumber: expected, + expectedAssertionsNumberError: error, + }); +} +function hasAssertions(...args: Array): void { + const error = new Error(); + if (Error.captureStackTrace) + Error.captureStackTrace(error, hasAssertions); + + + matcherUtils.ensureNoExpected(args[0], '.hasAssertions'); + setState({ + isExpectingAssertions: true, + isExpectingAssertionsError: error, + }); +} + +// add default jest matchers +setMatchers(matchers, true, expect); +setMatchers(spyMatchers, true, expect); +setMatchers(toThrowMatchers, true, expect); + +expect.assertions = assertions; +expect.hasAssertions = hasAssertions; +expect.getState = getState; +expect.setState = setState; +expect.extractExpectedAssertionsErrors = extractExpectedAssertionsErrors; + +export default expect; diff --git a/packages/playwright/bundles/expect/third_party/jestMatchersObject.ts b/packages/playwright/bundles/expect/third_party/jestMatchersObject.ts new file mode 100644 index 0000000000..7a9a304b30 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/jestMatchersObject.ts @@ -0,0 +1,144 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { Tester } from '@jest/expect-utils'; +import { getType } from 'jest-get-type'; +import { AsymmetricMatcher } from './asymmetricMatchers'; +import type { + Expect, + MatcherState, + MatchersObject, + SyncExpectationResult, +} from './types'; + +// Global matchers object holds the list of available matchers and +// the state, that can hold matcher specific values that change over time. +const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object'); + +// Notes a built-in/internal Jest matcher. +// Jest may override the stack trace of Errors thrown by internal matchers. +export const INTERNAL_MATCHER_FLAG = Symbol.for('$$jest-internal-matcher'); + +if (!Object.prototype.hasOwnProperty.call(globalThis, JEST_MATCHERS_OBJECT)) { + const defaultState: MatcherState = { + assertionCalls: 0, + expectedAssertionsNumber: null, + isExpectingAssertions: false, + numPassingAsserts: 0, + suppressedErrors: [], // errors that are not thrown immediately. + }; + Object.defineProperty(globalThis, JEST_MATCHERS_OBJECT, { + value: { + customEqualityTesters: [], + matchers: Object.create(null), + state: defaultState, + }, + }); +} + +export const getState = (): State => + (globalThis as any)[JEST_MATCHERS_OBJECT].state; + +export const setState = ( + state: Partial, +): void => { + Object.assign((globalThis as any)[JEST_MATCHERS_OBJECT].state, state); +}; + +export const getMatchers = (): MatchersObject => + (globalThis as any)[JEST_MATCHERS_OBJECT].matchers; + +export const setMatchers = ( + matchers: MatchersObject, + isInternal: boolean, + expect: Expect, +): void => { + Object.keys(matchers).forEach(key => { + const matcher = matchers[key]; + + if (typeof matcher !== 'function') { + throw new TypeError( + `expect.extend: \`${key}\` is not a valid matcher. Must be a function, is "${getType( + matcher, + )}"`, + ); + } + + Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, { + value: isInternal, + }); + + if (!isInternal) { + // expect is defined + + class CustomMatcher extends AsymmetricMatcher< + [unknown, ...Array] + > { + constructor(inverse = false, ...sample: [unknown, ...Array]) { + super(sample, inverse); + } + + asymmetricMatch(other: unknown) { + const { pass } = matcher.call( + this.getMatcherContext(), + other, + ...this.sample, + ) as SyncExpectationResult; + + return this.inverse ? !pass : pass; + } + + toString() { + return `${this.inverse ? 'not.' : ''}${key}`; + } + + override getExpectedType() { + return 'any'; + } + + override toAsymmetricMatcher() { + return `${this.toString()}<${this.sample.map(String).join(', ')}>`; + } + } + + Object.defineProperty(expect, key, { + configurable: true, + enumerable: true, + value: (...sample: [unknown, ...Array]) => + new CustomMatcher(false, ...sample), + writable: true, + }); + Object.defineProperty(expect.not, key, { + configurable: true, + enumerable: true, + value: (...sample: [unknown, ...Array]) => + new CustomMatcher(true, ...sample), + writable: true, + }); + } + }); + + Object.assign((globalThis as any)[JEST_MATCHERS_OBJECT].matchers, matchers); +}; + +export const getCustomEqualityTesters = (): Array => + (globalThis as any)[JEST_MATCHERS_OBJECT].customEqualityTesters; + +export const addCustomEqualityTesters = (newTesters: Array): void => { + if (!Array.isArray(newTesters)) { + throw new TypeError( + `expect.customEqualityTesters: Must be set to an array of Testers. Was given "${getType( + newTesters, + )}"`, + ); + } + + (globalThis as any)[JEST_MATCHERS_OBJECT].customEqualityTesters.push( + ...newTesters, + ); +}; diff --git a/packages/playwright/bundles/expect/third_party/matchers.ts b/packages/playwright/bundles/expect/third_party/matchers.ts new file mode 100644 index 0000000000..5102b50c32 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/matchers.ts @@ -0,0 +1,983 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +/* eslint-disable eqeqeq */ + +import { + arrayBufferEquality, + equals, + getObjectSubset, + getPath, + iterableEquality, + pathAsArray, + sparseArrayEquality, + subsetEquality, + typeEquality, +} from '@jest/expect-utils'; +import { getType, isPrimitive } from 'jest-get-type'; +import { + DIM_COLOR, + EXPECTED_COLOR, + type MatcherHintOptions, + RECEIVED_COLOR, + SUGGEST_TO_CONTAIN_EQUAL, + ensureExpectedIsNonNegativeInteger, + ensureNoExpected, + ensureNumbers, + getLabelPrinter, + matcherErrorMessage, + matcherHint, + printDiffOrStringify, + printExpected, + printReceived, + printWithType, + stringify, +} from 'jest-matcher-utils'; +import { + printCloseTo, + printExpectedConstructorName, + printExpectedConstructorNameNot, + printReceivedArrayContainExpectedItem, + printReceivedConstructorName, + printReceivedConstructorNameNot, + printReceivedStringContainExpectedResult, + printReceivedStringContainExpectedSubstring, +} from './print'; +import type { MatchersObject } from './types'; + +// Omit colon and one or more spaces, so can call getLabelPrinter. +const EXPECTED_LABEL = 'Expected'; +const RECEIVED_LABEL = 'Received'; +const EXPECTED_VALUE_LABEL = 'Expected value'; +const RECEIVED_VALUE_LABEL = 'Received value'; + +// The optional property of matcher context is true if undefined. +const isExpand = (expand?: boolean): boolean => expand !== false; + +const toStrictEqualTesters = [ + iterableEquality, + typeEquality, + sparseArrayEquality, + arrayBufferEquality, +]; + +type ContainIterable = + | Array + | Set + | NodeListOf + | DOMTokenList + | HTMLCollectionOf; + +const matchers: MatchersObject = { + toBe(received: unknown, expected: unknown) { + const matcherName = 'toBe'; + const options: MatcherHintOptions = { + comment: 'Object.is equality', + isNot: this.isNot, + promise: this.promise, + }; + + const pass = Object.is(received, expected); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}` + : () => { + const expectedType = getType(expected); + + let deepEqualityName = null; + if (expectedType !== 'map' && expectedType !== 'set') { + // If deep equality passes when referential identity fails, + // but exclude map and set until review of their equality logic. + if ( + equals( + received, + expected, + [...this.customTesters, ...toStrictEqualTesters], + true, + ) + ) + deepEqualityName = 'toStrictEqual'; + else if ( + equals(received, expected, [ + ...this.customTesters, + iterableEquality, + ]) + ) + deepEqualityName = 'toEqual'; + + } + + return ( + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + (deepEqualityName !== null + ? `${DIM_COLOR( + `If it should pass with deep equality, replace "${matcherName}" with "${deepEqualityName}"`, + )}\n\n` + : '') + + printDiffOrStringify( + expected, + received, + EXPECTED_LABEL, + RECEIVED_LABEL, + isExpand(this.expand), + ) + ); + }; + + // Passing the actual and expected objects so that a custom reporter + // could access them, for example in order to display a custom visual diff, + // or create a different error message + return { actual: received, expected, message, name: matcherName, pass }; + }, + + toBeCloseTo(received: number, expected: number, precision = 2) { + const matcherName = 'toBeCloseTo'; + const secondArgument = arguments.length === 3 ? 'precision' : undefined; + const isNot = this.isNot; + const options: MatcherHintOptions = { + isNot, + promise: this.promise, + secondArgument, + secondArgumentColor: (arg: string) => arg, + }; + + if (typeof expected !== 'number') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${EXPECTED_COLOR('expected')} value must be a number`, + printWithType('Expected', expected, printExpected), + ), + ); + } + + if (typeof received !== 'number') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${RECEIVED_COLOR('received')} value must be a number`, + printWithType('Received', received, printReceived), + ), + ); + } + + let pass = false; + let expectedDiff = 0; + let receivedDiff = 0; + + if (received === Infinity && expected === Infinity) { + pass = true; // Infinity - Infinity is NaN + } else if (received === -Infinity && expected === -Infinity) { + pass = true; // -Infinity - -Infinity is NaN + } else { + expectedDiff = Math.pow(10, -precision) / 2; + receivedDiff = Math.abs(expected - received); + pass = receivedDiff < expectedDiff; + } + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}\n` + + (receivedDiff === 0 + ? '' + : `Received: ${printReceived(received)}\n` + + `\n${printCloseTo(receivedDiff, expectedDiff, precision, isNot)}`) + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected: ${printExpected(expected)}\n` + + `Received: ${printReceived(received)}\n` + + '\n' + + printCloseTo(receivedDiff, expectedDiff, precision, isNot); + + return { message, pass }; + }, + + toBeDefined(received: unknown, expected: void) { + const matcherName = 'toBeDefined'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + + const pass = received !== void 0; + + const message = () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received: ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeFalsy(received: unknown, expected: void) { + const matcherName = 'toBeFalsy'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + + const pass = !received; + + const message = () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received: ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeGreaterThan(received: number | bigint, expected: number | bigint) { + const matcherName = 'toBeGreaterThan'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + isNot, + promise: this.promise, + }; + ensureNumbers(received, expected, matcherName, options); + + const pass = received > expected; + + const message = () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected:${isNot ? ' not' : ''} > ${printExpected(expected)}\n` + + `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeGreaterThanOrEqual(received: number | bigint, expected: number | bigint) { + const matcherName = 'toBeGreaterThanOrEqual'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + isNot, + promise: this.promise, + }; + ensureNumbers(received, expected, matcherName, options); + + const pass = received >= expected; + + const message = () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected:${isNot ? ' not' : ''} >= ${printExpected(expected)}\n` + + `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeInstanceOf(received: any, expected: Function) { + const matcherName = 'toBeInstanceOf'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + + if (typeof expected !== 'function') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${EXPECTED_COLOR('expected')} value must be a function`, + printWithType('Expected', expected, printExpected), + ), + ); + } + + const pass = received instanceof expected; + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printExpectedConstructorNameNot('Expected constructor', expected) + + (typeof received.constructor === 'function' && + received.constructor !== expected + ? printReceivedConstructorNameNot( + 'Received constructor', + received.constructor, + expected, + ) + : '') + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printExpectedConstructorName('Expected constructor', expected) + + (isPrimitive(received) || Object.getPrototypeOf(received) === null + ? `\nReceived value has no prototype\nReceived value: ${printReceived( + received, + )}` + : typeof received.constructor !== 'function' + ? `\nReceived value: ${printReceived(received)}` + : printReceivedConstructorName( + 'Received constructor', + received.constructor, + )); + + return { message, pass }; + }, + + toBeLessThan(received: number | bigint, expected: number | bigint) { + const matcherName = 'toBeLessThan'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + isNot, + promise: this.promise, + }; + ensureNumbers(received, expected, matcherName, options); + + const pass = received < expected; + + const message = () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected:${isNot ? ' not' : ''} < ${printExpected(expected)}\n` + + `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeLessThanOrEqual(received: number | bigint, expected: number | bigint) { + const matcherName = 'toBeLessThanOrEqual'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + isNot, + promise: this.promise, + }; + ensureNumbers(received, expected, matcherName, options); + + const pass = received <= expected; + + const message = () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected:${isNot ? ' not' : ''} <= ${printExpected(expected)}\n` + + `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeNaN(received: any, expected: void) { + const matcherName = 'toBeNaN'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + + const pass = Number.isNaN(received); + + const message = () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received: ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeNull(received: unknown, expected: void) { + const matcherName = 'toBeNull'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + + const pass = received === null; + + const message = () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received: ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeTruthy(received: unknown, expected: void) { + const matcherName = 'toBeTruthy'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + + const pass = !!received; + + const message = () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received: ${printReceived(received)}`; + + return { message, pass }; + }, + + toBeUndefined(received: unknown, expected: void) { + const matcherName = 'toBeUndefined'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + + const pass = received === void 0; + + const message = () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received: ${printReceived(received)}`; + + return { message, pass }; + }, + + toContain(received: ContainIterable | string, expected: unknown) { + const matcherName = 'toContain'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + comment: 'indexOf', + isNot, + promise: this.promise, + }; + + if (received == null) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${RECEIVED_COLOR('received')} value must not be null nor undefined`, + printWithType('Received', received, printReceived), + ), + ); + } + + if (typeof received === 'string') { + const wrongTypeErrorMessage = `${EXPECTED_COLOR( + 'expected', + )} value must be a string if ${RECEIVED_COLOR( + 'received', + )} value is a string`; + + if (typeof expected !== 'string') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, received, String(expected), options), + wrongTypeErrorMessage, + + printWithType('Expected', expected, printExpected) + + '\n' + + printWithType('Received', received, printReceived), + ), + ); + } + + const index = received.indexOf(String(expected)); + const pass = index !== -1; + + const message = () => { + const labelExpected = `Expected ${typeof expected === 'string' ? 'substring' : 'value' + }`; + const labelReceived = 'Received string'; + const printLabel = getLabelPrinter(labelExpected, labelReceived); + + return ( + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( + expected, + )}\n` + + `${printLabel(labelReceived)}${isNot ? ' ' : ''}${isNot + ? printReceivedStringContainExpectedSubstring( + received, + index, + String(expected).length, + ) + : printReceived(received) + }` + ); + }; + + return { message, pass }; + } + + const indexable = Array.from(received); + const index = indexable.indexOf(expected); + const pass = index !== -1; + + const message = () => { + const labelExpected = 'Expected value'; + const labelReceived = `Received ${getType(received)}`; + const printLabel = getLabelPrinter(labelExpected, labelReceived); + + return ( + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( + expected, + )}\n` + + `${printLabel(labelReceived)}${isNot ? ' ' : ''}${isNot && Array.isArray(received) + ? printReceivedArrayContainExpectedItem(received, index) + : printReceived(received) + }` + + (!isNot && + indexable.findIndex(item => + equals(item, expected, [...this.customTesters, iterableEquality]), + ) !== -1 + ? `\n\n${SUGGEST_TO_CONTAIN_EQUAL}` + : '') + ); + }; + + return { message, pass }; + }, + + toContainEqual(received: ContainIterable, expected: unknown) { + const matcherName = 'toContainEqual'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + comment: 'deep equality', + isNot, + promise: this.promise, + }; + + if (received == null) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${RECEIVED_COLOR('received')} value must not be null nor undefined`, + printWithType('Received', received, printReceived), + ), + ); + } + + const index = Array.from(received).findIndex(item => + equals(item, expected, [...this.customTesters, iterableEquality]), + ); + const pass = index !== -1; + + const message = () => { + const labelExpected = 'Expected value'; + const labelReceived = `Received ${getType(received)}`; + const printLabel = getLabelPrinter(labelExpected, labelReceived); + + return ( + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( + expected, + )}\n` + + `${printLabel(labelReceived)}${isNot ? ' ' : ''}${isNot && Array.isArray(received) + ? printReceivedArrayContainExpectedItem(received, index) + : printReceived(received) + }` + ); + }; + + return { message, pass }; + }, + + toEqual(received: unknown, expected: unknown) { + const matcherName = 'toEqual'; + const options: MatcherHintOptions = { + comment: 'deep equality', + isNot: this.isNot, + promise: this.promise, + }; + + const pass = equals(received, expected, [ + ...this.customTesters, + iterableEquality, + ]); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}\n` + + (stringify(expected) !== stringify(received) + ? `Received: ${printReceived(received)}` + : '') + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printDiffOrStringify( + expected, + received, + EXPECTED_LABEL, + RECEIVED_LABEL, + isExpand(this.expand), + ); + + // Passing the actual and expected objects so that a custom reporter + // could access them, for example in order to display a custom visual diff, + // or create a different error message + return { actual: received, expected, message, name: matcherName, pass }; + }, + + toHaveLength(received: any, expected: number) { + const matcherName = 'toHaveLength'; + const isNot = this.isNot; + const options: MatcherHintOptions = { + isNot, + promise: this.promise, + }; + + if (typeof received?.length !== 'number') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${RECEIVED_COLOR( + 'received', + )} value must have a length property whose value must be a number`, + printWithType('Received', received, printReceived), + ), + ); + } + + ensureExpectedIsNonNegativeInteger(expected, matcherName, options); + + const pass = received.length === expected; + + const message = () => { + const labelExpected = 'Expected length'; + const labelReceivedLength = 'Received length'; + const labelReceivedValue = `Received ${getType(received)}`; + const printLabel = getLabelPrinter( + labelExpected, + labelReceivedLength, + labelReceivedValue, + ); + + return ( + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( + expected, + )}\n` + + (isNot + ? '' + : `${printLabel(labelReceivedLength)}${printReceived( + received.length, + )}\n`) + + `${printLabel(labelReceivedValue)}${isNot ? ' ' : ''}${printReceived( + received, + )}` + ); + }; + + return { message, pass }; + }, + + toHaveProperty( + received: object, + expectedPath: string | Array, + expectedValue?: unknown, + ) { + const matcherName = 'toHaveProperty'; + const expectedArgument = 'path'; + const hasValue = arguments.length === 3; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + secondArgument: hasValue ? 'value' : '', + }; + + if (received === null || received === undefined) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${RECEIVED_COLOR('received')} value must not be null nor undefined`, + printWithType('Received', received, printReceived), + ), + ); + } + + const expectedPathType = getType(expectedPath); + + if (expectedPathType !== 'string' && expectedPathType !== 'array') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${EXPECTED_COLOR('expected')} path must be a string or array`, + printWithType('Expected', expectedPath, printExpected), + ), + ); + } + + const expectedPathLength = + typeof expectedPath === 'string' + ? pathAsArray(expectedPath).length + : expectedPath.length; + + if (expectedPathType === 'array' && expectedPathLength === 0) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${EXPECTED_COLOR('expected')} path must not be an empty array`, + printWithType('Expected', expectedPath, printExpected), + ), + ); + } + + const result = getPath(received, expectedPath); + const { lastTraversedObject, endPropIsDefined, hasEndProp, value } = result; + const receivedPath = result.traversedPath; + const hasCompletePath = receivedPath.length === expectedPathLength; + const receivedValue = hasCompletePath ? result.value : lastTraversedObject; + + const pass = + hasValue && endPropIsDefined + ? equals(value, expectedValue, [ + ...this.customTesters, + iterableEquality, + ]) + : Boolean(hasEndProp); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, expectedArgument, options) + + '\n\n' + + (hasValue + ? `Expected path: ${printExpected(expectedPath)}\n\n` + + `Expected value: not ${printExpected(expectedValue)}${stringify(expectedValue) !== stringify(receivedValue) + ? `\nReceived value: ${printReceived(receivedValue)}` + : '' + }` + : `Expected path: not ${printExpected(expectedPath)}\n\n` + + `Received value: ${printReceived(receivedValue)}`) + : () => + + matcherHint(matcherName, undefined, expectedArgument, options) + + '\n\n' + + `Expected path: ${printExpected(expectedPath)}\n` + + (hasCompletePath + ? `\n${printDiffOrStringify( + expectedValue, + receivedValue, + EXPECTED_VALUE_LABEL, + RECEIVED_VALUE_LABEL, + isExpand(this.expand), + )}` + : `Received path: ${printReceived( + expectedPathType === 'array' || receivedPath.length === 0 + ? receivedPath + : receivedPath.join('.'), + )}\n\n${hasValue + ? `Expected value: ${printExpected(expectedValue)}\n` + : '' + }Received value: ${printReceived(receivedValue)}`); + + return { message, pass }; + }, + + toMatch(received: string, expected: string | RegExp) { + const matcherName = 'toMatch'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + + if (typeof received !== 'string') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${RECEIVED_COLOR('received')} value must be a string`, + printWithType('Received', received, printReceived), + ), + ); + } + + if ( + !(typeof expected === 'string') && + !(expected && typeof expected.test === 'function') + ) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${EXPECTED_COLOR( + 'expected', + )} value must be a string or regular expression`, + printWithType('Expected', expected, printExpected), + ), + ); + } + + const pass = + typeof expected === 'string' + ? received.includes(expected) + : new RegExp(expected).test(received); + + const message = pass + ? () => + typeof expected === 'string' + ? + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected substring: not ${printExpected(expected)}\n` + + `Received string: ${printReceivedStringContainExpectedSubstring( + received, + received.indexOf(expected), + expected.length, + )}` + : + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected pattern: not ${printExpected(expected)}\n` + + `Received string: ${printReceivedStringContainExpectedResult( + received, + typeof expected.exec === 'function' + ? expected.exec(received) + : null, + )}` + : () => { + const labelExpected = `Expected ${typeof expected === 'string' ? 'substring' : 'pattern' + }`; + const labelReceived = 'Received string'; + const printLabel = getLabelPrinter(labelExpected, labelReceived); + + return ( + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `${printLabel(labelExpected)}${printExpected(expected)}\n` + + `${printLabel(labelReceived)}${printReceived(received)}` + ); + }; + + return { message, pass }; + }, + + toMatchObject(received: object, expected: object) { + const matcherName = 'toMatchObject'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + + if (typeof received !== 'object' || received === null) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${RECEIVED_COLOR('received')} value must be a non-null object`, + printWithType('Received', received, printReceived), + ), + ); + } + + if (typeof expected !== 'object' || expected === null) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${EXPECTED_COLOR('expected')} value must be a non-null object`, + printWithType('Expected', expected, printExpected), + ), + ); + } + + const pass = equals(received, expected, [ + ...this.customTesters, + iterableEquality, + subsetEquality, + ]); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}` + + (stringify(expected) !== stringify(received) + ? `\nReceived: ${printReceived(received)}` + : '') + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printDiffOrStringify( + expected, + getObjectSubset(received, expected, this.customTesters), + EXPECTED_LABEL, + RECEIVED_LABEL, + isExpand(this.expand), + ); + + return { message, pass }; + }, + + toStrictEqual(received: unknown, expected: unknown) { + const matcherName = 'toStrictEqual'; + const options: MatcherHintOptions = { + comment: 'deep equality', + isNot: this.isNot, + promise: this.promise, + }; + + const pass = equals( + received, + expected, + [...this.customTesters, ...toStrictEqualTesters], + true, + ); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}\n` + + (stringify(expected) !== stringify(received) + ? `Received: ${printReceived(received)}` + : '') + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printDiffOrStringify( + expected, + received, + EXPECTED_LABEL, + RECEIVED_LABEL, + isExpand(this.expand), + ); + + // Passing the actual and expected objects so that a custom reporter + // could access them, for example in order to display a custom visual diff, + // or create a different error message + return { actual: received, expected, message, name: matcherName, pass }; + }, +}; + +export default matchers; diff --git a/packages/playwright/bundles/expect/third_party/print.ts b/packages/playwright/bundles/expect/third_party/print.ts new file mode 100644 index 0000000000..63cad0f91e --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/print.ts @@ -0,0 +1,134 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + EXPECTED_COLOR, + INVERTED_COLOR, + RECEIVED_COLOR, + printReceived, + stringify, +} from 'jest-matcher-utils'; + +// Format substring but do not enclose in double quote marks. +// The replacement is compatible with pretty-format package. +const printSubstring = (val: string): string => val.replace(/"|\\/g, '\\$&'); + +export const printReceivedStringContainExpectedSubstring = ( + received: string, + start: number, + length: number, // not end +): string => + RECEIVED_COLOR( + `"${printSubstring(received.slice(0, start))}${INVERTED_COLOR( + printSubstring(received.slice(start, start + length)), + )}${printSubstring(received.slice(start + length))}"`, + ); + +export const printReceivedStringContainExpectedResult = ( + received: string, + result: RegExpExecArray | null, +): string => + result === null + ? printReceived(received) + : printReceivedStringContainExpectedSubstring( + received, + result.index, + result[0].length, + ); + +// The serialized array is compatible with pretty-format package min option. +// However, items have default stringify depth (instead of depth - 1) +// so expected item looks consistent by itself and enclosed in the array. +export const printReceivedArrayContainExpectedItem = ( + received: Array, + index: number, +): string => + RECEIVED_COLOR( + `[${received + .map((item, i) => { + const stringified = stringify(item); + return i === index ? INVERTED_COLOR(stringified) : stringified; + }) + .join(', ')}]`, + ); + +export const printCloseTo = ( + receivedDiff: number, + expectedDiff: number, + precision: number, + isNot: boolean | undefined, +): string => { + const receivedDiffString = stringify(receivedDiff); + const expectedDiffString = receivedDiffString.includes('e') + ? // toExponential arg is number of digits after the decimal point. + expectedDiff.toExponential(0) + : 0 <= precision && precision < 20 + ? // toFixed arg is number of digits after the decimal point. + // It may be a value between 0 and 20 inclusive. + // Implementations may optionally support a larger range of values. + expectedDiff.toFixed(precision + 1) + : stringify(expectedDiff); + + return ( + `Expected precision: ${isNot ? ' ' : ''} ${stringify(precision)}\n` + + `Expected difference: ${isNot ? 'not ' : ''}< ${EXPECTED_COLOR( + expectedDiffString, + )}\n` + + `Received difference: ${isNot ? ' ' : ''} ${RECEIVED_COLOR( + receivedDiffString, + )}` + ); +}; + +export const printExpectedConstructorName = ( + label: string, + expected: Function, +): string => `${printConstructorName(label, expected, false, true)}\n`; + +export const printExpectedConstructorNameNot = ( + label: string, + expected: Function, +): string => `${printConstructorName(label, expected, true, true)}\n`; + +export const printReceivedConstructorName = ( + label: string, + received: Function, +): string => `${printConstructorName(label, received, false, false)}\n`; + +// Do not call function if received is equal to expected. +export const printReceivedConstructorNameNot = ( + label: string, + received: Function, + expected: Function, +): string => + typeof expected.name === 'string' && + expected.name.length !== 0 && + typeof received.name === 'string' && + received.name.length !== 0 + ? `${printConstructorName(label, received, true, false)} ${ + Object.getPrototypeOf(received) === expected + ? 'extends' + : 'extends … extends' + } ${EXPECTED_COLOR(expected.name)}\n` + : `${printConstructorName(label, received, false, false)}\n`; + +const printConstructorName = ( + label: string, + constructor: Function, + isNot: boolean, + isExpected: boolean, +): string => + typeof constructor.name !== 'string' + ? `${label} name is not a string` + : constructor.name.length === 0 + ? `${label} name is an empty string` + : `${label}: ${!isNot ? '' : isExpected ? 'not ' : ' '}${ + isExpected + ? EXPECTED_COLOR(constructor.name) + : RECEIVED_COLOR(constructor.name) + }`; diff --git a/packages/playwright/bundles/expect/third_party/spyMatchers.ts b/packages/playwright/bundles/expect/third_party/spyMatchers.ts new file mode 100644 index 0000000000..ac74fd8d91 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/spyMatchers.ts @@ -0,0 +1,1184 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { equals, iterableEquality } from '@jest/expect-utils'; +import { getType, isPrimitive } from 'jest-get-type'; +import { + DIM_COLOR, + EXPECTED_COLOR, + type MatcherHintOptions, + RECEIVED_COLOR, + diff, + ensureExpectedIsNonNegativeInteger, + ensureNoExpected, + matcherErrorMessage, + matcherHint, + printExpected, + printReceived, + printWithType, + stringify, +} from 'jest-matcher-utils'; +import { getCustomEqualityTesters } from './jestMatchersObject'; +import type { + MatcherFunction, + MatchersObject, + SyncExpectationResult, +} from './types'; + +/* eslint-disable eqeqeq */ + +// The optional property of matcher context is true if undefined. +const isExpand = (expand?: boolean): boolean => expand !== false; + +const PRINT_LIMIT = 3; + +const NO_ARGUMENTS = 'called with 0 arguments'; + +const printExpectedArgs = (expected: Array): string => + expected.length === 0 + ? NO_ARGUMENTS + : expected.map(arg => printExpected(arg)).join(', '); + +const printReceivedArgs = ( + received: Array, + expected?: Array, +): string => + received.length === 0 + ? NO_ARGUMENTS + : received + .map((arg, i) => + Array.isArray(expected) && + i < expected.length && + isEqualValue(expected[i], arg) + ? printCommon(arg) + : printReceived(arg), + ) + .join(', '); + +const printCommon = (val: unknown) => DIM_COLOR(stringify(val)); + +const isEqualValue = (expected: unknown, received: unknown): boolean => + equals(expected, received, [...getCustomEqualityTesters(), iterableEquality]); + +const isEqualCall = ( + expected: Array, + received: Array, +): boolean => + received.length === expected.length && isEqualValue(expected, received); + +const isEqualReturn = (expected: unknown, result: any): boolean => + result.type === 'return' && isEqualValue(expected, result.value); + +const countReturns = (results: Array): number => + results.reduce( + (n: number, result: any) => (result.type === 'return' ? n + 1 : n), + 0, + ); + +const printNumberOfReturns = ( + countReturns: number, + countCalls: number, +): string => + `\nNumber of returns: ${printReceived(countReturns)}${countCalls !== countReturns + ? `\nNumber of calls: ${printReceived(countCalls)}` + : '' + }`; + +type PrintLabel = (string: string, isExpectedCall: boolean) => string; + +// Given a label, return a function which given a string, +// right-aligns it preceding the colon in the label. +const getRightAlignedPrinter = (label: string): PrintLabel => { + // Assume that the label contains a colon. + const index = label.indexOf(':'); + const suffix = label.slice(index); + + return (string: string, isExpectedCall: boolean) => + (isExpectedCall + ? `->${' '.repeat(Math.max(0, index - 2 - string.length))}` + : ' '.repeat(Math.max(index - string.length))) + + string + + suffix; +}; + +type IndexedCall = [number, Array]; + +const printReceivedCallsNegative = ( + expected: Array, + indexedCalls: Array, + isOnlyCall: boolean, + iExpectedCall?: number, +) => { + if (indexedCalls.length === 0) + return ''; + + + const label = 'Received: '; + if (isOnlyCall) + return `${label + printReceivedArgs(indexedCalls[0], expected)}\n`; + + + const printAligned = getRightAlignedPrinter(label); + + return `Received\n${indexedCalls.reduce( + (printed: string, [i, args]: IndexedCall) => + `${printed + + printAligned(String(i + 1), i === iExpectedCall) + + printReceivedArgs(args, expected) + }\n`, + '', + )}`; +}; + +const printExpectedReceivedCallsPositive = ( + expected: Array, + indexedCalls: Array, + expand: boolean, + isOnlyCall: boolean, + iExpectedCall?: number, +) => { + const expectedLine = `Expected: ${printExpectedArgs(expected)}\n`; + if (indexedCalls.length === 0) + return expectedLine; + + + const label = 'Received: '; + if (isOnlyCall && (iExpectedCall === 0 || iExpectedCall === undefined)) { + const received = indexedCalls[0][1]; + + if (isLineDiffableCall(expected, received)) { + // Display diff without indentation. + const lines = [ + EXPECTED_COLOR('- Expected'), + RECEIVED_COLOR('+ Received'), + '', + ]; + + const length = Math.max(expected.length, received.length); + for (let i = 0; i < length; i += 1) { + if (i < expected.length && i < received.length) { + if (isEqualValue(expected[i], received[i])) { + lines.push(` ${printCommon(received[i])},`); + continue; + } + + if (isLineDiffableArg(expected[i], received[i])) { + const difference = diff(expected[i], received[i], { expand }); + if ( + typeof difference === 'string' && + difference.includes('- Expected') && + difference.includes('+ Received') + ) { + // Omit annotation in case multiple args have diff. + lines.push(`${difference.split('\n').slice(3).join('\n')},`); + continue; + } + } + } + + if (i < expected.length) + lines.push(`${EXPECTED_COLOR(`- ${stringify(expected[i])}`)},`); + + if (i < received.length) + lines.push(`${RECEIVED_COLOR(`+ ${stringify(received[i])}`)},`); + + } + + return `${lines.join('\n')}\n`; + } + + return `${expectedLine + label + printReceivedArgs(received, expected)}\n`; + } + + const printAligned = getRightAlignedPrinter(label); + + return ( + + expectedLine + + 'Received\n' + + indexedCalls.reduce((printed: string, [i, received]: IndexedCall) => { + const aligned = printAligned(String(i + 1), i === iExpectedCall); + return `${printed + + ((i === iExpectedCall || iExpectedCall === undefined) && + isLineDiffableCall(expected, received) + ? aligned.replace(': ', '\n') + + printDiffCall(expected, received, expand) + : aligned + printReceivedArgs(received, expected)) + }\n`; + }, '') + ); +}; + +const indentation = 'Received'.replace(/\w/g, ' '); + +const printDiffCall = ( + expected: Array, + received: Array, + expand: boolean, +) => + received + .map((arg, i) => { + if (i < expected.length) { + if (isEqualValue(expected[i], arg)) + return `${indentation} ${printCommon(arg)},`; + + + if (isLineDiffableArg(expected[i], arg)) { + const difference = diff(expected[i], arg, { expand }); + + if ( + typeof difference === 'string' && + difference.includes('- Expected') && + difference.includes('+ Received') + ) { + // Display diff with indentation. + // Omit annotation in case multiple args have diff. + return `${difference + .split('\n') + .slice(3) + .map(line => indentation + line) + .join('\n')},`; + } + } + } + + // Display + only if received arg has no corresponding expected arg. + return `${indentation + + (i < expected.length + ? ` ${printReceived(arg)}` + : RECEIVED_COLOR(`+ ${stringify(arg)}`)) + },`; + }) + .join('\n'); + +const isLineDiffableCall = ( + expected: Array, + received: Array, +): boolean => + expected.some( + (arg, i) => i < received.length && isLineDiffableArg(arg, received[i]), + ); + +// Almost redundant with function in jest-matcher-utils, +// except no line diff for any strings. +const isLineDiffableArg = (expected: unknown, received: unknown): boolean => { + const expectedType = getType(expected); + const receivedType = getType(received); + + if (expectedType !== receivedType) + return false; + + + if (isPrimitive(expected)) + return false; + + + if ( + expectedType === 'date' || + expectedType === 'function' || + expectedType === 'regexp' + ) + return false; + + + if (expected instanceof Error && received instanceof Error) + return false; + + + if ( + expectedType === 'object' && + typeof (expected as any).asymmetricMatch === 'function' + ) + return false; + + + if ( + receivedType === 'object' && + typeof (received as any).asymmetricMatch === 'function' + ) + return false; + + + return true; +}; + +const printResult = (result: any, expected: unknown) => + result.type === 'throw' + ? 'function call threw an error' + : result.type === 'incomplete' + ? 'function call has not returned yet' + : isEqualValue(expected, result.value) + ? printCommon(result.value) + : printReceived(result.value); + +type IndexedResult = [number, any]; + +// Return either empty string or one line per indexed result, +// so additional empty line can separate from `Number of returns` which follows. +const printReceivedResults = ( + label: string, + expected: unknown, + indexedResults: Array, + isOnlyCall: boolean, + iExpectedCall?: number, +) => { + if (indexedResults.length === 0) + return ''; + + + if (isOnlyCall && (iExpectedCall === 0 || iExpectedCall === undefined)) + return `${label + printResult(indexedResults[0][1], expected)}\n`; + + + const printAligned = getRightAlignedPrinter(label); + + return ( + + label.replace(':', '').trim() + + '\n' + + indexedResults.reduce( + (printed: string, [i, result]: IndexedResult) => + `${printed + + printAligned(String(i + 1), i === iExpectedCall) + + printResult(result, expected) + }\n`, + '', + ) + ); +}; + +const createToBeCalledMatcher = ( + matcherName: string, +): MatcherFunction<[unknown]> => + function(received: any, expected: unknown): SyncExpectationResult { + const expectedArgument = ''; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + ensureMockOrSpy(received, matcherName, expectedArgument, options); + + const receivedIsSpy = isSpy(received); + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + const count = receivedIsSpy + ? received.calls.count() + : received.mock.calls.length; + const calls = receivedIsSpy + ? received.calls.all().map((x: any) => x.args) + : received.mock.calls; + const pass = count > 0; + const message = pass + ? () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of calls: ${printExpected(0)}\n` + + `Received number of calls: ${printReceived(count)}\n\n` + + calls + .reduce((lines: Array, args: any, i: number) => { + if (lines.length < PRINT_LIMIT) + lines.push(`${i + 1}: ${printReceivedArgs(args)}`); + + + return lines; + }, []) + .join('\n') + : () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of calls: >= ${printExpected(1)}\n` + + `Received number of calls: ${printReceived(count)}`; + + return { message, pass }; + }; + +const createToReturnMatcher = ( + matcherName: string, +): MatcherFunction<[unknown]> => + function(received: any, expected): SyncExpectationResult { + const expectedArgument = ''; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureNoExpected(expected, matcherName, options); + ensureMock(received, matcherName, expectedArgument, options); + + const receivedName = received.getMockName(); + + // Count return values that correspond only to calls that returned + const count = received.mock.results.reduce( + (n: number, result: any) => (result.type === 'return' ? n + 1 : n), + 0, + ); + + const pass = count > 0; + + const message = pass + ? () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of returns: ${printExpected(0)}\n` + + `Received number of returns: ${printReceived(count)}\n\n` + + received.mock.results + .reduce((lines: Array, result: any, i: number) => { + if (result.type === 'return' && lines.length < PRINT_LIMIT) + lines.push(`${i + 1}: ${printReceived(result.value)}`); + + + return lines; + }, []) + .join('\n') + + (received.mock.calls.length !== count + ? `\n\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : '') + : () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of returns: >= ${printExpected(1)}\n` + + `Received number of returns: ${printReceived(count)}` + + (received.mock.calls.length !== count + ? `\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : ''); + + return { message, pass }; + }; + +const createToBeCalledTimesMatcher = ( + matcherName: string, +): MatcherFunction<[number]> => + function(received: any, expected): SyncExpectationResult { + const expectedArgument = 'expected'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureExpectedIsNonNegativeInteger(expected, matcherName, options); + ensureMockOrSpy(received, matcherName, expectedArgument, options); + + const receivedIsSpy = isSpy(received); + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + const count = receivedIsSpy + ? received.calls.count() + : received.mock.calls.length; + + const pass = count === expected; + + const message = pass + ? () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of calls: not ${printExpected(expected)}` + : () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of calls: ${printExpected(expected)}\n` + + `Received number of calls: ${printReceived(count)}`; + + return { message, pass }; + }; + +const createToReturnTimesMatcher = ( + matcherName: string, +): MatcherFunction<[number]> => + function(received: any, expected): SyncExpectationResult { + const expectedArgument = 'expected'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureExpectedIsNonNegativeInteger(expected, matcherName, options); + ensureMock(received, matcherName, expectedArgument, options); + + const receivedName = received.getMockName(); + + // Count return values that correspond only to calls that returned + const count = received.mock.results.reduce( + (n: number, result: any) => (result.type === 'return' ? n + 1 : n), + 0, + ); + + const pass = count === expected; + + const message = pass + ? () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of returns: not ${printExpected(expected)}` + + (received.mock.calls.length !== count + ? `\n\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : '') + : () => + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected number of returns: ${printExpected(expected)}\n` + + `Received number of returns: ${printReceived(count)}` + + (received.mock.calls.length !== count + ? `\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : ''); + + return { message, pass }; + }; + +const createToBeCalledWithMatcher = ( + matcherName: string, +): MatcherFunction> => + function(received: any, ...expected): SyncExpectationResult { + const expectedArgument = '...expected'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureMockOrSpy(received, matcherName, expectedArgument, options); + + const receivedIsSpy = isSpy(received); + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + + const calls = receivedIsSpy + ? received.calls.all().map((x: any) => x.args) + : received.mock.calls; + + const pass = calls.some((call: any) => isEqualCall(expected, call)); + + const message = pass + ? () => { + // Some examples of calls that are equal to expected value. + const indexedCalls: Array = []; + let i = 0; + while (i < calls.length && indexedCalls.length < PRINT_LIMIT) { + if (isEqualCall(expected, calls[i])) + indexedCalls.push([i, calls[i]]); + + i += 1; + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected: not ${printExpectedArgs(expected)}\n` + + (calls.length === 1 && stringify(calls[0]) === stringify(expected) + ? '' + : printReceivedCallsNegative( + expected, + indexedCalls, + calls.length === 1, + )) + + `\nNumber of calls: ${printReceived(calls.length)}` + ); + } + : () => { + // Some examples of calls that are not equal to expected value. + const indexedCalls: Array = []; + let i = 0; + while (i < calls.length && indexedCalls.length < PRINT_LIMIT) { + indexedCalls.push([i, calls[i]]); + i += 1; + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + printExpectedReceivedCallsPositive( + expected, + indexedCalls, + isExpand(this.expand), + calls.length === 1, + ) + + `\nNumber of calls: ${printReceived(calls.length)}` + ); + }; + + return { message, pass }; + }; + +const createToReturnWithMatcher = ( + matcherName: string, +): MatcherFunction<[unknown]> => + function(received: any, expected): SyncExpectationResult { + const expectedArgument = 'expected'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureMock(received, matcherName, expectedArgument, options); + + const receivedName = received.getMockName(); + const { calls, results } = received.mock; + + const pass = results.some((result: any) => isEqualReturn(expected, result)); + + const message = pass + ? () => { + // Some examples of results that are equal to expected value. + const indexedResults: Array = []; + let i = 0; + while (i < results.length && indexedResults.length < PRINT_LIMIT) { + if (isEqualReturn(expected, results[i])) + indexedResults.push([i, results[i]]); + + i += 1; + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}\n` + + (results.length === 1 && + results[0].type === 'return' && + stringify(results[0].value) === stringify(expected) + ? '' + : printReceivedResults( + 'Received: ', + expected, + indexedResults, + results.length === 1, + )) + + printNumberOfReturns(countReturns(results), calls.length) + ); + } + : () => { + // Some examples of results that are not equal to expected value. + const indexedResults: Array = []; + let i = 0; + while (i < results.length && indexedResults.length < PRINT_LIMIT) { + indexedResults.push([i, results[i]]); + i += 1; + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected: ${printExpected(expected)}\n` + + printReceivedResults( + 'Received: ', + expected, + indexedResults, + results.length === 1, + ) + + printNumberOfReturns(countReturns(results), calls.length) + ); + }; + + return { message, pass }; + }; + +const createLastCalledWithMatcher = ( + matcherName: string, +): MatcherFunction> => + function(received: any, ...expected): SyncExpectationResult { + const expectedArgument = '...expected'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureMockOrSpy(received, matcherName, expectedArgument, options); + + const receivedIsSpy = isSpy(received); + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + + const calls = receivedIsSpy + ? received.calls.all().map((x: any) => x.args) + : received.mock.calls; + const iLast = calls.length - 1; + + const pass = iLast >= 0 && isEqualCall(expected, calls[iLast]); + + const message = pass + ? () => { + const indexedCalls: Array = []; + if (iLast > 0) { + // Display preceding call as context. + indexedCalls.push([iLast - 1, calls[iLast - 1]]); + } + indexedCalls.push([iLast, calls[iLast]]); + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected: not ${printExpectedArgs(expected)}\n` + + (calls.length === 1 && stringify(calls[0]) === stringify(expected) + ? '' + : printReceivedCallsNegative( + expected, + indexedCalls, + calls.length === 1, + iLast, + )) + + `\nNumber of calls: ${printReceived(calls.length)}` + ); + } + : () => { + const indexedCalls: Array = []; + if (iLast >= 0) { + if (iLast > 0) { + let i = iLast - 1; + // Is there a preceding call that is equal to expected args? + while (i >= 0 && !isEqualCall(expected, calls[i])) + i -= 1; + + if (i < 0) + i = iLast - 1; // otherwise, preceding call + + + indexedCalls.push([i, calls[i]]); + } + + indexedCalls.push([iLast, calls[iLast]]); + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + printExpectedReceivedCallsPositive( + expected, + indexedCalls, + isExpand(this.expand), + calls.length === 1, + iLast, + ) + + `\nNumber of calls: ${printReceived(calls.length)}` + ); + }; + + return { message, pass }; + }; + +const createLastReturnedMatcher = ( + matcherName: string, +): MatcherFunction<[unknown]> => + function(received: any, expected): SyncExpectationResult { + const expectedArgument = 'expected'; + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + ensureMock(received, matcherName, expectedArgument, options); + + const receivedName = received.getMockName(); + + const { calls, results } = received.mock; + const iLast = results.length - 1; + + const pass = iLast >= 0 && isEqualReturn(expected, results[iLast]); + + const message = pass + ? () => { + const indexedResults: Array = []; + if (iLast > 0) { + // Display preceding result as context. + indexedResults.push([iLast - 1, results[iLast - 1]]); + } + indexedResults.push([iLast, results[iLast]]); + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected: not ${printExpected(expected)}\n` + + (results.length === 1 && + results[0].type === 'return' && + stringify(results[0].value) === stringify(expected) + ? '' + : printReceivedResults( + 'Received: ', + expected, + indexedResults, + results.length === 1, + iLast, + )) + + printNumberOfReturns(countReturns(results), calls.length) + ); + } + : () => { + const indexedResults: Array = []; + if (iLast >= 0) { + if (iLast > 0) { + let i = iLast - 1; + // Is there a preceding result that is equal to expected value? + while (i >= 0 && !isEqualReturn(expected, results[i])) + i -= 1; + + if (i < 0) + i = iLast - 1; // otherwise, preceding result + + + indexedResults.push([i, results[i]]); + } + + indexedResults.push([iLast, results[iLast]]); + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `Expected: ${printExpected(expected)}\n` + + printReceivedResults( + 'Received: ', + expected, + indexedResults, + results.length === 1, + iLast, + ) + + printNumberOfReturns(countReturns(results), calls.length) + ); + }; + + return { message, pass }; + }; + +const createNthCalledWithMatcher = ( + matcherName: string, +): MatcherFunction<[number, ...Array]> => + function(received: any, nth, ...expected): SyncExpectationResult { + const expectedArgument = 'n'; + const options: MatcherHintOptions = { + expectedColor: (arg: string) => arg, + isNot: this.isNot, + promise: this.promise, + secondArgument: '...expected', + }; + ensureMockOrSpy(received, matcherName, expectedArgument, options); + + if (!Number.isSafeInteger(nth) || nth < 1) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${expectedArgument} must be a positive integer`, + printWithType(expectedArgument, nth, stringify), + ), + ); + } + + const receivedIsSpy = isSpy(received); + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + + const calls = receivedIsSpy + ? received.calls.all().map((x: any) => x.args) + : received.mock.calls; + const length = calls.length; + const iNth = nth - 1; + + const pass = iNth < length && isEqualCall(expected, calls[iNth]); + + const message = pass + ? () => { + // Display preceding and following calls, + // in case assertions fails because index is off by one. + const indexedCalls: Array = []; + if (iNth - 1 >= 0) + indexedCalls.push([iNth - 1, calls[iNth - 1]]); + + indexedCalls.push([iNth, calls[iNth]]); + if (iNth + 1 < length) + indexedCalls.push([iNth + 1, calls[iNth + 1]]); + + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `n: ${nth}\n` + + `Expected: not ${printExpectedArgs(expected)}\n` + + (calls.length === 1 && stringify(calls[0]) === stringify(expected) + ? '' + : printReceivedCallsNegative( + expected, + indexedCalls, + calls.length === 1, + iNth, + )) + + `\nNumber of calls: ${printReceived(calls.length)}` + ); + } + : () => { + // Display preceding and following calls: + // * nearest call that is equal to expected args + // * otherwise, adjacent call + // in case assertions fails because of index, especially off by one. + const indexedCalls: Array = []; + if (iNth < length) { + if (iNth - 1 >= 0) { + let i = iNth - 1; + // Is there a preceding call that is equal to expected args? + while (i >= 0 && !isEqualCall(expected, calls[i])) + i -= 1; + + if (i < 0) + i = iNth - 1; // otherwise, adjacent call + + + indexedCalls.push([i, calls[i]]); + } + indexedCalls.push([iNth, calls[iNth]]); + if (iNth + 1 < length) { + let i = iNth + 1; + // Is there a following call that is equal to expected args? + while (i < length && !isEqualCall(expected, calls[i])) + i += 1; + + if (i >= length) + i = iNth + 1; // otherwise, adjacent call + + + indexedCalls.push([i, calls[i]]); + } + } else if (length > 0) { + // The number of received calls is fewer than the expected number. + let i = length - 1; + // Is there a call that is equal to expected args? + while (i >= 0 && !isEqualCall(expected, calls[i])) + i -= 1; + + if (i < 0) + i = length - 1; // otherwise, last call + + + indexedCalls.push([i, calls[i]]); + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `n: ${nth}\n` + + printExpectedReceivedCallsPositive( + expected, + indexedCalls, + isExpand(this.expand), + calls.length === 1, + iNth, + ) + + `\nNumber of calls: ${printReceived(calls.length)}` + ); + }; + + return { message, pass }; + }; + +const createNthReturnedWithMatcher = ( + matcherName: string, +): MatcherFunction<[number, unknown]> => + function(received: any, nth, expected): SyncExpectationResult { + const expectedArgument = 'n'; + const options: MatcherHintOptions = { + expectedColor: (arg: string) => arg, + isNot: this.isNot, + promise: this.promise, + secondArgument: 'expected', + }; + ensureMock(received, matcherName, expectedArgument, options); + + if (!Number.isSafeInteger(nth) || nth < 1) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${expectedArgument} must be a positive integer`, + printWithType(expectedArgument, nth, stringify), + ), + ); + } + + const receivedName = received.getMockName(); + const { calls, results } = received.mock; + const length = results.length; + const iNth = nth - 1; + + const pass = iNth < length && isEqualReturn(expected, results[iNth]); + + const message = pass + ? () => { + // Display preceding and following results, + // in case assertions fails because index is off by one. + const indexedResults: Array = []; + if (iNth - 1 >= 0) + indexedResults.push([iNth - 1, results[iNth - 1]]); + + indexedResults.push([iNth, results[iNth]]); + if (iNth + 1 < length) + indexedResults.push([iNth + 1, results[iNth + 1]]); + + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `n: ${nth}\n` + + `Expected: not ${printExpected(expected)}\n` + + (results.length === 1 && + results[0].type === 'return' && + stringify(results[0].value) === stringify(expected) + ? '' + : printReceivedResults( + 'Received: ', + expected, + indexedResults, + results.length === 1, + iNth, + )) + + printNumberOfReturns(countReturns(results), calls.length) + ); + } + : () => { + // Display preceding and following results: + // * nearest result that is equal to expected value + // * otherwise, adjacent result + // in case assertions fails because of index, especially off by one. + const indexedResults: Array = []; + if (iNth < length) { + if (iNth - 1 >= 0) { + let i = iNth - 1; + // Is there a preceding result that is equal to expected value? + while (i >= 0 && !isEqualReturn(expected, results[i])) + i -= 1; + + if (i < 0) + i = iNth - 1; // otherwise, adjacent result + + + indexedResults.push([i, results[i]]); + } + indexedResults.push([iNth, results[iNth]]); + if (iNth + 1 < length) { + let i = iNth + 1; + // Is there a following result that is equal to expected value? + while (i < length && !isEqualReturn(expected, results[i])) + i += 1; + + if (i >= length) + i = iNth + 1; // otherwise, adjacent result + + + indexedResults.push([i, results[i]]); + } + } else if (length > 0) { + // The number of received calls is fewer than the expected number. + let i = length - 1; + // Is there a result that is equal to expected value? + while (i >= 0 && !isEqualReturn(expected, results[i])) + i -= 1; + + if (i < 0) + i = length - 1; // otherwise, last result + + + indexedResults.push([i, results[i]]); + } + + return ( + + matcherHint(matcherName, receivedName, expectedArgument, options) + + '\n\n' + + `n: ${nth}\n` + + `Expected: ${printExpected(expected)}\n` + + printReceivedResults( + 'Received: ', + expected, + indexedResults, + results.length === 1, + iNth, + ) + + printNumberOfReturns(countReturns(results), calls.length) + ); + }; + + return { message, pass }; + }; + +const spyMatchers: MatchersObject = { + lastCalledWith: createLastCalledWithMatcher('lastCalledWith'), + lastReturnedWith: createLastReturnedMatcher('lastReturnedWith'), + nthCalledWith: createNthCalledWithMatcher('nthCalledWith'), + nthReturnedWith: createNthReturnedWithMatcher('nthReturnedWith'), + toBeCalled: createToBeCalledMatcher('toBeCalled'), + toBeCalledTimes: createToBeCalledTimesMatcher('toBeCalledTimes'), + toBeCalledWith: createToBeCalledWithMatcher('toBeCalledWith'), + toHaveBeenCalled: createToBeCalledMatcher('toHaveBeenCalled'), + toHaveBeenCalledTimes: createToBeCalledTimesMatcher('toHaveBeenCalledTimes'), + toHaveBeenCalledWith: createToBeCalledWithMatcher('toHaveBeenCalledWith'), + toHaveBeenLastCalledWith: createLastCalledWithMatcher( + 'toHaveBeenLastCalledWith', + ), + toHaveBeenNthCalledWith: createNthCalledWithMatcher( + 'toHaveBeenNthCalledWith', + ), + toHaveLastReturnedWith: createLastReturnedMatcher('toHaveLastReturnedWith'), + toHaveNthReturnedWith: createNthReturnedWithMatcher('toHaveNthReturnedWith'), + toHaveReturned: createToReturnMatcher('toHaveReturned'), + toHaveReturnedTimes: createToReturnTimesMatcher('toHaveReturnedTimes'), + toHaveReturnedWith: createToReturnWithMatcher('toHaveReturnedWith'), + toReturn: createToReturnMatcher('toReturn'), + toReturnTimes: createToReturnTimesMatcher('toReturnTimes'), + toReturnWith: createToReturnWithMatcher('toReturnWith'), +}; + +const isMock = (received: any) => + received != null && received._isMockFunction === true; + +const isSpy = (received: any) => + received != null && + received.calls != null && + typeof received.calls.all === 'function' && + typeof received.calls.count === 'function'; + +const ensureMockOrSpy = ( + received: any, + matcherName: string, + expectedArgument: string, + options: MatcherHintOptions, +) => { + if (!isMock(received) && !isSpy(received)) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${RECEIVED_COLOR('received')} value must be a mock or spy function`, + printWithType('Received', received, printReceived), + ), + ); + } +}; + +const ensureMock = ( + received: any, + matcherName: string, + expectedArgument: string, + options: MatcherHintOptions, +) => { + if (!isMock(received)) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${RECEIVED_COLOR('received')} value must be a mock function`, + printWithType('Received', received, printReceived), + ), + ); + } +}; + +export default spyMatchers; diff --git a/packages/playwright/bundles/expect/third_party/toThrowMatchers.ts b/packages/playwright/bundles/expect/third_party/toThrowMatchers.ts new file mode 100644 index 0000000000..5a2bb9dcad --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/toThrowMatchers.ts @@ -0,0 +1,481 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { isError } from '@jest/expect-utils'; +import { + EXPECTED_COLOR, + type MatcherHintOptions, + RECEIVED_COLOR, + matcherErrorMessage, + matcherHint, + printDiffOrStringify, + printExpected, + printReceived, + printWithType, +} from 'jest-matcher-utils'; +import { formatStackTrace, separateMessageFromStack } from 'jest-message-util'; +import { + printExpectedConstructorName, + printExpectedConstructorNameNot, + printReceivedConstructorName, + printReceivedConstructorNameNot, + printReceivedStringContainExpectedResult, + printReceivedStringContainExpectedSubstring, +} from './print'; +import type { + ExpectationResult, + MatcherFunction, + MatchersObject, + SyncExpectationResult, +} from './types'; + +/* eslint-disable eqeqeq */ + +const DID_NOT_THROW = 'Received function did not throw'; + +type Thrown = + | { + hasMessage: true; + isError: true; + message: string; + value: Error; + } + | { + hasMessage: boolean; + isError: false; + message: string; + value: any; + }; + +const getThrown = (e: any): Thrown => { + const hasMessage = + e !== null && e !== undefined && typeof e.message === 'string'; + + if (hasMessage && typeof e.name === 'string' && typeof e.stack === 'string') { + return { + hasMessage, + isError: true, + message: e.message, + value: e, + }; + } + + return { + hasMessage, + isError: false, + message: hasMessage ? e.message : String(e), + value: e, + }; +}; + +export const createMatcher = ( + matcherName: string, + fromPromise?: boolean, +): MatcherFunction<[any]> => + function(received, expected): ExpectationResult { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + let thrown = null; + + if (fromPromise && isError(received)) { + thrown = getThrown(received); + } else { + if (typeof received !== 'function') { + if (!fromPromise) { + const placeholder = expected === undefined ? '' : 'expected'; + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, placeholder, options), + `${RECEIVED_COLOR('received')} value must be a function`, + printWithType('Received', received, printReceived), + ), + ); + } + } else { + try { + received(); + } catch (e) { + thrown = getThrown(e); + } + } + } + + if (expected === undefined) { + return toThrow(matcherName, options, thrown); + } else if (typeof expected === 'function') { + return toThrowExpectedClass(matcherName, options, thrown, expected); + } else if (typeof expected === 'string') { + return toThrowExpectedString(matcherName, options, thrown, expected); + } else if (expected !== null && typeof expected.test === 'function') { + return toThrowExpectedRegExp(matcherName, options, thrown, expected); + } else if ( + expected !== null && + typeof expected.asymmetricMatch === 'function' + ) { + return toThrowExpectedAsymmetric(matcherName, options, thrown, expected); + } else if (expected !== null && typeof expected === 'object') { + return toThrowExpectedObject(matcherName, options, thrown, expected); + } else { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${EXPECTED_COLOR( + 'expected', + )} value must be a string or regular expression or class or error`, + printWithType('Expected', expected, printExpected), + ), + ); + } + }; + +const matchers: MatchersObject = { + toThrow: createMatcher('toThrow'), + toThrowError: createMatcher('toThrowError'), +}; + +const toThrowExpectedRegExp = ( + matcherName: string, + options: MatcherHintOptions, + thrown: Thrown | null, + expected: RegExp, +): SyncExpectationResult => { + const pass = thrown !== null && expected.test(thrown.message); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected pattern: not ', expected) + + (thrown !== null && thrown.hasMessage + ? formatReceived( + 'Received message: ', + thrown, + 'message', + expected, + ) + formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value')) + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected pattern: ', expected) + + (thrown === null + ? `\n${DID_NOT_THROW}` + : thrown.hasMessage + ? formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value')); + + return { message, pass }; +}; + +type AsymmetricMatcher = { + asymmetricMatch: (received: unknown) => boolean; +}; + +const toThrowExpectedAsymmetric = ( + matcherName: string, + options: MatcherHintOptions, + thrown: Thrown | null, + expected: AsymmetricMatcher, +): SyncExpectationResult => { + const pass = thrown !== null && expected.asymmetricMatch(thrown.value); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected asymmetric matcher: not ', expected) + + '\n' + + (thrown !== null && thrown.hasMessage + ? formatReceived('Received name: ', thrown, 'name') + + formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Thrown value: ', thrown, 'value')) + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected asymmetric matcher: ', expected) + + '\n' + + (thrown === null + ? DID_NOT_THROW + : thrown.hasMessage + ? formatReceived('Received name: ', thrown, 'name') + + formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Thrown value: ', thrown, 'value')); + + return { message, pass }; +}; + +const toThrowExpectedObject = ( + matcherName: string, + options: MatcherHintOptions, + thrown: Thrown | null, + expected: Error, +): SyncExpectationResult => { + const expectedMessageAndCause = createMessageAndCause(expected); + const thrownMessageAndCause = + thrown !== null ? createMessageAndCause(thrown.value) : null; + const pass = + thrown !== null && + thrown.message === expected.message && + thrownMessageAndCause === expectedMessageAndCause; + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected( + `Expected ${messageAndCause(expected)}: not `, + expectedMessageAndCause, + ) + + (thrown !== null && thrown.hasMessage + ? formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value')) + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + (thrown === null + ? + formatExpected( + `Expected ${messageAndCause(expected)}: `, + expectedMessageAndCause, + ) + + '\n' + + DID_NOT_THROW + : thrown.hasMessage + ? + printDiffOrStringify( + expectedMessageAndCause, + thrownMessageAndCause, + `Expected ${messageAndCause(expected)}`, + `Received ${messageAndCause(thrown.value)}`, + true, + ) + + '\n' + + formatStack(thrown) + : formatExpected( + `Expected ${messageAndCause(expected)}: `, + expectedMessageAndCause, + ) + formatReceived('Received value: ', thrown, 'value')); + + return { message, pass }; +}; + +const toThrowExpectedClass = ( + matcherName: string, + options: MatcherHintOptions, + thrown: Thrown | null, + expected: Function, +): SyncExpectationResult => { + const pass = thrown !== null && thrown.value instanceof expected; + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printExpectedConstructorNameNot('Expected constructor', expected) + + (thrown !== null && + thrown.value != null && + typeof thrown.value.constructor === 'function' && + thrown.value.constructor !== expected + ? printReceivedConstructorNameNot( + 'Received constructor', + thrown.value.constructor, + expected, + ) + : '') + + '\n' + + (thrown !== null && thrown.hasMessage + ? formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value')) + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + printExpectedConstructorName('Expected constructor', expected) + + (thrown === null + ? `\n${DID_NOT_THROW}` + : `${thrown.value != null && + typeof thrown.value.constructor === 'function' + ? printReceivedConstructorName( + 'Received constructor', + thrown.value.constructor, + ) + : '' + }\n${thrown.hasMessage + ? formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value') + }`); + + return { message, pass }; +}; + +const toThrowExpectedString = ( + matcherName: string, + options: MatcherHintOptions, + thrown: Thrown | null, + expected: string, +): SyncExpectationResult => { + const pass = thrown !== null && thrown.message.includes(expected); + + const message = pass + ? () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected substring: not ', expected) + + (thrown !== null && thrown.hasMessage + ? formatReceived( + 'Received message: ', + thrown, + 'message', + expected, + ) + formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value')) + : () => + + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected substring: ', expected) + + (thrown === null + ? `\n${DID_NOT_THROW}` + : thrown.hasMessage + ? formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Received value: ', thrown, 'value')); + + return { message, pass }; +}; + +const toThrow = ( + matcherName: string, + options: MatcherHintOptions, + thrown: Thrown | null, +): SyncExpectationResult => { + const pass = thrown !== null; + + const message = pass + ? () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + (thrown !== null && thrown.hasMessage + ? formatReceived('Error name: ', thrown, 'name') + + formatReceived('Error message: ', thrown, 'message') + + formatStack(thrown) + : formatReceived('Thrown value: ', thrown, 'value')) + : () => + + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + DID_NOT_THROW; + + return { message, pass }; +}; + +const formatExpected = (label: string, expected: unknown) => + `${label + printExpected(expected)}\n`; + +const formatReceived = ( + label: string, + thrown: Thrown | null, + key: string, + expected?: string | RegExp, +) => { + if (thrown === null) + return ''; + + + if (key === 'message') { + const message = thrown.message; + + if (typeof expected === 'string') { + const index = message.indexOf(expected); + if (index !== -1) { + return `${label + + printReceivedStringContainExpectedSubstring( + message, + index, + expected.length, + ) + }\n`; + } + } else if (expected instanceof RegExp) { + return `${label + + printReceivedStringContainExpectedResult( + message, + typeof expected.exec === 'function' ? expected.exec(message) : null, + ) + }\n`; + } + + return `${label + printReceived(message)}\n`; + } + + if (key === 'name') { + return thrown.isError + ? `${label + printReceived(thrown.value.name)}\n` + : ''; + } + + if (key === 'value') + return thrown.isError ? '' : `${label + printReceived(thrown.value)}\n`; + + + return ''; +}; + +const formatStack = (thrown: Thrown | null) => + thrown === null || !thrown.isError + ? '' + : formatStackTrace( + separateMessageFromStack(thrown.value.stack!).stack, + { + rootDir: process.cwd(), + testMatch: [], + }, + { + noStackTrace: false, + }, + ); + +function createMessageAndCauseMessage(error: Error): string { + if (error.cause instanceof Error) { + return `{ message: ${error.message}, cause: ${createMessageAndCauseMessage( + error.cause, + )}}`; + } + + return `{ message: ${error.message} }`; +} + +function createMessageAndCause(error: Error) { + if (error.cause instanceof Error) + return createMessageAndCauseMessage(error); + + return error.message; +} + +function messageAndCause(error: Error) { + return error.cause === undefined ? 'message' : 'message and cause'; +} + +export default matchers; diff --git a/packages/playwright/bundles/expect/third_party/types.ts b/packages/playwright/bundles/expect/third_party/types.ts new file mode 100644 index 0000000000..abaa3a3b69 --- /dev/null +++ b/packages/playwright/bundles/expect/third_party/types.ts @@ -0,0 +1,353 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { EqualsFunction, Tester } from '@jest/expect-utils'; +import type * as jestMatcherUtils from 'jest-matcher-utils'; +import type { INTERNAL_MATCHER_FLAG } from './jestMatchersObject'; + +export type SyncExpectationResult = { + pass: boolean; + message(): string; +}; + +export type AsyncExpectationResult = Promise; + +export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; + +export type MatcherFunctionWithContext< + Context extends MatcherContext = MatcherContext, + Expected extends Array = [] /** TODO should be: extends Array = [] */, +> = ( + this: Context, + actual: unknown, + ...expected: Expected +) => ExpectationResult; + +export type MatcherFunction = []> = + MatcherFunctionWithContext; + +// TODO should be replaced with `MatcherFunctionWithContext` +export type RawMatcherFn = { + (this: Context, actual: any, ...expected: Array): ExpectationResult; + /** @internal */ + [INTERNAL_MATCHER_FLAG]?: boolean; +}; + +export type MatchersObject = { + [name: string]: RawMatcherFn; +}; + +export type ThrowingMatcherFn = (actual: any) => void; +export type PromiseMatcherFn = (actual: any) => Promise; + +export interface MatcherUtils { + customTesters: Array; + dontThrow(): void; + equals: EqualsFunction; + utils: typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; + }; +} + +export interface MatcherState { + assertionCalls: number; + currentConcurrentTestName?: () => string | undefined; + currentTestName?: string; + error?: Error; + expand?: boolean; + expectedAssertionsNumber: number | null; + expectedAssertionsNumberError?: Error; + isExpectingAssertions: boolean; + isExpectingAssertionsError?: Error; + isNot?: boolean; + numPassingAsserts: number; + promise?: string; + suppressedErrors: Array; + testPath?: string; +} + +export type MatcherContext = MatcherUtils & Readonly; + +export type AsymmetricMatcher = { + asymmetricMatch(other: unknown): boolean; + toString(): string; + getExpectedType?(): string; + toAsymmetricMatcher?(): string; +}; + +export type ExpectedAssertionsErrors = Array<{ + actual: string | number; + error: Error; + expected: string; +}>; + +export interface BaseExpect { + assertions(numberOfAssertions: number): void; + addEqualityTesters(testers: Array): void; + extend(matchers: MatchersObject): void; + extractExpectedAssertionsErrors(): ExpectedAssertionsErrors; + getState(): MatcherState; + hasAssertions(): void; + setState(state: Partial): void; +} + +export type Expect = { + (actual: T): Matchers & + Inverse> & + PromiseMatchers; +} & BaseExpect & + AsymmetricMatchers & + Inverse>; + +type Inverse = { + /** + * Inverse next matcher. If you know how to test something, `.not` lets you test its opposite. + */ + not: Matchers; +}; + +export interface AsymmetricMatchers { + any(sample: unknown): AsymmetricMatcher; + anything(): AsymmetricMatcher; + arrayContaining(sample: Array): AsymmetricMatcher; + closeTo(sample: number, precision?: number): AsymmetricMatcher; + objectContaining(sample: Record): AsymmetricMatcher; + stringContaining(sample: string): AsymmetricMatcher; + stringMatching(sample: string | RegExp): AsymmetricMatcher; +} + +type PromiseMatchers = { + /** + * Unwraps the reason of a rejected promise so any other matcher can be chained. + * If the promise is fulfilled the assertion fails. + */ + rejects: Matchers, T> & Inverse, T>>; + /** + * Unwraps the value of a fulfilled promise so any other matcher can be chained. + * If the promise is rejected the assertion fails. + */ + resolves: Matchers, T> & Inverse, T>>; +}; + +export interface Matchers, T = unknown> { + /** + * T is a type param for the benefit of users who extend Matchers. It's + * intentionally unused and needs to be named T, not _T, for those users. + * This makes sure TypeScript agrees. + * + * @internal + */ + _unusedT(expected: T): R; + /** + * Ensures the last call to a mock function was provided specific args. + */ + lastCalledWith(...expected: Array): R; + /** + * Ensure that the last call to a mock function has returned a specified value. + */ + lastReturnedWith(expected?: unknown): R; + /** + * Ensure that a mock function is called with specific arguments on an Nth call. + */ + nthCalledWith(nth: number, ...expected: Array): R; + /** + * Ensure that the nth call to a mock function has returned a specified value. + */ + nthReturnedWith(nth: number, expected?: unknown): R; + /** + * Checks that a value is what you expect. It calls `Object.is` to compare values. + * Don't use `toBe` with floating-point numbers. + */ + toBe(expected: unknown): R; + /** + * Ensures that a mock function is called. + */ + toBeCalled(): R; + /** + * Ensures that a mock function is called an exact number of times. + */ + toBeCalledTimes(expected: number): R; + /** + * Ensure that a mock function is called with specific arguments. + */ + toBeCalledWith(...expected: Array): R; + /** + * Using exact equality with floating point numbers is a bad idea. + * Rounding means that intuitive things fail. + * The default for `precision` is 2. + */ + toBeCloseTo(expected: number, precision?: number): R; + /** + * Ensure that a variable is not undefined. + */ + toBeDefined(): R; + /** + * When you don't care what a value is, you just want to + * ensure a value is false in a boolean context. + */ + toBeFalsy(): R; + /** + * For comparing floating point numbers. + */ + toBeGreaterThan(expected: number | bigint): R; + /** + * For comparing floating point numbers. + */ + toBeGreaterThanOrEqual(expected: number | bigint): R; + /** + * Ensure that an object is an instance of a class. + * This matcher uses `instanceof` underneath. + */ + toBeInstanceOf(expected: unknown): R; + /** + * For comparing floating point numbers. + */ + toBeLessThan(expected: number | bigint): R; + /** + * For comparing floating point numbers. + */ + toBeLessThanOrEqual(expected: number | bigint): R; + /** + * Used to check that a variable is NaN. + */ + toBeNaN(): R; + /** + * This is the same as `.toBe(null)` but the error messages are a bit nicer. + * So use `.toBeNull()` when you want to check that something is null. + */ + toBeNull(): R; + /** + * Use when you don't care what a value is, you just want to ensure a value + * is true in a boolean context. In JavaScript, there are six falsy values: + * `false`, `0`, `''`, `null`, `undefined`, and `NaN`. Everything else is truthy. + */ + toBeTruthy(): R; + /** + * Used to check that a variable is undefined. + */ + toBeUndefined(): R; + /** + * Used when you want to check that an item is in a list. + * For testing the items in the list, this uses `===`, a strict equality check. + */ + toContain(expected: unknown): R; + /** + * Used when you want to check that an item is in a list. + * For testing the items in the list, this matcher recursively checks the + * equality of all fields, rather than checking for object identity. + */ + toContainEqual(expected: unknown): R; + /** + * Used when you want to check that two objects have the same value. + * This matcher recursively checks the equality of all fields, rather than checking for object identity. + */ + toEqual(expected: unknown): R; + /** + * Ensures that a mock function is called. + */ + toHaveBeenCalled(): R; + /** + * Ensures that a mock function is called an exact number of times. + */ + toHaveBeenCalledTimes(expected: number): R; + /** + * Ensure that a mock function is called with specific arguments. + */ + toHaveBeenCalledWith(...expected: Array): R; + /** + * Ensure that a mock function is called with specific arguments on an Nth call. + */ + toHaveBeenNthCalledWith(nth: number, ...expected: Array): R; + /** + * If you have a mock function, you can use `.toHaveBeenLastCalledWith` + * to test what arguments it was last called with. + */ + toHaveBeenLastCalledWith(...expected: Array): R; + /** + * Use to test the specific value that a mock function last returned. + * If the last call to the mock function threw an error, then this matcher will fail + * no matter what value you provided as the expected return value. + */ + toHaveLastReturnedWith(expected?: unknown): R; + /** + * Used to check that an object has a `.length` property + * and it is set to a certain numeric value. + */ + toHaveLength(expected: number): R; + /** + * Use to test the specific value that a mock function returned for the nth call. + * If the nth call to the mock function threw an error, then this matcher will fail + * no matter what value you provided as the expected return value. + */ + toHaveNthReturnedWith(nth: number, expected?: unknown): R; + /** + * Use to check if property at provided reference keyPath exists for an object. + * For checking deeply nested properties in an object you may use dot notation or an array containing + * the keyPath for deep references. + * + * Optionally, you can provide a value to check if it's equal to the value present at keyPath + * on the target object. This matcher uses 'deep equality' (like `toEqual()`) and recursively checks + * the equality of all fields. + * + * @example + * + * expect(houseForSale).toHaveProperty('kitchen.area', 20); + */ + toHaveProperty( + expectedPath: string | Array, + expectedValue?: unknown, + ): R; + /** + * Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time + */ + toHaveReturned(): R; + /** + * Use to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times. + * Any calls to the mock function that throw an error are not counted toward the number of times the function returned. + */ + toHaveReturnedTimes(expected: number): R; + /** + * Use to ensure that a mock function returned a specific value. + */ + toHaveReturnedWith(expected?: unknown): R; + /** + * Check that a string matches a regular expression. + */ + toMatch(expected: string | RegExp): R; + /** + * Used to check that a JavaScript object matches a subset of the properties of an object + */ + toMatchObject( + expected: Record | Array>, + ): R; + /** + * Ensure that a mock function has returned (as opposed to thrown) at least once. + */ + toReturn(): R; + /** + * Ensure that a mock function has returned (as opposed to thrown) a specified number of times. + */ + toReturnTimes(expected: number): R; + /** + * Ensure that a mock function has returned a specified value at least once. + */ + toReturnWith(expected?: unknown): R; + /** + * Use to test that objects have the same types as well as structure. + */ + toStrictEqual(expected: unknown): R; + /** + * Used to test that a function throws when it is called. + */ + toThrow(expected?: unknown): R; + /** + * If you want to test that a specific error is thrown inside a function. + */ + toThrowError(expected?: unknown): R; +} diff --git a/packages/playwright/package.json b/packages/playwright/package.json index d6a2aa23b2..16c3bd24c7 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -1,6 +1,6 @@ { "name": "playwright", - "version": "1.47.0-next", + "version": "1.48.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", @@ -24,8 +24,6 @@ "./lib/program": "./lib/program.js", "./lib/transform/babelBundle": "./lib/transform/babelBundle.js", "./lib/transform/compilationCache": "./lib/transform/compilationCache.js", - "./lib/runner/runner": "./lib/runner/runner.js", - "./lib/runner/testServer": "./lib/runner/testServer.js", "./lib/transform/esmLoader": "./lib/transform/esmLoader.js", "./lib/transform/transform": "./lib/transform/transform.js", "./lib/internalsForTest": "./lib/internalsForTest.js", @@ -58,7 +56,7 @@ }, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.0-next" + "playwright-core": "1.48.0-next" }, "optionalDependencies": { "fsevents": "2.3.2" diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index d9e218a3da..d7fb499645 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -53,6 +53,7 @@ export class FullConfigInternal { cliListOnly = false; cliPassWithNoTests?: boolean; cliFailOnFlakyTests?: boolean; + cliLastFailed?: boolean; testIdMatcher?: Matcher; defineConfigWasUsed = false; diff --git a/packages/playwright/src/common/expectBundle.ts b/packages/playwright/src/common/expectBundle.ts index 3322c36277..4b601b77ae 100644 --- a/packages/playwright/src/common/expectBundle.ts +++ b/packages/playwright/src/common/expectBundle.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -export const expect: typeof import('../../bundles/expect/node_modules/expect/build').expect = require('./expectBundleImpl').expect; +export const expect: typeof import('../../bundles/expect/third_party/index').expect = require('./expectBundleImpl').expect; +export const mock: typeof import('../../bundles/expect/node_modules/jest-mock') = require('./expectBundleImpl').mock; +export const asymmetricMatchers = require('./expectBundleImpl').asymmetricMatchers; +export const matcherUtils = require('./expectBundleImpl').matcherUtils; export const EXPECTED_COLOR: typeof import('../../bundles/expect/node_modules/jest-matcher-utils/build').EXPECTED_COLOR = require('./expectBundleImpl').EXPECTED_COLOR; export const INVERTED_COLOR: typeof import('../../bundles/expect/node_modules/jest-matcher-utils/build').INVERTED_COLOR = require('./expectBundleImpl').INVERTED_COLOR; export const RECEIVED_COLOR: typeof import('../../bundles/expect/node_modules/jest-matcher-utils/build').RECEIVED_COLOR = require('./expectBundleImpl').RECEIVED_COLOR; diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 130e731b5a..add1661502 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -83,15 +83,15 @@ const playwrightFixtures: Fixtures = ({ options.channel = channel; options.tracesDir = tracing().tracesDir(); - for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) + for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit, playwright._bidiChromium, playwright._bidiFirefox]) (browserType as any)._defaultLaunchOptions = options; await use(options); - for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) + for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit, playwright._bidiChromium, playwright._bidiFirefox]) (browserType as any)._defaultLaunchOptions = undefined; }, { scope: 'worker', auto: true, box: true }], browser: [async ({ playwright, browserName, _browserOptions, connectOptions, _reuseContext }, use, testInfo) => { - if (!['chromium', 'firefox', 'webkit'].includes(browserName)) + if (!['chromium', 'firefox', 'webkit', '_bidiChromium', '_bidiFirefox'].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`); if (connectOptions) { diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index 6391f8a64c..0c4408096d 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -133,12 +133,12 @@ export class TeleReporterReceiver { public isListing = false; private _rootSuite: TeleSuite; private _options: TeleReporterReceiverOptions; - private _reporter: Partial; + private _reporter: ReporterV2; private _tests = new Map(); private _rootDir!: string; private _config!: reporterTypes.FullConfig; - constructor(reporter: Partial, options: TeleReporterReceiverOptions = {}) { + constructor(reporter: ReporterV2, options: TeleReporterReceiverOptions = {}) { this._rootSuite = new TeleSuite('', 'root'); this._options = options; this._reporter = reporter; diff --git a/packages/trace-viewer/src/ui/teleSuiteUpdater.ts b/packages/playwright/src/isomorphic/teleSuiteUpdater.ts similarity index 84% rename from packages/trace-viewer/src/ui/teleSuiteUpdater.ts rename to packages/playwright/src/isomorphic/teleSuiteUpdater.ts index 0d448ea172..6f86c50147 100644 --- a/packages/trace-viewer/src/ui/teleSuiteUpdater.ts +++ b/packages/playwright/src/isomorphic/teleSuiteUpdater.ts @@ -14,11 +14,24 @@ * limitations under the License. */ -import { TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver'; -import { statusEx } from '@testIsomorphic/testTree'; -import type { ReporterV2 } from 'playwright/src/reporters/reporterV2'; -import type * as reporterTypes from 'playwright/types/testReporter'; -import type { Progress, TestModel } from './uiModeModel'; +import { TeleReporterReceiver, TeleSuite } from './teleReceiver'; +import { statusEx } from './testTree'; +import type { ReporterV2 } from '../reporters/reporterV2'; +import type * as reporterTypes from '../../types/testReporter'; + +export type TeleSuiteUpdaterProgress = { + total: number; + passed: number; + failed: number; + skipped: number; +}; + +export type TeleSuiteUpdaterTestModel = { + config: reporterTypes.FullConfig; + rootSuite: reporterTypes.Suite; + loadErrors: reporterTypes.TestError[]; + progress: TeleSuiteUpdaterProgress; +}; export type TeleSuiteUpdaterOptions = { onUpdate: (force?: boolean) => void, @@ -30,7 +43,7 @@ export class TeleSuiteUpdater { rootSuite: TeleSuite | undefined; config: reporterTypes.FullConfig | undefined; readonly loadErrors: reporterTypes.TestError[] = []; - readonly progress: Progress = { + readonly progress: TeleSuiteUpdaterProgress = { total: 0, passed: 0, failed: 0, @@ -64,6 +77,7 @@ export class TeleSuiteUpdater { // To work around that, have a dedicated per-run receiver that will only have // suite for a single test run, and hence will have correct total. this._lastRunReceiver = new TeleReporterReceiver({ + version: () => 'v2', onBegin: (suite: reporterTypes.Suite) => { this._lastRunTestCount = suite.allTests().length; this._lastRunReceiver = undefined; @@ -114,27 +128,20 @@ export class TeleSuiteUpdater { onError: (error: reporterTypes.TestError) => this._handleOnError(error), - printsToStdio: () => { - return false; - }, - - onStdOut: () => {}, - onStdErr: () => {}, - onExit: () => {}, - onStepBegin: () => {}, - onStepEnd: () => {}, + printsToStdio: () => false, }; } processGlobalReport(report: any[]) { const receiver = new TeleReporterReceiver({ + version: () => 'v2', onConfigure: (c: reporterTypes.FullConfig) => { this.config = c; }, onError: (error: reporterTypes.TestError) => this._handleOnError(error) }); for (const message of report) - receiver.dispatch(message); + void receiver.dispatch(message); } processListReport(report: any[]) { @@ -144,14 +151,14 @@ export class TeleSuiteUpdater { this._testResultsSnapshot = new Map(tests.map(test => [test.id, test.results])); this._receiver.reset(); for (const message of report) - this._receiver.dispatch(message); + void this._receiver.dispatch(message); } processTestReportEvent(message: any) { // The order of receiver dispatches matters here, we want to assign `lastRunTestCount` // before we use it. - this._lastRunReceiver?.dispatch(message)?.catch(() => {}); - this._receiver.dispatch(message)?.catch(() => {}); + this._lastRunReceiver?.dispatch(message)?.catch(() => { }); + this._receiver.dispatch(message)?.catch(() => { }); } private _handleOnError(error: reporterTypes.TestError) { @@ -160,7 +167,7 @@ export class TeleSuiteUpdater { this._options.onUpdate(); } - asModel(): TestModel { + asModel(): TeleSuiteUpdaterTestModel { return { rootSuite: this.rootSuite || new TeleSuite('', 'root'), config: this.config!, diff --git a/packages/playwright/src/isomorphic/testServerConnection.ts b/packages/playwright/src/isomorphic/testServerConnection.ts index 34ce0e31ef..22bdce30ff 100644 --- a/packages/playwright/src/isomorphic/testServerConnection.ts +++ b/packages/playwright/src/isomorphic/testServerConnection.ts @@ -19,6 +19,48 @@ import * as events from './events'; // -- Reuse boundary -- Everything below this line is reused in the vscode extension. +export interface TestServerTransport { + onmessage(listener: (message: string) => void): void; + onopen(listener: () => void): void; + onerror(listener: () => void): void; + onclose(listener: () => void): void; + + send(data: string): void; + close(): void; +} + +export class WebSocketTestServerTransport implements TestServerTransport { + private _ws: WebSocket; + + constructor(url: string | URL) { + this._ws = new WebSocket(url); + } + + onmessage(listener: (message: string) => void) { + this._ws.addEventListener('message', event => listener(event.data)); + } + + onopen(listener: () => void) { + this._ws.addEventListener('open', listener); + } + + onerror(listener: () => void) { + this._ws.addEventListener('error', listener); + } + + onclose(listener: () => void) { + this._ws.addEventListener('close', listener); + } + + send(data: string) { + this._ws.send(data); + } + + close() { + this._ws.close(); + } +} + export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents { readonly onClose: events.Event; readonly onReport: events.Event; @@ -33,21 +75,21 @@ export class TestServerConnection implements TestServerInterface, TestServerInte private _onLoadTraceRequestedEmitter = new events.EventEmitter<{ traceUrl: string }>(); private _lastId = 0; - private _ws: WebSocket; + private _transport: TestServerTransport; private _callbacks = new Map void, reject: (arg: Error) => void }>(); private _connectedPromise: Promise; private _isClosed = false; - constructor(wsURL: string) { + constructor(transport: TestServerTransport) { this.onClose = this._onCloseEmitter.event; this.onReport = this._onReportEmitter.event; this.onStdio = this._onStdioEmitter.event; this.onTestFilesChanged = this._onTestFilesChangedEmitter.event; this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event; - this._ws = new WebSocket(wsURL); - this._ws.addEventListener('message', event => { - const message = JSON.parse(String(event.data)); + this._transport = transport; + this._transport.onmessage(data => { + const message = JSON.parse(data); const { id, result, error, method, params } = message; if (id) { const callback = this._callbacks.get(id); @@ -62,12 +104,12 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this._dispatchEvent(method, params); } }); - const pingInterval = setInterval(() => this._sendMessage('ping').catch(() => {}), 30000); + const pingInterval = setInterval(() => this._sendMessage('ping').catch(() => { }), 30000); this._connectedPromise = new Promise((f, r) => { - this._ws.addEventListener('open', () => f()); - this._ws.addEventListener('error', r); + this._transport.onopen(f); + this._transport.onerror(r); }); - this._ws.addEventListener('close', () => { + this._transport.onclose(() => { this._isClosed = true; this._onCloseEmitter.fire(); clearInterval(pingInterval); @@ -85,14 +127,14 @@ export class TestServerConnection implements TestServerInterface, TestServerInte await this._connectedPromise; const id = ++this._lastId; const message = { id, method, params }; - this._ws.send(JSON.stringify(message)); + this._transport.send(JSON.stringify(message)); return new Promise((resolve, reject) => { this._callbacks.set(id, { resolve, reject }); }); } private _sendMessageNoReply(method: string, params?: any) { - this._sendMessage(method, params).catch(() => {}); + this._sendMessage(method, params).catch(() => { }); } private _dispatchEvent(method: string, params?: any) { @@ -200,7 +242,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte close() { try { - this._ws.close(); + this._transport.close(); } catch { } } diff --git a/packages/playwright/src/isomorphic/testServerInterface.ts b/packages/playwright/src/isomorphic/testServerInterface.ts index 0460ae56d9..28f82688dc 100644 --- a/packages/playwright/src/isomorphic/testServerInterface.ts +++ b/packages/playwright/src/isomorphic/testServerInterface.ts @@ -44,7 +44,7 @@ export interface TestServerInterface { installBrowsers(params: {}): Promise; - runGlobalSetup(params: {}): Promise<{ + runGlobalSetup(params: { outputDir?: string }): Promise<{ report: ReportEntry[], status: reporterTypes.FullResult['status'] }>; @@ -81,6 +81,7 @@ export interface TestServerInterface { locations?: string[]; grep?: string; grepInvert?: string; + outputDir?: string; }): Promise<{ report: ReportEntry[], status: reporterTypes.FullResult['status'] diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index 3e49360eb7..9c426fd86e 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -16,6 +16,7 @@ import { captureRawStack, + createGuid, isString, pollAgainstDeadline } from 'playwright-core/lib/utils'; import type { ExpectZone } from 'playwright-core/lib/utils'; @@ -60,7 +61,7 @@ import { } from '../common/expectBundle'; import { zones } from 'playwright-core/lib/utils'; import { TestInfoImpl } from '../worker/testInfo'; -import { ExpectError } from './matcherHint'; +import { ExpectError, isExpectError } from './matcherHint'; // #region // Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts @@ -104,11 +105,17 @@ export const printReceivedStringContainExpectedResult = ( type ExpectMessage = string | { message?: string }; -function createMatchers(actual: unknown, info: ExpectMetaInfo): any { - return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info)); +function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[]): any { + return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info, prefix)); } -function createExpect(info: ExpectMetaInfo) { +const getCustomMatchersSymbol = Symbol('get custom matchers'); + +function qualifiedMatcherName(qualifier: string[], matcherName: string) { + return qualifier.join(':') + '$' + matcherName; +} + +function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Record) { const expectInstance: Expect<{}> = new Proxy(expectLibrary, { apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) { const [actual, messageOrOptions] = argumentsList; @@ -119,18 +126,22 @@ function createExpect(info: ExpectMetaInfo) { throw new Error('`expect.poll()` accepts only function as a first argument'); newInfo.generator = actual as any; } - return createMatchers(actual, newInfo); + return createMatchers(actual, newInfo, prefix); }, - get: function(target: any, property: string) { + get: function(target: any, property: string | typeof getCustomMatchersSymbol) { if (property === 'configure') return configure; if (property === 'extend') { return (matchers: any) => { + const qualifier = [...prefix, createGuid()]; + const wrappedMatchers: any = {}; + const extendedMatchers: any = { ...customMatchers }; for (const [name, matcher] of Object.entries(matchers)) { - wrappedMatchers[name] = function(...args: any[]) { + const key = qualifiedMatcherName(qualifier, name); + wrappedMatchers[key] = function(...args: any[]) { const { isNot, promise, utils } = this; const newThis: ExpectMatcherState = { isNot, @@ -141,9 +152,12 @@ function createExpect(info: ExpectMetaInfo) { (newThis as any).equals = throwUnsupportedExpectMatcherError; return (matcher as any).call(newThis, ...args); }; + Object.defineProperty(wrappedMatchers[key], 'name', { value: name }); + extendedMatchers[name] = wrappedMatchers[key]; } expectLibrary.extend(wrappedMatchers); - return expectInstance; + + return createExpect(info, qualifier, extendedMatchers); }; } @@ -153,6 +167,9 @@ function createExpect(info: ExpectMetaInfo) { }; } + if (property === getCustomMatchersSymbol) + return customMatchers; + if (property === 'poll') { return (actual: unknown, messageOrOptions?: ExpectMessage & { timeout?: number, intervals?: number[] }) => { const poll = isString(messageOrOptions) ? {} : messageOrOptions || {}; @@ -178,7 +195,7 @@ function createExpect(info: ExpectMetaInfo) { newInfo.pollIntervals = configuration._poll.intervals; } } - return createExpect(newInfo); + return createExpect(newInfo, prefix, customMatchers); }; return expectInstance; @@ -241,15 +258,28 @@ type ExpectMetaInfo = { class ExpectMetaInfoProxyHandler implements ProxyHandler { private _info: ExpectMetaInfo; + private _prefix: string[]; - constructor(info: ExpectMetaInfo) { + constructor(info: ExpectMetaInfo, prefix: string[]) { this._info = { ...info }; + this._prefix = prefix; } get(target: Object, matcherName: string | symbol, receiver: any): any { let matcher = Reflect.get(target, matcherName, receiver); if (typeof matcherName !== 'string') return matcher; + + let resolvedMatcherName = matcherName; + for (let i = this._prefix.length; i > 0; i--) { + const qualifiedName = qualifiedMatcherName(this._prefix.slice(0, i), matcherName); + if (Reflect.has(target, qualifiedName)) { + matcher = Reflect.get(target, qualifiedName, receiver); + resolvedMatcherName = qualifiedName; + break; + } + } + if (matcher === undefined) throw new Error(`expect: Property '${matcherName}' not found.`); if (typeof matcher !== 'function') { @@ -260,7 +290,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler { if (this._info.isPoll) { if ((customAsyncMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects') throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`); - matcher = (...args: any[]) => pollMatcher(matcherName, !!this._info.isNot, this._info.pollIntervals, this._info.pollTimeout ?? currentExpectTimeout(), this._info.generator!, ...args); + matcher = (...args: any[]) => pollMatcher(resolvedMatcherName, !!this._info.isNot, this._info.pollIntervals, this._info.pollTimeout ?? currentExpectTimeout(), this._info.generator!, ...args); } return (...args: any[]) => { const testInfo = currentTestInfo(); @@ -289,8 +319,8 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler { const step = testInfo._addStep(stepInfo); - const reportStepError = (jestError: ExpectError) => { - const error = new ExpectError(jestError, customMessage, stackFrames); + const reportStepError = (jestError: Error | unknown) => { + const error = isExpectError(jestError) ? new ExpectError(jestError, customMessage, stackFrames) : jestError; step.complete({ error }); if (this._info.isSoft) testInfo._failWithError(error); @@ -320,7 +350,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler { } } -async function pollMatcher(matcherName: any, isNot: boolean, pollIntervals: number[] | undefined, timeout: number, generator: () => any, ...args: any[]) { +async function pollMatcher(qualifiedMatcherName: any, isNot: boolean, pollIntervals: number[] | undefined, timeout: number, generator: () => any, ...args: any[]) { const testInfo = currentTestInfo(); const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : TestInfoImpl._defaultDeadlineForMatcher(timeout); @@ -333,7 +363,7 @@ async function pollMatcher(matcherName: any, isNot: boolean, pollIntervals: numb if (isNot) expectInstance = expectInstance.not; try { - expectInstance[matcherName].call(expectInstance, ...args); + expectInstance[qualifiedMatcherName].call(expectInstance, ...args); return { continuePolling: false, result: undefined }; } catch (error) { return { continuePolling: true, result: error }; @@ -375,8 +405,15 @@ function computeArgsSuffix(matcherName: string, args: any[]) { return value ? `(${value})` : ''; } -export const expect: Expect<{}> = createExpect({}).extend(customMatchers); +export const expect: Expect<{}> = createExpect({}, [], {}).extend(customMatchers); export function mergeExpects(...expects: any[]) { - return expect; + let merged = expect; + for (const e of expects) { + const internals = e[getCustomMatchersSymbol]; + if (!internals) // non-playwright expects mutate the global expect, so we don't need to do anything special + continue; + merged = merged.extend(internals); + } + return merged; } diff --git a/packages/playwright/src/matchers/matcherHint.ts b/packages/playwright/src/matchers/matcherHint.ts index e8aba2bbff..8a78932c68 100644 --- a/packages/playwright/src/matchers/matcherHint.ts +++ b/packages/playwright/src/matchers/matcherHint.ts @@ -64,3 +64,7 @@ export class ExpectError extends Error { this.stack = this.name + ': ' + this.message + '\n' + stringifyStackFrames(stackFrames).join('\n'); } } + +export function isExpectError(e: unknown): e is ExpectError { + return e instanceof Error && 'matcherResult' in e; +} diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index 08ae8b6385..3ca9180ae2 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -20,8 +20,8 @@ import { colors } from 'playwright-core/lib/utilsBundle'; import { expectTypes, callLogText } from '../util'; import { toBeTruthy } from './toBeTruthy'; import { toEqual } from './toEqual'; -import { toExpectedTextValues, toMatchText } from './toMatchText'; -import { constructURLBasedOnBaseURL, isRegExp, isString, isTextualMimeType, pollAgainstDeadline } from 'playwright-core/lib/utils'; +import { toMatchText } from './toMatchText'; +import { constructURLBasedOnBaseURL, isRegExp, isString, isTextualMimeType, pollAgainstDeadline, serializeExpectedTextValues } from 'playwright-core/lib/utils'; import { currentTestInfo } from '../common/globals'; import { TestInfoImpl } from '../worker/testInfo'; import type { ExpectMatcherState } from '../../types/test'; @@ -163,12 +163,12 @@ export function toContainText( ) { if (Array.isArray(expected)) { return toEqual.call(this, 'toContainText', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues(expected, { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); + const expectedText = serializeExpectedTextValues(expected, { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); return await locator._expect('to.contain.text.array', { expectedText, isNot, useInnerText: options.useInnerText, timeout }); }, expected, { ...options, contains: true }); } else { return toMatchText.call(this, 'toContainText', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); return await locator._expect('to.have.text', { expectedText, isNot, useInnerText: options.useInnerText, timeout }); }, expected, options); } @@ -181,7 +181,7 @@ export function toHaveAccessibleDescription( options?: { timeout?: number, ignoreCase?: boolean }, ) { return toMatchText.call(this, 'toHaveAccessibleDescription', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); return await locator._expect('to.have.accessible.description', { expectedText, isNot, timeout }); }, expected, options); } @@ -193,7 +193,7 @@ export function toHaveAccessibleName( options?: { timeout?: number, ignoreCase?: boolean }, ) { return toMatchText.call(this, 'toHaveAccessibleName', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); return await locator._expect('to.have.accessible.name', { expectedText, isNot, timeout }); }, expected, options); } @@ -218,7 +218,7 @@ export function toHaveAttribute( }, options); } return toMatchText.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected as (string | RegExp)], { ignoreCase: options?.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected as (string | RegExp)], { ignoreCase: options?.ignoreCase }); return await locator._expect('to.have.attribute.value', { expressionArg: name, expectedText, isNot, timeout }); }, expected as (string | RegExp), options); } @@ -231,12 +231,12 @@ export function toHaveClass( ) { if (Array.isArray(expected)) { return toEqual.call(this, 'toHaveClass', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues(expected); + const expectedText = serializeExpectedTextValues(expected); return await locator._expect('to.have.class.array', { expectedText, isNot, timeout }); }, expected, options); } else { return toMatchText.call(this, 'toHaveClass', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected]); + const expectedText = serializeExpectedTextValues([expected]); return await locator._expect('to.have.class', { expectedText, isNot, timeout }); }, expected, options); } @@ -261,7 +261,7 @@ export function toHaveCSS( options?: { timeout?: number }, ) { return toMatchText.call(this, 'toHaveCSS', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected]); + const expectedText = serializeExpectedTextValues([expected]); return await locator._expect('to.have.css', { expressionArg: name, expectedText, isNot, timeout }); }, expected, options); } @@ -273,7 +273,7 @@ export function toHaveId( options?: { timeout?: number }, ) { return toMatchText.call(this, 'toHaveId', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected]); + const expectedText = serializeExpectedTextValues([expected]); return await locator._expect('to.have.id', { expectedText, isNot, timeout }); }, expected, options); } @@ -299,7 +299,7 @@ export function toHaveRole( if (!isString(expected)) throw new Error(`"role" argument in toHaveRole must be a string`); return toMatchText.call(this, 'toHaveRole', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected]); + const expectedText = serializeExpectedTextValues([expected]); return await locator._expect('to.have.role', { expectedText, isNot, timeout }); }, expected, options); } @@ -312,12 +312,12 @@ export function toHaveText( ) { if (Array.isArray(expected)) { return toEqual.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); + const expectedText = serializeExpectedTextValues(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); return await locator._expect('to.have.text.array', { expectedText, isNot, useInnerText: options?.useInnerText, timeout }); }, expected, options); } else { return toMatchText.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected], { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase }); return await locator._expect('to.have.text', { expectedText, isNot, useInnerText: options?.useInnerText, timeout }); }, expected, options); } @@ -330,7 +330,7 @@ export function toHaveValue( options?: { timeout?: number }, ) { return toMatchText.call(this, 'toHaveValue', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected]); + const expectedText = serializeExpectedTextValues([expected]); return await locator._expect('to.have.value', { expectedText, isNot, timeout }); }, expected, options); } @@ -342,7 +342,7 @@ export function toHaveValues( options?: { timeout?: number }, ) { return toEqual.call(this, 'toHaveValues', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues(expected); + const expectedText = serializeExpectedTextValues(expected); return await locator._expect('to.have.values', { expectedText, isNot, timeout }); }, expected, options); } @@ -355,7 +355,7 @@ export function toHaveTitle( ) { const locator = page.locator(':root') as LocatorEx; return toMatchText.call(this, 'toHaveTitle', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected], { normalizeWhiteSpace: true }); + const expectedText = serializeExpectedTextValues([expected], { normalizeWhiteSpace: true }); return await locator._expect('to.have.title', { expectedText, isNot, timeout }); }, expected, options); } @@ -370,7 +370,7 @@ export function toHaveURL( expected = typeof expected === 'string' ? constructURLBasedOnBaseURL(baseURL, expected) : expected; const locator = page.locator(':root') as LocatorEx; return toMatchText.call(this, 'toHaveURL', locator, 'Locator', async (isNot, timeout) => { - const expectedText = toExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); return await locator._expect('to.have.url', { expectedText, isNot, timeout }); }, expected, options); } diff --git a/packages/playwright/src/matchers/toMatchText.ts b/packages/playwright/src/matchers/toMatchText.ts index 790b402d2f..ebac8f8028 100644 --- a/packages/playwright/src/matchers/toMatchText.ts +++ b/packages/playwright/src/matchers/toMatchText.ts @@ -15,8 +15,6 @@ */ -import type { ExpectedTextValue } from '@protocol/channels'; -import { isRegExp, isString } from 'playwright-core/lib/utils'; import { expectTypes, callLogText } from '../util'; import { printReceivedStringContainExpectedResult, @@ -95,14 +93,3 @@ export async function toMatchText( timeout: timedOut ? timeout : undefined, }; } - -export function toExpectedTextValues(items: (string | RegExp)[], options: { matchSubstring?: boolean, normalizeWhiteSpace?: boolean, ignoreCase?: boolean } = {}): ExpectedTextValue[] { - return items.map(i => ({ - string: isString(i) ? i : undefined, - regexSource: isRegExp(i) ? i.source : undefined, - regexFlags: isRegExp(i) ? i.flags : undefined, - matchSubstring: options.matchSubstring, - ignoreCase: options.ignoreCase, - normalizeWhiteSpace: options.normalizeWhiteSpace, - })); -} diff --git a/packages/playwright/src/plugins/index.ts b/packages/playwright/src/plugins/index.ts index 3502d90966..2f7995cb2f 100644 --- a/packages/playwright/src/plugins/index.ts +++ b/packages/playwright/src/plugins/index.ts @@ -21,6 +21,8 @@ export interface TestRunnerPlugin { name: string; setup?(config: FullConfig, configDir: string, reporter: ReporterV2): Promise; populateDependencies?(): Promise; + startDevServer?(): Promise<() => Promise>; + clearCache?(): Promise; begin?(suite: Suite): Promise; end?(): Promise; teardown?(): Promise; @@ -29,6 +31,7 @@ export interface TestRunnerPlugin { export type TestRunnerPluginRegistration = { factory: TestRunnerPlugin | (() => TestRunnerPlugin | Promise); instance?: TestRunnerPlugin; + devServerCleanup?: any; }; export { webServer } from './webServerPlugin'; diff --git a/packages/playwright/src/plugins/webServerPlugin.ts b/packages/playwright/src/plugins/webServerPlugin.ts index 86f84ad652..e2474f35f2 100644 --- a/packages/playwright/src/plugins/webServerPlugin.ts +++ b/packages/playwright/src/plugins/webServerPlugin.ts @@ -73,7 +73,9 @@ export class WebServerPlugin implements TestRunnerPlugin { } public async teardown() { + debugWebServer(`Terminating the WebServer`); await this._killProcess?.(); + debugWebServer(`Terminated the WebServer`); } private async _startProcess(): Promise { @@ -111,13 +113,13 @@ export class WebServerPlugin implements TestRunnerPlugin { debugWebServer(`Process started`); - launchedProcess.stderr!.on('data', line => { + launchedProcess.stderr!.on('data', data => { if (debugWebServer.enabled || (this._options.stderr === 'pipe' || !this._options.stderr)) - this._reporter!.onStdErr?.(colors.dim('[WebServer] ') + line.toString()); + this._reporter!.onStdErr?.(prefixOutputLines(data.toString())); }); - launchedProcess.stdout!.on('data', line => { + launchedProcess.stdout!.on('data', data => { if (debugWebServer.enabled || this._options.stdout === 'pipe') - this._reporter!.onStdOut?.(colors.dim('[WebServer] ') + line.toString()); + this._reporter!.onStdOut?.(prefixOutputLines(data.toString())); }); } @@ -199,3 +201,14 @@ export const webServerPluginsForConfig = (config: FullConfigInternal): TestRunne return webServerPlugins; }; + +function prefixOutputLines(output: string) { + const lastIsNewLine = output[output.length - 1] === '\n'; + let lines = output.split('\n'); + if (lastIsNewLine) + lines.pop(); + lines = lines.map(line => colors.dim('[WebServer] ') + line); + if (lastIsNewLine) + lines.push(''); + return lines.join('\n'); +} diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 803b9d2291..1bf2fb42b2 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -19,14 +19,14 @@ import type { Command } from 'playwright-core/lib/utilsBundle'; import fs from 'fs'; import path from 'path'; -import { Runner, readLastRunInfo } from './runner/runner'; +import { Runner } from './runner/runner'; import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils'; import { serializeError } from './util'; import { showHTMLReport } from './reporters/html'; import { createMergedReport } from './reporters/merge'; -import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports } from './common/configLoader'; +import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports, resolveConfigLocation } from './common/configLoader'; import type { ConfigCLIOverrides } from './common/ipc'; -import type { FullResult, TestError } from '../types/testReporter'; +import type { TestError } from '../types/testReporter'; import type { TraceMode } from '../types/test'; import { builtInReporters, defaultReporter, defaultTimeout } from './common/config'; import { program } from 'playwright-core/lib/cli/program'; @@ -34,7 +34,7 @@ export { program } from 'playwright-core/lib/cli/program'; import type { ReporterDescription } from '../types/test'; import { prepareErrorStack } from './reporters/base'; import * as testServer from './runner/testServer'; -import { clearCacheAndLogToConsole } from './runner/testServer'; +import { runWatchModeLoop } from './runner/watchMode'; function addTestCommand(program: Command) { const command = program.command('test [test-filter...]'); @@ -73,10 +73,13 @@ function addClearCacheCommand(program: Command) { command.description('clears build and test caches'); command.option('-c, --config ', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.action(async opts => { - const configInternal = await loadConfigFromFileRestartIfNeeded(opts.config); - if (!configInternal) + const config = await loadConfigFromFileRestartIfNeeded(opts.config); + if (!config) return; - await clearCacheAndLogToConsole(configInternal); + const runner = new Runner(config); + const { status } = await runner.clearCache(); + const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); + gracefullyProcessExitDoNotHang(exitCode); }); } @@ -85,7 +88,8 @@ function addFindRelatedTestFilesCommand(program: Command) { command.description('Returns the list of related tests to the given files'); command.option('-c, --config ', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.action(async (files, options) => { - await withRunnerAndMutedWrite(options.config, runner => runner.findRelatedTestFiles('in-process', files)); + const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file)); + await withRunnerAndMutedWrite(options.config, runner => runner.findRelatedTestFiles(resolvedFiles)); }); } @@ -94,17 +98,13 @@ function addDevServerCommand(program: Command) { command.description('start dev server'); command.option('-c, --config ', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.action(async options => { - const configInternal = await loadConfigFromFileRestartIfNeeded(options.config); - if (!configInternal) + const config = await loadConfigFromFileRestartIfNeeded(options.config); + if (!config) return; - const { config } = configInternal; - const implementation = (config as any)['@playwright/test']?.['cli']?.['dev-server']; - if (implementation) { - await implementation(configInternal); - } else { - console.log(`DevServer is not available in the package you are using. Did you mean to use component testing?`); - gracefullyProcessExitDoNotHang(1); - } + const runner = new Runner(config); + const { status } = await runner.runDevServer(); + const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); + gracefullyProcessExitDoNotHang(exitCode); }); } @@ -183,15 +183,30 @@ async function runTests(args: string[], opts: { [key: string]: any }) { return; } + if (process.env.PWTEST_WATCH) { + if (opts.onlyChanged) + throw new Error(`--only-changed is not supported in watch mode. If you'd like that to change, file an issue and let us know about your usecase for it.`); + + const status = await runWatchModeLoop( + resolveConfigLocation(opts.config), + { + projects: opts.project, + files: args, + grep: opts.grep + } + ); + await stopProfiling('runner'); + if (status === 'restarted') + return; + const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); + gracefullyProcessExitDoNotHang(exitCode); + return; + } + const config = await loadConfigFromFileRestartIfNeeded(opts.config, cliOverrides, opts.deps === false); if (!config) return; - if (opts.lastFailed) { - const lastRunInfo = await readLastRunInfo(config); - config.testIdMatcher = id => lastRunInfo.failedTests.includes(id); - } - config.cliArgs = args; config.cliGrep = opts.grep as string | undefined; config.cliOnlyChanged = opts.onlyChanged === true ? 'HEAD' : opts.onlyChanged; @@ -200,13 +215,10 @@ async function runTests(args: string[], opts: { [key: string]: any }) { config.cliProjectFilter = opts.project || undefined; config.cliPassWithNoTests = !!opts.passWithNoTests; config.cliFailOnFlakyTests = !!opts.failOnFlakyTests; + config.cliLastFailed = !!opts.lastFailed; const runner = new Runner(config); - let status: FullResult['status']; - if (process.env.PWTEST_WATCH) - status = await runner.watchAllTests(); - else - status = await runner.runAllTests(); + const status = await runner.runAllTests(); await stopProfiling('runner'); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); gracefullyProcessExitDoNotHang(exitCode); diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index c9ce2f7bcd..e0aa7fa04e 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -123,9 +123,6 @@ export class BaseReporter implements ReporterV2 { (result as any)[kOutputSymbol].push(output); } - onTestBegin(test: TestCase, result: TestResult): void { - } - onTestEnd(test: TestCase, result: TestResult) { if (result.status !== 'skipped' && result.status !== test.expectedStatus) ++this._failureCount; @@ -146,19 +143,6 @@ export class BaseReporter implements ReporterV2 { this.result = result; } - onStepBegin(test: TestCase, result: TestResult, step: TestStep): void { - } - - onStepEnd(test: TestCase, result: TestResult, step: TestStep): void { - } - - async onExit() { - } - - printsToStdio() { - return true; - } - protected fitToScreen(line: string, prefix?: string): string { if (!ttyWidth) { // Guard against the case where we cannot determine available width. diff --git a/packages/playwright/src/reporters/dot.ts b/packages/playwright/src/reporters/dot.ts index 05d2da1e4e..169af3b1e7 100644 --- a/packages/playwright/src/reporters/dot.ts +++ b/packages/playwright/src/reporters/dot.ts @@ -20,10 +20,6 @@ import type { FullResult, TestCase, TestResult, Suite, TestError } from '../../t class DotReporter extends BaseReporter { private _counter = 0; - override printsToStdio() { - return true; - } - override onBegin(suite: Suite) { super.onBegin(suite); console.log(this.generateStartingMessage()); diff --git a/packages/playwright/src/reporters/empty.ts b/packages/playwright/src/reporters/empty.ts index 09bd63668c..3bab9bda0f 100644 --- a/packages/playwright/src/reporters/empty.ts +++ b/packages/playwright/src/reporters/empty.ts @@ -15,49 +15,15 @@ */ import type { ReporterV2 } from './reporterV2'; -import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Suite } from '../../types/testReporter'; class EmptyReporter implements ReporterV2 { - onConfigure(config: FullConfig) { - } - - onBegin(suite: Suite) { - } - - onTestBegin(test: TestCase, result: TestResult) { - } - - onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) { - } - - onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) { - } - - onTestEnd(test: TestCase, result: TestResult) { - } - - async onEnd(result: FullResult) { - } - - async onExit() { - } - - onError(error: TestError) { - } - - onStepBegin(test: TestCase, result: TestResult, step: TestStep) { - } - - onStepEnd(test: TestCase, result: TestResult, step: TestStep) { + version(): 'v2' { + return 'v2'; } printsToStdio() { return false; } - - version(): 'v2' { - return 'v2'; - } } export default EmptyReporter; diff --git a/packages/playwright/src/reporters/github.ts b/packages/playwright/src/reporters/github.ts index 85bd4f8525..dc03677714 100644 --- a/packages/playwright/src/reporters/github.ts +++ b/packages/playwright/src/reporters/github.ts @@ -59,7 +59,7 @@ class GitHubLogger { export class GitHubReporter extends BaseReporter { githubLogger = new GitHubLogger(); - override printsToStdio() { + printsToStdio() { return false; } diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 7b279a70a1..fd1fb0cbdf 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -30,7 +30,7 @@ import type { ZipFile } from 'playwright-core/lib/zipBundle'; import { yazl } from 'playwright-core/lib/zipBundle'; import { mime } from 'playwright-core/lib/utilsBundle'; import type { HTMLReport, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep } from '@html-reporter/types'; -import EmptyReporter from './empty'; +import type { ReporterV2 } from './reporterV2'; type TestEntry = { testCase: TestCase; @@ -55,7 +55,7 @@ type HtmlReporterOptions = { _isTestServer?: boolean; }; -class HtmlReporter extends EmptyReporter { +class HtmlReporter implements ReporterV2 { private config!: FullConfig; private suite!: Suite; private _options: HtmlReporterOptions; @@ -68,19 +68,22 @@ class HtmlReporter extends EmptyReporter { private _topLevelErrors: TestError[] = []; constructor(options: HtmlReporterOptions) { - super(); this._options = options; } - override printsToStdio() { + version(): 'v2' { + return 'v2'; + } + + printsToStdio() { return false; } - override onConfigure(config: FullConfig) { + onConfigure(config: FullConfig) { this.config = config; } - override onBegin(suite: Suite) { + onBegin(suite: Suite) { const { outputFolder, open, attachmentsBaseURL, host, port } = this._resolveOptions(); this._outputFolder = outputFolder; this._open = open; @@ -122,18 +125,18 @@ class HtmlReporter extends EmptyReporter { return !!relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath); } - override onError(error: TestError): void { + onError(error: TestError): void { this._topLevelErrors.push(error); } - override async onEnd(result: FullResult) { + async onEnd(result: FullResult) { const projectSuites = this.suite.suites; await removeFolders([this._outputFolder]); const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL); this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors); } - override async onExit() { + async onExit() { if (process.env.CI || !this._buildResult) return; const { ok, singleTestId } = this._buildResult; diff --git a/packages/playwright/src/reporters/internalReporter.ts b/packages/playwright/src/reporters/internalReporter.ts index 02959ab11b..3526f89e0d 100644 --- a/packages/playwright/src/reporters/internalReporter.ts +++ b/packages/playwright/src/reporters/internalReporter.ts @@ -21,16 +21,17 @@ import { Suite } from '../common/test'; import { colors, prepareErrorStack, relativeFilePath } from './base'; import type { ReporterV2 } from './reporterV2'; import { monotonicTime } from 'playwright-core/lib/utils'; +import { Multiplexer } from './multiplexer'; -export class InternalReporter { +export class InternalReporter implements ReporterV2 { private _reporter: ReporterV2; private _didBegin = false; private _config!: FullConfig; private _startTime: Date | undefined; private _monotonicStartTime: number | undefined; - constructor(reporter: ReporterV2) { - this._reporter = reporter; + constructor(reporters: ReporterV2[]) { + this._reporter = new Multiplexer(reporters); } version(): 'v2' { @@ -41,29 +42,29 @@ export class InternalReporter { this._config = config; this._startTime = new Date(); this._monotonicStartTime = monotonicTime(); - this._reporter.onConfigure(config); + this._reporter.onConfigure?.(config); } onBegin(suite: Suite) { this._didBegin = true; - this._reporter.onBegin(suite); + this._reporter.onBegin?.(suite); } onTestBegin(test: TestCase, result: TestResult) { - this._reporter.onTestBegin(test, result); + this._reporter.onTestBegin?.(test, result); } onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) { - this._reporter.onStdOut(chunk, test, result); + this._reporter.onStdOut?.(chunk, test, result); } onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) { - this._reporter.onStdErr(chunk, test, result); + this._reporter.onStdErr?.(chunk, test, result); } onTestEnd(test: TestCase, result: TestResult) { this._addSnippetToTestErrors(test, result); - this._reporter.onTestEnd(test, result); + this._reporter.onTestEnd?.(test, result); } async onEnd(result: { status: FullResult['status'] }) { @@ -71,7 +72,7 @@ export class InternalReporter { // onBegin was not reported, emit it. this.onBegin(new Suite('', 'root')); } - return await this._reporter.onEnd({ + return await this._reporter.onEnd?.({ ...result, startTime: this._startTime!, duration: monotonicTime() - this._monotonicStartTime!, @@ -79,25 +80,25 @@ export class InternalReporter { } async onExit() { - await this._reporter.onExit(); + await this._reporter.onExit?.(); } onError(error: TestError) { addLocationAndSnippetToError(this._config, error); - this._reporter.onError(error); + this._reporter.onError?.(error); } onStepBegin(test: TestCase, result: TestResult, step: TestStep) { - this._reporter.onStepBegin(test, result, step); + this._reporter.onStepBegin?.(test, result, step); } onStepEnd(test: TestCase, result: TestResult, step: TestStep) { this._addSnippetToStepError(test, step); - this._reporter.onStepEnd(test, result, step); + this._reporter.onStepEnd?.(test, result, step); } printsToStdio() { - return this._reporter.printsToStdio(); + return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true; } private _addSnippetToTestErrors(test: TestCase, result: TestResult) { diff --git a/packages/playwright/src/reporters/json.ts b/packages/playwright/src/reporters/json.ts index 62a625db5d..e2b7ddf872 100644 --- a/packages/playwright/src/reporters/json.ts +++ b/packages/playwright/src/reporters/json.ts @@ -20,41 +20,44 @@ import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, Full import { formatError, prepareErrorStack, resolveOutputFile } from './base'; import { MultiMap, toPosixPath } from 'playwright-core/lib/utils'; import { getProjectId } from '../common/config'; -import EmptyReporter from './empty'; +import type { ReporterV2 } from './reporterV2'; type JSONOptions = { outputFile?: string, configDir: string, }; -class JSONReporter extends EmptyReporter { +class JSONReporter implements ReporterV2 { config!: FullConfig; suite!: Suite; private _errors: TestError[] = []; private _resolvedOutputFile: string | undefined; constructor(options: JSONOptions) { - super(); this._resolvedOutputFile = resolveOutputFile('JSON', options)?.outputFile; } - override printsToStdio() { + version(): 'v2' { + return 'v2'; + } + + printsToStdio() { return !this._resolvedOutputFile; } - override onConfigure(config: FullConfig) { + onConfigure(config: FullConfig) { this.config = config; } - override onBegin(suite: Suite) { + onBegin(suite: Suite) { this.suite = suite; } - override onError(error: TestError): void { + onError(error: TestError): void { this._errors.push(error); } - override async onEnd(result: FullResult) { + async onEnd(result: FullResult) { await outputReport(this._serializeReport(result), this._resolvedOutputFile); } diff --git a/packages/playwright/src/reporters/junit.ts b/packages/playwright/src/reporters/junit.ts index 61cf7e8e99..befd652e4b 100644 --- a/packages/playwright/src/reporters/junit.ts +++ b/packages/playwright/src/reporters/junit.ts @@ -18,8 +18,8 @@ import fs from 'fs'; import path from 'path'; import type { FullConfig, FullResult, Suite, TestCase } from '../../types/testReporter'; import { formatFailure, resolveOutputFile, stripAnsiEscapes } from './base'; -import EmptyReporter from './empty'; import { getAsBooleanFromENV } from 'playwright-core/lib/utils'; +import type { ReporterV2 } from './reporterV2'; type JUnitOptions = { outputFile?: string, @@ -29,7 +29,7 @@ type JUnitOptions = { configDir: string, }; -class JUnitReporter extends EmptyReporter { +class JUnitReporter implements ReporterV2 { private config!: FullConfig; private configDir: string; private suite!: Suite; @@ -42,27 +42,30 @@ class JUnitReporter extends EmptyReporter { private includeProjectInTestName = false; constructor(options: JUnitOptions) { - super(); this.stripANSIControlSequences = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_STRIP_ANSI', !!options.stripANSIControlSequences); this.includeProjectInTestName = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME', !!options.includeProjectInTestName); this.configDir = options.configDir; this.resolvedOutputFile = resolveOutputFile('JUNIT', options)?.outputFile; } - override printsToStdio() { + version(): 'v2' { + return 'v2'; + } + + printsToStdio() { return !this.resolvedOutputFile; } - override onConfigure(config: FullConfig) { + onConfigure(config: FullConfig) { this.config = config; } - override onBegin(suite: Suite) { + onBegin(suite: Suite) { this.suite = suite; this.timestamp = new Date(); } - override async onEnd(result: FullResult) { + async onEnd(result: FullResult) { const children: XMLEntry[] = []; for (const projectSuite of this.suite.suites) { for (const fileSuite of projectSuite.suites) diff --git a/packages/playwright/src/reporters/line.ts b/packages/playwright/src/reporters/line.ts index 7d875d0f31..af4c786e16 100644 --- a/packages/playwright/src/reporters/line.ts +++ b/packages/playwright/src/reporters/line.ts @@ -23,10 +23,6 @@ class LineReporter extends BaseReporter { private _lastTest: TestCase | undefined; private _didBegin = false; - override printsToStdio() { - return true; - } - override onBegin(suite: Suite) { super.onBegin(suite); const startingMessage = this.generateStartingMessage(); @@ -66,20 +62,17 @@ class LineReporter extends BaseReporter { console.log(); } - override onTestBegin(test: TestCase, result: TestResult) { - super.onTestBegin(test, result); + onTestBegin(test: TestCase, result: TestResult) { ++this._current; this._updateLine(test, result, undefined); } - override onStepBegin(test: TestCase, result: TestResult, step: TestStep) { - super.onStepBegin(test, result, step); + onStepBegin(test: TestCase, result: TestResult, step: TestStep) { if (step.category === 'test.step') this._updateLine(test, result, step); } - override onStepEnd(test: TestCase, result: TestResult, step: TestStep) { - super.onStepEnd(test, result, step); + onStepEnd(test: TestCase, result: TestResult, step: TestStep) { if (step.category === 'test.step') this._updateLine(test, result, step.parent); } diff --git a/packages/playwright/src/reporters/list.ts b/packages/playwright/src/reporters/list.ts index f87a7d4fe8..8704bfe104 100644 --- a/packages/playwright/src/reporters/list.ts +++ b/packages/playwright/src/reporters/list.ts @@ -39,10 +39,6 @@ class ListReporter extends BaseReporter { this._printSteps = getAsBooleanFromENV('PLAYWRIGHT_LIST_PRINT_STEPS', options.printSteps); } - override printsToStdio() { - return true; - } - override onBegin(suite: Suite) { super.onBegin(suite); const startingMessage = this.generateStartingMessage(); @@ -52,9 +48,7 @@ class ListReporter extends BaseReporter { } } - override onTestBegin(test: TestCase, result: TestResult) { - super.onTestBegin(test, result); - + onTestBegin(test: TestCase, result: TestResult) { const index = String(this._resultIndex.size + 1); this._resultIndex.set(result, index); @@ -88,8 +82,7 @@ class ListReporter extends BaseReporter { return stepIndex; } - override onStepBegin(test: TestCase, result: TestResult, step: TestStep) { - super.onStepBegin(test, result, step); + onStepBegin(test: TestCase, result: TestResult, step: TestStep) { if (step.category !== 'test.step') return; const testIndex = this._resultIndex.get(result) || ''; @@ -108,8 +101,7 @@ class ListReporter extends BaseReporter { } } - override onStepEnd(test: TestCase, result: TestResult, step: TestStep) { - super.onStepEnd(test, result, step); + onStepEnd(test: TestCase, result: TestResult, step: TestStep) { if (step.category !== 'test.step') return; diff --git a/packages/playwright/src/reporters/markdown.ts b/packages/playwright/src/reporters/markdown.ts index a32d7bda42..fe8c83cdd4 100644 --- a/packages/playwright/src/reporters/markdown.ts +++ b/packages/playwright/src/reporters/markdown.ts @@ -34,7 +34,7 @@ class MarkdownReporter extends BaseReporter { this._options = options; } - override printsToStdio() { + printsToStdio() { return false; } diff --git a/packages/playwright/src/reporters/multiplexer.ts b/packages/playwright/src/reporters/multiplexer.ts index bfd91f3350..b666128ab4 100644 --- a/packages/playwright/src/reporters/multiplexer.ts +++ b/packages/playwright/src/reporters/multiplexer.ts @@ -31,37 +31,37 @@ export class Multiplexer implements ReporterV2 { onConfigure(config: FullConfig) { for (const reporter of this._reporters) - wrap(() => reporter.onConfigure(config)); + wrap(() => reporter.onConfigure?.(config)); } onBegin(suite: Suite) { for (const reporter of this._reporters) - wrap(() => reporter.onBegin(suite)); + wrap(() => reporter.onBegin?.(suite)); } onTestBegin(test: TestCase, result: TestResult) { for (const reporter of this._reporters) - wrap(() => reporter.onTestBegin(test, result)); + wrap(() => reporter.onTestBegin?.(test, result)); } onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) { for (const reporter of this._reporters) - wrap(() => reporter.onStdOut(chunk, test, result)); + wrap(() => reporter.onStdOut?.(chunk, test, result)); } onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) { for (const reporter of this._reporters) - wrap(() => reporter.onStdErr(chunk, test, result)); + wrap(() => reporter.onStdErr?.(chunk, test, result)); } onTestEnd(test: TestCase, result: TestResult) { for (const reporter of this._reporters) - wrap(() => reporter.onTestEnd(test, result)); + wrap(() => reporter.onTestEnd?.(test, result)); } async onEnd(result: FullResult) { for (const reporter of this._reporters) { - const outResult = await wrapAsync(() => reporter.onEnd(result)); + const outResult = await wrapAsync(() => reporter.onEnd?.(result)); if (outResult?.status) result.status = outResult.status; } @@ -70,28 +70,28 @@ export class Multiplexer implements ReporterV2 { async onExit() { for (const reporter of this._reporters) - await wrapAsync(() => reporter.onExit()); + await wrapAsync(() => reporter.onExit?.()); } onError(error: TestError) { for (const reporter of this._reporters) - wrap(() => reporter.onError(error)); + wrap(() => reporter.onError?.(error)); } onStepBegin(test: TestCase, result: TestResult, step: TestStep) { for (const reporter of this._reporters) - wrap(() => reporter.onStepBegin(test, result, step)); + wrap(() => reporter.onStepBegin?.(test, result, step)); } onStepEnd(test: TestCase, result: TestResult, step: TestStep) { for (const reporter of this._reporters) - wrap(() => reporter.onStepEnd(test, result, step)); + wrap(() => reporter.onStepEnd?.(test, result, step)); } printsToStdio(): boolean { return this._reporters.some(r => { - let prints = true; - wrap(() => prints = r.printsToStdio()); + let prints = false; + wrap(() => prints = r.printsToStdio ? r.printsToStdio() : true); return prints; }); } diff --git a/packages/playwright/src/reporters/reporterV2.ts b/packages/playwright/src/reporters/reporterV2.ts index 3d640f97c3..2cdffdfe12 100644 --- a/packages/playwright/src/reporters/reporterV2.ts +++ b/packages/playwright/src/reporters/reporterV2.ts @@ -17,18 +17,18 @@ import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter, Suite } from '../../types/testReporter'; export interface ReporterV2 { - onConfigure(config: FullConfig): void; - onBegin(suite: Suite): void; - onTestBegin(test: TestCase, result: TestResult): void; - onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult): void; - onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult): void; - onTestEnd(test: TestCase, result: TestResult): void; - onEnd(result: FullResult): Promise<{ status?: FullResult['status'] } | undefined | void> | void; - onExit(): void | Promise; - onError(error: TestError): void; - onStepBegin(test: TestCase, result: TestResult, step: TestStep): void; - onStepEnd(test: TestCase, result: TestResult, step: TestStep): void; - printsToStdio(): boolean; + onConfigure?(config: FullConfig): void; + onBegin?(suite: Suite): void; + onTestBegin?(test: TestCase, result: TestResult): void; + onStdOut?(chunk: string | Buffer, test?: TestCase, result?: TestResult): void; + onStdErr?(chunk: string | Buffer, test?: TestCase, result?: TestResult): void; + onTestEnd?(test: TestCase, result: TestResult): void; + onEnd?(result: FullResult): Promise<{ status?: FullResult['status'] } | undefined | void> | void; + onExit?(): void | Promise; + onError?(error: TestError): void; + onStepBegin?(test: TestCase, result: TestResult, step: TestStep): void; + onStepEnd?(test: TestCase, result: TestResult, step: TestStep): void; + printsToStdio?(): boolean; version(): 'v2'; } diff --git a/packages/playwright/src/reporters/teleEmitter.ts b/packages/playwright/src/reporters/teleEmitter.ts index 670a8c16c8..f56178114d 100644 --- a/packages/playwright/src/reporters/teleEmitter.ts +++ b/packages/playwright/src/reporters/teleEmitter.ts @@ -145,9 +145,6 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - async onExit() { - } - printsToStdio() { return false; } diff --git a/packages/playwright/src/runner/DEPS.list b/packages/playwright/src/runner/DEPS.list index cdf6044844..bc1cc6d763 100644 --- a/packages/playwright/src/runner/DEPS.list +++ b/packages/playwright/src/runner/DEPS.list @@ -7,6 +7,5 @@ ../plugins/ ../util.ts ../utilsBundle.ts -../isomorphic/folders.ts -../isomorphic/teleReceiver.ts +../isomorphic/ ../fsWatcher.ts diff --git a/packages/playwright/src/runner/dispatcher.ts b/packages/playwright/src/runner/dispatcher.ts index ad44ce453e..4e971f2475 100644 --- a/packages/playwright/src/runner/dispatcher.ts +++ b/packages/playwright/src/runner/dispatcher.ts @@ -198,17 +198,17 @@ export class Dispatcher { worker.on('stdOut', (params: TestOutputPayload) => { const { chunk, test, result } = handleOutput(params); result?.stdout.push(chunk); - this._reporter.onStdOut(chunk, test, result); + this._reporter.onStdOut?.(chunk, test, result); }); worker.on('stdErr', (params: TestOutputPayload) => { const { chunk, test, result } = handleOutput(params); result?.stderr.push(chunk); - this._reporter.onStdErr(chunk, test, result); + this._reporter.onStdErr?.(chunk, test, result); }); worker.on('teardownErrors', (params: TeardownErrorsPayload) => { this._failureTracker.onWorkerError(); for (const error of params.fatalErrors) - this._reporter.onError(error); + this._reporter.onError?.(error); }); worker.on('exit', () => { const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {}; @@ -257,7 +257,7 @@ class JobDispatcher { result.parallelIndex = this._parallelIndex; result.workerIndex = this._workerIndex; result.startTime = new Date(params.startWallTime); - this._reporter.onTestBegin(test, result); + this._reporter.onTestBegin?.(test, result); this._currentlyRunning = { test, result }; } @@ -323,7 +323,7 @@ class JobDispatcher { }; steps.set(params.stepId, step); (parentStep || result).steps.push(step); - this._reporter.onStepBegin(test, result, step); + this._reporter.onStepBegin?.(test, result, step); } private _onStepEnd(params: StepEndPayload) { @@ -335,14 +335,14 @@ class JobDispatcher { const { result, steps, test } = data; const step = steps.get(params.stepId); if (!step) { - this._reporter.onStdErr('Internal error: step end without step begin: ' + params.stepId, test, result); + this._reporter.onStdErr?.('Internal error: step end without step begin: ' + params.stepId, test, result); return; } step.duration = params.wallTime - step.startTime.getTime(); if (params.error) step.error = params.error; steps.delete(params.stepId); - this._reporter.onStepEnd(test, result, step); + this._reporter.onStepEnd?.(test, result, step); } private _onAttach(params: AttachmentPayload) { @@ -368,7 +368,7 @@ class JobDispatcher { result = runData.result; } else { result = test._appendTestResult(); - this._reporter.onTestBegin(test, result); + this._reporter.onTestBegin?.(test, result); } result.errors = [...errors]; result.error = result.errors[0]; @@ -392,7 +392,7 @@ class JobDispatcher { // Let's just fail the test run. this._failureTracker.onWorkerError(); for (const error of errors) - this._reporter.onError(error); + this._reporter.onError?.(error); } } @@ -526,7 +526,7 @@ class JobDispatcher { if (allTestsSkipped && !this._failureTracker.hasReachedMaxFailures()) { for (const test of this._job.tests) { const result = test._appendTestResult(); - this._reporter.onTestBegin(test, result); + this._reporter.onTestBegin?.(test, result); result.status = 'skipped'; this._reportTestEnd(test, result); } @@ -540,13 +540,13 @@ class JobDispatcher { } private _reportTestEnd(test: TestCase, result: TestResult) { - this._reporter.onTestEnd(test, result); + this._reporter.onTestEnd?.(test, result); const hadMaxFailures = this._failureTracker.hasReachedMaxFailures(); this._failureTracker.onTestEnd(test, result); if (this._failureTracker.hasReachedMaxFailures()) { this._stopCallback(); if (!hadMaxFailures) - this._reporter.onError({ message: colors.red(`Testing stopped early after ${this._failureTracker.maxFailures()} maximum allowed failures.`) }); + this._reporter.onError?.({ message: colors.red(`Testing stopped early after ${this._failureTracker.maxFailures()} maximum allowed failures.`) }); } } } diff --git a/packages/playwright/src/runner/failureTracker.ts b/packages/playwright/src/runner/failureTracker.ts index 6ea8f81a34..ac62677571 100644 --- a/packages/playwright/src/runner/failureTracker.ts +++ b/packages/playwright/src/runner/failureTracker.ts @@ -31,7 +31,8 @@ export class FailureTracker { } onTestEnd(test: TestCase, result: TestResult) { - if (result.status !== 'skipped' && result.status !== test.expectedStatus) + // Test is considered failing after the last retry. + if (test.outcome() === 'unexpected' && test.results.length > test.retries) ++this._failureCount; } diff --git a/packages/playwright/src/runner/lastRun.ts b/packages/playwright/src/runner/lastRun.ts new file mode 100644 index 0000000000..407543041e --- /dev/null +++ b/packages/playwright/src/runner/lastRun.ts @@ -0,0 +1,71 @@ +/** + * Copyright Microsoft Corporation. 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. + */ + +import fs from 'fs'; +import path from 'path'; +import type { FullResult, Suite } from '../../types/testReporter'; +import { filterProjects } from './projectUtils'; +import type { FullConfigInternal } from '../common/config'; +import type { ReporterV2 } from '../reporters/reporterV2'; + +type LastRunInfo = { + status: FullResult['status']; + failedTests: string[]; +}; + +export class LastRunReporter implements ReporterV2 { + private _config: FullConfigInternal; + private _lastRunFile: string | undefined; + private _suite: Suite | undefined; + + constructor(config: FullConfigInternal) { + this._config = config; + const [project] = filterProjects(config.projects, config.cliProjectFilter); + if (project) + this._lastRunFile = path.join(project.project.outputDir, '.last-run.json'); + } + + async filterLastFailed() { + if (!this._lastRunFile) + return; + try { + const lastRunInfo = JSON.parse(await fs.promises.readFile(this._lastRunFile, 'utf8')) as LastRunInfo; + this._config.testIdMatcher = id => lastRunInfo.failedTests.includes(id); + } catch { + } + } + + version(): 'v2' { + return 'v2'; + } + + printsToStdio() { + return false; + } + + onBegin(suite: Suite) { + this._suite = suite; + } + + async onEnd(result: FullResult) { + if (!this._lastRunFile || this._config.cliListOnly) + return; + await fs.promises.mkdir(path.dirname(this._lastRunFile), { recursive: true }); + const failedTests = this._suite?.allTests().filter(t => !t.ok()).map(t => t.id); + const lastRunReport = JSON.stringify({ status: result.status, failedTests }, undefined, 2); + await fs.promises.writeFile(this._lastRunFile, lastRunReport); + } +} diff --git a/packages/playwright/src/runner/loadUtils.ts b/packages/playwright/src/runner/loadUtils.ts index cd735ceca3..63a2307507 100644 --- a/packages/playwright/src/runner/loadUtils.ts +++ b/packages/playwright/src/runner/loadUtils.ts @@ -33,7 +33,7 @@ import { sourceMapSupport } from '../utilsBundle'; import type { RawSourceMap } from 'source-map'; -export async function collectProjectsAndTestFiles(testRun: TestRun, doNotRunTestsOutsideProjectFilter: boolean, additionalFileMatcher?: Matcher) { +export async function collectProjectsAndTestFiles(testRun: TestRun, doNotRunTestsOutsideProjectFilter: boolean) { const config = testRun.config; const fsCache = new Map(); const sourceMapCache = new Map(); @@ -52,8 +52,6 @@ export async function collectProjectsAndTestFiles(testRun: TestRun, doNotRunTest for (const [project, files] of allFilesForProject) { const matchedFiles = files.filter(file => { const hasMatchingSources = sourceMapSources(file, sourceMapCache).some(source => { - if (additionalFileMatcher && !additionalFileMatcher(source)) - return false; if (cliFileMatcher && !cliFileMatcher(source)) return false; return true; diff --git a/packages/playwright/src/runner/reporters.ts b/packages/playwright/src/runner/reporters.ts index 1748292cc0..2f7b16f2a6 100644 --- a/packages/playwright/src/runner/reporters.ts +++ b/packages/playwright/src/runner/reporters.ts @@ -16,7 +16,7 @@ import path from 'path'; import type { FullConfig, TestError } from '../../types/testReporter'; -import { formatError } from '../reporters/base'; +import { colors, formatError } from '../reporters/base'; import DotReporter from '../reporters/dot'; import EmptyReporter from '../reporters/empty'; import GitHubReporter from '../reporters/github'; @@ -67,7 +67,7 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' | reporters.push(wrapReporterAsV2(new reporterConstructor(runOptions))); } - const someReporterPrintsToStdio = reporters.some(r => r.printsToStdio()); + const someReporterPrintsToStdio = reporters.some(r => r.printsToStdio ? r.printsToStdio() : true); if (reporters.length && !someReporterPrintsToStdio) { // Add a line/dot/list-mode reporter for convenience. // Important to put it first, just in case some other reporter stalls onEnd. @@ -86,6 +86,23 @@ export async function createReporterForTestServer(file: string, messageSink: (me })); } +interface ErrorCollectingReporter extends ReporterV2 { + errors(): TestError[]; +} + +export function createErrorCollectingReporter(writeToConsole?: boolean): ErrorCollectingReporter { + const errors: TestError[] = []; + return { + version: () => 'v2', + onError(error: TestError) { + errors.push(error); + if (writeToConsole) + process.stdout.write(formatError(error, colors.enabled).message + '\n'); + }, + errors: () => errors, + }; +} + function reporterOptions(config: FullConfigInternal, mode: 'list' | 'test' | 'merge', isTestServer: boolean) { return { configDir: config.configDir, @@ -114,14 +131,18 @@ function computeCommandHash(config: FullConfigInternal) { return parts.join('-'); } -class ListModeReporter extends EmptyReporter { +class ListModeReporter implements ReporterV2 { private config!: FullConfig; - override onConfigure(config: FullConfig) { + version(): 'v2' { + return 'v2'; + } + + onConfigure(config: FullConfig) { this.config = config; } - override onBegin(suite: Suite): void { + onBegin(suite: Suite): void { // eslint-disable-next-line no-console console.log(`Listing tests:`); const tests = suite.allTests(); @@ -139,12 +160,8 @@ class ListModeReporter extends EmptyReporter { console.log(`Total: ${tests.length} ${tests.length === 1 ? 'test' : 'tests'} in ${files.size} ${files.size === 1 ? 'file' : 'files'}`); } - override onError(error: TestError) { + onError(error: TestError) { // eslint-disable-next-line no-console console.error('\n' + formatError(error, false).message); } - - override printsToStdio(): boolean { - return true; - } } diff --git a/packages/playwright/src/runner/runner.ts b/packages/playwright/src/runner/runner.ts index a7fd28ec87..923bf36072 100644 --- a/packages/playwright/src/runner/runner.ts +++ b/packages/playwright/src/runner/runner.ts @@ -15,19 +15,15 @@ * limitations under the License. */ -import fs from 'fs'; -import path from 'path'; -import { monotonicTime } from 'playwright-core/lib/utils'; import type { FullResult, TestError } from '../../types/testReporter'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import { collectFilesForProject, filterProjects } from './projectUtils'; -import { createReporters } from './reporters'; -import { TestRun, createTaskRunner, createTaskRunnerForList } from './tasks'; +import { createErrorCollectingReporter, createReporters } from './reporters'; +import { TestRun, createClearCacheTask, createGlobalSetupTasks, createLoadTask, createPluginSetupTasks, createReportBeginTask, createRunTestsTasks, createStartDevServerTask, runTasks } from './tasks'; import type { FullConfigInternal } from '../common/config'; -import { runWatchModeLoop } from './watchMode'; -import type { Suite } from '../common/test'; -import { wrapReporterAsV2 } from '../reporters/reporterV2'; import { affectedTestFiles } from '../transform/compilationCache'; +import { InternalReporter } from '../reporters/internalReporter'; +import { LastRunReporter } from './lastRun'; type ProjectConfigWithFiles = { name: string; @@ -72,33 +68,25 @@ export class Runner { async runAllTests(): Promise { const config = this._config; const listOnly = config.cliListOnly; - const deadline = config.config.globalTimeout ? monotonicTime() + config.config.globalTimeout : 0; // Legacy webServer support. webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); const reporters = await createReporters(config, listOnly ? 'list' : 'test', false); - const taskRunner = listOnly ? createTaskRunnerForList( - config, - reporters, - 'in-process', - { failOnLoadErrors: true }) : createTaskRunner(config, reporters); + const lastRun = new LastRunReporter(config); + if (config.cliLastFailed) + await lastRun.filterLastFailed(); - const testRun = new TestRun(config); - taskRunner.reporter.onConfigure(config.config); - - const taskStatus = await taskRunner.run(testRun, deadline); - let status: FullResult['status'] = testRun.failureTracker.result(); - if (status === 'passed' && taskStatus !== 'passed') - status = taskStatus; - const modifiedResult = await taskRunner.reporter.onEnd({ status }); - if (modifiedResult && modifiedResult.status) - status = modifiedResult.status; - - if (!listOnly) - await writeLastRunInfo(testRun, status); - - await taskRunner.reporter.onExit(); + const reporter = new InternalReporter([...reporters, lastRun]); + const tasks = listOnly ? [ + createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false }), + createReportBeginTask(), + ] : [ + ...createGlobalSetupTasks(config), + createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true }), + ...createRunTestsTasks(config), + ]; + const status = await runTasks(new TestRun(config, reporter), tasks, config.config.globalTimeout); // Calling process.exit() might truncate large stdout/stderr output. // See https://github.com/nodejs/node/issues/6456. @@ -108,74 +96,35 @@ export class Runner { return status; } - async loadAllTests(mode: 'in-process' | 'out-of-process' = 'in-process'): Promise<{ status: FullResult['status'], suite?: Suite, errors: TestError[] }> { - const config = this._config; - const errors: TestError[] = []; - const reporters = [wrapReporterAsV2({ - onError(error: TestError) { - errors.push(error); - } - })]; - const taskRunner = createTaskRunnerForList(config, reporters, mode, { failOnLoadErrors: true }); - const testRun = new TestRun(config); - taskRunner.reporter.onConfigure(config.config); - - const taskStatus = await taskRunner.run(testRun, 0); - let status: FullResult['status'] = testRun.failureTracker.result(); - if (status === 'passed' && taskStatus !== 'passed') - status = taskStatus; - const modifiedResult = await taskRunner.reporter.onEnd({ status }); - if (modifiedResult && modifiedResult.status) - status = modifiedResult.status; - await taskRunner.reporter.onExit(); - return { status, suite: testRun.rootSuite, errors }; + async findRelatedTestFiles(files: string[]): Promise { + const errorReporter = createErrorCollectingReporter(); + const reporter = new InternalReporter([errorReporter]); + const status = await runTasks(new TestRun(this._config, reporter), [ + ...createPluginSetupTasks(this._config), + createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false, populateDependencies: true }), + ]); + if (status !== 'passed') + return { errors: errorReporter.errors(), testFiles: [] }; + return { testFiles: affectedTestFiles(files) }; } - async watchAllTests(): Promise { - const config = this._config; - webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); - return await runWatchModeLoop(config); + async runDevServer() { + const reporter = new InternalReporter([createErrorCollectingReporter(true)]); + const status = await runTasks(new TestRun(this._config, reporter), [ + ...createPluginSetupTasks(this._config), + createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false }), + createStartDevServerTask(), + { title: 'wait until interrupted', setup: async () => new Promise(() => {}) }, + ]); + return { status }; } - async findRelatedTestFiles(mode: 'in-process' | 'out-of-process', files: string[]): Promise { - const result = await this.loadAllTests(mode); - if (result.status !== 'passed' || !result.suite) - return { errors: result.errors, testFiles: [] }; - - const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file)); - const override = (this._config.config as any)['@playwright/test']?.['cli']?.['find-related-test-files']; - if (override) - return await override(resolvedFiles, this._config); - return { testFiles: affectedTestFiles(resolvedFiles) }; + async clearCache() { + const reporter = new InternalReporter([createErrorCollectingReporter(true)]); + const status = await runTasks(new TestRun(this._config, reporter), [ + ...createPluginSetupTasks(this._config), + createClearCacheTask(this._config), + ]); + return { status }; } } - -export type LastRunInfo = { - status: FullResult['status']; - failedTests: string[]; -}; - -async function writeLastRunInfo(testRun: TestRun, status: FullResult['status']) { - const [project] = filterProjects(testRun.config.projects, testRun.config.cliProjectFilter); - if (!project) - return; - const outputDir = project.project.outputDir; - await fs.promises.mkdir(outputDir, { recursive: true }); - const lastRunReportFile = path.join(outputDir, '.last-run.json'); - const failedTests = testRun.rootSuite?.allTests().filter(t => !t.ok()).map(t => t.id); - const lastRunReport = JSON.stringify({ status, failedTests }, undefined, 2); - await fs.promises.writeFile(lastRunReportFile, lastRunReport); -} - -export async function readLastRunInfo(config: FullConfigInternal): Promise { - const [project] = filterProjects(config.projects, config.cliProjectFilter); - if (!project) - return { status: 'passed', failedTests: [] }; - const outputDir = project.project.outputDir; - try { - const lastRunReportFile = path.join(outputDir, '.last-run.json'); - return JSON.parse(await fs.promises.readFile(lastRunReportFile, 'utf8')) as LastRunInfo; - } catch { - } - return { status: 'passed', failedTests: [] }; -} diff --git a/packages/playwright/src/runner/taskRunner.ts b/packages/playwright/src/runner/taskRunner.ts index 96505bef2a..dd29d6b0f8 100644 --- a/packages/playwright/src/runner/taskRunner.ts +++ b/packages/playwright/src/runner/taskRunner.ts @@ -19,32 +19,26 @@ import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils'; import type { FullResult, TestError } from '../../types/testReporter'; import { SigIntWatcher } from './sigIntWatcher'; import { serializeError } from '../util'; -import type { ReporterV2 } from '../reporters/reporterV2'; -import { InternalReporter } from '../reporters/internalReporter'; -import { Multiplexer } from '../reporters/multiplexer'; +import type { InternalReporter } from '../reporters/internalReporter'; -type TaskPhase = (reporter: ReporterV2, context: Context, errors: TestError[], softErrors: TestError[]) => Promise | void; -export type Task = { setup?: TaskPhase, teardown?: TaskPhase }; +type TaskPhase = (context: Context, errors: TestError[], softErrors: TestError[]) => Promise | void; +export type Task = { title: string, setup?: TaskPhase, teardown?: TaskPhase }; export class TaskRunner { - private _tasks: { name: string, task: Task }[] = []; - readonly reporter: InternalReporter; + private _tasks: Task[] = []; + private _reporter: InternalReporter; private _hasErrors = false; private _interrupted = false; private _isTearDown = false; private _globalTimeoutForError: number; - static create(reporters: ReporterV2[], globalTimeoutForError: number = 0) { - return new TaskRunner(createInternalReporter(reporters), globalTimeoutForError); - } - - private constructor(reporter: InternalReporter, globalTimeoutForError: number) { - this.reporter = reporter; + constructor(reporter: InternalReporter, globalTimeoutForError: number) { + this._reporter = reporter; this._globalTimeoutForError = globalTimeoutForError; } - addTask(name: string, task: Task) { - this._tasks.push({ name, task }); + addTask(task: Task) { + this._tasks.push(task); } async run(context: Context, deadline: number, cancelPromise?: ManualPromise): Promise { @@ -56,35 +50,35 @@ export class TaskRunner { async runDeferCleanup(context: Context, deadline: number, cancelPromise = new ManualPromise()): Promise<{ status: FullResult['status'], cleanup: () => Promise }> { const sigintWatcher = new SigIntWatcher(); const timeoutWatcher = new TimeoutWatcher(deadline); - const teardownRunner = new TaskRunner(this.reporter, this._globalTimeoutForError); + const teardownRunner = new TaskRunner(this._reporter, this._globalTimeoutForError); teardownRunner._isTearDown = true; let currentTaskName: string | undefined; const taskLoop = async () => { - for (const { name, task } of this._tasks) { - currentTaskName = name; + for (const task of this._tasks) { + currentTaskName = task.title; if (this._interrupted) break; - debug('pw:test:task')(`"${name}" started`); + debug('pw:test:task')(`"${task.title}" started`); const errors: TestError[] = []; const softErrors: TestError[] = []; try { - teardownRunner._tasks.unshift({ name: `teardown for ${name}`, task: { setup: task.teardown } }); - await task.setup?.(this.reporter, context, errors, softErrors); + teardownRunner._tasks.unshift({ title: `teardown for ${task.title}`, setup: task.teardown }); + await task.setup?.(context, errors, softErrors); } catch (e) { - debug('pw:test:task')(`error in "${name}": `, e); + debug('pw:test:task')(`error in "${task.title}": `, e); errors.push(serializeError(e)); } finally { for (const error of [...softErrors, ...errors]) - this.reporter.onError?.(error); + this._reporter.onError?.(error); if (errors.length) { if (!this._isTearDown) this._interrupted = true; this._hasErrors = true; } } - debug('pw:test:task')(`"${name}" finished`); + debug('pw:test:task')(`"${task.title}" finished`); } }; @@ -105,7 +99,7 @@ export class TaskRunner { if (sigintWatcher.hadSignal() || cancelPromise?.isDone()) { status = 'interrupted'; } else if (timeoutWatcher.timedOut()) { - this.reporter.onError?.({ message: colors.red(`Timed out waiting ${this._globalTimeoutForError / 1000}s for the ${currentTaskName} to run`) }); + this._reporter.onError?.({ message: colors.red(`Timed out waiting ${this._globalTimeoutForError / 1000}s for the ${currentTaskName} to run`) }); status = 'timedout'; } else if (this._hasErrors) { status = 'failed'; @@ -146,7 +140,3 @@ class TimeoutWatcher { clearTimeout(this._timer); } } - -function createInternalReporter(reporters: ReporterV2[]): InternalReporter { - return new InternalReporter(new Multiplexer(reporters)); -} diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts index 73eb31f1e7..77d84419f4 100644 --- a/packages/playwright/src/runner/tasks.ts +++ b/packages/playwright/src/runner/tasks.ts @@ -18,20 +18,22 @@ import fs from 'fs'; import path from 'path'; import { promisify } from 'util'; import { debug } from 'playwright-core/lib/utilsBundle'; -import { removeFolders } from 'playwright-core/lib/utils'; +import { type ManualPromise, monotonicTime, removeFolders } from 'playwright-core/lib/utils'; import { Dispatcher, type EnvByProjectId } from './dispatcher'; import type { TestRunnerPluginRegistration } from '../plugins'; -import type { ReporterV2 } from '../reporters/reporterV2'; import { createTestGroups, type TestGroup } from '../runner/testGroups'; import type { Task } from './taskRunner'; import { TaskRunner } from './taskRunner'; import type { FullConfigInternal, FullProjectInternal } from '../common/config'; import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGlobalHook } from './loadUtils'; -import type { Matcher } from '../util'; +import { removeDirAndLogToConsole, type Matcher } from '../util'; import { Suite } from '../common/test'; import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils'; import { FailureTracker } from './failureTracker'; import { detectChangedTestFiles } from './vcs'; +import type { InternalReporter } from '../reporters/internalReporter'; +import { cacheDir } from '../transform/compilationCache'; +import type { FullResult } from '../../types/testReporter'; const readDirAsync = promisify(fs.readdir); @@ -41,97 +43,100 @@ type ProjectWithTestGroups = { testGroups: TestGroup[]; }; -export type Phase = { +type Phase = { dispatcher: Dispatcher, projects: ProjectWithTestGroups[] }; export class TestRun { readonly config: FullConfigInternal; + readonly reporter: InternalReporter; readonly failureTracker: FailureTracker; rootSuite: Suite | undefined = undefined; readonly phases: Phase[] = []; projectFiles: Map = new Map(); projectSuites: Map = new Map(); - constructor(config: FullConfigInternal) { + constructor(config: FullConfigInternal, reporter: InternalReporter) { this.config = config; + this.reporter = reporter; this.failureTracker = new FailureTracker(config); } } -export function createTaskRunner(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner { - const taskRunner = TaskRunner.create(reporters, config.config.globalTimeout); - addGlobalSetupTasks(taskRunner, config); - taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true })); - addRunTasks(taskRunner, config); - return taskRunner; +export async function runTasks(testRun: TestRun, tasks: Task[], globalTimeout?: number, cancelPromise?: ManualPromise) { + const deadline = globalTimeout ? monotonicTime() + globalTimeout : 0; + const taskRunner = new TaskRunner(testRun.reporter, globalTimeout || 0); + for (const task of tasks) + taskRunner.addTask(task); + testRun.reporter.onConfigure(testRun.config.config); + const status = await taskRunner.run(testRun, deadline, cancelPromise); + return await finishTaskRun(testRun, status); } -export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner { - const taskRunner = TaskRunner.create(reporters); - addGlobalSetupTasks(taskRunner, config); - return taskRunner; +export async function runTasksDeferCleanup(testRun: TestRun, tasks: Task[]) { + const taskRunner = new TaskRunner(testRun.reporter, 0); + for (const task of tasks) + taskRunner.addTask(task); + testRun.reporter.onConfigure(testRun.config.config); + const { status, cleanup } = await taskRunner.runDeferCleanup(testRun, 0); + return { status: await finishTaskRun(testRun, status), cleanup }; } -export function createTaskRunnerForWatch(config: FullConfigInternal, reporters: ReporterV2[], additionalFileMatcher?: Matcher): TaskRunner { - const taskRunner = TaskRunner.create(reporters); - taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true, additionalFileMatcher })); - addRunTasks(taskRunner, config); - return taskRunner; +async function finishTaskRun(testRun: TestRun, status: FullResult['status']) { + if (status === 'passed') + status = testRun.failureTracker.result(); + const modifiedResult = await testRun.reporter.onEnd({ status }); + if (modifiedResult && modifiedResult.status) + status = modifiedResult.status; + await testRun.reporter.onExit(); + return status; } -export function createTaskRunnerForTestServer(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner { - const taskRunner = TaskRunner.create(reporters); - taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true })); - addRunTasks(taskRunner, config); - return taskRunner; -} - -function addGlobalSetupTasks(taskRunner: TaskRunner, config: FullConfigInternal) { +export function createGlobalSetupTasks(config: FullConfigInternal) { + const tasks: Task[] = []; if (!config.configCLIOverrides.preserveOutputDir && !process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS) - taskRunner.addTask('clear output', createRemoveOutputDirsTask()); - for (const plugin of config.plugins) - taskRunner.addTask('plugin setup', createPluginSetupTask(plugin)); + tasks.push(createRemoveOutputDirsTask()); + tasks.push(...createPluginSetupTasks(config)); if (config.config.globalSetup || config.config.globalTeardown) - taskRunner.addTask('global setup', createGlobalSetupTask()); + tasks.push(createGlobalSetupTask()); + return tasks; } -function addRunTasks(taskRunner: TaskRunner, config: FullConfigInternal) { - taskRunner.addTask('create phases', createPhasesTask()); - taskRunner.addTask('report begin', createReportBeginTask()); - for (const plugin of config.plugins) - taskRunner.addTask('plugin begin', createPluginBeginTask(plugin)); - taskRunner.addTask('test suite', createRunTestsTask()); - return taskRunner; +export function createRunTestsTasks(config: FullConfigInternal) { + return [ + createPhasesTask(), + createReportBeginTask(), + ...config.plugins.map(plugin => createPluginBeginTask(plugin)), + createRunTestsTask(), + ]; } -export function createTaskRunnerForList(config: FullConfigInternal, reporters: ReporterV2[], mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner { - const taskRunner = TaskRunner.create(reporters, config.config.globalTimeout); - taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false })); - taskRunner.addTask('report begin', createReportBeginTask()); - return taskRunner; -} - -export function createTaskRunnerForListFiles(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner { - const taskRunner = TaskRunner.create(reporters, config.config.globalTimeout); - taskRunner.addTask('load tests', createListFilesTask()); - taskRunner.addTask('report begin', createReportBeginTask()); - return taskRunner; -} - -function createReportBeginTask(): Task { +export function createClearCacheTask(config: FullConfigInternal): Task { return { - setup: async (reporter, { rootSuite }) => { - reporter.onBegin(rootSuite!); + title: 'clear cache', + setup: async () => { + await removeDirAndLogToConsole(cacheDir); + for (const plugin of config.plugins) + await plugin.instance?.clearCache?.(); + }, + }; +} + +export function createReportBeginTask(): Task { + return { + title: 'report begin', + setup: async testRun => { + testRun.reporter.onBegin?.(testRun.rootSuite!); }, teardown: async ({}) => {}, }; } -function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task { - return { - setup: async (reporter, { config }) => { +export function createPluginSetupTasks(config: FullConfigInternal): Task[] { + return config.plugins.map(plugin => ({ + title: 'plugin setup', + setup: async ({ reporter }) => { if (typeof plugin.factory === 'function') plugin.instance = await plugin.factory(); else @@ -141,13 +146,14 @@ function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task { await plugin.instance?.teardown?.(); }, - }; + })); } function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task { return { - setup: async (reporter, { rootSuite }) => { - await plugin.instance?.begin?.(rootSuite!); + title: 'plugin begin', + setup: async testRun => { + await plugin.instance?.begin?.(testRun.rootSuite!); }, teardown: async () => { await plugin.instance?.end?.(); @@ -160,13 +166,14 @@ function createGlobalSetupTask(): Task { let globalSetupFinished = false; let teardownHook: any; return { - setup: async (reporter, { config }) => { + title: 'global setup', + setup: async ({ config }) => { const setupHook = config.config.globalSetup ? await loadGlobalHook(config, config.config.globalSetup) : undefined; teardownHook = config.config.globalTeardown ? await loadGlobalHook(config, config.config.globalTeardown) : undefined; globalSetupResult = setupHook ? await setupHook(config.config) : undefined; globalSetupFinished = true; }, - teardown: async (reporter, { config }) => { + teardown: async ({ config }) => { if (typeof globalSetupResult === 'function') await globalSetupResult(); if (globalSetupFinished) @@ -177,7 +184,8 @@ function createGlobalSetupTask(): Task { function createRemoveOutputDirsTask(): Task { return { - setup: async (reporter, { config }) => { + title: 'clear output', + setup: async ({ config }) => { const outputDirs = new Set(); const projects = filterProjects(config.projects, config.cliProjectFilter); projects.forEach(p => outputDirs.add(p.project.outputDir)); @@ -199,9 +207,10 @@ function createRemoveOutputDirsTask(): Task { }; } -function createListFilesTask(): Task { +export function createListFilesTask(): Task { return { - setup: async (reporter, testRun, errors) => { + title: 'load tests', + setup: async (testRun, errors) => { testRun.rootSuite = await createRootSuite(testRun, errors, false); testRun.failureTracker.onRootSuite(testRun.rootSuite); await collectProjectsAndTestFiles(testRun, false); @@ -222,16 +231,20 @@ function createListFilesTask(): Task { }; } -function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, additionalFileMatcher?: Matcher }): Task { +export function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, populateDependencies?: boolean }): Task { return { - setup: async (reporter, testRun, errors, softErrors) => { - await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter, options.additionalFileMatcher); + title: 'load tests', + setup: async (testRun, errors, softErrors) => { + await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter); await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors); + if (testRun.config.cliOnlyChanged || options.populateDependencies) { + for (const plugin of testRun.config.plugins) + await plugin.instance?.populateDependencies?.(); + } + let cliOnlyChangedMatcher: Matcher | undefined = undefined; if (testRun.config.cliOnlyChanged) { - for (const plugin of testRun.config.plugins) - await plugin.instance?.populateDependencies?.(); const changedFiles = await detectChangedTestFiles(testRun.config.cliOnlyChanged, testRun.config.configDir); cliOnlyChangedMatcher = file => changedFiles.has(file); } @@ -239,7 +252,7 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filter testRun.rootSuite = await createRootSuite(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly, cliOnlyChangedMatcher); testRun.failureTracker.onRootSuite(testRun.rootSuite); // Fail when no tests. - if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard) { + if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard && !testRun.config.cliOnlyChanged) { if (testRun.config.cliArgs.length) { throw new Error([ `No tests found.`, @@ -255,7 +268,8 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filter function createPhasesTask(): Task { return { - setup: async (reporter, testRun) => { + title: 'create phases', + setup: async testRun => { let maxConcurrentTestGroups = 0; const processed = new Set(); @@ -286,7 +300,7 @@ function createPhasesTask(): Task { processed.add(project); if (phaseProjects.length) { let testGroupsInPhase = 0; - const phase: Phase = { dispatcher: new Dispatcher(testRun.config, reporter, testRun.failureTracker), projects: [] }; + const phase: Phase = { dispatcher: new Dispatcher(testRun.config, testRun.reporter, testRun.failureTracker), projects: [] }; testRun.phases.push(phase); for (const project of phaseProjects) { const projectSuite = projectToSuite.get(project)!; @@ -306,7 +320,8 @@ function createPhasesTask(): Task { function createRunTestsTask(): Task { return { - setup: async (reporter, { phases, failureTracker }) => { + title: 'test suite', + setup: async ({ phases, failureTracker }) => { const successfulProjects = new Set(); const extraEnvByProjectId: EnvByProjectId = new Map(); const teardownToSetups = buildTeardownToSetupsMap(phases.map(phase => phase.projects.map(p => p.project)).flat()); @@ -350,9 +365,32 @@ function createRunTestsTask(): Task { } } }, - teardown: async (reporter, { phases }) => { + teardown: async ({ phases }) => { for (const { dispatcher } of phases.reverse()) await dispatcher.stop(); }, }; } + +export function createStartDevServerTask(): Task { + return { + title: 'start dev server', + setup: async ({ config }, errors, softErrors) => { + if (config.plugins.some(plugin => !!plugin.devServerCleanup)) { + errors.push({ message: `DevServer is already running` }); + return; + } + for (const plugin of config.plugins) + plugin.devServerCleanup = await plugin.instance?.startDevServer?.(); + if (!config.plugins.some(plugin => !!plugin.devServerCleanup)) + errors.push({ message: `DevServer is not available in the package you are using. Did you mean to use component testing?` }); + }, + + teardown: async ({ config }) => { + for (const plugin of config.plugins) { + await plugin.devServerCleanup?.(); + plugin.devServerCleanup = undefined; + } + }, + }; +} diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 5a498337af..f34a7314f1 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -20,25 +20,24 @@ import { installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils'; import type { Transport, HttpServer } from 'playwright-core/lib/utils'; import type * as reporterTypes from '../../types/testReporter'; -import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache'; +import { affectedTestFiles, collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache'; import type { ConfigLocation, FullConfigInternal } from '../common/config'; -import { createReporterForTestServer, createReporters } from './reporters'; -import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles } from './tasks'; +import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters'; +import { TestRun, runTasks, createLoadTask, createRunTestsTasks, createReportBeginTask, createListFilesTask, runTasksDeferCleanup, createClearCacheTask, createGlobalSetupTasks, createStartDevServerTask } from './tasks'; import { open } from 'playwright-core/lib/utilsBundle'; import ListReporter from '../reporters/list'; import { SigIntWatcher } from './sigIntWatcher'; import { Watcher } from '../fsWatcher'; import type { ReportEntry, TestServerInterface, TestServerInterfaceEventEmitters } from '../isomorphic/testServerInterface'; -import { Runner } from './runner'; import type { ConfigCLIOverrides } from '../common/ipc'; import { loadConfig, resolveConfigLocation, restartWithExperimentalTsEsm } from '../common/configLoader'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playwright-core/lib/server/trace/viewer/traceViewer'; import type { TestRunnerPluginRegistration } from '../plugins'; import { serializeError } from '../util'; -import { cacheDir } from '../transform/compilationCache'; import { baseFullConfig } from '../isomorphic/teleReceiver'; import { InternalReporter } from '../reporters/internalReporter'; +import type { ReporterV2 } from '../reporters/reporterV2'; const originalStdoutWrite = process.stdout.write; const originalStderrWrite = process.stderr.write; @@ -62,7 +61,7 @@ class TestServer { } } -class TestServerDispatcher implements TestServerInterface { +export class TestServerDispatcher implements TestServerInterface { private _configLocation: ConfigLocation; private _watcher: Watcher; @@ -74,12 +73,12 @@ class TestServerDispatcher implements TestServerInterface { readonly transport: Transport; private _queue = Promise.resolve(); private _globalSetup: { cleanup: () => Promise, report: ReportEntry[] } | undefined; + private _devServer: { cleanup: () => Promise, report: ReportEntry[] } | undefined; readonly _dispatchEvent: TestServerInterfaceEventEmitters['dispatchEvent']; private _plugins: TestRunnerPluginRegistration[] | undefined; private _serializer = require.resolve('./uiModeReporter'); private _watchTestDirs = false; private _closeOnDisconnect = false; - private _devServerHandle: (() => Promise) | undefined; constructor(configLocation: ConfigLocation) { this._configLocation = configLocation; @@ -102,26 +101,18 @@ class TestServerDispatcher implements TestServerInterface { return await createReporterForTestServer(this._serializer, messageSink); } - private async _collectingReporter() { + private async _collectingInternalReporter(...extraReporters: ReporterV2[]) { const report: ReportEntry[] = []; const collectingReporter = await createReporterForTestServer(this._serializer, e => report.push(e)); - return { collectingReporter, report }; - } - - private async _collectingInternalReporter() { - const { collectingReporter, report } = await this._collectingReporter(); - return { reporter: new InternalReporter(collectingReporter), report }; + return { reporter: new InternalReporter([collectingReporter, ...extraReporters]), report }; } async initialize(params: Parameters[0]): ReturnType { - if (params.serializer) - this._serializer = params.serializer; - if (params.closeOnDisconnect) - this._closeOnDisconnect = true; - if (params.interceptStdio) - await this._setInterceptStdio(true); - if (params.watchTestDirs) - this._watchTestDirs = true; + // Note: this method can be called multiple times, for example from a new connection after UI mode reload. + this._serializer = params.serializer || require.resolve('./uiModeReporter'); + this._closeOnDisconnect = !!params.closeOnDisconnect; + await this._setInterceptStdio(!!params.interceptStdio); + this._watchTestDirs = !!params.watchTestDirs; } async ping() {} @@ -151,30 +142,21 @@ class TestServerDispatcher implements TestServerInterface { async runGlobalSetup(params: Parameters[0]): ReturnType { await this.runGlobalTeardown(); - const { config, error } = await this._loadConfig(); - if (!config) { - const { reporter, report } = await this._collectingInternalReporter(); - // Produce dummy config when it has an error. - reporter.onConfigure(baseFullConfig); - reporter.onError(error!); - await reporter.onExit(); + const overrides: ConfigCLIOverrides = { + outputDir: params.outputDir, + }; + const { reporter, report } = await this._collectingInternalReporter(new ListReporter()); + const config = await this._loadConfigOrReportError(reporter, overrides); + if (!config) return { status: 'failed', report }; - } - webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); - const { collectingReporter, report } = await this._collectingReporter(); - const listReporter = new ListReporter(); - const taskRunner = createTaskRunnerForWatchSetup(config, [collectingReporter, listReporter]); - taskRunner.reporter.onConfigure(config.config); - const testRun = new TestRun(config); - const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0); - await taskRunner.reporter.onEnd({ status }); - await taskRunner.reporter.onExit(); - if (status !== 'passed') { - await globalCleanup(); - return { report, status }; - } - this._globalSetup = { cleanup: globalCleanup, report }; + const { status, cleanup } = await runTasksDeferCleanup(new TestRun(config, reporter), [ + ...createGlobalSetupTasks(config), + ]); + if (status !== 'passed') + await cleanup(); + else + this._globalSetup = { cleanup, report }; return { report, status }; } @@ -186,87 +168,82 @@ class TestServerDispatcher implements TestServerInterface { } async startDevServer(params: Parameters[0]): ReturnType { - if (this._devServerHandle) - return { status: 'failed', report: [] }; + await this.stopDevServer({}); + const { reporter, report } = await this._collectingInternalReporter(); - const { config, error } = await this._loadConfig(); - if (!config) { - reporter.onError(error!); - return { status: 'failed', report }; - } - const devServerCommand = (config.config as any)['@playwright/test']?.['cli']?.['dev-server']; - if (!devServerCommand) { - reporter.onError({ message: 'No dev-server command found in the configuration' }); - return { status: 'failed', report }; - } - try { - this._devServerHandle = await devServerCommand(config); - return { status: 'passed', report }; - } catch (e) { - reporter.onError(serializeError(e)); - return { status: 'failed', report }; - } + const config = await this._loadConfigOrReportError(reporter); + if (!config) + return { report, status: 'failed' }; + + const { status, cleanup } = await runTasksDeferCleanup(new TestRun(config, reporter), [ + createLoadTask('out-of-process', { failOnLoadErrors: true, filterOnly: false }), + createStartDevServerTask(), + ]); + if (status !== 'passed') + await cleanup(); + else + this._devServer = { cleanup, report }; + return { report, status }; } async stopDevServer(params: Parameters[0]): ReturnType { - if (!this._devServerHandle) - return { status: 'failed', report: [] }; - try { - await this._devServerHandle(); - this._devServerHandle = undefined; - return { status: 'passed', report: [] }; - } catch (e) { - const { reporter, report } = await this._collectingInternalReporter(); - reporter.onError(serializeError(e)); - return { status: 'failed', report }; - } + const devServer = this._devServer; + const status = await devServer?.cleanup(); + this._devServer = undefined; + return { status, report: devServer?.report || [] }; } async clearCache(params: Parameters[0]): ReturnType { - const { config } = await this._loadConfig(); - if (config) - await clearCacheAndLogToConsole(config); + const reporter = new InternalReporter([]); + const config = await this._loadConfigOrReportError(reporter); + if (!config) + return; + await runTasks(new TestRun(config, reporter), [ + createClearCacheTask(config), + ]); } async listFiles(params: Parameters[0]): ReturnType { - const { config, error } = await this._loadConfig(); - if (!config) { - const { reporter, report } = await this._collectingInternalReporter(); - reporter.onError(error!); + const { reporter, report } = await this._collectingInternalReporter(); + const config = await this._loadConfigOrReportError(reporter); + if (!config) return { status: 'failed', report }; - } - const { collectingReporter, report } = await this._collectingReporter(); config.cliProjectFilter = params.projects?.length ? params.projects : undefined; - const taskRunner = createTaskRunnerForListFiles(config, [collectingReporter]); - taskRunner.reporter.onConfigure(config.config); - const testRun = new TestRun(config); - const status = await taskRunner.run(testRun, 0); - await taskRunner.reporter.onEnd({ status }); - await taskRunner.reporter.onExit(); + const status = await runTasks(new TestRun(config, reporter), [ + createListFilesTask(), + createReportBeginTask(), + ]); return { report, status }; } async listTests(params: Parameters[0]): ReturnType { let result: Awaited>; this._queue = this._queue.then(async () => { - result = await this._innerListTests(params); + const { config, report, status } = await this._innerListTests(params); + if (config) + await this._updateWatchedDirs(config); + result = { report, status }; }).catch(printInternalError); await this._queue; return result!; } - private async _innerListTests(params: Parameters[0]): ReturnType { + private async _innerListTests(params: Parameters[0]): Promise<{ + report: ReportEntry[], + reporter: InternalReporter, + status: reporterTypes.FullResult['status'], + config?: FullConfigInternal, + }> { const overrides: ConfigCLIOverrides = { repeatEach: 1, retries: 0, + outputDir: params.outputDir, }; - const { config, error } = await this._loadConfig(overrides); - if (!config) { - const { reporter, report } = await this._collectingInternalReporter(); - reporter.onError(error!); - return { report, status: 'failed' }; - } + const { reporter, report } = await this._collectingInternalReporter(); + const config = await this._loadConfigOrReportError(reporter, overrides); + if (!config) + return { report, reporter, status: 'failed' }; config.cliArgs = params.locations || []; config.cliGrep = params.grep; @@ -274,14 +251,14 @@ class TestServerDispatcher implements TestServerInterface { config.cliProjectFilter = params.projects?.length ? params.projects : undefined; config.cliListOnly = true; - const { collectingReporter, report } = await this._collectingReporter(); - const taskRunner = createTaskRunnerForList(config, [collectingReporter], 'out-of-process', { failOnLoadErrors: false }); - const testRun = new TestRun(config); - taskRunner.reporter.onConfigure(config.config); - const status = await taskRunner.run(testRun, 0); - await taskRunner.reporter.onEnd({ status }); - await taskRunner.reporter.onExit(); + const status = await runTasks(new TestRun(config, reporter), [ + createLoadTask('out-of-process', { failOnLoadErrors: false, filterOnly: false }), + createReportBeginTask(), + ]); + return { config, report, reporter, status }; + } + private async _updateWatchedDirs(config: FullConfigInternal) { this._watchedProjectDirs = new Set(); this._ignoredProjectOutputs = new Set(); for (const p of config.projects) { @@ -296,11 +273,10 @@ class TestServerDispatcher implements TestServerInterface { } if (this._watchTestDirs) - await this.updateWatcher(false); - return { report, status }; + await this._updateWatcher(false); } - private async updateWatcher(reportPending: boolean) { + private async _updateWatcher(reportPending: boolean) { await this._watcher.update([...this._watchedProjectDirs, ...this._watchedTestDependencies], [...this._ignoredProjectOutputs], reportPending); } @@ -337,12 +313,10 @@ class TestServerDispatcher implements TestServerInterface { else process.env.PW_LIVE_TRACE_STACKS = undefined; - const { config, error } = await this._loadConfig(overrides); - if (!config) { - const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e)); - wireReporter.onError(error!); + const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e)); + const config = await this._loadConfigOrReportError(new InternalReporter([wireReporter]), overrides); + if (!config) return { status: 'failed' }; - } const testIdSet = params.testIds ? new Set(params.testIds) : null; config.cliListOnly = false; @@ -353,16 +327,14 @@ class TestServerDispatcher implements TestServerInterface { config.cliProjectFilter = params.projects?.length ? params.projects : undefined; config.testIdMatcher = testIdSet ? id => testIdSet.has(id) : undefined; - const reporters = await createReporters(config, 'test', true); - const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e)); - reporters.push(wireReporter); - const taskRunner = createTaskRunnerForTestServer(config, reporters); - const testRun = new TestRun(config); - taskRunner.reporter.onConfigure(config.config); + const configReporters = await createReporters(config, 'test', true); + const reporter = new InternalReporter([...configReporters, wireReporter]); const stop = new ManualPromise(); - const run = taskRunner.run(testRun, 0, stop).then(async status => { - await taskRunner.reporter.onEnd({ status }); - await taskRunner.reporter.onExit(); + const tasks = [ + createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }), + ...createRunTestsTasks(config), + ]; + const run = runTasks(new TestRun(config, reporter), tasks, 0, stop).then(async status => { this._testRun = undefined; return status; }); @@ -376,15 +348,21 @@ class TestServerDispatcher implements TestServerInterface { this._watchedTestDependencies.add(fileName); dependenciesForTestFile(fileName).forEach(file => this._watchedTestDependencies.add(file)); } - await this.updateWatcher(true); + await this._updateWatcher(true); } async findRelatedTestFiles(params: Parameters[0]): ReturnType { - const { config, error } = await this._loadConfig(); - if (error) - return { testFiles: [], errors: [error] }; - const runner = new Runner(config!); - return runner.findRelatedTestFiles('out-of-process', params.files); + const errorReporter = createErrorCollectingReporter(); + const reporter = new InternalReporter([errorReporter]); + const config = await this._loadConfigOrReportError(reporter); + if (!config) + return { errors: errorReporter.errors(), testFiles: [] }; + const status = await runTasks(new TestRun(config, reporter), [ + createLoadTask('out-of-process', { failOnLoadErrors: true, filterOnly: false, populateDependencies: true }), + ]); + if (status !== 'passed') + return { errors: errorReporter.errors(), testFiles: [] }; + return { testFiles: affectedTestFiles(params.files) }; } async stopTests() { @@ -418,15 +396,29 @@ class TestServerDispatcher implements TestServerInterface { try { const config = await loadConfig(this._configLocation, overrides); // Preserve plugin instances between setup and build. - if (!this._plugins) + if (!this._plugins) { + webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); this._plugins = config.plugins || []; - else + } else { config.plugins.splice(0, config.plugins.length, ...this._plugins); + } return { config }; } catch (e) { return { config: null, error: serializeError(e) }; } } + + private async _loadConfigOrReportError(reporter: InternalReporter, overrides?: ConfigCLIOverrides): Promise { + const { config, error } = await this._loadConfig(overrides); + if (config) + return config; + // Produce dummy config when it has an error. + reporter.onConfigure(baseFullConfig); + reporter.onError(error!); + await reporter.onEnd({ status: 'failed' }); + await reporter.onExit(); + return null; + } } export async function runUIMode(configFile: string | undefined, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise { @@ -520,23 +512,3 @@ export async function resolveCtDirs(config: FullConfigInternal) { templateDir }; } - -export async function clearCacheAndLogToConsole(config: FullConfigInternal) { - const override = (config.config as any)['@playwright/test']?.['cli']?.['clear-cache']; - if (override) { - await override(config); - return; - } - await removeFolderAndLogToConsole(cacheDir); -} - -export async function removeFolderAndLogToConsole(folder: string) { - try { - if (!fs.existsSync(folder)) - return; - // eslint-disable-next-line no-console - console.log(`Removing ${await fs.promises.realpath(folder)}`); - await fs.promises.rm(folder, { recursive: true, force: true }); - } catch { - } -} diff --git a/packages/playwright/src/runner/vcs.ts b/packages/playwright/src/runner/vcs.ts index 707d820ed5..6f7ed55c9a 100644 --- a/packages/playwright/src/runner/vcs.ts +++ b/packages/playwright/src/runner/vcs.ts @@ -30,7 +30,7 @@ export async function detectChangedTestFiles(baseCommit: string, configDir: stri const unknownRevision = error.output.some(line => line?.includes('unknown revision')); if (unknownRevision) { - const isShallowClone = childProcess.execSync('git rev-parse --is-shallow-repository', { encoding: 'utf-8', stdio: 'pipe' }).trim() === 'true'; + const isShallowClone = childProcess.execSync('git rev-parse --is-shallow-repository', { encoding: 'utf-8', stdio: 'pipe', cwd: configDir }).trim() === 'true'; if (isShallowClone) { throw new Error([ `The repository is a shallow clone and does not have '${baseCommit}' available locally.`, diff --git a/packages/playwright/src/runner/watchMode.ts b/packages/playwright/src/runner/watchMode.ts index 709e39100b..ba2c5a34e7 100644 --- a/packages/playwright/src/runner/watchMode.ts +++ b/packages/playwright/src/runner/watchMode.ts @@ -15,130 +15,132 @@ */ import readline from 'readline'; +import path from 'path'; import { createGuid, getPackageManagerExecCommand, ManualPromise } from 'playwright-core/lib/utils'; -import type { FullConfigInternal, FullProjectInternal } from '../common/config'; -import { createFileMatcher, createFileMatcherFromArguments } from '../util'; -import type { Matcher } from '../util'; -import { TestRun, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks'; -import { buildProjectsClosure, filterProjects } from './projectUtils'; -import { collectAffectedTestFiles } from '../transform/compilationCache'; +import type { ConfigLocation } from '../common/config'; import type { FullResult } from '../../types/testReporter'; -import { chokidar } from '../utilsBundle'; -import type { FSWatcher as CFSWatcher } from 'chokidar'; import { colors } from 'playwright-core/lib/utilsBundle'; import { enquirer } from '../utilsBundle'; import { separator } from '../reporters/base'; import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer'; -import ListReporter from '../reporters/list'; +import { TestServerDispatcher } from './testServer'; +import { EventEmitter } from 'stream'; +import { type TestServerTransport, TestServerConnection } from '../isomorphic/testServerConnection'; +import { TeleSuiteUpdater } from '../isomorphic/teleSuiteUpdater'; +import { restartWithExperimentalTsEsm } from '../common/configLoader'; -class FSWatcher { - private _dirtyTestFiles = new Map>(); - private _notifyDirtyFiles: (() => void) | undefined; - private _watcher: CFSWatcher | undefined; - private _timer: NodeJS.Timeout | undefined; - - async update(config: FullConfigInternal) { - const commandLineFileMatcher = config.cliArgs.length ? createFileMatcherFromArguments(config.cliArgs) : () => true; - const projects = filterProjects(config.projects, config.cliProjectFilter); - const projectClosure = buildProjectsClosure(projects); - const projectFilters = new Map(); - for (const [project, type] of projectClosure) { - const testMatch = createFileMatcher(project.project.testMatch); - const testIgnore = createFileMatcher(project.project.testIgnore); - projectFilters.set(project, file => { - if (!file.startsWith(project.project.testDir) || !testMatch(file) || testIgnore(file)) - return false; - return type === 'dependency' || commandLineFileMatcher(file); - }); - } - - if (this._timer) - clearTimeout(this._timer); - if (this._watcher) - await this._watcher.close(); - - this._watcher = chokidar.watch([...projectClosure.keys()].map(p => p.project.testDir), { ignoreInitial: true }).on('all', async (event, file) => { - if (event !== 'add' && event !== 'change') - return; - - const testFiles = new Set(); - collectAffectedTestFiles(file, testFiles); - const testFileArray = [...testFiles]; - - let hasMatches = false; - for (const [project, filter] of projectFilters) { - const filteredFiles = testFileArray.filter(filter); - if (!filteredFiles.length) - continue; - let set = this._dirtyTestFiles.get(project); - if (!set) { - set = new Set(); - this._dirtyTestFiles.set(project, set); - } - filteredFiles.map(f => set!.add(f)); - hasMatches = true; - } - - if (!hasMatches) - return; - - if (this._timer) - clearTimeout(this._timer); - this._timer = setTimeout(() => { - this._notifyDirtyFiles?.(); - }, 250); - }); +class InMemoryTransport extends EventEmitter implements TestServerTransport { + public readonly _send: (data: string) => void; + constructor(send: (data: any) => void) { + super(); + this._send = send; } - async onDirtyTestFiles(): Promise { - if (this._dirtyTestFiles.size) - return; - await new Promise(f => this._notifyDirtyFiles = f); + close() { + this.emit('close'); } - takeDirtyTestFiles(): Map> { - const result = this._dirtyTestFiles; - this._dirtyTestFiles = new Map(); - return result; + onclose(listener: () => void): void { + this.on('close', listener); + } + + onerror(listener: () => void): void { + // no-op to fulfil the interface, the user of InMemoryTransport doesn't emit any errors. + } + + onmessage(listener: (message: string) => void): void { + this.on('message', listener); + } + + onopen(listener: () => void): void { + this.on('open', listener); + } + + send(data: string): void { + this._send(data); } } -export async function runWatchModeLoop(config: FullConfigInternal): Promise { - // Reset the settings that don't apply to watch. - config.cliPassWithNoTests = true; - for (const p of config.projects) - p.project.retries = 0; +interface WatchModeOptions { + files?: string[]; + projects?: string[]; + grep?: string; +} - // Perform global setup. - const testRun = new TestRun(config); - const taskRunner = createTaskRunnerForWatchSetup(config, [new ListReporter()]); - taskRunner.reporter.onConfigure(config.config); - const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0); - if (status !== 'passed') - await globalCleanup(); - await taskRunner.reporter.onEnd({ status }); - await taskRunner.reporter.onExit(); - if (status !== 'passed') - return status; +export async function runWatchModeLoop(configLocation: ConfigLocation, initialOptions: WatchModeOptions): Promise { + if (restartWithExperimentalTsEsm(undefined, true)) + return 'restarted'; - // Prepare projects that will be watched, set up watcher. - const failedTestIdCollector = new Set(); - const originalWorkers = config.config.workers; - const fsWatcher = new FSWatcher(); - await fsWatcher.update(config); + const options: WatchModeOptions = { ...initialOptions }; - let lastRun: { type: 'changed' | 'regular' | 'failed', failedTestIds?: Set, dirtyTestFiles?: Map> } = { type: 'regular' }; + const testServerDispatcher = new TestServerDispatcher(configLocation); + const transport = new InMemoryTransport( + async data => { + const { id, method, params } = JSON.parse(data); + try { + const result = await testServerDispatcher.transport.dispatch(method, params); + transport.emit('message', JSON.stringify({ id, result })); + } catch (e) { + transport.emit('message', JSON.stringify({ id, error: String(e) })); + } + } + ); + testServerDispatcher.transport.sendEvent = (method, params) => { + transport.emit('message', JSON.stringify({ method, params })); + }; + const testServerConnection = new TestServerConnection(transport); + transport.emit('open'); + + const teleSuiteUpdater = new TeleSuiteUpdater({ pathSeparator: path.sep, onUpdate() { } }); + + const dirtyTestIds = new Set(); + let onDirtyTests = new ManualPromise(); + + let queue = Promise.resolve(); + const changedFiles = new Set(); + testServerConnection.onTestFilesChanged(({ testFiles }) => { + testFiles.forEach(file => changedFiles.add(file)); + + queue = queue.then(async () => { + if (changedFiles.size === 0) + return; + + const { report } = await testServerConnection.listTests({ locations: options.files, projects: options.projects, grep: options.grep }); + teleSuiteUpdater.processListReport(report); + + for (const test of teleSuiteUpdater.rootSuite!.allTests()) { + if (changedFiles.has(test.location.file)) + dirtyTestIds.add(test.id); + } + + changedFiles.clear(); + + if (dirtyTestIds.size > 0) + onDirtyTests.resolve?.(); + }); + }); + testServerConnection.onReport(report => teleSuiteUpdater.processTestReportEvent(report)); + + await testServerConnection.initialize({ interceptStdio: false, watchTestDirs: true }); + await testServerConnection.runGlobalSetup({}); + + const { report } = await testServerConnection.listTests({}); + teleSuiteUpdater.processListReport(report); + + const projectNames = teleSuiteUpdater.rootSuite!.suites.map(s => s.title); + + let lastRun: { type: 'changed' | 'regular' | 'failed', failedTestIds?: string[], dirtyTestIds?: string[] } = { type: 'regular' }; let result: FullResult['status'] = 'passed'; // Enter the watch loop. - await runTests(config, failedTestIdCollector); + await runTests(options, testServerConnection); while (true) { printPrompt(); const readCommandPromise = readCommand(); await Promise.race([ - fsWatcher.onDirtyTestFiles(), + onDirtyTests, readCommandPromise, ]); if (!readCommandPromise.isDone()) @@ -147,32 +149,32 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise({ + const { selectedProjects } = await enquirer.prompt<{ selectedProjects: string[] }>({ type: 'multiselect', - name: 'projectNames', + name: 'selectedProjects', message: 'Select projects', - choices: config.projects.map(p => ({ name: p.project.name })), - }).catch(() => ({ projectNames: null })); - if (!projectNames) + choices: projectNames, + }).catch(() => ({ selectedProjects: null })); + if (!selectedProjects) continue; - config.cliProjectFilter = projectNames.length ? projectNames : undefined; - await fsWatcher.update(config); - await runTests(config, failedTestIdCollector); + options.projects = selectedProjects.length ? selectedProjects : undefined; + await runTests(options, testServerConnection); lastRun = { type: 'regular' }; continue; } @@ -186,11 +188,10 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise failedTestIdCollector.has(id); - const failedTestIds = new Set(failedTestIdCollector); - await runTests(config, failedTestIdCollector, { title: 'running failed tests' }); - config.testIdMatcher = undefined; + const failedTestIds = teleSuiteUpdater.rootSuite!.allTests().filter(t => !t.ok()).map(t => t.id); + await runTests({}, testServerConnection, { title: 'running failed tests', testIds: failedTestIds }); lastRun = { type: 'failed', failedTestIds }; continue; } if (command === 'repeat') { if (lastRun.type === 'regular') { - await runTests(config, failedTestIdCollector, { title: 're-running tests' }); + await runTests(options, testServerConnection, { title: 're-running tests' }); continue; } else if (lastRun.type === 'changed') { - await runChangedTests(config, failedTestIdCollector, lastRun.dirtyTestFiles!, 're-running tests'); + await runTests(options, testServerConnection, { title: 're-running tests', testIds: lastRun.dirtyTestIds }); } else if (lastRun.type === 'failed') { - config.testIdMatcher = id => lastRun.failedTestIds!.has(id); - await runTests(config, failedTestIdCollector, { title: 're-running tests' }); - config.testIdMatcher = undefined; + await runTests({}, testServerConnection, { title: 're-running tests', testIds: lastRun.failedTestIds }); } continue; } if (command === 'toggle-show-browser') { - await toggleShowBrowser(config, originalWorkers); + await toggleShowBrowser(); continue; } @@ -250,71 +246,27 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise, filesByProject: Map>, title?: string) { - const testFiles = new Set(); - for (const files of filesByProject.values()) - files.forEach(f => testFiles.add(f)); - - // Collect all the affected projects, follow project dependencies. - // Prepare to exclude all the projects that do not depend on this file, as if they did not exist. - const projects = filterProjects(config.projects, config.cliProjectFilter); - const projectClosure = buildProjectsClosure(projects); - const affectedProjects = affectedProjectsClosure([...projectClosure.keys()], [...filesByProject.keys()]); - const affectsAnyDependency = [...affectedProjects].some(p => projectClosure.get(p) === 'dependency'); - - // If there are affected dependency projects, do the full run, respect the original CLI. - // if there are no affected dependency projects, intersect CLI with dirty files - const additionalFileMatcher = affectsAnyDependency ? () => true : (file: string) => testFiles.has(file); - await runTests(config, failedTestIdCollector, { additionalFileMatcher, title: title || 'files changed' }); -} - -async function runTests(config: FullConfigInternal, failedTestIdCollector: Set, options?: { - projectsToIgnore?: Set, - additionalFileMatcher?: Matcher, +async function runTests(watchOptions: WatchModeOptions, testServerConnection: TestServerConnection, options?: { title?: string, + testIds?: string[], }) { - printConfiguration(config, options?.title); - const taskRunner = createTaskRunnerForWatch(config, [new ListReporter()], options?.additionalFileMatcher); - const testRun = new TestRun(config); - taskRunner.reporter.onConfigure(config.config); - const taskStatus = await taskRunner.run(testRun, 0); - let status: FullResult['status'] = 'passed'; + printConfiguration(watchOptions, options?.title); - let hasFailedTests = false; - for (const test of testRun.rootSuite?.allTests() || []) { - if (test.outcome() === 'unexpected') { - failedTestIdCollector.add(test.id); - hasFailedTests = true; - } else { - failedTestIdCollector.delete(test.id); - } - } - - if (testRun.failureTracker.hasWorkerErrors() || hasFailedTests) - status = 'failed'; - if (status === 'passed' && taskStatus !== 'passed') - status = taskStatus; - await taskRunner.reporter.onEnd({ status }); - await taskRunner.reporter.onExit(); -} - -function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected: FullProjectInternal[]): Set { - const result = new Set(affected); - for (let i = 0; i < projectClosure.length; ++i) { - for (const p of projectClosure) { - for (const dep of p.deps) { - if (result.has(dep)) - result.add(p); - } - if (p.teardown && result.has(p.teardown)) - result.add(p); - } - } - return result; + await testServerConnection.runTests({ + grep: watchOptions.grep, + testIds: options?.testIds, + locations: watchOptions?.files, + projects: watchOptions.projects, + connectWsEndpoint, + reuseContext: connectWsEndpoint ? true : undefined, + workers: connectWsEndpoint ? 1 : undefined, + headed: connectWsEndpoint ? true : undefined, + }); } function readCommand(): ManualPromise { @@ -377,17 +329,19 @@ Change settings } let showBrowserServer: PlaywrightServer | undefined; +let connectWsEndpoint: string | undefined = undefined; let seq = 0; -function printConfiguration(config: FullConfigInternal, title?: string) { +function printConfiguration(options: WatchModeOptions, title?: string) { const packageManagerCommand = getPackageManagerExecCommand(); const tokens: string[] = []; tokens.push(`${packageManagerCommand} playwright test`); - tokens.push(...(config.cliProjectFilter || [])?.map(p => colors.blue(`--project ${p}`))); - if (config.cliGrep) - tokens.push(colors.red(`--grep ${config.cliGrep}`)); - if (config.cliArgs) - tokens.push(...config.cliArgs.map(a => colors.bold(a))); + if (options.projects) + tokens.push(...options.projects.map(p => colors.blue(`--project ${p}`))); + if (options.grep) + tokens.push(colors.red(`--grep ${options.grep}`)); + if (options.files) + tokens.push(...options.files.map(a => colors.bold(a))); if (title) tokens.push(colors.dim(`(${title})`)); if (seq) @@ -409,25 +363,15 @@ ${colors.dim('Waiting for file changes. Press')} ${colors.bold('enter')} ${color `); } -async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) { +async function toggleShowBrowser() { if (!showBrowserServer) { - config.config.workers = 1; showBrowserServer = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: 1 }); - const wsEndpoint = await showBrowserServer.listen(); - config.configCLIOverrides.use = { - ...config.configCLIOverrides.use, - _optionContextReuseMode: 'when-possible', - _optionConnectOptions: { wsEndpoint }, - }; + connectWsEndpoint = await showBrowserServer.listen(); process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('on')}\n`); } else { - config.config.workers = originalWorkers; - if (config.configCLIOverrides.use) { - delete config.configCLIOverrides.use._optionContextReuseMode; - delete config.configCLIOverrides.use._optionConnectOptions; - } await showBrowserServer?.close(); showBrowserServer = undefined; + connectWsEndpoint = undefined; process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('off')}\n`); } } diff --git a/packages/playwright/src/third_party/tsconfig-loader.ts b/packages/playwright/src/third_party/tsconfig-loader.ts index 490704c330..b654a3b963 100644 --- a/packages/playwright/src/third_party/tsconfig-loader.ts +++ b/packages/playwright/src/third_party/tsconfig-loader.ts @@ -52,47 +52,14 @@ export interface LoadedTsConfig { allowJs?: boolean; } -export function tsConfigLoader(tsconfigPathOrDirecotry: string): LoadedTsConfig[] { - const configPath = resolveConfigPath(tsconfigPathOrDirecotry); - - if (!configPath) - return []; - - const references: LoadedTsConfig[] = []; - const config = loadTsConfig(configPath, references); - return [config, ...references]; -} - -function resolveConfigPath(tsconfigPathOrDirecotry: string): string | undefined { - if (fs.statSync(tsconfigPathOrDirecotry).isFile()) { - return path.resolve(tsconfigPathOrDirecotry); +export function loadTsConfig(configPath: string): LoadedTsConfig[] { + try { + const references: LoadedTsConfig[] = []; + const config = innerLoadTsConfig(configPath, references); + return [config, ...references]; + } catch (e) { + throw new Error(`Failed to load tsconfig file at ${configPath}:\n${e.message}`); } - - const configAbsolutePath = walkForTsConfig(tsconfigPathOrDirecotry); - return configAbsolutePath ? path.resolve(configAbsolutePath) : undefined; -} - -export function walkForTsConfig( - directory: string, - existsSync: (path: string) => boolean = fs.existsSync -): string | undefined { - const tsconfigPath = path.join(directory, "./tsconfig.json"); - if (existsSync(tsconfigPath)) { - return tsconfigPath; - } - const jsconfigPath = path.join(directory, "./jsconfig.json"); - if (existsSync(jsconfigPath)) { - return jsconfigPath; - } - - const parentDirectory = path.join(directory, "../"); - - // If we reached the top - if (directory === parentDirectory) { - return undefined; - } - - return walkForTsConfig(parentDirectory, existsSync); } function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string) { @@ -106,7 +73,7 @@ function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string) return resolvedConfigFile; } -function loadTsConfig( +function innerLoadTsConfig( configFilePath: string, references: LoadedTsConfig[], visited = new Map(), @@ -130,7 +97,7 @@ function loadTsConfig( const extendsArray = Array.isArray(parsedConfig.extends) ? parsedConfig.extends : (parsedConfig.extends ? [parsedConfig.extends] : []); for (const extendedConfig of extendsArray) { const extendedConfigPath = resolveConfigFile(configFilePath, extendedConfig); - const base = loadTsConfig(extendedConfigPath, references, visited); + const base = innerLoadTsConfig(extendedConfigPath, references, visited); // Retain result instance, so that caching works. Object.assign(result, base, { tsConfigPath: configFilePath }); } @@ -154,7 +121,7 @@ function loadTsConfig( } for (const ref of parsedConfig.references || []) - references.push(loadTsConfig(resolveConfigFile(configFilePath, ref.path), references, visited)); + references.push(innerLoadTsConfig(resolveConfigFile(configFilePath, ref.path), references, visited)); if (path.basename(configFilePath) === 'jsconfig.json' && result.allowJs === undefined) result.allowJs = true; diff --git a/packages/playwright/src/transform/esmLoader.ts b/packages/playwright/src/transform/esmLoader.ts index 0ef0f29eaf..c84d15146b 100644 --- a/packages/playwright/src/transform/esmLoader.ts +++ b/packages/playwright/src/transform/esmLoader.ts @@ -26,7 +26,7 @@ import { fileIsModule } from '../util'; async function resolve(specifier: string, context: { parentURL?: string }, defaultResolve: Function) { if (context.parentURL && context.parentURL.startsWith('file://')) { const filename = url.fileURLToPath(context.parentURL); - const resolved = resolveHook(filename, specifier, true); + const resolved = resolveHook(filename, specifier); if (resolved !== undefined) specifier = url.pathToFileURL(resolved).toString(); } diff --git a/packages/playwright/src/transform/transform.ts b/packages/playwright/src/transform/transform.ts index 925cf7a83e..f70f385b5b 100644 --- a/packages/playwright/src/transform/transform.ts +++ b/packages/playwright/src/transform/transform.ts @@ -15,15 +15,16 @@ */ import crypto from 'crypto'; +import fs from 'fs'; import path from 'path'; import url from 'url'; import { sourceMapSupport, pirates } from '../utilsBundle'; import type { Location } from '../../types/testReporter'; import type { LoadedTsConfig } from '../third_party/tsconfig-loader'; -import { tsConfigLoader } from '../third_party/tsconfig-loader'; +import { loadTsConfig } from '../third_party/tsconfig-loader'; import Module from 'module'; import type { BabelPlugin, BabelTransformFunction } from './babelBundle'; -import { createFileMatcher, fileIsModule, resolveImportSpecifierExtension } from '../util'; +import { createFileMatcher, fileIsModule, resolveImportSpecifierAfterMapping } from '../util'; import type { Matcher } from '../util'; import { getFromCompilationCache, currentFileDepsCollector, belongsToNodeModules, installSourceMapSupport } from './compilationCache'; @@ -57,14 +58,15 @@ export function transformConfig(): TransformConfig { return _transformConfig; } -let _singleTSConfig: string | undefined; +let _singleTSConfigPath: string | undefined; +let _singleTSConfig: ParsedTsConfigData[] | undefined; export function setSingleTSConfig(value: string | undefined) { - _singleTSConfig = value; + _singleTSConfigPath = value; } export function singleTSConfig(): string | undefined { - return _singleTSConfig; + return _singleTSConfigPath; } function validateTsConfig(tsconfig: LoadedTsConfig): ParsedTsConfigData { @@ -81,31 +83,65 @@ function validateTsConfig(tsconfig: LoadedTsConfig): ParsedTsConfigData { } function loadAndValidateTsconfigsForFile(file: string): ParsedTsConfigData[] { - const tsconfigPathOrDirecotry = _singleTSConfig || path.dirname(file); - if (!cachedTSConfigs.has(tsconfigPathOrDirecotry)) { - const loaded = tsConfigLoader(tsconfigPathOrDirecotry); - cachedTSConfigs.set(tsconfigPathOrDirecotry, loaded.map(validateTsConfig)); + if (_singleTSConfigPath && !_singleTSConfig) + _singleTSConfig = loadTsConfig(_singleTSConfigPath).map(validateTsConfig); + if (_singleTSConfig) + return _singleTSConfig; + return loadAndValidateTsconfigsForFolder(path.dirname(file)); +} + +function loadAndValidateTsconfigsForFolder(folder: string): ParsedTsConfigData[] { + const foldersWithConfig: string[] = []; + let currentFolder = path.resolve(folder); + let result: ParsedTsConfigData[] | undefined; + while (true) { + const cached = cachedTSConfigs.get(currentFolder); + if (cached) { + result = cached; + break; + } + + foldersWithConfig.push(currentFolder); + + for (const name of ['tsconfig.json', 'jsconfig.json']) { + const configPath = path.join(currentFolder, name); + if (fs.existsSync(configPath)) { + const loaded = loadTsConfig(configPath); + result = loaded.map(validateTsConfig); + break; + } + } + if (result) + break; + + const parentFolder = path.resolve(currentFolder, '../'); + if (currentFolder === parentFolder) + break; + currentFolder = parentFolder; } - return cachedTSConfigs.get(tsconfigPathOrDirecotry)!; + + result = result || []; + for (const folder of foldersWithConfig) + cachedTSConfigs.set(folder, result); + return result; } const pathSeparator = process.platform === 'win32' ? ';' : ':'; const builtins = new Set(Module.builtinModules); -export function resolveHook(filename: string, specifier: string, isESM: boolean): string | undefined { +export function resolveHook(filename: string, specifier: string): string | undefined { if (specifier.startsWith('node:') || builtins.has(specifier)) return; if (!shouldTransform(filename)) return; if (isRelativeSpecifier(specifier)) - return resolveImportSpecifierExtension(path.resolve(path.dirname(filename), specifier), false, isESM); + return resolveImportSpecifierAfterMapping(path.resolve(path.dirname(filename), specifier), false); /** - * TypeScript discourages path-mapping into node_modules - * (https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages). - * It seems like TypeScript tries path-mapping first, but does not look at the `package.json` or `index.js` files in ESM. - * If path-mapping doesn't yield a result, TypeScript falls back to the default resolution (typically node_modules). + * TypeScript discourages path-mapping into node_modules: + * https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages + * However, if path-mapping doesn't yield a result, TypeScript falls back to the default resolution through node_modules. */ const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx'); const tsconfigs = loadAndValidateTsconfigsForFile(filename); @@ -148,7 +184,7 @@ export function resolveHook(filename: string, specifier: string, isESM: boolean) if (value.includes('*')) candidate = candidate.replace('*', matchedPartOfSpecifier); candidate = path.resolve(tsconfig.pathsBase!, candidate); - const existing = resolveImportSpecifierExtension(candidate, true, isESM); + const existing = resolveImportSpecifierAfterMapping(candidate, true); if (existing) { longestPrefixLength = keyPrefix.length; pathMatchedByLongestPrefix = existing; @@ -162,7 +198,7 @@ export function resolveHook(filename: string, specifier: string, isESM: boolean) if (path.isAbsolute(specifier)) { // Handle absolute file paths like `import '/path/to/file'` // Do not handle module imports like `import 'fs'` - return resolveImportSpecifierExtension(specifier, false, isESM); + return resolveImportSpecifierAfterMapping(specifier, false); } } @@ -244,7 +280,7 @@ function installTransformIfNeeded() { const originalResolveFilename = (Module as any)._resolveFilename; function resolveFilename(this: any, specifier: string, parent: Module, ...rest: any[]) { if (parent) { - const resolved = resolveHook(parent.filename, specifier, false); + const resolved = resolveHook(parent.filename, specifier); if (resolved !== undefined) specifier = resolved; } diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index 367bb721d9..460b3de07e 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -295,8 +295,23 @@ function folderIsModule(folder: string): boolean { return require(packageJsonPath).type === 'module'; } -// This follows the --moduleResolution=bundler strategy from tsc. -// https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#moduleresolution-bundler +const packageJsonMainFieldCache = new Map(); + +function getMainFieldFromPackageJson(packageJsonPath: string) { + if (!packageJsonMainFieldCache.has(packageJsonPath)) { + let mainField: string | undefined; + try { + mainField = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).main; + } catch { + } + packageJsonMainFieldCache.set(packageJsonPath, mainField); + } + return packageJsonMainFieldCache.get(packageJsonPath); +} + +// This method performs "file extension subsitution" to find the ts, js or similar source file +// based on the import specifier, which might or might not have an extension. See TypeScript docs: +// https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution. const kExtLookups = new Map([ ['.js', ['.jsx', '.ts', '.tsx']], ['.jsx', ['.tsx']], @@ -304,7 +319,7 @@ const kExtLookups = new Map([ ['.mjs', ['.mts']], ['', ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.cts', '.mts']], ]); -export function resolveImportSpecifierExtension(resolved: string, isPathMapping: boolean, isESM: boolean): string | undefined { +function resolveImportSpecifierExtension(resolved: string): string | undefined { if (fileExists(resolved)) return resolved; @@ -318,26 +333,47 @@ export function resolveImportSpecifierExtension(resolved: string, isPathMapping: } break; // Do not try '' when a more specific extension like '.jsx' matched. } +} - // After TypeScript path mapping, here's how directories with a `package.json` are resolved: - // - `package.json#exports` is not respected - // - `package.json#main` is respected only in CJS mode - // - `index.js` default is respected only in CJS mode - // - // More info: - // - https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages - // - https://www.typescriptlang.org/docs/handbook/modules/reference.html#directory-modules-index-file-resolution - // - https://nodejs.org/dist/latest-v20.x/docs/api/modules.html#folders-as-modules +// This method resolves directory imports and performs "file extension subsitution". +// It is intended to be called after the path mapping resolution. +// +// Directory imports follow the --moduleResolution=bundler strategy from tsc. +// https://www.typescriptlang.org/docs/handbook/modules/reference.html#directory-modules-index-file-resolution +// https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler +// +// See also Node.js "folder as module" behavior: +// https://nodejs.org/dist/latest-v20.x/docs/api/modules.html#folders-as-modules. +export function resolveImportSpecifierAfterMapping(resolved: string, afterPathMapping: boolean): string | undefined { + const resolvedFile = resolveImportSpecifierExtension(resolved); + if (resolvedFile) + return resolvedFile; - const shouldNotResolveDirectory = isPathMapping && isESM; + if (dirExists(resolved)) { + const packageJsonPath = path.join(resolved, 'package.json'); + + if (afterPathMapping) { + // Most notably, the module resolution algorithm is not performed after the path mapping. + // This means no node_modules lookup or package.json#exports. + // + // Only the "folder as module" Node.js behavior is respected: + // - consult `package.json#main`; + // - look for `index.js` or similar. + const mainField = getMainFieldFromPackageJson(packageJsonPath); + const mainFieldResolved = mainField ? resolveImportSpecifierExtension(path.resolve(resolved, mainField)) : undefined; + return mainFieldResolved || resolveImportSpecifierExtension(path.join(resolved, 'index')); + } - if (!shouldNotResolveDirectory && dirExists(resolved)) { // If we import a package, let Node.js figure out the correct import based on package.json. - if (fileExists(path.join(resolved, 'package.json'))) + // This also covers the "main" field for "folder as module". + if (fileExists(packageJsonPath)) return resolved; + // Implement the "folder as module" Node.js behavior. + // Note that we do not delegate to Node.js, because we support this for ESM as well, + // following the TypeScript "bundler" mode. const dirImport = path.join(resolved, 'index'); - return resolveImportSpecifierExtension(dirImport, isPathMapping, isESM); + return resolveImportSpecifierExtension(dirImport); } } @@ -348,3 +384,14 @@ function fileExists(resolved: string) { function dirExists(resolved: string) { return fs.statSync(resolved, { throwIfNoEntry: false })?.isDirectory(); } + +export async function removeDirAndLogToConsole(dir: string) { + try { + if (!fs.existsSync(dir)) + return; + // eslint-disable-next-line no-console + console.log(`Removing ${await fs.promises.realpath(dir)}`); + await fs.promises.rm(dir, { recursive: true, force: true }); + } catch { + } +} diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index b94da22bd9..378b32524f 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -30,7 +30,7 @@ import type { Attachment } from './testTracing'; import type { StackFrame } from '@protocol/channels'; export interface TestStepInternal { - complete(result: { error?: Error, attachments?: Attachment[] }): void; + complete(result: { error?: Error | unknown, attachments?: Attachment[] }): void; stepId: string; title: string; category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string; @@ -69,7 +69,6 @@ export class TestInfoImpl implements TestInfo { readonly _configInternal: FullConfigInternal; private readonly _steps: TestStepInternal[] = []; _onDidFinishTestFunction: (() => Promise) | undefined; - private readonly _stages: TestStage[] = []; _hasNonRetriableError = false; _hasUnhandledError = false; _allowSkips = false; @@ -227,10 +226,14 @@ export class TestInfoImpl implements TestInfo { } } - private _findLastStageStep() { - for (let i = this._stages.length - 1; i >= 0; i--) { - if (this._stages[i].step) - return this._stages[i].step; + private _findLastStageStep(steps: TestStepInternal[]): TestStepInternal | undefined { + // Find the deepest step that is marked as isStage and has not finished yet. + for (let i = steps.length - 1; i >= 0; i--) { + const child = this._findLastStageStep(steps[i].steps); + if (child) + return child; + if (steps[i].isStage && !steps[i].endWallTime) + return steps[i]; } } @@ -240,12 +243,12 @@ export class TestInfoImpl implements TestInfo { let parentStep: TestStepInternal | undefined; if (data.isStage) { // Predefined stages form a fixed hierarchy - use the current one as parent. - parentStep = this._findLastStageStep(); + parentStep = this._findLastStageStep(this._steps); } else { parentStep = zones.zoneData('stepZone'); if (!parentStep) { // If no parent step on stack, assume the current stage as parent. - parentStep = this._findLastStageStep(); + parentStep = this._findLastStageStep(this._steps); } } @@ -267,7 +270,7 @@ export class TestInfoImpl implements TestInfo { step.endWallTime = Date.now(); if (result.error) { - if (!(result.error as any)[stepSymbol]) + if (typeof result.error === 'object' && !(result.error as any)?.[stepSymbol]) (result.error as any)[stepSymbol] = step; const error = serializeError(result.error); if (data.boxedStack) @@ -324,13 +327,13 @@ export class TestInfoImpl implements TestInfo { this.status = 'interrupted'; } - _failWithError(error: Error) { + _failWithError(error: Error | unknown) { if (this.status === 'passed' || this.status === 'skipped') this.status = error instanceof TimeoutManagerError ? 'timedOut' : 'failed'; const serialized = serializeError(error); - const step = (error as any)[stepSymbol] as TestStepInternal | undefined; + const step: TestStepInternal | undefined = typeof error === 'object' ? (error as any)?.[stepSymbol] : undefined; if (step && step.boxedStack) - serialized.stack = `${error.name}: ${error.message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`; + serialized.stack = `${(error as Error).name}: ${(error as Error).message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`; this.errors.push(serialized); this._tracing.appendForError(serialized); } @@ -341,7 +344,6 @@ export class TestInfoImpl implements TestInfo { debugTest(`started stage "${stage.title}"${location}`); } stage.step = stage.stepInfo ? this._addStep({ ...stage.stepInfo, title: stage.title, isStage: true }) : undefined; - this._stages.push(stage); try { await this._timeoutManager.withRunnable(stage.runnable, async () => { @@ -376,9 +378,6 @@ export class TestInfoImpl implements TestInfo { stage.step?.complete({ error }); throw error; } finally { - if (this._stages[this._stages.length - 1] !== stage) - throw new Error(`Internal error: inconsistent stages!`); - this._stages.pop(); debugTest(`finished stage "${stage.title}"`); } } @@ -388,11 +387,8 @@ export class TestInfoImpl implements TestInfo { } _currentHookType() { - for (let i = this._stages.length - 1; i >= 0; i--) { - const type = this._stages[i].runnable?.type; - if (type && ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].includes(type)) - return type; - } + const type = this._timeoutManager.currentSlotType(); + return ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].includes(type) ? type : undefined; } _setDebugMode() { diff --git a/packages/playwright/src/worker/timeoutManager.ts b/packages/playwright/src/worker/timeoutManager.ts index e88af26933..71b853c463 100644 --- a/packages/playwright/src/worker/timeoutManager.ts +++ b/packages/playwright/src/worker/timeoutManager.ts @@ -142,6 +142,10 @@ export class TimeoutManager { return this._running ? this._running.deadline : kMaxDeadline; } + currentSlotType() { + return this._running ? this._running.runnable.type : 'test'; + } + private _createTimeoutError(running: Running): Error { let message = ''; const timeout = running.slot.timeout; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 0131cb16c9..73cc415a87 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6546,6 +6546,7 @@ export type MatcherReturnType = { expected?: unknown; actual?: any; log?: string[]; + timeout?: number; }; type MakeMatchers = { @@ -6565,15 +6566,17 @@ type MakeMatchers = { rejects: MakeMatchers, any, ExtendedMatchers>; } & IfAny, SpecificMatchers & ToUserMatcherObject>; +type PollMatchers = { + /** + * If you know how to test something, `.not` lets you test its opposite. + */ + not: PollMatchers; +} & BaseMatchers & ToUserMatcherObject; + export type Expect = { (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; - poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { - /** - * If you know how to test something, `.not` lets you test its opposite. - */ - not: BaseMatchers, T>; - }; + poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => PollMatchers, T, ExtendedMatchers>; extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; configure: (configuration: { message?: string, @@ -8205,7 +8208,7 @@ export interface TestInfo { title: string; /** - * The full title path starting with the project. + * The full title path starting with the test file name. */ titlePath: Array; diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index f3e0a2c35a..143f1ad0e0 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -318,6 +318,7 @@ export interface APIRequestContextChannel extends APIRequestContextEventTarget, } export type APIRequestContextFetchParams = { url: string, + encodedParams?: string, params?: NameValue[], method?: string, headers?: NameValue[], @@ -332,6 +333,7 @@ export type APIRequestContextFetchParams = { maxRetries?: number, }; export type APIRequestContextFetchOptions = { + encodedParams?: string, params?: NameValue[], method?: string, headers?: NameValue[], @@ -562,6 +564,8 @@ export type PlaywrightInitializer = { chromium: BrowserTypeChannel, firefox: BrowserTypeChannel, webkit: BrowserTypeChannel, + bidiChromium: BrowserTypeChannel, + bidiFirefox: BrowserTypeChannel, android: AndroidChannel, electron: ElectronChannel, utils?: LocalUtilsChannel, @@ -1753,7 +1757,6 @@ export type BrowserContextRecorderSupplementEnableParams = { device?: string, saveStorage?: string, outputFile?: string, - handleSIGINT?: boolean, omitCallTracking?: boolean, }; export type BrowserContextRecorderSupplementEnableOptions = { @@ -1766,7 +1769,6 @@ export type BrowserContextRecorderSupplementEnableOptions = { device?: string, saveStorage?: string, outputFile?: string, - handleSIGINT?: boolean, omitCallTracking?: boolean, }; export type BrowserContextRecorderSupplementEnableResult = void; @@ -2939,7 +2941,6 @@ export type FrameSelectOptionParams = { }[], force?: boolean, timeout?: number, - noWaitAfter?: boolean, }; export type FrameSelectOptionOptions = { strict?: boolean, @@ -2952,7 +2953,6 @@ export type FrameSelectOptionOptions = { }[], force?: boolean, timeout?: number, - noWaitAfter?: boolean, }; export type FrameSelectOptionResult = { values: string[], @@ -3555,7 +3555,6 @@ export type ElementHandleSelectOptionParams = { }[], force?: boolean, timeout?: number, - noWaitAfter?: boolean, }; export type ElementHandleSelectOptionOptions = { elements?: ElementHandleChannel[], @@ -3567,7 +3566,6 @@ export type ElementHandleSelectOptionOptions = { }[], force?: boolean, timeout?: number, - noWaitAfter?: boolean, }; export type ElementHandleSelectOptionResult = { values: string[], diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 4c25212c57..4f064ffa08 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -287,6 +287,7 @@ APIRequestContext: fetch: parameters: url: string + encodedParams: string? params: type: array? items: NameValue @@ -670,6 +671,8 @@ Playwright: chromium: BrowserType firefox: BrowserType webkit: BrowserType + bidiChromium: BrowserType + bidiFirefox: BrowserType android: Android electron: Electron utils: LocalUtils? @@ -1189,7 +1192,6 @@ BrowserContext: device: string? saveStorage: string? outputFile: string? - handleSIGINT: boolean? omitCallTracking: boolean? newCDPSession: @@ -2185,7 +2187,6 @@ Frame: index: number? force: boolean? timeout: number? - noWaitAfter: boolean? returns: values: type: array @@ -2741,7 +2742,6 @@ ElementHandle: index: number? force: boolean? timeout: number? - noWaitAfter: boolean? returns: values: type: array diff --git a/packages/trace-viewer/recorder.html b/packages/trace-viewer/recorder.html new file mode 100644 index 0000000000..c33d6586e5 --- /dev/null +++ b/packages/trace-viewer/recorder.html @@ -0,0 +1,28 @@ + + + + + + + + Playwright Recorder + + +
+ + + diff --git a/packages/trace-viewer/src/entries.ts b/packages/trace-viewer/src/entries.ts index bca5751ab2..dbfec63be8 100644 --- a/packages/trace-viewer/src/entries.ts +++ b/packages/trace-viewer/src/entries.ts @@ -41,9 +41,11 @@ export type ContextEntry = { }; export type PageEntry = { + pageId: string, screencastFrames: { sha1: string, timestamp: number, + frameSwapWallTime?: number, width: number, height: number, }[]; diff --git a/packages/trace-viewer/src/recorder.tsx b/packages/trace-viewer/src/recorder.tsx new file mode 100644 index 0000000000..4de705d4fc --- /dev/null +++ b/packages/trace-viewer/src/recorder.tsx @@ -0,0 +1,41 @@ +/** + * 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 '@web/common.css'; +import { applyTheme } from '@web/theme'; +import '@web/third_party/vscode/codicon.css'; +import * as ReactDOM from 'react-dom/client'; +import { RecorderView } from './ui/recorderView'; + +(async () => { + applyTheme(); + + if (window.location.protocol !== 'file:') { + if (!navigator.serviceWorker) + throw new Error(`Service workers are not supported.\nMake sure to serve the Recorder (${window.location}) via HTTPS or localhost.`); + navigator.serviceWorker.register('sw.bundle.js'); + if (!navigator.serviceWorker.controller) { + await new Promise(f => { + navigator.serviceWorker.oncontrollerchange = () => f(); + }); + } + + // Keep SW running. + setInterval(function() { fetch('ping'); }, 10000); + } + + ReactDOM.createRoot(document.querySelector('#root')!).render(); +})(); diff --git a/packages/trace-viewer/src/snapshotRenderer.ts b/packages/trace-viewer/src/snapshotRenderer.ts index 0b458c0cb5..0d2f2af502 100644 --- a/packages/trace-viewer/src/snapshotRenderer.ts +++ b/packages/trace-viewer/src/snapshotRenderer.ts @@ -25,6 +25,34 @@ function isSubtreeReferenceSnapshot(n: NodeSnapshot): n is SubtreeReferenceSnaps return Array.isArray(n) && Array.isArray(n[0]); } +let cacheSize = 0; +const cache = new Map(); +const CACHE_SIZE = 300_000_000; // 300mb + +function lruCache(key: SnapshotRenderer, compute: () => string): string { + if (cache.has(key)) { + const value = cache.get(key)!; + // reinserting makes this the least recently used entry + cache.delete(key); + cache.set(key, value); + return value; + } + + + const result = compute(); + + while (cache.size && cacheSize + result.length > CACHE_SIZE) { + const [firstKey, firstValue] = cache.entries().next().value; + cacheSize -= firstValue.length; + cache.delete(firstKey); + } + + cache.set(key, result); + cacheSize += result.length; + + return result; +} + export class SnapshotRenderer { private _snapshots: FrameSnapshot[]; private _index: number; @@ -51,89 +79,89 @@ export class SnapshotRenderer { } render(): RenderedFrameSnapshot { - const visit = (n: NodeSnapshot, snapshotIndex: number, parentTag: string | undefined, parentAttrs: [string, string][] | undefined): string => { + const result: string[] = []; + const visit = (n: NodeSnapshot, snapshotIndex: number, parentTag: string | undefined, parentAttrs: [string, string][] | undefined) => { // Text node. if (typeof n === 'string') { // Best-effort Electron support: rewrite custom protocol in url() links in stylesheets. // Old snapshotter was sending lower-case. if (parentTag === 'STYLE' || parentTag === 'style') - return rewriteURLsInStyleSheetForCustomProtocol(n); - return escapeHTML(n); + result.push(rewriteURLsInStyleSheetForCustomProtocol(n)); + else + result.push(escapeHTML(n)); + return; } - if (!(n as any)._string) { - if (isSubtreeReferenceSnapshot(n)) { - // Node reference. - const referenceIndex = snapshotIndex - n[0][0]; - if (referenceIndex >= 0 && referenceIndex <= snapshotIndex) { - const nodes = snapshotNodes(this._snapshots[referenceIndex]); - const nodeIndex = n[0][1]; - if (nodeIndex >= 0 && nodeIndex < nodes.length) - (n as any)._string = visit(nodes[nodeIndex], referenceIndex, parentTag, parentAttrs); - } - } else if (isNodeNameAttributesChildNodesSnapshot(n)) { - const [name, nodeAttrs, ...children] = n; - // Element node. - // Note that