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 swappingOutChildFrames = Symbol('swappingOutChildFrames');
|
||||
|
||||
export class CRPage implements PageDelegate {
|
||||
readonly _mainFrameSession: 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, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
|
||||
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.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
|
||||
helper.addEventListener(this._client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
|
||||
|
|
@ -551,35 +549,23 @@ class FrameSession {
|
|||
this._page._frameManager.frameCommittedSameDocumentNavigation(frameId, url);
|
||||
}
|
||||
|
||||
private _takeParentForSwappingOutFrame(targetId: string) {
|
||||
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) {
|
||||
_onFrameDetached(frameId: string, reason: 'remove' | 'swap') {
|
||||
if (this._crPage._sessions.has(frameId)) {
|
||||
// This is a local -> remote frame transtion.
|
||||
// We already got a new target and handled frame reattach - nothing to do here.
|
||||
// This is a local -> remote frame transtion, where
|
||||
// Page.frameDetached arrives after Target.attachedToTarget.
|
||||
// We've already handled the new target and frame reattach - nothing to do here.
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -615,16 +601,8 @@ class FrameSession {
|
|||
if (event.targetInfo.type === 'iframe') {
|
||||
// Frame id equals target id.
|
||||
const targetId = event.targetInfo.targetId;
|
||||
const frame = this._page._frameManager.frame(targetId);
|
||||
if (frame) {
|
||||
const frame = this._page._frameManager.frame(targetId)!;
|
||||
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);
|
||||
this._crPage._sessions.set(targetId, frameSession);
|
||||
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.
|
||||
*/
|
||||
export type BrowserCommandId = "openTabSearch";
|
||||
export type BrowserCommandId = "openTabSearch"|"closeTabSearch";
|
||||
/**
|
||||
* 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";
|
||||
/**
|
||||
* 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 {
|
||||
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.
|
||||
*/
|
||||
refreshPolicy: "UseCached"|"Refresh";
|
||||
|
|
@ -7410,8 +7411,8 @@ https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-
|
|||
reportOnlyReportingEndpoint?: string;
|
||||
}
|
||||
export interface SecurityIsolationStatus {
|
||||
coop: CrossOriginOpenerPolicyStatus;
|
||||
coep: CrossOriginEmbedderPolicyStatus;
|
||||
coop?: CrossOriginOpenerPolicyStatus;
|
||||
coep?: CrossOriginEmbedderPolicyStatus;
|
||||
}
|
||||
/**
|
||||
* An object providing the result of a network resource load.
|
||||
|
|
@ -8509,6 +8510,36 @@ continueInterceptedRequest call.
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
@ -8573,6 +8604,10 @@ continueInterceptedRequest call.
|
|||
* The grid layout highlight configuration (default: all transparent).
|
||||
*/
|
||||
gridHighlightConfig?: GridHighlightConfig;
|
||||
/**
|
||||
* The flex container highlight configuration (default: all transparent).
|
||||
*/
|
||||
flexContainerHighlightConfig?: FlexContainerHighlightConfig;
|
||||
}
|
||||
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.
|
||||
*/
|
||||
export type CrossOriginIsolatedContextType = "Isolated"|"NotIsolated"|"NotIsolatedFeatureDisabled";
|
||||
export type GatedAPIFeatures = "SharedArrayBuffers"|"SharedArrayBuffersTransferAllowed"|"PerformanceMeasureMemory"|"PerformanceProfile";
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
crossOriginIsolatedContextType: CrossOriginIsolatedContextType;
|
||||
/**
|
||||
* Indicated which gated APIs / features are available.
|
||||
*/
|
||||
gatedAPIFeatures: GatedAPIFeatures[];
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
frameId: FrameId;
|
||||
reason: "remove"|"swap";
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
const outputPath = path.join(__dirname, '../../src/server/chromium/protocol.ts');
|
||||
process.env.PLAYWRIGHT_CHROMIUM_DEBUG_PORT = '9339';
|
||||
const playwright = require('../../index').chromium;
|
||||
const browser = await playwright.launch({ executablePath });
|
||||
delete process.env.PLAYWRIGHT_CHROMIUM_DEBUG_PORT;
|
||||
const browser = await playwright.launch({ executablePath, args: ['--remote-debugging-port=9339'] });
|
||||
const page = await browser.newPage();
|
||||
await page.goto(`http://localhost:9339/json/protocol`);
|
||||
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
|
||||
|
|
|
|||
Loading…
Reference in a new issue