summaryrefslogtreecommitdiffstats
path: root/remote/webdriver-bidi/modules
diff options
context:
space:
mode:
Diffstat (limited to 'remote/webdriver-bidi/modules')
-rw-r--r--remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs2
-rw-r--r--remote/webdriver-bidi/modules/root/browsingContext.sys.mjs128
-rw-r--r--remote/webdriver-bidi/modules/root/network.sys.mjs457
-rw-r--r--remote/webdriver-bidi/modules/root/permissions.sys.mjs140
-rw-r--r--remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs228
-rw-r--r--remote/webdriver-bidi/modules/windowglobal/input.sys.mjs4
6 files changed, 600 insertions, 359 deletions
diff --git a/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs b/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs
index 63713f1f02..145e227fc7 100644
--- a/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs
+++ b/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs
@@ -18,6 +18,8 @@ ChromeUtils.defineESModuleGetters(modules.root, {
log: "chrome://remote/content/webdriver-bidi/modules/root/log.sys.mjs",
network:
"chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
+ permissions:
+ "chrome://remote/content/webdriver-bidi/modules/root/permissions.sys.mjs",
script: "chrome://remote/content/webdriver-bidi/modules/root/script.sys.mjs",
session:
"chrome://remote/content/webdriver-bidi/modules/root/session.sys.mjs",
diff --git a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs
index 8424bebf4a..649e801175 100644
--- a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs
+++ b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs
@@ -85,6 +85,7 @@ const CreateType = {
* @enum {LocatorType}
*/
export const LocatorType = {
+ accessibility: "accessibility",
css: "css",
innerText: "innerText",
xpath: "xpath",
@@ -545,7 +546,7 @@ class BrowsingContextModule extends Module {
// On Android there is only a single window allowed. As such fallback to
// open a new tab instead.
const type = lazy.AppInfo.isAndroid ? "tab" : typeHint;
-
+ let waitForVisibilityChangePromise;
switch (type) {
case "window": {
const newWindow = await lazy.windowManager.openBrowserWindow({
@@ -572,8 +573,6 @@ class BrowsingContextModule extends Module {
window = lazy.TabManager.getWindowForTab(referenceTab);
}
- const promises = [];
-
if (!background && !lazy.AppInfo.isAndroid) {
// When opening a new foreground tab we need to wait until the
// "document.visibilityState" of the currently selected tab in this
@@ -581,32 +580,32 @@ class BrowsingContextModule extends Module {
//
// Bug 1884142: It's not supported on Android for the TestRunner package.
const selectedTab = lazy.TabManager.getTabBrowser(window).selectedTab;
- promises.push(
- this.#waitForVisibilityChange(
- lazy.TabManager.getBrowserForTab(selectedTab).browsingContext
- )
+
+ // Create the promise immediately, but await it later in parallel with
+ // waitForInitialNavigationCompleted.
+ waitForVisibilityChangePromise = this.#waitForVisibilityChange(
+ lazy.TabManager.getBrowserForTab(selectedTab).browsingContext
);
}
- promises.unshift(
- lazy.TabManager.addTab({
- focus: !background,
- referenceTab,
- userContextId: userContext,
- })
- );
-
- const [tab] = await Promise.all(promises);
+ const tab = await lazy.TabManager.addTab({
+ focus: !background,
+ referenceTab,
+ userContextId: userContext,
+ });
browser = lazy.TabManager.getBrowserForTab(tab);
}
}
- await lazy.waitForInitialNavigationCompleted(
- browser.browsingContext.webProgress,
- {
- unloadTimeout: 5000,
- }
- );
+ await Promise.all([
+ lazy.waitForInitialNavigationCompleted(
+ browser.browsingContext.webProgress,
+ {
+ unloadTimeout: 5000,
+ }
+ ),
+ waitForVisibilityChangePromise,
+ ]);
// The tab on Android is always opened in the foreground,
// so we need to select the previous tab,
@@ -805,13 +804,33 @@ class BrowsingContextModule extends Module {
/**
* Used as an argument for browsingContext.locateNodes command, as one of the available variants
- * {CssLocator}, {InnerTextLocator} or {XPathLocator}, to represent a way of how lookup of nodes
+ * {AccessibilityLocator}, {CssLocator}, {InnerTextLocator} or {XPathLocator}, to represent a way of how lookup of nodes
* is going to be performed.
*
* @typedef Locator
*/
/**
+ * Used as a value argument for browsingContext.locateNodes command
+ * in case of a lookup by accessibility attributes.
+ *
+ * @typedef AccessibilityLocatorValue
+ *
+ * @property {string=} name
+ * @property {string=} role
+ */
+
+ /**
+ * Used as an argument for browsingContext.locateNodes command
+ * to represent a lookup by accessibility attributes.
+ *
+ * @typedef AccessibilityLocator
+ *
+ * @property {LocatorType} [type=LocatorType.accessibility]
+ * @property {AccessibilityLocatorValue} value
+ */
+
+ /**
* Used as an argument for browsingContext.locateNodes command
* to represent a lookup by css selector.
*
@@ -900,7 +919,42 @@ class BrowsingContextModule extends Module {
`Expected "locator.type" to be one of ${locatorTypes}, got ${locator.type}`
)(locator.type);
- if (![LocatorType.css, LocatorType.xpath].includes(locator.type)) {
+ if (
+ [LocatorType.css, LocatorType.innerText, LocatorType.xpath].includes(
+ locator.type
+ )
+ ) {
+ lazy.assert.string(
+ locator.value,
+ `Expected "locator.value" of "locator.type" "${locator.type}" to be a string, got ${locator.value}`
+ );
+ }
+ if (locator.type == LocatorType.accessibility) {
+ lazy.assert.object(
+ locator.value,
+ `Expected "locator.value" of "locator.type" "${locator.type}" to be an object, got ${locator.value}`
+ );
+
+ const { name = null, role = null } = locator.value;
+ if (name !== null) {
+ lazy.assert.string(
+ locator.value.name,
+ `Expected "locator.value.name" of "locator.type" "${locator.type}" to be a string, got ${name}`
+ );
+ }
+ if (role !== null) {
+ lazy.assert.string(
+ locator.value.role,
+ `Expected "locator.value.role" of "locator.type" "${locator.type}" to be a string, got ${role}`
+ );
+ }
+ }
+
+ if (
+ ![LocatorType.accessibility, LocatorType.css, LocatorType.xpath].includes(
+ locator.type
+ )
+ ) {
throw new lazy.error.UnsupportedOperationError(
`"locator.type" argument with value: ${locator.type} is not supported yet.`
);
@@ -1249,7 +1303,11 @@ class BrowsingContextModule extends Module {
* @param {object=} options
* @param {string} options.context
* Id of the browsing context.
- * @param {Viewport|null} options.viewport
+ * @param {(number|null)=} options.devicePixelRatio
+ * A value to override device pixel ratio, or `null` to reset it to
+ * the original value. Different values will not cause the rendering to change,
+ * only image srcsets and media queries will be applied as if DPR is redefined.
+ * @param {(Viewport|null)=} options.viewport
* Dimensions to set the viewport to, or `null` to reset it
* to the original dimensions.
*
@@ -1259,7 +1317,7 @@ class BrowsingContextModule extends Module {
* Raised when the command is called on Android.
*/
async setViewport(options = {}) {
- const { context: contextId, viewport } = options;
+ const { context: contextId, devicePixelRatio, viewport } = options;
if (lazy.AppInfo.isAndroid) {
// Bug 1840084: Add Android support for modifying the viewport.
@@ -1322,6 +1380,24 @@ class BrowsingContextModule extends Module {
browser.style.setProperty("width", targetWidth + "px");
}
+ if (devicePixelRatio !== undefined) {
+ if (devicePixelRatio !== null) {
+ lazy.assert.number(
+ devicePixelRatio,
+ `Expected "devicePixelRatio" to be a number or null, got ${devicePixelRatio}`
+ );
+ lazy.assert.that(
+ devicePixelRatio => devicePixelRatio > 0,
+ `Expected "devicePixelRatio" to be greater than 0, got ${devicePixelRatio}`
+ )(devicePixelRatio);
+
+ context.overrideDPPX = devicePixelRatio;
+ } else {
+ // Will reset to use the global default scaling factor.
+ context.overrideDPPX = 0;
+ }
+ }
+
if (targetHeight !== currentHeight || targetWidth !== currentWidth) {
// Wait until the viewport has been resized
await this.messageHandler.forwardCommand({
diff --git a/remote/webdriver-bidi/modules/root/network.sys.mjs b/remote/webdriver-bidi/modules/root/network.sys.mjs
index 6850e3f372..326fa87a02 100644
--- a/remote/webdriver-bidi/modules/root/network.sys.mjs
+++ b/remote/webdriver-bidi/modules/root/network.sys.mjs
@@ -12,8 +12,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
matchURLPattern:
"chrome://remote/content/shared/webdriver/URLPattern.sys.mjs",
- notifyNavigationStarted:
- "chrome://remote/content/shared/NavigationManager.sys.mjs",
NetworkListener:
"chrome://remote/content/shared/listeners/NetworkListener.sys.mjs",
parseChallengeHeader:
@@ -309,7 +307,9 @@ class NetworkModule extends Module {
// Set of event names which have active subscriptions
this.#subscribedEvents = new Set();
- this.#networkListener = new lazy.NetworkListener();
+ this.#networkListener = new lazy.NetworkListener(
+ this.messageHandler.navigationManager
+ );
this.#networkListener.on("auth-required", this.#onAuthRequired);
this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent);
this.#networkListener.on("fetch-error", this.#onFetchError);
@@ -549,8 +549,7 @@ class NetworkModule extends Module {
);
}
- const wrapper = ChannelWrapper.get(request);
- wrapper.resume();
+ request.wrappedChannel.resume();
resolveBlockedEvent();
}
@@ -684,8 +683,7 @@ class NetworkModule extends Module {
await authCallbacks.provideAuthCredentials();
}
} else {
- const wrapper = ChannelWrapper.get(request);
- wrapper.resume();
+ request.wrappedChannel.resume();
}
resolveBlockedEvent();
@@ -803,9 +801,8 @@ class NetworkModule extends Module {
);
}
- const wrapper = ChannelWrapper.get(request);
- wrapper.resume();
- wrapper.cancel(
+ request.wrappedChannel.resume();
+ request.wrappedChannel.cancel(
Cr.NS_ERROR_ABORT,
Ci.nsILoadInfo.BLOCKING_REASON_WEBDRIVER_BIDI
);
@@ -933,8 +930,7 @@ class NetworkModule extends Module {
if (phase === InterceptPhase.AuthRequired) {
await authCallbacks.provideAuthCredentials();
} else {
- const wrapper = ChannelWrapper.get(request);
- wrapper.resume();
+ request.wrappedChannel.resume();
}
resolveBlockedEvent();
@@ -987,11 +983,7 @@ class NetworkModule extends Module {
* The response channel.
*/
#addBlockedRequest(requestId, phase, options = {}) {
- const {
- authCallbacks,
- requestChannel: request,
- responseChannel: response,
- } = options;
+ const { authCallbacks, request, response } = options;
const { promise: blockedEventPromise, resolve: resolveBlockedEvent } =
Promise.withResolvers();
@@ -1117,14 +1109,14 @@ class NetworkModule extends Module {
}
}
- #extractChallenges(responseData) {
+ #extractChallenges(response) {
let headerName;
// Using case-insensitive match for header names, so we use the lowercase
// version of the "WWW-Authenticate" / "Proxy-Authenticate" strings.
- if (responseData.status === 401) {
+ if (response.status === 401) {
headerName = "www-authenticate";
- } else if (responseData.status === 407) {
+ } else if (response.status === 407) {
headerName = "proxy-authenticate";
} else {
return null;
@@ -1132,10 +1124,10 @@ class NetworkModule extends Module {
const challenges = [];
- for (const header of responseData.headers) {
- if (header.name.toLowerCase() === headerName) {
+ for (const [name, value] of response.getHeadersList()) {
+ if (name.toLowerCase() === headerName) {
// A single header can contain several challenges.
- const headerChallenges = lazy.parseChallengeHeader(header.value);
+ const headerChallenges = lazy.parseChallengeHeader(value);
for (const headerChallenge of headerChallenges) {
const realmParam = headerChallenge.params.find(
param => param.name == "realm"
@@ -1177,7 +1169,7 @@ class NetworkModule extends Module {
};
}
- #getNetworkIntercepts(event, requestData, contextId) {
+ #getNetworkIntercepts(event, request, topContextId) {
const intercepts = [];
let phase;
@@ -1197,17 +1189,11 @@ class NetworkModule extends Module {
return intercepts;
}
- // Retrieve the top browsing context id for this network event.
- const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
- const topLevelContextId = lazy.TabManager.getIdForBrowsingContext(
- browsingContext.top
- );
-
- const url = requestData.url;
+ const url = request.serializedURL;
for (const [interceptId, intercept] of this.#interceptMap) {
if (
intercept.contexts !== null &&
- !intercept.contexts.includes(topLevelContextId)
+ !intercept.contexts.includes(topContextId)
) {
// Skip this intercept if the event's context does not match the list
// of contexts for this intercept.
@@ -1228,31 +1214,96 @@ class NetworkModule extends Module {
return intercepts;
}
- #getNavigationId(eventName, isNavigationRequest, browsingContext, url) {
- if (!isNavigationRequest) {
- // Not a navigation request return null.
- return null;
+ #getRequestData(request) {
+ const requestId = request.requestId;
+
+ // "Let url be the result of running the URL serializer with request’s URL"
+ // request.serializedURL is already serialized.
+ const url = request.serializedURL;
+ const method = request.method;
+
+ const bodySize = request.postDataSize;
+ const headersSize = request.headersSize;
+ const headers = [];
+ const cookies = [];
+
+ for (const [name, value] of request.getHeadersList()) {
+ headers.push(this.#serializeHeader(name, value));
+ if (name.toLowerCase() == "cookie") {
+ // TODO: Retrieve the actual cookies from the cookie store.
+ const headerCookies = value.split(";");
+ for (const cookie of headerCookies) {
+ const equal = cookie.indexOf("=");
+ const cookieName = cookie.substr(0, equal);
+ const cookieValue = cookie.substr(equal + 1);
+ const serializedCookie = this.#serializeHeader(
+ unescape(cookieName.trim()),
+ unescape(cookieValue.trim())
+ );
+ cookies.push(serializedCookie);
+ }
+ }
}
- let navigation =
- this.messageHandler.navigationManager.getNavigationForBrowsingContext(
- browsingContext
- );
+ const timings = request.getFetchTimings();
- // `onBeforeRequestSent` might be too early for the NavigationManager.
- // If there is no ongoing navigation, create one ourselves.
- // TODO: Bug 1835704 to detect navigations earlier and avoid this.
- if (
- eventName === "network.beforeRequestSent" &&
- (!navigation || navigation.finished)
- ) {
- navigation = lazy.notifyNavigationStarted({
- contextDetails: { context: browsingContext },
- url,
- });
+ return {
+ request: requestId,
+ url,
+ method,
+ bodySize,
+ headersSize,
+ headers,
+ cookies,
+ timings,
+ };
+ }
+
+ #getResponseContentInfo(response) {
+ return {
+ size: response.decodedBodySize,
+ };
+ }
+
+ #getResponseData(response) {
+ const url = response.serializedURL;
+ const protocol = response.protocol;
+ const status = response.status;
+ const statusText = response.statusMessage;
+ // TODO: Ideally we should have a `isCacheStateLocal` getter
+ // const fromCache = response.isCacheStateLocal();
+ const fromCache = response.fromCache;
+ const mimeType = response.getComputedMimeType();
+ const headers = [];
+ for (const [name, value] of response.getHeadersList()) {
+ headers.push(this.#serializeHeader(name, value));
+ }
+
+ const bytesReceived = response.totalTransmittedSize;
+ const headersSize = response.headersTransmittedSize;
+ const bodySize = response.encodedBodySize;
+ const content = this.#getResponseContentInfo(response);
+ const authChallenges = this.#extractChallenges(response);
+
+ const params = {
+ url,
+ protocol,
+ status,
+ statusText,
+ fromCache,
+ headers,
+ mimeType,
+ bytesReceived,
+ headersSize,
+ bodySize,
+ content,
+ };
+
+ if (authChallenges !== null) {
+ params.authChallenges = authChallenges;
}
- return navigation ? navigation.navigationId : null;
+ return params;
}
#getSuspendMarkerText(requestData, phase) {
@@ -1260,21 +1311,13 @@ class NetworkModule extends Module {
}
#onAuthRequired = (name, data) => {
- const {
- authCallbacks,
- contextId,
- isNavigationRequest,
- redirectCount,
- requestChannel,
- requestData,
- responseChannel,
- responseData,
- timestamp,
- } = data;
+ const { authCallbacks, request, response } = data;
let isBlocked = false;
try {
- const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
+ const browsingContext = lazy.TabManager.getBrowsingContextById(
+ request.contextId
+ );
if (!browsingContext) {
// Do not emit events if the context id does not match any existing
// browsing context.
@@ -1283,18 +1326,9 @@ class NetworkModule extends Module {
const protocolEventName = "network.authRequired";
- // Process the navigation to create potentially missing navigation ids
- // before the early return below.
- const navigation = this.#getNavigationId(
- protocolEventName,
- isNavigationRequest,
- browsingContext,
- requestData.url
- );
-
const isListening = this.messageHandler.eventsDispatcher.hasListener(
protocolEventName,
- { contextId }
+ { contextId: request.contextId }
);
if (!isListening) {
// If there are no listeners subscribed to this event and this context,
@@ -1302,23 +1336,16 @@ class NetworkModule extends Module {
return;
}
- const baseParameters = this.#processNetworkEvent(protocolEventName, {
- contextId,
- navigation,
- redirectCount,
- requestData,
- timestamp,
- });
+ const baseParameters = this.#processNetworkEvent(
+ protocolEventName,
+ request
+ );
- const authRequiredEvent = this.#serializeNetworkEvent({
+ const responseData = this.#getResponseData(response);
+ const authRequiredEvent = {
...baseParameters,
response: responseData,
- });
-
- const authChallenges = this.#extractChallenges(responseData);
- // authChallenges should never be null for a request which triggered an
- // authRequired event.
- authRequiredEvent.response.authChallenges = authChallenges;
+ };
this.emitEvent(
protocolEventName,
@@ -1337,8 +1364,8 @@ class NetworkModule extends Module {
InterceptPhase.AuthRequired,
{
authCallbacks,
- requestChannel,
- responseChannel,
+ request,
+ response,
}
);
}
@@ -1352,16 +1379,11 @@ class NetworkModule extends Module {
};
#onBeforeRequestSent = (name, data) => {
- const {
- contextId,
- isNavigationRequest,
- redirectCount,
- requestChannel,
- requestData,
- timestamp,
- } = data;
+ const { request } = data;
- const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
+ const browsingContext = lazy.TabManager.getBrowsingContextById(
+ request.contextId
+ );
if (!browsingContext) {
// Do not emit events if the context id does not match any existing
// browsing context.
@@ -1371,15 +1393,6 @@ class NetworkModule extends Module {
const internalEventName = "network._beforeRequestSent";
const protocolEventName = "network.beforeRequestSent";
- // Process the navigation to create potentially missing navigation ids
- // before the early return below.
- const navigation = this.#getNavigationId(
- protocolEventName,
- isNavigationRequest,
- browsingContext,
- requestData.url
- );
-
// Always emit internal events, they are used to support the browsingContext
// navigate command.
// Bug 1861922: Replace internal events with a Network listener helper
@@ -1387,15 +1400,15 @@ class NetworkModule extends Module {
this.emitEvent(
internalEventName,
{
- navigation,
- url: requestData.url,
+ navigation: request.navigationId,
+ url: request.serializedURL,
},
this.#getContextInfo(browsingContext)
);
const isListening = this.messageHandler.eventsDispatcher.hasListener(
protocolEventName,
- { contextId }
+ { contextId: request.contextId }
);
if (!isListening) {
// If there are no listeners subscribed to this event and this context,
@@ -1403,23 +1416,20 @@ class NetworkModule extends Module {
return;
}
- const baseParameters = this.#processNetworkEvent(protocolEventName, {
- contextId,
- navigation,
- redirectCount,
- requestData,
- timestamp,
- });
+ const baseParameters = this.#processNetworkEvent(
+ protocolEventName,
+ request
+ );
// Bug 1805479: Handle the initiator, including stacktrace details.
const initiator = {
type: InitiatorType.Other,
};
- const beforeRequestSentEvent = this.#serializeNetworkEvent({
+ const beforeRequestSentEvent = {
...baseParameters,
initiator,
- });
+ };
this.emitEvent(
protocolEventName,
@@ -1430,32 +1440,26 @@ class NetworkModule extends Module {
if (beforeRequestSentEvent.isBlocked) {
// TODO: Requests suspended in beforeRequestSent still reach the server at
// the moment. https://bugzilla.mozilla.org/show_bug.cgi?id=1849686
- const wrapper = ChannelWrapper.get(requestChannel);
- wrapper.suspend(
- this.#getSuspendMarkerText(requestData, "beforeRequestSent")
+ request.wrappedChannel.suspend(
+ this.#getSuspendMarkerText(request, "beforeRequestSent")
);
this.#addBlockedRequest(
beforeRequestSentEvent.request.request,
InterceptPhase.BeforeRequestSent,
{
- requestChannel,
+ request,
}
);
}
};
#onFetchError = (name, data) => {
- const {
- contextId,
- errorText,
- isNavigationRequest,
- redirectCount,
- requestData,
- timestamp,
- } = data;
+ const { request } = data;
- const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
+ const browsingContext = lazy.TabManager.getBrowsingContextById(
+ request.contextId
+ );
if (!browsingContext) {
// Do not emit events if the context id does not match any existing
// browsing context.
@@ -1465,15 +1469,6 @@ class NetworkModule extends Module {
const internalEventName = "network._fetchError";
const protocolEventName = "network.fetchError";
- // Process the navigation to create potentially missing navigation ids
- // before the early return below.
- const navigation = this.#getNavigationId(
- protocolEventName,
- isNavigationRequest,
- browsingContext,
- requestData.url
- );
-
// Always emit internal events, they are used to support the browsingContext
// navigate command.
// Bug 1861922: Replace internal events with a Network listener helper
@@ -1481,15 +1476,15 @@ class NetworkModule extends Module {
this.emitEvent(
internalEventName,
{
- navigation,
- url: requestData.url,
+ navigation: request.navigationId,
+ url: request.serializedURL,
},
this.#getContextInfo(browsingContext)
);
const isListening = this.messageHandler.eventsDispatcher.hasListener(
protocolEventName,
- { contextId }
+ { contextId: request.contextId }
);
if (!isListening) {
// If there are no listeners subscribed to this event and this context,
@@ -1497,18 +1492,15 @@ class NetworkModule extends Module {
return;
}
- const baseParameters = this.#processNetworkEvent(protocolEventName, {
- contextId,
- navigation,
- redirectCount,
- requestData,
- timestamp,
- });
+ const baseParameters = this.#processNetworkEvent(
+ protocolEventName,
+ request
+ );
- const fetchErrorEvent = this.#serializeNetworkEvent({
+ const fetchErrorEvent = {
...baseParameters,
- errorText,
- });
+ errorText: request.errorText,
+ };
this.emitEvent(
protocolEventName,
@@ -1518,18 +1510,11 @@ class NetworkModule extends Module {
};
#onResponseEvent = (name, data) => {
- const {
- contextId,
- isNavigationRequest,
- redirectCount,
- requestChannel,
- requestData,
- responseChannel,
- responseData,
- timestamp,
- } = data;
+ const { request, response } = data;
- const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
+ const browsingContext = lazy.TabManager.getBrowsingContextById(
+ request.contextId
+ );
if (!browsingContext) {
// Do not emit events if the context id does not match any existing
// browsing context.
@@ -1546,15 +1531,6 @@ class NetworkModule extends Module {
? "network._responseStarted"
: "network._responseCompleted";
- // Process the navigation to create potentially missing navigation ids
- // before the early return below.
- const navigation = this.#getNavigationId(
- protocolEventName,
- isNavigationRequest,
- browsingContext,
- requestData.url
- );
-
// Always emit internal events, they are used to support the browsingContext
// navigate command.
// Bug 1861922: Replace internal events with a Network listener helper
@@ -1562,15 +1538,15 @@ class NetworkModule extends Module {
this.emitEvent(
internalEventName,
{
- navigation,
- url: requestData.url,
+ navigation: request.navigationId,
+ url: request.serializedURL,
},
this.#getContextInfo(browsingContext)
);
const isListening = this.messageHandler.eventsDispatcher.hasListener(
protocolEventName,
- { contextId }
+ { contextId: request.contextId }
);
if (!isListening) {
// If there are no listeners subscribed to this event and this context,
@@ -1578,23 +1554,17 @@ class NetworkModule extends Module {
return;
}
- const baseParameters = this.#processNetworkEvent(protocolEventName, {
- contextId,
- navigation,
- redirectCount,
- requestData,
- timestamp,
- });
+ const baseParameters = this.#processNetworkEvent(
+ protocolEventName,
+ request
+ );
- const responseEvent = this.#serializeNetworkEvent({
+ const responseData = this.#getResponseData(response);
+
+ const responseEvent = {
...baseParameters,
response: responseData,
- });
-
- const authChallenges = this.#extractChallenges(responseData);
- if (authChallenges !== null) {
- responseEvent.response.authChallenges = authChallenges;
- }
+ };
this.emitEvent(
protocolEventName,
@@ -1606,51 +1576,40 @@ class NetworkModule extends Module {
protocolEventName === "network.responseStarted" &&
responseEvent.isBlocked
) {
- const wrapper = ChannelWrapper.get(requestChannel);
- wrapper.suspend(
- this.#getSuspendMarkerText(requestData, "responseStarted")
+ request.wrappedChannel.suspend(
+ this.#getSuspendMarkerText(request, "responseStarted")
);
this.#addBlockedRequest(
responseEvent.request.request,
InterceptPhase.ResponseStarted,
{
- requestChannel,
- responseChannel,
+ request,
+ response,
}
);
}
};
- /**
- * Process the network event data for a given network event name and create
- * the corresponding base parameters.
- *
- * @param {string} eventName
- * One of the supported network event names.
- * @param {object} data
- * @param {string} data.contextId
- * The browsing context id for the network event.
- * @param {string|null} data.navigation
- * The navigation id if this is a network event for a navigation request.
- * @param {number} data.redirectCount
- * The redirect count for the network event.
- * @param {RequestData} data.requestData
- * The network.RequestData information for the network event.
- * @param {number} data.timestamp
- * The timestamp when the network event was created.
- */
- #processNetworkEvent(eventName, data) {
- const { contextId, navigation, redirectCount, requestData, timestamp } =
- data;
- const intercepts = this.#getNetworkIntercepts(
- eventName,
- requestData,
- contextId
- );
- const isBlocked = !!intercepts.length;
+ #processNetworkEvent(event, request) {
+ const requestData = this.#getRequestData(request);
+ const navigation = request.navigationId;
+ let contextId = null;
+ let topContextId = null;
+ if (request.contextId) {
+ // Retrieve the top browsing context id for this network event.
+ contextId = request.contextId;
+ const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
+ topContextId = lazy.TabManager.getIdForBrowsingContext(
+ browsingContext.top
+ );
+ }
- const baseParameters = {
+ const intercepts = this.#getNetworkIntercepts(event, request, topContextId);
+ const redirectCount = request.redirectCount;
+ const timestamp = Date.now();
+ const isBlocked = !!intercepts.length;
+ const params = {
context: contextId,
isBlocked,
navigation,
@@ -1660,51 +1619,17 @@ class NetworkModule extends Module {
};
if (isBlocked) {
- baseParameters.intercepts = intercepts;
+ params.intercepts = intercepts;
}
- return baseParameters;
- }
-
- #serializeHeadersOrCookies(headersOrCookies) {
- return headersOrCookies.map(item => ({
- name: item.name,
- value: this.#serializeStringAsBytesValue(item.value),
- }));
+ return params;
}
- /**
- * Serialize in-place all cookies and headers arrays found in a given network
- * event payload.
- *
- * @param {object} networkEvent
- * The network event parameters object to serialize.
- * @returns {object}
- * The serialized network event parameters.
- */
- #serializeNetworkEvent(networkEvent) {
- // Make a shallow copy of networkEvent before serializing the headers and
- // cookies arrays in request/response.
- const serialized = { ...networkEvent };
-
- // Make a shallow copy of the request data.
- serialized.request = { ...networkEvent.request };
- serialized.request.cookies = this.#serializeHeadersOrCookies(
- networkEvent.request.cookies
- );
- serialized.request.headers = this.#serializeHeadersOrCookies(
- networkEvent.request.headers
- );
-
- if (networkEvent.response?.headers) {
- // Make a shallow copy of the response data.
- serialized.response = { ...networkEvent.response };
- serialized.response.headers = this.#serializeHeadersOrCookies(
- networkEvent.response.headers
- );
- }
-
- return serialized;
+ #serializeHeader(name, value) {
+ return {
+ name,
+ value: this.#serializeStringAsBytesValue(value),
+ };
}
/**
diff --git a/remote/webdriver-bidi/modules/root/permissions.sys.mjs b/remote/webdriver-bidi/modules/root/permissions.sys.mjs
new file mode 100644
index 0000000000..7c66b113b0
--- /dev/null
+++ b/remote/webdriver-bidi/modules/root/permissions.sys.mjs
@@ -0,0 +1,140 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
+ error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
+ permissions: "chrome://remote/content/shared/Permissions.sys.mjs",
+});
+
+export const PermissionState = {
+ denied: "denied",
+ granted: "granted",
+ prompt: "prompt",
+};
+
+class PermissionsModule extends Module {
+ constructor(messageHandler) {
+ super(messageHandler);
+ }
+
+ destroy() {}
+
+ /**
+ * An object that holds the information about permission descriptor
+ * for Webdriver BiDi permissions.setPermission command.
+ *
+ * @typedef PermissionDescriptor
+ *
+ * @property {string} name
+ * The name of the permission.
+ */
+
+ /**
+ * Set to a given permission descriptor a given state on a provided origin.
+ *
+ * @param {object=} options
+ * @param {PermissionDescriptor} options.descriptor
+ * The descriptor of the permission which will be updated.
+ * @param {PermissionState} options.state
+ * The state which will be set to the permission.
+ * @param {string} options.origin
+ * The origin which is used as a target for permission update.
+ * @param {string=} options.userContext [unsupported]
+ * The id of the user context which should be used as a target
+ * for permission update.
+ *
+ * @throws {InvalidArgumentError}
+ * Raised if an argument is of an invalid type or value.
+ * @throws {UnsupportedOperationError}
+ * Raised when unsupported permissions are set or <var>userContext</var>
+ * argument is used.
+ */
+ async setPermission(options = {}) {
+ const {
+ descriptor,
+ state,
+ origin,
+ userContext: userContextId = null,
+ } = options;
+
+ lazy.assert.object(
+ descriptor,
+ `Expected "descriptor" to be an object, got ${descriptor}`
+ );
+ const permissionName = descriptor.name;
+ lazy.assert.string(
+ permissionName,
+ `Expected "descriptor.name" to be a string, got ${permissionName}`
+ );
+
+ lazy.permissions.validatePermission(permissionName);
+
+ // Bug 1878741: Allowing this permission causes timing related Android crash.
+ if (descriptor.name === "notifications") {
+ if (Services.prefs.getBoolPref("notification.prompt.testing", false)) {
+ // Okay, do nothing. The notifications module will work without permission.
+ return;
+ }
+ throw new lazy.error.UnsupportedOperationError(
+ `Setting "descriptor.name" "notifications" expected "notification.prompt.testing" preference to be set`
+ );
+ }
+
+ if (permissionName === "storage-access") {
+ // TODO: Bug 1895457. Add support for "storage-access" permission.
+ throw new lazy.error.UnsupportedOperationError(
+ `"descriptor.name" "${permissionName}" is currently unsupported`
+ );
+ }
+
+ const permissionStateTypes = Object.keys(PermissionState);
+ lazy.assert.that(
+ state => permissionStateTypes.includes(state),
+ `Expected "state" to be one of ${permissionStateTypes}, got ${state}`
+ )(state);
+
+ lazy.assert.string(
+ origin,
+ `Expected "origin" to be a string, got ${origin}`
+ );
+ lazy.assert.that(
+ origin => URL.canParse(origin),
+ `Expected "origin" to be a valid URL, got ${origin}`
+ )(origin);
+
+ if (userContextId !== null) {
+ lazy.assert.string(
+ userContextId,
+ `Expected "userContext" to be a string, got ${userContextId}`
+ );
+
+ // TODO: Bug 1894217. Add support for "userContext" argument.
+ throw new lazy.error.UnsupportedOperationError(
+ `"userContext" is not supported yet`
+ );
+ }
+
+ const activeWindow = Services.wm.getMostRecentBrowserWindow();
+ let typedDescriptor;
+ try {
+ typedDescriptor = activeWindow.navigator.permissions.parseSetParameters({
+ descriptor,
+ state,
+ });
+ } catch (err) {
+ throw new lazy.error.InvalidArgumentError(
+ `The conversion of "descriptor" was not successful: ${err.message}`
+ );
+ }
+
+ lazy.permissions.set(typedDescriptor, state, origin);
+ }
+}
+
+export const permissions = PermissionsModule;
diff --git a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs
index ef61954284..6dbd5440e0 100644
--- a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs
+++ b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs
@@ -7,6 +7,8 @@ import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/m
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ accessibility:
+ "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
ClipRectangleType:
@@ -48,6 +50,66 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
this.#subscribedEvents = null;
}
+ /**
+ * Collect nodes using accessibility attributes.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#collect-nodes-using-accessibility-attributes
+ */
+ async #collectNodesUsingAccessibilityAttributes(
+ contextNodes,
+ selector,
+ maxReturnedNodeCount,
+ returnedNodes
+ ) {
+ if (returnedNodes === null) {
+ returnedNodes = [];
+ }
+
+ for (const contextNode of contextNodes) {
+ let match = true;
+
+ if (contextNode.nodeType === ELEMENT_NODE) {
+ if ("role" in selector) {
+ const role = await lazy.accessibility.getComputedRole(contextNode);
+
+ if (selector.role !== role) {
+ match = false;
+ }
+ }
+
+ if ("name" in selector) {
+ const name = await lazy.accessibility.getAccessibleName(contextNode);
+ if (selector.name !== name) {
+ match = false;
+ }
+ }
+ } else {
+ match = false;
+ }
+
+ if (match) {
+ if (
+ maxReturnedNodeCount !== null &&
+ returnedNodes.length === maxReturnedNodeCount
+ ) {
+ break;
+ }
+ returnedNodes.push(contextNode);
+ }
+
+ const childNodes = [...contextNode.children];
+
+ await this.#collectNodesUsingAccessibilityAttributes(
+ childNodes,
+ selector,
+ maxReturnedNodeCount,
+ returnedNodes
+ );
+ }
+
+ return returnedNodes;
+ }
+
#getNavigationInfo(data) {
// Note: the navigation id is collected in the parent-process and will be
// added via event interception by the windowglobal-in-root module.
@@ -85,75 +147,29 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
);
}
- #startListening() {
- if (this.#subscribedEvents.size == 0) {
- this.#loadListener.startListening();
- }
- }
-
- #stopListening() {
- if (this.#subscribedEvents.size == 0) {
- this.#loadListener.stopListening();
- }
- }
-
- #subscribeEvent(event) {
- switch (event) {
- case "browsingContext._documentInteractive":
- this.#startListening();
- this.#subscribedEvents.add("browsingContext._documentInteractive");
- break;
- case "browsingContext.domContentLoaded":
- this.#startListening();
- this.#subscribedEvents.add("browsingContext.domContentLoaded");
- break;
- case "browsingContext.load":
- this.#startListening();
- this.#subscribedEvents.add("browsingContext.load");
- break;
- }
- }
-
- #unsubscribeEvent(event) {
- switch (event) {
- case "browsingContext._documentInteractive":
- this.#subscribedEvents.delete("browsingContext._documentInteractive");
- break;
- case "browsingContext.domContentLoaded":
- this.#subscribedEvents.delete("browsingContext.domContentLoaded");
- break;
- case "browsingContext.load":
- this.#subscribedEvents.delete("browsingContext.load");
- break;
- }
-
- this.#stopListening();
- }
-
- #onDOMContentLoaded = (eventName, data) => {
- if (this.#subscribedEvents.has("browsingContext._documentInteractive")) {
- this.messageHandler.emitEvent("browsingContext._documentInteractive", {
- baseURL: data.target.baseURI,
- contextId: this.messageHandler.contextId,
- documentURL: data.target.URL,
- innerWindowId: this.messageHandler.innerWindowId,
- readyState: data.target.readyState,
- });
- }
-
- if (this.#subscribedEvents.has("browsingContext.domContentLoaded")) {
- this.emitEvent(
- "browsingContext.domContentLoaded",
- this.#getNavigationInfo(data)
+ /**
+ * Locate nodes using accessibility attributes.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#locate-nodes-using-accessibility-attributes
+ */
+ async #locateNodesUsingAccessibilityAttributes(
+ contextNodes,
+ selector,
+ maxReturnedNodeCount
+ ) {
+ if (!("role" in selector) && !("name" in selector)) {
+ throw new lazy.error.InvalidSelectorError(
+ "Locating nodes by accessibility attributes requires `role` or `name` arguments"
);
}
- };
- #onLoad = (eventName, data) => {
- if (this.#subscribedEvents.has("browsingContext.load")) {
- this.emitEvent("browsingContext.load", this.#getNavigationInfo(data));
- }
- };
+ return this.#collectNodesUsingAccessibilityAttributes(
+ contextNodes,
+ selector,
+ maxReturnedNodeCount,
+ null
+ );
+ }
/**
* Locate nodes using css selector.
@@ -259,6 +275,31 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
return new DOMRect(x, y, width, height);
}
+ #onDOMContentLoaded = (eventName, data) => {
+ if (this.#subscribedEvents.has("browsingContext._documentInteractive")) {
+ this.messageHandler.emitEvent("browsingContext._documentInteractive", {
+ baseURL: data.target.baseURI,
+ contextId: this.messageHandler.contextId,
+ documentURL: data.target.URL,
+ innerWindowId: this.messageHandler.innerWindowId,
+ readyState: data.target.readyState,
+ });
+ }
+
+ if (this.#subscribedEvents.has("browsingContext.domContentLoaded")) {
+ this.emitEvent(
+ "browsingContext.domContentLoaded",
+ this.#getNavigationInfo(data)
+ );
+ }
+ };
+
+ #onLoad = (eventName, data) => {
+ if (this.#subscribedEvents.has("browsingContext.load")) {
+ this.emitEvent("browsingContext.load", this.#getNavigationInfo(data));
+ }
+ };
+
/**
* Create a new rectangle which will be an intersection of
* rectangles specified as arguments.
@@ -288,6 +329,51 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
return new DOMRect(x_min, y_min, width, height);
}
+ #startListening() {
+ if (this.#subscribedEvents.size == 0) {
+ this.#loadListener.startListening();
+ }
+ }
+
+ #stopListening() {
+ if (this.#subscribedEvents.size == 0) {
+ this.#loadListener.stopListening();
+ }
+ }
+
+ #subscribeEvent(event) {
+ switch (event) {
+ case "browsingContext._documentInteractive":
+ this.#startListening();
+ this.#subscribedEvents.add("browsingContext._documentInteractive");
+ break;
+ case "browsingContext.domContentLoaded":
+ this.#startListening();
+ this.#subscribedEvents.add("browsingContext.domContentLoaded");
+ break;
+ case "browsingContext.load":
+ this.#startListening();
+ this.#subscribedEvents.add("browsingContext.load");
+ break;
+ }
+ }
+
+ #unsubscribeEvent(event) {
+ switch (event) {
+ case "browsingContext._documentInteractive":
+ this.#subscribedEvents.delete("browsingContext._documentInteractive");
+ break;
+ case "browsingContext.domContentLoaded":
+ this.#subscribedEvents.delete("browsingContext.domContentLoaded");
+ break;
+ case "browsingContext.load":
+ this.#subscribedEvents.delete("browsingContext.load");
+ break;
+ }
+
+ this.#stopListening();
+ }
+
/**
* Internal commands
*/
@@ -425,7 +511,7 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
return this.#rectangleIntersection(originRect, clipRect);
}
- _locateNodes(params = {}) {
+ async _locateNodes(params = {}) {
const { locator, maxNodeCount, serializationOptions, startNodes } = params;
const realm = this.messageHandler.getRealm();
@@ -451,6 +537,14 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
let returnedNodes;
switch (locator.type) {
+ case lazy.LocatorType.accessibility: {
+ returnedNodes = await this.#locateNodesUsingAccessibilityAttributes(
+ contextNodes,
+ locator.value,
+ maxNodeCount
+ );
+ break;
+ }
case lazy.LocatorType.css: {
returnedNodes = this.#locateNodesUsingCss(
contextNodes,
diff --git a/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs
index b91cce2310..8db2cbb30b 100644
--- a/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs
+++ b/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs
@@ -34,6 +34,10 @@ class InputModule extends WindowGlobalBiDiModule {
const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions);
await actionChain.dispatch(this.#actionState, this.messageHandler.window);
+
+ // Terminate the current wheel transaction if there is one. Wheel
+ // transactions should not live longer than a single action chain.
+ ChromeUtils.endWheelTransaction();
}
async releaseActions() {