Files
sousa-gecko/remote/shared/BrowsingContextUtils.sys.mjs
T

322 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
});
// Schemes that WebDriver can always load without restrictions when
// system access is not enabled.
const webdriverSafeSchemes = ["blob", "file", "http", "https"];
/**
* @typedef {object} BrowsingContextDetails
*
* @property {number} browserId
* Browser id for this browsing context.
* @property {number} browsingContextId
* Internal id of the browsing context.
* @property {boolean} isTopBrowsingContext
* Flag that indicates if the browsing context is top-level.
* @property {boolean} isContent
* Flag that indicates if it is a content or a chrome browsing context.
*/
/**
* A browsing context might be replaced before reaching the parent process,
* instead we serialize enough information to retrieve the navigable in the
* parent process.
*
* If the browsing context is top level, then the browserId can be used to
* find the browser element and the new browsing context.
* Otherwise (frames) the browsing context should not be replaced and the
* browsing context id should be enough to find the browsing context.
*
* Should be used when preparing an event payload from the content to the
* parent process.
*
* @param {BrowsingContext} browsingContext
* The browsing context for which we want to get details.
*
* @returns {BrowsingContextDetails}
* Details of the browsing context.
*/
export function getBrowsingContextDetails(browsingContext) {
return {
browserId: browsingContext.browserId,
browsingContextId: browsingContext.id,
isContent: browsingContext.isContent,
isTopBrowsingContext: browsingContext.parent === null,
};
}
function isExtensionContext(browsingContext) {
let principal;
try {
if (CanonicalBrowsingContext.isInstance(browsingContext)) {
principal = browsingContext.currentWindowGlobal.documentPrincipal;
} else {
principal = browsingContext.window.document.nodePrincipal;
}
} catch (e) {
throw new Error(
`Could not retrieve principal for browsingContext (${e.message})`
);
}
// In practice, note that the principal will never be an expanded principal.
// The are only used for content scripts executed in a Sandbox, and do not
// have a browsing context on their own.
// But we still use this flag because there is no isAddonPrincipal flag.
return principal.isAddonOrExpandedAddonPrincipal;
}
export function isParentProcess(browsingContext) {
if (CanonicalBrowsingContext.isInstance(browsingContext)) {
return browsingContext.currentWindowGlobal?.osPid === -1;
}
// If `browsingContext` is not a `CanonicalBrowsingContext`, then we are
// necessarily in a content process page.
return false;
}
/**
* Check if the browsing context is running in a web content process,
* ie. a process that exclusively hosts regular web pages (http, https,
* etc.). This excludes the parent process, extension processes,
* privileged about: content processes, and file content processes.
*
* @param {BrowsingContext} browsingContext
* Browsing context to check.
*
* @returns {boolean}
* True if the browsing context is in a web content process.
*/
export function isWebContentProcess(browsingContext) {
if (CanonicalBrowsingContext.isInstance(browsingContext)) {
const remoteType = browsingContext.currentWindowGlobal?.remoteType;
return remoteType !== null && lazy.E10SUtils.isWebRemoteType(remoteType);
}
// If `browsingContext` is not a `CanonicalBrowsingContext`, then we are
// necessarily in a content process page.
return true;
}
/**
* Check if the given URL is allowed to be loaded in the specified
* browsing context when system access is not allowed.
*
* @param {nsIURI} uri
* URI to check.
* @param {BrowsingContext} browsingContext
* Target browsing context for the navigation.
*
* @returns {boolean}
* True if the URL is web safe, false otherwise.
*/
export function isWebdriverSafeNavigationURL(uri, browsingContext) {
if (
webdriverSafeSchemes.includes(uri.scheme) ||
(uri.scheme === "about" && uri.filePath === "blank")
) {
return true;
}
// For other protocols, check for the URI_INHERITS_SECURITY_CONTEXT flag.
// Protocols like "data:" and "javascript:" inherit the loading document’s
// security context and are only safe when the browsing context is in a
// web content process and the current document does not have the system
// principal.
const flags = Services.io.getDynamicProtocolFlags(uri);
if (flags & Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT) {
const principal = browsingContext.currentWindowGlobal?.documentPrincipal;
return (
isWebContentProcess(browsingContext) && !principal?.isSystemPrincipal
);
}
return false;
}
/**
* Check if the provided browsing context is currently displaying its initial
* document. For top level browsing contexts, this is usually the initial
* about:blank.
*
* @param {BrowsingContext} browsingContext
* The browsing context to check.
*
* @returns {boolean}
* True if the browsing context is on the initial document, false otherwise.
*/
export function isInitialDocument(browsingContext) {
if (!browsingContext.currentWindowGlobal) {
// Right after a browsing context has been attached it could happen that
// no window global has been set yet. Consider this as nothing has been
// loaded yet.
return true;
}
return browsingContext.currentWindowGlobal.isInitialDocument;
}
/**
* Check if the provided browsing context is currently displaying its initial
* document. For top level browsing contexts, this is usually the initial
* about:blank which will be replaced soon.
*
* @param {BrowsingContext} browsingContext
* The browsing context to check.
*
* @returns {boolean}
* True if the browsing context is on the initial document, false otherwise.
*/
export function isUncommittedInitialDocument(browsingContext) {
if (!browsingContext.currentWindowGlobal) {
// Right after a browsing context has been attached it could happen that
// no window global has been set yet. Consider this as nothing has been
// loaded yet.
return true;
}
return browsingContext.currentWindowGlobal.isUncommittedInitialDocument;
}
/**
* Check if the given browsing context is valid for the message handler
* to use.
*
* @param {BrowsingContext} browsingContext
* The browsing context to check.
* @param {object=} options
* @param {string=} options.browserId
* The id of the browser to filter the browsing contexts by (optional).
* @param {string=} options.userContext
* The id of the user context to filter the browsing contexts by (optional).
*
* @returns {boolean}
* True if the browsing context is valid, false otherwise.
*/
export function isBrowsingContextCompatible(browsingContext, options = {}) {
const { browserId, userContext } = options;
if (!BrowsingContext.isInstance(browsingContext)) {
return false;
}
// If a browserId was provided, skip browsing contexts which are not
// associated with this browserId.
if (browserId !== undefined && browsingContext.browserId !== browserId) {
return false;
}
// If a userContext was provided, skip browsing contexts which are not
// associated with this userContext.
if (
userContext !== undefined &&
browsingContext.originAttributes.userContextId !== userContext
) {
return false;
}
// If this is a CanonicalBrowsingContext but the currentWindowGlobal is not
// attached yet, skip it.
if (
CanonicalBrowsingContext.isInstance(browsingContext) &&
!browsingContext.currentWindowGlobal
) {
return false;
}
// Skip:
// - extension contexts until we support debugging webextensions, see Bug 1755014.
// - privileged contexts until we support debugging Chrome context, see Bug 1713440.
return (
!isExtensionContext(browsingContext) && !isParentProcess(browsingContext)
);
}
/**
* Wait until `currentWindowGlobal` is available on a browsing context. When a
* browsing context has just been created, the `currentWindowGlobal` might not
* be attached yet.
*
* @param {CanonicalBrowsingContext} browsingContext
* The browsing context to wait for.
*
* @returns {Promise}
* Promise which resolves when `currentWindowGlobal` is available.
*
* @throws DiscardedBrowsingContextError
* Browsing context is discarded or still no
* `currentWindowGlobal` set after 100ms.
*/
export async function waitForCurrentWindowGlobal(browsingContext) {
await lazy.PollPromise(
(resolve, reject) => {
if (browsingContext.currentWindowGlobal || browsingContext.isDiscarded) {
// If the browsing context is discarded while checking for
// the current window global, return early to avoid waiting
// unnecessarily until the timeout expires.
resolve();
} else {
reject();
}
},
{
timeout: 100,
}
);
if (!browsingContext.currentWindowGlobal) {
throw new lazy.error.DiscardedBrowsingContextError(
`BrowsingContext does no longer exist`
);
}
}
/**
* Wait until `currentWindowGlobal` is available on a top-level browsing context.
* When a browsing context has just been created, the `currentWindowGlobal` might not
* be attached yet.
*
* @param {CanonicalBrowsingContext} browsingContext
* The top-level browsing context to wait for.
*
* @returns {CanonicalBrowsingContext|null}
* Return the browsing context or `null` if the browsing context has been discarded.
*
* @throws {Error}
* If the passed browsing context is not top-level.
*/
export async function waitForTopBrowsingContextToBeReady(browsingContext) {
// We might want to do the same for iframes once
// https://github.com/w3c/webdriver-bidi/issues/832 is resolved.
if (browsingContext.parent) {
throw new Error(`The provided browsing context is not top-level`);
}
if (!browsingContext.currentWindowGlobal) {
try {
await waitForCurrentWindowGlobal(browsingContext);
} catch (e) {
// Don't throw if the browsing context was discarded or
// the window global never attached.
if (e.name === "DiscardedBrowsingContextError") {
return null;
}
throw e;
}
}
return browsingContext;
}