Merge branch 'master' into ff-use-attach-to-bc
This commit is contained in:
commit
61bc21dd7c
|
|
@ -1 +1 @@
|
|||
1025
|
||||
1028
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ if [[ $OBJ_FOLDER == "" ]]; then
|
|||
fi
|
||||
|
||||
./mach package
|
||||
node ../install-preferences.js $PWD/$OBJ_FOLDER/dist/firefox
|
||||
|
||||
if ! [[ -d $OBJ_FOLDER/dist/firefox ]]; then
|
||||
echo "ERROR: cannot find $OBJ_FOLDER/dist/firefox folder in the checkout/. Did you build?"
|
||||
|
|
|
|||
|
|
@ -37,3 +37,11 @@ else
|
|||
fi
|
||||
|
||||
./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
|
||||
|
||||
|
|
|
|||
96
browser_patches/firefox/install-preferences.js
Normal file
96
browser_patches/firefox/install-preferences.js
Normal 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);
|
||||
});
|
||||
|
|
@ -469,10 +469,10 @@ index 6dca2b78830edc1ddbd66264bd332853729dac71..fbe89c9682834e11b9d9219d9eb056ed
|
|||
|
||||
diff --git a/testing/juggler/BrowserContextManager.js b/testing/juggler/BrowserContextManager.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c276337d8f0c0
|
||||
index 0000000000000000000000000000000000000000..8f031b3f9afbb357a6bebc9938fca50a04d0421c
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/BrowserContextManager.js
|
||||
@@ -0,0 +1,174 @@
|
||||
@@ -0,0 +1,180 @@
|
||||
+"use strict";
|
||||
+
|
||||
+const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");
|
||||
|
|
@ -503,9 +503,8 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
|
|||
+ }
|
||||
+
|
||||
+ constructor() {
|
||||
+ this._browserContextIdToUserContextId = new Map();
|
||||
+ this._userContextIdToBrowserContextId = new Map();
|
||||
+ this._principalsForBrowserContextId = new Map();
|
||||
+ this._browserContextIdToBrowserContext = new Map();
|
||||
+ this._userContextIdToBrowserContext = new Map();
|
||||
+
|
||||
+ // Cleanup containers from previous runs (if any)
|
||||
+ for (const identity of ContextualIdentityService.getPublicIdentities()) {
|
||||
|
|
@ -514,66 +513,75 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
|
|||
+ ContextualIdentityService.closeContainerTabs(identity.userContextId);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ this._defaultContext = new BrowserContext(this, undefined, undefined);
|
||||
+ }
|
||||
+
|
||||
+ grantPermissions(browserContextId, origin, permissions) {
|
||||
+ const attrs = browserContextId ? {userContextId: this.userContextId(browserContextId)} : {};
|
||||
+ createBrowserContext(options) {
|
||||
+ 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);
|
||||
+ if (!this._principalsForBrowserContextId.has(browserContextId))
|
||||
+ this._principalsForBrowserContextId.set(browserContextId, []);
|
||||
+ this._principalsForBrowserContextId.get(browserContextId).push(principal);
|
||||
+ this._principals.push(principal);
|
||||
+ for (const permission of ALL_PERMISSIONS) {
|
||||
+ const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION;
|
||||
+ Services.perms.addFromPrincipal(principal, permission, action);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ resetPermissions(browserContextId) {
|
||||
+ if (!this._principalsForBrowserContextId.has(browserContextId))
|
||||
+ return;
|
||||
+ const principals = this._principalsForBrowserContextId.get(browserContextId);
|
||||
+ for (const principal of principals) {
|
||||
+ resetPermissions() {
|
||||
+ for (const principal of this._principals) {
|
||||
+ for (const permission of ALL_PERMISSIONS)
|
||||
+ Services.perms.removeFromPrincipal(principal, permission);
|
||||
+ }
|
||||
+ this._principalsForBrowserContextId.delete(browserContextId);
|
||||
+ this._principals = [];
|
||||
+ }
|
||||
+
|
||||
+ createBrowserContext() {
|
||||
+ 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) {
|
||||
+ setCookies(cookies) {
|
||||
+ const protocolToSameSite = {
|
||||
+ [undefined]: Ci.nsICookie.SAMESITE_NONE,
|
||||
+ 'Lax': Ci.nsICookie.SAMESITE_LAX,
|
||||
+ 'Strict': Ci.nsICookie.SAMESITE_STRICT,
|
||||
+ };
|
||||
+ const userContextId = browserContextId ? this._browserContextIdToUserContextId.get(browserContextId) : undefined;
|
||||
+ for (const cookie of cookies) {
|
||||
+ const uri = cookie.url ? NetUtil.newURI(cookie.url) : null;
|
||||
+ let domain = cookie.domain;
|
||||
|
|
@ -599,19 +607,17 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
|
|||
+ cookie.httpOnly || false,
|
||||
+ cookie.expires === undefined || cookie.expires === -1 /* isSession */,
|
||||
+ cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires,
|
||||
+ { userContextId } /* originAttributes */,
|
||||
+ { userContextId: this.userContextId } /* originAttributes */,
|
||||
+ protocolToSameSite[cookie.sameSite],
|
||||
+ );
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ clearCookies(browserContextId) {
|
||||
+ const userContextId = browserContextId ? this._browserContextIdToUserContextId.get(browserContextId) : undefined;
|
||||
+ Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId }));
|
||||
+ clearCookies() {
|
||||
+ Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId: this.userContextId }));
|
||||
+ }
|
||||
+
|
||||
+ getCookies(browserContextId) {
|
||||
+ const userContextId = browserContextId ? this._browserContextIdToUserContextId.get(browserContextId) : 0;
|
||||
+ getCookies() {
|
||||
+ const result = [];
|
||||
+ const sameSiteToProtocol = {
|
||||
+ [Ci.nsICookie.SAMESITE_NONE]: 'None',
|
||||
|
|
@ -619,7 +625,7 @@ index 0000000000000000000000000000000000000000..a0a3799b6060692fa64f41411c0c2763
|
|||
+ [Ci.nsICookie.SAMESITE_STRICT]: 'Strict',
|
||||
+ };
|
||||
+ for (let cookie of Services.cookies.cookies) {
|
||||
+ if (cookie.originAttributes.userContextId !== userContextId)
|
||||
+ if (cookie.originAttributes.userContextId !== (this.userContextId || 0))
|
||||
+ continue;
|
||||
+ if (cookie.host === 'addons.mozilla.org')
|
||||
+ continue;
|
||||
|
|
@ -1444,10 +1450,10 @@ index 0000000000000000000000000000000000000000..66f61d432f9ad2f50931b780ec5ea0e3
|
|||
+this.NetworkObserver = NetworkObserver;
|
||||
diff --git a/testing/juggler/TargetRegistry.js b/testing/juggler/TargetRegistry.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf043c5bb292
|
||||
index 0000000000000000000000000000000000000000..2b1a1bd0f931d82824a86ecbb46f86483177a4e0
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/TargetRegistry.js
|
||||
@@ -0,0 +1,196 @@
|
||||
@@ -0,0 +1,214 @@
|
||||
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
|
||||
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
|
@ -1481,9 +1487,14 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
|
|||
+ this._tabToTarget = new Map();
|
||||
+
|
||||
+ for (const tab of this._mainWindow.gBrowser.tabs)
|
||||
+ this._ensureTargetForTab(tab);
|
||||
+ this._createTargetForTab(tab);
|
||||
+ 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 => {
|
||||
+ const tab = event.target;
|
||||
|
|
@ -1498,26 +1509,14 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
|
|||
+ }
|
||||
+
|
||||
+ async newPage({browserContextId}) {
|
||||
+ const browserContext = this._contextManager.browserContextForId(browserContextId);
|
||||
+ const tab = this._mainWindow.gBrowser.addTab('about:blank', {
|
||||
+ userContextId: this._contextManager.userContextId(browserContextId),
|
||||
+ userContextId: browserContext.userContextId,
|
||||
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
+ });
|
||||
+ this._mainWindow.gBrowser.selectedTab = tab;
|
||||
+ // Await navigation to about:blank
|
||||
+ await new Promise(resolve => {
|
||||
+ 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);
|
||||
+ const target = this._tabToTarget.get(tab);
|
||||
+ await target._contentReadyPromise;
|
||||
+ return target.id();
|
||||
+ }
|
||||
+
|
||||
|
|
@ -1550,30 +1549,31 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
|
|||
+ return target._tab;
|
||||
+ }
|
||||
+
|
||||
+ _ensureTargetForTab(tab) {
|
||||
+ if (this._tabToTarget.has(tab))
|
||||
+ 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);
|
||||
+ targetForId(targetId) {
|
||||
+ return this._targets.get(targetId);
|
||||
+ }
|
||||
+
|
||||
+ _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._tabToTarget.set(tab, target);
|
||||
+ this.emit(TargetRegistry.Events.TargetCreated, target.info());
|
||||
+ return target;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+class PageTarget {
|
||||
+ constructor(registry, tab, browserContextId, opener) {
|
||||
+ constructor(registry, tab, browserContext, opener) {
|
||||
+ this._targetId = helper.generateId();
|
||||
+ this._registry = registry;
|
||||
+ this._tab = tab;
|
||||
+ this._browserContextId = browserContextId;
|
||||
+ this._browserContext = browserContext;
|
||||
+ this._openerId = opener ? opener.id() : undefined;
|
||||
+ this._url = tab.linkedBrowser.currentURI.spec;
|
||||
+
|
||||
+ // First navigation always happens to about:blank - do not report it.
|
||||
+ this._skipNextNavigation = true;
|
||||
+
|
||||
+ const navigationListener = {
|
||||
+ QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener]),
|
||||
+ onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
|
||||
|
|
@ -1584,13 +1584,41 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
|
|||
+ 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() {
|
||||
+ const attachInfo = [];
|
||||
+ const data = { attachInfo, targetInfo: this.info() };
|
||||
+ const sessionIds = [];
|
||||
+ const data = { sessionIds, targetInfo: this.info() };
|
||||
+ this._registry.emit(TargetRegistry.Events.PageTargetReady, data);
|
||||
+ return attachInfo;
|
||||
+ this._contentReadyCallback();
|
||||
+ return {
|
||||
+ browserContextOptions: this._browserContext ? this._browserContext.options : {},
|
||||
+ waitForInitialNavigation: this._waitForInitialNavigation,
|
||||
+ sessionIds
|
||||
+ };
|
||||
+ }
|
||||
+
|
||||
+ id() {
|
||||
|
|
@ -1602,16 +1630,12 @@ index 0000000000000000000000000000000000000000..6231668027bb83bef2b3f839d44bcf04
|
|||
+ targetId: this.id(),
|
||||
+ type: 'page',
|
||||
+ url: this._url,
|
||||
+ browserContextId: this._browserContextId,
|
||||
+ browserContextId: this._browserContext ? this._browserContext.browserContextId : undefined,
|
||||
+ openerId: this._openerId,
|
||||
+ };
|
||||
+ }
|
||||
+
|
||||
+ _onNavigated(aLocation) {
|
||||
+ if (this._skipNextNavigation) {
|
||||
+ this._skipNextNavigation = false;
|
||||
+ return;
|
||||
+ }
|
||||
+ this._url = aLocation.spec;
|
||||
+ 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
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab2514648a
|
||||
index 0000000000000000000000000000000000000000..3891da101e6906ae2a3888e256aefd03f724ab4b
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/content/ContentSession.js
|
||||
@@ -0,0 +1,67 @@
|
||||
@@ -0,0 +1,68 @@
|
||||
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
+const {RuntimeAgent} = ChromeUtils.import('chrome://juggler/content/content/RuntimeAgent.js');
|
||||
+const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
|
||||
|
|
@ -1807,14 +1831,13 @@ index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab
|
|||
+ * @param {string} sessionId
|
||||
+ * @param {!ContentFrameMessageManager} messageManager
|
||||
+ * @param {!FrameTree} frameTree
|
||||
+ * @param {!ScrollbarManager} scrollbarManager
|
||||
+ * @param {!NetworkMonitor} networkMonitor
|
||||
+ */
|
||||
+ constructor(sessionId, messageManager, frameTree, scrollbarManager, networkMonitor) {
|
||||
+ constructor(sessionId, messageManager, frameTree, networkMonitor) {
|
||||
+ this._sessionId = sessionId;
|
||||
+ this._messageManager = messageManager;
|
||||
+ const runtimeAgent = new RuntimeAgent(this);
|
||||
+ const pageAgent = new PageAgent(this, runtimeAgent, frameTree, scrollbarManager, networkMonitor);
|
||||
+ const pageAgent = new PageAgent(this, runtimeAgent, frameTree, networkMonitor);
|
||||
+ this._agents = {
|
||||
+ Page: pageAgent,
|
||||
+ Runtime: runtimeAgent,
|
||||
|
|
@ -1822,6 +1845,8 @@ index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab
|
|||
+ this._eventListeners = [
|
||||
+ helper.addMessageListener(messageManager, this._sessionId, this._onMessage.bind(this)),
|
||||
+ ];
|
||||
+ runtimeAgent.enable();
|
||||
+ pageAgent.enable();
|
||||
+ }
|
||||
+
|
||||
+ emitEvent(eventName, params) {
|
||||
|
|
@ -1865,10 +1890,10 @@ index 0000000000000000000000000000000000000000..2302be180eeee0cc686171cefb56f7ab
|
|||
+
|
||||
diff --git a/testing/juggler/content/FrameTree.js b/testing/juggler/content/FrameTree.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe1730d20bfa0
|
||||
index 0000000000000000000000000000000000000000..dcebb7bbf6d0c9bb7a350443dfa2574bee5915ea
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/content/FrameTree.js
|
||||
@@ -0,0 +1,242 @@
|
||||
@@ -0,0 +1,252 @@
|
||||
+"use strict";
|
||||
+const Ci = Components.interfaces;
|
||||
+const Cr = Components.results;
|
||||
|
|
@ -1880,10 +1905,11 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
|
|||
+const helper = new Helper();
|
||||
+
|
||||
+class FrameTree {
|
||||
+ constructor(rootDocShell) {
|
||||
+ constructor(rootDocShell, waitForInitialNavigation) {
|
||||
+ EventEmitter.decorate(this);
|
||||
+ this._docShellToFrame = new Map();
|
||||
+ this._frameIdToFrame = new Map();
|
||||
+ this._pageReady = !waitForInitialNavigation;
|
||||
+ this._mainFrame = this._createFrame(rootDocShell);
|
||||
+ const webProgress = rootDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
+ .getInterface(Ci.nsIWebProgress);
|
||||
|
|
@ -1902,6 +1928,10 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
|
|||
+ ];
|
||||
+ }
|
||||
+
|
||||
+ isPageReady() {
|
||||
+ return this._pageReady;
|
||||
+ }
|
||||
+
|
||||
+ frameForDocShell(docShell) {
|
||||
+ return this._docShellToFrame.get(docShell) || null;
|
||||
+ }
|
||||
|
|
@ -1960,6 +1990,10 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
|
|||
+ frame._lastCommittedNavigationId = navigationId;
|
||||
+ frame._url = channel.URI.spec;
|
||||
+ 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) {
|
||||
+ // Navigation is aborted.
|
||||
+ const navigationId = frame._pendingNavigationId;
|
||||
|
|
@ -2035,6 +2069,7 @@ index 0000000000000000000000000000000000000000..f239981ae0d87581d9a1c25ca1ebe173
|
|||
+ NavigationCommitted: 'navigationcommitted',
|
||||
+ NavigationAborted: 'navigationaborted',
|
||||
+ SameDocumentNavigation: 'samedocumentnavigation',
|
||||
+ PageReady: 'pageready',
|
||||
+};
|
||||
+
|
||||
+class Frame {
|
||||
|
|
@ -2181,10 +2216,10 @@ index 0000000000000000000000000000000000000000..2508cce41565023b7fee9c7b85afe8ec
|
|||
+
|
||||
diff --git a/testing/juggler/content/PageAgent.js b/testing/juggler/content/PageAgent.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a8aa1b032
|
||||
index 0000000000000000000000000000000000000000..e505911e81ef014f19a3a732f3c5f631f0bd1780
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/content/PageAgent.js
|
||||
@@ -0,0 +1,895 @@
|
||||
@@ -0,0 +1,875 @@
|
||||
+"use strict";
|
||||
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
+const Ci = Components.interfaces;
|
||||
|
|
@ -2343,12 +2378,11 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
|
|||
+}
|
||||
+
|
||||
+class PageAgent {
|
||||
+ constructor(session, runtimeAgent, frameTree, scrollbarManager, networkMonitor) {
|
||||
+ constructor(session, runtimeAgent, frameTree, networkMonitor) {
|
||||
+ this._session = session;
|
||||
+ this._runtime = runtimeAgent;
|
||||
+ this._frameTree = frameTree;
|
||||
+ this._networkMonitor = networkMonitor;
|
||||
+ this._scrollbarManager = scrollbarManager;
|
||||
+
|
||||
+ this._frameData = new Map();
|
||||
+ this._scriptsToEvaluateOnNewDocument = new Map();
|
||||
|
|
@ -2399,14 +2433,6 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
|
|||
+ 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}) {
|
||||
+ const docShell = this._frameTree.mainFrame().docShell();
|
||||
+ 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}) {
|
||||
+ const scriptId = helper.generateId();
|
||||
+ this._scriptsToEvaluateOnNewDocument.set(scriptId, {script, worldName});
|
||||
|
|
@ -2454,11 +2470,6 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
|
|||
+ docShell.defaultLoadFlags = cacheDisabled ? disable : enable;
|
||||
+ }
|
||||
+
|
||||
+ setJavascriptEnabled({enabled}) {
|
||||
+ const docShell = this._frameTree.mainFrame().docShell();
|
||||
+ docShell.allowJavascript = enabled;
|
||||
+ }
|
||||
+
|
||||
+ enable() {
|
||||
+ if (this._enabled)
|
||||
+ return;
|
||||
|
|
@ -2486,11 +2497,15 @@ index 0000000000000000000000000000000000000000..d592ad9355e7e74a1685acd9338b387a
|
|||
+ helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)),
|
||||
+ helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.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);
|
||||
+ for (const workerDebugger of this._wdm.getWorkerDebuggerEnumerator())
|
||||
+ this._onWorkerCreated(workerDebugger);
|
||||
+
|
||||
+ if (this._frameTree.isPageReady())
|
||||
+ this._session.emitEvent('Page.ready', {});
|
||||
+ }
|
||||
+
|
||||
+ setInterceptFileChooserDialog({enabled}) {
|
||||
|
|
@ -3869,10 +3884,10 @@ index 0000000000000000000000000000000000000000..3a386425d3796d0a6786dea193b3402d
|
|||
+
|
||||
diff --git a/testing/juggler/content/main.js b/testing/juggler/content/main.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..6a9f908676fc025b74ea585a0e4e9194f704d13f
|
||||
index 0000000000000000000000000000000000000000..556f48d627401b8507b8bbec6dbf7ca797644baf
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/content/main.js
|
||||
@@ -0,0 +1,56 @@
|
||||
@@ -0,0 +1,76 @@
|
||||
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
+const {ContentSession} = ChromeUtils.import('chrome://juggler/content/content/ContentSession.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 sessions = new Map();
|
||||
+const frameTree = new FrameTree(docShell);
|
||||
+const networkMonitor = new NetworkMonitor(docShell, frameTree);
|
||||
+const scrollbarManager = new ScrollbarManager(docShell);
|
||||
+
|
||||
+let frameTree;
|
||||
+let networkMonitor;
|
||||
+const helper = new Helper();
|
||||
+const messageManager = this;
|
||||
+let gListeners;
|
||||
+
|
||||
+function createContentSession(sessionId) {
|
||||
+ const session = new ContentSession(sessionId, messageManager, frameTree, scrollbarManager, networkMonitor);
|
||||
+ sessions.set(sessionId, session);
|
||||
+ return session;
|
||||
+ sessions.set(sessionId, new ContentSession(sessionId, messageManager, frameTree, networkMonitor));
|
||||
+}
|
||||
+
|
||||
+function disposeContentSession(sessionId) {
|
||||
|
|
@ -3901,34 +3914,56 @@ index 0000000000000000000000000000000000000000..6a9f908676fc025b74ea585a0e4e9194
|
|||
+ session.dispose();
|
||||
+}
|
||||
+
|
||||
+const gListeners = [
|
||||
+ helper.addMessageListener(messageManager, 'juggler:create-content-session', msg => {
|
||||
+ const sessionId = msg.data;
|
||||
+function initialize() {
|
||||
+ let response = sendSyncMessage('juggler:content-ready', {})[0];
|
||||
+ 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);
|
||||
+ }),
|
||||
+
|
||||
+ helper.addMessageListener(messageManager, 'juggler:dispose-content-session', msg => {
|
||||
+ const sessionId = msg.data;
|
||||
+ disposeContentSession(sessionId);
|
||||
+ }),
|
||||
+ gListeners = [
|
||||
+ helper.addMessageListener(messageManager, 'juggler:create-content-session', msg => {
|
||||
+ const sessionId = msg.data;
|
||||
+ createContentSession(sessionId);
|
||||
+ }),
|
||||
+
|
||||
+ helper.addEventListener(messageManager, 'unload', msg => {
|
||||
+ helper.removeListeners(gListeners);
|
||||
+ for (const session of sessions.values())
|
||||
+ session.dispose();
|
||||
+ sessions.clear();
|
||||
+ scrollbarManager.dispose();
|
||||
+ networkMonitor.dispose();
|
||||
+ frameTree.dispose();
|
||||
+ }),
|
||||
+];
|
||||
+ helper.addMessageListener(messageManager, 'juggler:dispose-content-session', msg => {
|
||||
+ const sessionId = msg.data;
|
||||
+ disposeContentSession(sessionId);
|
||||
+ }),
|
||||
+
|
||||
+const [attachInfo] = sendSyncMessage('juggler:content-ready', {});
|
||||
+for (const { sessionId, messages } of attachInfo || []) {
|
||||
+ const session = createContentSession(sessionId);
|
||||
+ for (const message of messages)
|
||||
+ session.handleMessage(message);
|
||||
+ helper.addEventListener(messageManager, 'unload', msg => {
|
||||
+ helper.removeListeners(gListeners);
|
||||
+ for (const session of sessions.values())
|
||||
+ session.dispose();
|
||||
+ sessions.clear();
|
||||
+ scrollbarManager.dispose();
|
||||
+ networkMonitor.dispose();
|
||||
+ frameTree.dispose();
|
||||
+ }),
|
||||
+ ];
|
||||
+}
|
||||
+
|
||||
+initialize();
|
||||
diff --git a/testing/juggler/jar.mn b/testing/juggler/jar.mn
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..76377927a8c9af3cac3b028ff754491966d03ba3
|
||||
|
|
@ -4009,10 +4044,10 @@ index 0000000000000000000000000000000000000000..a2d3b79469566ca2edb7d864621f7085
|
|||
+this.AccessibilityHandler = AccessibilityHandler;
|
||||
diff --git a/testing/juggler/protocol/BrowserHandler.js b/testing/juggler/protocol/BrowserHandler.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f350475551a73e
|
||||
index 0000000000000000000000000000000000000000..6b42032e8f6d39025f455300d376084826a781cc
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/protocol/BrowserHandler.js
|
||||
@@ -0,0 +1,72 @@
|
||||
@@ -0,0 +1,73 @@
|
||||
+"use strict";
|
||||
+
|
||||
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
|
@ -4042,7 +4077,7 @@ index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f35047
|
|||
+
|
||||
+ async setIgnoreHTTPSErrors({enabled}) {
|
||||
+ if (!enabled) {
|
||||
+ allowAllCerts.disable()
|
||||
+ allowAllCerts.disable()
|
||||
+ Services.prefs.setBoolPref('security.mixed_content.block_active_content', true);
|
||||
+ } else {
|
||||
+ allowAllCerts.enable()
|
||||
|
|
@ -4051,23 +4086,24 @@ index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f35047
|
|||
+ }
|
||||
+
|
||||
+ grantPermissions({browserContextId, origin, permissions}) {
|
||||
+ this._contextManager.grantPermissions(browserContextId, origin, permissions);
|
||||
+ this._contextManager.browserContextForId(browserContextId).grantPermissions(origin, permissions);
|
||||
+ }
|
||||
+
|
||||
+ resetPermissions({browserContextId}) {
|
||||
+ this._contextManager.resetPermissions(browserContextId);
|
||||
+ this._contextManager.browserContextForId(browserContextId).resetPermissions();
|
||||
+ }
|
||||
+
|
||||
+ setCookies({browserContextId, cookies}) {
|
||||
+ this._contextManager.setCookies(browserContextId, cookies);
|
||||
+ this._contextManager.browserContextForId(browserContextId).setCookies(cookies);
|
||||
+ }
|
||||
+
|
||||
+ clearCookies({browserContextId}) {
|
||||
+ this._contextManager.clearCookies(browserContextId);
|
||||
+ this._contextManager.browserContextForId(browserContextId).clearCookies();
|
||||
+ }
|
||||
+
|
||||
+ getCookies({browserContextId}) {
|
||||
+ return {cookies: this._contextManager.getCookies(browserContextId)};
|
||||
+ const cookies = this._contextManager.browserContextForId(browserContextId).getCookies();
|
||||
+ return {cookies};
|
||||
+ }
|
||||
+
|
||||
+ async getInfo() {
|
||||
|
|
@ -4087,10 +4123,10 @@ index 0000000000000000000000000000000000000000..9bf14b3c4842d15508f67daa10f35047
|
|||
+this.BrowserHandler = BrowserHandler;
|
||||
diff --git a/testing/juggler/protocol/Dispatcher.js b/testing/juggler/protocol/Dispatcher.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd1c8b18c9
|
||||
index 0000000000000000000000000000000000000000..5c5a73b35cd178b51899ab3dd681d46b6c3e4770
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/protocol/Dispatcher.js
|
||||
@@ -0,0 +1,254 @@
|
||||
@@ -0,0 +1,265 @@
|
||||
+const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js");
|
||||
+const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/protocol/Protocol.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);
|
||||
+ if (!targetInfo)
|
||||
+ throw new Error(`Target "${targetId}" is not found`);
|
||||
|
|
@ -4135,6 +4171,8 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
|
|||
+
|
||||
+ const sessionId = helper.generateId();
|
||||
+ const contentSession = targetInfo.type === 'page' ? new ContentSession(this, sessionId, targetInfo) : null;
|
||||
+ if (shouldConnect && contentSession)
|
||||
+ contentSession.connect();
|
||||
+ const chromeSession = new ChromeSession(this, sessionId, contentSession, targetInfo);
|
||||
+ targetSessions.set(sessionId, chromeSession);
|
||||
+ this._sessions.set(sessionId, chromeSession);
|
||||
|
|
@ -4234,6 +4272,12 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
|
|||
+ if (protocol.domains[domainName].targets.includes(targetInfo.type))
|
||||
+ 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() {
|
||||
|
|
@ -4284,7 +4328,6 @@ index 0000000000000000000000000000000000000000..835aa8b7d1c5a8e643691c4b89da77cd
|
|||
+ this._messageId = 0;
|
||||
+ this._pendingMessages = new Map();
|
||||
+ this._sessionId = sessionId;
|
||||
+ this._browser.messageManager.sendAsyncMessage('juggler:create-content-session', this._sessionId);
|
||||
+ this._disposed = false;
|
||||
+ this._eventListeners = [
|
||||
+ 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() {
|
||||
+ return this._disposed;
|
||||
+ }
|
||||
|
|
@ -4519,10 +4566,10 @@ index 0000000000000000000000000000000000000000..5d776ab6f28ccff44ef4663e8618ad9c
|
|||
+this.NetworkHandler = NetworkHandler;
|
||||
diff --git a/testing/juggler/protocol/PageHandler.js b/testing/juggler/protocol/PageHandler.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e9c5d94cf65b44d57bdb21ec892c3e325220a879
|
||||
index 0000000000000000000000000000000000000000..efb0fc1f3f7af37e101976cf8a682e09c223e59f
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/protocol/PageHandler.js
|
||||
@@ -0,0 +1,285 @@
|
||||
@@ -0,0 +1,266 @@
|
||||
+"use strict";
|
||||
+
|
||||
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
|
|
@ -4540,6 +4587,7 @@ index 0000000000000000000000000000000000000000..e9c5d94cf65b44d57bdb21ec892c3e32
|
|||
+ constructor(chromeSession, contentSession) {
|
||||
+ this._chromeSession = chromeSession;
|
||||
+ this._contentSession = contentSession;
|
||||
+ this._pageTarget = TargetRegistry.instance().targetForId(chromeSession.targetId());
|
||||
+ this._browser = TargetRegistry.instance().tabForTarget(chromeSession.targetId()).linkedBrowser;
|
||||
+ this._dialogs = new Map();
|
||||
+
|
||||
|
|
@ -4568,38 +4616,18 @@ index 0000000000000000000000000000000000000000..e9c5d94cf65b44d57bdb21ec892c3e32
|
|||
+ }),
|
||||
+ helper.addEventListener(this._browser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
|
||||
+ ];
|
||||
+ await this._contentSession.send('Page.enable');
|
||||
+ }
|
||||
+
|
||||
+ dispose() {
|
||||
+ helper.removeListeners(this._eventListeners);
|
||||
+ }
|
||||
+
|
||||
+ async setViewport({viewport}) {
|
||||
+ if (viewport) {
|
||||
+ const {width, height} = viewport;
|
||||
+ this._browser.style.setProperty('min-width', width + 'px');
|
||||
+ this._browser.style.setProperty('min-height', height + 'px');
|
||||
+ 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
|
||||
+ }),
|
||||
+ ]);
|
||||
+ async setViewportSize({viewportSize}) {
|
||||
+ const size = this._pageTarget.setViewportSize(viewportSize);
|
||||
+ await this._contentSession.send('Page.awaitViewportDimensions', {
|
||||
+ width: size.width,
|
||||
+ height: size.height
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ _updateModalDialogs() {
|
||||
|
|
@ -4959,10 +4987,10 @@ index 0000000000000000000000000000000000000000..78b6601b91d0b7fcda61114e6846aa07
|
|||
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
|
||||
diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497a16559ea
|
||||
index 0000000000000000000000000000000000000000..a0a96a87ff4a422deccae1045962690fa7941f25
|
||||
--- /dev/null
|
||||
+++ 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');
|
||||
+
|
||||
+// Protocol-specific types.
|
||||
|
|
@ -5016,13 +5044,16 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ height: t.Number,
|
||||
+};
|
||||
+
|
||||
+pageTypes.Viewport = {
|
||||
+pageTypes.Size = {
|
||||
+ width: t.Number,
|
||||
+ height: t.Number,
|
||||
+};
|
||||
+
|
||||
+pageTypes.Viewport = {
|
||||
+ viewportSize: pageTypes.Size,
|
||||
+ deviceScaleFactor: t.Number,
|
||||
+ isMobile: t.Boolean,
|
||||
+ hasTouch: t.Boolean,
|
||||
+ isLandscape: t.Boolean,
|
||||
+};
|
||||
+
|
||||
+pageTypes.DOMQuad = {
|
||||
|
|
@ -5218,6 +5249,9 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ params: {
|
||||
+ removeOnDetach: t.Optional(t.Boolean),
|
||||
+ userAgent: t.Optional(t.String),
|
||||
+ bypassCSP: t.Optional(t.Boolean),
|
||||
+ javaScriptDisabled: t.Optional(t.Boolean),
|
||||
+ viewport: t.Optional(pageTypes.Viewport),
|
||||
+ },
|
||||
+ returns: {
|
||||
+ browserContextId: t.String,
|
||||
|
|
@ -5288,7 +5322,6 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ },
|
||||
+ },
|
||||
+ methods: {
|
||||
+ 'enable': {},
|
||||
+ 'setRequestInterception': {
|
||||
+ params: {
|
||||
+ enabled: t.Boolean,
|
||||
|
|
@ -5359,9 +5392,6 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ },
|
||||
+ },
|
||||
+ methods: {
|
||||
+ 'enable': {
|
||||
+ params: {},
|
||||
+ },
|
||||
+ 'evaluate': {
|
||||
+ params: {
|
||||
+ // Pass frameId here.
|
||||
|
|
@ -5414,6 +5444,8 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+
|
||||
+ types: pageTypes,
|
||||
+ events: {
|
||||
+ 'ready': {
|
||||
+ },
|
||||
+ 'eventFired': {
|
||||
+ frameId: t.String,
|
||||
+ name: t.Enum(['load', 'DOMContentLoaded']),
|
||||
|
|
@ -5485,9 +5517,6 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ },
|
||||
+
|
||||
+ methods: {
|
||||
+ 'enable': {
|
||||
+ params: {},
|
||||
+ },
|
||||
+ 'close': {
|
||||
+ params: {
|
||||
+ runBeforeUnload: t.Optional(t.Boolean),
|
||||
|
|
@ -5505,9 +5534,9 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ name: t.String,
|
||||
+ },
|
||||
+ },
|
||||
+ 'setViewport': {
|
||||
+ 'setViewportSize': {
|
||||
+ params: {
|
||||
+ viewport: t.Nullable(pageTypes.Viewport),
|
||||
+ viewportSize: t.Nullable(pageTypes.Size),
|
||||
+ },
|
||||
+ },
|
||||
+ 'setEmulatedMedia': {
|
||||
|
|
@ -5516,21 +5545,11 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+ colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])),
|
||||
+ },
|
||||
+ },
|
||||
+ 'setBypassCSP': {
|
||||
+ params: {
|
||||
+ enabled: t.Boolean
|
||||
+ }
|
||||
+ },
|
||||
+ 'setCacheDisabled': {
|
||||
+ params: {
|
||||
+ cacheDisabled: t.Boolean,
|
||||
+ },
|
||||
+ },
|
||||
+ 'setJavascriptEnabled': {
|
||||
+ params: {
|
||||
+ enabled: t.Boolean,
|
||||
+ },
|
||||
+ },
|
||||
+ 'describeNode': {
|
||||
+ params: {
|
||||
+ frameId: t.String,
|
||||
|
|
@ -5720,10 +5739,10 @@ index 0000000000000000000000000000000000000000..a59a7c218fdc3d2b3282bc5419eb4497
|
|||
+this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme'];
|
||||
diff --git a/testing/juggler/protocol/RuntimeHandler.js b/testing/juggler/protocol/RuntimeHandler.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0026e8ff58ef6268f4c63783d0ff68ff355b1e72
|
||||
index 0000000000000000000000000000000000000000..089e66c617f114fcb32b3cea20abc6fb80e26a1e
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/protocol/RuntimeHandler.js
|
||||
@@ -0,0 +1,41 @@
|
||||
@@ -0,0 +1,37 @@
|
||||
+"use strict";
|
||||
+
|
||||
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
|
|
@ -5740,10 +5759,6 @@ index 0000000000000000000000000000000000000000..0026e8ff58ef6268f4c63783d0ff68ff
|
|||
+ this._contentSession = contentSession;
|
||||
+ }
|
||||
+
|
||||
+ async enable(options) {
|
||||
+ return await this._contentSession.send('Runtime.enable', options);
|
||||
+ }
|
||||
+
|
||||
+ async evaluate(options) {
|
||||
+ return await this._contentSession.send('Runtime.evaluate', options);
|
||||
+ }
|
||||
|
|
@ -5767,10 +5782,10 @@ index 0000000000000000000000000000000000000000..0026e8ff58ef6268f4c63783d0ff68ff
|
|||
+this.RuntimeHandler = RuntimeHandler;
|
||||
diff --git a/testing/juggler/protocol/TargetHandler.js b/testing/juggler/protocol/TargetHandler.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c33212f75
|
||||
index 0000000000000000000000000000000000000000..4795a4ddecdd016d6efbcde35aa7321af17cd7dc
|
||||
--- /dev/null
|
||||
+++ b/testing/juggler/protocol/TargetHandler.js
|
||||
@@ -0,0 +1,104 @@
|
||||
@@ -0,0 +1,100 @@
|
||||
+"use strict";
|
||||
+
|
||||
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
|
@ -5789,34 +5804,34 @@ index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c
|
|||
+ this._targetRegistry = TargetRegistry.instance();
|
||||
+ this._enabled = false;
|
||||
+ this._eventListeners = [];
|
||||
+ this._createdBrowserContextOptions = new Map();
|
||||
+ this._createdBrowserContextIds = new Set();
|
||||
+ }
|
||||
+
|
||||
+ async attachToTarget({targetId}) {
|
||||
+ if (!this._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};
|
||||
+ }
|
||||
+
|
||||
+ async createBrowserContext(options) {
|
||||
+ if (!this._enabled)
|
||||
+ throw new Error('Target domain is not enabled');
|
||||
+ const browserContextId = this._contextManager.createBrowserContext();
|
||||
+ // TODO: introduce BrowserContext class, with options?
|
||||
+ this._createdBrowserContextOptions.set(browserContextId, options);
|
||||
+ return {browserContextId};
|
||||
+ const browserContext = this._contextManager.createBrowserContext(options);
|
||||
+ this._createdBrowserContextIds.add(browserContext.browserContextId);
|
||||
+ return {browserContextId: browserContext.browserContextId};
|
||||
+ }
|
||||
+
|
||||
+ async removeBrowserContext({browserContextId}) {
|
||||
+ if (!this._enabled)
|
||||
+ throw new Error('Target domain is not enabled');
|
||||
+ this._createdBrowserContextOptions.delete(browserContextId);
|
||||
+ this._contextManager.removeBrowserContext(browserContextId);
|
||||
+ this._createdBrowserContextIds.delete(browserContextId);
|
||||
+ this._contextManager.browserContextForId(browserContextId).destroy();
|
||||
+ }
|
||||
+
|
||||
+ async getBrowserContexts() {
|
||||
+ return {browserContextIds: this._contextManager.getBrowserContexts()};
|
||||
+ const browserContexts = this._contextManager.getBrowserContexts();
|
||||
+ return {browserContextIds: browserContexts.map(bc => bc.browserContextId)};
|
||||
+ }
|
||||
+
|
||||
+ async enable() {
|
||||
|
|
@ -5836,9 +5851,10 @@ index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c
|
|||
+
|
||||
+ dispose() {
|
||||
+ helper.removeListeners(this._eventListeners);
|
||||
+ for (const [browserContextId, options] of this._createdBrowserContextOptions) {
|
||||
+ if (options.removeOnDetach)
|
||||
+ this._contextManager.removeBrowserContext(browserContextId);
|
||||
+ for (const browserContextId of this._createdBrowserContextIds) {
|
||||
+ const browserContext = this._contextManager.browserContextForId(browserContextId);
|
||||
+ if (browserContext.options.removeOnDetach)
|
||||
+ browserContext.destroy();
|
||||
+ }
|
||||
+ this._createdBrowserContextOptions.clear();
|
||||
+ }
|
||||
|
|
@ -5855,16 +5871,11 @@ index 0000000000000000000000000000000000000000..454fa4ebb9bda29bb957fa64a08ca92c
|
|||
+ this._session.emitEvent('Target.targetDestroyed', targetInfo);
|
||||
+ }
|
||||
+
|
||||
+ _onPageTargetReady({attachInfo, targetInfo}) {
|
||||
+ const options = this._createdBrowserContextOptions.get(targetInfo.browserContextId);
|
||||
+ if (!options)
|
||||
+ _onPageTargetReady({sessionIds, targetInfo}) {
|
||||
+ if (!this._createdBrowserContextIds.has(targetInfo.browserContextId))
|
||||
+ return;
|
||||
+ const sessionId = this._session.dispatcher().createSession(targetInfo.targetId);
|
||||
+ const messages = [];
|
||||
+ // 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 });
|
||||
+ const sessionId = this._session.dispatcher().createSession(targetInfo.targetId, false /* shouldConnect */);
|
||||
+ sessionIds.push(sessionId);
|
||||
+ }
|
||||
+
|
||||
+ async newPage({browserContextId}) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
233
browser_patches/firefox/preferences/playwright.cfg
Normal file
233
browser_patches/firefox/preferences/playwright.cfg
Normal 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;
|
||||
|
|
@ -1 +1 @@
|
|||
1141
|
||||
1143
|
||||
|
|
|
|||
|
|
@ -731,10 +731,10 @@ index 0000000000000000000000000000000000000000..79edea03fed4e9be5da96e1275e182a4
|
|||
+}
|
||||
diff --git a/Source/JavaScriptCore/inspector/protocol/Emulation.json b/Source/JavaScriptCore/inspector/protocol/Emulation.json
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..bcf863e4bba3b99f66e75dabfc4d8c1289cc2b78
|
||||
index 0000000000000000000000000000000000000000..552e5dd60fa53fada79f8d6e333f52bc10a2bead
|
||||
--- /dev/null
|
||||
+++ b/Source/JavaScriptCore/inspector/protocol/Emulation.json
|
||||
@@ -0,0 +1,32 @@
|
||||
@@ -0,0 +1,39 @@
|
||||
+{
|
||||
+ "domain": "Emulation",
|
||||
+ "availability": ["web"],
|
||||
|
|
@ -764,6 +764,13 @@ index 0000000000000000000000000000000000000000..bcf863e4bba3b99f66e75dabfc4d8c12
|
|||
+ { "name": "username", "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
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..78815bc17978a23ca811ff99039b4fe86e6fb4a0
|
||||
index 0000000000000000000000000000000000000000..466d5129363e8b3d2e7cfc10f2a1ac7ae77f634f
|
||||
--- /dev/null
|
||||
+++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp
|
||||
@@ -0,0 +1,95 @@
|
||||
@@ -0,0 +1,103 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2019 Microsoft Corporation.
|
||||
+ *
|
||||
|
|
@ -8821,13 +8828,21 @@ index 0000000000000000000000000000000000000000..78815bc17978a23ca811ff99039b4fe8
|
|||
+ 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
|
||||
diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..77dff2c191fee081773bc5705d80168c3898f496
|
||||
index 0000000000000000000000000000000000000000..43d827233df725fa8c85fc9ff4b20a1f716803e0
|
||||
--- /dev/null
|
||||
+++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h
|
||||
@@ -0,0 +1,66 @@
|
||||
@@ -0,0 +1,67 @@
|
||||
+/*
|
||||
+ * 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 setJavaScriptEnabled(Inspector::ErrorString&, bool enabled) override;
|
||||
+ void setAuthCredentials(Inspector::ErrorString&, const String*, const String*) override;
|
||||
+ void setActiveAndFocused(Inspector::ErrorString&, const bool*) override;
|
||||
+
|
||||
+private:
|
||||
+ void platformSetSize(int width, int height, Function<void (const String& error)>&&);
|
||||
|
|
@ -9235,7 +9251,7 @@ index 0000000000000000000000000000000000000000..76290475097e756e3d932d22be4d8c79
|
|||
+
|
||||
+} // namespace WebKit
|
||||
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
|
||||
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
|
||||
@@ -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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
+void WebPageProxy::setAuthCredentialsForAutomation(Optional<WebCore::Credential>&& credentials) {
|
||||
+void WebPageProxy::setAuthCredentialsForAutomation(Optional<WebCore::Credential>&& 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;
|
||||
+}
|
||||
+
|
||||
+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)
|
||||
+{
|
||||
+ fprintf(stderr, "RENDERER: %s\n", str.utf8().data());
|
||||
|
|
@ -9288,7 +9316,33 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -9297,7 +9351,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
const EventNames& names = eventNames();
|
||||
for (auto& touchPoint : touchStartEvent.touchPoints()) {
|
||||
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.touchMoveTracking = TrackingType::Synchronous;
|
||||
m_touchAndPointerEventTracking.touchEndTracking = TrackingType::Synchronous;
|
||||
|
|
@ -9306,7 +9360,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -9314,7 +9368,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
if (!hasRunningProcess()) {
|
||||
sender->send(PolicyAction::Ignore, 0, DownloadID(), WTF::nullopt);
|
||||
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.
|
||||
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)
|
||||
@@ -4449,6 +4480,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& p
|
||||
@@ -4449,6 +4511,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& p
|
||||
|
||||
m_failingProvisionalLoadURL = { };
|
||||
|
||||
|
|
@ -9331,7 +9385,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
// If the provisional page's load fails then we destroy the provisional page.
|
||||
if (m_provisionalPage && m_provisionalPage->mainFrame() == frame && willContinueLoading == WillContinueLoading::No)
|
||||
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,
|
||||
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,
|
||||
@@ -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())
|
||||
automationSession->willShowJavaScriptDialog(*this);
|
||||
}
|
||||
|
|
@ -9359,7 +9413,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
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())
|
||||
automationSession->willShowJavaScriptDialog(*this);
|
||||
}
|
||||
|
|
@ -9368,7 +9422,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
|
||||
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())
|
||||
automationSession->willShowJavaScriptDialog(*this);
|
||||
}
|
||||
|
|
@ -9377,7 +9431,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
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())
|
||||
automationSession->mouseEventsFlushedForPage(*this);
|
||||
didFinishProcessingAllPendingMouseEvents();
|
||||
|
|
@ -9394,7 +9448,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
}
|
||||
|
||||
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::Char: {
|
||||
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());
|
||||
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.
|
||||
// Protect against this being destroyed.
|
||||
Ref<WebPageProxy> protect(*this);
|
||||
|
|
@ -9410,7 +9464,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
pageClient().doneWithKeyEvent(event, handled);
|
||||
if (!handled)
|
||||
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 (auto* automationSession = process().processPool().automationSession())
|
||||
automationSession->keyboardEventsFlushedForPage(*this);
|
||||
|
|
@ -9418,7 +9472,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
}
|
||||
break;
|
||||
}
|
||||
@@ -7238,8 +7287,10 @@ static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason)
|
||||
@@ -7238,8 +7318,10 @@ static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason)
|
||||
void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason)
|
||||
{
|
||||
RELEASE_LOG_IF_ALLOWED(Loading, "dispatchProcessDidTerminate: reason = %d", reason);
|
||||
|
|
@ -9430,7 +9484,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
if (m_loaderClient)
|
||||
handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this);
|
||||
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)
|
||||
{
|
||||
|
|
@ -9445,7 +9499,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) {
|
||||
m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = makeRef(*this), authenticationChallenge = authenticationChallenge.copyRef()] (bool 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);
|
||||
|
||||
// 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);
|
||||
Function<void(bool)> completionHandler = [request = WTFMove(request)](bool allowed) {
|
||||
if (allowed)
|
||||
@@ -7802,6 +7862,11 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID,
|
||||
@@ -7802,6 +7893,11 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID,
|
||||
else
|
||||
request->deny();
|
||||
};
|
||||
|
|
@ -9468,7 +9522,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205
|
|||
// FIXME: Once iOS migrates to the new WKUIDelegate SPI, clean this up
|
||||
// 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
|
||||
index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c3605f8e4 100644
|
||||
index b3856a4f9e7859bf3e0b9957c95040a784e9b317..2d154febc73de4877463e5099ffdc3002f6dfd2b 100644
|
||||
--- a/Source/WebKit/UIProcess/WebPageProxy.h
|
||||
+++ b/Source/WebKit/UIProcess/WebPageProxy.h
|
||||
@@ -35,6 +35,7 @@
|
||||
|
|
@ -9488,18 +9542,19 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c
|
|||
|
||||
#if PLATFORM(IOS_FAMILY)
|
||||
void showInspectorIndication();
|
||||
@@ -555,6 +558,10 @@ public:
|
||||
@@ -555,6 +558,11 @@ public:
|
||||
|
||||
void setPageLoadStateObserver(std::unique_ptr<PageLoadState::Observer>&&);
|
||||
|
||||
+ void setAuthCredentialsForAutomation(Optional<WebCore::Credential>&&);
|
||||
+ void setPermissionsForAutomation(const HashMap<String, HashSet<String>>&);
|
||||
+ void setActiveForAutomation(Optional<bool> active);
|
||||
+ void logToStderr(const String& str);
|
||||
+
|
||||
void initializeWebPage();
|
||||
void setDrawingArea(std::unique_ptr<DrawingAreaProxy>&&);
|
||||
|
||||
@@ -580,6 +587,7 @@ public:
|
||||
@@ -580,6 +588,7 @@ public:
|
||||
void closePage();
|
||||
|
||||
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> 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);
|
||||
@@ -2293,6 +2301,7 @@ private:
|
||||
@@ -2293,6 +2302,7 @@ private:
|
||||
String m_overrideContentSecurityPolicy;
|
||||
|
||||
RefPtr<WebInspectorProxy> m_inspector;
|
||||
|
|
@ -9515,12 +9570,13 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c
|
|||
|
||||
#if ENABLE(FULLSCREEN_API)
|
||||
std::unique_ptr<WebFullScreenManagerProxy> m_fullScreenManager;
|
||||
@@ -2707,6 +2716,8 @@ private:
|
||||
@@ -2707,6 +2717,9 @@ private:
|
||||
bool m_isLayerTreeFrozenDueToSwipeAnimation { false };
|
||||
|
||||
String m_overriddenMediaType;
|
||||
+ Optional<WebCore::Credential> m_credentialsForAutomation;
|
||||
+ HashMap<String, HashSet<String>> m_permissionsForAutomation;
|
||||
+ Optional<bool> m_activeForAutomation;
|
||||
|
||||
#if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
|
||||
std::unique_ptr<WebDeviceOrientationUpdateProviderProxy> m_webDeviceOrientationUpdateProviderProxy;
|
||||
|
|
@ -12529,7 +12585,7 @@ index f84e5cde0baaa08dab1fc77771e4a62a08a5df78..dac73d3d9db04c5d502432cea8adfa89
|
|||
void navigateToHistory(UINT menuID);
|
||||
bool seedInitialDefaultPreferences();
|
||||
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
|
||||
+++ b/Tools/MiniBrowser/win/WinMain.cpp
|
||||
@@ -32,6 +32,9 @@
|
||||
|
|
@ -12587,7 +12643,18 @@ index b1d17e88de61a6f196830f62604e4174564506bd..0cc40ce5a5f0a3331275fcdaf7ea95cd
|
|||
|
||||
if (options.useFullDesktop)
|
||||
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
|
||||
auto factory = options.windowType == BrowserWindowType::WebKit ? WebKitBrowserWindow::create : WebKitLegacyBrowserWindow::create;
|
||||
#endif
|
||||
|
|
|
|||
96
docs/api.md
96
docs/api.md
|
|
@ -153,13 +153,12 @@ See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific
|
|||
- [browser.isConnected()](#browserisconnected)
|
||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||
- [browser.newPage([options])](#browsernewpageoptions)
|
||||
- [browser.pages()](#browserpages)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
#### event: 'disconnected'
|
||||
Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following:
|
||||
- Browser application is closed or crashed.
|
||||
- The [`browser.disconnect`](#browserdisconnect) method was called.
|
||||
- The [`browser.close`](#browserclose) method was called.
|
||||
|
||||
#### browser.close()
|
||||
- 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.
|
||||
- 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()
|
||||
- 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.
|
||||
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.
|
||||
|
||||
### class: BrowserContext
|
||||
|
||||
|
|
@ -263,6 +259,7 @@ await context.close();
|
|||
```
|
||||
|
||||
<!-- GEN:toc -->
|
||||
- [event: 'close'](#event-close)
|
||||
- [browserContext.clearCookies()](#browsercontextclearcookies)
|
||||
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
|
||||
- [browserContext.close()](#browsercontextclose)
|
||||
|
|
@ -274,6 +271,13 @@ await context.close();
|
|||
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
|
||||
<!-- 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()
|
||||
- returns: <[Promise]>
|
||||
|
||||
|
|
@ -426,7 +430,7 @@ page.removeListener('request', logRequest);
|
|||
```
|
||||
|
||||
<!-- GEN:toc -->
|
||||
- [event: 'close'](#event-close)
|
||||
- [event: 'close'](#event-close-1)
|
||||
- [event: 'console'](#event-console)
|
||||
- [event: 'dialog'](#event-dialog)
|
||||
- [event: 'domcontentloaded'](#event-domcontentloaded)
|
||||
|
|
@ -448,7 +452,7 @@ page.removeListener('request', logRequest);
|
|||
- [page.$$(selector)](#pageselector-1)
|
||||
- [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
|
||||
- [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.addScriptTag(options)](#pageaddscripttagoptions)
|
||||
- [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).
|
||||
|
||||
#### page.$wait(selector, pageFunction[, options[, ...args]])
|
||||
- `selector` <[string]> A selector to query page for
|
||||
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
|
||||
- `options` <[Object]> Optional waiting parameters
|
||||
- `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:
|
||||
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
|
||||
- `'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
|
||||
#### page.$wait(selector[, options])
|
||||
- `selector` <[string]> A selector of an element to wait for
|
||||
- `options` <[Object]>
|
||||
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
|
||||
- `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.
|
||||
- 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.
|
||||
|
||||
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
|
||||
is being called on the element periodically until either timeout expires or the function returns the truthy value.
|
||||
This method works across navigations:
|
||||
```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
|
||||
- returns: <[Accessibility]>
|
||||
|
|
@ -1664,7 +1669,7 @@ An example of getting text from an iframe element:
|
|||
- [frame.$$(selector)](#frameselector-1)
|
||||
- [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
|
||||
- [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.addStyleTag(options)](#frameaddstyletagoptions)
|
||||
- [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);
|
||||
```
|
||||
|
||||
#### frame.$wait(selector, pageFunction[, options[, ...args]])
|
||||
- `selector` <[string]> A selector to query page for
|
||||
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
|
||||
- `options` <[Object]> Optional waiting parameters
|
||||
- `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:
|
||||
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
|
||||
- `'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
|
||||
#### frame.$wait(selector[, options])
|
||||
- `selector` <[string]> A selector of an element to wait for
|
||||
- `options` <[Object]>
|
||||
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
|
||||
- `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.
|
||||
- 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.
|
||||
|
||||
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
|
||||
is being called on the element periodically until either timeout expires or the function returns the truthy value.
|
||||
This method works across navigations:
|
||||
```js
|
||||
const handle = await page.$wait(selector);
|
||||
await handle.click();
|
||||
```
|
||||
|
||||
This is a shortcut to [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options).
|
||||
|
||||
#### frame.addScriptTag(options)
|
||||
- `options` <[Object]>
|
||||
|
|
@ -3168,7 +3176,7 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
|
|||
The [WebSocket] class represents websocket connections in the page.
|
||||
|
||||
<!-- GEN:toc -->
|
||||
- [event: 'close'](#event-close-1)
|
||||
- [event: 'close'](#event-close-2)
|
||||
- [event: 'error'](#event-error)
|
||||
- [event: 'messageReceived'](#event-messagereceived)
|
||||
- [event: 'messageSent'](#event-messagesent)
|
||||
|
|
@ -3423,7 +3431,7 @@ If the function passed to the `worker.evaluateHandle` returns a [Promise], then
|
|||
### class: BrowserServer
|
||||
|
||||
<!-- GEN:toc -->
|
||||
- [event: 'close'](#event-close-2)
|
||||
- [event: 'close'](#event-close-3)
|
||||
- [browserServer.close()](#browserserverclose)
|
||||
- [browserServer.kill()](#browserserverkill)
|
||||
- [browserServer.process()](#browserserverprocess)
|
||||
|
|
@ -3470,6 +3478,7 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
|
|||
<!-- GEN:toc -->
|
||||
- [browserType.connect(options)](#browsertypeconnectoptions)
|
||||
- [browserType.devices](#browsertypedevices)
|
||||
- [browserType.downloadBrowserIfNeeded([progress])](#browsertypedownloadbrowserifneededprogress)
|
||||
- [browserType.errors](#browsertypeerrors)
|
||||
- [browserType.executablePath()](#browsertypeexecutablepath)
|
||||
- [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
|
||||
- returns: <[Object]>
|
||||
- `TimeoutError` <[function]> A class of [TimeoutError].
|
||||
|
|
@ -3652,7 +3667,6 @@ await browser.stopTracing();
|
|||
- [browser.isConnected()](#browserisconnected)
|
||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||
- [browser.newPage([options])](#browsernewpageoptions)
|
||||
- [browser.pages()](#browserpages)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
#### event: 'targetchanged'
|
||||
|
|
@ -3819,7 +3833,6 @@ Firefox browser instance does not expose Firefox-specific features.
|
|||
- [browser.isConnected()](#browserisconnected)
|
||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||
- [browser.newPage([options])](#browsernewpageoptions)
|
||||
- [browser.pages()](#browserpages)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
### class: WebKitBrowser
|
||||
|
|
@ -3835,7 +3848,6 @@ WebKit browser instance does not expose WebKit-specific features.
|
|||
- [browser.isConnected()](#browserisconnected)
|
||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||
- [browser.newPage([options])](#browsernewpageoptions)
|
||||
- [browser.pages()](#browserpages)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
### Working with selectors
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ async function downloadBrowser(browser) {
|
|||
// Do nothing if the revision is already downloaded.
|
||||
if (revisionInfo.local)
|
||||
return revisionInfo;
|
||||
await fetcher.download(revisionInfo.revision, onProgress);
|
||||
await browserType.downloadBrowserIfNeeded(onProgress);
|
||||
logPolitely(`${browser} downloaded to ${revisionInfo.folderPath}`);
|
||||
return revisionInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"main": "index.js",
|
||||
"playwright": {
|
||||
"chromium_revision": "739261",
|
||||
"chromium_revision": "740289",
|
||||
"firefox_revision": "1028",
|
||||
"webkit_revision": "1141"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,11 +21,9 @@ import { Page } from './page';
|
|||
export interface Browser extends platform.EventEmitterType {
|
||||
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
|
||||
contexts(): BrowserContext[];
|
||||
pages(): Promise<Page[]>;
|
||||
newPage(options?: BrowserContextOptions): Promise<Page>;
|
||||
isConnected(): boolean;
|
||||
close(): Promise<void>;
|
||||
_defaultContext: BrowserContext | undefined;
|
||||
}
|
||||
|
||||
export type ConnectOptions = {
|
||||
|
|
@ -33,14 +31,11 @@ export type ConnectOptions = {
|
|||
wsEndpoint: string
|
||||
};
|
||||
|
||||
export async function collectPages(browser: Browser): Promise<Page[]> {
|
||||
const result: Promise<Page[]>[] = [];
|
||||
for (const browserContext of browser.contexts())
|
||||
result.push(browserContext.pages());
|
||||
const pages: Page[] = [];
|
||||
for (const group of await Promise.all(result))
|
||||
pages.push(...group);
|
||||
return pages;
|
||||
export async function createPageInNewContext(browser: Browser, options?: BrowserContextOptions): Promise<Page> {
|
||||
const context = await browser.newContext(options);
|
||||
const page = await context.newPage();
|
||||
page._ownedContext = context;
|
||||
return page;
|
||||
}
|
||||
|
||||
export type LaunchType = 'local' | 'server' | 'persistent';
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import { Page } from './page';
|
|||
import * as network from './network';
|
||||
import * as types from './types';
|
||||
import { helper } from './helper';
|
||||
import * as platform from './platform';
|
||||
import { Events } from './events';
|
||||
|
||||
export interface BrowserContextDelegate {
|
||||
pages(): Promise<Page[]>;
|
||||
|
|
@ -47,12 +49,13 @@ export type BrowserContextOptions = {
|
|||
permissions?: { [key: string]: string[] };
|
||||
};
|
||||
|
||||
export class BrowserContext {
|
||||
export class BrowserContext extends platform.EventEmitter {
|
||||
private readonly _delegate: BrowserContextDelegate;
|
||||
readonly _options: BrowserContextOptions;
|
||||
private _closed = false;
|
||||
|
||||
constructor(delegate: BrowserContextDelegate, options: BrowserContextOptions) {
|
||||
super();
|
||||
this._delegate = delegate;
|
||||
this._options = { ...options };
|
||||
if (!this._options.viewport && this._options.viewport !== null)
|
||||
|
|
@ -79,6 +82,11 @@ export class BrowserContext {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -114,12 +122,20 @@ export class BrowserContext {
|
|||
return;
|
||||
await this._delegate.close();
|
||||
this._closed = true;
|
||||
this.emit(Events.BrowserContext.Close);
|
||||
}
|
||||
|
||||
static validateOptions(options: BrowserContextOptions) {
|
||||
if (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 {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { Page, Worker } from '../page';
|
|||
import { CRTarget } from './crTarget';
|
||||
import { Protocol } from './protocol';
|
||||
import { CRPage } from './crPage';
|
||||
import { Browser, collectPages } from '../browser';
|
||||
import { Browser, createPageInNewContext } from '../browser';
|
||||
import * as network from '../network';
|
||||
import * as types from '../types';
|
||||
import * as platform from '../platform';
|
||||
|
|
@ -56,7 +56,11 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||
this._client = connection.rootSession;
|
||||
|
||||
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.targetDestroyed', this._targetDestroyed.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());
|
||||
}
|
||||
|
||||
async pages(): Promise<Page[]> {
|
||||
return collectPages(this);
|
||||
}
|
||||
|
||||
async newPage(options?: BrowserContextOptions): Promise<Page> {
|
||||
const context = await this.newContext(options);
|
||||
return context.newPage();
|
||||
return createPageInNewContext(this, options);
|
||||
}
|
||||
|
||||
async _targetCreated(event: Protocol.Target.targetCreatedPayload) {
|
||||
|
|
|
|||
|
|
@ -484,6 +484,17 @@ export class CRPage implements PageDelegate {
|
|||
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> {
|
||||
const result = await this._client.send('DOM.getContentQuads', {
|
||||
objectId: toRemoteObject(handle).objectId
|
||||
|
|
|
|||
74
src/dom.ts
74
src/dom.ts
|
|
@ -160,58 +160,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
return this._page._delegate.getContentFrame(this);
|
||||
}
|
||||
|
||||
async scrollIntoViewIfNeeded() {
|
||||
const error = await this._evaluateInUtility(async (node: Node, pageJavascriptEnabled: boolean) => {
|
||||
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);
|
||||
async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<void> {
|
||||
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
|
||||
}
|
||||
|
||||
private async _ensurePointerActionPoint(relativePoint?: types.Point): Promise<types.Point> {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
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;
|
||||
async scrollIntoViewIfNeeded() {
|
||||
await this._scrollRectIntoViewIfNeeded();
|
||||
}
|
||||
|
||||
private async _clickablePoint(): Promise<types.Point> {
|
||||
|
|
@ -253,7 +207,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
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([
|
||||
this.boundingBox(),
|
||||
this._evaluateInUtility((node: Node) => {
|
||||
|
|
@ -273,23 +227,13 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
point.x += border.x;
|
||||
point.y += border.y;
|
||||
}
|
||||
const metrics = await this._page._delegate.layoutViewport();
|
||||
// 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 };
|
||||
return point;
|
||||
}
|
||||
|
||||
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;
|
||||
if (options && options.modifiers)
|
||||
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ export const Events = {
|
|||
Disconnected: 'disconnected'
|
||||
},
|
||||
|
||||
BrowserContext: {
|
||||
Close: 'close'
|
||||
},
|
||||
|
||||
BrowserServer: {
|
||||
Close: 'close',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Browser, collectPages } from '../browser';
|
||||
import { Browser, createPageInNewContext } from '../browser';
|
||||
import { BrowserContext, BrowserContextOptions } from '../browserContext';
|
||||
import { Events } from '../events';
|
||||
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._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 = [
|
||||
helper.addEventListener(this._connection, 'Target.targetCreated', this._onTargetCreated.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());
|
||||
}
|
||||
|
||||
async pages(): Promise<Page[]> {
|
||||
return collectPages(this);
|
||||
}
|
||||
|
||||
async newPage(options?: BrowserContextOptions): Promise<Page> {
|
||||
const context = await this.newContext(options);
|
||||
return context.newPage();
|
||||
return createPageInNewContext(this, options);
|
||||
}
|
||||
|
||||
async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> {
|
||||
|
|
|
|||
|
|
@ -392,6 +392,14 @@ export class FFPage implements PageDelegate {
|
|||
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> {
|
||||
const result = await this._session.send('Page.getContentQuads', {
|
||||
frameId: handle._context.frame._id,
|
||||
|
|
|
|||
|
|
@ -620,6 +620,10 @@ export class Frame {
|
|||
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) => {
|
||||
const context = await this._mainContext();
|
||||
const elementHandle = await context._$(selector);
|
||||
|
|
@ -938,12 +942,6 @@ export class Frame {
|
|||
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> {
|
||||
const context = await this._utilityContext();
|
||||
return context.evaluate(() => document.title);
|
||||
|
|
|
|||
12
src/page.ts
12
src/page.ts
|
|
@ -69,6 +69,7 @@ export interface PageDelegate {
|
|||
setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>;
|
||||
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
|
||||
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}>;
|
||||
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 coverage: Coverage | undefined;
|
||||
readonly _requestHandlers: { url: types.URLMatch, handler: (request: network.Request) => void }[] = [];
|
||||
_ownedContext: BrowserContext | undefined;
|
||||
|
||||
constructor(delegate: PageDelegate, browserContext: BrowserContext) {
|
||||
super();
|
||||
|
|
@ -213,6 +215,10 @@ export class Page extends platform.EventEmitter {
|
|||
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) => {
|
||||
return this.mainFrame().evaluateHandle(pageFunction, ...args as any);
|
||||
}
|
||||
|
|
@ -471,6 +477,8 @@ export class Page extends platform.EventEmitter {
|
|||
await this._delegate.closePage(runBeforeUnload);
|
||||
if (!runBeforeUnload)
|
||||
await this._closedPromise;
|
||||
if (this._ownedContext)
|
||||
await this._ownedContext.close();
|
||||
}
|
||||
|
||||
isClosed(): boolean {
|
||||
|
|
@ -525,10 +533,6 @@ export class Page extends platform.EventEmitter {
|
|||
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[] {
|
||||
return [...this._workers.values()];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { TimeoutError } from '../errors';
|
|||
import { Browser, ConnectOptions } from '../browser';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { BrowserServer } from './browserServer';
|
||||
import { OnProgressCallback } from './browserFetcher';
|
||||
|
||||
export type BrowserArgOptions = {
|
||||
headless?: boolean,
|
||||
|
|
@ -44,6 +45,7 @@ export interface BrowserType {
|
|||
launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer>;
|
||||
launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext>;
|
||||
connect(options: ConnectOptions): Promise<Browser>;
|
||||
downloadBrowserIfNeeded(progress?: OnProgressCallback): Promise<void>;
|
||||
devices: types.Devices;
|
||||
errors: { TimeoutError: typeof TimeoutError };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import * as fs from 'fs';
|
|||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import { BrowserFetcher, BrowserFetcherOptions } from '../server/browserFetcher';
|
||||
import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from '../server/browserFetcher';
|
||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||
import * as types from '../types';
|
||||
import { assert } from '../helper';
|
||||
|
|
@ -194,6 +194,15 @@ export class Chromium implements BrowserType {
|
|||
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 {
|
||||
const downloadURLs = {
|
||||
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
import { FFBrowser } from '../firefox/ffBrowser';
|
||||
import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher';
|
||||
import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from './browserFetcher';
|
||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||
import { launchProcess, waitForLine } from './processLauncher';
|
||||
import * as types from '../types';
|
||||
|
|
@ -44,6 +44,15 @@ export class Firefox implements BrowserType {
|
|||
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() {
|
||||
return 'firefox';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher';
|
||||
import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from './browserFetcher';
|
||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||
import { TimeoutError } from '../errors';
|
||||
import * as types from '../types';
|
||||
|
|
@ -52,6 +52,15 @@ export class WebKit implements BrowserType {
|
|||
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> {
|
||||
const { browserServer, transport } = await this._launchServer(options, 'local');
|
||||
const browser = await WKBrowser.connect(transport!, options && options.slowMo);
|
||||
|
|
@ -84,7 +93,6 @@ export class WebKit implements BrowserType {
|
|||
handleSIGINT = true,
|
||||
handleSIGTERM = true,
|
||||
handleSIGHUP = true,
|
||||
timeout = 30000
|
||||
} = options;
|
||||
|
||||
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);
|
||||
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? await wrapTransportWithWebSocket(transport, port || 0) : null);
|
||||
return { browserServer, transport };
|
||||
|
|
|
|||
|
|
@ -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 $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 EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Browser, collectPages } from '../browser';
|
||||
import { Browser, createPageInNewContext } from '../browser';
|
||||
import { BrowserContext, BrowserContextOptions } from '../browserContext';
|
||||
import { assert, helper, RegisteredListener } from '../helper';
|
||||
import * as network from '../network';
|
||||
|
|
@ -66,6 +66,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
|||
}
|
||||
|
||||
_onDisconnect() {
|
||||
for (const context of this.contexts())
|
||||
context._browserClosed();
|
||||
for (const pageProxy of this._pageProxies.values())
|
||||
pageProxy.dispose();
|
||||
this._pageProxies.clear();
|
||||
|
|
@ -87,13 +89,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
|||
return Array.from(this._contexts.values());
|
||||
}
|
||||
|
||||
async pages(): Promise<Page[]> {
|
||||
return collectPages(this);
|
||||
}
|
||||
|
||||
async newPage(options?: BrowserContextOptions): Promise<Page> {
|
||||
const context = await this.newContext(options);
|
||||
return context.newPage();
|
||||
return createPageInNewContext(this, options);
|
||||
}
|
||||
|
||||
async _waitForFirstPageTarget(timeout: number): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -545,6 +545,17 @@ export class WKPage implements PageDelegate {
|
|||
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> {
|
||||
const result = await this._session.send('DOM.getContentQuads', {
|
||||
objectId: toRemoteObject(handle).objectId!
|
||||
|
|
|
|||
|
|
@ -24,24 +24,26 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
|
|||
const {it, fit, xit, dit} = testRunner;
|
||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe('Browser', function() {
|
||||
describe('Browser.newPage', function() {
|
||||
it('should create new page', async function({browser}) {
|
||||
expect((await browser.pages()).length).toBe(0);
|
||||
const page1 = await browser.newPage();
|
||||
expect((await browser.pages()).length).toBe(1);
|
||||
expect(browser.contexts().length).toBe(1);
|
||||
|
||||
const page2 = await browser.newPage();
|
||||
expect((await browser.pages()).length).toBe(2);
|
||||
expect(browser.contexts().length).toBe(2);
|
||||
|
||||
await page1.context().close();
|
||||
expect((await browser.pages()).length).toBe(1);
|
||||
await page1.close();
|
||||
expect(browser.contexts().length).toBe(1);
|
||||
|
||||
await page2.context().close();
|
||||
expect((await browser.pages()).length).toBe(0);
|
||||
await page2.close();
|
||||
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()');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||
await page.goto(server.EMPTY_PAGE);
|
||||
const target = await targetPromise;
|
||||
expect(await target.page()).toBe(page);
|
||||
await page.close();
|
||||
await page.context().close();
|
||||
});
|
||||
it('should fire target events', async function({browser, newContext, server}) {
|
||||
const context = await newContext();
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
const newPage = await browser.newPage();
|
||||
let error = null;
|
||||
await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e);
|
||||
await newPage.context().close();
|
||||
await newPage.close();
|
||||
expect(error).toBeTruthy();
|
||||
await browser.stopTracing();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -286,6 +286,8 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||
expect(await frame.evaluate(() => window.result)).toBe('Clicked');
|
||||
});
|
||||
// @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}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
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(() => 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.$eval('button', button => button.style.borderWidth = '8px');
|
||||
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(() => 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.$eval('button', button => {
|
||||
const container = document.createElement('div');
|
||||
|
|
@ -347,11 +349,13 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||
container.appendChild(button);
|
||||
button.style.height = '2000px';
|
||||
button.style.width = '2000px';
|
||||
button.style.borderWidth = '8px';
|
||||
});
|
||||
await page.click('button', { relativePoint: { x: 1900, y: 1910 } });
|
||||
expect(await page.evaluate(() => window.result)).toBe('Clicked');
|
||||
expect(await page.evaluate(() => offsetX)).toBe(1900);
|
||||
expect(await page.evaluate(() => offsetY)).toBe(1910);
|
||||
// Safari reports border-relative offsetX/offsetY.
|
||||
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}) => {
|
||||
|
|
@ -370,7 +374,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||
await page.click('button');
|
||||
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(`
|
||||
<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>
|
||||
|
|
@ -379,7 +383,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||
await page.click('button');
|
||||
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 containerWidth = 500;
|
||||
const transition = 500;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,24 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
|
|||
}, element);
|
||||
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() {
|
||||
|
|
@ -201,9 +219,8 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
|
|||
it('should work for TextNodes', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
|
||||
let error = null;
|
||||
await buttonTextNode.click().catch(err => error = err);
|
||||
expect(error.message).toBe('Node is not of type HTMLElement');
|
||||
await buttonTextNode.click();
|
||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||
});
|
||||
it('should throw for detached nodes', async({page, server}) => {
|
||||
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);
|
||||
let error = null;
|
||||
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}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ module.exports.describe = function({testRunner, expect, CHROMIUM, FFOX, WEBKIT})
|
|||
const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
|
||||
const element = aHandle.asElement();
|
||||
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}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
|
|
|
|||
|
|
@ -172,6 +172,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
expect(error.message).toContain('has been closed');
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
|||
|
||||
state.tearDown = async () => {
|
||||
await Promise.all(contexts.map(c => c.close()));
|
||||
expect((await state.browser.contexts()).length).toBe(0, `"${test.fullName}" leaked a context`);
|
||||
if (rl) {
|
||||
rl.removeListener('line', onLine);
|
||||
rl.close();
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
const addElement = tag => document.body.appendChild(document.createElement(tag));
|
||||
|
||||
it('should immediately resolve promise if node exists', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const frame = page.mainFrame();
|
||||
|
|
@ -248,7 +217,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
|
|||
await frame.evaluate(addElement, 'div');
|
||||
await frame.waitForSelector('div');
|
||||
});
|
||||
|
||||
it('should work with removed MutationObserver', async({page, server}) => {
|
||||
await page.evaluate(() => delete window.MutationObserver);
|
||||
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');
|
||||
});
|
||||
|
||||
it('should resolve promise when node is added', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
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());
|
||||
expect(tagName).toBe('DIV');
|
||||
});
|
||||
|
||||
it('should work when node is added through innerHTML', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
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 watchdog;
|
||||
});
|
||||
|
||||
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
|
||||
await page.goto(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;
|
||||
expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
|
||||
});
|
||||
|
||||
it('should run in specified frame', async({page, server}) => {
|
||||
await utils.attachFrame(page, 'frame1', 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;
|
||||
expect(await eHandle.ownerFrame()).toBe(frame2);
|
||||
});
|
||||
|
||||
it('should throw when frame is detached', async({page, server}) => {
|
||||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||
const frame = page.frames()[1];
|
||||
|
|
@ -391,7 +354,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
|
|||
expect(error).toBeTruthy();
|
||||
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
|
||||
});
|
||||
|
||||
it('should respond to node attribute mutation', async({page, server}) => {
|
||||
let divFound = false;
|
||||
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());
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
|
||||
afterEach(async state => {
|
||||
await state.page.evaluate(() => teardown());
|
||||
await state.page.context().close();
|
||||
await state.page.close();
|
||||
state.page = null;
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue