Files
tubestation/browser/devtools/inspector/test/head.js

654 lines
21 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* vim: set ts=2 et sw=2 tw=80: */
/* 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 Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
// Services.prefs.setBoolPref("devtools.debugger.log", true);
// SimpleTest.registerCleanupFunction(() => {
// Services.prefs.clearUserPref("devtools.debugger.log");
// });
// Uncomment this pref to dump all devtools emitted events to the console.
// Services.prefs.setBoolPref("devtools.dump.emit", true);
const TEST_URL_ROOT = "http://example.com/browser/browser/devtools/inspector/test/";
const ROOT_TEST_DIR = getRootDirectory(gTestPath);
const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
// All test are asynchronous
waitForExplicitFinish();
let {TargetFactory, require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
// Import the GCLI test helper
let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
gDevTools.testing = true;
SimpleTest.registerCleanupFunction(() => {
gDevTools.testing = false;
});
SimpleTest.registerCleanupFunction(() => {
console.error("Here we are\n");
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
console.error("DebuggerServer open connections: " + Object.getOwnPropertyNames(DebuggerServer._connections).length);
Services.prefs.clearUserPref("devtools.dump.emit");
Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
});
registerCleanupFunction(function*() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
yield gDevTools.closeToolbox(target);
// Move the mouse outside inspector. If the test happened fake a mouse event
// somewhere over inspector the pointer is considered to be there when the
// next test begins. This might cause unexpected events to be emitted when
// another test moves the mouse.
EventUtils.synthesizeMouseAtPoint(1, 1, {type: "mousemove"}, window);
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});
/**
* Add a new test tab in the browser and load the given url.
* @param {String} url The url to be loaded in the new tab
* @return a promise that resolves to the tab object when the url is loaded
*/
let addTab = Task.async(function* (url) {
info("Adding a new tab with URL: '" + url + "'");
window.focus();
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
let browser = tab.linkedBrowser;
info("Loading the helper frame script " + FRAME_SCRIPT_URL);
browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
yield once(browser, "load", true);
info("URL '" + url + "' loading complete");
return tab;
});
/**
* Simple DOM node accesor function that takes either a node or a string css
* selector as argument and returns the corresponding node
* @param {String|DOMNode} nodeOrSelector
* @param {Object} options
* An object containing any of the following options:
* - document: HTMLDocument that should be queried for the selector.
* Default: content.document.
* - expectNoMatch: If true and a node matches the given selector, a
* failure is logged for an unexpected match.
* If false and nothing matches the given selector, a
* failure is logged for a missing match.
* Default: false.
* @return {DOMNode}
*/
function getNode(nodeOrSelector, options = {}) {
let document = options.document || content.document;
let noMatches = !!options.expectNoMatch;
if (typeof nodeOrSelector === "string") {
info("Looking for a node that matches selector " + nodeOrSelector);
let node = document.querySelector(nodeOrSelector);
if (noMatches) {
ok(!node, "Selector " + nodeOrSelector + " didn't match any nodes.");
}
else {
ok(node, "Selector " + nodeOrSelector + " matched a node.");
}
return node;
}
info("Looking for a node but selector was not a string.");
return nodeOrSelector;
}
/**
* Highlight a node and set the inspector's current selection to the node or
* the first match of the given css selector.
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector
* The instance of InspectorPanel currently loaded in the toolbox
* @return a promise that resolves when the inspector is updated with the new
* node
*/
function selectAndHighlightNode(selector, inspector) {
info("Highlighting and selecting the node " + selector);
return selectNode(selector, inspector, "test-highlight");
}
/**
* Set the inspector's current selection to the first match of the given css
* selector
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @param {String} reason Defaults to "test" which instructs the inspector not
* to highlight the node upon selection
* @return {Promise} Resolves when the inspector is updated with the new node
*/
let selectNode = Task.async(function*(selector, inspector, reason="test") {
info("Selecting the node for '" + selector + "'");
let nodeFront = yield getNodeFront(selector, inspector);
let updated = inspector.once("inspector-updated");
inspector.selection.setNodeFront(nodeFront, reason);
yield updated;
});
/**
* Open the inspector in a tab with given URL.
* @param {string} url The URL to open.
* @return A promise that is resolved once the tab and inspector have loaded
* with an object: { tab, toolbox, inspector }.
*/
let openInspectorForURL = Task.async(function* (url) {
let tab = yield addTab(url);
let { inspector, toolbox } = yield openInspector();
return { tab, inspector, toolbox };
});
/**
* Open the toolbox, with the inspector tool visible.
* @param {Function} cb Optional callback, if you don't want to use the returned
* promise
* @return a promise that resolves when the inspector is ready
*/
let openInspector = Task.async(function*(cb) {
info("Opening the inspector");
let target = TargetFactory.forTab(gBrowser.selectedTab);
let inspector, toolbox;
// Checking if the toolbox and the inspector are already loaded
// The inspector-updated event should only be waited for if the inspector
// isn't loaded yet
toolbox = gDevTools.getToolbox(target);
if (toolbox) {
inspector = toolbox.getPanel("inspector");
if (inspector) {
info("Toolbox and inspector already open");
if (cb) {
return cb(inspector, toolbox);
} else {
return {
toolbox: toolbox,
inspector: inspector
};
}
}
}
info("Opening the toolbox");
toolbox = yield gDevTools.showToolbox(target, "inspector");
yield waitForToolboxFrameFocus(toolbox);
inspector = toolbox.getPanel("inspector");
info("Waiting for the inspector to update");
yield inspector.once("inspector-updated");
if (cb) {
return cb(inspector, toolbox);
} else {
return {
toolbox: toolbox,
inspector: inspector
};
}
});
/**
* Wait for the toolbox frame to receive focus after it loads
* @param {Toolbox} toolbox
* @return a promise that resolves when focus has been received
*/
function waitForToolboxFrameFocus(toolbox) {
info("Making sure that the toolbox's frame is focused");
let def = promise.defer();
let win = toolbox.frame.contentWindow;
waitForFocus(def.resolve, win);
return def.promise;
}
function getActiveInspector() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
return gDevTools.getToolbox(target).getPanel("inspector");
}
/**
* Get the NodeFront for a node that matches a given css selector, via the
* protocol.
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return {Promise} Resolves to the NodeFront instance
*/
function getNodeFront(selector, {walker}) {
if (selector._form) {
return selector;
}
return walker.querySelector(walker.rootNode, selector);
}
/**
* Get the NodeFront for a node that matches a given css selector inside a
* given iframe.
* @param {String|NodeFront} selector
* @param {String|NodeFront} frameSelector A selector that matches the iframe
* the node is in
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @param {String} reason Defaults to "test" which instructs the inspector not
* to highlight the node upon selection
* @return {Promise} Resolves when the inspector is updated with the new node
*/
let getNodeFrontInFrame = Task.async(function*(selector, frameSelector, inspector,
reason="test") {
let iframe = yield getNodeFront(frameSelector, inspector);
let {nodes} = yield inspector.walker.children(iframe);
return inspector.walker.querySelector(nodes[0], selector);
});
/**
* Get the current rect of the border region of the box-model highlighter
*/
let getSimpleBorderRect = Task.async(function*(toolbox) {
let {border} = yield getBoxModelStatus(toolbox);
let {p1, p2, p3, p4} = border.points;
return {
top: p1.y,
left: p1.x,
width: p2.x - p1.x,
height: p4.y - p1.y
};
});
function getHighlighterActorID(toolbox) {
return toolbox.highlighter.actorID;
}
/**
* Get the current positions and visibility of the various box-model highlighter
* elements.
*/
let getBoxModelStatus = Task.async(function*(toolbox) {
let isVisible = yield isHighlighting(toolbox);
let ret = {
visible: isVisible
};
for (let region of ["margin", "border", "padding", "content"]) {
let points = yield getPointsForRegion(region, toolbox);
let visible = yield isRegionHidden(region, toolbox);
ret[region] = {points, visible};
}
ret.guides = {};
for (let guide of ["top", "right", "bottom", "left"]) {
ret.guides[guide] = yield getGuideStatus(guide, toolbox);
}
return ret;
});
let getGuideStatus = Task.async(function*(location, toolbox) {
let actorID = getHighlighterActorID(toolbox);
let {data: hidden} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-guide-" + location,
name: "hidden",
actorID
});
let {data: x1} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-guide-" + location,
name: "x1",
actorID
});
let {data: y1} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-guide-" + location,
name: "y1",
actorID
});
let {data: x2} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-guide-" + location,
name: "x2",
actorID
});
let {data: y2} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-guide-" + location,
name: "y2",
actorID
});
return {
visible: !hidden,
x1: x1,
y1: y1,
x2: x2,
y2: y2
};
});
/**
* Get the coordinate (points attribute) from one of the polygon elements in the
* box model highlighter.
*/
let getPointsForRegion = Task.async(function*(region, toolbox) {
let {data: points} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-" + region,
name: "points",
actorID: getHighlighterActorID(toolbox)
});
points = points.split(/[, ]/);
return {
p1: {
x: parseFloat(points[0]),
y: parseFloat(points[1])
},
p2: {
x: parseFloat(points[2]),
y: parseFloat(points[3])
},
p3: {
x: parseFloat(points[4]),
y: parseFloat(points[5])
},
p4: {
x: parseFloat(points[6]),
y: parseFloat(points[7])
}
};
});
/**
* Is a given region polygon element of the box-model highlighter currently
* hidden?
*/
let isRegionHidden = Task.async(function*(region, toolbox) {
let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-" + region,
name: "hidden",
actorID: getHighlighterActorID(toolbox)
});
return value !== null;
});
/**
* Is the highlighter currently visible on the page?
*/
let isHighlighting = Task.async(function*(toolbox) {
let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: "box-model-root",
name: "hidden",
actorID: getHighlighterActorID(toolbox)
});
return value === null;
});
let getHighlitNode = Task.async(function*(toolbox) {
let {visible, content} = yield getBoxModelStatus(toolbox);
let points = content.points;
if (visible) {
let x = (points.p1.x + points.p2.x + points.p3.x + points.p4.x) / 4;
let y = (points.p1.y + points.p2.y + points.p3.y + points.p4.y) / 4;
let {objects} = yield executeInContent("Test:ElementFromPoint", {x, y});
return objects.element;
}
});
/**
* Assert that the box-model highlighter's current position corresponds to the
* given node boxquads.
* @param {DOMNode|CPOW} node The node to get the boxQuads from
* @param {String} prefix An optional prefix for logging information to the
* console.
*/
let isNodeCorrectlyHighlighted = Task.async(function*(node, toolbox, prefix="") {
prefix += (prefix ? " " : "") + node.nodeName;
prefix += (node.id ? "#" + node.id : "");
prefix += (node.classList.length ? "." + [...node.classList].join(".") : "");
prefix += " ";
let boxModel = yield getBoxModelStatus(toolbox);
let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads", null,
{node});
for (let boxType of ["content", "padding", "border", "margin"]) {
let quads = regions[boxType];
for (let point in boxModel[boxType].points) {
is(boxModel[boxType].points[point].x, quads[point].x,
prefix + boxType + " point " + point + " x coordinate is correct");
is(boxModel[boxType].points[point].y, quads[point].y,
prefix + boxType + " point " + point + " y coordinate is correct");
}
}
});
function synthesizeKeyFromKeyTag(aKeyId, aDocument = null) {
let document = aDocument || document;
let key = document.getElementById(aKeyId);
isnot(key, null, "Successfully retrieved the <key> node");
let modifiersAttr = key.getAttribute("modifiers");
let name = null;
if (key.getAttribute("keycode"))
name = key.getAttribute("keycode");
else if (key.getAttribute("key"))
name = key.getAttribute("key");
isnot(name, null, "Successfully retrieved keycode/key");
let modifiers = {
shiftKey: modifiersAttr.match("shift"),
ctrlKey: modifiersAttr.match("ctrl"),
altKey: modifiersAttr.match("alt"),
metaKey: modifiersAttr.match("meta"),
accelKey: modifiersAttr.match("accel")
}
EventUtils.synthesizeKey(name, modifiers);
}
let focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) {
info("Focusing search box");
let searchBox = panelWin.document.getElementById("inspector-searchbox");
let focused = once(searchBox, "focus");
panelWin.focus();
synthesizeKeyFromKeyTag("nodeSearchKey", panelWin.document);
yield focused;
if (callback) {
callback();
}
});
/**
* Get the MarkupContainer object instance that corresponds to the given
* NodeFront
* @param {NodeFront} nodeFront
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return {MarkupContainer}
*/
function getContainerForNodeFront(nodeFront, {markup}) {
return markup.getContainer(nodeFront);
}
/**
* Get the MarkupContainer object instance that corresponds to the given
* selector
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return {MarkupContainer}
*/
let getContainerForSelector = Task.async(function*(selector, inspector) {
info("Getting the markup-container for node " + selector);
let nodeFront = yield getNodeFront(selector, inspector);
let container = getContainerForNodeFront(nodeFront, inspector);
info("Found markup-container " + container);
return container;
});
/**
* Simulate a mouse-over on the markup-container (a line in the markup-view)
* that corresponds to the selector passed.
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return {Promise} Resolves when the container is hovered and the higlighter
* is shown on the corresponding node
*/
let hoverContainer = Task.async(function*(selector, inspector) {
info("Hovering over the markup-container for node " + selector);
let nodeFront = yield getNodeFront(selector, inspector);
let container = getContainerForNodeFront(nodeFront, inspector);
let highlit = inspector.toolbox.once("node-highlight");
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"},
inspector.markup.doc.defaultView);
return highlit;
});
/**
* Simulate a click on the markup-container (a line in the markup-view)
* that corresponds to the selector passed.
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return {Promise} Resolves when the node has been selected.
*/
let clickContainer = Task.async(function*(selector, inspector) {
info("Clicking on the markup-container for node " + selector);
let nodeFront = yield getNodeFront(selector, inspector);
let container = getContainerForNodeFront(nodeFront, inspector);
let updated = inspector.once("inspector-updated");
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"},
inspector.markup.doc.defaultView);
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mouseup"},
inspector.markup.doc.defaultView);
return updated;
});
/**
* Simulate the mouse leaving the markup-view area
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @return a promise when done
*/
function mouseLeaveMarkupView(inspector) {
info("Leaving the markup-view area");
let def = promise.defer();
// Find another element to mouseover over in order to leave the markup-view
let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button");
EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"},
inspector.toolbox.doc.defaultView);
executeSoon(def.resolve);
return def.promise;
}
/**
* 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 + ".");
let deferred = promise.defer();
for (let [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);
deferred.resolve.apply(deferred, aArgs);
}, useCapture);
break;
}
}
return deferred.promise;
}
/**
* Wait for a content -> chrome message on the message manager (the window
* messagemanager is used).
* @param {String} name The message name
* @return {Promise} A promise that resolves to the response data when the
* message has been received
*/
function waitForContentMessage(name) {
let mm = gBrowser.selectedTab.linkedBrowser.messageManager;
let def = promise.defer();
mm.addMessageListener(name, function onMessage(msg) {
mm.removeMessageListener(name, onMessage);
def.resolve(msg);
});
return def.promise;
}
function wait(ms) {
let def = promise.defer();
setTimeout(def.resolve, ms);
return def.promise;
}
/**
* Send an async message to the frame script (chrome -> content) and wait for a
* response message with the same name (content -> chrome).
* @param {String} name The message name. Should be one of the messages defined
* in doc_frame_script.js
* @param {Object} data Optional data to send along
* @param {Object} objects Optional CPOW objects to send along
* @param {Boolean} expectResponse If set to false, don't wait for a response
* with the same name from the content script. Defaults to true.
* @return {Promise} Resolves to the response data if a response is expected,
* immediately resolves otherwise
*/
function executeInContent(name, data={}, objects={}, expectResponse=true) {
let mm = gBrowser.selectedTab.linkedBrowser.messageManager;
mm.sendAsyncMessage(name, data, objects);
if (expectResponse) {
return waitForContentMessage(name);
} else {
return promise.resolve();
}
}