Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
2 /* Any copyright is dedicated to the Public Domain.
3 http://creativecommons.org/publicdomain/zero/1.0/ */
5 "use strict";
7 const Cu = Components.utils;
8 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
9 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
10 let TargetFactory = devtools.TargetFactory;
11 let {CssHtmlTree} = devtools.require("devtools/styleinspector/computed-view");
12 let {CssRuleView, _ElementStyle} = devtools.require("devtools/styleinspector/rule-view");
13 let {CssLogic, CssSelector} = devtools.require("devtools/styleinspector/css-logic");
14 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
15 let {editableField, getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
16 let {console} = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {});
18 // All test are asynchronous
19 waitForExplicitFinish();
21 const TEST_URL_ROOT = "http://example.com/browser/browser/devtools/styleinspector/test/";
22 const TEST_URL_ROOT_SSL = "https://example.com/browser/browser/devtools/styleinspector/test/";
24 // Auto clean-up when a test ends
25 registerCleanupFunction(() => {
26 try {
27 let target = TargetFactory.forTab(gBrowser.selectedTab);
28 gDevTools.closeToolbox(target);
29 } catch (ex) {
30 dump(ex);
31 }
32 while (gBrowser.tabs.length > 1) {
33 gBrowser.removeCurrentTab();
34 }
35 });
37 // Uncomment to log events
38 // Services.prefs.setBoolPref("devtools.dump.emit", true);
40 // Clean-up all prefs that might have been changed during a test run
41 // (safer here because if the test fails, then the pref is never reverted)
42 registerCleanupFunction(() => {
43 Services.prefs.clearUserPref("devtools.dump.emit");
44 Services.prefs.clearUserPref("devtools.defaultColorUnit");
45 });
47 /**
48 * The functions found below are here to ease test development and maintenance.
49 * Most of these functions are stateless and will require some form of context
50 * (the instance of the current toolbox, or inspector panel for instance).
51 *
52 * Most of these functions are async too and return promises.
53 *
54 * All tests should follow the following pattern:
55 *
56 * let test = asyncTest(function*() {
57 * yield addTab(TEST_URI);
58 * let {toolbox, inspector, view} = yield openComputedView();
59 *
60 * yield selectNode("#test", inspector);
61 * yield someAsyncTestFunction(view);
62 * });
63 *
64 * asyncTest is the way to define the testcase in the test file. It accepts
65 * a single generator-function argument.
66 * The generator function should yield any async call.
67 *
68 * There is no need to clean tabs up at the end of a test as this is done
69 * automatically.
70 *
71 * It is advised not to store any references on the global scope. There shouldn't
72 * be a need to anyway. Thanks to asyncTest, test steps, even though asynchronous,
73 * can be described in a nice flat way, and if/for/while/... control flow can be
74 * used as in sync code, making it possible to write the outline of the test case
75 * all in asyncTest, and delegate actual processing and assertions to other
76 * functions.
77 */
79 /* *********************************************
80 * UTILS
81 * *********************************************
82 * General test utilities.
83 * Define the test case, add new tabs, open the toolbox and switch to the
84 * various panels, select nodes, get node references, ...
85 */
87 /**
88 * Define an async test based on a generator function
89 */
90 function asyncTest(generator) {
91 return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
92 }
94 /**
95 * Add a new test tab in the browser and load the given url.
96 * @param {String} url The url to be loaded in the new tab
97 * @return a promise that resolves to the tab object when the url is loaded
98 */
99 function addTab(url) {
100 let def = promise.defer();
102 let tab = gBrowser.selectedTab = gBrowser.addTab();
103 gBrowser.selectedBrowser.addEventListener("load", function onload() {
104 gBrowser.selectedBrowser.removeEventListener("load", onload, true);
105 info("URL " + url + " loading complete into new test tab");
106 waitForFocus(() => {
107 def.resolve(tab);
108 }, content);
109 }, true);
110 content.location = url;
112 return def.promise;
113 }
115 /**
116 * Simple DOM node accesor function that takes either a node or a string css
117 * selector as argument and returns the corresponding node
118 * @param {String|DOMNode} nodeOrSelector
119 * @return {DOMNode}
120 */
121 function getNode(nodeOrSelector) {
122 return typeof nodeOrSelector === "string" ?
123 content.document.querySelector(nodeOrSelector) :
124 nodeOrSelector;
125 }
127 /**
128 * Set the inspector's current selection to a node or to the first match of the
129 * given css selector
130 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
131 * loaded in the toolbox
132 * @param {String} reason Defaults to "test" which instructs the inspector not
133 * to highlight the node upon selection
134 * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection
135 * @return a promise that resolves when the inspector is updated with the new
136 * node
137 */
138 function selectNode(nodeOrSelector, inspector, reason="test") {
139 info("Selecting the node " + nodeOrSelector);
140 let node = getNode(nodeOrSelector);
141 let updated = inspector.once("inspector-updated");
142 inspector.selection.setNode(node, reason);
143 return updated;
144 }
146 /**
147 * Set the inspector's current selection to null so that no node is selected
148 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
149 * loaded in the toolbox
150 * @return a promise that resolves when the inspector is updated
151 */
152 function clearCurrentNodeSelection(inspector) {
153 info("Clearing the current selection");
154 let updated = inspector.once("inspector-updated");
155 inspector.selection.setNode(null);
156 return updated;
157 }
159 /**
160 * Open the toolbox, with the inspector tool visible.
161 * @return a promise that resolves when the inspector is ready
162 */
163 let openInspector = Task.async(function*() {
164 info("Opening the inspector");
165 let target = TargetFactory.forTab(gBrowser.selectedTab);
167 let inspector, toolbox;
169 // Checking if the toolbox and the inspector are already loaded
170 // The inspector-updated event should only be waited for if the inspector
171 // isn't loaded yet
172 toolbox = gDevTools.getToolbox(target);
173 if (toolbox) {
174 inspector = toolbox.getPanel("inspector");
175 if (inspector) {
176 info("Toolbox and inspector already open");
177 return {
178 toolbox: toolbox,
179 inspector: inspector
180 };
181 }
182 }
184 info("Opening the toolbox");
185 toolbox = yield gDevTools.showToolbox(target, "inspector");
186 yield waitForToolboxFrameFocus(toolbox);
187 inspector = toolbox.getPanel("inspector");
189 info("Waiting for the inspector to update");
190 yield inspector.once("inspector-updated");
192 return {
193 toolbox: toolbox,
194 inspector: inspector
195 };
196 });
198 /**
199 * Wait for the toolbox frame to receive focus after it loads
200 * @param {Toolbox} toolbox
201 * @return a promise that resolves when focus has been received
202 */
203 function waitForToolboxFrameFocus(toolbox) {
204 info("Making sure that the toolbox's frame is focused");
205 let def = promise.defer();
206 let win = toolbox.frame.contentWindow;
207 waitForFocus(def.resolve, win);
208 return def.promise;
209 }
211 /**
212 * Open the toolbox, with the inspector tool visible, and the sidebar that
213 * corresponds to the given id selected
214 * @return a promise that resolves when the inspector is ready and the sidebar
215 * view is visible and ready
216 */
217 let openInspectorSideBar = Task.async(function*(id) {
218 let {toolbox, inspector} = yield openInspector();
220 if (!hasSideBarTab(inspector, id)) {
221 info("Waiting for the " + id + " sidebar to be ready");
222 yield inspector.sidebar.once(id + "-ready");
223 }
225 info("Selecting the " + id + " sidebar");
226 inspector.sidebar.select(id);
228 return {
229 toolbox: toolbox,
230 inspector: inspector,
231 view: inspector.sidebar.getWindowForTab(id)[id].view
232 };
233 });
235 /**
236 * Open the toolbox, with the inspector tool visible, and the computed-view
237 * sidebar tab selected.
238 * @return a promise that resolves when the inspector is ready and the computed
239 * view is visible and ready
240 */
241 function openComputedView() {
242 return openInspectorSideBar("computedview");
243 }
245 /**
246 * Open the toolbox, with the inspector tool visible, and the rule-view
247 * sidebar tab selected.
248 * @return a promise that resolves when the inspector is ready and the rule
249 * view is visible and ready
250 */
251 function openRuleView() {
252 return openInspectorSideBar("ruleview");
253 }
255 /**
256 * Wait for eventName on target.
257 * @param {Object} target An observable object that either supports on/off or
258 * addEventListener/removeEventListener
259 * @param {String} eventName
260 * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
261 * @return A promise that resolves when the event has been handled
262 */
263 function once(target, eventName, useCapture=false) {
264 info("Waiting for event: '" + eventName + "' on " + target + ".");
266 let deferred = promise.defer();
268 for (let [add, remove] of [
269 ["addEventListener", "removeEventListener"],
270 ["addListener", "removeListener"],
271 ["on", "off"]
272 ]) {
273 if ((add in target) && (remove in target)) {
274 target[add](eventName, function onEvent(...aArgs) {
275 target[remove](eventName, onEvent, useCapture);
276 deferred.resolve.apply(deferred, aArgs);
277 }, useCapture);
278 break;
279 }
280 }
282 return deferred.promise;
283 }
285 /**
286 * This shouldn't be used in the tests, but is useful when writing new tests or
287 * debugging existing tests in order to introduce delays in the test steps
288 * @param {Number} ms The time to wait
289 * @return A promise that resolves when the time is passed
290 */
291 function wait(ms) {
292 let def = promise.defer();
293 content.setTimeout(def.resolve, ms);
294 return def.promise;
295 }
297 /**
298 * Given an inplace editable element, click to switch it to edit mode, wait for
299 * focus
300 * @return a promise that resolves to the inplace-editor element when ready
301 */
302 let focusEditableField = Task.async(function*(editable, xOffset=1, yOffset=1, options={}) {
303 let onFocus = once(editable.parentNode, "focus", true);
305 info("Clicking on editable field to turn to edit mode");
306 EventUtils.synthesizeMouse(editable, xOffset, yOffset, options,
307 editable.ownerDocument.defaultView);
308 let event = yield onFocus;
310 info("Editable field gained focus, returning the input field now");
311 return inplaceEditor(editable.ownerDocument.activeElement);
312 });
314 /**
315 * Given a tooltip object instance (see Tooltip.js), checks if it is set to
316 * toggle and hover and if so, checks if the given target is a valid hover target.
317 * This won't actually show the tooltip (the less we interact with XUL panels
318 * during test runs, the better).
319 * @return a promise that resolves when the answer is known
320 */
321 function isHoverTooltipTarget(tooltip, target) {
322 if (!tooltip._basedNode || !tooltip.panel) {
323 return promise.reject(new Error(
324 "The tooltip passed isn't set to toggle on hover or is not a tooltip"));
325 }
326 return tooltip.isValidHoverTarget(target);
327 }
329 /**
330 * Same as isHoverTooltipTarget except that it will fail the test if there is no
331 * tooltip defined on hover of the given element
332 * @return a promise
333 */
334 function assertHoverTooltipOn(tooltip, element) {
335 return isHoverTooltipTarget(tooltip, element).then(() => {
336 ok(true, "A tooltip is defined on hover of the given element");
337 }, () => {
338 ok(false, "No tooltip is defined on hover of the given element");
339 });
340 }
342 /**
343 * Same as assertHoverTooltipOn but fails the test if there is a tooltip defined
344 * on hover of the given element
345 * @return a promise
346 */
347 function assertNoHoverTooltipOn(tooltip, element) {
348 return isHoverTooltipTarget(tooltip, element).then(() => {
349 ok(false, "A tooltip is defined on hover of the given element");
350 }, () => {
351 ok(true, "No tooltip is defined on hover of the given element");
352 });
353 }
355 /**
356 * Listen for a new window to open and return a promise that resolves when one
357 * does and completes its load.
358 * Only resolves when the new window topic isn't domwindowopened.
359 * @return a promise that resolves to the window object
360 */
361 function waitForWindow() {
362 let def = promise.defer();
364 info("Waiting for a window to open");
365 Services.ww.registerNotification(function onWindow(subject, topic) {
366 if (topic != "domwindowopened") {
367 return;
368 }
369 info("A window has been opened");
370 let win = subject.QueryInterface(Ci.nsIDOMWindow);
371 once(win, "load").then(() => {
372 info("The window load completed");
373 Services.ww.unregisterNotification(onWindow);
374 def.resolve(win);
375 });
376 });
378 return def.promise;
379 }
381 /**
382 * @see SimpleTest.waitForClipboard
383 * @param {Function} setup Function to execute before checking for the
384 * clipboard content
385 * @param {String|Boolean} expected An expected string or validator function
386 * @return a promise that resolves when the expected string has been found or
387 * the validator function has returned true, rejects otherwise.
388 */
389 function waitForClipboard(setup, expected) {
390 let def = promise.defer();
391 SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject);
392 return def.promise;
393 }
395 /**
396 * Dispatch the copy event on the given element
397 */
398 function fireCopyEvent(element) {
399 let evt = element.ownerDocument.createEvent("Event");
400 evt.initEvent("copy", true, true);
401 element.dispatchEvent(evt);
402 }
404 /**
405 * Polls a given function waiting for it to return true.
406 *
407 * @param {Function} validatorFn A validator function that returns a boolean.
408 * This is called every few milliseconds to check if the result is true. When
409 * it is true, the promise resolves. If validatorFn never returns true, then
410 * polling timeouts after several tries and the promise rejects.
411 * @param {String} name Optional name of the test. This is used to generate
412 * the success and failure messages.
413 * @param {Number} timeout Optional timeout for the validator function, in
414 * milliseconds. Default is 5000.
415 * @return a promise that resolves when the function returned true or rejects
416 * if the timeout is reached
417 */
418 function waitForSuccess(validatorFn, name="untitled", timeout=5000) {
419 let def = promise.defer();
420 let start = Date.now();
422 function wait(validatorFn) {
423 if ((Date.now() - start) > timeout) {
424 ok(false, "Validator function " + name + " timed out");
425 return def.reject();
426 }
427 if (validatorFn()) {
428 ok(true, "Validator function " + name + " returned true");
429 def.resolve();
430 } else {
431 setTimeout(() => wait(validatorFn), 100);
432 }
433 }
434 wait(validatorFn);
436 return def.promise;
437 }
439 /**
440 * Create a new style tag containing the given style text and append it to the
441 * document's head node
442 * @param {Document} doc
443 * @param {String} style
444 * @return {DOMNode} The newly created style node
445 */
446 function addStyle(doc, style) {
447 info("Adding a new style tag to the document with style content: " +
448 style.substring(0, 50));
449 let node = doc.createElement('style');
450 node.setAttribute("type", "text/css");
451 node.textContent = style;
452 doc.getElementsByTagName("head")[0].appendChild(node);
453 return node;
454 }
456 /**
457 * Checks whether the inspector's sidebar corresponding to the given id already
458 * exists
459 * @param {InspectorPanel}
460 * @param {String}
461 * @return {Boolean}
462 */
463 function hasSideBarTab(inspector, id) {
464 return !!inspector.sidebar.getWindowForTab(id);
465 }
467 /* *********************************************
468 * RULE-VIEW
469 * *********************************************
470 * Rule-view related test utility functions
471 * This object contains functions to get rules, get properties, ...
472 */
474 /**
475 * Get the DOMNode for a css rule in the rule-view that corresponds to the given
476 * selector
477 * @param {CssRuleView} view The instance of the rule-view panel
478 * @param {String} selectorText The selector in the rule-view for which the rule
479 * object is wanted
480 * @return {DOMNode}
481 */
482 function getRuleViewRule(view, selectorText) {
483 let rule;
484 for (let r of view.doc.querySelectorAll(".ruleview-rule")) {
485 let selector = r.querySelector(".ruleview-selector, .ruleview-selector-matched");
486 if (selector && selector.textContent === selectorText) {
487 rule = r;
488 break;
489 }
490 }
492 return rule;
493 }
495 /**
496 * Get references to the name and value span nodes corresponding to a given
497 * selector and property name in the rule-view
498 * @param {CssRuleView} view The instance of the rule-view panel
499 * @param {String} selectorText The selector in the rule-view to look for the
500 * property in
501 * @param {String} propertyName The name of the property
502 * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode}
503 */
504 function getRuleViewProperty(view, selectorText, propertyName) {
505 let prop;
507 let rule = getRuleViewRule(view, selectorText);
508 if (rule) {
509 // Look for the propertyName in that rule element
510 for (let p of rule.querySelectorAll(".ruleview-property")) {
511 let nameSpan = p.querySelector(".ruleview-propertyname");
512 let valueSpan = p.querySelector(".ruleview-propertyvalue");
514 if (nameSpan.textContent === propertyName) {
515 prop = {nameSpan: nameSpan, valueSpan: valueSpan};
516 break;
517 }
518 }
519 }
520 return prop;
521 }
523 /**
524 * Get the text value of the property corresponding to a given selector and name
525 * in the rule-view
526 * @param {CssRuleView} view The instance of the rule-view panel
527 * @param {String} selectorText The selector in the rule-view to look for the
528 * property in
529 * @param {String} propertyName The name of the property
530 * @return {String} The property value
531 */
532 function getRuleViewPropertyValue(view, selectorText, propertyName) {
533 return getRuleViewProperty(view, selectorText, propertyName)
534 .valueSpan.textContent;
535 }
537 /**
538 * Simulate a color change in a given color picker tooltip, and optionally wait
539 * for a given element in the page to have its style changed as a result
540 * @param {SwatchColorPickerTooltip} colorPicker
541 * @param {Array} newRgba The new color to be set [r, g, b, a]
542 * @param {Object} expectedChange Optional object that needs the following props:
543 * - {DOMNode} element The element in the page that will have its
544 * style changed.
545 * - {String} name The style name that will be changed
546 * - {String} value The expected style value
547 * The style will be checked like so: getComputedStyle(element)[name] === value
548 */
549 let simulateColorPickerChange = Task.async(function*(colorPicker, newRgba, expectedChange) {
550 info("Getting the spectrum colorpicker object");
551 let spectrum = yield colorPicker.spectrum;
552 info("Setting the new color");
553 spectrum.rgb = newRgba;
554 info("Applying the change");
555 spectrum.updateUI();
556 spectrum.onChange();
558 if (expectedChange) {
559 info("Waiting for the style to be applied on the page");
560 yield waitForSuccess(() => {
561 let {element, name, value} = expectedChange;
562 return content.getComputedStyle(element)[name] === value;
563 }, "Color picker change applied on the page");
564 }
565 });
567 /**
568 * Get a rule-link from the rule-view given its index
569 * @param {CssRuleView} view The instance of the rule-view panel
570 * @param {Number} index The index of the link to get
571 * @return {DOMNode} The link if any at this index
572 */
573 function getRuleViewLinkByIndex(view, index) {
574 let links = view.doc.querySelectorAll(".ruleview-rule-source");
575 return links[index];
576 }
578 /**
579 * Click on a rule-view's close brace to focus a new property name editor
580 * @param {RuleEditor} ruleEditor An instance of RuleEditor that will receive
581 * the new property
582 * @return a promise that resolves to the newly created editor when ready and
583 * focused
584 */
585 let focusNewRuleViewProperty = Task.async(function*(ruleEditor) {
586 info("Clicking on a close ruleEditor brace to start editing a new property");
587 ruleEditor.closeBrace.scrollIntoView();
588 let editor = yield focusEditableField(ruleEditor.closeBrace);
590 is(inplaceEditor(ruleEditor.newPropSpan), editor, "Focused editor is the new property editor.");
591 is(ruleEditor.rule.textProps.length, 0, "Starting with one new text property.");
592 is(ruleEditor.propertyList.children.length, 1, "Starting with two property editors.");
594 return editor;
595 });
597 /**
598 * Create a new property name in the rule-view, focusing a new property editor
599 * by clicking on the close brace, and then entering the given text.
600 * Keep in mind that the rule-view knows how to handle strings with multiple
601 * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3".
602 * @param {RuleEditor} ruleEditor The instance of RuleEditor that will receive
603 * the new property(ies)
604 * @param {String} inputValue The text to be entered in the new property name
605 * field
606 * @return a promise that resolves when the new property name has been entered
607 * and once the value field is focused
608 */
609 let createNewRuleViewProperty = Task.async(function*(ruleEditor, inputValue) {
610 info("Creating a new property editor");
611 let editor = yield focusNewRuleViewProperty(ruleEditor);
613 info("Entering the value " + inputValue);
614 editor.input.value = inputValue;
616 info("Submitting the new value and waiting for value field focus");
617 let onFocus = once(ruleEditor.element, "focus", true);
618 EventUtils.synthesizeKey("VK_RETURN", {},
619 ruleEditor.element.ownerDocument.defaultView);
620 yield onFocus;
621 });
623 // TO BE UNCOMMENTED WHEN THE EYEDROPPER FINALLY LANDS
624 // /**
625 // * Given a color swatch in the ruleview, click on it to open the color picker
626 // * and then click on the eyedropper button to start the eyedropper tool
627 // * @param {CssRuleView} view The instance of the rule-view panel
628 // * @param {DOMNode} swatch The color swatch to be clicked on
629 // * @return A promise that resolves when the dropper is opened
630 // */
631 // let openRuleViewEyeDropper = Task.async(function*(view, swatch) {
632 // info("Opening the colorpicker tooltip on a colorswatch");
633 // let tooltip = view.colorPicker.tooltip;
634 // let onTooltipShown = tooltip.once("shown");
635 // swatch.click();
636 // yield onTooltipShown;
638 // info("Finding the eyedropper icon in the colorpicker document");
639 // let tooltipDoc = tooltip.content.contentDocument;
640 // let dropperButton = tooltipDoc.querySelector("#eyedropper-button");
641 // ok(dropperButton, "Found the eyedropper icon");
643 // info("Opening the eyedropper");
644 // let onOpen = tooltip.once("eyedropper-opened");
645 // dropperButton.click();
646 // return yield onOpen;
647 // });
649 /* *********************************************
650 * COMPUTED-VIEW
651 * *********************************************
652 * Computed-view related utility functions.
653 * Allows to get properties, links, expand properties, ...
654 */
656 /**
657 * Get references to the name and value span nodes corresponding to a given
658 * property name in the computed-view
659 * @param {CssHtmlTree} view The instance of the computed view panel
660 * @param {String} name The name of the property to retrieve
661 * @return an object {nameSpan, valueSpan}
662 */
663 function getComputedViewProperty(view, name) {
664 let prop;
665 for (let property of view.styleDocument.querySelectorAll(".property-view")) {
666 let nameSpan = property.querySelector(".property-name");
667 let valueSpan = property.querySelector(".property-value");
669 if (nameSpan.textContent === name) {
670 prop = {nameSpan: nameSpan, valueSpan: valueSpan};
671 break;
672 }
673 }
674 return prop;
675 }
677 /**
678 * Get the text value of the property corresponding to a given name in the
679 * computed-view
680 * @param {CssHtmlTree} view The instance of the computed view panel
681 * @param {String} name The name of the property to retrieve
682 * @return {String} The property value
683 */
684 function getComputedViewPropertyValue(view, selectorText, propertyName) {
685 return getComputedViewProperty(view, selectorText, propertyName)
686 .valueSpan.textContent;
687 }
689 /**
690 * Expand a given property, given its index in the current property list of
691 * the computed view
692 * @param {CssHtmlTree} view The instance of the computed view panel
693 * @param {InspectorPanel} inspector The instance of the inspector panel
694 * @param {Number} index The index of the property to be expanded
695 * @return a promise that resolves when the property has been expanded, or
696 * rejects if the property was not found
697 */
698 function expandComputedViewPropertyByIndex(view, inspector, index) {
699 info("Expanding property " + index + " in the computed view");
700 let expandos = view.styleDocument.querySelectorAll(".expandable");
701 if (!expandos.length || !expandos[index]) {
702 return promise.reject();
703 }
705 let onExpand = inspector.once("computed-view-property-expanded");
706 expandos[index].click();
707 return onExpand;
708 }
710 /**
711 * Get a rule-link from the computed-view given its index
712 * @param {CssHtmlTree} view The instance of the computed view panel
713 * @param {Number} index The index of the link to be retrieved
714 * @return {DOMNode} The link at the given index, if one exists, null otherwise
715 */
716 function getComputedViewLinkByIndex(view, index) {
717 let links = view.styleDocument.querySelectorAll(".rule-link .link");
718 return links[index];
719 }
721 /* *********************************************
722 * STYLE-EDITOR
723 * *********************************************
724 * Style-editor related utility functions.
725 */
727 /**
728 * Wait for the toolbox to emit the styleeditor-selected event and when done
729 * wait for the stylesheet identified by href to be loaded in the stylesheet
730 * editor
731 * @param {Toolbox} toolbox
732 * @param {String} href Optional, if not provided, wait for the first editor
733 * to be ready
734 * @return a promise that resolves to the editor when the stylesheet editor is
735 * ready
736 */
737 function waitForStyleEditor(toolbox, href) {
738 let def = promise.defer();
740 info("Waiting for the toolbox to switch to the styleeditor");
741 toolbox.once("styleeditor-ready").then(() => {
742 let panel = toolbox.getCurrentPanel();
743 ok(panel && panel.UI, "Styleeditor panel switched to front");
745 panel.UI.on("editor-selected", function onEditorSelected(event, editor) {
746 let currentHref = editor.styleSheet.href;
747 if (!href || (href && currentHref.endsWith(href))) {
748 info("Stylesheet editor selected");
749 panel.UI.off("editor-selected", onEditorSelected);
750 editor.getSourceEditor().then(editor => {
751 info("Stylesheet editor fully loaded");
752 def.resolve(editor);
753 });
754 }
755 });
756 });
758 return def.promise;
759 }