forked from mirrors/gecko-dev
Differential Revision: https://phabricator.services.mozilla.com/D46518 --HG-- extra : moz-landing-system : lando
243 lines
7.4 KiB
JavaScript
243 lines
7.4 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
/* eslint-disable no-undef */
|
|
|
|
"use strict";
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
|
|
this
|
|
);
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers.js",
|
|
this
|
|
);
|
|
|
|
const EXAMPLE_URL =
|
|
"http://example.com/browser/devtools/client/webreplay/mochitest/examples/";
|
|
|
|
// Attach a debugger to a tab, returning a promise that resolves with the
|
|
// debugger's toolbox.
|
|
async function attachDebugger(tab) {
|
|
const target = await TargetFactory.forTab(tab);
|
|
const toolbox = await gDevTools.showToolbox(target, "jsdebugger");
|
|
const dbg = createDebuggerContext(toolbox);
|
|
const threadFront = dbg.toolbox.threadFront;
|
|
return { ...dbg, tab, threadFront };
|
|
}
|
|
|
|
async function attachRecordingDebugger(
|
|
url,
|
|
{ waitForRecording, disableLogging, skipInterrupt } = {}
|
|
) {
|
|
if (!disableLogging) {
|
|
await pushPref("devtools.recordreplay.logging", true);
|
|
}
|
|
|
|
const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
|
|
gBrowser.selectedTab = tab;
|
|
openTrustedLinkIn(EXAMPLE_URL + url, "current");
|
|
|
|
if (waitForRecording) {
|
|
await once(Services.ppmm, "RecordingFinished");
|
|
}
|
|
const dbg = await attachDebugger(tab);
|
|
|
|
if (!skipInterrupt) {
|
|
await interrupt(dbg);
|
|
}
|
|
|
|
return dbg;
|
|
}
|
|
|
|
async function waitForPausedNoSource(dbg) {
|
|
await waitForState(dbg, state => isPaused(dbg), "paused");
|
|
}
|
|
|
|
async function shutdownDebugger(dbg) {
|
|
await dbg.actions.removeAllBreakpoints(getContext(dbg));
|
|
await waitForRequestsToSettle(dbg);
|
|
await dbg.toolbox.destroy();
|
|
await gBrowser.removeTab(dbg.tab);
|
|
}
|
|
|
|
async function interrupt(dbg) {
|
|
await dbg.actions.breakOnNext(getThreadContext(dbg));
|
|
await waitForPausedNoSource(dbg);
|
|
}
|
|
|
|
function resumeThenPauseAtLineFunctionFactory(method) {
|
|
return async function(dbg, lineno) {
|
|
await dbg.actions[method](getThreadContext(dbg));
|
|
if (lineno !== undefined) {
|
|
await waitForPaused(dbg);
|
|
} else {
|
|
await waitForPausedNoSource(dbg);
|
|
}
|
|
const pauseLine = getVisibleSelectedFrameLine(dbg);
|
|
ok(pauseLine == lineno, `Paused at line ${pauseLine} expected ${lineno}`);
|
|
};
|
|
}
|
|
|
|
// Define various methods that resume a thread in a specific way and ensure it
|
|
// pauses at a specified line.
|
|
var rewindToLine = resumeThenPauseAtLineFunctionFactory("rewind");
|
|
var resumeToLine = resumeThenPauseAtLineFunctionFactory("resume");
|
|
var reverseStepOverToLine = resumeThenPauseAtLineFunctionFactory(
|
|
"reverseStepOver"
|
|
);
|
|
var stepOverToLine = resumeThenPauseAtLineFunctionFactory("stepOver");
|
|
var stepInToLine = resumeThenPauseAtLineFunctionFactory("stepIn");
|
|
var stepOutToLine = resumeThenPauseAtLineFunctionFactory("stepOut");
|
|
|
|
// Return a promise that resolves when a thread evaluates a string in the
|
|
// topmost frame, with the result throwing an exception.
|
|
async function checkEvaluateInTopFrameThrows(dbg, text) {
|
|
const threadFront = dbg.toolbox.target.threadFront;
|
|
const consoleFront = await dbg.toolbox.target.getFront("console");
|
|
const { frames } = await threadFront.getFrames(0, 1);
|
|
ok(frames.length == 1, "Got one frame");
|
|
const options = { thread: threadFront.actor, frameActor: frames[0].actor };
|
|
const response = await consoleFront.evaluateJS(text, options);
|
|
ok(response.exception, "Eval threw an exception");
|
|
}
|
|
|
|
// Return a pathname that can be used for a new recording file.
|
|
function newRecordingFile() {
|
|
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
|
return OS.Path.join(
|
|
OS.Constants.Path.tmpDir,
|
|
"MochitestRecording" + Math.round(Math.random() * 1000000000)
|
|
);
|
|
}
|
|
|
|
function findMessage(hud, text, selector = ".message") {
|
|
const messages = findMessages(hud, text, selector);
|
|
return messages ? messages[0] : null;
|
|
}
|
|
|
|
function findMessages(hud, text, selector = ".message") {
|
|
const messages = hud.ui.outputNode.querySelectorAll(selector);
|
|
const elements = Array.prototype.filter.call(messages, el =>
|
|
el.textContent.includes(text)
|
|
);
|
|
|
|
if (elements.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
function waitForMessage(hud, text, selector = ".message") {
|
|
return waitUntilPredicate(() => findMessage(hud, text, selector));
|
|
}
|
|
|
|
function waitForMessages(hud, text, selector = ".message") {
|
|
return waitUntilPredicate(() => findMessages(hud, text, selector));
|
|
}
|
|
|
|
async function waitForMessageCount(hud, text, length, selector = ".message") {
|
|
let messages;
|
|
await waitUntil(() => {
|
|
messages = findMessages(hud, text, selector);
|
|
return messages && messages.length == length;
|
|
});
|
|
ok(messages.length == length, "Found expected message count");
|
|
return messages;
|
|
}
|
|
|
|
async function warpToMessage(hud, dbg, text, maybeLine) {
|
|
let messages = await waitForMessages(hud, text);
|
|
ok(messages.length == 1, "Found one message");
|
|
const message = messages.pop();
|
|
|
|
const menuPopup = await openConsoleContextMenu(message);
|
|
console.log(`.>> menu`, menuPopup);
|
|
|
|
const timeWarpItem = menuPopup.querySelector("#console-menu-time-warp");
|
|
ok(timeWarpItem, "Time warp menu item is available");
|
|
|
|
timeWarpItem.click();
|
|
|
|
await hideConsoleContextMenu();
|
|
await once(Services.ppmm, "TimeWarpFinished");
|
|
await waitForPaused(dbg);
|
|
|
|
messages = findMessages(hud, "", ".paused");
|
|
ok(messages.length == 1, "Found one paused message");
|
|
|
|
if (maybeLine) {
|
|
const pauseLine = getVisibleSelectedFrameLine(dbg);
|
|
ok(pauseLine == maybeLine, `Paused at line ${maybeLine} after warp`);
|
|
}
|
|
|
|
return message;
|
|
|
|
async function openConsoleContextMenu(element) {
|
|
const onConsoleMenuOpened = hud.ui.wrapper.once("menu-open");
|
|
synthesizeContextMenuEvent(element);
|
|
await onConsoleMenuOpened;
|
|
return dbg.toolbox.topDoc.getElementById("webconsole-menu");
|
|
}
|
|
|
|
function hideConsoleContextMenu() {
|
|
const popup = dbg.toolbox.topDoc.getElementById("webconsole-menu");
|
|
if (!popup) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const onPopupHidden = once(popup, "popuphidden");
|
|
popup.hidePopup();
|
|
return onPopupHidden;
|
|
}
|
|
}
|
|
|
|
// For tests that need webconsole test features.
|
|
const BrowserTest = {
|
|
gTestPath,
|
|
ok,
|
|
is,
|
|
registerCleanupFunction,
|
|
waitForExplicitFinish,
|
|
BrowserTestUtils,
|
|
};
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/head.js",
|
|
BrowserTest
|
|
);
|
|
|
|
async function checkMessageObjectContents(msg, expected, expandList = []) {
|
|
const oi = msg.querySelector(".tree");
|
|
const node = oi.querySelector(".tree-node");
|
|
BrowserTest.expandObjectInspectorNode(node);
|
|
|
|
for (const label of expandList) {
|
|
const labelNode = await waitFor(() =>
|
|
BrowserTest.findObjectInspectorNode(oi, label)
|
|
);
|
|
BrowserTest.expandObjectInspectorNode(labelNode);
|
|
}
|
|
|
|
const properties = await waitFor(() => {
|
|
const nodes = BrowserTest.getObjectInspectorNodes(oi);
|
|
if (nodes && nodes.length > 1) {
|
|
return [...nodes].map(n => n.textContent);
|
|
}
|
|
return null;
|
|
});
|
|
|
|
expected.forEach(s => {
|
|
ok(properties.find(v => v.includes(s)), `Object contents include "${s}"`);
|
|
});
|
|
}
|
|
|
|
PromiseTestUtils.whitelistRejectionsGlobally(/NS_ERROR_NOT_INITIALIZED/);
|
|
PromiseTestUtils.whitelistRejectionsGlobally(/Error in asyncStorage/);
|
|
|
|
// When running the full test suite, long delays can occur early on in tests,
|
|
// before child processes have even been spawned. Allow a longer timeout to
|
|
// avoid failures from this.
|
|
requestLongerTimeout(4);
|