Bug 1697159 - [marionette] Simplify handling of application information. r=marionette-reviewers,jdescottes,jgraham

Simplify code and improve performance by adding
lazy getters for checks that will be constant
for the whole lifetime of the application process.

Differential Revision: https://phabricator.services.mozilla.com/D107645
This commit is contained in:
Henrik Skupin 2021-03-18 19:02:38 +00:00
parent b82346e44d
commit c33fc977ba
15 changed files with 189 additions and 105 deletions

View File

@ -9,12 +9,12 @@
const EXPORTED_SYMBOLS = ["action"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
AppInfo: "chrome://marionette/content/appinfo.js",
assert: "chrome://marionette/content/assert.js",
element: "chrome://marionette/content/element.js",
error: "chrome://marionette/content/error.js",
@ -1251,7 +1251,7 @@ function dispatchPointerDown(a, inputState, win) {
let mouseEvent = new action.Mouse("mousedown", a.button);
mouseEvent.update(inputState);
if (mouseEvent.ctrlKey) {
if (Services.appinfo.OS === "Darwin") {
if (AppInfo.isMac) {
mouseEvent.button = 2;
event.DoubleClickTracker.resetClick();
}
@ -1266,7 +1266,7 @@ function dispatchPointerDown(a, inputState, win) {
);
if (
event.MouseButton.isSecondary(a.button) ||
(mouseEvent.ctrlKey && Services.appinfo.OS === "Darwin")
(mouseEvent.ctrlKey && AppInfo.isMac)
) {
let contextMenuEvent = Object.assign({}, mouseEvent, {
type: "contextmenu",

View File

@ -0,0 +1,73 @@
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["AppInfo"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm",
});
const ID_FIREFOX = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
const ID_THUNDERBIRD = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
/**
* Extends Services.appinfo with further properties that are
* used across Marionette.
*
* @typedef {object} Marionette.AppInfo
* @property {Boolean} isAndroid - Whether the application runs on Android.
* @property {Boolean} isLinux - Whether the application runs on Linux.
* @property {Boolean} isMac - Whether the application runs on Mac OS.
* @property {Boolean} isWindows - Whether the application runs on Windows.
* @property {Boolean} isFirefox - Whether the application is Firefox.
* @property {Boolean} isThunderbird - Whether the application is Thunderbird.
*
* @since 88
*/
const AppInfo = new Proxy(
{},
{
get(target, prop, receiver) {
if (target.hasOwnProperty(prop)) {
return target[prop];
}
return Services.appinfo[prop];
},
}
);
// Platform support
XPCOMUtils.defineLazyGetter(AppInfo, "isAndroid", () => {
return Services.appinfo.OS === "Android";
});
XPCOMUtils.defineLazyGetter(AppInfo, "isLinux", () => {
return Services.appinfo.OS === "Linux";
});
XPCOMUtils.defineLazyGetter(AppInfo, "isMac", () => {
return Services.appinfo.OS === "Darwin";
});
XPCOMUtils.defineLazyGetter(AppInfo, "isWindows", () => {
return Services.appinfo.OS === "WINNT";
});
// Application type
XPCOMUtils.defineLazyGetter(AppInfo, "isFirefox", () => {
return Services.appinfo.ID == ID_FIREFOX;
});
XPCOMUtils.defineLazyGetter(AppInfo, "isThunderbird", () => {
return Services.appinfo.ID == ID_THUNDERBIRD;
});

View File

@ -14,18 +14,13 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
AppInfo: "chrome://marionette/content/appinfo.js",
browser: "chrome://marionette/content/browser.js",
error: "chrome://marionette/content/error.js",
evaluate: "chrome://marionette/content/evaluate.js",
pprint: "chrome://marionette/content/format.js",
});
const isFennec = () => AppConstants.platform == "android";
const isFirefox = () =>
Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
const isThunderbird = () =>
Services.appinfo.ID == "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
/**
* Shorthands for common assertions made in Marionette.
*
@ -84,39 +79,47 @@ assert.session = function(session, msg = "") {
*/
assert.firefox = function(msg = "") {
msg = msg || "Only supported in Firefox";
assert.that(isFirefox, msg, error.UnsupportedOperationError)();
assert.that(
isFirefox => isFirefox,
msg,
error.UnsupportedOperationError
)(AppInfo.isFirefox);
};
/**
* Asserts that the current browser is Firefox Desktop or Thunderbird.
* Asserts that the current application is Firefox Desktop or Thunderbird.
*
* @param {string=} msg
* Custom error message.
*
* @throws {UnsupportedOperationError}
* If current browser is not Firefox or Thunderbird.
* If current application is not running on desktop.
*/
assert.desktop = function(msg = "") {
msg = msg || "Only supported in desktop applications";
assert.that(
obj => isFirefox(obj) || isThunderbird(obj),
isDesktop => isDesktop,
msg,
error.UnsupportedOperationError
)();
)(!AppInfo.isAndroid);
};
/**
* Asserts that the current browser is Fennec, or Firefox for Android.
* Asserts that the current application runs on Android.
*
* @param {string=} msg
* Custom error message.
*
* @throws {UnsupportedOperationError}
* If current browser is not Fennec.
* If current application is not running on Android.
*/
assert.fennec = function(msg = "") {
msg = msg || "Only supported in Fennec";
assert.that(isFennec, msg, error.UnsupportedOperationError)();
assert.mobile = function(msg = "") {
msg = msg || "Only supported on Android";
assert.that(
isAndroid => isAndroid,
msg,
error.UnsupportedOperationError
)(AppInfo.isAndroid);
};
/**

View File

@ -13,8 +13,7 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
AppInfo: "chrome://marionette/content/appinfo.js",
element: "chrome://marionette/content/element.js",
error: "chrome://marionette/content/error.js",
Log: "chrome://marionette/content/log.js",
@ -24,11 +23,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
WebElementEventTarget: "chrome://marionette/content/dom.js",
});
XPCOMUtils.defineLazyGetter(
this,
"isAndroid",
() => AppConstants.platform === "android"
);
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
/** @namespace */
@ -135,7 +129,7 @@ browser.getBrowserForTab = function(tab) {
*/
browser.getTabBrowser = function(window) {
// GeckoView
if (isAndroid) {
if (AppInfo.isAndroid) {
return new MobileTabBrowser(window);
// Firefox
} else if ("gBrowser" in window) {
@ -311,8 +305,8 @@ browser.Context = class {
* A promise resolving to the newly created chrome window.
*/
async openBrowserWindow(focus = false, isPrivate = false) {
switch (this.driver.appName) {
case "firefox":
switch (AppInfo.name) {
case "Firefox":
// Open new browser window, and wait until it is fully loaded.
// Also wait for the window to be focused and activated to prevent a
// race condition when promptly focusing to the original window again.
@ -340,7 +334,7 @@ browser.Context = class {
default:
throw new error.UnsupportedOperationError(
`openWindow() not supported in ${this.driver.appName}`
`openWindow() not supported in ${AppInfo.name}`
);
}
}
@ -369,15 +363,15 @@ browser.Context = class {
let destroyed = new MessageManagerDestroyedPromise(this.messageManager);
let tabClosed;
switch (this.driver.appName) {
case "firefox":
switch (AppInfo.name) {
case "Firefox":
tabClosed = waitForEvent(this.tab, "TabClose");
this.tabBrowser.removeTab(this.tab);
break;
default:
throw new error.UnsupportedOperationError(
`closeTab() not supported in ${this.driver.appName}`
`closeTab() not supported in ${AppInfo.name}`
);
}
@ -390,8 +384,8 @@ browser.Context = class {
async openTab(focus = false) {
let tab = null;
switch (this.driver.appName) {
case "firefox":
switch (AppInfo.name) {
case "Firefox":
const opened = waitForEvent(this.window, "TabOpen");
this.window.BrowserOpenTab();
await opened;
@ -408,7 +402,7 @@ browser.Context = class {
default:
throw new error.UnsupportedOperationError(
`openTab() not supported in ${this.driver.appName}`
`openTab() not supported in ${AppInfo.name}`
);
}

View File

@ -0,0 +1,7 @@
appinfo module
==============
AppInfo
-------
.. js:autoclass:: Marionette.AppInfo
:members:

View File

@ -7,15 +7,16 @@
const EXPORTED_SYMBOLS = ["GeckoDriver"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
OS: "resource://gre/modules/osfile.jsm",
Services: "resource://gre/modules/Services.jsm",
Addon: "chrome://marionette/content/addon.js",
AppInfo: "chrome://marionette/content/appinfo.js",
assert: "chrome://marionette/content/assert.js",
atom: "chrome://marionette/content/atom.js",
browser: "chrome://marionette/content/browser.js",
@ -63,9 +64,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
const APP_ID_FIREFOX = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
const APP_ID_THUNDERBIRD = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const SUPPORTED_STRATEGIES = new Set([
@ -107,9 +105,6 @@ const globalMessageManager = Services.mm;
* The instance of Marionette server.
*/
this.GeckoDriver = function(server) {
this.appId = Services.appinfo.ID;
this.appName = Services.appinfo.name.toLowerCase();
this._server = server;
// WebDriver Session
@ -411,7 +406,7 @@ GeckoDriver.prototype.registerBrowser = function(browserElement) {
// as well as XUL frames. Ideally this should be cleaned up and we should
// keep track of browsers a different way.
if (
this.appId != APP_ID_FIREFOX ||
!AppInfo.isFirefox ||
browserElement.namespaceURI != XUL_NS ||
browserElement.nodeName != "browser" ||
browserElement.getTabBrowser()
@ -542,15 +537,12 @@ GeckoDriver.prototype.newSession = async function(cmd) {
await new Promise(resolve => {
const waitForWindow = () => {
let windowTypes;
switch (this.appId) {
case APP_ID_THUNDERBIRD:
windowTypes = ["mail:3pane"];
break;
default:
// We assume that an app either has GeckoView windows, or
// Firefox/Fennec windows, but not both.
windowTypes = ["navigator:browser", "navigator:geckoview"];
break;
if (AppInfo.isThunderbird) {
windowTypes = ["mail:3pane"];
} else {
// We assume that an app either has GeckoView windows, or
// Firefox/Fennec windows, but not both.
windowTypes = ["navigator:browser", "navigator:geckoview"];
}
let win;
@ -2411,7 +2403,7 @@ GeckoDriver.prototype.takeScreenshot = async function(cmd) {
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.getScreenOrientation = function() {
assert.fennec();
assert.mobile();
assert.open(this.getBrowsingContext({ top: true }));
const win = this.getCurrentWindow();
@ -2434,7 +2426,7 @@ GeckoDriver.prototype.getScreenOrientation = function() {
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.setScreenOrientation = function(cmd) {
assert.fennec();
assert.mobile();
assert.open(this.getBrowsingContext({ top: true }));
const ors = [

View File

@ -8,12 +8,12 @@
const EXPORTED_SYMBOLS = ["event"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
AppInfo: "chrome://marionette/content/appinfo.js",
element: "chrome://marionette/content/element.js",
});
@ -102,7 +102,7 @@ event.parseModifiers_ = function(modifiers) {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_META;
}
if (modifiers.accelKey) {
if (Services.appinfo.OS === "Darwin") {
if (AppInfo.isMac) {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_META;
} else {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_CONTROL;
@ -556,7 +556,7 @@ function emulateToActivateModifiers_(TIP, keyEvent, win) {
{ key: "Shift", attr: "shiftKey" },
{ key: "Symbol", attr: "symbolKey" },
{
key: Services.appinfo.OS === "Darwin" ? "Meta" : "Control",
key: AppInfo.isMac ? "Meta" : "Control",
attr: "accelKey",
},
],

View File

@ -13,6 +13,7 @@ marionette.jar:
content/actors/MarionetteReftestChild.jsm (actors/MarionetteReftestChild.jsm)
content/actors/MarionetteReftestParent.jsm (actors/MarionetteReftestParent.jsm)
content/addon.js (addon.js)
content/appinfo.js (appinfo.js)
content/assert.js (assert.js)
content/atom.js (atom.js)
content/browser.js (browser.js)

View File

@ -6,12 +6,13 @@
const EXPORTED_SYMBOLS = ["modal"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm",
Log: "chrome://marionette/content/log.js",
});
@ -19,9 +20,6 @@ XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
const isFirefox = () =>
Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
/** @namespace */
this.modal = {
ACTION_CLOSED: "closed",

View File

@ -16,6 +16,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
OS: "resource://gre/modules/osfile.jsm",
Preferences: "resource://gre/modules/Preferences.jsm",
AppInfo: "chrome://marionette/content/appinfo.js",
assert: "chrome://marionette/content/assert.js",
capture: "chrome://marionette/content/capture.js",
error: "chrome://marionette/content/error.js",
@ -73,8 +74,8 @@ reftest.Runner = class {
this.isPrint = null;
this.windowUtils = null;
this.lastURL = null;
this.useRemoteTabs = Services.appinfo.browserTabsRemoteAutostart;
this.useRemoteSubframes = Services.appinfo.fissionAutostart;
this.useRemoteTabs = AppInfo.browserTabsRemoteAutostart;
this.useRemoteSubframes = AppInfo.fissionAutostart;
}
/**
@ -148,7 +149,7 @@ reftest.Runner = class {
}
let reftestWin;
if (Services.appinfo.OS == "Android") {
if (AppInfo.isAndroid) {
logger.debug("Using current window");
reftestWin = this.parentWindow;
await navigate.waitForNavigationCompleted(this.driver, () => {
@ -198,7 +199,7 @@ reftest.Runner = class {
setupWindow(reftestWin, width, height) {
let browser;
if (Services.appinfo.OS === "Android") {
if (AppInfo.isAndroid) {
browser = reftestWin.document.getElementsByTagName("browser")[0];
browser.setAttribute("remote", "false");
} else {
@ -216,7 +217,7 @@ min-width: ${width}px; min-height: ${height}px;
max-width: ${width}px; max-height: ${height}px`;
browser.setAttribute("style", windowStyle);
if (Services.appinfo.OS !== "Android") {
if (!AppInfo.isAndroid) {
let doc = reftestWin.document.documentElement;
while (doc.firstChild) {
doc.firstChild.remove();
@ -612,7 +613,7 @@ max-width: ${width}px; max-height: ${height}px`;
updateBrowserRemotenessByURL(browser, url) {
// We don't use remote tabs on Android.
if (Services.appinfo.OS === "Android") {
if (AppInfo.isAndroid) {
return;
}
let oa = E10SUtils.predictOriginAttributes({ browser });

View File

@ -19,11 +19,11 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
Preferences: "resource://gre/modules/Preferences.jsm",
accessibility: "chrome://marionette/content/accessibility.js",
allowAllCerts: "chrome://marionette/content/cert.js",
AppInfo: "chrome://marionette/content/appinfo.js",
assert: "chrome://marionette/content/assert.js",
clearActionInputState:
"chrome://marionette/content/actors/MarionetteCommandsChild.jsm",
@ -34,12 +34,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
XPCOMUtils.defineLazyGetter(
this,
"isAndroid",
() => AppConstants.platform === "android"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"uuidGen",
@ -47,20 +41,6 @@ XPCOMUtils.defineLazyServiceGetter(
"nsIUUIDGenerator"
);
XPCOMUtils.defineLazyGetter(this, "appinfo", () => {
// Enable testing this module, as Services.appinfo.* is not available
// in xpcshell tests.
const appinfo = { name: "<missing>", version: "<missing>" };
try {
appinfo.name = Services.appinfo.name.toLowerCase();
} catch (e) {}
try {
appinfo.version = Services.appinfo.version;
} catch (e) {}
return appinfo;
});
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
XPCOMUtils.defineLazyGetter(this, "remoteAgent", () => {
@ -536,29 +516,29 @@ class Capabilities extends Map {
super([
// webdriver
["browserName", getWebDriverBrowserName()],
["browserVersion", appinfo.version],
["browserVersion", AppInfo.version],
["platformName", getWebDriverPlatformName()],
["platformVersion", Services.sysinfo.getProperty("version")],
["acceptInsecureCerts", false],
["pageLoadStrategy", PageLoadStrategy.Normal],
["proxy", new Proxy()],
["setWindowRect", !isAndroid],
["setWindowRect", !AppInfo.isAndroid],
["timeouts", new Timeouts()],
["strictFileInteractability", false],
["unhandledPromptBehavior", UnhandledPromptBehavior.DismissAndNotify],
// features
["rotatable", appinfo.name == "B2G"],
["rotatable", AppInfo.isAndroid],
// proprietary
["moz:accessibilityChecks", false],
["moz:buildID", Services.appinfo.appBuildID],
["moz:buildID", AppInfo.appBuildID],
["moz:debuggerAddress", remoteAgent?.debuggerAddress || null],
[
"moz:headless",
Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless,
],
["moz:processID", Services.appinfo.processID],
["moz:processID", AppInfo.processID],
["moz:profile", maybeProfile()],
[
"moz:shutdownTimeout",
@ -646,11 +626,11 @@ class Capabilities extends Map {
case "setWindowRect":
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
if (!isAndroid && !v) {
if (!AppInfo.isAndroid && !v) {
throw new error.InvalidArgumentError(
"setWindowRect cannot be disabled"
);
} else if (isAndroid && v) {
} else if (AppInfo.isAndroid && v) {
throw new error.InvalidArgumentError(
"setWindowRect is only supported on desktop"
);
@ -708,17 +688,17 @@ this.UnhandledPromptBehavior = UnhandledPromptBehavior;
function getWebDriverBrowserName() {
// Similar to chromedriver which reports "chrome" as browser name for all
// WebView apps, we will report "firefox" for all GeckoView apps.
if (isAndroid) {
if (AppInfo.isAndroid) {
return "firefox";
}
return appinfo.name;
return AppInfo.name?.toLowerCase();
}
function getWebDriverPlatformName() {
let name = Services.sysinfo.getProperty("name");
if (isAndroid) {
if (AppInfo.isAndroid) {
return "android";
}

View File

@ -0,0 +1,34 @@
/* 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/. */
"use strict";
const { AppInfo } = ChromeUtils.import(
"chrome://marionette/content/appinfo.js"
);
// Minimal xpcshell tests for AppInfo; Services.appinfo.* is not available
add_test(function test_custom_properties() {
const properties = [
// platforms
"isAndroid",
"isLinux",
"isMac",
"isWindows",
// applications
"isFirefox",
"isThunderbird",
];
for (const prop of properties) {
equal(
typeof AppInfo[prop],
"boolean",
`Custom property ${prop} has expected type`
);
}
run_next_test();
});

View File

@ -58,7 +58,7 @@ add_test(function test_session() {
add_test(function test_platforms() {
// at least one will fail
let raised;
for (let fn of [assert.firefox, assert.fennec]) {
for (let fn of [assert.desktop, assert.mobile]) {
try {
fn();
} catch (e) {

View File

@ -4,14 +4,14 @@
"use strict";
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { Preferences } = ChromeUtils.import(
"resource://gre/modules/Preferences.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppInfo } = ChromeUtils.import(
"chrome://marionette/content/appinfo.js"
);
const { error } = ChromeUtils.import("chrome://marionette/content/error.js");
const {
Capabilities,
@ -457,7 +457,7 @@ add_test(function test_Capabilities_ctor() {
equal(false, caps.get("acceptInsecureCerts"));
ok(caps.get("timeouts") instanceof Timeouts);
ok(caps.get("proxy") instanceof Proxy);
equal(caps.get("setWindowRect"), AppConstants.platform !== "android");
equal(caps.get("setWindowRect"), !AppInfo.isAndroid);
equal(caps.get("strictFileInteractability"), false);
ok(caps.has("rotatable"));
@ -554,7 +554,7 @@ add_test(function test_Capabilities_fromJSON() {
caps = fromJSON({ timeouts: timeoutsConfig });
equal(123, caps.get("timeouts").implicit);
if (AppConstants.platform !== "android") {
if (!AppInfo.isAndroid) {
caps = fromJSON({ setWindowRect: true });
equal(true, caps.get("setWindowRect"));
Assert.throws(

View File

@ -7,6 +7,7 @@ skip-if = appname == "thunderbird"
[test_action.js]
[test_actors.js]
[test_appinfo.js]
[test_assert.js]
[test_browser.js]
[test_cookie.js]