tenfourfox/devtools/client/inspector/test/head.js
Cameron Kaiser c9b2922b70 hello FPR
2017-04-19 00:56:45 -07:00

596 lines
20 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";
var Cu = Components.utils;
var Ci = Components.interfaces;
var Cc = Components.classes;
var CC = Components.Constructor;
// 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);
var TEST_URL_ROOT = "http://example.com/browser/devtools/client/inspector/test/";
var ROOT_TEST_DIR = getRootDirectory(gTestPath);
// All test are asynchronous
waitForExplicitFinish();
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
var {TargetFactory} = require("devtools/client/framework/target");
var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var promise = require("promise");
// Import the GCLI test helper
var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
// Import helpers registering the test-actor in remote targets
Services.scriptloader.loadSubScript(testDir + "../../../shared/test/test-actor-registry.js", this);
DevToolsUtils.testing = true;
registerCleanupFunction(() => {
DevToolsUtils.testing = false;
});
registerCleanupFunction(() => {
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
*/
var 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;
yield once(browser, "load", true);
info("URL '" + url + "' loading complete");
return tab;
});
var navigateTo = function (toolbox, url) {
let activeTab = toolbox.target.activeTab;
return activeTab.navigateTo(url);
};
/**
* 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
*/
var 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.
* @param {String} hostType Optional hostType, as defined in Toolbox.HostType
* @return A promise that is resolved once the tab and inspector have loaded
* with an object: { tab, toolbox, inspector }.
*/
var openInspectorForURL = Task.async(function*(url, hostType) {
let tab = yield addTab(url);
let { inspector, toolbox, testActor } = yield openInspector(null, hostType);
return { tab, inspector, toolbox, testActor };
});
/**
* Open the toolbox, with the inspector tool visible.
* @param {Function} cb Optional callback, if you don't want to use the returned
* promise
* @param {String} hostType Optional hostType, as defined in Toolbox.HostType
* @return a promise that resolves when the inspector is ready
*/
var openInspector = Task.async(function*(cb, hostType) {
info("Opening the inspector");
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
if (toolbox) {
if (toolbox.getPanel("inspector")) {
info("Toolbox and inspector already open");
throw new Error("Inspector is already opened, please use getActiveInspector");
}
}
info("Opening the toolbox");
toolbox = yield gDevTools.showToolbox(target, "inspector", hostType);
yield waitForToolboxFrameFocus(toolbox);
let inspector = toolbox.getPanel("inspector");
info("Waiting for the inspector to update");
yield inspector.once("inspector-updated");
yield registerTestActor(toolbox.target.client);
let testActor = yield getTestActor(toolbox);
return {
toolbox: toolbox,
inspector: inspector,
testActor: testActor
};
});
/**
* 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
*/
var 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);
});
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);
}
var 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}
*/
var 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
*/
var 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.
*/
var 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-controls");
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;
}
/**
* Dispatch the copy event on the given element
*/
function fireCopyEvent(element) {
let evt = element.ownerDocument.createEvent("Event");
evt.initEvent("copy", true, true);
element.dispatchEvent(evt);
}
/**
* Undo the last markup-view action and wait for the corresponding mutation to
* occur
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return a promise that resolves when the markup-mutation has been treated or
* rejects if no undo action is possible
*/
function undoChange(inspector) {
let canUndo = inspector.markup.undo.canUndo();
ok(canUndo, "The last change in the markup-view can be undone");
if (!canUndo) {
return promise.reject();
}
let mutated = inspector.once("markupmutation");
inspector.markup.undo.undo();
return mutated;
}
/**
* Redo the last markup-view action and wait for the corresponding mutation to
* occur
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return a promise that resolves when the markup-mutation has been treated or
* rejects if no redo action is possible
*/
function redoChange(inspector) {
let canRedo = inspector.markup.undo.canRedo();
ok(canRedo, "The last change in the markup-view can be redone");
if (!canRedo) {
return promise.reject();
}
let mutated = inspector.once("markupmutation");
inspector.markup.undo.redo();
return mutated;
}
/**
* Dispatch a command event on a node (e.g. click on a contextual menu item).
* @param {DOMNode} node
*/
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
node.dispatchEvent(commandEvent);
}
/**
* A helper that simulates a contextmenu event on the given chrome DOM element.
*/
function contextMenuClick(element) {
let evt = element.ownerDocument.createEvent('MouseEvents');
let button = 2; // right click
evt.initMouseEvent('contextmenu', true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}
/**
* A helper that fetches a front for a node that matches the given selector or
* doctype node if the selector is falsy.
*/
function* getNodeFrontForSelector(selector, inspector) {
if (selector) {
info("Retrieving front for selector " + selector);
return getNodeFront(selector, inspector);
} else {
info("Retrieving front for doctype node");
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
return nodes[0];
}
}
/**
* Encapsulate some common operations for highlighter's tests, to have
* the tests cleaner, without exposing directly `inspector`, `highlighter`, and
* `testActor` if not needed.
*
* @param {String}
* The highlighter's type
* @return
* A generator function that takes an object with `inspector` and `testActor`
* properties. (see `openInspector`)
*/
const getHighlighterHelperFor = (type) => Task.async(
function*({inspector, testActor}) {
let front = inspector.inspector;
let highlighter = yield front.getHighlighterByType(type);
let prefix = "";
return {
set prefix(value) {
prefix = value;
},
show: function*(selector = ":root") {
let node = yield getNodeFront(selector, inspector);
yield highlighter.show(node);
},
isElementHidden: function*(id) {
return (yield testActor.getHighlighterNodeAttribute(
prefix + id, "hidden", highlighter)) === "true";
},
getElementTextContent: function*(id) {
return yield testActor.getHighlighterNodeTextContent(
prefix + id, highlighter);
},
getElementAttribute: function*(id, name) {
return yield testActor.getHighlighterNodeAttribute(
prefix + id, name, highlighter);
},
synthesizeMouse: function*(options) {
yield testActor.synthesizeMouse(options);
},
finalize: function*() {
yield highlighter.finalize();
}
};
}
);
// The expand all operation of the markup-view calls itself recursively and
// there's not one event we can wait for to know when it's done
// so use this helper function to wait until all recursive children updates are done.
function* waitForMultipleChildrenUpdates(inspector) {
// As long as child updates are queued up while we wait for an update already
// wait again
if (inspector.markup._queuedChildUpdates &&
inspector.markup._queuedChildUpdates.size) {
yield waitForChildrenUpdated(inspector);
return yield waitForMultipleChildrenUpdates(inspector);
}
}
/**
* Using the markupview's _waitForChildren function, wait for all queued
* children updates to be handled.
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return a promise that resolves when all queued children updates have been
* handled
*/
function waitForChildrenUpdated({markup}) {
info("Waiting for queued children updates to be handled");
let def = promise.defer();
markup._waitForChildren().then(() => {
executeSoon(def.resolve);
});
return def.promise;
}