Merge branch 'master' into ff-use-attach-to-bc

This commit is contained in:
Andrey Lushnikov 2020-02-11 18:43:25 -08:00 committed by GitHub
commit 61bc21dd7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 952 additions and 506 deletions

View file

@ -1 +1 @@
1025 1028

View file

@ -39,6 +39,7 @@ if [[ $OBJ_FOLDER == "" ]]; then
fi fi
./mach package ./mach package
node ../install-preferences.js $PWD/$OBJ_FOLDER/dist/firefox
if ! [[ -d $OBJ_FOLDER/dist/firefox ]]; then if ! [[ -d $OBJ_FOLDER/dist/firefox ]]; then
echo "ERROR: cannot find $OBJ_FOLDER/dist/firefox folder in the checkout/. Did you build?" echo "ERROR: cannot find $OBJ_FOLDER/dist/firefox folder in the checkout/. Did you build?"

View file

@ -37,3 +37,11 @@ else
fi fi
./mach build ./mach build
OBJ_FOLDER=$(ls -1 | grep obj-)
if [[ "$(uname)" == "Darwin" ]]; then
node ../install-preferences.js $PWD/$OBJ_FOLDER/dist
else
node ../install-preferences.js $PWD/$OBJ_FOLDER/dist/bin
fi

View file

@ -0,0 +1,96 @@
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const os = require('os');
const fs = require('fs');
const path = require('path');
const util = require('util');
const writeFileAsync = util.promisify(fs.writeFile.bind(fs));
const mkdirAsync = util.promisify(fs.mkdir.bind(fs));
// Install browser preferences after downloading and unpacking
// firefox instances.
// Based on: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Enterprise_deployment_before_60#Configuration
async function installFirefoxPreferences(distpath) {
let executablePath = '';
if (os.platform() === 'linux')
executablePath = path.join(distpath, 'firefox');
else if (os.platform() === 'darwin')
executablePath = path.join(distpath, 'Nightly.app', 'Contents', 'MacOS', 'firefox');
else if (os.platform() === 'win32')
executablePath = path.join(distpath, 'firefox.exe');
const firefoxFolder = path.dirname(executablePath);
let prefPath = '';
let configPath = '';
if (os.platform() === 'darwin') {
prefPath = path.join(firefoxFolder, '..', 'Resources', 'defaults', 'pref');
configPath = path.join(firefoxFolder, '..', 'Resources');
} else if (os.platform() === 'linux') {
if (!fs.existsSync(path.join(firefoxFolder, 'browser', 'defaults')))
await mkdirAsync(path.join(firefoxFolder, 'browser', 'defaults'));
if (!fs.existsSync(path.join(firefoxFolder, 'browser', 'defaults', 'preferences')))
await mkdirAsync(path.join(firefoxFolder, 'browser', 'defaults', 'preferences'));
prefPath = path.join(firefoxFolder, 'browser', 'defaults', 'preferences');
configPath = firefoxFolder;
} else if (os.platform() === 'win32') {
prefPath = path.join(firefoxFolder, 'defaults', 'pref');
configPath = firefoxFolder;
} else {
throw new Error('Unsupported platform: ' + os.platform());
}
await Promise.all([
copyFile({
from: path.join(__dirname, 'preferences', '00-playwright-prefs.js'),
to: path.join(prefPath, '00-playwright-prefs.js'),
}),
copyFile({
from: path.join(__dirname, 'preferences', 'playwright.cfg'),
to: path.join(configPath, 'playwright.cfg'),
}),
]);
}
function copyFile({from, to}) {
const rd = fs.createReadStream(from);
const wr = fs.createWriteStream(to);
return new Promise(function(resolve, reject) {
rd.on('error', reject);
wr.on('error', reject);
wr.on('finish', resolve);
rd.pipe(wr);
}).catch(function(error) {
rd.destroy();
wr.end();
throw error;
});
}
if (process.argv.length !== 3) {
console.log('ERROR: expected a path to the directory with browser build');
process.exit(1);
return;
}
installFirefoxPreferences(process.argv[2]).catch(error => {
console.error('ERROR: failed to put preferences!');
console.error(error);
process.exit(1);
});

View file

