- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
Source code
Revision control
Copy as Markdown
Other Tools
/* 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 { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
BrowsingContextListener:
"chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
ContextDescriptorType:
"chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
Log: "chrome://remote/content/shared/Log.sys.mjs",
NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs",
pprint: "chrome://remote/content/shared/Format.sys.mjs",
setDevicePixelRatioForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
setLocaleOverrideForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/emulation.sys.mjs",
setNetworkConditionsForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/emulation.sys.mjs",
setScreenOrientationOverrideForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/emulation.sys.mjs",
setScreenSettingsOverrideForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/emulation.sys.mjs",
setTimezoneOverrideForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/emulation.sys.mjs",
setUserAgentOverrideForBrowsingContext:
"chrome://remote/content/webdriver-bidi/modules/root/emulation.sys.mjs",
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
UserContextManager:
"chrome://remote/content/shared/UserContextManager.sys.mjs",
WindowGlobalMessageHandler:
"chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
);
/**
* Return value for #getConfigurationTargets.
*
* @typedef {object} ConfigurationTargets
*
* @property {Set<Navigable>} navigables
* @property {Set<number>} userContexts
*/
const NULL = Symbol("NULL");
// Apply here only the configurations that will be initialized in the parent process,
// except from `viewport-override` which is handled separately.
const CONFIGURATIONS_TO_APPLY = [
"locale-override",
"network-conditions",
"screen-orientation-override",
"screen-settings-override",
"timezone-override",
"user-agent-override",
];
/**
* Internal module to set the configuration on the newly created navigables.
*/
class _ConfigurationModule extends RootBiDiModule {
#configurationMap;
#contextListener;
constructor(messageHandler) {
super(messageHandler);
Services.obs.addObserver(
this,
"tabbrowser-browser-element-will-be-inserted"
);
this.#contextListener = new lazy.BrowsingContextListener();
this.#contextListener.on("attached", this.#onContextAttached);
this.#contextListener.startListening();
// The configuration map, which maps a configuration to the settings
// that derived from session data when a browsing context is created
// to define which configurations have to be applied to this browsing context.
this.#configurationMap = {};
for (const configuration of CONFIGURATIONS_TO_APPLY) {
this.#configurationMap[configuration] = {
[lazy.ContextDescriptorType.TopBrowsingContext]: null,
[lazy.ContextDescriptorType.UserContext]: null,
};
// User agent override also supports a global setting.
if (configuration === "user-agent-override") {
this.#configurationMap["user-agent-override"][
lazy.ContextDescriptorType.All
] = null;
}
}
}
destroy() {
Services.obs.removeObserver(
this,
"tabbrowser-browser-element-will-be-inserted"
);
this.#contextListener.stopListening();
this.#contextListener.off("attached", this.#onContextAttached);
this.#contextListener.destroy();
this.#configurationMap = null;
}
observe(subject, topic) {
if (topic === "tabbrowser-browser-element-will-be-inserted") {
const userContextId = subject.getAttribute("usercontextid");
const sessionData = this.messageHandler.sessionData.getSessionData(
"_configuration",
"viewport-override",
{
type: lazy.ContextDescriptorType.UserContext,
id: userContextId === null ? 0 : parseInt(userContextId),
}
);
if (!sessionData.length) {
return;
}
const lastViewportItem = sessionData.findLast(
item => item.value.viewport !== undefined
);
if (!lastViewportItem || !lastViewportItem.value.viewport) {
return;
}
const { height, width } = lastViewportItem.value.viewport;
subject.style.setProperty("height", height + "px");
subject.style.setProperty("width", width + "px");
lazy.logger.trace(
`[${lazy.NavigableManager.getIdForBrowser(subject)}] Updated viewport to height: ${height}, width: ${width}`
);
}
}
// For some configurations a value set per a browsing context supersedes
// a value set per a user context or set globally. And a value set per
// a user context supersedes a global value.
#findCorrectConfigurationValue(configuration, type) {
const contextValue =
configuration[lazy.ContextDescriptorType.TopBrowsingContext];
const userContextValue =
configuration[lazy.ContextDescriptorType.UserContext];
const globalValue = configuration[lazy.ContextDescriptorType.All];
if (this.#isOfType(contextValue, type)) {
return contextValue;
}
if (this.#isOfType(userContextValue, type)) {
return userContextValue;
}
if (this.#isOfType(globalValue, type)) {
return globalValue;
}
return null;
}
/**
* Check if the provided value matches the provided type.
*
* @param {*} value
* The value to verify.
* @param {string} type
* The type to match.
*
* @returns {boolean}
* Returns `true` if the value type is the same as
* the provided type. `false`, otherwise.
* Also always returns `false` for `null`.
*/
#isOfType(value, type) {
return typeof value === type && value !== null;
}
#onContextAttached = async (eventName, data = {}) => {
const { browsingContext } = data;
// We have to apply configuration only to top-level browsing contexts.
if (browsingContext.parent) {
return;
}
const sessionDataItems =
this.messageHandler.sessionData.getSessionDataForContext(
"_configuration",
undefined,
browsingContext
);
const configurationMap = structuredClone(this.#configurationMap);
let devicePixelRatioOverride = null;
for (const { category, contextDescriptor, value } of sessionDataItems) {
if (
!CONFIGURATIONS_TO_APPLY.includes(category) &&
category !== "viewport-override"
) {
continue;
}
if (category === "viewport-override") {
if (value.devicePixelRatio !== undefined) {
devicePixelRatioOverride = value.devicePixelRatio;
}
} else {
configurationMap[category][contextDescriptor.type] = value;
}
}
if (devicePixelRatioOverride !== null) {
lazy.setDevicePixelRatioForBrowsingContext({
context: browsingContext,
value: devicePixelRatioOverride,
});
}
// For the following configurations on the previous step, we found session items
// that would apply a configuration for a browsing context, a user context, and in some cases globally.
// Now from these items we have to choose the one that would take precedence.
// The order is the user context item supersedes the global one, and the browsing context supersedes the user context item.
const localeOverride = this.#findCorrectConfigurationValue(
configurationMap["locale-override"],
"string"
);
if (localeOverride !== null) {
lazy.setLocaleOverrideForBrowsingContext({
context: browsingContext,
value: localeOverride,
});
}
const networkConditions = this.#findCorrectConfigurationValue(
configurationMap["network-conditions"],
"object"
);
if (networkConditions !== null) {
lazy.setNetworkConditionsForBrowsingContext({
context: browsingContext,
value: networkConditions,
});
}
const screenOrientationOverride = this.#findCorrectConfigurationValue(
configurationMap["screen-orientation-override"],
"object"
);
if (screenOrientationOverride !== null) {
lazy.setScreenOrientationOverrideForBrowsingContext({
context: browsingContext,
value: screenOrientationOverride,
});
}
const screenSettingsOverride = this.#findCorrectConfigurationValue(
configurationMap["screen-settings-override"],
"object"
);
if (screenSettingsOverride !== null) {
lazy.setScreenSettingsOverrideForBrowsingContext({
context: browsingContext,
value: screenSettingsOverride,
});
}
const timezoneOverride = this.#findCorrectConfigurationValue(
configurationMap["timezone-override"],
"string"
);
if (timezoneOverride !== null) {
lazy.setTimezoneOverrideForBrowsingContext({
context: browsingContext,
value: timezoneOverride,
});
}
const userAgentOverride = this.#findCorrectConfigurationValue(
configurationMap["user-agent-override"],
"string"
);
if (userAgentOverride !== null) {
lazy.setUserAgentOverrideForBrowsingContext({
context: browsingContext,
value: userAgentOverride,
});
}
const contextId =
lazy.NavigableManager.getIdForBrowsingContext(browsingContext);
lazy.logger.trace(
`[${contextId}] All required configurations are applied to a new browsing context`
);
};
/**
* Apply configuration command parameters for contexts, user contexts, or
* globally.
*
* @param {object} options
* @param {bool} options.async
* @param {string} options.category
* @param {Array<string>|null} options.contextIds
* @param {boolean} options.supportsGlobalConfiguration
* @param {*} options.resetValue
* @param {Array<string>|null} options.userContextIds
* @param {*} options.value
*/
async _applyConfigurationParameters(options) {
const {
async: isAsync,
category,
contextIds = NULL,
resetValue,
supportsGlobalConfiguration,
userContextIds = NULL,
value,
} = options;
const hasContextConfiguration = contextIds !== NULL;
const hasUserContextConfiguration = userContextIds !== NULL;
const { navigables, userContexts } = this.#getConfigurationTargets(
contextIds,
userContextIds,
{
hasContextConfiguration,
hasUserContextConfiguration,
supportsGlobalConfiguration,
}
);
const sessionDataItems = this.#generateSessionDataUpdate({
category,
hasContextConfiguration,
hasGlobalConfiguration: true,
navigables,
resetValue,
userContexts,
hasUserContextConfiguration,
value,
});
if (sessionDataItems.length) {
// TODO: Bug 1953079. Saving configurations in the session data works fine
// with one session, but when we start supporting multiple BiDi session,
// we will have to rethink this approach.
await this.messageHandler.updateSessionData(sessionDataItems);
}
await this.#applyConfiguration({
async: isAsync,
category,
hasContextConfiguration,
hasUserContextConfiguration,
navigables,
resetValue,
value,
});
}
async #applyConfiguration(options) {
const {
async: isAsync = false,
category,
hasContextConfiguration,
hasUserContextConfiguration,
navigables,
resetValue = "",
value,
} = options;
const commands = [];
for (const navigable of navigables) {
const configurationValue = this.#getConfigurationValue(
{
category,
context: navigable,
hasContextConfiguration,
hasUserContextConfiguration,
value,
},
resetValue
);
if (configurationValue === undefined) {
continue;
}
const commandArgs = {
context: navigable,
value: configurationValue,
};
if (isAsync) {
commands.push(
this.#applyConfigurationForCategory(category, commandArgs)
);
} else {
this.#applyConfigurationForCategory(category, commandArgs);
}
}
if (isAsync) {
await Promise.all(commands);
}
}
#applyConfigurationForCategory(category, commandArgs) {
switch (category) {
case "geolocation-override":
return this.#applyGeolocationOverride(commandArgs);
case "locale-override":
return this.#applyLocaleOverride(commandArgs);
case "network-conditions":
return lazy.setNetworkConditionsForBrowsingContext(commandArgs);
case "screen-orientation-override":
return lazy.setScreenOrientationOverrideForBrowsingContext(commandArgs);
case "screen-settings-override":
return lazy.setScreenSettingsOverrideForBrowsingContext(commandArgs);
case "timezone-override":
return this.#applyTimezoneOverride(commandArgs);
case "user-agent-override":
return lazy.setUserAgentOverrideForBrowsingContext(commandArgs);
default:
return undefined;
}
}
async #applyGeolocationOverride(options) {
const { context, value } = options;
await this.messageHandler.handleCommand({
moduleName: "emulation",
commandName: "_setGeolocationOverride",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
contextDescriptor: {
type: lazy.ContextDescriptorType.TopBrowsingContext,
id: context.browserId,
},
},
params: {
coordinates: value,
},
retryOnAbort: true,
});
}
async #applyLocaleOverride(options) {
const { context, value } = options;
lazy.setLocaleOverrideForBrowsingContext(options);
await this.messageHandler.handleCommand({
moduleName: "emulation",
commandName: "_setLocaleOverrideToSandboxes",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
contextDescriptor: {
type: lazy.ContextDescriptorType.TopBrowsingContext,
id: context.browserId,
},
},
params: {
locale: value,
},
});
}
async #applyTimezoneOverride(options) {
const { context, value } = options;
lazy.setTimezoneOverrideForBrowsingContext(options);
await this.messageHandler.handleCommand({
moduleName: "emulation",
commandName: "_setTimezoneOverrideToSandboxes",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
contextDescriptor: {
type: lazy.ContextDescriptorType.TopBrowsingContext,
id: context.browserId,
},
},
params: {
timezone: value,
},
});
}
#generateSessionDataUpdate(options) {
const {
category,
hasContextConfiguration,
hasGlobalConfiguration,
hasUserContextConfiguration,
navigables,
resetValue,
userContexts,
value,
} = options;
const sessionDataItems = [];
const onlyRemoveSessionDataItem = value === resetValue;
if (hasUserContextConfiguration) {
for (const userContext of userContexts) {
sessionDataItems.push(
...this.messageHandler.sessionData.generateSessionDataItemUpdate(
"_configuration",
category,
{
type: lazy.ContextDescriptorType.UserContext,
id: userContext,
},
onlyRemoveSessionDataItem,
value
)
);
}
} else if (hasContextConfiguration) {
for (const navigable of navigables) {
sessionDataItems.push(
...this.messageHandler.sessionData.generateSessionDataItemUpdate(
"_configuration",
category,
{
type: lazy.ContextDescriptorType.TopBrowsingContext,
id: navigable.browserId,
},
onlyRemoveSessionDataItem,
value
)
);
}
} else if (hasGlobalConfiguration) {
sessionDataItems.push(
...this.messageHandler.sessionData.generateSessionDataItemUpdate(
"_configuration",
category,
{
type: lazy.ContextDescriptorType.All,
},
onlyRemoveSessionDataItem,
value
)
);
}
return sessionDataItems;
}
/**
* Validates the provided browsing contexts or user contexts and resolves them
* to a set of navigables.
*
* @param {Array<string>|null} contextIds
* Optional list of browsing context ids.
* @param {Array<string>|null} userContextIds
* Optional list of user context ids.
* @param {object=} options
* @param {boolean} options.hasContextConfiguration
* Whether the contextIds parameter was present or omitted.
* @param {boolean} options.hasUserContextConfiguration
* Whether the userContextIds parameter was present or omitted.
* @param {boolean} options.supportsGlobalConfiguration
* Allow global configuration if no contextIds or userContextIds are provided.
*
* @returns {ConfigurationTargets}
*/
#getConfigurationTargets(contextIds, userContextIds, options = {}) {
const {
hasContextConfiguration,
hasUserContextConfiguration,
supportsGlobalConfiguration = false,
} = options;
if (hasContextConfiguration && hasUserContextConfiguration) {
throw new lazy.error.InvalidArgumentError(
`Providing both "contexts" and "userContexts" arguments is not supported`
);
}
const navigables = new Set();
const userContexts = new Set();
if (hasContextConfiguration) {
lazy.assert.isNonEmptyArray(
contextIds,
lazy.pprint`Expected "contexts" to be a non-empty array, got ${contextIds}`
);
for (const contextId of contextIds) {
lazy.assert.string(
contextId,
lazy.pprint`Expected elements of "contexts" to be a string, got ${contextId}`
);
const context = this._getNavigable(contextId);
lazy.assert.topLevel(
context,
`Browsing context with id ${contextId} is not top-level`
);
navigables.add(context);
}
} else if (hasUserContextConfiguration) {
lazy.assert.isNonEmptyArray(
userContextIds,
lazy.pprint`Expected "userContexts" to be a non-empty array, got ${userContextIds}`
);
for (const userContextId of userContextIds) {
lazy.assert.string(
userContextId,
lazy.pprint`Expected elements of "userContexts" to be a string, got ${userContextId}`
);
const internalId =
lazy.UserContextManager.getInternalIdById(userContextId);
if (internalId === null) {
throw new lazy.error.NoSuchUserContextError(
`User context with id: ${userContextId} doesn't exist`
);
}
userContexts.add(internalId);
// Prepare the list of navigables to update.
lazy.UserContextManager.getTabsForUserContext(internalId).forEach(
tab => {
const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
navigables.add(contentBrowser.browsingContext);
}
);
}
} else if (supportsGlobalConfiguration) {
lazy.TabManager.getBrowsers().forEach(browser =>
navigables.add(browser.browsingContext)
);
} else {
throw new lazy.error.InvalidArgumentError(
`At least one of "contexts" or "userContexts" arguments should be provided`
);
}
return { navigables, userContexts };
}
#getConfigurationValue(params, resetValue = "") {
const {
category,
context,
hasContextConfiguration,
hasUserContextConfiguration,
value,
} = params;
const [
configurationPerContext,
configurationPerUserContext,
configurationGlobal,
] = this.#findExistingConfigurationForContext(category, context);
if (hasContextConfiguration) {
if (value === resetValue) {
// In case of resetting a configuration for navigable,
// if there is an existing configuration for user context or global,
// we should apply it to browsing context.
return configurationPerUserContext || configurationGlobal || resetValue;
}
} else if (hasUserContextConfiguration) {
// No need to do anything if there is a configuration
// for the browsing context.
if (configurationPerContext) {
return undefined;
}
// In case of resetting a configuration for user context,
// apply a global configuration if it exists
if (value === resetValue && configurationGlobal) {
return configurationGlobal;
}
} else if (configurationPerContext || configurationPerUserContext) {
// No need to do anything if there is a configuration
// for the browsing or user context.
return undefined;
}
return value;
}
/**
* Find the existing configurations for a given category and context.
*
* @param {string} category
* The session data category.
* @param {BrowsingContext} context
* The browsing context.
*
* @returns {Array<string>}
* Return the list of existing values.
*/
#findExistingConfigurationForContext(category, context) {
let configurationGlobal,
configurationPerUserContext,
configurationPerContext;
const sessionDataItems =
this.messageHandler.sessionData.getSessionDataForContext(
"_configuration",
category,
context
);
sessionDataItems.forEach(item => {
switch (item.contextDescriptor.type) {
case lazy.ContextDescriptorType.All: {
configurationGlobal = item.value;
break;
}
case lazy.ContextDescriptorType.UserContext: {
configurationPerUserContext = item.value;
break;
}
case lazy.ContextDescriptorType.TopBrowsingContext: {
configurationPerContext = item.value;
break;
}
}
});
return [
configurationPerContext,
configurationPerUserContext,
configurationGlobal,
];
}
/**
* Internal commands
*/
_applySessionData() {}
}
export const _configuration = _ConfigurationModule;