feat(firefox): support isolated worlds (#507)

This commit is contained in:
Dmitry Gozman 2020-01-16 12:57:28 -08:00 committed by GitHub
parent 6b0b7500bd
commit 21510a5b06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 31 additions and 18 deletions

View file

@ -9,7 +9,7 @@
"main": "index.js", "main": "index.js",
"playwright": { "playwright": {
"chromium_revision": "724623", "chromium_revision": "724623",
"firefox_revision": "1012", "firefox_revision": "1013",
"webkit_revision": "1092" "webkit_revision": "1092"
}, },
"scripts": { "scripts": {

View file

@ -38,7 +38,6 @@ import { CRBrowser } from './crBrowser';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import * as types from '../types'; import * as types from '../types';
import { ConsoleMessage } from '../console'; import { ConsoleMessage } from '../console';
import * as accessibility from '../accessibility';
import * as platform from '../platform'; import * as platform from '../platform';
const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const UTILITY_WORLD_NAME = '__playwright_utility_world__';

View file

@ -16,7 +16,7 @@
*/ */
import * as frames from '../frames'; import * as frames from '../frames';
import { assert, helper, RegisteredListener, debugError } from '../helper'; import { helper, RegisteredListener, debugError } from '../helper';
import * as dom from '../dom'; import * as dom from '../dom';
import { FFSession } from './ffConnection'; import { FFSession } from './ffConnection';
import { FFExecutionContext } from './ffExecutionContext'; import { FFExecutionContext } from './ffExecutionContext';
@ -30,10 +30,11 @@ import { BrowserContext } from '../browserContext';
import { getAccessibilityTree } from './ffAccessibility'; import { getAccessibilityTree } from './ffAccessibility';
import * as network from '../network'; import * as network from '../network';
import * as types from '../types'; import * as types from '../types';
import * as accessibility from '../accessibility';
import * as platform from '../platform'; import * as platform from '../platform';
import { kScreenshotDuringNavigationError } from '../screenshotter'; import { kScreenshotDuringNavigationError } from '../screenshotter';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
export class FFPage implements PageDelegate { export class FFPage implements PageDelegate {
readonly rawMouse: RawMouseImpl; readonly rawMouse: RawMouseImpl;
readonly rawKeyboard: RawKeyboardImpl; readonly rawKeyboard: RawKeyboardImpl;
@ -70,7 +71,7 @@ export class FFPage implements PageDelegate {
async _initialize() { async _initialize() {
const promises: Promise<any>[] = [ const promises: Promise<any>[] = [
this._session.send('Runtime.enable'), this._session.send('Runtime.enable').then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
this._session.send('Network.enable'), this._session.send('Network.enable'),
this._session.send('Page.enable'), this._session.send('Page.enable'),
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }) this._session.send('Page.setInterceptFileChooserDialog', { enabled: true })
@ -87,6 +88,13 @@ export class FFPage implements PageDelegate {
await Promise.all(promises); await Promise.all(promises);
} }
async _ensureIsolatedWorld(name: string) {
await this._session.send('Page.addScriptToEvaluateOnNewDocument', {
script: '',
worldName: name,
});
}
_onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) { _onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) {
const {executionContextId, auxData} = payload; const {executionContextId, auxData} = payload;
const frame = this._page._frameManager.frame(auxData ? auxData.frameId : null); const frame = this._page._frameManager.frame(auxData ? auxData.frameId : null);
@ -94,8 +102,10 @@ export class FFPage implements PageDelegate {
return; return;
const delegate = new FFExecutionContext(this._session, executionContextId); const delegate = new FFExecutionContext(this._session, executionContextId);
const context = new dom.FrameExecutionContext(delegate, frame); const context = new dom.FrameExecutionContext(delegate, frame);
frame._contextCreated('main', context); if (auxData.name === UTILITY_WORLD_NAME)
frame._contextCreated('utility', context); frame._contextCreated('utility', context);
else if (!auxData.name)
frame._contextCreated('main', context);
this._contextIdToContext.set(executionContextId, context); this._contextIdToContext.set(executionContextId, context);
} }
@ -351,8 +361,12 @@ export class FFPage implements PageDelegate {
} }
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> { async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {
assert(false, 'Multiple isolated worlds are not implemented'); const result = await this._session.send('Page.adoptNode', {
return handle; frameId: handle._context.frame._id,
objectId: toRemoteObject(handle).objectId!,
executionContextId: (to._delegate as FFExecutionContext)._executionContextId
});
return to._createHandle(result.remoteObject) as dom.ElementHandle<T>;
} }
async getAccessibilityTree(needle?: dom.ElementHandle) { async getAccessibilityTree(needle?: dom.ElementHandle) {

View file

@ -37,7 +37,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
await page.click('circle'); await page.click('circle');
expect(await page.evaluate(() => window.__CLICKED)).toBe(42); expect(await page.evaluate(() => window.__CLICKED)).toBe(42);
}); });
it.skip(FFOX)('should click the button if window.Node is removed', async({page, server}) => { it('should click the button if window.Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => delete window.Node); await page.evaluate(() => delete window.Node);
await page.click('button'); await page.click('button');

View file

@ -182,7 +182,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
await button.click(); await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked'); expect(await page.evaluate(() => result)).toBe('Clicked');
}); });
it.skip(FFOX)('should work with Node removed', async({page, server}) => { it('should work with Node removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => delete window['Node']); await page.evaluate(() => delete window['Node']);
const button = await page.$('button'); const button = await page.$('button');
@ -239,7 +239,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
await button.hover(); await button.hover();
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
}); });
it.skip(FFOX)('should work when Node is removed', async({page, server}) => { it('should work when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window['Node']); await page.evaluate(() => delete window['Node']);
const button = await page.$('#button-6'); const button = await page.$('#button-6');
@ -257,7 +257,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(Math.round(ratio * 10)).toBe(10 - i); expect(Math.round(ratio * 10)).toBe(10 - i);
} }
}); });
it.skip(FFOX)('should work when Node is removed', async({page, server}) => { it('should work when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/offscreenbuttons.html'); await page.goto(server.PREFIX + '/offscreenbuttons.html');
await page.evaluate(() => delete window['Node']); await page.evaluate(() => delete window['Node']);
for (let i = 0; i < 11; ++i) { for (let i = 0; i < 11; ++i) {
@ -290,7 +290,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
await handle.fill('some value'); await handle.fill('some value');
expect(await page.evaluate(() => result)).toBe('some value'); expect(await page.evaluate(() => result)).toBe('some value');
}); });
it.skip(FFOX)('should fill input when Node is removed', async({page, server}) => { it('should fill input when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
await page.evaluate(() => delete window['Node']); await page.evaluate(() => delete window['Node']);
const handle = await page.$('input'); const handle = await page.$('input');

View file

@ -98,7 +98,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT,
await page.hover('#button-91'); await page.hover('#button-91');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91'); expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91');
}); });
it.skip(FFOX)('should trigger hover state with removed window.Node', async({page, server}) => { it('should trigger hover state with removed window.Node', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window.Node); await page.evaluate(() => delete window.Node);
await page.hover('#button-6'); await page.hover('#button-6');

View file

@ -960,7 +960,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
expect(error.message).toContain('Indices must be numbers'); expect(error.message).toContain('Indices must be numbers');
}); });
// @see https://github.com/GoogleChrome/puppeteer/issues/3327 // @see https://github.com/GoogleChrome/puppeteer/issues/3327
it.skip(FFOX)('should work when re-defining top-level Event class', async({page, server}) => { it('should work when re-defining top-level Event class', async({page, server}) => {
await page.goto(server.PREFIX + '/input/select.html'); await page.goto(server.PREFIX + '/input/select.html');
await page.evaluate(() => window.Event = null); await page.evaluate(() => window.Event = null);
await page.select('select', 'blue'); await page.select('select', 'blue');

View file

@ -246,7 +246,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await frame.waitForSelector('div'); await frame.waitForSelector('div');
}); });
it.skip(FFOX)('should work with removed MutationObserver', async({page, server}) => { it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver); await page.evaluate(() => delete window.MutationObserver);
const [handle] = await Promise.all([ const [handle] = await Promise.all([
page.waitForSelector('.zombo'), page.waitForSelector('.zombo'),
@ -318,7 +318,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await waitForSelector; await waitForSelector;
expect(boxFound).toBe(true); expect(boxFound).toBe(true);
}); });
it.skip(FFOX)('should wait for visible', async({page, server}) => { it('should wait for visible', async({page, server}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('div').then(() => divFound = true); const waitForSelector = page.waitForSelector('div').then(() => divFound = true);
await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`); await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`);