Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 // Avoid leaks by using tmp for imports...
8 let tmp = {};
9 Cu.import("resource://gre/modules/Promise.jsm", tmp);
10 Cu.import("resource:///modules/CustomizableUI.jsm", tmp);
11 let {Promise, CustomizableUI} = tmp;
13 let ChromeUtils = {};
14 Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
16 Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
17 registerCleanupFunction(() => Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck"));
19 let {synthesizeDragStart, synthesizeDrop} = ChromeUtils;
21 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
22 const kTabEventFailureTimeoutInMs = 20000;
24 function createDummyXULButton(id, label) {
25 let btn = document.createElementNS(kNSXUL, "toolbarbutton");
26 btn.id = id;
27 btn.setAttribute("label", label || id);
28 btn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
29 window.gNavToolbox.palette.appendChild(btn);
30 return btn;
31 }
33 let gAddedToolbars = new Set();
35 function createToolbarWithPlacements(id, placements = []) {
36 gAddedToolbars.add(id);
37 let tb = document.createElementNS(kNSXUL, "toolbar");
38 tb.id = id;
39 tb.setAttribute("customizable", "true");
40 CustomizableUI.registerArea(id, {
41 type: CustomizableUI.TYPE_TOOLBAR,
42 defaultPlacements: placements
43 });
44 gNavToolbox.appendChild(tb);
45 return tb;
46 }
48 function createOverflowableToolbarWithPlacements(id, placements) {
49 gAddedToolbars.add(id);
51 let tb = document.createElementNS(kNSXUL, "toolbar");
52 tb.id = id;
53 tb.setAttribute("customizationtarget", id + "-target");
55 let customizationtarget = document.createElementNS(kNSXUL, "hbox");
56 customizationtarget.id = id + "-target";
57 customizationtarget.setAttribute("flex", "1");
58 tb.appendChild(customizationtarget);
60 let overflowPanel = document.createElementNS(kNSXUL, "panel");
61 overflowPanel.id = id + "-overflow";
62 document.getElementById("mainPopupSet").appendChild(overflowPanel);
64 let overflowList = document.createElementNS(kNSXUL, "vbox");
65 overflowList.id = id + "-overflow-list";
66 overflowPanel.appendChild(overflowList);
68 let chevron = document.createElementNS(kNSXUL, "toolbarbutton");
69 chevron.id = id + "-chevron";
70 tb.appendChild(chevron);
72 CustomizableUI.registerArea(id, {
73 type: CustomizableUI.TYPE_TOOLBAR,
74 defaultPlacements: placements,
75 overflowable: true,
76 });
78 tb.setAttribute("customizable", "true");
79 tb.setAttribute("overflowable", "true");
80 tb.setAttribute("overflowpanel", overflowPanel.id);
81 tb.setAttribute("overflowtarget", overflowList.id);
82 tb.setAttribute("overflowbutton", chevron.id);
84 gNavToolbox.appendChild(tb);
85 return tb;
86 }
88 function removeCustomToolbars() {
89 CustomizableUI.reset();
90 for (let toolbarId of gAddedToolbars) {
91 CustomizableUI.unregisterArea(toolbarId, true);
92 let tb = document.getElementById(toolbarId);
93 if (tb.hasAttribute("overflowpanel")) {
94 let panel = document.getElementById(tb.getAttribute("overflowpanel"));
95 if (panel)
96 panel.remove();
97 }
98 tb.remove();
99 }
100 gAddedToolbars.clear();
101 }
103 function getToolboxCustomToolbarId(toolbarName) {
104 return "__customToolbar_" + toolbarName.replace(" ", "_");
105 }
107 function resetCustomization() {
108 return CustomizableUI.reset();
109 }
111 function isInWin8() {
112 if (!Services.metro)
113 return false;
114 return Services.metro.supported;
115 }
117 function addSwitchToMetroButtonInWindows8(areaPanelPlacements) {
118 if (isInWin8()) {
119 areaPanelPlacements.push("switch-to-metro-button");
120 }
121 }
123 function assertAreaPlacements(areaId, expectedPlacements) {
124 let actualPlacements = getAreaWidgetIds(areaId);
125 placementArraysEqual(areaId, actualPlacements, expectedPlacements);
126 }
128 function placementArraysEqual(areaId, actualPlacements, expectedPlacements) {
129 is(actualPlacements.length, expectedPlacements.length,
130 "Area " + areaId + " should have " + expectedPlacements.length + " items.");
131 let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
132 for (let i = 0; i < minItems; i++) {
133 if (typeof expectedPlacements[i] == "string") {
134 is(actualPlacements[i], expectedPlacements[i],
135 "Item " + i + " in " + areaId + " should match expectations.");
136 } else if (expectedPlacements[i] instanceof RegExp) {
137 ok(expectedPlacements[i].test(actualPlacements[i]),
138 "Item " + i + " (" + actualPlacements[i] + ") in " +
139 areaId + " should match " + expectedPlacements[i]);
140 } else {
141 ok(false, "Unknown type of expected placement passed to " +
142 " assertAreaPlacements. Is your test broken?");
143 }
144 }
145 }
147 function todoAssertAreaPlacements(areaId, expectedPlacements) {
148 let actualPlacements = getAreaWidgetIds(areaId);
149 let isPassing = actualPlacements.length == expectedPlacements.length;
150 let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
151 for (let i = 0; i < minItems; i++) {
152 if (typeof expectedPlacements[i] == "string") {
153 isPassing = isPassing && actualPlacements[i] == expectedPlacements[i];
154 } else if (expectedPlacements[i] instanceof RegExp) {
155 isPassing = isPassing && expectedPlacements[i].test(actualPlacements[i]);
156 } else {
157 ok(false, "Unknown type of expected placement passed to " +
158 " assertAreaPlacements. Is your test broken?");
159 }
160 }
161 todo(isPassing, "The area placements for " + areaId +
162 " should equal the expected placements.");
163 }
165 function getAreaWidgetIds(areaId) {
166 return CustomizableUI.getWidgetIdsInArea(areaId);
167 }
169 function simulateItemDrag(toDrag, target) {
170 let docId = toDrag.ownerDocument.documentElement.id;
171 let dragData = [[{type: 'text/toolbarwrapper-id/' + docId,
172 data: toDrag.id}]];
173 synthesizeDragStart(toDrag.parentNode, dragData);
174 synthesizeDrop(target, target, dragData);
175 }
177 function endCustomizing(aWindow=window) {
178 if (aWindow.document.documentElement.getAttribute("customizing") != "true") {
179 return true;
180 }
181 Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true);
182 let deferredEndCustomizing = Promise.defer();
183 function onCustomizationEnds() {
184 Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", false);
185 aWindow.gNavToolbox.removeEventListener("aftercustomization", onCustomizationEnds);
186 deferredEndCustomizing.resolve();
187 }
188 aWindow.gNavToolbox.addEventListener("aftercustomization", onCustomizationEnds);
189 aWindow.gCustomizeMode.exit();
191 return deferredEndCustomizing.promise.then(function() {
192 let deferredLoadNewTab = Promise.defer();
194 //XXXgijs so some tests depend on this tab being about:blank. Make it so.
195 let newTabBrowser = aWindow.gBrowser.selectedBrowser;
196 newTabBrowser.stop();
198 // If we stop early enough, this might actually be about:blank.
199 if (newTabBrowser.contentDocument.location.href == "about:blank") {
200 return;
201 }
203 // Otherwise, make it be about:blank, and wait for that to be done.
204 function onNewTabLoaded(e) {
205 newTabBrowser.removeEventListener("load", onNewTabLoaded, true);
206 deferredLoadNewTab.resolve();
207 }
208 newTabBrowser.addEventListener("load", onNewTabLoaded, true);
209 newTabBrowser.contentDocument.location.replace("about:blank");
210 return deferredLoadNewTab.promise;
211 });
212 }
214 function startCustomizing(aWindow=window) {
215 if (aWindow.document.documentElement.getAttribute("customizing") == "true") {
216 return;
217 }
218 Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true);
219 let deferred = Promise.defer();
220 function onCustomizing() {
221 aWindow.gNavToolbox.removeEventListener("customizationready", onCustomizing);
222 Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", false);
223 deferred.resolve();
224 }
225 aWindow.gNavToolbox.addEventListener("customizationready", onCustomizing);
226 aWindow.gCustomizeMode.enter();
227 return deferred.promise;
228 }
230 function openAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
231 let deferred = Promise.defer();
232 let win = OpenBrowserWindow(aOptions);
233 if (aWaitForDelayedStartup) {
234 Services.obs.addObserver(function onDS(aSubject, aTopic, aData) {
235 if (aSubject != win) {
236 return;
237 }
238 Services.obs.removeObserver(onDS, "browser-delayed-startup-finished");
239 deferred.resolve(win);
240 }, "browser-delayed-startup-finished", false);
242 } else {
243 win.addEventListener("load", function onLoad() {
244 win.removeEventListener("load", onLoad);
245 deferred.resolve(win);
246 });
247 }
248 return deferred.promise;
249 }
251 function promiseWindowClosed(win) {
252 let deferred = Promise.defer();
253 win.addEventListener("unload", function onunload() {
254 win.removeEventListener("unload", onunload);
255 deferred.resolve();
256 });
257 win.close();
258 return deferred.promise;
259 }
261 function promisePanelShown(win) {
262 let panelEl = win.PanelUI.panel;
263 return promisePanelElementShown(win, panelEl);
264 }
266 function promiseOverflowShown(win) {
267 let panelEl = win.document.getElementById("widget-overflow");
268 return promisePanelElementShown(win, panelEl);
269 }
271 function promisePanelElementShown(win, aPanel) {
272 let deferred = Promise.defer();
273 let timeoutId = win.setTimeout(() => {
274 deferred.reject("Panel did not show within 20 seconds.");
275 }, 20000);
276 function onPanelOpen(e) {
277 aPanel.removeEventListener("popupshown", onPanelOpen);
278 win.clearTimeout(timeoutId);
279 deferred.resolve();
280 };
281 aPanel.addEventListener("popupshown", onPanelOpen);
282 return deferred.promise;
283 }
285 function promisePanelHidden(win) {
286 let panelEl = win.PanelUI.panel;
287 return promisePanelElementHidden(win, panelEl);
288 }
290 function promiseOverflowHidden(win) {
291 let panelEl = document.getElementById("widget-overflow");
292 return promisePanelElementHidden(win, panelEl);
293 }
295 function promisePanelElementHidden(win, aPanel) {
296 let deferred = Promise.defer();
297 let timeoutId = win.setTimeout(() => {
298 deferred.reject("Panel did not hide within 20 seconds.");
299 }, 20000);
300 function onPanelClose(e) {
301 aPanel.removeEventListener("popuphidden", onPanelClose);
302 win.clearTimeout(timeoutId);
303 deferred.resolve();
304 }
305 aPanel.addEventListener("popuphidden", onPanelClose);
306 return deferred.promise;
307 }
309 function isPanelUIOpen() {
310 return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing";
311 }
313 function subviewShown(aSubview) {
314 let deferred = Promise.defer();
315 let win = aSubview.ownerDocument.defaultView;
316 let timeoutId = win.setTimeout(() => {
317 deferred.reject("Subview (" + aSubview.id + ") did not show within 20 seconds.");
318 }, 20000);
319 function onViewShowing(e) {
320 aSubview.removeEventListener("ViewShowing", onViewShowing);
321 win.clearTimeout(timeoutId);
322 deferred.resolve();
323 };
324 aSubview.addEventListener("ViewShowing", onViewShowing);
325 return deferred.promise;
326 }
328 function subviewHidden(aSubview) {
329 let deferred = Promise.defer();
330 let win = aSubview.ownerDocument.defaultView;
331 let timeoutId = win.setTimeout(() => {
332 deferred.reject("Subview (" + aSubview.id + ") did not hide within 20 seconds.");
333 }, 20000);
334 function onViewHiding(e) {
335 aSubview.removeEventListener("ViewHiding", onViewHiding);
336 win.clearTimeout(timeoutId);
337 deferred.resolve();
338 };
339 aSubview.addEventListener("ViewHiding", onViewHiding);
340 return deferred.promise;
341 }
343 function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
344 function tryNow() {
345 tries++;
346 if (aConditionFn()) {
347 deferred.resolve();
348 } else if (tries < aMaxTries) {
349 tryAgain();
350 } else {
351 deferred.reject("Condition timed out: " + aConditionFn.toSource());
352 }
353 }
354 function tryAgain() {
355 setTimeout(tryNow, aCheckInterval);
356 }
357 let deferred = Promise.defer();
358 let tries = 0;
359 tryAgain();
360 return deferred.promise;
361 }
363 function waitFor(aTimeout=100) {
364 let deferred = Promise.defer();
365 setTimeout(function() deferred.resolve(), aTimeout);
366 return deferred.promise;
367 }
369 /**
370 * Starts a load in an existing tab and waits for it to finish (via some event).
371 *
372 * @param aTab The tab to load into.
373 * @param aUrl The url to load.
374 * @param aEventType The load event type to wait for. Defaults to "load".
375 * @return {Promise} resolved when the event is handled.
376 */
377 function promiseTabLoadEvent(aTab, aURL, aEventType="load") {
378 let deferred = Promise.defer();
379 info("Wait for tab event: " + aEventType);
381 let timeoutId = setTimeout(() => {
382 aTab.linkedBrowser.removeEventListener(aEventType, onTabLoad, true);
383 deferred.reject("TabSelect did not happen within " + kTabEventFailureTimeoutInMs + "ms");
384 }, kTabEventFailureTimeoutInMs);
386 function onTabLoad(event) {
387 if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
388 event.target.location.href == "about:blank") {
389 info("skipping spurious load event");
390 return;
391 }
392 clearTimeout(timeoutId);
393 aTab.linkedBrowser.removeEventListener(aEventType, onTabLoad, true);
394 info("Tab event received: " + aEventType);
395 deferred.resolve();
396 }
397 aTab.linkedBrowser.addEventListener(aEventType, onTabLoad, true, true);
398 aTab.linkedBrowser.loadURI(aURL);
399 return deferred.promise;
400 }
402 /**
403 * Navigate back or forward in tab history and wait for it to finish.
404 *
405 * @param aDirection Number to indicate to move backward or forward in history.
406 * @param aConditionFn Function that returns the result of an evaluated condition
407 * that needs to be `true` to resolve the promise.
408 * @return {Promise} resolved when navigation has finished.
409 */
410 function promiseTabHistoryNavigation(aDirection = -1, aConditionFn) {
411 let deferred = Promise.defer();
413 let timeoutId = setTimeout(() => {
414 gBrowser.removeEventListener("pageshow", listener, true);
415 deferred.reject("Pageshow did not happen within " + kTabEventFailureTimeoutInMs + "ms");
416 }, kTabEventFailureTimeoutInMs);
418 function listener(event) {
419 gBrowser.removeEventListener("pageshow", listener, true);
420 clearTimeout(timeoutId);
422 if (aConditionFn) {
423 waitForCondition(aConditionFn).then(() => deferred.resolve(),
424 aReason => deferred.reject(aReason));
425 } else {
426 deferred.resolve();
427 }
428 }
429 gBrowser.addEventListener("pageshow", listener, true);
431 content.history.go(aDirection);
433 return deferred.promise;
434 }
436 function popupShown(aPopup) {
437 return promisePopupEvent(aPopup, "shown");
438 }
440 function popupHidden(aPopup) {
441 return promisePopupEvent(aPopup, "hidden");
442 }
444 /**
445 * Returns a Promise that resolves when aPopup fires an event of type
446 * aEventType. Times out and rejects after 20 seconds.
447 *
448 * @param aPopup the popup to monitor for events.
449 * @param aEventSuffix the _suffix_ for the popup event type to watch for.
450 *
451 * Example usage:
452 * let popupShownPromise = promisePopupEvent(somePopup, "shown");
453 * // ... something that opens a popup
454 * yield popupShownPromise;
455 *
456 * let popupHiddenPromise = promisePopupEvent(somePopup, "hidden");
457 * // ... something that hides a popup
458 * yield popupHiddenPromise;
459 */
460 function promisePopupEvent(aPopup, aEventSuffix) {
461 let deferred = Promise.defer();
462 let win = aPopup.ownerDocument.defaultView;
463 let eventType = "popup" + aEventSuffix;
465 let timeoutId = win.setTimeout(() => {
466 deferred.reject("Context menu (" + aPopup.id + ") did not fire "
467 + eventType + " within 20 seconds.");
468 }, 20000);
470 function onPopupEvent(e) {
471 win.clearTimeout(timeoutId);
472 aPopup.removeEventListener(eventType, onPopupEvent);
473 deferred.resolve();
474 };
476 aPopup.addEventListener(eventType, onPopupEvent);
477 return deferred.promise;
478 }
480 // This is a simpler version of the context menu check that
481 // exists in contextmenu_common.js.
482 function checkContextMenu(aContextMenu, aExpectedEntries, aWindow=window) {
483 let childNodes = aContextMenu.childNodes;
484 for (let i = 0; i < childNodes.length; i++) {
485 let menuitem = childNodes[i];
486 try {
487 if (aExpectedEntries[i][0] == "---") {
488 is(menuitem.localName, "menuseparator", "menuseparator expected");
489 continue;
490 }
492 let selector = aExpectedEntries[i][0];
493 ok(menuitem.mozMatchesSelector(selector), "menuitem should match " + selector + " selector");
494 let commandValue = menuitem.getAttribute("command");
495 let relatedCommand = commandValue ? aWindow.document.getElementById(commandValue) : null;
496 let menuItemDisabled = relatedCommand ?
497 relatedCommand.getAttribute("disabled") == "true" :
498 menuitem.getAttribute("disabled") == "true";
499 is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector);
500 } catch (e) {
501 ok(false, "Exception when checking context menu: " + e);
502 }
503 }
504 }