summaryrefslogtreecommitdiffstats
path: root/devtools/client/responsive/utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/responsive/utils
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/responsive/utils')
-rw-r--r--devtools/client/responsive/utils/e10s.js99
-rw-r--r--devtools/client/responsive/utils/key.js25
-rw-r--r--devtools/client/responsive/utils/l10n.js16
-rw-r--r--devtools/client/responsive/utils/message.js55
-rw-r--r--devtools/client/responsive/utils/moz.build16
-rw-r--r--devtools/client/responsive/utils/notification.js60
-rw-r--r--devtools/client/responsive/utils/orientation.js76
-rw-r--r--devtools/client/responsive/utils/ua.js129
-rw-r--r--devtools/client/responsive/utils/window.js43
9 files changed, 519 insertions, 0 deletions
diff --git a/devtools/client/responsive/utils/e10s.js b/devtools/client/responsive/utils/e10s.js
new file mode 100644
index 0000000000..62ec6924a6
--- /dev/null
+++ b/devtools/client/responsive/utils/e10s.js
@@ -0,0 +1,99 @@
+/* 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";
+
+// The prefix used for RDM messages in content.
+// see: devtools/client/responsive/browser/content.js
+const MESSAGE_PREFIX = "ResponsiveMode:";
+const REQUEST_DONE_SUFFIX = ":Done";
+
+/**
+ * Registers a message `listener` that is called every time messages of
+ * specified `message` is emitted on the given message manager.
+ * @param {nsIMessageListenerManager} mm
+ * The Message Manager
+ * @param {String} message
+ * The message. It will be prefixed with the constant `MESSAGE_PREFIX`
+ * @param {Function} listener
+ * The listener function that processes the message.
+ */
+function on(mm, message, listener) {
+ mm.addMessageListener(MESSAGE_PREFIX + message, listener);
+}
+exports.on = on;
+
+/**
+ * Removes a message `listener` for the specified `message` on the given
+ * message manager.
+ * @param {nsIMessageListenerManager} mm
+ * The Message Manager
+ * @param {String} message
+ * The message. It will be prefixed with the constant `MESSAGE_PREFIX`
+ * @param {Function} listener
+ * The listener function that processes the message.
+ */
+function off(mm, message, listener) {
+ mm.removeMessageListener(MESSAGE_PREFIX + message, listener);
+}
+exports.off = off;
+
+/**
+ * Resolves a promise the next time the specified `message` is sent over the
+ * given message manager.
+ * @param {nsIMessageListenerManager} mm
+ * The Message Manager
+ * @param {String} message
+ * The message. It will be prefixed with the constant `MESSAGE_PREFIX`
+ * @returns {Promise}
+ * A promise that is resolved when the given message is emitted.
+ */
+function once(mm, message) {
+ return new Promise(resolve => {
+ on(mm, message, function onMessage({ data }) {
+ off(mm, message, onMessage);
+ resolve(data);
+ });
+ });
+}
+exports.once = once;
+
+/**
+ * Asynchronously emit a `message` to the listeners of the given message
+ * manager.
+ *
+ * @param {nsIMessageListenerManager} mm
+ * The Message Manager
+ * @param {String} message
+ * The message. It will be prefixed with the constant `MESSAGE_PREFIX`.
+ * @param {Object} data
+ * A JSON object containing data to be delivered to the listeners.
+ */
+function emit(mm, message, data) {
+ mm.sendAsyncMessage(MESSAGE_PREFIX + message, data);
+}
+exports.emit = emit;
+
+/**
+ * Asynchronously send a "request" over the given message manager, and returns
+ * a promise that is resolved when the request is complete.
+ *
+ * @param {nsIMessageListenerManager} mm
+ * The Message Manager
+ * @param {String} message
+ * The message. It will be prefixed with the constant `MESSAGE_PREFIX`, and
+ * also suffixed with `REQUEST_DONE_SUFFIX` for the reply.
+ * @param {Object} data
+ * A JSON object containing data to be delivered to the listeners.
+ * @returns {Promise}
+ * A promise that is resolved when the request is done.
+ */
+function request(mm, message, data) {
+ const done = once(mm, message + REQUEST_DONE_SUFFIX);
+
+ emit(mm, message, data);
+
+ return done;
+}
+exports.request = request;
diff --git a/devtools/client/responsive/utils/key.js b/devtools/client/responsive/utils/key.js
new file mode 100644
index 0000000000..22c7278b2f
--- /dev/null
+++ b/devtools/client/responsive/utils/key.js
@@ -0,0 +1,25 @@
+/* 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 { KeyCodes } = require("resource://devtools/client/shared/keycodes.js");
+
+/**
+ * Helper to check if the provided key matches one of the expected keys.
+ * Keys will be prefixed with DOM_VK_ and should match a key in KeyCodes.
+ *
+ * @param {String} key
+ * the key to check (can be a keyCode).
+ * @param {...String} keys
+ * list of possible keys allowed.
+ * @return {Boolean} true if the key matches one of the keys.
+ */
+function isKeyIn(key, ...keys) {
+ return keys.some(expectedKey => {
+ return key === KeyCodes["DOM_VK_" + expectedKey];
+ });
+}
+
+exports.isKeyIn = isKeyIn;
diff --git a/devtools/client/responsive/utils/l10n.js b/devtools/client/responsive/utils/l10n.js
new file mode 100644
index 0000000000..bccea57040
--- /dev/null
+++ b/devtools/client/responsive/utils/l10n.js
@@ -0,0 +1,16 @@
+/* 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 { LocalizationHelper } = require("resource://devtools/shared/l10n.js");
+const STRINGS_URI = "devtools/client/locales/responsive.properties";
+const L10N = new LocalizationHelper(STRINGS_URI);
+
+module.exports = {
+ getStr: (...args) => L10N.getStr(...args),
+ getFormatStr: (...args) => L10N.getFormatStr(...args),
+ getFormatStrWithNumbers: (...args) => L10N.getFormatStrWithNumbers(...args),
+ numberWithDecimals: (...args) => L10N.numberWithDecimals(...args),
+};
diff --git a/devtools/client/responsive/utils/message.js b/devtools/client/responsive/utils/message.js
new file mode 100644
index 0000000000..d06f95ab27
--- /dev/null
+++ b/devtools/client/responsive/utils/message.js
@@ -0,0 +1,55 @@
+/* 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 REQUEST_DONE_SUFFIX = ":done";
+
+function wait(win, type) {
+ return new Promise(resolve => {
+ const onMessage = event => {
+ if (event.data.type !== type) {
+ return;
+ }
+ win.removeEventListener("message", onMessage);
+ resolve();
+ };
+ win.addEventListener("message", onMessage);
+ });
+}
+
+/**
+ * Post a message to some window.
+ *
+ * @param win
+ * The window to post to.
+ * @param typeOrMessage
+ * Either a string or and an object representing the message to send.
+ * If this is a string, it will be expanded into an object with the string as the
+ * `type` field. If this is an object, it will be sent as is.
+ */
+function post(win, typeOrMessage) {
+ // When running unit tests on XPCShell, there is no window to send messages to.
+ if (!win) {
+ return;
+ }
+
+ let message = typeOrMessage;
+ if (typeof typeOrMessage == "string") {
+ message = {
+ type: typeOrMessage,
+ };
+ }
+ win.postMessage(message, "*");
+}
+
+function request(win, type) {
+ const done = wait(win, type + REQUEST_DONE_SUFFIX);
+ post(win, type);
+ return done;
+}
+
+exports.wait = wait;
+exports.post = post;
+exports.request = request;
diff --git a/devtools/client/responsive/utils/moz.build b/devtools/client/responsive/utils/moz.build
new file mode 100644
index 0000000000..503da8932a
--- /dev/null
+++ b/devtools/client/responsive/utils/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+ "e10s.js",
+ "key.js",
+ "l10n.js",
+ "message.js",
+ "notification.js",
+ "orientation.js",
+ "ua.js",
+ "window.js",
+)
diff --git a/devtools/client/responsive/utils/notification.js b/devtools/client/responsive/utils/notification.js
new file mode 100644
index 0000000000..a261fe4252
--- /dev/null
+++ b/devtools/client/responsive/utils/notification.js
@@ -0,0 +1,60 @@
+/* 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";
+
+loader.lazyRequireGetter(
+ this,
+ "gDevTools",
+ "resource://devtools/client/framework/devtools.js",
+ true
+);
+
+/**
+ * Displays a notification either at the browser or toolbox level, depending on whether
+ * a toolbox is currently open for this tab.
+ *
+ * @param window
+ * The main browser chrome window.
+ * @param tab
+ * The browser tab.
+ * @param options
+ * Other options associated with opening. Currently includes:
+ * - `toolbox`: Whether initiated via toolbox button
+ * - `msg`: String to show in the notification
+ * - `priority`: Priority level for the notification, which affects the icon and
+ * overall appearance.
+ */
+async function showNotification(
+ window,
+ tab,
+ { toolboxButton, msg, priority } = {}
+) {
+ // Default to using the browser's per-tab notification box
+ let nbox = window.gBrowser.getNotificationBox(tab.linkedBrowser);
+
+ // If opening was initiated by a toolbox button, check for an open
+ // toolbox for the tab. If one exists, use the toolbox's notification box so that the
+ // message is placed closer to the action taken by the user.
+ if (toolboxButton) {
+ const toolbox = gDevTools.getToolboxForTab(tab);
+ if (toolbox) {
+ nbox = toolbox.notificationBox;
+ }
+ }
+
+ const value = "devtools-responsive";
+ if (nbox.getNotificationWithValue(value)) {
+ // Notification already displayed
+ return;
+ }
+
+ if (!priority) {
+ priority = nbox.PRIORITY_INFO_MEDIUM;
+ }
+
+ nbox.appendNotification(value, { label: msg, priority });
+}
+
+exports.showNotification = showNotification;
diff --git a/devtools/client/responsive/utils/orientation.js b/devtools/client/responsive/utils/orientation.js
new file mode 100644
index 0000000000..500eab8faa
--- /dev/null
+++ b/devtools/client/responsive/utils/orientation.js
@@ -0,0 +1,76 @@
+/* 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 {
+ PORTRAIT_PRIMARY,
+ LANDSCAPE_PRIMARY,
+} = require("resource://devtools/client/responsive/constants.js");
+
+/**
+ * Helper that gets the screen orientation of the device displayed in the RDM viewport.
+ * This function take in both a device and viewport object and an optional rotated angle.
+ * If a rotated angle is passed, then we calculate what the orientation type of the device
+ * would be in relation to its current orientation. Otherwise, return the current
+ * orientation and angle.
+ *
+ * @param {Object} device
+ * The device whose content is displayed in the viewport. Used to determine the
+ * primary orientation.
+ * @param {Object} viewport
+ * The viewport displaying device content. Used to determine the current
+ * orientation type of the device while in RDM.
+ * @param {Number|null} angleToRotateTo
+ * Optional. The rotated angle specifies the degree to which the device WILL be
+ * turned to. If undefined, then only return the current orientation and angle
+ * of the device.
+ * @return {Object} the orientation of the device.
+ */
+function getOrientation(device, viewport, angleToRotateTo = null) {
+ const { width: deviceWidth, height: deviceHeight } = device;
+ const { width: viewportWidth, height: viewportHeight } = viewport;
+
+ // Determine the primary orientation of the device screen.
+ const primaryOrientation =
+ deviceHeight >= deviceWidth ? PORTRAIT_PRIMARY : LANDSCAPE_PRIMARY;
+
+ // Determine the current orientation of the device screen.
+ const currentOrientation =
+ viewportHeight >= viewportWidth ? PORTRAIT_PRIMARY : LANDSCAPE_PRIMARY;
+
+ // Calculate the orientation angle of the device.
+ let angle;
+
+ if (typeof angleToRotateTo === "number") {
+ angle = angleToRotateTo;
+ } else if (currentOrientation !== primaryOrientation) {
+ angle = 90;
+ } else {
+ angle = 0;
+ }
+
+ // Calculate the orientation type of the device.
+ let orientationType = currentOrientation;
+
+ // If the viewport orientation is different from the primary orientation and the angle
+ // to rotate to is 0, then we are moving the device orientation back to its primary
+ // orientation.
+ if (currentOrientation !== primaryOrientation && angleToRotateTo === 0) {
+ orientationType = primaryOrientation;
+ } else if (angleToRotateTo === 90 || angleToRotateTo === 270) {
+ if (currentOrientation.includes("portrait")) {
+ orientationType = LANDSCAPE_PRIMARY;
+ } else if (currentOrientation.includes("landscape")) {
+ orientationType = PORTRAIT_PRIMARY;
+ }
+ }
+
+ return {
+ type: orientationType,
+ angle,
+ };
+}
+
+exports.getOrientation = getOrientation;
diff --git a/devtools/client/responsive/utils/ua.js b/devtools/client/responsive/utils/ua.js
new file mode 100644
index 0000000000..70c99c552d
--- /dev/null
+++ b/devtools/client/responsive/utils/ua.js
@@ -0,0 +1,129 @@
+/* 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 BROWSERS = [
+ {
+ name: "Firefox",
+ mustContain: new RegExp(`(?:Firefox|FxiOS)\/(${getVersionRegex(1, 1)})`),
+ },
+ {
+ name: "Opera",
+ mustContain: new RegExp(`(?:OPR|Opera)\/(${getVersionRegex(1, 1)})`),
+ },
+ {
+ name: "Safari",
+ mustContain: new RegExp(`Version\/(${getVersionRegex(1, 1)}).+Safari`),
+ mustNotContain: new RegExp("Chrome|Chromium"),
+ },
+ {
+ name: "Edge",
+ mustContain: new RegExp(`Edge\/(${getVersionRegex(0, 1)})`),
+ },
+ {
+ name: "Chrome",
+ mustContain: new RegExp(`(?:Chrome|CriOS)\/(${getVersionRegex(1, 1)})`),
+ },
+ {
+ name: "IE",
+ mustContain: new RegExp(`MSIE (${getVersionRegex(1, 1)})`),
+ },
+];
+
+const OSES = [
+ {
+ name: "iOS",
+ minMinorVersionCount: 0,
+ mustContain: new RegExp(`CPU iPhone OS (${getVersionRegex(0, 2)})`),
+ },
+ {
+ name: "iPadOS",
+ minMinorVersionCount: 0,
+ mustContain: new RegExp(`CPU OS (${getVersionRegex(0, 2)})`),
+ },
+ {
+ name: "Windows Phone",
+ minMinorVersionCount: 1,
+ mustContain: new RegExp(`Windows Phone (${getVersionRegex(1, 2)})`),
+ },
+ {
+ name: "Chrome OS",
+ minMinorVersionCount: 1,
+ mustContain: new RegExp(`CrOS .+ (${getVersionRegex(1, 2)})`),
+ },
+ {
+ name: "Android",
+ minMinorVersionCount: 0,
+ mustContain: new RegExp(`Android (${getVersionRegex(0, 2)})`),
+ },
+ {
+ name: "Windows NT",
+ minMinorVersionCount: 1,
+ mustContain: new RegExp(`Windows NT (${getVersionRegex(1, 2)})`),
+ },
+ {
+ name: "Mac OSX",
+ minMinorVersionCount: 1,
+ mustContain: new RegExp(`Intel Mac OS X (${getVersionRegex(1, 2)})`),
+ },
+ {
+ name: "Linux",
+ mustContain: new RegExp("Linux"),
+ },
+];
+
+function getVersionRegex(minMinorVersionCount, maxMinorVersionCount) {
+ return `\\d+(?:[._][0-9a-z]+){${minMinorVersionCount},${maxMinorVersionCount}}`;
+}
+
+function detect(ua, dataset) {
+ for (const {
+ name,
+ mustContain,
+ mustNotContain,
+ minMinorVersionCount,
+ } of dataset) {
+ const result = mustContain.exec(ua);
+
+ if (!result) {
+ continue;
+ }
+
+ if (mustNotContain && mustNotContain.test(ua)) {
+ continue;
+ }
+
+ let version = null;
+
+ if (result && result.length === 2) {
+ // Remove most minor version if that expresses 0.
+ let parts = result[1].match(/([0-9a-z]+)/g);
+ parts = parts.reverse();
+ const validVersionIndex = parts.findIndex(
+ part => parseInt(part, 10) !== 0
+ );
+ if (validVersionIndex !== -1) {
+ parts = parts.splice(validVersionIndex);
+ for (let i = 0; i < minMinorVersionCount + 1 - parts.length; i++) {
+ parts.unshift(0);
+ }
+ }
+ version = parts.reverse().join(".");
+ }
+
+ return { name, version };
+ }
+
+ return null;
+}
+
+function parseUserAgent(ua) {
+ return {
+ browser: detect(ua, BROWSERS),
+ os: detect(ua, OSES),
+ };
+}
+
+module.exports = { parseUserAgent };
diff --git a/devtools/client/responsive/utils/window.js b/devtools/client/responsive/utils/window.js
new file mode 100644
index 0000000000..f9bdaf0879
--- /dev/null
+++ b/devtools/client/responsive/utils/window.js
@@ -0,0 +1,43 @@
+/* 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";
+
+/**
+ * Returns the `nsIDOMWindow` toplevel window for any child/inner window
+ */
+function getTopLevelWindow(window) {
+ return window.browsingContext.topChromeWindow;
+}
+exports.getTopLevelWindow = getTopLevelWindow;
+
+function getDOMWindowUtils(window) {
+ return window.windowUtils;
+}
+exports.getDOMWindowUtils = getDOMWindowUtils;
+
+/**
+ * Check if the given browser window has finished the startup.
+ * @params {nsIDOMWindow} window
+ */
+const isStartupFinished = window => window.gBrowserInit?.delayedStartupFinished;
+
+function startup(window) {
+ return new Promise(resolve => {
+ if (isStartupFinished(window)) {
+ resolve(window);
+ return;
+ }
+ Services.obs.addObserver(function listener({ subject }) {
+ if (subject === window) {
+ Services.obs.removeObserver(
+ listener,
+ "browser-delayed-startup-finished"
+ );
+ resolve(window);
+ }
+ }, "browser-delayed-startup-finished");
+ });
+}
+exports.startup = startup;