diff options
Diffstat (limited to 'toolkit/devtools/inspector/test/doc_frame_script.js')
-rw-r--r-- | toolkit/devtools/inspector/test/doc_frame_script.js | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/toolkit/devtools/inspector/test/doc_frame_script.js b/toolkit/devtools/inspector/test/doc_frame_script.js new file mode 100644 index 000000000..3eb534900 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_frame_script.js @@ -0,0 +1,308 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// A helper frame-script for brower/devtools/inspector tests. +// +// Most listeners in the script expect "Test:"-namespaced messages from chrome, +// then execute code upon receiving, and immediately send back a message. +// This is so that chrome test code can execute code in content and wait for a +// response. +// Some listeners do not send a response message back. + +let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +let {LayoutHelpers} = Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", {}); +let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); +let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); +let EventUtils = {}; +loader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils); + +/** + * If the test page creates and triggeres the custom event + * "test-page-processing-done", then the Test:TestPageProcessingDone message + * will be sent to the parent process for tests to wait for this event if needed. + */ +addEventListener("DOMWindowCreated", () => { + content.addEventListener("test-page-processing-done", () => { + sendAsyncMessage("Test:TestPageProcessingDone"); + }, false); +}); + +/** + * Given an actorID and connection prefix, get the corresponding actor from the + * debugger-server connection. + * @param {String} actorID + * @param {String} connPrefix + */ +function getHighlighterActor(actorID, connPrefix) { + let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); + if (!DebuggerServer.initialized) { + return; + } + + let conn = DebuggerServer._connections[connPrefix]; + if (!conn) { + return; + } + + return conn.getActor(actorID); +} + +/** + * Get the instance of CanvasFrameAnonymousContentHelper used by a given + * highlighter actor. + * The instance provides methods to get/set attributes/text/style on nodes of + * the highlighter, inserted into the nsCanvasFrame. + * @see /toolkit/devtools/server/actors/highlighter.js + * @param {String} actorID + * @param {String} connPrefix + */ +function getHighlighterCanvasFrameHelper(actorID, connPrefix) { + let actor = getHighlighterActor(actorID, connPrefix); + if (actor && actor._highlighter) { + return actor._highlighter.markup; + } +} + +/** + * Get a value for a given attribute name, on one of the elements of the box + * model highlighter, given its ID. + * @param {Object} msg The msg.data part expects the following properties + * - {String} nodeID The full ID of the element to get the attribute for + * - {String} name The name of the attribute to get + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter actor ID's connection prefix + * @return {String} The value, if found, null otherwise + */ +addMessageListener("Test:GetHighlighterAttribute", function(msg) { + let {nodeID, name, actorID, connPrefix} = msg.data; + + let value; + let helper = getHighlighterCanvasFrameHelper(actorID, connPrefix); + if (helper) { + value = helper.getAttributeForElement(nodeID, name); + } + + sendAsyncMessage("Test:GetHighlighterAttribute", value); +}); + +/** + * Get the textcontent of one of the elements of the box model highlighter, + * given its ID. + * @param {Object} msg The msg.data part expects the following properties + * - {String} nodeID The full ID of the element to get the attribute for + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + * @return {String} The textcontent value + */ +addMessageListener("Test:GetHighlighterTextContent", function(msg) { + let {nodeID, actorID, connPrefix} = msg.data; + + let value; + let helper = getHighlighterCanvasFrameHelper(actorID, connPrefix); + if (helper) { + value = helper.getTextContentForElement(nodeID); + } + + sendAsyncMessage("Test:GetHighlighterTextContent", value); +}); + +/** + * Get the number of box-model highlighters created by the SelectorHighlighter + * @param {Object} msg The msg.data part expects the following properties: + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + * @return {Number} The number of box-model highlighters created, or null if the + * SelectorHighlighter was not found. + */ +addMessageListener("Test:GetSelectorHighlighterBoxNb", function(msg) { + let {actorID, connPrefix} = msg.data; + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + if (!h || !h._highlighters) { + sendAsyncMessage("Test:GetSelectorHighlighterBoxNb", null); + } else { + sendAsyncMessage("Test:GetSelectorHighlighterBoxNb", h._highlighters.length); + } +}); + +/** + * Subscribe to the box-model highlighter's update event, modify an attribute of + * the currently highlighted node and send a message when the highlighter has + * updated. + * @param {Object} msg The msg.data part expects the following properties + * - {String} the name of the attribute to be changed + * - {String} the new value for the attribute + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + */ +addMessageListener("Test:ChangeHighlightedNodeWaitForUpdate", function(msg) { + // The name and value of the attribute to be changed + let {name, value, actorID, connPrefix} = msg.data; + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + + h.once("updated", () => { + sendAsyncMessage("Test:ChangeHighlightedNodeWaitForUpdate"); + }); + + h.currentNode.setAttribute(name, value); +}); + +/** + * Subscribe to a given highlighter event and respond when the event is received. + * @param {Object} msg The msg.data part expects the following properties + * - {String} event The name of the highlighter event to listen to + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + */ +addMessageListener("Test:WaitForHighlighterEvent", function(msg) { + let {event, actorID, connPrefix} = msg.data; + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + + h.once(event, () => { + sendAsyncMessage("Test:WaitForHighlighterEvent"); + }); +}); + +/** + * Change the zoom level of the page. + * Optionally subscribe to the box-model highlighter's update event and waiting + * for it to refresh before responding. + * @param {Object} msg The msg.data part expects the following properties + * - {Number} level The new zoom level + * - {String} actorID Optional. The highlighter actor ID + * - {String} connPrefix Optional. The highlighter connection prefix + */ +addMessageListener("Test:ChangeZoomLevel", function(msg) { + let {level, actorID, connPrefix} = msg.data; + dumpn("Zooming page to " + level); + + if (actorID) { + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + h.once("updated", () => { + sendAsyncMessage("Test:ChangeZoomLevel"); + }); + } + + let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + docShell.contentViewer.fullZoom = level; + + if (!actorID) { + sendAsyncMessage("Test:ChangeZoomLevel"); + } +}); + +/** + * Get the element at the given x/y coordinates. + * @param {Object} msg The msg.data part expects the following properties + * - {Number} x + * - {Number} y + * @return {DOMNode} The CPOW of the element + */ +addMessageListener("Test:ElementFromPoint", function(msg) { + let {x, y} = msg.data; + dumpn("Getting the element at " + x + "/" + y); + + let helper = new LayoutHelpers(content); + let element = helper.getElementFromPoint(content.document, x, y); + sendAsyncMessage("Test:ElementFromPoint", null, {element}); +}); + +/** + * Get all box-model regions' adjusted boxquads for the given element + * @param {Object} msg The msg.data part should contain the node selector. + * @return {Object} An object with each property being a box-model region, each + * of them being an array of objects with the p1/p2/p3/p4 properties. + */ +addMessageListener("Test:GetAllAdjustedQuads", function(msg) { + let {selector} = msg.data; + let node = superQuerySelector(selector); + + let regions = {}; + + let helper = new LayoutHelpers(content); + for (let boxType of ["content", "padding", "border", "margin"]) { + regions[boxType] = helper.getAdjustedQuads(node, boxType); + } + + sendAsyncMessage("Test:GetAllAdjustedQuads", regions); +}); + +/** + * Synthesize a mouse event on an element. This handler doesn't send a message + * back. Consumers should listen to specific events on the inspector/highlighter + * to know when the event got synthesized. + * @param {Object} msg The msg.data part expects the following properties: + * - {Number} x + * - {Number} y + * - {Boolean} center If set to true, x/y will be ignored and + * synthesizeMouseAtCenter will be used instead + * - {Object} options Other event options + * The msg.objects part should be the element. + * @param {Object} data Event detail properties: + */ +addMessageListener("Test:SynthesizeMouse", function(msg) { + let {node} = msg.objects; + let {x, y, center, options, selector} = msg.data; + + if (!node && selector) { + node = superQuerySelector(selector); + } + + if (center) { + EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView); + } else { + EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView); + } + + // Most consumers won't need to listen to this message, unless they want to + // wait for the mouse event to be synthesized and don't have another event + // to listen to instead. + sendAsyncMessage("Test:SynthesizeMouse"); +}); + +/** + * Check that an element currently has a pseudo-class lock. + * @param {Object} msg The msg.data part expects the following properties: + * - {String} pseudo The pseudoclass to check for + * The msg.objects part should be the element. + * @param {Object} + * @return {Boolean} + */ +addMessageListener("Test:HasPseudoClassLock", function(msg) { + let {node} = msg.objects; + let {pseudo} = msg.data + sendAsyncMessage("Test:HasPseudoClassLock", DOMUtils.hasPseudoClassLock(node, pseudo)); +}); + +/** + * Like document.querySelector but can go into iframes too. + * ".container iframe || .sub-container div" will first try to find the node + * matched by ".container iframe" in the root document, then try to get the + * content document inside it, and then try to match ".sub-container div" inside + * this document. + * Any selector coming before the || separator *MUST* match a frame node. + * @param {String} superSelector. + * @return {DOMNode} The node, or null if not found. + */ +function superQuerySelector(superSelector, root=content.document) { + let frameIndex = superSelector.indexOf("||"); + if (frameIndex === -1) { + return root.querySelector(superSelector); + } else { + let rootSelector = superSelector.substring(0, frameIndex).trim(); + let childSelector = superSelector.substring(frameIndex+2).trim(); + root = root.querySelector(rootSelector); + if (!root || !root.contentWindow) { + return null; + } + + return superQuerySelector(childSelector, root.contentWindow.document); + } +} + +let dumpn = msg => dump(msg + "\n"); |