@ -469,10 +469,10 @@ index 6dca2b78830edc1ddbd66264bd332853729dac71..fbe89c9682834e11b9d9219d9eb056ed
diff --git a/testing/juggler/BrowserContextManager.js b/testing/juggler/BrowserContextManager.js diff --git a/testing/juggler/BrowserContextManager.js b/testing/juggler/BrowserContextManager.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c276337d8f0c0 index 0000000000000000000000000000000000000000..8f031b3f9afbb357a6bebc9938fca50a04d0421c
--- /dev/null --- /dev/null
+++ b/testing/juggler/BrowserContextManager.js +++ b/testing/juggler/BrowserContextManager.js
@@ -0,0 +1,174 @@ @@ -0,0 +1,180 @@
+"use strict"; +"use strict";
+ +
+const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm"); +const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");
@ -503,9 +503,8 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
+ } + }
+ +
+ constructor() { + constructor() {
+ this._browserContextIdToUserContextId = new Map(); + this._browserContextIdToBrowserContext = new Map();
+ this._userContextIdToBrowserContextId = new Map(); + this._userContextIdToBrowserContext = new Map();
+ this._principalsForBrowserContextId = new Map();
+ +
+ // Cleanup containers from previous runs (if any) + // Cleanup containers from previous runs (if any)
+ for (const identity of ContextualIdentityService.getPublicIdentities()) { + for (const identity of ContextualIdentityService.getPublicIdentities()) {
@ -514,66 +513,75 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
+ ContextualIdentityService.closeContainerTabs(identity.userContextId); + ContextualIdentityService.closeContainerTabs(identity.userContextId);
+ } + }
+ } + }
+
+ this._defaultContext = new BrowserContext(this, undefined, undefined);
+ } + }
+ +
+ grantPermissions(browserContextId, origin, permissions) { + createBrowserContext(options) {
+ const attrs = browserContextId ? {userContextId: this.userContextId(browserContextId)} : {}; + return new BrowserContext(this, helper.generateId(), options);
+ }
+
+ browserContextForId(browserContextId) {
+ return this._browserContextIdToBrowserContext.get(browserContextId);
+ }
+
+ browserContextForUserContextId(userContextId) {
+ return this._userContextIdToBrowserContext.get(userContextId);
+ }
+
+ getBrowserContexts() {
+ return Array.from(this._browserContextIdToBrowserContext.values());
+ }
+}
+
+class BrowserContext {
+ constructor(manager, browserContextId, options) {
+ this._manager = manager;
+ this.browserContextId = browserContextId;
+ this.userContextId = undefined;
+ if (browserContextId !== undefined) {
+ const identity = ContextualIdentityService.create(IDENTITY_NAME + browserContextId);
+ this.userContextId = identity.userContextId;
+ }
+ this._principals = [];
+ this._manager._browserContextIdToBrowserContext.set(this.browserContextId, this);
+ this._manager._userContextIdToBrowserContext.set(this.userContextId, this);
+ this.options = options || {};
+ }
+
+ destroy() {
+ if (this.userContextId !== undefined) {
+ ContextualIdentityService.remove(this.userContextId);
+ ContextualIdentityService.closeContainerTabs(this.userContextId);
+ }
+ this._manager._browserContextIdToBrowserContext.delete(this.browserContextId);
+ this._manager._userContextIdToBrowserContext.delete(this.userContextId);
+ }
+
+ grantPermissions(origin, permissions) {
+ const attrs = {userContextId: this.userContextId};
+ const principal = Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(origin), attrs); + const principal = Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(origin), attrs);
+ if (!this._principalsForBrowserContextId.has(browserContextId)) + this._principals.push(principal);
+ this._principalsForBrowserContextId.set(browserContextId, []);
+ this._principalsForBrowserContextId.get(browserContextId).push(principal);
+ for (const permission of ALL_PERMISSIONS) { + for (const permission of ALL_PERMISSIONS) {
+ const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION; + const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION;
+ Services.perms.addFromPrincipal(principal, permission, action); + Services.perms.addFromPrincipal(principal, permission, action);
+ } + }
+ } + }
+ +
+ resetPermissions(browserContextId) { + resetPermissions() {
+ if (!this._principalsForBrowserContextId.has(browserContextId)) + for (const principal of this._principals) {
+ return;
+ const principals = this._principalsForBrowserContextId.get(browserContextId);
+ for (const principal of principals) {
+ for (const permission of ALL_PERMISSIONS) + for (const permission of ALL_PERMISSIONS)
+ Services.perms.removeFromPrincipal(principal, permission); + Services.perms.removeFromPrincipal(principal, permission);
+ } + }
+ this._principalsForBrowserContextId.delete(browserContextId); + this._principals = [];
+ } + }
+ +
+ createBrowserContext() { + setCookies(cookies) {
+ const browserContextId = helper.generateId();
+ const identity = ContextualIdentityService.create(IDENTITY_NAME + browserContextId);
+ this._browserContextIdToUserContextId.set(browserContextId, identity.userContextId);
+ this._userContextIdToBrowserContextId.set(identity.userContextId, browserContextId);
+ return browserContextId;
+ }
+
+ browserContextId(userContextId) {
+ return this._userContextIdToBrowserContextId.get(userContextId);
+ }
+
+ userContextId(browserContextId) {
+ return this._browserContextIdToUserContextId.get(browserContextId);
+ }
+
+ removeBrowserContext(browserContextId) {
+ const userContextId = this._browserContextIdToUserContextId.get(browserContextId);
+ ContextualIdentityService.remove(userContextId);
+ ContextualIdentityService.closeContainerTabs(userContextId);
+ this._browserContextIdToUserContextId.delete(browserContextId);
+ this._userContextIdToBrowserContextId.delete(userContextId);
+ }
+
+ getBrowserContexts() {
+ return Array.from(this._browserContextIdToUserContextId.keys());
+ }
+
+ setCookies(browserContextId, cookies) {
+ const protocolToSameSite = { + const protocolToSameSite = {
+ [undefined]: Ci.nsICookie.SAMESITE_NONE, + [undefined]: Ci.nsICookie.SAMESITE_NONE,
+ 'Lax': Ci.nsICookie.SAMESITE_LAX, + 'Lax': Ci.nsICookie.SAMESITE_LAX,
+ 'Strict': Ci.nsICookie.SAMESITE_STRICT, + 'Strict': Ci.nsICookie.SAMESITE_STRICT,
+ }; + };
+ const userContextId = browserContextId ? this._browserContextIdToUserContextId.get(browserContextId) : undefined;
+ for (const cookie of cookies) { + for (const cookie of cookies) {
+ const uri = cookie.url ? NetUtil.newURI(cookie.url) : null; + const uri = cookie.url ? NetUtil.newURI(cookie.url) : null;
+ let domain = cookie.domain; + let domain = cookie.domain;
@ -599,19 +607,17 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
+ cookie.httpOnly || false, + cookie.httpOnly || false,
+ cookie.expires === undefined || cookie.expires === -1 /* isSession */, + cookie.expires === undefined || cookie.expires === -1 /* isSession */,
+ cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires, + cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires,
+ { userContextId } /* originAttributes */, + { userContextId: this.userContextId } /* originAttributes */,
+ protocolToSameSite[cookie.sameSite], + protocolToSameSite[cookie.sameSite],
+ ); + );
+ } + }
+ } + }
+ +
+ clearCookies(browserContextId) { + clearCookies() {
+ const userContextId = browserContextId ? this._browserContextIdToUserContextId.get(browserContextId) : undefined; + Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId: this.userContextId }));
+ Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId }));
+ } + }
+ +
+ getCookies(browserContextId) { + getCookies() {
+ const userContextId = browserContextId ? this._browserContextIdToUserContextId.get(browserContextId) : 0;
+ const result = []; + const result = [];
+ const sameSiteToProtocol = { + const sameSiteToProtocol = {
+ [Ci.nsICookie.SAMESITE_NONE]: 'None', + [Ci.nsICookie.SAMESITE_NONE]: 'None',
@ -619,7 +625,7 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
+ [Ci.nsICookie.SAMESITE_STRICT]: 'Strict', + [Ci.nsICookie.SAMESITE_STRICT]: 'Strict',
+ }; + };
+ for (let cookie of Services.cookies.cookies) { + for (let cookie of Services.cookies.cookies) {
+ if (cookie.originAttributes.userContextId !== userContextId) + if (cookie.originAttributes.userContextId !== (this.userContextId || 0))
+ continue; + continue;
+ if (cookie.host === 'addons.mozilla.org') + if (cookie.host === 'addons.mozilla.org')
+ continue; + continue;
@ -1444,10 +1450,10 @@ index 0000000000000000000000000000000000000000..66f61d432f9ad2f50931b780ec5ea0e3
+this.NetworkObserver = NetworkObserver; +this.NetworkObserver = NetworkObserver;
diff --git a/testing/juggler/TargetRegistry.js b/testing/juggler/TargetRegistry.js diff --git a/testing/juggler/TargetRegistry.js b/testing/juggler/TargetRegistry.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf043c5bb292 index 0000000000000000000000000000000000000000..2b1a1bd0f931d82824a86ecbb46f86483177a4e0
--- /dev/null --- /dev/null
+++ b/testing/juggler/TargetRegistry.js +++ b/testing/juggler/TargetRegistry.js
@@ -0,0 +1,196 @@ @@ -0,0 +1,214 @@
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); +const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -1481,9 +1487,14 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
+ this._tabToTarget = new Map(); + this._tabToTarget = new Map();
+ +
+ for (const tab of this._mainWindow.gBrowser.tabs) + for (const tab of this._mainWindow.gBrowser.tabs)
+ this._ensureTargetForTab(tab); + this._createTargetForTab(tab);
+ this._mainWindow.gBrowser.tabContainer.addEventListener('TabOpen', event => { + this._mainWindow.gBrowser.tabContainer.addEventListener('TabOpen', event => {
+ this._ensureTargetForTab(event.target); + const target = this._createTargetForTab(event.target);
+ // If we come here, content will have juggler script from the start,
+ // and we should wait for initial navigation, unless the tab was window.open'ed.
+ target._waitForInitialNavigation = !event.target.linkedBrowser.hasContentOpener;
+ // For pages created before we attach to them, we don't wait for initial
+ // navigation (target._waitForInitialNavigation is false by default).
+ }); + });
+ this._mainWindow.gBrowser.tabContainer.addEventListener('TabClose', event => { + this._mainWindow.gBrowser.tabContainer.addEventListener('TabClose', event => {
+ const tab = event.target; + const tab = event.target;
@ -1498,26 +1509,14 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
+ } + }
+ +
+ async newPage({browserContextId}) { + async newPage({browserContextId}) {
+ const browserContext = this._contextManager.browserContextForId(browserContextId);
+ const tab = this._mainWindow.gBrowser.addTab('about:blank', { + const tab = this._mainWindow.gBrowser.addTab('about:blank', {
+ userContextId: this._contextManager.userContextId(browserContextId), + userContextId: browserContext.userContextId,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ }); + });
+ this._mainWindow.gBrowser.selectedTab = tab; + this._mainWindow.gBrowser.selectedTab = tab;
+ // Await navigation to about:blank + const target = this._tabToTarget.get(tab);
+ await new Promise(resolve => { + await target._contentReadyPromise;
+ const wpl = {
+ onLocationChange: function(aWebProgress, aRequest, aLocation) {
+ tab.linkedBrowser.removeProgressListener(wpl);
+ resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ ]),
+ };
+ tab.linkedBrowser.addProgressListener(wpl);
+ });
+ const target = this._ensureTargetForTab(tab);
+ return target.id(); + return target.id();
+ } + }
+ +
@ -1550,30 +1549,31 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
+ return target._tab; + return target._tab;
+ } + }
+ +
+ _ensureTargetForTab(tab) { + targetForId(targetId) {
+ if (this._tabToTarget.has(tab)) + return this._targets.get(targetId);
+ return this._tabToTarget.get(tab); + }
+ const openerTarget = tab.openerTab ? this._ensureTargetForTab(tab.openerTab) : null;
+ const target = new PageTarget(this, tab, this._contextManager.browserContextId(tab.userContextId), openerTarget);
+ +
+ _createTargetForTab(tab) {
+ if (this._tabToTarget.has(tab))
+ throw new Error(`Internal error: two targets per tab`);
+ const openerTarget = tab.openerTab ? this._tabToTarget.get(tab.openerTab) : null;
+ const target = new PageTarget(this, tab, this._contextManager.browserContextForUserContextId(tab.userContextId), openerTarget);
+ this._targets.set(target.id(), target); + this._targets.set(target.id(), target);
+ this._tabToTarget.set(tab, target); + this._tabToTarget.set(tab, target);
+ this.emit(TargetRegistry.Events.TargetCreated, target.info()); + this.emit(TargetRegistry.Events.TargetCreated, target.info());
+ return target;
+ } + }
+} +}
+ +
+class PageTarget { +class PageTarget {
+ constructor(registry, tab, browserContextId, opener) { + constructor(registry, tab, browserContext, opener) {
+ this._targetId = helper.generateId(); + this._targetId = helper.generateId();
+ this._registry = registry; + this._registry = registry;
+ this._tab = tab; + this._tab = tab;
+ this._browserContextId = browserContextId; + this._browserContext = browserContext;
+ this._openerId = opener ? opener.id() : undefined; + this._openerId = opener ? opener.id() : undefined;
+ this._url = tab.linkedBrowser.currentURI.spec; + this._url = tab.linkedBrowser.currentURI.spec;
+ +
+ // First navigation always happens to about:blank - do not report it.
+ this._skipNextNavigation = true;
+
+ const navigationListener = { + const navigationListener = {
+ QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener]), + QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener]),
+ onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation), + onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
@ -1584,13 +1584,41 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
+ receiveMessage: () => this._onContentReady() + receiveMessage: () => this._onContentReady()
+ }), + }),
+ ]; + ];
+
+ this._contentReadyPromise = new Promise(f => this._contentReadyCallback = f);
+ this._waitForInitialNavigation = false;
+
+ if (browserContext && browserContext.options.viewport)
+ this.setViewportSize(browserContext.options.viewport.viewportSize);
+ }
+
+ setViewportSize(viewportSize) {
+ if (viewportSize) {
+ const {width, height} = viewportSize;
+ this._tab.linkedBrowser.style.setProperty('min-width', width + 'px');
+ this._tab.linkedBrowser.style.setProperty('min-height', height + 'px');
+ this._tab.linkedBrowser.style.setProperty('max-width', width + 'px');
+ this._tab.linkedBrowser.style.setProperty('max-height', height + 'px');
+ } else {
+ this._tab.linkedBrowser.style.removeProperty('min-width');
+ this._tab.linkedBrowser.style.removeProperty('min-height');
+ this._tab.linkedBrowser.style.removeProperty('max-width');
+ this._tab.linkedBrowser.style.removeProperty('max-height');
+ }
+ const rect = this._tab.linkedBrowser.getBoundingClientRect();
+ return { width: rect.width, height: rect.height };
+ } + }
+ +
+ _onContentReady() { + _onContentReady() {
+ const attachInfo = []; + const sessionIds = [];
+ const data = { attachInfo, targetInfo: this.info() }; + const data = { sessionIds, targetInfo: this.info() };
+ this._registry.emit(TargetRegistry.Events.PageTargetReady, data); + this._registry.emit(TargetRegistry.Events.PageTargetReady, data);
+ return attachInfo; + this._contentReadyCallback();
+ return {
+ browserContextOptions: this._browserContext ? this._browserContext.options : {},
+ waitForInitialNavigation: this._waitForInitialNavigation,
+ sessionIds
+ };
+ } + }
+ +
+ id() { + id() {
@ -1602,16 +1630,12 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
+ targetId: this.id(), + targetId: this.id(),
+ type: 'page', + type: 'page',
+ url: this._url, + url: this._url,
+ browserContextId: this._browserContextId, + browserContextId: this._browserContext ? this._browserContext.browserContextId : undefined,
+ openerId: this._openerId, + openerId: this._openerId,
+ }; + };
+ } + }
+ +
+ _onNavigated(aLocation) { + _onNavigated(aLocation) {
+ if (this._skipNextNavigation) {
+ this._skipNextNavigation = false;
+ return;
+ }
+ this._url = aLocation.spec; + this._url = aLocation.spec;
+ this._registry.emit(TargetRegistry.Events.TargetChanged, this.info()); + this._registry.emit(TargetRegistry.Events.TargetChanged, this.info());
+ } + }
@ -1792,10 +1816,10 @@ index 0000000000000000000000000000000000000000..268fbc361d8053182bb6c27f626e853d
+ +
diff --git a/testing/juggler/content/ContentSession.js b/testing/juggler/content/ContentSession.js diff --git a/testing/juggler/content/ContentSession.js b/testing/juggler/content/ContentSession.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab2514648a index 0000000000000000000000000000000000000000..3891da101e6906ae2a3888e256aefd03f724ab4b
--- /dev/null --- /dev/null
+++ b/testing/juggler/content/ContentSession.js +++ b/testing/juggler/content/ContentSession.js
@@ -0,0 +1,67 @@ @@ -0,0 +1,68 @@
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {RuntimeAgent} = ChromeUtils.import('chrome://juggler/content/content/RuntimeAgent.js'); +const {RuntimeAgent} = ChromeUtils.import('chrome://juggler/content/content/RuntimeAgent.js');
+const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js'); +const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
@ -1807,14 +1831,13 @@ index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab
+ * @param {string} sessionId + * @param {string} sessionId
+ * @param {!ContentFrameMessageManager} messageManager + * @param {!ContentFrameMessageManager} messageManager
+ * @param {!FrameTree} frameTree + * @param {!FrameTree} frameTree
+ * @param {!ScrollbarManager} scrollbarManager
+ * @param {!NetworkMonitor} networkMonitor + * @param {!NetworkMonitor} networkMonitor
+ */ + */
+ constructor(sessionId, messageManager, frameTree, scrollbarManager, networkMonitor) { + constructor(sessionId, messageManager, frameTree, networkMonitor) {
+ this._sessionId = sessionId; + this._sessionId = sessionId;
+ this._messageManager = messageManager; + this._messageManager = messageManager;
+ const runtimeAgent = new RuntimeAgent(this); + const runtimeAgent = new RuntimeAgent(this);
+ const pageAgent = new PageAgent(this, runtimeAgent, frameTree, scrollbarManager, networkMonitor); + const pageAgent = new PageAgent(this, runtimeAgent, frameTree, networkMonitor);
+ this._agents = { + this._agents = {
+ Page: pageAgent, + Page: pageAgent,
+ Runtime: runtimeAgent, + Runtime: runtimeAgent,
@ -1822,6 +1845,8 @@ index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab
+ this._eventListeners = [ + this._eventListeners = [
+ helper.addMessageListener(messageManager, this._sessionId, this._onMessage.bind(this)), + helper.addMessageListener(messageManager, this._sessionId, this._onMessage.bind(this)),
+ ]; + ];
+ runtimeAgent.enable();
+ pageAgent.enable();
+ } + }
+ +
+ emitEvent(eventName, params) { + emitEvent(eventName, params) {
@ -1865,10 +1890,10 @@ index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab
+ +
diff --git a/testing/juggler/content/FrameTree.js b/testing/juggler/content/FrameTree.js diff --git a/testing/juggler/content/FrameTree.js b/testing/juggler/content/FrameTree.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe1730d20bfa0 index 0000000000000000000000000000000000000000..dcebb7bbf6d0c9bb7a350443dfa2574bee5915ea
--- /dev/null --- /dev/null
+++ b/testing/juggler/content/FrameTree.js +++ b/testing/juggler/content/FrameTree.js
@@ -0,0 +1,242 @@ @@ -0,0 +1,252 @@
+"use strict"; +"use strict";
+const Ci = Components.interfaces; +const Ci = Components.interfaces;
+const Cr = Components.results; +const Cr = Components.results;
@ -1880,10 +1905,11 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
+const helper = new Helper(); +const helper = new Helper();
+ +
+class FrameTree { +class FrameTree {
+ constructor(rootDocShell) { + constructor(rootDocShell, waitForInitialNavigation) {
+ EventEmitter.decorate(this); + EventEmitter.decorate(this);
+ this._docShellToFrame = new Map(); + this._docShellToFrame = new Map();
+ this._frameIdToFrame = new Map(); + this._frameIdToFrame = new Map();
+ this._pageReady = !waitForInitialNavigation;
+ this._mainFrame = this._createFrame(rootDocShell); + this._mainFrame = this._createFrame(rootDocShell);
+ const webProgress = rootDocShell.QueryInterface(Ci.nsIInterfaceRequestor) + const webProgress = rootDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress); + .getInterface(Ci.nsIWebProgress);
@ -1902,6 +1928,10 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
+ ]; + ];
+ } + }
+ +
+ isPageReady() {
+ return this._pageReady;
+ }
+
+ frameForDocShell(docShell) { + frameForDocShell(docShell) {
+ return this._docShellToFrame.get(docShell) || null; + return this._docShellToFrame.get(docShell) || null;
+ } + }
@ -1960,6 +1990,10 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
+ frame._lastCommittedNavigationId = navigationId; + frame._lastCommittedNavigationId = navigationId;
+ frame._url = channel.URI.spec; + frame._url = channel.URI.spec;
+ this.emit(FrameTree.Events.NavigationCommitted, frame); + this.emit(FrameTree.Events.NavigationCommitted, frame);
+ if (frame === this._mainFrame && !this._pageReady) {
+ this._pageReady = true;
+ this.emit(FrameTree.Events.PageReady);
+ }
+ } else if (isStop && frame._pendingNavigationId && status) { + } else if (isStop && frame._pendingNavigationId && status) {
+ // Navigation is aborted. + // Navigation is aborted.
+ const navigationId = frame._pendingNavigationId; + const navigationId = frame._pendingNavigationId;
@ -2035,6 +2069,7 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
+ NavigationCommitted: 'navigationcommitted', + NavigationCommitted: 'navigationcommitted',
+ NavigationAborted: 'navigationaborted', + NavigationAborted: 'navigationaborted',
+ SameDocumentNavigation: 'samedocumentnavigation', + SameDocumentNavigation: 'samedocumentnavigation',
+ PageReady: 'pageready',
+}; +};
+ +
+class Frame { +class Frame {
@ -2181,10 +2216,10 @@ index 0000000000000000000000000000000000000000..2508cce41565023b7fee9c7b85afe8ec
+ +
diff --git a/testing/juggler/content/PageAgent.js b/testing/juggler/content/PageAgent.js diff --git a/testing/juggler/content/PageAgent.js b/testing/juggler/content/PageAgent.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a8aa1b032 index 0000000000000000000000000000000000000000..e505911e81ef014f19a3a732f3c5f631f0bd1780
--- /dev/null --- /dev/null
+++ b/testing/juggler/content/PageAgent.js +++ b/testing/juggler/content/PageAgent.js
@@ -0,0 +1,895 @@ @@ -0,0 +1,875 @@
+"use strict"; +"use strict";
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const Ci = Components.interfaces; +const Ci = Components.interfaces;
@ -2343,12 +2378,11 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
+} +}
+ +
+class PageAgent { +class PageAgent {
+ constructor(session, runtimeAgent, frameTree, scrollbarManager, networkMonitor) { + constructor(session, runtimeAgent, frameTree, networkMonitor) {
+ this._session = session; + this._session = session;
+ this._runtime = runtimeAgent; + this._runtime = runtimeAgent;
+ this._frameTree = frameTree; + this._frameTree = frameTree;
+ this._networkMonitor = networkMonitor; + this._networkMonitor = networkMonitor;
+ this._scrollbarManager = scrollbarManager;
+ +
+ this._frameData = new Map(); + this._frameData = new Map();
+ this._scriptsToEvaluateOnNewDocument = new Map(); + this._scriptsToEvaluateOnNewDocument = new Map();
@ -2399,14 +2433,6 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
+ return this._networkMonitor.requestDetails(channelId); + return this._networkMonitor.requestDetails(channelId);
+ } + }
+ +
+ async setViewport({deviceScaleFactor, isMobile, hasTouch}) {
+ const docShell = this._frameTree.mainFrame().docShell();
+ docShell.contentViewer.overrideDPPX = deviceScaleFactor || this._initialDPPX;
+ docShell.deviceSizeIsPageSize = isMobile;
+ docShell.touchEventsOverride = hasTouch ? Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED : Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_NONE;
+ this._scrollbarManager.setFloatingScrollbars(isMobile);
+ }
+
+ async setEmulatedMedia({type, colorScheme}) { + async setEmulatedMedia({type, colorScheme}) {
+ const docShell = this._frameTree.mainFrame().docShell(); + const docShell = this._frameTree.mainFrame().docShell();
+ const cv = docShell.contentViewer; + const cv = docShell.contentViewer;
@ -2421,16 +2447,6 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
+ } + }
+ } + }
+ +
+ async setUserAgent({userAgent}) {
+ const docShell = this._frameTree.mainFrame().docShell();
+ docShell.customUserAgent = userAgent;
+ }
+
+ async setBypassCSP({enabled}) {
+ const docShell = this._frameTree.mainFrame().docShell();
+ docShell.bypassCSPEnabled = enabled;
+ }
+
+ addScriptToEvaluateOnNewDocument({script, worldName}) { + addScriptToEvaluateOnNewDocument({script, worldName}) {
+ const scriptId = helper.generateId(); + const scriptId = helper.generateId();
+ this._scriptsToEvaluateOnNewDocument.set(scriptId, {script, worldName}); + this._scriptsToEvaluateOnNewDocument.set(scriptId, {script, worldName});
@ -2454,11 +2470,6 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
+ docShell.defaultLoadFlags = cacheDisabled ? disable : enable; + docShell.defaultLoadFlags = cacheDisabled ? disable : enable;
+ } + }
+ +
+ setJavascriptEnabled({enabled}) {
+ const docShell = this._frameTree.mainFrame().docShell();
+ docShell.allowJavascript = enabled;
+ }
+
+ enable() { + enable() {
+ if (this._enabled) + if (this._enabled)
+ return; + return;
@ -2486,11 +2497,15 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
+ helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)), + helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)),
+ helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)), + helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)),
+ helper.on(this._frameTree, 'samedocumentnavigation', this._onSameDocumentNavigation.bind(this)), + helper.on(this._frameTree, 'samedocumentnavigation', this._onSameDocumentNavigation.bind(this)),
+ helper.on(this._frameTree, 'pageready', () => this._session.emitEvent('Page.ready', {})),
+ ]; + ];
+ +
+ this._wdm.addListener(this._wdmListener); + this._wdm.addListener(this._wdmListener);
+ for (const workerDebugger of this._wdm.getWorkerDebuggerEnumerator()) + for (const workerDebugger of this._wdm.getWorkerDebuggerEnumerator())
+ this._onWorkerCreated(workerDebugger); + this._onWorkerCreated(workerDebugger);
+
+ if (this._frameTree.isPageReady())
+ this._session.emitEvent('Page.ready', {});
+ } + }
+ +
+ setInterceptFileChooserDialog({enabled}) { + setInterceptFileChooserDialog({enabled}) {
@ -3869,10 +3884,10 @@ index 0000000000000000000000000000000000000000..3a386425d3796d0a6786dea193b3402d
+ +
diff --git a/testing/juggler/content/main.js b/testing/juggler/content/main.js diff --git a/testing/juggler/content/main.js b/testing/juggler/content/main.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..6a9f908676fc025b74ea585a0e4e9194f704d13f index 0000000000000000000000000000000000000000..556f48d627401b8507b8bbec6dbf7ca797644baf
--- /dev/null --- /dev/null
+++ b/testing/juggler/content/main.js +++ b/testing/juggler/content/main.js
@@ -0,0 +1,56 @@ @@ -0,0 +1,76 @@
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {ContentSession} = ChromeUtils.import('chrome://juggler/content/content/ContentSession.js'); +const {ContentSession} = ChromeUtils.import('chrome://juggler/content/content/ContentSession.js');
+const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js'); +const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js');
@ -3880,17 +3895,15 @@ index 0000000000000000000000000000000000000000..6a9f908676fc025b74ea585a0e4e9194
+const {ScrollbarManager} = ChromeUtils.import('chrome://juggler/content/content/ScrollbarManager.js'); +const {ScrollbarManager} = ChromeUtils.import('chrome://juggler/content/content/ScrollbarManager.js');
+ +
+const sessions = new Map(); +const sessions = new Map();
+const frameTree = new FrameTree(docShell);
+const networkMonitor = new NetworkMonitor(docShell, frameTree);
+const scrollbarManager = new ScrollbarManager(docShell); +const scrollbarManager = new ScrollbarManager(docShell);
+ +let frameTree;
+let networkMonitor;
+const helper = new Helper(); +const helper = new Helper();
+const messageManager = this; +const messageManager = this;
+let gListeners;
+ +
+function createContentSession(sessionId) { +function createContentSession(sessionId) {
+ const session = new ContentSession(sessionId, messageManager, frameTree, scrollbarManager, networkMonitor); + sessions.set(sessionId, new ContentSession(sessionId, messageManager, frameTree, networkMonitor));
+ sessions.set(sessionId, session);
+ return session;
+} +}
+ +
+function disposeContentSession(sessionId) { +function disposeContentSession(sessionId) {
@ -3901,34 +3914,56 @@ index 0000000000000000000000000000000000000000..6a9f908676fc025b74ea585a0e4e9194
+ session.dispose(); + session.dispose();
+} +}
+ +
+const gListeners = [ +function initialize() {
+ helper.addMessageListener(messageManager, 'juggler:create-content-session', msg => { + let response = sendSyncMessage('juggler:content-ready', {})[0];
+ const sessionId = msg.data; + if (!response)
+ response = { sessionIds: [], browserContextOptions: {}, waitForInitialNavigation: false };
+
+ const { sessionIds, browserContextOptions, waitForInitialNavigation } = response;
+ const { userAgent, bypassCSP, javaScriptDisabled, viewport} = browserContextOptions;
+
+ if (userAgent !== undefined)
+ docShell.customUserAgent = userAgent;
+ if (bypassCSP !== undefined)
+ docShell.bypassCSPEnabled = bypassCSP;
+ if (javaScriptDisabled !== undefined)
+ docShell.allowJavascript = !javaScriptDisabled;
+ if (viewport !== undefined) {
+ docShell.contentViewer.overrideDPPX = viewport.deviceScaleFactor || this._initialDPPX;
+ docShell.deviceSizeIsPageSize = viewport.isMobile;
+ docShell.touchEventsOverride = viewport.hasTouch ? Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED : Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_NONE;
+ scrollbarManager.setFloatingScrollbars(viewport.isMobile);
+ }
+
+ frameTree = new FrameTree(docShell, waitForInitialNavigation);
+ networkMonitor = new NetworkMonitor(docShell, frameTree);
+ for (const sessionId of sessionIds)
+ createContentSession(sessionId); + createContentSession(sessionId);
+ }),
+ +
+ helper.addMessageListener(messageManager, 'juggler:dispose-content-session', msg => { + gListeners = [
+ const sessionId = msg.data; + helper.addMessageListener(messageManager, 'juggler:create-content-session', msg => {
+ disposeContentSession(sessionId); + const sessionId = msg.data;
+ }), + createContentSession(sessionId);
+ }),
+ +
+ helper.addEventListener(messageManager, 'unload', msg => { + helper.addMessageListener(messageManager, 'juggler:dispose-content-session', msg => {
+ helper.removeListeners(gListeners); + const sessionId = msg.data;
+ for (const session of sessions.values()) + disposeContentSession(sessionId);
+ session.dispose(); + }),
+ sessions.clear();
+ scrollbarManager.dispose();
+ networkMonitor.dispose();
+ frameTree.dispose();
+ }),
+];
+ +
+const [attachInfo] = sendSyncMessage('juggler:content-ready', {}); + helper.addEventListener(messageManager, 'unload', msg => {
+for (const { sessionId, messages } of attachInfo || []) { + helper.removeListeners(gListeners);
+ const session = createContentSession(sessionId); + for (const session of sessions.values())
+ for (const message of messages) + session.dispose();
+ session.handleMessage(message); + sessions.clear();
+ scrollbarManager.dispose();
+ networkMonitor.dispose();
+ frameTree.dispose();
+ }),
+ ];
+} +}
+
+initialize();
diff --git a/testing/juggler/jar.mn b/testing/juggler/jar.mn diff --git a/testing/juggler/jar.mn b/testing/juggler/jar.mn
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..76377927a8c9af3cac3b028ff754491966d03ba3 index 0000000000000000000000000000000000000000..76377927a8c9af3cac3b028ff754491966d03ba3
@ -4009,10 +4044,10 @@ index 0000000000000000000000000000000000000000..a2d3b79469566ca2edb7d864621f7085
+this.AccessibilityHandler = AccessibilityHandler; +this.AccessibilityHandler = AccessibilityHandler;
diff --git a/testing/juggler/protocol/BrowserHandler.js b/testing/juggler/protocol/BrowserHandler.js diff --git a/testing/juggler/protocol/BrowserHandler.js b/testing/juggler/protocol/BrowserHandler.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f350475551a73e index 0000000000000000000000000000000000000000..6b42032e8f6d39025f455300d376084826a781cc
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/BrowserHandler.js +++ b/testing/juggler/protocol/BrowserHandler.js
@@ -0,0 +1,72 @@ @@ -0,0 +1,73 @@
+"use strict"; +"use strict";
+ +
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -4042,7 +4077,7 @@ index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f35047
+ +
+ async setIgnoreHTTPSErrors({enabled}) { + async setIgnoreHTTPSErrors({enabled}) {
+ if (!enabled) { + if (!enabled) {
+ allowAllCerts.disable() + allowAllCerts.disable()
+ Services.prefs.setBoolPref('security.mixed_content.block_active_content', true); + Services.prefs.setBoolPref('security.mixed_content.block_active_content', true);
+ } else { + } else {
+ allowAllCerts.enable() + allowAllCerts.enable()
@ -4051,23 +4086,24 @@ index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f35047
+ } + }
+ +
+ grantPermissions({browserContextId, origin, permissions}) { + grantPermissions({browserContextId, origin, permissions}) {
+ this._contextManager.grantPermissions(browserContextId, origin, permissions); + this._contextManager.browserContextForId(browserContextId).grantPermissions(origin, permissions);
+ } + }
+ +
+ resetPermissions({browserContextId}) { + resetPermissions({browserContextId}) {
+ this._contextManager.resetPermissions(browserContextId); + this._contextManager.browserContextForId(browserContextId).resetPermissions();
+ } + }
+ +
+ setCookies({browserContextId, cookies}) { + setCookies({browserContextId, cookies}) {
+ this._contextManager.setCookies(browserContextId, cookies); + this._contextManager.browserContextForId(browserContextId).setCookies(cookies);
+ } + }
+ +
+ clearCookies({browserContextId}) { + clearCookies({browserContextId}) {
+ this._contextManager.clearCookies(browserContextId); + this._contextManager.browserContextForId(browserContextId).clearCookies();
+ } + }
+ +
+ getCookies({browserContextId}) { + getCookies({browserContextId}) {
+ return {cookies: this._contextManager.getCookies(browserContextId)}; + const cookies = this._contextManager.browserContextForId(browserContextId).getCookies();
+ return {cookies};
+ } + }
+ +
+ async getInfo() { + async getInfo() {
@ -4087,10 +4123,10 @@ index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f35047
+this.BrowserHandler = BrowserHandler; +this.BrowserHandler = BrowserHandler;
diff --git a/testing/juggler/protocol/Dispatcher.js b/testing/juggler/protocol/Dispatcher.js diff --git a/testing/juggler/protocol/Dispatcher.js b/testing/juggler/protocol/Dispatcher.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd1c8b18c9 index 0000000000000000000000000000000000000000..5c5a73b35cd178b51899ab3dd681d46b6c3e4770
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/Dispatcher.js +++ b/testing/juggler/protocol/Dispatcher.js
@@ -0,0 +1,254 @@ @@ -0,0 +1,265 @@
+const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js"); +const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js");
+const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/protocol/Protocol.js"); +const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/protocol/Protocol.js");
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
@ -4123,7 +4159,7 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
+ ]; + ];
+ } + }
+ +
+ createSession(targetId) { + createSession(targetId, shouldConnect) {
+ const targetInfo = TargetRegistry.instance().targetInfo(targetId); + const targetInfo = TargetRegistry.instance().targetInfo(targetId);
+ if (!targetInfo) + if (!targetInfo)
+ throw new Error(`Target "${targetId}" is not found`); + throw new Error(`Target "${targetId}" is not found`);
@ -4135,6 +4171,8 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
+ +
+ const sessionId = helper.generateId(); + const sessionId = helper.generateId();
+ const contentSession = targetInfo.type === 'page' ? new ContentSession(this, sessionId, targetInfo) : null; + const contentSession = targetInfo.type === 'page' ? new ContentSession(this, sessionId, targetInfo) : null;
+ if (shouldConnect && contentSession)
+ contentSession.connect();
+ const chromeSession = new ChromeSession(this, sessionId, contentSession, targetInfo); + const chromeSession = new ChromeSession(this, sessionId, contentSession, targetInfo);
+ targetSessions.set(sessionId, chromeSession); + targetSessions.set(sessionId, chromeSession);
+ this._sessions.set(sessionId, chromeSession); + this._sessions.set(sessionId, chromeSession);
@ -4234,6 +4272,12 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
+ if (protocol.domains[domainName].targets.includes(targetInfo.type)) + if (protocol.domains[domainName].targets.includes(targetInfo.type))
+ this._handlers[domainName] = new handlerFactory(this, contentSession); + this._handlers[domainName] = new handlerFactory(this, contentSession);
+ } + }
+ const pageHandler = this._handlers['Page'];
+ if (pageHandler)
+ pageHandler.enable();
+ const networkHandler = this._handlers['Network'];
+ if (networkHandler)
+ networkHandler.enable();
+ } + }
+ +
+ dispatcher() { + dispatcher() {
@ -4284,7 +4328,6 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
+ this._messageId = 0; + this._messageId = 0;
+ this._pendingMessages = new Map(); + this._pendingMessages = new Map();
+ this._sessionId = sessionId; + this._sessionId = sessionId;
+ this._browser.messageManager.sendAsyncMessage('juggler:create-content-session', this._sessionId);
+ this._disposed = false; + this._disposed = false;
+ this._eventListeners = [ + this._eventListeners = [
+ helper.addMessageListener(this._browser.messageManager, this._sessionId, { + helper.addMessageListener(this._browser.messageManager, this._sessionId, {
@ -4293,6 +4336,10 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
+ ]; + ];
+ } + }
+ +
+ connect() {
+ this._browser.messageManager.sendAsyncMessage('juggler:create-content-session', this._sessionId);
+ }
+
+ isDisposed() { + isDisposed() {
+ return this._disposed; + return this._disposed;
+ } + }
@ -4519,10 +4566,10 @@ index 0000000000000000000000000000000000000000..5d776ab6f28ccff44ef4663e8618ad9c
+this.NetworkHandler = NetworkHandler; +this.NetworkHandler = NetworkHandler;
diff --git a/testing/juggler/protocol/PageHandler.js b/testing/juggler/protocol/PageHandler.js diff --git a/testing/juggler/protocol/PageHandler.js b/testing/juggler/protocol/PageHandler.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..e9c5d94cf65b44d57bdb21ec892c3e325220a879 index 0000000000000000000000000000000000000000..efb0fc1f3f7af37e101976cf8a682e09c223e59f
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/PageHandler.js +++ b/testing/juggler/protocol/PageHandler.js
@@ -0,0 +1,285 @@ @@ -0,0 +1,266 @@
+"use strict"; +"use strict";
+ +
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
@ -4540,6 +4587,7 @@ index 0000000000000000000000000000000000000000..e9c5d94cf65b44d57bdb21ec892c3e32
+ constructor(chromeSession, contentSession) { + constructor(chromeSession, contentSession) {
+ this._chromeSession = chromeSession; + this._chromeSession = chromeSession;
+ this._contentSession = contentSession; + this._contentSession = contentSession;
+ this._pageTarget = TargetRegistry.instance().targetForId(chromeSession.targetId());
+ this._browser = TargetRegistry.instance().tabForTarget(chromeSession.targetId()).linkedBrowser; + this._browser = TargetRegistry.instance().tabForTarget(chromeSession.targetId()).linkedBrowser;
+ this._dialogs = new Map(); + this._dialogs = new Map();
+ +
@ -4568,38 +4616,18 @@ index 0000000000000000000000000000000000000000..e9c5d94cf65b44d57bdb21ec892c3e32
+ }), + }),
+ helper.addEventListener(this._browser, 'DOMModalDialogClosed', event => this._updateModalDialogs()), + helper.addEventListener(this._browser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
+ ]; + ];
+ await this._contentSession.send('Page.enable');
+ } + }
+ +
+ dispose() { + dispose() {
+ helper.removeListeners(this._eventListeners); + helper.removeListeners(this._eventListeners);
+ } + }
+ +
+ async setViewport({viewport}) { + async setViewportSize({viewportSize}) {
+ if (viewport) { + const size = this._pageTarget.setViewportSize(viewportSize);
+ const {width, height} = viewport; + await this._contentSession.send('Page.awaitViewportDimensions', {
+ this._browser.style.setProperty('min-width', width + 'px'); + width: size.width,
+ this._browser.style.setProperty('min-height', height + 'px'); + height: size.height
+ this._browser.style.setProperty('max-width', width + 'px'); + });
+ this._browser.style.setProperty('max-height', height + 'px');
+ } else {
+ this._browser.style.removeProperty('min-width');
+ this._browser.style.removeProperty('min-height');
+ this._browser.style.removeProperty('max-width');
+ this._browser.style.removeProperty('max-height');
+ }
+ const dimensions = this._browser.getBoundingClientRect();
+ await Promise.all([
+ this._contentSession.send('Page.setViewport', {
+ deviceScaleFactor: viewport ? viewport.deviceScaleFactor : 0,
+ isMobile: viewport && viewport.isMobile,
+ hasTouch: viewport && viewport.hasTouch,
+ }),
+ this._contentSession.send('Page.awaitViewportDimensions', {
+ width: dimensions.width,
+ height: dimensions.height
+ }),
+ ]);
+ } + }
+ +
+ _updateModalDialogs() { + _updateModalDialogs() {
@ -4959,10 +4987,10 @@ index 0000000000000000000000000000000000000000..78b6601b91d0b7fcda61114e6846aa07
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme']; +this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497a16559ea index 0000000000000000000000000000000000000000..a0a96a87ff4a422deccae1045962690fa7941f25
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/Protocol.js +++ b/testing/juggler/protocol/Protocol.js
@@ -0,0 +1,755 @@ @@ -0,0 +1,746 @@
+const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js'); +const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
+ +
+// Protocol-specific types. +// Protocol-specific types.
@ -5016,13 +5044,16 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ height: t.Number, + height: t.Number,
+}; +};
+ +
+pageTypes.Viewport = { +pageTypes.Size = {
+ width: t.Number, + width: t.Number,
+ height: t.Number, + height: t.Number,
+};
+
+pageTypes.Viewport = {
+ viewportSize: pageTypes.Size,
+ deviceScaleFactor: t.Number, + deviceScaleFactor: t.Number,
+ isMobile: t.Boolean, + isMobile: t.Boolean,
+ hasTouch: t.Boolean, + hasTouch: t.Boolean,
+ isLandscape: t.Boolean,
+}; +};
+ +
+pageTypes.DOMQuad = { +pageTypes.DOMQuad = {
@ -5218,6 +5249,9 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ params: { + params: {
+ removeOnDetach: t.Optional(t.Boolean), + removeOnDetach: t.Optional(t.Boolean),
+ userAgent: t.Optional(t.String), + userAgent: t.Optional(t.String),
+ bypassCSP: t.Optional(t.Boolean),
+ javaScriptDisabled: t.Optional(t.Boolean),
+ viewport: t.Optional(pageTypes.Viewport),
+ }, + },
+ returns: { + returns: {
+ browserContextId: t.String, + browserContextId: t.String,
@ -5288,7 +5322,6 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ }, + },
+ }, + },
+ methods: { + methods: {
+ 'enable': {},
+ 'setRequestInterception': { + 'setRequestInterception': {
+ params: { + params: {
+ enabled: t.Boolean, + enabled: t.Boolean,
@ -5359,9 +5392,6 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ }, + },
+ }, + },
+ methods: { + methods: {
+ 'enable': {
+ params: {},
+ },
+ 'evaluate': { + 'evaluate': {
+ params: { + params: {
+ // Pass frameId here. + // Pass frameId here.
@ -5414,6 +5444,8 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ +
+ types: pageTypes, + types: pageTypes,
+ events: { + events: {
+ 'ready': {
+ },
+ 'eventFired': { + 'eventFired': {
+ frameId: t.String, + frameId: t.String,
+ name: t.Enum(['load', 'DOMContentLoaded']), + name: t.Enum(['load', 'DOMContentLoaded']),
@ -5485,9 +5517,6 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ }, + },
+ +
+ methods: { + methods: {
+ 'enable': {
+ params: {},
+ },
+ 'close': { + 'close': {
+ params: { + params: {
+ runBeforeUnload: t.Optional(t.Boolean), + runBeforeUnload: t.Optional(t.Boolean),
@ -5505,9 +5534,9 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ name: t.String, + name: t.String,
+ }, + },
+ }, + },
+ 'setViewport': { + 'setViewportSize': {
+ params: { + params: {
+ viewport: t.Nullable(pageTypes.Viewport), + viewportSize: t.Nullable(pageTypes.Size),
+ }, + },
+ }, + },
+ 'setEmulatedMedia': { + 'setEmulatedMedia': {
@ -5516,21 +5545,11 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+ colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])), + colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])),
+ }, + },
+ }, + },
+ 'setBypassCSP': {
+ params: {
+ enabled: t.Boolean
+ }
+ },
+ 'setCacheDisabled': { + 'setCacheDisabled': {
+ params: { + params: {
+ cacheDisabled: t.Boolean, + cacheDisabled: t.Boolean,
+ }, + },
+ }, + },
+ 'setJavascriptEnabled': {
+ params: {
+ enabled: t.Boolean,
+ },
+ },
+ 'describeNode': { + 'describeNode': {
+ params: { + params: {
+ frameId: t.String, + frameId: t.String,
@ -5720,10 +5739,10 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
+this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme']; +this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme'];
diff --git a/testing/juggler/protocol/RuntimeHandler.js b/testing/juggler/protocol/RuntimeHandler.js diff --git a/testing/juggler/protocol/RuntimeHandler.js b/testing/juggler/protocol/RuntimeHandler.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..0026e8ff58ef6268f4c63783d0ff68ff355b1e72 index 0000000000000000000000000000000000000000..089e66c617f114fcb32b3cea20abc6fb80e26a1e
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/RuntimeHandler.js +++ b/testing/juggler/protocol/RuntimeHandler.js
@@ -0,0 +1,41 @@ @@ -0,0 +1,37 @@
+"use strict"; +"use strict";
+ +
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); +const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
@ -5740,10 +5759,6 @@ index 0000000000000000000000000000000000000000..0026e8ff58ef6268f4c63783d0ff68ff
+ this._contentSession = contentSession; + this._contentSession = contentSession;
+ } + }
+ +
+ async enable(options) {
+ return await this._contentSession.send('Runtime.enable', options);
+ }
+
+ async evaluate(options) { + async evaluate(options) {
+ return await this._contentSession.send('Runtime.evaluate', options); + return await this._contentSession.send('Runtime.evaluate', options);
+ } + }
@ -5767,10 +5782,10 @@ index 0000000000000000000000000000000000000000..0026e8ff58ef6268f4c63783d0ff68ff
+this.RuntimeHandler = RuntimeHandler; +this.RuntimeHandler = RuntimeHandler;
diff --git a/testing/juggler/protocol/TargetHandler.js b/testing/juggler/protocol/TargetHandler.js diff --git a/testing/juggler/protocol/TargetHandler.js b/testing/juggler/protocol/TargetHandler.js
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c33212f75 index 0000000000000000000000000000000000000000..4795a4ddecdd016d6efbcde35aa7321af17cd7dc
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/TargetHandler.js +++ b/testing/juggler/protocol/TargetHandler.js
@@ -0,0 +1,104 @@ @@ -0,0 +1,100 @@
+"use strict"; +"use strict";
+ +
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -5789,34 +5804,34 @@ index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c
+ this._targetRegistry = TargetRegistry.instance(); + this._targetRegistry = TargetRegistry.instance();
+ this._enabled = false; + this._enabled = false;
+ this._eventListeners = []; + this._eventListeners = [];
+ this._createdBrowserContextOptions = new Map(); + this._createdBrowserContextIds = new Set();
+ } + }
+ +
+ async attachToTarget({targetId}) { + async attachToTarget({targetId}) {
+ if (!this._enabled) + if (!this._enabled)
+ throw new Error('Target domain is not enabled'); + throw new Error('Target domain is not enabled');
+ const sessionId = this._session.dispatcher().createSession(targetId); + const sessionId = this._session.dispatcher().createSession(targetId, true /* shouldConnect */);
+ return {sessionId}; + return {sessionId};
+ } + }
+ +
+ async createBrowserContext(options) { + async createBrowserContext(options) {
+ if (!this._enabled) + if (!this._enabled)
+ throw new Error('Target domain is not enabled'); + throw new Error('Target domain is not enabled');
+ const browserContextId = this._contextManager.createBrowserContext(); + const browserContext = this._contextManager.createBrowserContext(options);
+ // TODO: introduce BrowserContext class, with options? + this._createdBrowserContextIds.add(browserContext.browserContextId);
+ this._createdBrowserContextOptions.set(browserContextId, options); + return {browserContextId: browserContext.browserContextId};
+ return {browserContextId};
+ } + }
+ +
+ async removeBrowserContext({browserContextId}) { + async removeBrowserContext({browserContextId}) {
+ if (!this._enabled) + if (!this._enabled)
+ throw new Error('Target domain is not enabled'); + throw new Error('Target domain is not enabled');
+ this._createdBrowserContextOptions.delete(browserContextId); + this._createdBrowserContextIds.delete(browserContextId);
+ this._contextManager.removeBrowserContext(browserContextId); + this._contextManager.browserContextForId(browserContextId).destroy();
+ } + }
+ +
+ async getBrowserContexts() { + async getBrowserContexts() {
+ return {browserContextIds: this._contextManager.getBrowserContexts()}; + const browserContexts = this._contextManager.getBrowserContexts();
+ return {browserContextIds: browserContexts.map(bc => bc.browserContextId)};
+ } + }
+ +
+ async enable() { + async enable() {
@ -5836,9 +5851,10 @@ index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c
+ +
+ dispose() { + dispose() {
+ helper.removeListeners(this._eventListeners); + helper.removeListeners(this._eventListeners);
+ for (const [browserContextId, options] of this._createdBrowserContextOptions) { + for (const browserContextId of this._createdBrowserContextIds) {
+ if (options.removeOnDetach) + const browserContext = this._contextManager.browserContextForId(browserContextId);
+ this._contextManager.removeBrowserContext(browserContextId); + if (browserContext.options.removeOnDetach)
+ browserContext.destroy();
+ } + }
+ this._createdBrowserContextOptions.clear(); + this._createdBrowserContextOptions.clear();
+ } + }
@ -5855,16 +5871,11 @@ index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c
+ this._session.emitEvent('Target.targetDestroyed', targetInfo); + this._session.emitEvent('Target.targetDestroyed', targetInfo);
+ } + }
+ +
+ _onPageTargetReady({attachInfo, targetInfo}) { + _onPageTargetReady({sessionIds, targetInfo}) {
+ const options = this._createdBrowserContextOptions.get(targetInfo.browserContextId); + if (!this._createdBrowserContextIds.has(targetInfo.browserContextId))
+ if (!options)
+ return; + return;
+ const sessionId = this._session.dispatcher().createSession(targetInfo.targetId); + const sessionId = this._session.dispatcher().createSession(targetInfo.targetId, false /* shouldConnect */);
+ const messages = []; + sessionIds.push(sessionId);
+ // TODO: perhaps, we should just have a single message 'initBrowserContextOptions'.
+ if (options.userAgent !== undefined)
+ messages.push({ id: 0, methodName: 'Page.setUserAgent', params: { userAgent: options.userAgent } });
+ attachInfo.push({ sessionId, messages });
+ } + }
+ +
+ async newPage({browserContextId}) { + async newPage({browserContextId}) {

View file

@ -0,0 +1,3 @@
// Any comment. You must start the file with a single-line comment!
pref("general.config.filename", "playwright.cfg");
pref("general.config.obscure_value", 0);

View file

@ -0,0 +1,233 @@
// Any comment. You must start the file with a comment!
// Make sure Shield doesn't hit the network.
pref("app.normandy.api_url", "");
pref("app.normandy.enabled", false);
// Disable updater
pref("app.update.enabled", false);
// Disable Firefox old build background check
pref("app.update.checkInstallTime", false);
// Disable automatically upgrading Firefox
pref("app.update.disabledForTesting", true);
// make absolutely sure it is really off
pref("app.update.auto", false);
pref("app.update.mode", 0);
pref("app.update.service.enabled", false);
// Dislabe newtabpage
pref("browser.startup.homepage", "about:blank");
pref("browser.startup.page", 0);
pref("browser.newtabpage.enabled", false);
// Do not redirect user when a milstone upgrade of Firefox is detected
pref("browser.startup.homepage_override.mstone", "ignore");
// Disable topstories
pref("browser.newtabpage.activity-stream.feeds.section.topstories", false);
// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
// This doesn't affect Puppeteer operations, but spams console with a lot of
// unpleasant errors.
// (bug 1424372)
pref("devtools.jsonview.enabled", false);
// Prevent various error message on the console
pref("browser.contentblocking.features.standard", "-tp,tpPrivate,cookieBehavior0,-cm,-fp");
pref("network.cookie.cookieBehavior", 0);
// Increase the APZ content response timeout in tests to 1 minute.
// This is to accommodate the fact that test environments tends to be
// slower than production environments (with the b2g emulator being
// the slowest of them all), resulting in the production timeout value
// sometimes being exceeded and causing false-positive test failures.
//
// (bug 1176798, bug 1177018, bug 1210465)
pref("apz.content_response_timeout", 60000);
// Allow creating files in content process - required for
// |Page.setFileInputFiles| protocol method.
pref("dom.file.createInChild", true);
// Indicate that the download panel has been shown once so that
// whichever download test runs first doesn't show the popup
// inconsistently.
pref("browser.download.panel.shown", true);
// Background thumbnails in particular cause grief, and disabling
// thumbnails in general cannot hurt
pref("browser.pagethumbnails.capturing_disabled", true);
// Disable safebrowsing components.
pref("browser.safebrowsing.blockedURIs.enabled", false);
pref("browser.safebrowsing.downloads.enabled", false);
pref("browser.safebrowsing.passwords.enabled", false);
pref("browser.safebrowsing.malware.enabled", false);
pref("browser.safebrowsing.phishing.enabled", false);
// Disable updates to search engines.
pref("browser.search.update", false);
// Do not restore the last open set of tabs if the browser has crashed
pref("browser.sessionstore.resume_from_crash", false);
// Don't check for the default web browser during startup.
pref("browser.shell.checkDefaultBrowser", false);
// Disable browser animations (tabs, fullscreen, sliding alerts)
pref("toolkit.cosmeticAnimations.enabled", false);
// Close the window when the last tab gets closed
pref("browser.tabs.closeWindowWithLastTab", true);
// Do not allow background tabs to be zombified on Android, otherwise for
// tests that open additional tabs, the test harness tab itself might get
// unloaded
pref("browser.tabs.disableBackgroundZombification", false);
// Do not warn when closing all open tabs
pref("browser.tabs.warnOnClose", false);
// Do not warn when closing all other open tabs
pref("browser.tabs.warnOnCloseOtherTabs", false);
// Do not warn when multiple tabs will be opened
pref("browser.tabs.warnOnOpen", false);
// Disable first run splash page on Windows 10
pref("browser.usedOnWindows10.introURL", "");
// Disable the UI tour.
//
// Should be set in profile.
pref("browser.uitour.enabled", false);
// Turn off search suggestions in the location bar so as not to trigger
// network connections.
pref("browser.urlbar.suggest.searches", false);
// Do not warn on quitting Firefox
pref("browser.warnOnQuit", false);
// Do not show datareporting policy notifications which can
// interfere with tests
pref("datareporting.healthreport.documentServerURI", "http://dummydummy.test/dummy/healthreport/");
pref("datareporting.healthreport.about.reportUrl", "http://dummydummy.test/dummy/abouthealthreport/");
pref("datareporting.healthreport.logging.consoleEnabled", false);
pref("datareporting.healthreport.service.enabled", false);
pref("datareporting.healthreport.service.firstRun", false);
pref("datareporting.healthreport.uploadEnabled", false);
pref("datareporting.policy.dataSubmissionEnabled", false);
pref("datareporting.policy.dataSubmissionPolicyAccepted", false);
pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
// Automatically unload beforeunload alerts
pref("dom.disable_beforeunload", false);
// Disable popup-blocker
pref("dom.disable_open_during_load", false);
// Disable the ProcessHangMonitor
pref("dom.ipc.reportProcessHangs", false);
pref("hangmonitor.timeout", 0);
// Disable slow script dialogues
pref("dom.max_chrome_script_run_time", 0);
pref("dom.max_script_run_time", 0);
// Only load extensions from the application and user profile
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
pref("extensions.autoDisableScopes", 0);
pref("extensions.enabledScopes", 5);
// Disable metadata caching for installed add-ons by default
pref("extensions.getAddons.cache.enabled", false);
// Disable installing any distribution extensions or add-ons.
pref("extensions.installDistroAddons", false);
// Turn off extension updates so they do not bother tests
pref("extensions.update.enabled", false);
pref("extensions.update.notifyUser", false);
// Make sure opening about:addons will not hit the network
pref("extensions.webservice.discoverURL", "http://dummydummy.test/dummy/discoveryURL");
pref("extensions.screenshots.disabled", true);
pref("extensions.screenshots.upload-disabled", true);
// Allow the application to have focus even it runs in the background
pref("focusmanager.testmode", true);
// Disable useragent updates
pref("general.useragent.updates.enabled", false);
// No ICC color correction.
// See https://developer.mozilla.org/en/docs/Mozilla/Firefox/Releases/3.5/ICC_color_correction_in_Firefox.
pref("gfx.color_management.mode", 0);
pref("gfx.color_management.rendering_intent", 3);
// Always use network provider for geolocation tests so we bypass the
// macOS dialog raised by the corelocation provider
pref("geo.provider.testing", true);
// Do not scan Wifi
pref("geo.wifi.scan", false);
// Show chrome errors and warnings in the error console
pref("javascript.options.showInConsole", true);
// Disable download and usage of OpenH264: and Widevine plugins
pref("media.gmp-manager.updateEnabled", false);
// Do not prompt with long usernames or passwords in URLs
pref("network.http.phishy-userpass-length", 255);
// Do not prompt for temporary redirects
pref("network.http.prompt-temp-redirect", false);
// Disable speculative connections so they are not reported as leaking
// when they are hanging around
pref("network.http.speculative-parallel-limit", 0);
// Do not automatically switch between offline and online
pref("network.manage-offline-status", false);
// Make sure SNTP requests do not hit the network
pref("network.sntp.pools", "dummydummy.test");
// Disable Flash
pref("plugin.state.flash", 0);
pref("privacy.trackingprotection.enabled", false);
pref("security.certerrors.mitm.priming.enabled", false);
// Local documents have access to all other local documents,
// including directory listings
pref("security.fileuri.strict_origin_policy", false);
// Tests do not wait for the notification button security delay
pref("security.notification_enable_delay", 0);
// Ensure blocklist updates do not hit the network
pref("services.settings.server", "http://dummydummy.test/dummy/blocklist/");
// Disable DocumentChannel.
// See https://github.com/microsoft/playwright/pull/451
pref("browser.tabs.documentchannel", false);
// Do not automatically fill sign-in forms with known usernames and
// passwords
pref("signon.autofillForms", false);
// Disable password capture, so that tests that include forms are not
// influenced by the presence of the persistent doorhanger notification
pref("signon.rememberSignons", false);
// Disable first-run welcome page
pref("startup.homepage_welcome_url", "about:blank");
pref("startup.homepage_welcome_url.additional", "");
// Prevent starting into safe mode after application crashes
pref("toolkit.startup.max_resumed_crashes", -1);
lockPref("toolkit.crashreporter.enabled", false);
pref("toolkit.telemetry.enabled", false);
pref("toolkit.telemetry.server", "https://dummydummy.test/dummy/telemetry");
// Disable crash reporter.
Components.classes["@mozilla.org/toolkit/crash-reporter;1"].getService(Components.interfaces.nsICrashReporter).submitReports = false;

View file

@ -1 +1 @@
1141 1143

View file

@ -731,10 +731,10 @@ index 0000000000000000000000000000000000000000..79edea03fed4e9be5da96e1275e182a4
+} +}
diff --git a/Source/JavaScriptCore/inspector/protocol/Emulation.json b/Source/JavaScriptCore/inspector/protocol/Emulation.json diff --git a/Source/JavaScriptCore/inspector/protocol/Emulation.json b/Source/JavaScriptCore/inspector/protocol/Emulation.json
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..bcf863e4bba3b99f66e75dabfc4d8c1289cc2b78 index 0000000000000000000000000000000000000000..552e5dd60fa53fada79f8d6e333f52bc10a2bead
--- /dev/null --- /dev/null
+++ b/Source/JavaScriptCore/inspector/protocol/Emulation.json +++ b/Source/JavaScriptCore/inspector/protocol/Emulation.json
@@ -0,0 +1,32 @@ @@ -0,0 +1,39 @@
+{ +{
+ "domain": "Emulation", + "domain": "Emulation",
+ "availability": ["web"], + "availability": ["web"],
@ -764,6 +764,13 @@ index 0000000000000000000000000000000000000000..bcf863e4bba3b99f66e75dabfc4d8c12
+ { "name": "username", "type": "string", "optional": true }, + { "name": "username", "type": "string", "optional": true },
+ { "name": "password", "type": "string", "optional": true } + { "name": "password", "type": "string", "optional": true }
+ ] + ]
+ },
+ {
+ "name": "setActiveAndFocused",
+ "description": "Makes page focused for test.",
+ "parameters": [
+ { "name": "active", "type": "boolean", "optional": true }
+ ]
+ } + }
+ ] + ]
+} +}
@ -8723,10 +8730,10 @@ index 846a5aa27dfab3d274cffa4873861f2587d17fd8..cf0dc99f5601636c48abff09cd47ace4
diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..78815bc17978a23ca811ff99039b4fe86e6fb4a0 index 0000000000000000000000000000000000000000..466d5129363e8b3d2e7cfc10f2a1ac7ae77f634f
--- /dev/null --- /dev/null
+++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp +++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp
@@ -0,0 +1,95 @@ @@ -0,0 +1,103 @@
+/* +/*
+ * Copyright (C) 2019 Microsoft Corporation. + * Copyright (C) 2019 Microsoft Corporation.
+ * + *
@ -8821,13 +8828,21 @@ index 0000000000000000000000000000000000000000..78815bc17978a23ca811ff99039b4fe8
+ m_page.setAuthCredentialsForAutomation(Optional<WebCore::Credential>()); + m_page.setAuthCredentialsForAutomation(Optional<WebCore::Credential>());
+} +}
+ +
+void WebPageInspectorEmulationAgent::setActiveAndFocused(Inspector::ErrorString&, const bool* active)
+{
+ Optional<bool> value;
+ if (active)
+ value = *active;
+ m_page.setActiveForAutomation(value);
+}
+
+} // namespace WebKit +} // namespace WebKit
diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..77dff2c191fee081773bc5705d80168c3898f496 index 0000000000000000000000000000000000000000..43d827233df725fa8c85fc9ff4b20a1f716803e0
--- /dev/null --- /dev/null
+++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h +++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h
@@ -0,0 +1,66 @@ @@ -0,0 +1,67 @@
+/* +/*
+ * Copyright (C) 2019 Microsoft Corporation. + * Copyright (C) 2019 Microsoft Corporation.
+ * + *
@ -8885,6 +8900,7 @@ index 0000000000000000000000000000000000000000..77dff2c191fee081773bc5705d80168c
+ void setDeviceMetricsOverride(int width, int height, double deviceScaleFactor, bool fixedlayout, Ref<SetDeviceMetricsOverrideCallback>&&) override; + void setDeviceMetricsOverride(int width, int height, double deviceScaleFactor, bool fixedlayout, Ref<SetDeviceMetricsOverrideCallback>&&) override;
+ void setJavaScriptEnabled(Inspector::ErrorString&, bool enabled) override; + void setJavaScriptEnabled(Inspector::ErrorString&, bool enabled) override;
+ void setAuthCredentials(Inspector::ErrorString&, const String*, const String*) override; + void setAuthCredentials(Inspector::ErrorString&, const String*, const String*) override;
+ void setActiveAndFocused(Inspector::ErrorString&, const bool*) override;
+ +
+private: +private:
+ void platformSetSize(int width, int height, Function<void (const String& error)>&&); + void platformSetSize(int width, int height, Function<void (const String& error)>&&);
@ -9235,7 +9251,7 @@ index 0000000000000000000000000000000000000000..76290475097e756e3d932d22be4d8c79
+ +
+} // namespace WebKit +} // namespace WebKit
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e7772056b250ee5 100644 index 059e904a16c6c4b91d6271e25d239b975191facd..df51560a3abc953030e7cb7602275c0a9ab72281 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp --- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -905,6 +905,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) @@ -905,6 +905,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason)
@ -9268,18 +9284,30 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
RefPtr<API::Navigation> WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData) RefPtr<API::Navigation> WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData)
{ {
if (m_isClosed) if (m_isClosed)
@@ -1680,6 +1696,19 @@ void WebPageProxy::setControlledByAutomation(bool controlled) @@ -1680,6 +1696,31 @@ void WebPageProxy::setControlledByAutomation(bool controlled)
m_process->processPool().sendToNetworkingProcess(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation)); m_process->processPool().sendToNetworkingProcess(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation));
} }
+void WebPageProxy::setAuthCredentialsForAutomation(Optional<WebCore::Credential>&& credentials) { +void WebPageProxy::setAuthCredentialsForAutomation(Optional<WebCore::Credential>&& credentials)
+{
+ m_credentialsForAutomation = WTFMove(credentials); + m_credentialsForAutomation = WTFMove(credentials);
+} +}
+ +
+void WebPageProxy::setPermissionsForAutomation(const HashMap<String, HashSet<String>>& permissions) { +void WebPageProxy::setPermissionsForAutomation(const HashMap<String, HashSet<String>>& permissions)
+{
+ m_permissionsForAutomation = permissions; + m_permissionsForAutomation = permissions;
+} +}
+ +
+void WebPageProxy::setActiveForAutomation(Optional<bool> active) {
+ m_activeForAutomation = active;
+ OptionSet<ActivityState::Flag> state;
+ state.add(ActivityState::IsFocused);
+ state.add(ActivityState::WindowIsActive);
+ state.add(ActivityState::IsVisible);
+ state.add(ActivityState::IsVisibleOrOccluded);
+ activityStateDidChange(state);
+}
+
+void WebPageProxy::logToStderr(const String& str) +void WebPageProxy::logToStderr(const String& str)
+{ +{
+ fprintf(stderr, "RENDERER: %s\n", str.utf8().data()); + fprintf(stderr, "RENDERER: %s\n", str.utf8().data());
@ -9288,7 +9316,33 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type) void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type)
{ {
MESSAGE_CHECK(m_process, !targetId.isEmpty()); MESSAGE_CHECK(m_process, !targetId.isEmpty());
@@ -2767,7 +2796,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) @@ -1822,6 +1863,25 @@ void WebPageProxy::updateActivityState(OptionSet<ActivityState::Flag> flagsToUpd
{
bool wasVisible = isViewVisible();
m_activityState.remove(flagsToUpdate);
+
+
+ if (m_activeForAutomation) {
+ if (*m_activeForAutomation) {
+ if (flagsToUpdate & ActivityState::IsFocused)
+ m_activityState.add(ActivityState::IsFocused);
+ if (flagsToUpdate & ActivityState::WindowIsActive)
+ m_activityState.add(ActivityState::WindowIsActive);
+ if (flagsToUpdate & ActivityState::IsVisible)
+ m_activityState.add(ActivityState::IsVisible);
+ if (flagsToUpdate & ActivityState::IsVisibleOrOccluded)
+ m_activityState.add(ActivityState::IsVisibleOrOccluded);
+ }
+ flagsToUpdate.remove(ActivityState::IsFocused);
+ flagsToUpdate.remove(ActivityState::WindowIsActive);
+ flagsToUpdate.remove(ActivityState::IsVisible);
+ flagsToUpdate.remove(ActivityState::IsVisibleOrOccluded);
+ }
+
if (flagsToUpdate & ActivityState::IsFocused && pageClient().isViewFocused())
m_activityState.add(ActivityState::IsFocused);
if (flagsToUpdate & ActivityState::WindowIsActive && pageClient().isViewWindowActive())
@@ -2767,7 +2827,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b)
void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent) void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent)
{ {
@ -9297,7 +9351,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
const EventNames& names = eventNames(); const EventNames& names = eventNames();
for (auto& touchPoint : touchStartEvent.touchPoints()) { for (auto& touchPoint : touchStartEvent.touchPoints()) {
IntPoint location = touchPoint.location(); IntPoint location = touchPoint.location();
@@ -2800,7 +2829,7 @@ void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent @@ -2800,7 +2860,7 @@ void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent
m_touchAndPointerEventTracking.touchStartTracking = TrackingType::Synchronous; m_touchAndPointerEventTracking.touchStartTracking = TrackingType::Synchronous;
m_touchAndPointerEventTracking.touchMoveTracking = TrackingType::Synchronous; m_touchAndPointerEventTracking.touchMoveTracking = TrackingType::Synchronous;
m_touchAndPointerEventTracking.touchEndTracking = TrackingType::Synchronous; m_touchAndPointerEventTracking.touchEndTracking = TrackingType::Synchronous;
@ -9306,7 +9360,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
} }
TrackingType WebPageProxy::touchEventTrackingType(const WebTouchEvent& touchStartEvent) const TrackingType WebPageProxy::touchEventTrackingType(const WebTouchEvent& touchStartEvent) const
@@ -3135,6 +3164,7 @@ void WebPageProxy::receivedNavigationPolicyDecision(PolicyAction policyAction, A @@ -3135,6 +3195,7 @@ void WebPageProxy::receivedNavigationPolicyDecision(PolicyAction policyAction, A
void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, Optional<WebsitePoliciesData>&& websitePolicies, Ref<PolicyDecisionSender>&& sender, WillContinueLoadInNewProcess willContinueLoadInNewProcess) void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, Optional<WebsitePoliciesData>&& websitePolicies, Ref<PolicyDecisionSender>&& sender, WillContinueLoadInNewProcess willContinueLoadInNewProcess)
{ {
@ -9314,7 +9368,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
if (!hasRunningProcess()) { if (!hasRunningProcess()) {
sender->send(PolicyAction::Ignore, 0, DownloadID(), WTF::nullopt); sender->send(PolicyAction::Ignore, 0, DownloadID(), WTF::nullopt);
return; return;
@@ -4228,6 +4258,7 @@ void WebPageProxy::didDestroyNavigation(uint64_t navigationID) @@ -4228,6 +4289,7 @@ void WebPageProxy::didDestroyNavigation(uint64_t navigationID)
// FIXME: Message check the navigationID. // FIXME: Message check the navigationID.
m_navigationState->didDestroyNavigation(navigationID); m_navigationState->didDestroyNavigation(navigationID);
@ -9322,7 +9376,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
} }
void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData) void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData)
@@ -4449,6 +4480,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& p @@ -4449,6 +4511,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& p
m_failingProvisionalLoadURL = { }; m_failingProvisionalLoadURL = { };
@ -9331,7 +9385,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
// If the provisional page's load fails then we destroy the provisional page. // If the provisional page's load fails then we destroy the provisional page.
if (m_provisionalPage && m_provisionalPage->mainFrame() == frame && willContinueLoading == WillContinueLoading::No) if (m_provisionalPage && m_provisionalPage->mainFrame() == frame && willContinueLoading == WillContinueLoading::No)
m_provisionalPage = nullptr; m_provisionalPage = nullptr;
@@ -4886,8 +4919,16 @@ void WebPageProxy::decidePolicyForNavigationActionAsync(FrameIdentifier frameID, @@ -4886,8 +4950,16 @@ void WebPageProxy::decidePolicyForNavigationActionAsync(FrameIdentifier frameID,
NavigationActionData&& navigationActionData, FrameInfoData&& frameInfoData, Optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, NavigationActionData&& navigationActionData, FrameInfoData&& frameInfoData, Optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request,
IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, uint64_t listenerID) IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, uint64_t listenerID)
{ {
@ -9350,7 +9404,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
} }
void WebPageProxy::decidePolicyForNavigationActionAsyncShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, FrameIdentifier frameID, WebCore::SecurityOriginData&& frameSecurityOrigin, void WebPageProxy::decidePolicyForNavigationActionAsyncShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, FrameIdentifier frameID, WebCore::SecurityOriginData&& frameSecurityOrigin,
@@ -5459,6 +5500,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, SecurityOriginDat @@ -5459,6 +5531,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, SecurityOriginDat
if (auto* automationSession = process().processPool().automationSession()) if (auto* automationSession = process().processPool().automationSession())
automationSession->willShowJavaScriptDialog(*this); automationSession->willShowJavaScriptDialog(*this);
} }
@ -9359,7 +9413,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
m_uiClient->runJavaScriptAlert(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply)); m_uiClient->runJavaScriptAlert(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply));
} }
@@ -5478,6 +5521,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, SecurityOriginD @@ -5478,6 +5552,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, SecurityOriginD
if (auto* automationSession = process().processPool().automationSession()) if (auto* automationSession = process().processPool().automationSession())
automationSession->willShowJavaScriptDialog(*this); automationSession->willShowJavaScriptDialog(*this);
} }
@ -9368,7 +9422,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
m_uiClient->runJavaScriptConfirm(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply)); m_uiClient->runJavaScriptConfirm(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply));
} }
@@ -5497,6 +5542,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, SecurityOriginDa @@ -5497,6 +5573,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, SecurityOriginDa
if (auto* automationSession = process().processPool().automationSession()) if (auto* automationSession = process().processPool().automationSession())
automationSession->willShowJavaScriptDialog(*this); automationSession->willShowJavaScriptDialog(*this);
} }
@ -9377,7 +9431,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
m_uiClient->runJavaScriptPrompt(*this, message, defaultValue, frame, WTFMove(securityOrigin), WTFMove(reply)); m_uiClient->runJavaScriptPrompt(*this, message, defaultValue, frame, WTFMove(securityOrigin), WTFMove(reply));
} }
@@ -5656,6 +5703,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, Security @@ -5656,6 +5734,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, Security
return; return;
} }
} }
@ -9386,7 +9440,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
// Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer. // Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer.
m_process->stopResponsivenessTimer(); m_process->stopResponsivenessTimer();
@@ -6715,6 +6764,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) @@ -6715,6 +6795,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
if (auto* automationSession = process().processPool().automationSession()) if (auto* automationSession = process().processPool().automationSession())
automationSession->mouseEventsFlushedForPage(*this); automationSession->mouseEventsFlushedForPage(*this);
didFinishProcessingAllPendingMouseEvents(); didFinishProcessingAllPendingMouseEvents();
@ -9394,7 +9448,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
} }
break; break;
@@ -6741,7 +6791,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) @@ -6741,7 +6822,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
case WebEvent::RawKeyDown: case WebEvent::RawKeyDown:
case WebEvent::Char: { case WebEvent::Char: {
LOG(KeyHandling, "WebPageProxy::didReceiveEvent: %s (queue empty %d)", webKeyboardEventTypeString(type), m_keyEventQueue.isEmpty()); LOG(KeyHandling, "WebPageProxy::didReceiveEvent: %s (queue empty %d)", webKeyboardEventTypeString(type), m_keyEventQueue.isEmpty());
@ -9402,7 +9456,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
MESSAGE_CHECK(m_process, !m_keyEventQueue.isEmpty()); MESSAGE_CHECK(m_process, !m_keyEventQueue.isEmpty());
NativeWebKeyboardEvent event = m_keyEventQueue.takeFirst(); NativeWebKeyboardEvent event = m_keyEventQueue.takeFirst();
@@ -6761,7 +6810,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) @@ -6761,7 +6841,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
// The call to doneWithKeyEvent may close this WebPage. // The call to doneWithKeyEvent may close this WebPage.
// Protect against this being destroyed. // Protect against this being destroyed.
Ref<WebPageProxy> protect(*this); Ref<WebPageProxy> protect(*this);
@ -9410,7 +9464,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
pageClient().doneWithKeyEvent(event, handled); pageClient().doneWithKeyEvent(event, handled);
if (!handled) if (!handled)
m_uiClient->didNotHandleKeyEvent(this, event); m_uiClient->didNotHandleKeyEvent(this, event);
@@ -6770,6 +6818,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) @@ -6770,6 +6849,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
if (!canProcessMoreKeyEvents) { if (!canProcessMoreKeyEvents) {
if (auto* automationSession = process().processPool().automationSession()) if (auto* automationSession = process().processPool().automationSession())
automationSession->keyboardEventsFlushedForPage(*this); automationSession->keyboardEventsFlushedForPage(*this);
@ -9418,7 +9472,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
} }
break; break;
} }
@@ -7238,8 +7287,10 @@ static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason) @@ -7238,8 +7318,10 @@ static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason)
void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason) void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason)
{ {
RELEASE_LOG_IF_ALLOWED(Loading, "dispatchProcessDidTerminate: reason = %d", reason); RELEASE_LOG_IF_ALLOWED(Loading, "dispatchProcessDidTerminate: reason = %d", reason);
@ -9430,7 +9484,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
if (m_loaderClient) if (m_loaderClient)
handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this); handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this);
else else
@@ -7712,6 +7763,14 @@ void WebPageProxy::gamepadActivity(const Vector<GamepadData>& gamepadDatas, bool @@ -7712,6 +7794,14 @@ void WebPageProxy::gamepadActivity(const Vector<GamepadData>& gamepadDatas, bool
void WebPageProxy::didReceiveAuthenticationChallengeProxy(Ref<AuthenticationChallengeProxy>&& authenticationChallenge, NegotiatedLegacyTLS negotiatedLegacyTLS) void WebPageProxy::didReceiveAuthenticationChallengeProxy(Ref<AuthenticationChallengeProxy>&& authenticationChallenge, NegotiatedLegacyTLS negotiatedLegacyTLS)
{ {
@ -9445,7 +9499,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) { if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) {
m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = makeRef(*this), authenticationChallenge = authenticationChallenge.copyRef()] (bool shouldAllowLegacyTLS) { m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = makeRef(*this), authenticationChallenge = authenticationChallenge.copyRef()] (bool shouldAllowLegacyTLS) {
if (shouldAllowLegacyTLS) if (shouldAllowLegacyTLS)
@@ -7794,7 +7853,8 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID, @@ -7794,7 +7884,8 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID,
MESSAGE_CHECK(m_process, securityOriginData); MESSAGE_CHECK(m_process, securityOriginData);
// FIXME: Geolocation should probably be using toString() as its string representation instead of databaseIdentifier(). // FIXME: Geolocation should probably be using toString() as its string representation instead of databaseIdentifier().
@ -9455,7 +9509,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
auto request = m_geolocationPermissionRequestManager.createRequest(geolocationID); auto request = m_geolocationPermissionRequestManager.createRequest(geolocationID);
Function<void(bool)> completionHandler = [request = WTFMove(request)](bool allowed) { Function<void(bool)> completionHandler = [request = WTFMove(request)](bool allowed) {
if (allowed) if (allowed)
@@ -7802,6 +7862,11 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID, @@ -7802,6 +7893,11 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID,
else else
request->deny(); request->deny();
}; };
@ -9468,7 +9522,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
// FIXME: Once iOS migrates to the new WKUIDelegate SPI, clean this up // FIXME: Once iOS migrates to the new WKUIDelegate SPI, clean this up
// and make it one UIClient call that calls the completionHandler with false // and make it one UIClient call that calls the completionHandler with false
diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h
index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c3605f8e4 100644 index b3856a4f9e7859bf3e0b9957c95040a784e9b317..2d154febc73de4877463e5099ffdc3002f6dfd2b 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.h --- a/Source/WebKit/UIProcess/WebPageProxy.h
+++ b/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h
@@ -35,6 +35,7 @@ @@ -35,6 +35,7 @@
@ -9488,18 +9542,19 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c
#if PLATFORM(IOS_FAMILY) #if PLATFORM(IOS_FAMILY)
void showInspectorIndication(); void showInspectorIndication();
@@ -555,6 +558,10 @@ public: @@ -555,6 +558,11 @@ public:
void setPageLoadStateObserver(std::unique_ptr<PageLoadState::Observer>&&); void setPageLoadStateObserver(std::unique_ptr<PageLoadState::Observer>&&);
+ void setAuthCredentialsForAutomation(Optional<WebCore::Credential>&&); + void setAuthCredentialsForAutomation(Optional<WebCore::Credential>&&);
+ void setPermissionsForAutomation(const HashMap<String, HashSet<String>>&); + void setPermissionsForAutomation(const HashMap<String, HashSet<String>>&);
+ void setActiveForAutomation(Optional<bool> active);
+ void logToStderr(const String& str); + void logToStderr(const String& str);
+ +
void initializeWebPage(); void initializeWebPage();
void setDrawingArea(std::unique_ptr<DrawingAreaProxy>&&); void setDrawingArea(std::unique_ptr<DrawingAreaProxy>&&);
@@ -580,6 +587,7 @@ public: @@ -580,6 +588,7 @@ public:
void closePage(); void closePage();
void addPlatformLoadParameters(LoadParameters&); void addPlatformLoadParameters(LoadParameters&);
@ -9507,7 +9562,7 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c
RefPtr<API::Navigation> loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes, API::Object* userData = nullptr); RefPtr<API::Navigation> loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes, API::Object* userData = nullptr);
RefPtr<API::Navigation> loadFile(const String& fileURL, const String& resourceDirectoryURL, API::Object* userData = nullptr); RefPtr<API::Navigation> loadFile(const String& fileURL, const String& resourceDirectoryURL, API::Object* userData = nullptr);
RefPtr<API::Navigation> loadData(const IPC::DataReference&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData = nullptr, WebCore::ShouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow); RefPtr<API::Navigation> loadData(const IPC::DataReference&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData = nullptr, WebCore::ShouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow);
@@ -2293,6 +2301,7 @@ private: @@ -2293,6 +2302,7 @@ private:
String m_overrideContentSecurityPolicy; String m_overrideContentSecurityPolicy;
RefPtr<WebInspectorProxy> m_inspector; RefPtr<WebInspectorProxy> m_inspector;
@ -9515,12 +9570,13 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c
#if ENABLE(FULLSCREEN_API) #if ENABLE(FULLSCREEN_API)
std::unique_ptr<WebFullScreenManagerProxy> m_fullScreenManager; std::unique_ptr<WebFullScreenManagerProxy> m_fullScreenManager;
@@ -2707,6 +2716,8 @@ private: @@ -2707,6 +2717,9 @@ private:
bool m_isLayerTreeFrozenDueToSwipeAnimation { false }; bool m_isLayerTreeFrozenDueToSwipeAnimation { false };
String m_overriddenMediaType; String m_overriddenMediaType;
+ Optional<WebCore::Credential> m_credentialsForAutomation; + Optional<WebCore::Credential> m_credentialsForAutomation;
+ HashMap<String, HashSet<String>> m_permissionsForAutomation; + HashMap<String, HashSet<String>> m_permissionsForAutomation;
+ Optional<bool> m_activeForAutomation;
#if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION) #if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
std::unique_ptr<WebDeviceOrientationUpdateProviderProxy> m_webDeviceOrientationUpdateProviderProxy; std::unique_ptr<WebDeviceOrientationUpdateProviderProxy> m_webDeviceOrientationUpdateProviderProxy;
@ -12529,7 +12585,7 @@ index f84e5cde0baaa08dab1fc77771e4a62a08a5df78..dac73d3d9db04c5d502432cea8adfa89
void navigateToHistory(UINT menuID); void navigateToHistory(UINT menuID);
bool seedInitialDefaultPreferences(); bool seedInitialDefaultPreferences();
diff --git a/Tools/MiniBrowser/win/WinMain.cpp b/Tools/MiniBrowser/win/WinMain.cpp diff --git a/Tools/MiniBrowser/win/WinMain.cpp b/Tools/MiniBrowser/win/WinMain.cpp
index b1d17e88de61a6f196830f62604e4174564506bd..0cc40ce5a5f0a3331275fcdaf7ea95cd0dc5031f 100644 index b1d17e88de61a6f196830f62604e4174564506bd..2035067fef157cb23dc3a93f1a086719a32d7d38 100644
--- a/Tools/MiniBrowser/win/WinMain.cpp --- a/Tools/MiniBrowser/win/WinMain.cpp
+++ b/Tools/MiniBrowser/win/WinMain.cpp +++ b/Tools/MiniBrowser/win/WinMain.cpp
@@ -32,6 +32,9 @@ @@ -32,6 +32,9 @@
@ -12587,7 +12643,18 @@ index b1d17e88de61a6f196830f62604e4174564506bd..0cc40ce5a5f0a3331275fcdaf7ea95cd
if (options.useFullDesktop) if (options.useFullDesktop)
computeFullDesktopFrame(); computeFullDesktopFrame();
@@ -86,19 +115,35 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, @@ -75,9 +104,7 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
OleInitialize(nullptr);
if (SetProcessDpiAwarenessContextPtr())
- SetProcessDpiAwarenessContextPtr()(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
- else
- ::SetProcessDPIAware();
+ SetProcessDpiAwarenessContextPtr()(DPI_AWARENESS_CONTEXT_UNAWARE);
#if !ENABLE(WEBKIT_LEGACY)
auto factory = WebKitBrowserWindow::create;
@@ -86,19 +113,35 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
#else #else
auto factory = options.windowType == BrowserWindowType::WebKit ? WebKitBrowserWindow::create : WebKitLegacyBrowserWindow::create; auto factory = options.windowType == BrowserWindowType::WebKit ? WebKitBrowserWindow::create : WebKitLegacyBrowserWindow::create;
#endif #endif

View file

@ -153,13 +153,12 @@ See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific
- [browser.isConnected()](#browserisconnected) - [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions) - [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage([options])](#browsernewpageoptions) - [browser.newPage([options])](#browsernewpageoptions)
- [browser.pages()](#browserpages)
<!-- GEN:stop --> <!-- GEN:stop -->
#### event: 'disconnected' #### event: 'disconnected'
Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following: Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following:
- Browser application is closed or crashed. - Browser application is closed or crashed.
- The [`browser.disconnect`](#browserdisconnect) method was called. - The [`browser.close`](#browserclose) method was called.
#### browser.close() #### browser.close()
- returns: <[Promise]> - returns: <[Promise]>
@ -233,12 +232,9 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details. - `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- returns: <[Promise]<[Page]>> - returns: <[Promise]<[Page]>>
Creates a new page in a new browser context. Creates a new page in a new browser context. Closing this page will close the context as well.
#### browser.pages() This is a convenience API that should only be used for the single-page scenarios and short snippets. Production code and testing frameworks should explicitly create [browser.newContext](#browsernewcontextoptions) followed by the [browserContext.newPage](#browsercontextnewpage) to control their exact life times.
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages.
An array of all the pages inside all the browser contexts.
### class: BrowserContext ### class: BrowserContext
@ -263,6 +259,7 @@ await context.close();
``` ```
<!-- GEN:toc --> <!-- GEN:toc -->
- [event: 'close'](#event-close)
- [browserContext.clearCookies()](#browsercontextclearcookies) - [browserContext.clearCookies()](#browsercontextclearcookies)
- [browserContext.clearPermissions()](#browsercontextclearpermissions) - [browserContext.clearPermissions()](#browsercontextclearpermissions)
- [browserContext.close()](#browsercontextclose) - [browserContext.close()](#browsercontextclose)
@ -274,6 +271,13 @@ await context.close();
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions) - [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
<!-- GEN:stop --> <!-- GEN:stop -->
#### event: 'close'
Emitted when Browser context gets closed. This might happen because of one of the following:
- Browser context is closed.
- Browser application is closed or crashed.
- The [`browser.close`](#browserclose) method was called.
#### browserContext.clearCookies() #### browserContext.clearCookies()
- returns: <[Promise]> - returns: <[Promise]>
@ -426,7 +430,7 @@ page.removeListener('request', logRequest);
``` ```
<!-- GEN:toc --> <!-- GEN:toc -->
- [event: 'close'](#event-close) - [event: 'close'](#event-close-1)
- [event: 'console'](#event-console) - [event: 'console'](#event-console)
- [event: 'dialog'](#event-dialog) - [event: 'dialog'](#event-dialog)
- [event: 'domcontentloaded'](#event-domcontentloaded) - [event: 'domcontentloaded'](#event-domcontentloaded)
@ -448,7 +452,7 @@ page.removeListener('request', logRequest);
- [page.$$(selector)](#pageselector-1) - [page.$$(selector)](#pageselector-1)
- [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) - [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
- [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1) - [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1)
- [page.$wait(selector, pageFunction[, options[, ...args]])](#pagewaitselector-pagefunction-options-args) - [page.$wait(selector[, options])](#pagewaitselector-options)
- [page.accessibility](#pageaccessibility) - [page.accessibility](#pageaccessibility)
- [page.addScriptTag(options)](#pageaddscripttagoptions) - [page.addScriptTag(options)](#pageaddscripttagoptions)
- [page.addStyleTag(options)](#pageaddstyletagoptions) - [page.addStyleTag(options)](#pageaddstyletagoptions)
@ -679,23 +683,24 @@ const html = await page.$eval('.main-container', e => e.outerHTML);
Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args). Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args).
#### page.$wait(selector, pageFunction[, options[, ...args]]) #### page.$wait(selector[, options])
- `selector` <[string]> A selector to query page for - `selector` <[string]> A selector of an element to wait for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `options` <[Object]>
- `options` <[Object]> Optional waiting parameters - `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.
- `'mutation'` - to execute `pageFunction` on every DOM mutation.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method runs `document.querySelector` within the page and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
If `pageFunction` returns a [Promise], then `page.$wait` would wait for the promise to resolve and return its value. The function This method works across navigations:
is being called on the element periodically until either timeout expires or the function returns the truthy value. ```js
const handle = await page.$wait(selector);
await handle.click();
```
Shortcut for [page.mainFrame().$wait(selector, pageFunction[, options[, ...args]])](#framewaitselector-pagefunction-options-args). This is a shortcut to [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options).
#### page.accessibility #### page.accessibility
- returns: <[Accessibility]> - returns: <[Accessibility]>
@ -1664,7 +1669,7 @@ An example of getting text from an iframe element:
- [frame.$$(selector)](#frameselector-1) - [frame.$$(selector)](#frameselector-1)
- [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) - [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
- [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1) - [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1)
- [frame.$wait(selector, pageFunction[, options[, ...args]])](#framewaitselector-pagefunction-options-args) - [frame.$wait(selector[, options])](#framewaitselector-options)
- [frame.addScriptTag(options)](#frameaddscripttagoptions) - [frame.addScriptTag(options)](#frameaddscripttagoptions)
- [frame.addStyleTag(options)](#frameaddstyletagoptions) - [frame.addStyleTag(options)](#frameaddstyletagoptions)
- [frame.check(selector, [options])](#framecheckselector-options) - [frame.check(selector, [options])](#framecheckselector-options)
@ -1740,21 +1745,24 @@ const preloadHref = await frame.$eval('link[rel=preload]', el => el.href);
const html = await frame.$eval('.main-container', e => e.outerHTML); const html = await frame.$eval('.main-container', e => e.outerHTML);
``` ```
#### frame.$wait(selector, pageFunction[, options[, ...args]]) #### frame.$wait(selector[, options])
- `selector` <[string]> A selector to query page for - `selector` <[string]> A selector of an element to wait for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `options` <[Object]>
- `options` <[Object]> Optional waiting parameters - `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.
- `'mutation'` - to execute `pageFunction` on every DOM mutation.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method runs `document.querySelector` within the frame and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
If `pageFunction` returns a [Promise], then `page.$wait` would wait for the promise to resolve and return its value. The function This method works across navigations:
is being called on the element periodically until either timeout expires or the function returns the truthy value. ```js
const handle = await page.$wait(selector);
await handle.click();
```
This is a shortcut to [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### frame.addScriptTag(options) #### frame.addScriptTag(options)
- `options` <[Object]> - `options` <[Object]>
@ -3168,7 +3176,7 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
The [WebSocket] class represents websocket connections in the page. The [WebSocket] class represents websocket connections in the page.
<!-- GEN:toc --> <!-- GEN:toc -->
- [event: 'close'](#event-close-1) - [event: 'close'](#event-close-2)
- [event: 'error'](#event-error) - [event: 'error'](#event-error)
- [event: 'messageReceived'](#event-messagereceived) - [event: 'messageReceived'](#event-messagereceived)
- [event: 'messageSent'](#event-messagesent) - [event: 'messageSent'](#event-messagesent)
@ -3423,7 +3431,7 @@ If the function passed to the `worker.evaluateHandle` returns a [Promise], then
### class: BrowserServer ### class: BrowserServer
<!-- GEN:toc --> <!-- GEN:toc -->
- [event: 'close'](#event-close-2) - [event: 'close'](#event-close-3)
- [browserServer.close()](#browserserverclose) - [browserServer.close()](#browserserverclose)
- [browserServer.kill()](#browserserverkill) - [browserServer.kill()](#browserserverkill)
- [browserServer.process()](#browserserverprocess) - [browserServer.process()](#browserserverprocess)
@ -3470,6 +3478,7 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
<!-- GEN:toc --> <!-- GEN:toc -->
- [browserType.connect(options)](#browsertypeconnectoptions) - [browserType.connect(options)](#browsertypeconnectoptions)
- [browserType.devices](#browsertypedevices) - [browserType.devices](#browsertypedevices)
- [browserType.downloadBrowserIfNeeded([progress])](#browsertypedownloadbrowserifneededprogress)
- [browserType.errors](#browsertypeerrors) - [browserType.errors](#browsertypeerrors)
- [browserType.executablePath()](#browsertypeexecutablepath) - [browserType.executablePath()](#browsertypeexecutablepath)
- [browserType.launch([options])](#browsertypelaunchoptions) - [browserType.launch([options])](#browsertypelaunchoptions)
@ -3508,6 +3517,12 @@ const iPhone = webkit.devices['iPhone 6'];
})(); })();
``` ```
#### browserType.downloadBrowserIfNeeded([progress])
- `progress` <[function]> If download is initiated, this function is called with two parameters: `downloadedBytes` and `totalBytes`.
- returns: <[Promise]> promise that resolves when browser is successfully downloaded.
Download browser binary if it is missing.
#### browserType.errors #### browserType.errors
- returns: <[Object]> - returns: <[Object]>
- `TimeoutError` <[function]> A class of [TimeoutError]. - `TimeoutError` <[function]> A class of [TimeoutError].
@ -3652,7 +3667,6 @@ await browser.stopTracing();
- [browser.isConnected()](#browserisconnected) - [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions) - [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage([options])](#browsernewpageoptions) - [browser.newPage([options])](#browsernewpageoptions)
- [browser.pages()](#browserpages)
<!-- GEN:stop --> <!-- GEN:stop -->
#### event: 'targetchanged' #### event: 'targetchanged'
@ -3819,7 +3833,6 @@ Firefox browser instance does not expose Firefox-specific features.
- [browser.isConnected()](#browserisconnected) - [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions) - [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage([options])](#browsernewpageoptions) - [browser.newPage([options])](#browsernewpageoptions)
- [browser.pages()](#browserpages)
<!-- GEN:stop --> <!-- GEN:stop -->
### class: WebKitBrowser ### class: WebKitBrowser
@ -3835,7 +3848,6 @@ WebKit browser instance does not expose WebKit-specific features.
- [browser.isConnected()](#browserisconnected) - [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions) - [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage([options])](#browsernewpageoptions) - [browser.newPage([options])](#browsernewpageoptions)
- [browser.pages()](#browserpages)
<!-- GEN:stop --> <!-- GEN:stop -->
### Working with selectors ### Working with selectors

View file

@ -38,7 +38,7 @@ async function downloadBrowser(browser) {
// Do nothing if the revision is already downloaded. // Do nothing if the revision is already downloaded.
if (revisionInfo.local) if (revisionInfo.local)
return revisionInfo; return revisionInfo;
await fetcher.download(revisionInfo.revision, onProgress); await browserType.downloadBrowserIfNeeded(onProgress);
logPolitely(`${browser} downloaded to ${revisionInfo.folderPath}`); logPolitely(`${browser} downloaded to ${revisionInfo.folderPath}`);
return revisionInfo; return revisionInfo;
} }

View file

@ -8,7 +8,7 @@
}, },
"main": "index.js", "main": "index.js",
"playwright": { "playwright": {
"chromium_revision": "739261", "chromium_revision": "740289",
"firefox_revision": "1028", "firefox_revision": "1028",
"webkit_revision": "1141" "webkit_revision": "1141"
}, },

View file

@ -21,11 +21,9 @@ import { Page } from './page';
export interface Browser extends platform.EventEmitterType { export interface Browser extends platform.EventEmitterType {
newContext(options?: BrowserContextOptions): Promise<BrowserContext>; newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
contexts(): BrowserContext[]; contexts(): BrowserContext[];
pages(): Promise<Page[]>;
newPage(options?: BrowserContextOptions): Promise<Page>; newPage(options?: BrowserContextOptions): Promise<Page>;
isConnected(): boolean; isConnected(): boolean;
close(): Promise<void>; close(): Promise<void>;
_defaultContext: BrowserContext | undefined;
} }
export type ConnectOptions = { export type ConnectOptions = {
@ -33,14 +31,11 @@ export type ConnectOptions = {
wsEndpoint: string wsEndpoint: string
}; };
export async function collectPages(browser: Browser): Promise<Page[]> { export async function createPageInNewContext(browser: Browser, options?: BrowserContextOptions): Promise<Page> {
const result: Promise<Page[]>[] = []; const context = await browser.newContext(options);
for (const browserContext of browser.contexts()) const page = await context.newPage();
result.push(browserContext.pages()); page._ownedContext = context;
const pages: Page[] = []; return page;
for (const group of await Promise.all(result))
pages.push(...group);
return pages;
} }
export type LaunchType = 'local' | 'server' | 'persistent'; export type LaunchType = 'local' | 'server' | 'persistent';

View file

@ -19,6 +19,8 @@ import { Page } from './page';
import * as network from './network'; import * as network from './network';
import * as types from './types'; import * as types from './types';
import { helper } from './helper'; import { helper } from './helper';
import * as platform from './platform';
import { Events } from './events';
export interface BrowserContextDelegate { export interface BrowserContextDelegate {
pages(): Promise<Page[]>; pages(): Promise<Page[]>;
@ -47,12 +49,13 @@ export type BrowserContextOptions = {
permissions?: { [key: string]: string[] }; permissions?: { [key: string]: string[] };
}; };
export class BrowserContext { export class BrowserContext extends platform.EventEmitter {
private readonly _delegate: BrowserContextDelegate; private readonly _delegate: BrowserContextDelegate;
readonly _options: BrowserContextOptions; readonly _options: BrowserContextOptions;
private _closed = false; private _closed = false;
constructor(delegate: BrowserContextDelegate, options: BrowserContextOptions) { constructor(delegate: BrowserContextDelegate, options: BrowserContextOptions) {
super();
this._delegate = delegate; this._delegate = delegate;
this._options = { ...options }; this._options = { ...options };
if (!this._options.viewport && this._options.viewport !== null) if (!this._options.viewport && this._options.viewport !== null)
@ -79,6 +82,11 @@ export class BrowserContext {
} }
async newPage(): Promise<Page> { async newPage(): Promise<Page> {
const pages = this._delegate.existingPages();
for (const page of pages) {
if (page._ownedContext)
throw new Error('Please use browser.newContext() for multi-page scripts that share the context.');
}
return this._delegate.newPage(); return this._delegate.newPage();
} }
@ -114,12 +122,20 @@ export class BrowserContext {
return; return;
await this._delegate.close(); await this._delegate.close();
this._closed = true; this._closed = true;
this.emit(Events.BrowserContext.Close);
} }
static validateOptions(options: BrowserContextOptions) { static validateOptions(options: BrowserContextOptions) {
if (options.geolocation) if (options.geolocation)
verifyGeolocation(options.geolocation); verifyGeolocation(options.geolocation);
} }
_browserClosed() {
this._closed = true;
for (const page of this._delegate.existingPages())
page._didClose();
this.emit(Events.BrowserContext.Close);
}
} }
function verifyGeolocation(geolocation: types.Geolocation): types.Geolocation { function verifyGeolocation(geolocation: types.Geolocation): types.Geolocation {

View file

@ -24,7 +24,7 @@ import { Page, Worker } from '../page';
import { CRTarget } from './crTarget'; import { CRTarget } from './crTarget';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { CRPage } from './crPage'; import { CRPage } from './crPage';
import { Browser, collectPages } from '../browser'; import { Browser, createPageInNewContext } from '../browser';
import * as network from '../network'; import * as network from '../network';
import * as types from '../types'; import * as types from '../types';
import * as platform from '../platform'; import * as platform from '../platform';
@ -56,7 +56,11 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
this._client = connection.rootSession; this._client = connection.rootSession;
this._defaultContext = this._createBrowserContext(null, {}); this._defaultContext = this._createBrowserContext(null, {});
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(CommonEvents.Browser.Disconnected)); this._connection.on(ConnectionEvents.Disconnected, () => {
for (const context of this.contexts())
context._browserClosed();
this.emit(CommonEvents.Browser.Disconnected);
});
this._client.on('Target.targetCreated', this._targetCreated.bind(this)); this._client.on('Target.targetCreated', this._targetCreated.bind(this));
this._client.on('Target.targetDestroyed', this._targetDestroyed.bind(this)); this._client.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
@ -164,13 +168,8 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
return Array.from(this._contexts.values()); return Array.from(this._contexts.values());
} }
async pages(): Promise<Page[]> {
return collectPages(this);
}
async newPage(options?: BrowserContextOptions): Promise<Page> { async newPage(options?: BrowserContextOptions): Promise<Page> {
const context = await this.newContext(options); return createPageInNewContext(this, options);
return context.newPage();
} }
async _targetCreated(event: Protocol.Target.targetCreatedPayload) { async _targetCreated(event: Protocol.Target.targetCreatedPayload) {

View file

@ -484,6 +484,17 @@ export class CRPage implements PageDelegate {
return {x, y, width, height}; return {x, y, width, height};
} }
async scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void> {
await this._client.send('DOM.scrollIntoViewIfNeeded', {
objectId: toRemoteObject(handle).objectId,
rect,
}).catch(e => {
if (e instanceof Error && e.message.includes('Node does not have a layout object'))
e.message = 'Node is either not visible or not an HTMLElement';
throw e;
});
}
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._client.send('DOM.getContentQuads', { const result = await this._client.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId objectId: toRemoteObject(handle).objectId

View file

@ -160,58 +160,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return this._page._delegate.getContentFrame(this); return this._page._delegate.getContentFrame(this);
} }
async scrollIntoViewIfNeeded() { async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<void> {
const error = await this._evaluateInUtility(async (node: Node, pageJavascriptEnabled: boolean) => { await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
if (!node.isConnected)
return 'Node is detached from document';
if (node.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
const element = node as Element;
// force-scroll if page's javascript is disabled.
if (!pageJavascriptEnabled) {
// @ts-ignore because only Chromium still supports 'instant'
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false;
}
const visibleRatio = await new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
// Firefox doesn't call IntersectionObserver callback unless
// there are rafs.
requestAnimationFrame(() => {});
});
if (visibleRatio !== 1.0) {
// @ts-ignore because only Chromium still supports 'instant'
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
}
return false;
}, !!this._page.context()._options.javaScriptEnabled);
if (error)
throw new Error(error);
} }
private async _ensurePointerActionPoint(relativePoint?: types.Point): Promise<types.Point> { async scrollIntoViewIfNeeded() {
await this.scrollIntoViewIfNeeded(); await this._scrollRectIntoViewIfNeeded();
if (!relativePoint)
return this._clickablePoint();
let r = await this._viewportPointAndScroll(relativePoint);
if (r.scrollX || r.scrollY) {
const error = await this._evaluateInUtility((element, scrollX, scrollY) => {
if (!element.ownerDocument || !element.ownerDocument.defaultView)
return 'Node does not have a containing window';
element.ownerDocument.defaultView.scrollBy(scrollX, scrollY);
return false;
}, r.scrollX, r.scrollY);
if (error)
throw new Error(error);
r = await this._viewportPointAndScroll(relativePoint);
if (r.scrollX || r.scrollY)
throw new Error('Failed to scroll relative point into viewport');
}
return r.point;
} }
private async _clickablePoint(): Promise<types.Point> { private async _clickablePoint(): Promise<types.Point> {
@ -253,7 +207,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return result; return result;
} }
private async _viewportPointAndScroll(relativePoint: types.Point): Promise<{point: types.Point, scrollX: number, scrollY: number}> { private async _relativePoint(relativePoint: types.Point): Promise<types.Point> {
const [box, border] = await Promise.all([ const [box, border] = await Promise.all([
this.boundingBox(), this.boundingBox(),
this._evaluateInUtility((node: Node) => { this._evaluateInUtility((node: Node) => {
@ -273,23 +227,13 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
point.x += border.x; point.x += border.x;
point.y += border.y; point.y += border.y;
} }
const metrics = await this._page._delegate.layoutViewport(); return point;
// Give 20 extra pixels to avoid any issues on viewport edge.
let scrollX = 0;
if (point.x < 20)
scrollX = point.x - 20;
if (point.x > metrics.width - 20)
scrollX = point.x - metrics.width + 20;
let scrollY = 0;
if (point.y < 20)
scrollY = point.y - 20;
if (point.y > metrics.height - 20)
scrollY = point.y - metrics.height + 20;
return { point, scrollX, scrollY };
} }
async _performPointerAction(action: (point: types.Point) => Promise<void>, options?: input.PointerActionOptions): Promise<void> { async _performPointerAction(action: (point: types.Point) => Promise<void>, options?: input.PointerActionOptions): Promise<void> {
const point = await this._ensurePointerActionPoint(options ? options.relativePoint : undefined); const relativePoint = options ? options.relativePoint : undefined;
await this._scrollRectIntoViewIfNeeded(relativePoint ? { x: relativePoint.x, y: relativePoint.y, width: 0, height: 0 } : undefined);
const point = relativePoint ? await this._relativePoint(relativePoint) : await this._clickablePoint();
let restoreModifiers: input.Modifier[] | undefined; let restoreModifiers: input.Modifier[] | undefined;
if (options && options.modifiers) if (options && options.modifiers)
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers); restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);

View file

@ -20,6 +20,10 @@ export const Events = {
Disconnected: 'disconnected' Disconnected: 'disconnected'
}, },
BrowserContext: {
Close: 'close'
},
BrowserServer: { BrowserServer: {
Close: 'close', Close: 'close',
}, },

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Browser, collectPages } from '../browser'; import { Browser, createPageInNewContext } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext'; import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { Events } from '../events'; import { Events } from '../events';
import { assert, helper, RegisteredListener, debugError } from '../helper'; import { assert, helper, RegisteredListener, debugError } from '../helper';
@ -50,8 +50,11 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
this._defaultContext = this._createBrowserContext(null, {}); this._defaultContext = this._createBrowserContext(null, {});
this._contexts = new Map(); this._contexts = new Map();
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected)); this._connection.on(ConnectionEvents.Disconnected, () => {
for (const context of this.contexts())
context._browserClosed();
this.emit(Events.Browser.Disconnected);
});
this._eventListeners = [ this._eventListeners = [
helper.addEventListener(this._connection, 'Target.targetCreated', this._onTargetCreated.bind(this)), helper.addEventListener(this._connection, 'Target.targetCreated', this._onTargetCreated.bind(this)),
helper.addEventListener(this._connection, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)), helper.addEventListener(this._connection, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
@ -90,13 +93,8 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
return Array.from(this._contexts.values()); return Array.from(this._contexts.values());
} }
async pages(): Promise<Page[]> {
return collectPages(this);
}
async newPage(options?: BrowserContextOptions): Promise<Page> { async newPage(options?: BrowserContextOptions): Promise<Page> {
const context = await this.newContext(options); return createPageInNewContext(this, options);
return context.newPage();
} }
async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> { async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> {

View file

@ -392,6 +392,14 @@ export class FFPage implements PageDelegate {
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
} }
async scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void> {
await this._session.send('Page.scrollIntoViewIfNeeded', {
frameId: handle._context.frame._id,
objectId: toRemoteObject(handle).objectId!,
rect,
});
}
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('Page.getContentQuads', { const result = await this._session.send('Page.getContentQuads', {
frameId: handle._context.frame._id, frameId: handle._context.frame._id,

View file

@ -620,6 +620,10 @@ export class Frame {
return handle; return handle;
} }
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.waitForSelector(selector, options);
}
$eval: types.$Eval = async (selector, pageFunction, ...args) => { $eval: types.$Eval = async (selector, pageFunction, ...args) => {
const context = await this._mainContext(); const context = await this._mainContext();
const elementHandle = await context._$(selector); const elementHandle = await context._$(selector);
@ -938,12 +942,6 @@ export class Frame {
return this._scheduleRerunnableTask(task, 'main', options.timeout); return this._scheduleRerunnableTask(task, 'main', options.timeout);
} }
$wait: types.$Wait = async (selector, pageFunction, options, ...args) => {
options = { timeout: this._page._timeoutSettings.timeout(), ...(options || {}) };
const task = dom.waitForFunctionTask(selector, pageFunction, options, ...args);
return this._scheduleRerunnableTask(task, 'main', options.timeout) as any;
}
async title(): Promise<string> { async title(): Promise<string> {
const context = await this._utilityContext(); const context = await this._utilityContext();
return context.evaluate(() => document.title); return context.evaluate(() => document.title);

View file

@ -69,6 +69,7 @@ export interface PageDelegate {
setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>; setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>;
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>; getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>; getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void>;
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>; getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
pdf?: (options?: types.PDFOptions) => Promise<platform.BufferType>; pdf?: (options?: types.PDFOptions) => Promise<platform.BufferType>;
@ -113,6 +114,7 @@ export class Page extends platform.EventEmitter {
readonly pdf: ((options?: types.PDFOptions) => Promise<platform.BufferType>) | undefined; readonly pdf: ((options?: types.PDFOptions) => Promise<platform.BufferType>) | undefined;
readonly coverage: Coverage | undefined; readonly coverage: Coverage | undefined;
readonly _requestHandlers: { url: types.URLMatch, handler: (request: network.Request) => void }[] = []; readonly _requestHandlers: { url: types.URLMatch, handler: (request: network.Request) => void }[] = [];
_ownedContext: BrowserContext | undefined;
constructor(delegate: PageDelegate, browserContext: BrowserContext) { constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(); super();
@ -213,6 +215,10 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForSelector(selector, options);
} }
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.waitForSelector(selector, options);
}
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
return this.mainFrame().evaluateHandle(pageFunction, ...args as any); return this.mainFrame().evaluateHandle(pageFunction, ...args as any);
} }
@ -471,6 +477,8 @@ export class Page extends platform.EventEmitter {
await this._delegate.closePage(runBeforeUnload); await this._delegate.closePage(runBeforeUnload);
if (!runBeforeUnload) if (!runBeforeUnload)
await this._closedPromise; await this._closedPromise;
if (this._ownedContext)
await this._ownedContext.close();
} }
isClosed(): boolean { isClosed(): boolean {
@ -525,10 +533,6 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().waitForFunction(pageFunction, options, ...args); return this.mainFrame().waitForFunction(pageFunction, options, ...args);
} }
$wait: types.$Wait = async (selector, pageFunction, options, ...args) => {
return this.mainFrame().$wait(selector, pageFunction, options, ...args as any);
}
workers(): Worker[] { workers(): Worker[] {
return [...this._workers.values()]; return [...this._workers.values()];
} }

View file

@ -19,6 +19,7 @@ import { TimeoutError } from '../errors';
import { Browser, ConnectOptions } from '../browser'; import { Browser, ConnectOptions } from '../browser';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import { BrowserServer } from './browserServer'; import { BrowserServer } from './browserServer';
import { OnProgressCallback } from './browserFetcher';
export type BrowserArgOptions = { export type BrowserArgOptions = {
headless?: boolean, headless?: boolean,
@ -44,6 +45,7 @@ export interface BrowserType {
launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer>; launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer>;
launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext>; launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext>;
connect(options: ConnectOptions): Promise<Browser>; connect(options: ConnectOptions): Promise<Browser>;
downloadBrowserIfNeeded(progress?: OnProgressCallback): Promise<void>;
devices: types.Devices; devices: types.Devices;
errors: { TimeoutError: typeof TimeoutError }; errors: { TimeoutError: typeof TimeoutError };
} }

View file

@ -19,7 +19,7 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as util from 'util'; import * as util from 'util';
import { BrowserFetcher, BrowserFetcherOptions } from '../server/browserFetcher'; import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from '../server/browserFetcher';
import { DeviceDescriptors } from '../deviceDescriptors'; import { DeviceDescriptors } from '../deviceDescriptors';
import * as types from '../types'; import * as types from '../types';
import { assert } from '../helper'; import { assert } from '../helper';
@ -194,6 +194,15 @@ export class Chromium implements BrowserType {
return chromeArguments; return chromeArguments;
} }
async downloadBrowserIfNeeded(onProgress?: OnProgressCallback) {
const fetcher = this._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return;
await fetcher.download(revisionInfo.revision, onProgress);
}
_createBrowserFetcher(options: BrowserFetcherOptions = {}): BrowserFetcher { _createBrowserFetcher(options: BrowserFetcherOptions = {}): BrowserFetcher {
const downloadURLs = { const downloadURLs = {
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip', linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',

View file

@ -16,7 +16,7 @@
*/ */
import { FFBrowser } from '../firefox/ffBrowser'; import { FFBrowser } from '../firefox/ffBrowser';
import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher'; import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from './browserFetcher';
import { DeviceDescriptors } from '../deviceDescriptors'; import { DeviceDescriptors } from '../deviceDescriptors';
import { launchProcess, waitForLine } from './processLauncher'; import { launchProcess, waitForLine } from './processLauncher';
import * as types from '../types'; import * as types from '../types';
@ -44,6 +44,15 @@ export class Firefox implements BrowserType {
this._revision = preferredRevision; this._revision = preferredRevision;
} }
async downloadBrowserIfNeeded(onProgress?: OnProgressCallback) {
const fetcher = this._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return;
await fetcher.download(revisionInfo.revision, onProgress);
}
name() { name() {
return 'firefox'; return 'firefox';
} }

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher'; import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from './browserFetcher';
import { DeviceDescriptors } from '../deviceDescriptors'; import { DeviceDescriptors } from '../deviceDescriptors';
import { TimeoutError } from '../errors'; import { TimeoutError } from '../errors';
import * as types from '../types'; import * as types from '../types';
@ -52,6 +52,15 @@ export class WebKit implements BrowserType {
return 'webkit'; return 'webkit';
} }
async downloadBrowserIfNeeded(onProgress?: OnProgressCallback) {
const fetcher = this._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return;
await fetcher.download(revisionInfo.revision, onProgress);
}
async launch(options?: LaunchOptions & { slowMo?: number }): Promise<WKBrowser> { async launch(options?: LaunchOptions & { slowMo?: number }): Promise<WKBrowser> {
const { browserServer, transport } = await this._launchServer(options, 'local'); const { browserServer, transport } = await this._launchServer(options, 'local');
const browser = await WKBrowser.connect(transport!, options && options.slowMo); const browser = await WKBrowser.connect(transport!, options && options.slowMo);
@ -84,7 +93,6 @@ export class WebKit implements BrowserType {
handleSIGINT = true, handleSIGINT = true,
handleSIGTERM = true, handleSIGTERM = true,
handleSIGHUP = true, handleSIGHUP = true,
timeout = 30000
} = options; } = options;
let temporaryUserDataDir: string | null = null; let temporaryUserDataDir: string | null = null;
@ -136,7 +144,6 @@ export class WebKit implements BrowserType {
}, },
}); });
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to WebKit!`);
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream); transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? await wrapTransportWithWebSocket(transport, port || 0) : null); browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? await wrapTransportWithWebSocket(transport, port || 0) : null);
return { browserServer, transport }; return { browserServer, transport };

View file

@ -27,7 +27,6 @@ export type Evaluate = <Args extends any[], R>(pageFunction: PageFunction<Args,
export type EvaluateHandle = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>; export type EvaluateHandle = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
export type $Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>; export type $Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type $$Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>; export type $$Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type $Wait = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element | undefined, Args, R>, options?: WaitForFunctionOptions, ...args: Boxed<Args>) => Promise<Handle<R>>;
export type EvaluateOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<R>; export type EvaluateOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>; export type EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Browser, collectPages } from '../browser'; import { Browser, createPageInNewContext } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext'; import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { assert, helper, RegisteredListener } from '../helper'; import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network'; import * as network from '../network';
@ -66,6 +66,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
} }
_onDisconnect() { _onDisconnect() {
for (const context of this.contexts())
context._browserClosed();
for (const pageProxy of this._pageProxies.values()) for (const pageProxy of this._pageProxies.values())
pageProxy.dispose(); pageProxy.dispose();
this._pageProxies.clear(); this._pageProxies.clear();
@ -87,13 +89,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
return Array.from(this._contexts.values()); return Array.from(this._contexts.values());
} }
async pages(): Promise<Page[]> {
return collectPages(this);
}
async newPage(options?: BrowserContextOptions): Promise<Page> { async newPage(options?: BrowserContextOptions): Promise<Page> {
const context = await this.newContext(options); return createPageInNewContext(this, options);
return context.newPage();
} }
async _waitForFirstPageTarget(timeout: number): Promise<void> { async _waitForFirstPageTarget(timeout: number): Promise<void> {

View file

@ -545,6 +545,17 @@ export class WKPage implements PageDelegate {
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
} }
async scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void> {
await this._session.send('DOM.scrollIntoViewIfNeeded', {
objectId: toRemoteObject(handle).objectId!,
rect,
}).catch(e => {
if (e instanceof Error && e.message.includes('Node does not have a layout object'))
e.message = 'Node is either not visible or not an HTMLElement';
throw e;
});
}
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('DOM.getContentQuads', { const result = await this._session.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId! objectId: toRemoteObject(handle).objectId!

View file

@ -24,24 +24,26 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
const {it, fit, xit, dit} = testRunner; const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Browser', function() { describe('Browser.newPage', function() {
it('should create new page', async function({browser}) { it('should create new page', async function({browser}) {
expect((await browser.pages()).length).toBe(0);
const page1 = await browser.newPage(); const page1 = await browser.newPage();
expect((await browser.pages()).length).toBe(1);
expect(browser.contexts().length).toBe(1); expect(browser.contexts().length).toBe(1);
const page2 = await browser.newPage(); const page2 = await browser.newPage();
expect((await browser.pages()).length).toBe(2);
expect(browser.contexts().length).toBe(2); expect(browser.contexts().length).toBe(2);
await page1.context().close(); await page1.close();
expect((await browser.pages()).length).toBe(1);
expect(browser.contexts().length).toBe(1); expect(browser.contexts().length).toBe(1);
await page2.context().close(); await page2.close();
expect((await browser.pages()).length).toBe(0);
expect(browser.contexts().length).toBe(0); expect(browser.contexts().length).toBe(0);
}); });
it('should throw upon second create new page', async function({browser}) {
const page = await browser.newPage();
let error;
await page.context().newPage().catch(e => error = e);
await page.close();
expect(error.message).toContain('Please use browser.newContext()');
});
}); });
}; };

View file

@ -192,7 +192,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const target = await targetPromise; const target = await targetPromise;
expect(await target.page()).toBe(page); expect(await target.page()).toBe(page);
await page.close(); await page.context().close();
}); });
it('should fire target events', async function({browser, newContext, server}) { it('should fire target events', async function({browser, newContext, server}) {
const context = await newContext(); const context = await newContext();

View file

@ -58,7 +58,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const newPage = await browser.newPage(); const newPage = await browser.newPage();
let error = null; let error = null;
await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e); await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e);
await newPage.context().close(); await newPage.close();
expect(error).toBeTruthy(); expect(error).toBeTruthy();
await browser.stopTracing(); await browser.stopTracing();
}); });

View file

@ -286,6 +286,8 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
expect(await frame.evaluate(() => window.result)).toBe('Clicked'); expect(await frame.evaluate(() => window.result)).toBe('Clicked');
}); });
// @see https://github.com/GoogleChrome/puppeteer/issues/4110 // @see https://github.com/GoogleChrome/puppeteer/issues/4110
// @see https://bugs.chromium.org/p/chromium/issues/detail?id=986390
// @see https://chromium-review.googlesource.com/c/chromium/src/+/1742784
xit('should click the button with fixed position inside an iframe', async({page, server}) => { xit('should click the button with fixed position inside an iframe', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
@ -326,7 +328,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 12 * 2 + 20 : 20); expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 12 * 2 + 20 : 20);
expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 12 * 2 + 10 : 10); expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 12 * 2 + 10 : 10);
}); });
it('should click a very large button with relative point', async({page, server}) => { it.skip(FFOX)('should click a very large button with relative point', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', button => button.style.borderWidth = '8px'); await page.$eval('button', button => button.style.borderWidth = '8px');
await page.$eval('button', button => button.style.height = button.style.width = '2000px'); await page.$eval('button', button => button.style.height = button.style.width = '2000px');
@ -336,7 +338,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900);
expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910);
}); });
xit('should click a button in scrolling container with relative point', async({page, server}) => { it.skip(FFOX)('should click a button in scrolling container with relative point', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', button => { await page.$eval('button', button => {
const container = document.createElement('div'); const container = document.createElement('div');
@ -347,11 +349,13 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
container.appendChild(button); container.appendChild(button);
button.style.height = '2000px'; button.style.height = '2000px';
button.style.width = '2000px'; button.style.width = '2000px';
button.style.borderWidth = '8px';
}); });
await page.click('button', { relativePoint: { x: 1900, y: 1910 } }); await page.click('button', { relativePoint: { x: 1900, y: 1910 } });
expect(await page.evaluate(() => window.result)).toBe('Clicked'); expect(await page.evaluate(() => window.result)).toBe('Clicked');
expect(await page.evaluate(() => offsetX)).toBe(1900); // Safari reports border-relative offsetX/offsetY.
expect(await page.evaluate(() => offsetY)).toBe(1910); expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900);
expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910);
}); });
it('should update modifiers correctly', async({page, server}) => { it('should update modifiers correctly', async({page, server}) => {
@ -370,7 +374,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => shiftKey)).toBe(false); expect(await page.evaluate(() => shiftKey)).toBe(false);
}); });
it.skip(CHROMIUM)('should click an offscreen element when scroll-behavior is smooth', async({page}) => { it('should click an offscreen element when scroll-behavior is smooth', async({page}) => {
await page.setContent(` await page.setContent(`
<div style="border: 1px solid black; height: 500px; overflow: auto; width: 500px; scroll-behavior: smooth"> <div style="border: 1px solid black; height: 500px; overflow: auto; width: 500px; scroll-behavior: smooth">
<button style="margin-top: 2000px" onClick="window.clicked = true">hi</button> <button style="margin-top: 2000px" onClick="window.clicked = true">hi</button>
@ -379,7 +383,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
await page.click('button'); await page.click('button');
expect(await page.evaluate('window.clicked')).toBe(true); expect(await page.evaluate('window.clicked')).toBe(true);
}); });
it.skip(true)('should click on an animated button', async({page}) => { xit('should click on an animated button', async({page}) => {
const buttonSize = 50; const buttonSize = 50;
const containerWidth = 500; const containerWidth = 500;
const transition = 500; const transition = 500;

View file

@ -68,6 +68,24 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
}, element); }, element);
expect(pwBoundingBox).toEqual(webBoundingBox); expect(pwBoundingBox).toEqual(webBoundingBox);
}); });
it.skip(WEBKIT)('should work with page scale', async({newPage, server}) => {
const page = await newPage({ viewport: { width: 400, height: 400, isMobile: true} });
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await button.evaluate(button => {
document.body.style.margin = '0';
button.style.borderWidth = '0';
button.style.width = '200px';
button.style.height = '20px';
button.style.marginLeft = '17px';
button.style.marginTop = '23px';
});
const box = await button.boundingBox();
expect(Math.round(box.x * 100)).toBe(17 * 100);
expect(Math.round(box.y * 100)).toBe(23 * 100);
expect(Math.round(box.width * 100)).toBe(200 * 100);
expect(Math.round(box.height * 100)).toBe(20 * 100);
});
}); });
describe('ElementHandle.contentFrame', function() { describe('ElementHandle.contentFrame', function() {
@ -201,9 +219,8 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should work for TextNodes', async({page, server}) => { it('should work for TextNodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild); const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
let error = null; await buttonTextNode.click();
await buttonTextNode.click().catch(err => error = err); expect(await page.evaluate(() => result)).toBe('Clicked');
expect(error.message).toBe('Node is not of type HTMLElement');
}); });
it('should throw for detached nodes', async({page, server}) => { it('should throw for detached nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
@ -211,7 +228,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
await page.evaluate(button => button.remove(), button); await page.evaluate(button => button.remove(), button);
let error = null; let error = null;
await button.click().catch(err => error = err); await button.click().catch(err => error = err);
expect(error.message).toBe('Node is detached from document'); expect(error.message).toContain('Node is detached from document');
}); });
it('should throw for hidden nodes', async({page, server}) => { it('should throw for hidden nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');

View file

@ -160,7 +160,7 @@ module.exports.describe = function({testRunner, expect, CHROMIUM, FFOX, WEBKIT})
const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild); const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
const element = aHandle.asElement(); const element = aHandle.asElement();
expect(element).toBeTruthy(); expect(element).toBeTruthy();
expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element)); expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element)).toBeTruthy();
}); });
it('should work with nullified Node', async({page, server}) => { it('should work with nullified Node', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');

View file

@ -172,6 +172,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(error.message).toContain('has been closed'); expect(error.message).toContain('has been closed');
await browserServer.close(); await browserServer.close();
}); });
it('should emit close events on pages and contexts', async({server}) => {
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const context = await remote.newContext();
const page = await context.newPage();
let pageClosed = false;
page.on('close', e => pageClosed = true);
await Promise.all([
new Promise(f => context.on('close', f)),
browserServer.close()
]);
expect(pageClosed).toBeTruthy();
});
}); });
describe('Browser.close', function() { describe('Browser.close', function() {

View file

@ -118,6 +118,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
state.tearDown = async () => { state.tearDown = async () => {
await Promise.all(contexts.map(c => c.close())); await Promise.all(contexts.map(c => c.close()));
expect((await state.browser.contexts()).length).toBe(0, `"${test.fullName}" leaked a context`);
if (rl) { if (rl) {
rl.removeListener('line', onLine); rl.removeListener('line', onLine);
rl.close(); rl.close();

View file

@ -208,39 +208,8 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
}); });
describe('Frame.$wait', function() {
it('should accept arguments', async({page, server}) => {
await page.setContent('<div></div>');
const result = await page.$wait('div', (e, foo, bar) => e.nodeName + foo + bar, {}, 'foo1', 'bar2');
expect(await result.jsonValue()).toBe('DIVfoo1bar2');
});
it('should query selector constantly', async({page, server}) => {
await page.setContent('<div></div>');
let done = null;
const resultPromise = page.$wait('span', e => e).then(r => done = r);
expect(done).toBe(null);
await page.setContent('<section></section>');
expect(done).toBe(null);
await page.setContent('<span>text</span>');
await resultPromise;
expect(done).not.toBe(null);
expect(await done.evaluate(e => e.textContent)).toBe('text');
});
it('should be able to wait for removal', async({page}) => {
await page.setContent('<div></div>');
let done = null;
const resultPromise = page.$wait('div', e => !e).then(r => done = r);
expect(done).toBe(null);
await page.setContent('<section></section>');
await resultPromise;
expect(done).not.toBe(null);
expect(await done.jsonValue()).toBe(true);
});
});
describe('Frame.waitForSelector', function() { describe('Frame.waitForSelector', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag)); const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should immediately resolve promise if node exists', async({page, server}) => { it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
@ -248,7 +217,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div'); await frame.waitForSelector('div');
}); });
it('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([
@ -257,7 +225,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
]); ]);
expect(await page.evaluate(x => x.textContent, handle)).toBe('anything'); expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
}); });
it('should resolve promise when node is added', async({page, server}) => { it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
@ -268,7 +235,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue()); const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('DIV'); expect(tagName).toBe('DIV');
}); });
it('should work when node is added through innerHTML', async({page, server}) => { it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div'); const watchdog = page.waitForSelector('h3 div');
@ -276,7 +242,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>'); await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog; await watchdog;
}); });
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => { it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
@ -287,7 +252,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const eHandle = await watchdog; const eHandle = await watchdog;
expect(await eHandle.ownerFrame()).toBe(page.mainFrame()); expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
}); });
it('should run in specified frame', async({page, server}) => { it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
@ -299,7 +263,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const eHandle = await waitForSelectorPromise; const eHandle = await waitForSelectorPromise;
expect(await eHandle.ownerFrame()).toBe(frame2); expect(await eHandle.ownerFrame()).toBe(frame2);
}); });
it('should throw when frame is detached', async({page, server}) => { it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1];
@ -391,7 +354,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout'); expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
}); });
it('should respond to node attribute mutation', async({page, server}) => { it('should respond to node attribute mutation', async({page, server}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true); const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true);
@ -441,6 +403,11 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue()); const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('SPAN'); expect(tagName).toBe('SPAN');
}); });
it('$wait alias should work', async({page, server}) => {
await page.setContent('<section>test</section>');
const handle = await page.$wait('section');
expect(await handle.evaluate(e => e.textContent)).toBe('test');
});
}); });
describe('Frame.waitForSelector xpath', function() { describe('Frame.waitForSelector xpath', function() {

View file

@ -46,7 +46,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
afterEach(async state => { afterEach(async state => {
await state.page.evaluate(() => teardown()); await state.page.evaluate(() => teardown());
await state.page.context().close(); await state.page.close();
state.page = null; state.page = null;
}); });