diff --git a/src/common/FrameManager.ts b/src/common/FrameManager.ts index 7a39864894ee3..115c241b385f5 100644 --- a/src/common/FrameManager.ts +++ b/src/common/FrameManager.ts @@ -53,6 +53,7 @@ export const FrameManagerEmittedEvents = { FrameAttached: Symbol('FrameManager.FrameAttached'), FrameNavigated: Symbol('FrameManager.FrameNavigated'), FrameDetached: Symbol('FrameManager.FrameDetached'), + FrameSwapped: Symbol('FrameManager.FrameSwapped'), LifecycleEvent: Symbol('FrameManager.LifecycleEvent'), FrameNavigatedWithinDocument: Symbol( 'FrameManager.FrameNavigatedWithinDocument' @@ -422,6 +423,8 @@ export class FrameManager extends EventEmitter { // an actual removement of the frame. // For frames that become OOP iframes, the reason would be 'swap'. if (frame) this._removeFramesRecursively(frame); + } else if (reason === 'swap') { + this.emit(FrameManagerEmittedEvents.FrameSwapped, frame); } } diff --git a/src/common/LifecycleWatcher.ts b/src/common/LifecycleWatcher.ts index 0ab639dfee212..0407a9cc9b9fb 100644 --- a/src/common/LifecycleWatcher.ts +++ b/src/common/LifecycleWatcher.ts @@ -82,6 +82,7 @@ export class LifecycleWatcher { _maximumTimer?: NodeJS.Timeout; _hasSameDocumentNavigation?: boolean; + _swapped?: boolean; constructor( frameManager: FrameManager, @@ -121,6 +122,11 @@ export class LifecycleWatcher { FrameManagerEmittedEvents.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this) ), + helper.addEventListener( + this._frameManager, + FrameManagerEmittedEvents.FrameSwapped, + this._frameSwapped.bind(this) + ), helper.addEventListener( this._frameManager, FrameManagerEmittedEvents.FrameDetached, @@ -211,6 +217,12 @@ export class LifecycleWatcher { this._checkLifecycleComplete(); } + _frameSwapped(frame: Frame): void { + if (frame !== this._frame) return; + this._swapped = true; + this._checkLifecycleComplete(); + } + _checkLifecycleComplete(): void { // We expect navigation to commit. if (!checkLifecycle(this._frame, this._expectedLifecycle)) return; @@ -218,8 +230,13 @@ export class LifecycleWatcher { if ( this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation - ) + ) { + if (this._swapped) { + this._swapped = false; + this._newDocumentNavigationCompleteCallback(); + } return; + } if (this._hasSameDocumentNavigation) this._sameDocumentNavigationCompleteCallback(); if (this._frame._loaderId !== this._initialLoaderId) diff --git a/test/oopif.spec.ts b/test/oopif.spec.ts index 50727b61aee1f..e9999a3cfa5f9 100644 --- a/test/oopif.spec.ts +++ b/test/oopif.spec.ts @@ -154,6 +154,30 @@ describeChromeOnly('OOPIF', function () { await utils.detachFrame(page, 'frame1'); expect(page.frames()).toHaveLength(1); }); + + it('should support wait for navigation for transitions from local to OOPIF', async () => { + const { server } = getTestState(); + + await page.goto(server.EMPTY_PAGE); + const framePromise = page.waitForFrame((frame) => { + return page.frames().indexOf(frame) === 1; + }); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + + const frame = await framePromise; + expect(frame.isOOPFrame()).toBe(false); + const nav = frame.waitForNavigation(); + await utils.navigateFrame( + page, + 'frame1', + server.CROSS_PROCESS_PREFIX + '/empty.html' + ); + await nav; + expect(frame.isOOPFrame()).toBe(true); + await utils.detachFrame(page, 'frame1'); + expect(page.frames()).toHaveLength(1); + }); + it('should keep track of a frames OOP state', async () => { const { server } = getTestState();