summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/browser/head.js')
-rw-r--r--devtools/server/tests/browser/head.js337
1 files changed, 337 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js
new file mode 100644
index 0000000000..aba6d578f2
--- /dev/null
+++ b/devtools/server/tests/browser/head.js
@@ -0,0 +1,337 @@
+/* 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";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+ this
+);
+
+const {
+ DevToolsClient,
+} = require("resource://devtools/client/devtools-client.js");
+const {
+ ActorRegistry,
+} = require("resource://devtools/server/actors/utils/actor-registry.js");
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+
+const PATH = "browser/devtools/server/tests/browser/";
+const TEST_DOMAIN = "http://test1.example.org";
+const MAIN_DOMAIN = `${TEST_DOMAIN}/${PATH}`;
+const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
+const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
+
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/resources/storage/index.js,
+// devtools/client/storage/ui.js and devtools/client/storage/test/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+// does almost the same thing as addTab, but directly returns an object
+async function addTabTarget(url) {
+ info(`Adding a new tab with URL: ${url}`);
+ const tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ info(`Tab added a URL ${url} loaded`);
+ return createAndAttachTargetForTab(tab);
+}
+
+async function initAnimationsFrontForUrl(url) {
+ const { inspector, walker, target } = await initInspectorFront(url);
+ const animations = await target.getFront("animations");
+
+ return { inspector, walker, animations, target };
+}
+
+async function initLayoutFrontForUrl(url) {
+ const { inspector, walker, target } = await initInspectorFront(url);
+ const layout = await walker.getLayoutInspector();
+
+ return { inspector, walker, layout, target };
+}
+
+async function initAccessibilityFrontsForUrl(
+ url,
+ { enableByDefault = true } = {}
+) {
+ const { inspector, walker, target } = await initInspectorFront(url);
+ const parentAccessibility = await target.client.mainRoot.getFront(
+ "parentaccessibility"
+ );
+ const accessibility = await target.getFront("accessibility");
+ const a11yWalker = accessibility.accessibleWalkerFront;
+ if (enableByDefault) {
+ await parentAccessibility.enable();
+ }
+
+ return {
+ inspector,
+ walker,
+ accessibility,
+ parentAccessibility,
+ a11yWalker,
+ target,
+ };
+}
+
+function initDevToolsServer() {
+ try {
+ // Sometimes devtools server does not get destroyed correctly by previous
+ // tests.
+ DevToolsServer.destroy();
+ } catch (e) {
+ info(`DevToolsServer destroy error: ${e}\n${e.stack}`);
+ }
+ DevToolsServer.init();
+ DevToolsServer.registerAllActors();
+}
+
+async function initPerfFront() {
+ initDevToolsServer();
+ const client = new DevToolsClient(DevToolsServer.connectPipe());
+ await waitUntilClientConnected(client);
+ const front = await client.mainRoot.getFront("perf");
+ return { front, client };
+}
+
+async function initInspectorFront(url) {
+ const target = await addTabTarget(url);
+ const inspector = await target.getFront("inspector");
+ const walker = inspector.walker;
+
+ return { inspector, walker, target };
+}
+
+/**
+ * Wait until a DevToolsClient is connected.
+ * @param {DevToolsClient} client
+ * @return {Promise} Resolves when connected.
+ */
+function waitUntilClientConnected(client) {
+ return client.once("connected");
+}
+
+/**
+ * Wait for eventName on target.
+ * @param {Object} target An observable object that either supports on/off or
+ * addEventListener/removeEventListener
+ * @param {String} eventName
+ * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
+ * @return A promise that resolves when the event has been handled
+ */
+function once(target, eventName, useCapture = false) {
+ info("Waiting for event: '" + eventName + "' on " + target + ".");
+
+ return new Promise(resolve => {
+ for (const [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"],
+ ["on", "off"],
+ ]) {
+ if (add in target && remove in target) {
+ target[add](
+ eventName,
+ function onEvent(...aArgs) {
+ info("Got event: '" + eventName + "' on " + target + ".");
+ target[remove](eventName, onEvent, useCapture);
+ resolve(...aArgs);
+ },
+ useCapture
+ );
+ break;
+ }
+ }
+ });
+}
+
+/**
+ * Forces GC, CC and Shrinking GC to get rid of disconnected docshells and
+ * windows.
+ */
+function forceCollections() {
+ Cu.forceGC();
+ Cu.forceCC();
+ Cu.forceShrinkingGC();
+}
+
+registerCleanupFunction(function tearDown() {
+ Services.cookies.removeAll();
+
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function idleWait(time) {
+ return DevToolsUtils.waitForTime(time);
+}
+
+function busyWait(time) {
+ const start = Date.now();
+ let stack;
+ while (Date.now() - start < time) {
+ stack = Components.stack; // eslint-disable-line no-unused-vars
+ }
+}
+
+/**
+ * Waits until a predicate returns true.
+ *
+ * @param function predicate
+ * Invoked once in a while until it returns true.
+ * @param number interval [optional]
+ * How often the predicate is invoked, in milliseconds.
+ */
+function waitUntil(predicate, interval = 10) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve => {
+ setTimeout(function () {
+ waitUntil(predicate).then(() => resolve(true));
+ }, interval);
+ });
+}
+
+function waitForMarkerType(
+ front,
+ types,
+ predicate,
+ unpackFun = (name, data) => data.markers,
+ eventName = "timeline-data"
+) {
+ types = [].concat(types);
+ predicate =
+ predicate ||
+ function () {
+ return true;
+ };
+ let filteredMarkers = [];
+
+ return new Promise(resolve => {
+ info("Waiting for markers of type: " + types);
+
+ function handler(name, data) {
+ if (typeof name === "string" && name !== "markers") {
+ return;
+ }
+
+ const markers = unpackFun(name, data);
+ info("Got markers");
+
+ filteredMarkers = filteredMarkers.concat(
+ markers.filter(m => types.includes(m.name))
+ );
+
+ if (
+ types.every(t => filteredMarkers.some(m => m.name === t)) &&
+ predicate(filteredMarkers)
+ ) {
+ front.off(eventName, handler);
+ resolve(filteredMarkers);
+ }
+ }
+ front.on(eventName, handler);
+ });
+}
+
+function getCookieId(name, domain, path) {
+ return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
+
+/**
+ * Trigger DOM activity and wait for the corresponding accessibility event.
+ * @param {Object} emitter Devtools event emitter, usually a front.
+ * @param {Sting} name Accessibility event in question.
+ * @param {Function} handler Accessibility event handler function with checks.
+ * @param {Promise} task A promise that resolves when DOM activity is done.
+ */
+async function emitA11yEvent(emitter, name, handler, task) {
+ const promise = emitter.once(name, handler);
+ await task();
+ await promise;
+}
+
+/**
+ * Check that accessibilty front is correct and its attributes are also
+ * up-to-date.
+ * @param {Object} front Accessibility front to be tested.
+ * @param {Object} expected A map of a11y front properties to be verified.
+ * @param {Object} expectedFront Expected accessibility front.
+ */
+function checkA11yFront(front, expected, expectedFront) {
+ ok(front, "The accessibility front is created");
+
+ if (expectedFront) {
+ is(front, expectedFront, "Matching accessibility front");
+ }
+
+ // Clone the front so we could modify some values for comparison.
+ front = Object.assign(front);
+ for (const key in expected) {
+ if (key === "checks") {
+ const { CONTRAST } = front[key];
+ // Contrast values are rounded to two digits after the decimal point.
+ if (CONTRAST && CONTRAST.value) {
+ CONTRAST.value = parseFloat(CONTRAST.value.toFixed(2));
+ }
+ }
+
+ if (["actions", "states", "attributes", "checks"].includes(key)) {
+ SimpleTest.isDeeply(
+ front[key],
+ expected[key],
+ `Accessible Front has correct ${key}`
+ );
+ } else {
+ is(front[key], expected[key], `accessibility front has correct ${key}`);
+ }
+ }
+}
+
+function getA11yInitOrShutdownPromise() {
+ return new Promise(resolve => {
+ const observe = (subject, topic, data) => {
+ Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+ resolve(data);
+ };
+ Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+ });
+}
+
+/**
+ * Wait for accessibility service to shut down. We consider it shut down when
+ * an "a11y-init-or-shutdown" event is received with a value of "0".
+ */
+async function waitForA11yShutdown(parentAccessibility) {
+ await parentAccessibility.disable();
+ if (!Services.appinfo.accessibilityEnabled) {
+ return;
+ }
+
+ await getA11yInitOrShutdownPromise().then(data =>
+ data === "0" ? Promise.resolve() : Promise.reject()
+ );
+}
+
+/**
+ * Wait for accessibility service to initialize. We consider it initialized when
+ * an "a11y-init-or-shutdown" event is received with a value of "1".
+ */
+async function waitForA11yInit() {
+ if (Services.appinfo.accessibilityEnabled) {
+ return;
+ }
+
+ await getA11yInitOrShutdownPromise().then(data =>
+ data === "1" ? Promise.resolve() : Promise.reject()
+ );
+}