feat: add Page.opener() to the API (#790)

Fixes #783
This commit is contained in:
Yury Semikhatsky 2020-01-31 18:38:45 -08:00 коммит произвёл GitHub
Родитель 1489fbdbff
Коммит 25f2a32af3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 76 добавлений и 6 удалений

Просмотреть файл

@ -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)
@ -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.

Просмотреть файл

@ -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__';
@ -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');
}

Просмотреть файл

@ -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();

Просмотреть файл

@ -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();
@ -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 });
}

Просмотреть файл

@ -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>;
@ -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();
}

Просмотреть файл

@ -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) {

Просмотреть файл

@ -45,6 +45,7 @@ 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>;
@ -52,8 +53,9 @@ export class WKPage implements PageDelegate {
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();
@ -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');
}

Просмотреть файл

@ -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>;
@ -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)),
@ -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);

Просмотреть файл

@ -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;