Skip to content

Commit

Permalink
feat: add Page.opener() to the API (#790)
Browse files Browse the repository at this point in the history
Fixes #783
  • Loading branch information
yury-s authored Feb 1, 2020
1 parent 1489fbd commit 25f2a32
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 6 deletions.
5 changes: 5 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ page.removeListener('request', logRequest);
- [page.keyboard](#pagekeyboard)
- [page.mainFrame()](#pagemainframe)
- [page.mouse](#pagemouse)
- [page.opener()](#pageopener)
- [page.pdf([options])](#pagepdfoptions)
- [page.reload([options])](#pagereloadoptions)
- [page.screenshot([options])](#pagescreenshotoptions)
Expand Down Expand Up @@ -1098,6 +1099,10 @@ Page is guaranteed to have a main frame which persists during navigations.

- returns: <[Mouse]>

#### page.opener()

- returns: <[Promise]<?[Page]>> Promise which resolves to the opener for popup pages and `null` for others. If the opener has been closed already the promise may resolve to `null`.

#### page.pdf([options])
- `options` <[Object]> Options object which might have the following properties:
- `path` <[string]> The file path to save the PDF to. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). If no path is provided, the PDF won't be saved to the disk.
Expand Down
8 changes: 8 additions & 0 deletions src/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { BrowserContext } from '../browserContext';
import * as types from '../types';
import { ConsoleMessage } from '../console';
import * as platform from '../platform';
import { CRTarget } from './crTarget';

const UTILITY_WORLD_NAME = '__playwright_utility_world__';

Expand Down Expand Up @@ -349,6 +350,13 @@ export class CRPage implements PageDelegate {
await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed.
}

async opener() : Promise<Page | null> {
const openerTarget = CRTarget.fromPage(this._page).opener();
if (!openerTarget)
return null;
return await openerTarget.page();
}

async reload(): Promise<void> {
await this._client.send('Page.reload');
}
Expand Down
7 changes: 6 additions & 1 deletion src/firefox/ffBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,12 @@ class Target {
if (!this._pagePromise) {
this._pagePromise = new Promise(async f => {
const session = await this._connection.createSession(this._targetId);
this._ffPage = new FFPage(session, this._context);
this._ffPage = new FFPage(session, this._context, async () => {
const openerTarget = this.opener();
if (!openerTarget)
return null;
return await openerTarget.page();
});
const page = this._ffPage._page;
session.once(FFSessionEvents.Disconnected, () => page._didDisconnect());
await this._ffPage._initialize();
Expand Down
8 changes: 7 additions & 1 deletion src/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ export class FFPage implements PageDelegate {
readonly _session: FFSession;
readonly _page: Page;
readonly _networkManager: FFNetworkManager;
private readonly _openerResolver: () => Promise<Page | null>;
private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
private _eventListeners: RegisteredListener[];
private _workers = new Map<string, { frameId: string, session: FFSession }>();

constructor(session: FFSession, browserContext: BrowserContext) {
constructor(session: FFSession, browserContext: BrowserContext, openerResolver: () => Promise<Page | null>) {
this._session = session;
this._openerResolver = openerResolver;
this.rawKeyboard = new RawKeyboardImpl(session);
this.rawMouse = new RawMouseImpl(session);
this._contextIdToContext = new Map();
Expand Down Expand Up @@ -305,6 +307,10 @@ export class FFPage implements PageDelegate {
await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed.
}

async opener() : Promise<Page | null> {
return await this._openerResolver();
}

async reload(): Promise<void> {
await this._session.send('Page.reload', { frameId: this._page.mainFrame()._id });
}
Expand Down
6 changes: 6 additions & 0 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface PageDelegate {
readonly rawMouse: input.RawMouse;
readonly rawKeyboard: input.RawKeyboard;

opener(): Promise<Page | null>;

reload(): Promise<void>;
goBack(): Promise<boolean>;
goForward(): Promise<boolean>;
Expand Down Expand Up @@ -173,6 +175,10 @@ export class Page extends platform.EventEmitter {
return this._browserContext;
}

async opener(): Promise<Page | null> {
return await this._delegate.opener();
}

mainFrame(): frames.Frame {
return this._frameManager.mainFrame();
}
Expand Down
9 changes: 8 additions & 1 deletion src/webkit/wkBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
const pageProxySession = new WKSession(this._connection, pageProxyId, `The page has been closed.`, (message: any) => {
this._connection.rawSend({ ...message, pageProxyId });
});
const pageProxy = new WKPageProxy(pageProxySession, context);
const pageProxy = new WKPageProxy(pageProxySession, context, () => {
if (!pageProxyInfo.openerId)
return null;
const opener = this._pageProxies.get(pageProxyInfo.openerId);
if (!opener)
return null;
return opener;
});
this._pageProxies.set(pageProxyId, pageProxy);

if (pageProxyInfo.openerId) {
Expand Down
8 changes: 7 additions & 1 deletion src/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,17 @@ export class WKPage implements PageDelegate {
private _provisionalPage: WKProvisionalPage | null = null;
readonly _page: Page;
private readonly _pageProxySession: WKSession;
private readonly _openerResolver: () => Promise<Page | null>;
private readonly _requestIdToRequest = new Map<string, WKInterceptableRequest>();
private readonly _workers: WKWorkers;
private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private _mainFrameContextId?: number;
private _sessionListeners: RegisteredListener[] = [];
private readonly _bootstrapScripts: string[] = [];

constructor(browserContext: BrowserContext, pageProxySession: WKSession) {
constructor(browserContext: BrowserContext, pageProxySession: WKSession, openerResolver: () => Promise<Page | null>) {
this._pageProxySession = pageProxySession;
this._openerResolver = openerResolver;
this.rawKeyboard = new RawKeyboardImpl(pageProxySession);
this.rawMouse = new RawMouseImpl(pageProxySession);
this._contextIdToContext = new Map();
Expand Down Expand Up @@ -415,6 +417,10 @@ export class WKPage implements PageDelegate {
await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed.
}

async opener() {
return await this._openerResolver();
}

async reload(): Promise<void> {
await this._session.send('Page.reload');
}
Expand Down
11 changes: 9 additions & 2 deletions src/webkit/wkPageProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const isPovisionalSymbol = Symbol('isPovisional');
export class WKPageProxy {
private readonly _pageProxySession: WKSession;
readonly _browserContext: BrowserContext;
private readonly _openerResolver: () => WKPageProxy | null;
private _pagePromise: Promise<Page> | null = null;
private _wkPage: WKPage | null = null;
private readonly _firstTargetPromise: Promise<void>;
Expand All @@ -36,9 +37,10 @@ export class WKPageProxy {
private readonly _sessions = new Map<string, WKSession>();
private readonly _eventListeners: RegisteredListener[];

constructor(pageProxySession: WKSession, browserContext: BrowserContext) {
constructor(pageProxySession: WKSession, browserContext: BrowserContext, openerResolver: () => (WKPageProxy | null)) {
this._pageProxySession = pageProxySession;
this._browserContext = browserContext;
this._openerResolver = openerResolver;
this._firstTargetPromise = new Promise(r => this._firstTargetCallback = r);
this._eventListeners = [
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
Expand Down Expand Up @@ -111,7 +113,12 @@ export class WKPageProxy {
}
}
assert(session, 'One non-provisional target session must exist');
this._wkPage = new WKPage(this._browserContext, this._pageProxySession);
this._wkPage = new WKPage(this._browserContext, this._pageProxySession, async () => {
const pageProxy = this._openerResolver();
if (!pageProxy)
return null;
return await pageProxy.page();
});
await this._wkPage.initialize(session!);
if (this._pagePausedOnStart) {
this._resumeTarget(session!.sessionId);
Expand Down
20 changes: 20 additions & 0 deletions test/page.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,26 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
});
});

describe('Page.opener', function() {
it('should provide access to the opener page', async({page}) => {
const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)),
page.evaluate(() => window.open('about:blank')),
]);
const opener = await popup.opener();
expect(opener).toBe(page);
});
it('should return null if parent page has been closed', async({page}) => {
const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)),
page.evaluate(() => window.open('about:blank')),
]);
await page.close();
const opener = await popup.opener();
expect(opener).toBe(null);
});
});

describe('Page.Events.Console', function() {
it('should work', async({page, server}) => {
let message = null;
Expand Down

0 comments on commit 25f2a32

Please sign in to comment.