fix(chromium): use frameDetached reason (#4468)
This fixes the local -> remote frame swap when Page.frameDetached arrives before Target.attachedToTarget. Instead of error-prone logic we do currently, new CDP exposes frame detach reason that we can use.
This commit is contained in:
parent
c1a5cd51b1
commit
38fadcaded
|
|
@ -43,8 +43,6 @@ import { VideoRecorder } from './videoRecorder';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
||||||
const swappingOutChildFrames = Symbol('swappingOutChildFrames');
|
|
||||||
|
|
||||||
export class CRPage implements PageDelegate {
|
export class CRPage implements PageDelegate {
|
||||||
readonly _mainFrameSession: FrameSession;
|
readonly _mainFrameSession: FrameSession;
|
||||||
readonly _sessions = new Map<Protocol.Target.TargetID, FrameSession>();
|
readonly _sessions = new Map<Protocol.Target.TargetID, FrameSession>();
|
||||||
|
|
@ -363,7 +361,7 @@ class FrameSession {
|
||||||
helper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)),
|
helper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)),
|
||||||
helper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
|
helper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
|
||||||
helper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
|
helper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
|
||||||
helper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
|
helper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId, event.reason)),
|
||||||
helper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
|
helper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
|
||||||
helper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
|
helper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
|
||||||
helper.addEventListener(this._client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
|
helper.addEventListener(this._client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
|
||||||
|
|
@ -551,35 +549,23 @@ class FrameSession {
|
||||||
this._page._frameManager.frameCommittedSameDocumentNavigation(frameId, url);
|
this._page._frameManager.frameCommittedSameDocumentNavigation(frameId, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _takeParentForSwappingOutFrame(targetId: string) {
|
_onFrameDetached(frameId: string, reason: 'remove' | 'swap') {
|
||||||
for (const frame of this._page.frames()) {
|
|
||||||
if (!(frame as any)[swappingOutChildFrames])
|
|
||||||
continue;
|
|
||||||
if ((frame as any)[swappingOutChildFrames].delete(targetId))
|
|
||||||
return frame._id;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _frameMaybeSwappingOut(frameId: string) {
|
|
||||||
const frame = this._page._frameManager.frame(frameId);
|
|
||||||
if (!frame)
|
|
||||||
return;
|
|
||||||
const parent = frame.parentFrame() as any;
|
|
||||||
if (!parent)
|
|
||||||
return;
|
|
||||||
if (!parent[swappingOutChildFrames])
|
|
||||||
parent[swappingOutChildFrames] = new Set<string>();
|
|
||||||
parent[swappingOutChildFrames].add(frameId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFrameDetached(frameId: string) {
|
|
||||||
if (this._crPage._sessions.has(frameId)) {
|
if (this._crPage._sessions.has(frameId)) {
|
||||||
// This is a local -> remote frame transtion.
|
// This is a local -> remote frame transtion, where
|
||||||
// We already got a new target and handled frame reattach - nothing to do here.
|
// Page.frameDetached arrives after Target.attachedToTarget.
|
||||||
|
// We've already handled the new target and frame reattach - nothing to do here.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._frameMaybeSwappingOut(frameId);
|
if (reason === 'swap') {
|
||||||
|
// This is a local -> remote frame transtion, where
|
||||||
|
// Page.frameDetached arrives before Target.attachedToTarget.
|
||||||
|
// We should keep the frame in the tree, and it will be used for the new target.
|
||||||
|
const frame = this._page._frameManager.frame(frameId);
|
||||||
|
if (frame)
|
||||||
|
this._page._frameManager.removeChildFramesRecursively(frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Just a regular frame detach.
|
||||||
this._page._frameManager.frameDetached(frameId);
|
this._page._frameManager.frameDetached(frameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -615,16 +601,8 @@ class FrameSession {
|
||||||
if (event.targetInfo.type === 'iframe') {
|
if (event.targetInfo.type === 'iframe') {
|
||||||
// Frame id equals target id.
|
// Frame id equals target id.
|
||||||
const targetId = event.targetInfo.targetId;
|
const targetId = event.targetInfo.targetId;
|
||||||
const frame = this._page._frameManager.frame(targetId);
|
const frame = this._page._frameManager.frame(targetId)!;
|
||||||
if (frame) {
|
this._page._frameManager.removeChildFramesRecursively(frame);
|
||||||
this._page._frameManager.removeChildFramesRecursively(frame);
|
|
||||||
} else {
|
|
||||||
// There is a race between Page.frameDetached and Target.attachedToTarget. If the frame
|
|
||||||
// has already been detached we look up its last parent frame.
|
|
||||||
const parentFrameId = this._takeParentForSwappingOutFrame(targetId);
|
|
||||||
assert(parentFrameId, 'Cannot find parent for iframe: ' + targetId);
|
|
||||||
this._page._frameManager.frameAttached(targetId, parentFrameId);
|
|
||||||
}
|
|
||||||
const frameSession = new FrameSession(this._crPage, session, targetId, this);
|
const frameSession = new FrameSession(this._crPage, session, targetId, this);
|
||||||
this._crPage._sessions.set(targetId, frameSession);
|
this._crPage._sessions.set(targetId, frameSession);
|
||||||
frameSession._initialize(false).catch(e => e);
|
frameSession._initialize(false).catch(e => e);
|
||||||
|
|
|
||||||
|
|
@ -1041,7 +1041,7 @@ Note that userVisibleOnly = true is the only currently supported type.
|
||||||
/**
|
/**
|
||||||
* Browser command ids used by executeBrowserCommand.
|
* Browser command ids used by executeBrowserCommand.
|
||||||
*/
|
*/
|
||||||
export type BrowserCommandId = "openTabSearch";
|
export type BrowserCommandId = "openTabSearch"|"closeTabSearch";
|
||||||
/**
|
/**
|
||||||
* Chrome histogram bucket.
|
* Chrome histogram bucket.
|
||||||
*/
|
*/
|
||||||
|
|
@ -6866,12 +6866,13 @@ passed by the developer (e.g. via "fetch") as understood by the backend.
|
||||||
export type ServiceWorkerResponseSource = "cache-storage"|"http-cache"|"fallback-code"|"network";
|
export type ServiceWorkerResponseSource = "cache-storage"|"http-cache"|"fallback-code"|"network";
|
||||||
/**
|
/**
|
||||||
* Determines what type of Trust Token operation is executed and
|
* Determines what type of Trust Token operation is executed and
|
||||||
depending on the type, some additional parameters.
|
depending on the type, some additional parameters. The values
|
||||||
|
are specified in third_party/blink/renderer/core/fetch/trust_token.idl.
|
||||||
*/
|
*/
|
||||||
export interface TrustTokenParams {
|
export interface TrustTokenParams {
|
||||||
type: TrustTokenOperationType;
|
type: TrustTokenOperationType;
|
||||||
/**
|
/**
|
||||||
* Only set for "srr-token-redemption" type and determine whether
|
* Only set for "token-redemption" type and determine whether
|
||||||
to request a fresh SRR or use a still valid cached SRR.
|
to request a fresh SRR or use a still valid cached SRR.
|
||||||
*/
|
*/
|
||||||
refreshPolicy: "UseCached"|"Refresh";
|
refreshPolicy: "UseCached"|"Refresh";
|
||||||
|
|
@ -7410,8 +7411,8 @@ https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-
|
||||||
reportOnlyReportingEndpoint?: string;
|
reportOnlyReportingEndpoint?: string;
|
||||||
}
|
}
|
||||||
export interface SecurityIsolationStatus {
|
export interface SecurityIsolationStatus {
|
||||||
coop: CrossOriginOpenerPolicyStatus;
|
coop?: CrossOriginOpenerPolicyStatus;
|
||||||
coep: CrossOriginEmbedderPolicyStatus;
|
coep?: CrossOriginEmbedderPolicyStatus;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* An object providing the result of a network resource load.
|
* An object providing the result of a network resource load.
|
||||||
|
|
@ -8509,6 +8510,36 @@ continueInterceptedRequest call.
|
||||||
*/
|
*/
|
||||||
gridBackgroundColor?: DOM.RGBA;
|
gridBackgroundColor?: DOM.RGBA;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Configuration data for the highlighting of Flex container elements.
|
||||||
|
*/
|
||||||
|
export interface FlexContainerHighlightConfig {
|
||||||
|
/**
|
||||||
|
* The style of the container border
|
||||||
|
*/
|
||||||
|
containerBorder?: LineStyle;
|
||||||
|
/**
|
||||||
|
* The style of the separator between lines
|
||||||
|
*/
|
||||||
|
lineSeparator?: LineStyle;
|
||||||
|
/**
|
||||||
|
* The style of the separator between items
|
||||||
|
*/
|
||||||
|
itemSeparator?: LineStyle;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Style information for drawing a line.
|
||||||
|
*/
|
||||||
|
export interface LineStyle {
|
||||||
|
/**
|
||||||
|
* The color of the line (default: transparent)
|
||||||
|
*/
|
||||||
|
color?: DOM.RGBA;
|
||||||
|
/**
|
||||||
|
* The line pattern (default: solid)
|
||||||
|
*/
|
||||||
|
pattern?: "dashed"|"dotted";
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Configuration data for the highlighting of page elements.
|
* Configuration data for the highlighting of page elements.
|
||||||
*/
|
*/
|
||||||
|
|
@ -8573,6 +8604,10 @@ continueInterceptedRequest call.
|
||||||
* The grid layout highlight configuration (default: all transparent).
|
* The grid layout highlight configuration (default: all transparent).
|
||||||
*/
|
*/
|
||||||
gridHighlightConfig?: GridHighlightConfig;
|
gridHighlightConfig?: GridHighlightConfig;
|
||||||
|
/**
|
||||||
|
* The flex container highlight configuration (default: all transparent).
|
||||||
|
*/
|
||||||
|
flexContainerHighlightConfig?: FlexContainerHighlightConfig;
|
||||||
}
|
}
|
||||||
export type ColorFormat = "rgb"|"hsl"|"hex";
|
export type ColorFormat = "rgb"|"hsl"|"hex";
|
||||||
/**
|
/**
|
||||||
|
|
@ -8997,6 +9032,7 @@ Backend then generates 'inspectNodeRequested' event upon element selection.
|
||||||
* Indicates whether the frame is cross-origin isolated and why it is the case.
|
* Indicates whether the frame is cross-origin isolated and why it is the case.
|
||||||
*/
|
*/
|
||||||
export type CrossOriginIsolatedContextType = "Isolated"|"NotIsolated"|"NotIsolatedFeatureDisabled";
|
export type CrossOriginIsolatedContextType = "Isolated"|"NotIsolated"|"NotIsolatedFeatureDisabled";
|
||||||
|
export type GatedAPIFeatures = "SharedArrayBuffers"|"SharedArrayBuffersTransferAllowed"|"PerformanceMeasureMemory"|"PerformanceProfile";
|
||||||
/**
|
/**
|
||||||
* Information about the Frame on the page.
|
* Information about the Frame on the page.
|
||||||
*/
|
*/
|
||||||
|
|
@ -9056,6 +9092,10 @@ Example URLs: http://www.google.com/file.html -> "google.com"
|
||||||
* Indicates whether this is a cross origin isolated context.
|
* Indicates whether this is a cross origin isolated context.
|
||||||
*/
|
*/
|
||||||
crossOriginIsolatedContextType: CrossOriginIsolatedContextType;
|
crossOriginIsolatedContextType: CrossOriginIsolatedContextType;
|
||||||
|
/**
|
||||||
|
* Indicated which gated APIs / features are available.
|
||||||
|
*/
|
||||||
|
gatedAPIFeatures: GatedAPIFeatures[];
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Information about the Resource on the page.
|
* Information about the Resource on the page.
|
||||||
|
|
@ -9433,6 +9473,7 @@ Example URLs: http://www.google.com/file.html -> "google.com"
|
||||||
* Id of the frame that has been detached.
|
* Id of the frame that has been detached.
|
||||||
*/
|
*/
|
||||||
frameId: FrameId;
|
frameId: FrameId;
|
||||||
|
reason: "remove"|"swap";
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Fired once navigation of the frame has completed. Frame is now associated with the new loader.
|
* Fired once navigation of the frame has completed. Frame is now associated with the new loader.
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,8 @@ async function generateProtocol(name, executablePath) {
|
||||||
|
|
||||||
async function generateChromiumProtocol(executablePath) {
|
async function generateChromiumProtocol(executablePath) {
|
||||||
const outputPath = path.join(__dirname, '../../src/server/chromium/protocol.ts');
|
const outputPath = path.join(__dirname, '../../src/server/chromium/protocol.ts');
|
||||||
process.env.PLAYWRIGHT_CHROMIUM_DEBUG_PORT = '9339';
|
|
||||||
const playwright = require('../../index').chromium;
|
const playwright = require('../../index').chromium;
|
||||||
const browser = await playwright.launch({ executablePath });
|
const browser = await playwright.launch({ executablePath, args: ['--remote-debugging-port=9339'] });
|
||||||
delete process.env.PLAYWRIGHT_CHROMIUM_DEBUG_PORT;
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.goto(`http://localhost:9339/json/protocol`);
|
await page.goto(`http://localhost:9339/json/protocol`);
|
||||||
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
|
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue