339 lines
9.6 KiB
JavaScript
339 lines
9.6 KiB
JavaScript
/* 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 TEST_DOMAIN_HTTPS = "https://test1.example.org";
|
|
const MAIN_DOMAIN = `${TEST_DOMAIN}/${PATH}`;
|
|
const MAIN_DOMAIN_HTTPS = `${TEST_DOMAIN_HTTPS}/${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, partitionKey = "") {
|
|
return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}${SEPARATOR_GUID}${partitionKey}`;
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
);
|
|
}
|