michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cu = Components.utils; michael@0: let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); michael@0: let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: let TargetFactory = devtools.TargetFactory; michael@0: let {CssHtmlTree} = devtools.require("devtools/styleinspector/computed-view"); michael@0: let {CssRuleView, _ElementStyle} = devtools.require("devtools/styleinspector/rule-view"); michael@0: let {CssLogic, CssSelector} = devtools.require("devtools/styleinspector/css-logic"); michael@0: let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); michael@0: let {editableField, getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor"); michael@0: let {console} = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {}); michael@0: michael@0: // All test are asynchronous michael@0: waitForExplicitFinish(); michael@0: michael@0: const TEST_URL_ROOT = "http://example.com/browser/browser/devtools/styleinspector/test/"; michael@0: const TEST_URL_ROOT_SSL = "https://example.com/browser/browser/devtools/styleinspector/test/"; michael@0: michael@0: // Auto clean-up when a test ends michael@0: registerCleanupFunction(() => { michael@0: try { michael@0: let target = TargetFactory.forTab(gBrowser.selectedTab); michael@0: gDevTools.closeToolbox(target); michael@0: } catch (ex) { michael@0: dump(ex); michael@0: } michael@0: while (gBrowser.tabs.length > 1) { michael@0: gBrowser.removeCurrentTab(); michael@0: } michael@0: }); michael@0: michael@0: // Uncomment to log events michael@0: // Services.prefs.setBoolPref("devtools.dump.emit", true); michael@0: michael@0: // Clean-up all prefs that might have been changed during a test run michael@0: // (safer here because if the test fails, then the pref is never reverted) michael@0: registerCleanupFunction(() => { michael@0: Services.prefs.clearUserPref("devtools.dump.emit"); michael@0: Services.prefs.clearUserPref("devtools.defaultColorUnit"); michael@0: }); michael@0: michael@0: /** michael@0: * The functions found below are here to ease test development and maintenance. michael@0: * Most of these functions are stateless and will require some form of context michael@0: * (the instance of the current toolbox, or inspector panel for instance). michael@0: * michael@0: * Most of these functions are async too and return promises. michael@0: * michael@0: * All tests should follow the following pattern: michael@0: * michael@0: * let test = asyncTest(function*() { michael@0: * yield addTab(TEST_URI); michael@0: * let {toolbox, inspector, view} = yield openComputedView(); michael@0: * michael@0: * yield selectNode("#test", inspector); michael@0: * yield someAsyncTestFunction(view); michael@0: * }); michael@0: * michael@0: * asyncTest is the way to define the testcase in the test file. It accepts michael@0: * a single generator-function argument. michael@0: * The generator function should yield any async call. michael@0: * michael@0: * There is no need to clean tabs up at the end of a test as this is done michael@0: * automatically. michael@0: * michael@0: * It is advised not to store any references on the global scope. There shouldn't michael@0: * be a need to anyway. Thanks to asyncTest, test steps, even though asynchronous, michael@0: * can be described in a nice flat way, and if/for/while/... control flow can be michael@0: * used as in sync code, making it possible to write the outline of the test case michael@0: * all in asyncTest, and delegate actual processing and assertions to other michael@0: * functions. michael@0: */ michael@0: michael@0: /* ********************************************* michael@0: * UTILS michael@0: * ********************************************* michael@0: * General test utilities. michael@0: * Define the test case, add new tabs, open the toolbox and switch to the michael@0: * various panels, select nodes, get node references, ... michael@0: */ michael@0: michael@0: /** michael@0: * Define an async test based on a generator function michael@0: */ michael@0: function asyncTest(generator) { michael@0: return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish); michael@0: } michael@0: michael@0: /** michael@0: * Add a new test tab in the browser and load the given url. michael@0: * @param {String} url The url to be loaded in the new tab michael@0: * @return a promise that resolves to the tab object when the url is loaded michael@0: */ michael@0: function addTab(url) { michael@0: let def = promise.defer(); michael@0: michael@0: let tab = gBrowser.selectedTab = gBrowser.addTab(); michael@0: gBrowser.selectedBrowser.addEventListener("load", function onload() { michael@0: gBrowser.selectedBrowser.removeEventListener("load", onload, true); michael@0: info("URL " + url + " loading complete into new test tab"); michael@0: waitForFocus(() => { michael@0: def.resolve(tab); michael@0: }, content); michael@0: }, true); michael@0: content.location = url; michael@0: michael@0: return def.promise; michael@0: } michael@0: michael@0: /** michael@0: * Simple DOM node accesor function that takes either a node or a string css michael@0: * selector as argument and returns the corresponding node michael@0: * @param {String|DOMNode} nodeOrSelector michael@0: * @return {DOMNode} michael@0: */ michael@0: function getNode(nodeOrSelector) { michael@0: return typeof nodeOrSelector === "string" ? michael@0: content.document.querySelector(nodeOrSelector) : michael@0: nodeOrSelector; michael@0: } michael@0: michael@0: /** michael@0: * Set the inspector's current selection to a node or to the first match of the michael@0: * given css selector michael@0: * @param {InspectorPanel} inspector The instance of InspectorPanel currently michael@0: * loaded in the toolbox michael@0: * @param {String} reason Defaults to "test" which instructs the inspector not michael@0: * to highlight the node upon selection michael@0: * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection michael@0: * @return a promise that resolves when the inspector is updated with the new michael@0: * node michael@0: */ michael@0: function selectNode(nodeOrSelector, inspector, reason="test") { michael@0: info("Selecting the node " + nodeOrSelector); michael@0: let node = getNode(nodeOrSelector); michael@0: let updated = inspector.once("inspector-updated"); michael@0: inspector.selection.setNode(node, reason); michael@0: return updated; michael@0: } michael@0: michael@0: /** michael@0: * Set the inspector's current selection to null so that no node is selected michael@0: * @param {InspectorPanel} inspector The instance of InspectorPanel currently michael@0: * loaded in the toolbox michael@0: * @return a promise that resolves when the inspector is updated michael@0: */ michael@0: function clearCurrentNodeSelection(inspector) { michael@0: info("Clearing the current selection"); michael@0: let updated = inspector.once("inspector-updated"); michael@0: inspector.selection.setNode(null); michael@0: return updated; michael@0: } michael@0: michael@0: /** michael@0: * Open the toolbox, with the inspector tool visible. michael@0: * @return a promise that resolves when the inspector is ready michael@0: */ michael@0: let openInspector = Task.async(function*() { michael@0: info("Opening the inspector"); michael@0: let target = TargetFactory.forTab(gBrowser.selectedTab); michael@0: michael@0: let inspector, toolbox; michael@0: michael@0: // Checking if the toolbox and the inspector are already loaded michael@0: // The inspector-updated event should only be waited for if the inspector michael@0: // isn't loaded yet michael@0: toolbox = gDevTools.getToolbox(target); michael@0: if (toolbox) { michael@0: inspector = toolbox.getPanel("inspector"); michael@0: if (inspector) { michael@0: info("Toolbox and inspector already open"); michael@0: return { michael@0: toolbox: toolbox, michael@0: inspector: inspector michael@0: }; michael@0: } michael@0: } michael@0: michael@0: info("Opening the toolbox"); michael@0: toolbox = yield gDevTools.showToolbox(target, "inspector"); michael@0: yield waitForToolboxFrameFocus(toolbox); michael@0: inspector = toolbox.getPanel("inspector"); michael@0: michael@0: info("Waiting for the inspector to update"); michael@0: yield inspector.once("inspector-updated"); michael@0: michael@0: return { michael@0: toolbox: toolbox, michael@0: inspector: inspector michael@0: }; michael@0: }); michael@0: michael@0: /** michael@0: * Wait for the toolbox frame to receive focus after it loads michael@0: * @param {Toolbox} toolbox michael@0: * @return a promise that resolves when focus has been received michael@0: */ michael@0: function waitForToolboxFrameFocus(toolbox) { michael@0: info("Making sure that the toolbox's frame is focused"); michael@0: let def = promise.defer(); michael@0: let win = toolbox.frame.contentWindow; michael@0: waitForFocus(def.resolve, win); michael@0: return def.promise; michael@0: } michael@0: michael@0: /** michael@0: * Open the toolbox, with the inspector tool visible, and the sidebar that michael@0: * corresponds to the given id selected michael@0: * @return a promise that resolves when the inspector is ready and the sidebar michael@0: * view is visible and ready michael@0: */ michael@0: let openInspectorSideBar = Task.async(function*(id) { michael@0: let {toolbox, inspector} = yield openInspector(); michael@0: michael@0: if (!hasSideBarTab(inspector, id)) { michael@0: info("Waiting for the " + id + " sidebar to be ready"); michael@0: yield inspector.sidebar.once(id + "-ready"); michael@0: } michael@0: michael@0: info("Selecting the " + id + " sidebar"); michael@0: inspector.sidebar.select(id); michael@0: michael@0: return { michael@0: toolbox: toolbox, michael@0: inspector: inspector, michael@0: view: inspector.sidebar.getWindowForTab(id)[id].view michael@0: }; michael@0: }); michael@0: michael@0: /** michael@0: * Open the toolbox, with the inspector tool visible, and the computed-view michael@0: * sidebar tab selected. michael@0: * @return a promise that resolves when the inspector is ready and the computed michael@0: * view is visible and ready michael@0: */ michael@0: function openComputedView() { michael@0: return openInspectorSideBar("computedview"); michael@0: } michael@0: michael@0: /** michael@0: * Open the toolbox, with the inspector tool visible, and the rule-view michael@0: * sidebar tab selected. michael@0: * @return a promise that resolves when the inspector is ready and the rule michael@0: * view is visible and ready michael@0: */ michael@0: function openRuleView() { michael@0: return openInspectorSideBar("ruleview"); michael@0: } michael@0: michael@0: /** michael@0: * Wait for eventName on target. michael@0: * @param {Object} target An observable object that either supports on/off or michael@0: * addEventListener/removeEventListener michael@0: * @param {String} eventName michael@0: * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener michael@0: * @return A promise that resolves when the event has been handled michael@0: */ michael@0: function once(target, eventName, useCapture=false) { michael@0: info("Waiting for event: '" + eventName + "' on " + target + "."); michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: for (let [add, remove] of [ michael@0: ["addEventListener", "removeEventListener"], michael@0: ["addListener", "removeListener"], michael@0: ["on", "off"] michael@0: ]) { michael@0: if ((add in target) && (remove in target)) { michael@0: target[add](eventName, function onEvent(...aArgs) { michael@0: target[remove](eventName, onEvent, useCapture); michael@0: deferred.resolve.apply(deferred, aArgs); michael@0: }, useCapture); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * This shouldn't be used in the tests, but is useful when writing new tests or michael@0: * debugging existing tests in order to introduce delays in the test steps michael@0: * @param {Number} ms The time to wait michael@0: * @return A promise that resolves when the time is passed michael@0: */ michael@0: function wait(ms) { michael@0: let def = promise.defer(); michael@0: content.setTimeout(def.resolve, ms); michael@0: return def.promise; michael@0: } michael@0: michael@0: /** michael@0: * Given an inplace editable element, click to switch it to edit mode, wait for michael@0: * focus michael@0: * @return a promise that resolves to the inplace-editor element when ready michael@0: */ michael@0: let focusEditableField = Task.async(function*(editable, xOffset=1, yOffset=1, options={}) { michael@0: let onFocus = once(editable.parentNode, "focus", true); michael@0: michael@0: info("Clicking on editable field to turn to edit mode"); michael@0: EventUtils.synthesizeMouse(editable, xOffset, yOffset, options, michael@0: editable.ownerDocument.defaultView); michael@0: let event = yield onFocus; michael@0: michael@0: info("Editable field gained focus, returning the input field now"); michael@0: return inplaceEditor(editable.ownerDocument.activeElement); michael@0: }); michael@0: michael@0: /** michael@0: * Given a tooltip object instance (see Tooltip.js), checks if it is set to michael@0: * toggle and hover and if so, checks if the given target is a valid hover target. michael@0: * This won't actually show the tooltip (the less we interact with XUL panels michael@0: * during test runs, the better). michael@0: * @return a promise that resolves when the answer is known michael@0: */ michael@0: function isHoverTooltipTarget(tooltip, target) { michael@0: if (!tooltip._basedNode || !tooltip.panel) { michael@0: return promise.reject(new Error( michael@0: "The tooltip passed isn't set to toggle on hover or is not a tooltip")); michael@0: } michael@0: return tooltip.isValidHoverTarget(target); michael@0: } michael@0: michael@0: /** michael@0: * Same as isHoverTooltipTarget except that it will fail the test if there is no michael@0: * tooltip defined on hover of the given element michael@0: * @return a promise michael@0: */ michael@0: function assertHoverTooltipOn(tooltip, element) { michael@0: return isHoverTooltipTarget(tooltip, element).then(() => { michael@0: ok(true, "A tooltip is defined on hover of the given element"); michael@0: }, () => { michael@0: ok(false, "No tooltip is defined on hover of the given element"); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Same as assertHoverTooltipOn but fails the test if there is a tooltip defined michael@0: * on hover of the given element michael@0: * @return a promise michael@0: */ michael@0: function assertNoHoverTooltipOn(tooltip, element) { michael@0: return isHoverTooltipTarget(tooltip, element).then(() => { michael@0: ok(false, "A tooltip is defined on hover of the given element"); michael@0: }, () => { michael@0: ok(true, "No tooltip is defined on hover of the given element"); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Listen for a new window to open and return a promise that resolves when one michael@0: * does and completes its load. michael@0: * Only resolves when the new window topic isn't domwindowopened. michael@0: * @return a promise that resolves to the window object michael@0: */ michael@0: function waitForWindow() { michael@0: let def = promise.defer(); michael@0: michael@0: info("Waiting for a window to open"); michael@0: Services.ww.registerNotification(function onWindow(subject, topic) { michael@0: if (topic != "domwindowopened") { michael@0: return; michael@0: } michael@0: info("A window has been opened"); michael@0: let win = subject.QueryInterface(Ci.nsIDOMWindow); michael@0: once(win, "load").then(() => { michael@0: info("The window load completed"); michael@0: Services.ww.unregisterNotification(onWindow); michael@0: def.resolve(win); michael@0: }); michael@0: }); michael@0: michael@0: return def.promise; michael@0: } michael@0: michael@0: /** michael@0: * @see SimpleTest.waitForClipboard michael@0: * @param {Function} setup Function to execute before checking for the michael@0: * clipboard content michael@0: * @param {String|Boolean} expected An expected string or validator function michael@0: * @return a promise that resolves when the expected string has been found or michael@0: * the validator function has returned true, rejects otherwise. michael@0: */ michael@0: function waitForClipboard(setup, expected) { michael@0: let def = promise.defer(); michael@0: SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject); michael@0: return def.promise; michael@0: } michael@0: michael@0: /** michael@0: * Dispatch the copy event on the given element michael@0: */ michael@0: function fireCopyEvent(element) { michael@0: let evt = element.ownerDocument.createEvent("Event"); michael@0: evt.initEvent("copy", true, true); michael@0: element.dispatchEvent(evt); michael@0: } michael@0: michael@0: /** michael@0: * Polls a given function waiting for it to return true. michael@0: * michael@0: * @param {Function} validatorFn A validator function that returns a boolean. michael@0: * This is called every few milliseconds to check if the result is true. When michael@0: * it is true, the promise resolves. If validatorFn never returns true, then michael@0: * polling timeouts after several tries and the promise rejects. michael@0: * @param {String} name Optional name of the test. This is used to generate michael@0: * the success and failure messages. michael@0: * @param {Number} timeout Optional timeout for the validator function, in michael@0: * milliseconds. Default is 5000. michael@0: * @return a promise that resolves when the function returned true or rejects michael@0: * if the timeout is reached michael@0: */ michael@0: function waitForSuccess(validatorFn, name="untitled", timeout=5000) { michael@0: let def = promise.defer(); michael@0: let start = Date.now(); michael@0: michael@0: function wait(validatorFn) { michael@0: if ((Date.now() - start) > timeout) { michael@0: ok(false, "Validator function " + name + " timed out"); michael@0: return def.reject(); michael@0: } michael@0: if (validatorFn()) { michael@0: ok(true, "Validator function " + name + " returned true"); michael@0: def.resolve(); michael@0: } else { michael@0: setTimeout(() => wait(validatorFn), 100); michael@0: } michael@0: } michael@0: wait(validatorFn); michael@0: michael@0: return def.promise; michael@0: } michael@0: michael@0: /** michael@0: * Create a new style tag containing the given style text and append it to the michael@0: * document's head node michael@0: * @param {Document} doc michael@0: * @param {String} style michael@0: * @return {DOMNode} The newly created style node michael@0: */ michael@0: function addStyle(doc, style) { michael@0: info("Adding a new style tag to the document with style content: " + michael@0: style.substring(0, 50)); michael@0: let node = doc.createElement('style'); michael@0: node.setAttribute("type", "text/css"); michael@0: node.textContent = style; michael@0: doc.getElementsByTagName("head")[0].appendChild(node); michael@0: return node; michael@0: } michael@0: michael@0: /** michael@0: * Checks whether the inspector's sidebar corresponding to the given id already michael@0: * exists michael@0: * @param {InspectorPanel} michael@0: * @param {String} michael@0: * @return {Boolean} michael@0: */ michael@0: function hasSideBarTab(inspector, id) { michael@0: return !!inspector.sidebar.getWindowForTab(id); michael@0: } michael@0: michael@0: /* ********************************************* michael@0: * RULE-VIEW michael@0: * ********************************************* michael@0: * Rule-view related test utility functions michael@0: * This object contains functions to get rules, get properties, ... michael@0: */ michael@0: michael@0: /** michael@0: * Get the DOMNode for a css rule in the rule-view that corresponds to the given michael@0: * selector michael@0: * @param {CssRuleView} view The instance of the rule-view panel michael@0: * @param {String} selectorText The selector in the rule-view for which the rule michael@0: * object is wanted michael@0: * @return {DOMNode} michael@0: */ michael@0: function getRuleViewRule(view, selectorText) { michael@0: let rule; michael@0: for (let r of view.doc.querySelectorAll(".ruleview-rule")) { michael@0: let selector = r.querySelector(".ruleview-selector, .ruleview-selector-matched"); michael@0: if (selector && selector.textContent === selectorText) { michael@0: rule = r; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return rule; michael@0: } michael@0: michael@0: /** michael@0: * Get references to the name and value span nodes corresponding to a given michael@0: * selector and property name in the rule-view michael@0: * @param {CssRuleView} view The instance of the rule-view panel michael@0: * @param {String} selectorText The selector in the rule-view to look for the michael@0: * property in michael@0: * @param {String} propertyName The name of the property michael@0: * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode} michael@0: */ michael@0: function getRuleViewProperty(view, selectorText, propertyName) { michael@0: let prop; michael@0: michael@0: let rule = getRuleViewRule(view, selectorText); michael@0: if (rule) { michael@0: // Look for the propertyName in that rule element michael@0: for (let p of rule.querySelectorAll(".ruleview-property")) { michael@0: let nameSpan = p.querySelector(".ruleview-propertyname"); michael@0: let valueSpan = p.querySelector(".ruleview-propertyvalue"); michael@0: michael@0: if (nameSpan.textContent === propertyName) { michael@0: prop = {nameSpan: nameSpan, valueSpan: valueSpan}; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: return prop; michael@0: } michael@0: michael@0: /** michael@0: * Get the text value of the property corresponding to a given selector and name michael@0: * in the rule-view michael@0: * @param {CssRuleView} view The instance of the rule-view panel michael@0: * @param {String} selectorText The selector in the rule-view to look for the michael@0: * property in michael@0: * @param {String} propertyName The name of the property michael@0: * @return {String} The property value michael@0: */ michael@0: function getRuleViewPropertyValue(view, selectorText, propertyName) { michael@0: return getRuleViewProperty(view, selectorText, propertyName) michael@0: .valueSpan.textContent; michael@0: } michael@0: michael@0: /** michael@0: * Simulate a color change in a given color picker tooltip, and optionally wait michael@0: * for a given element in the page to have its style changed as a result michael@0: * @param {SwatchColorPickerTooltip} colorPicker michael@0: * @param {Array} newRgba The new color to be set [r, g, b, a] michael@0: * @param {Object} expectedChange Optional object that needs the following props: michael@0: * - {DOMNode} element The element in the page that will have its michael@0: * style changed. michael@0: * - {String} name The style name that will be changed michael@0: * - {String} value The expected style value michael@0: * The style will be checked like so: getComputedStyle(element)[name] === value michael@0: */ michael@0: let simulateColorPickerChange = Task.async(function*(colorPicker, newRgba, expectedChange) { michael@0: info("Getting the spectrum colorpicker object"); michael@0: let spectrum = yield colorPicker.spectrum; michael@0: info("Setting the new color"); michael@0: spectrum.rgb = newRgba; michael@0: info("Applying the change"); michael@0: spectrum.updateUI(); michael@0: spectrum.onChange(); michael@0: michael@0: if (expectedChange) { michael@0: info("Waiting for the style to be applied on the page"); michael@0: yield waitForSuccess(() => { michael@0: let {element, name, value} = expectedChange; michael@0: return content.getComputedStyle(element)[name] === value; michael@0: }, "Color picker change applied on the page"); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Get a rule-link from the rule-view given its index michael@0: * @param {CssRuleView} view The instance of the rule-view panel michael@0: * @param {Number} index The index of the link to get michael@0: * @return {DOMNode} The link if any at this index michael@0: */ michael@0: function getRuleViewLinkByIndex(view, index) { michael@0: let links = view.doc.querySelectorAll(".ruleview-rule-source"); michael@0: return links[index]; michael@0: } michael@0: michael@0: /** michael@0: * Click on a rule-view's close brace to focus a new property name editor michael@0: * @param {RuleEditor} ruleEditor An instance of RuleEditor that will receive michael@0: * the new property michael@0: * @return a promise that resolves to the newly created editor when ready and michael@0: * focused michael@0: */ michael@0: let focusNewRuleViewProperty = Task.async(function*(ruleEditor) { michael@0: info("Clicking on a close ruleEditor brace to start editing a new property"); michael@0: ruleEditor.closeBrace.scrollIntoView(); michael@0: let editor = yield focusEditableField(ruleEditor.closeBrace); michael@0: michael@0: is(inplaceEditor(ruleEditor.newPropSpan), editor, "Focused editor is the new property editor."); michael@0: is(ruleEditor.rule.textProps.length, 0, "Starting with one new text property."); michael@0: is(ruleEditor.propertyList.children.length, 1, "Starting with two property editors."); michael@0: michael@0: return editor; michael@0: }); michael@0: michael@0: /** michael@0: * Create a new property name in the rule-view, focusing a new property editor michael@0: * by clicking on the close brace, and then entering the given text. michael@0: * Keep in mind that the rule-view knows how to handle strings with multiple michael@0: * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3". michael@0: * @param {RuleEditor} ruleEditor The instance of RuleEditor that will receive michael@0: * the new property(ies) michael@0: * @param {String} inputValue The text to be entered in the new property name michael@0: * field michael@0: * @return a promise that resolves when the new property name has been entered michael@0: * and once the value field is focused michael@0: */ michael@0: let createNewRuleViewProperty = Task.async(function*(ruleEditor, inputValue) { michael@0: info("Creating a new property editor"); michael@0: let editor = yield focusNewRuleViewProperty(ruleEditor); michael@0: michael@0: info("Entering the value " + inputValue); michael@0: editor.input.value = inputValue; michael@0: michael@0: info("Submitting the new value and waiting for value field focus"); michael@0: let onFocus = once(ruleEditor.element, "focus", true); michael@0: EventUtils.synthesizeKey("VK_RETURN", {}, michael@0: ruleEditor.element.ownerDocument.defaultView); michael@0: yield onFocus; michael@0: }); michael@0: michael@0: // TO BE UNCOMMENTED WHEN THE EYEDROPPER FINALLY LANDS michael@0: // /** michael@0: // * Given a color swatch in the ruleview, click on it to open the color picker michael@0: // * and then click on the eyedropper button to start the eyedropper tool michael@0: // * @param {CssRuleView} view The instance of the rule-view panel michael@0: // * @param {DOMNode} swatch The color swatch to be clicked on michael@0: // * @return A promise that resolves when the dropper is opened michael@0: // */ michael@0: // let openRuleViewEyeDropper = Task.async(function*(view, swatch) { michael@0: // info("Opening the colorpicker tooltip on a colorswatch"); michael@0: // let tooltip = view.colorPicker.tooltip; michael@0: // let onTooltipShown = tooltip.once("shown"); michael@0: // swatch.click(); michael@0: // yield onTooltipShown; michael@0: michael@0: // info("Finding the eyedropper icon in the colorpicker document"); michael@0: // let tooltipDoc = tooltip.content.contentDocument; michael@0: // let dropperButton = tooltipDoc.querySelector("#eyedropper-button"); michael@0: // ok(dropperButton, "Found the eyedropper icon"); michael@0: michael@0: // info("Opening the eyedropper"); michael@0: // let onOpen = tooltip.once("eyedropper-opened"); michael@0: // dropperButton.click(); michael@0: // return yield onOpen; michael@0: // }); michael@0: michael@0: /* ********************************************* michael@0: * COMPUTED-VIEW michael@0: * ********************************************* michael@0: * Computed-view related utility functions. michael@0: * Allows to get properties, links, expand properties, ... michael@0: */ michael@0: michael@0: /** michael@0: * Get references to the name and value span nodes corresponding to a given michael@0: * property name in the computed-view michael@0: * @param {CssHtmlTree} view The instance of the computed view panel michael@0: * @param {String} name The name of the property to retrieve michael@0: * @return an object {nameSpan, valueSpan} michael@0: */ michael@0: function getComputedViewProperty(view, name) { michael@0: let prop; michael@0: for (let property of view.styleDocument.querySelectorAll(".property-view")) { michael@0: let nameSpan = property.querySelector(".property-name"); michael@0: let valueSpan = property.querySelector(".property-value"); michael@0: michael@0: if (nameSpan.textContent === name) { michael@0: prop = {nameSpan: nameSpan, valueSpan: valueSpan}; michael@0: break; michael@0: } michael@0: } michael@0: return prop; michael@0: } michael@0: michael@0: /** michael@0: * Get the text value of the property corresponding to a given name in the michael@0: * computed-view michael@0: * @param {CssHtmlTree} view The instance of the computed view panel michael@0: * @param {String} name The name of the property to retrieve michael@0: * @return {String} The property value michael@0: */ michael@0: function getComputedViewPropertyValue(view, selectorText, propertyName) { michael@0: return getComputedViewProperty(view, selectorText, propertyName) michael@0: .valueSpan.textContent; michael@0: } michael@0: michael@0: /** michael@0: * Expand a given property, given its index in the current property list of michael@0: * the computed view michael@0: * @param {CssHtmlTree} view The instance of the computed view panel michael@0: * @param {InspectorPanel} inspector The instance of the inspector panel michael@0: * @param {Number} index The index of the property to be expanded michael@0: * @return a promise that resolves when the property has been expanded, or michael@0: * rejects if the property was not found michael@0: */ michael@0: function expandComputedViewPropertyByIndex(view, inspector, index) { michael@0: info("Expanding property " + index + " in the computed view"); michael@0: let expandos = view.styleDocument.querySelectorAll(".expandable"); michael@0: if (!expandos.length || !expandos[index]) { michael@0: return promise.reject(); michael@0: } michael@0: michael@0: let onExpand = inspector.once("computed-view-property-expanded"); michael@0: expandos[index].click(); michael@0: return onExpand; michael@0: } michael@0: michael@0: /** michael@0: * Get a rule-link from the computed-view given its index michael@0: * @param {CssHtmlTree} view The instance of the computed view panel michael@0: * @param {Number} index The index of the link to be retrieved michael@0: * @return {DOMNode} The link at the given index, if one exists, null otherwise michael@0: */ michael@0: function getComputedViewLinkByIndex(view, index) { michael@0: let links = view.styleDocument.querySelectorAll(".rule-link .link"); michael@0: return links[index]; michael@0: } michael@0: michael@0: /* ********************************************* michael@0: * STYLE-EDITOR michael@0: * ********************************************* michael@0: * Style-editor related utility functions. michael@0: */ michael@0: michael@0: /** michael@0: * Wait for the toolbox to emit the styleeditor-selected event and when done michael@0: * wait for the stylesheet identified by href to be loaded in the stylesheet michael@0: * editor michael@0: * @param {Toolbox} toolbox michael@0: * @param {String} href Optional, if not provided, wait for the first editor michael@0: * to be ready michael@0: * @return a promise that resolves to the editor when the stylesheet editor is michael@0: * ready michael@0: */ michael@0: function waitForStyleEditor(toolbox, href) { michael@0: let def = promise.defer(); michael@0: michael@0: info("Waiting for the toolbox to switch to the styleeditor"); michael@0: toolbox.once("styleeditor-ready").then(() => { michael@0: let panel = toolbox.getCurrentPanel(); michael@0: ok(panel && panel.UI, "Styleeditor panel switched to front"); michael@0: michael@0: panel.UI.on("editor-selected", function onEditorSelected(event, editor) { michael@0: let currentHref = editor.styleSheet.href; michael@0: if (!href || (href && currentHref.endsWith(href))) { michael@0: info("Stylesheet editor selected"); michael@0: panel.UI.off("editor-selected", onEditorSelected); michael@0: editor.getSourceEditor().then(editor => { michael@0: info("Stylesheet editor fully loaded"); michael@0: def.resolve(editor); michael@0: }); michael@0: } michael@0: }); michael@0: }); michael@0: michael@0: return def.promise; michael@0: }