browser/metro/base/tests/mochitest/head.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 et sw=2 tw=80: */
michael@0 3 /* Any copyright is dedicated to the Public Domain.
michael@0 4 http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 5
michael@0 6 /*=============================================================================
michael@0 7 Globals
michael@0 8 =============================================================================*/
michael@0 9 XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
michael@0 10 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
michael@0 11
michael@0 12 /*=============================================================================
michael@0 13 Useful constants
michael@0 14 =============================================================================*/
michael@0 15 const serverRoot = "http://example.com/browser/metro/";
michael@0 16 const baseURI = "http://mochi.test:8888/browser/metro/";
michael@0 17 const chromeRoot = getRootDirectory(gTestPath);
michael@0 18 const kDefaultWait = 2000;
michael@0 19 const kDefaultInterval = 50;
michael@0 20
michael@0 21 /*=============================================================================
michael@0 22 Load Helpers
michael@0 23 =============================================================================*/
michael@0 24
michael@0 25 let splitPath = chromeRoot.split('/');
michael@0 26 if (!splitPath[splitPath.length-1]) {
michael@0 27 splitPath.pop();
michael@0 28 }
michael@0 29 // ../mochitest to make sure we're looking for the libs on the right path
michael@0 30 // even for mochiperf tests.
michael@0 31 splitPath.pop();
michael@0 32 splitPath.push('mochitest');
michael@0 33
michael@0 34 const mochitestPath = splitPath.join('/') + '/';
michael@0 35
michael@0 36 [
michael@0 37 "helpers/BookmarksHelper.js",
michael@0 38 "helpers/HistoryHelper.js",
michael@0 39 "helpers/ViewStateHelper.js"
michael@0 40 ].forEach(function(lib) {
michael@0 41 Services.scriptloader.loadSubScript(mochitestPath + lib, this);
michael@0 42 }, this);
michael@0 43
michael@0 44 /*=============================================================================
michael@0 45 Metro ui helpers
michael@0 46 =============================================================================*/
michael@0 47
michael@0 48 function isLandscapeMode()
michael@0 49 {
michael@0 50 return Elements.windowState.getAttribute("viewstate") == "landscape";
michael@0 51 }
michael@0 52
michael@0 53 function setDevPixelEqualToPx()
michael@0 54 {
michael@0 55 todo(false, "test depends on devPixelsPerPx set to 1.0 - see bugs 886624 and 859742");
michael@0 56 SpecialPowers.setCharPref("layout.css.devPixelsPerPx", "1.0");
michael@0 57 registerCleanupFunction(function () {
michael@0 58 SpecialPowers.clearUserPref("layout.css.devPixelsPerPx");
michael@0 59 });
michael@0 60 }
michael@0 61
michael@0 62 function checkContextUIMenuItemCount(aCount)
michael@0 63 {
michael@0 64 let visibleCount = 0;
michael@0 65 for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
michael@0 66 if (!ContextMenuUI.commands.childNodes[idx].hidden)
michael@0 67 visibleCount++;
michael@0 68 }
michael@0 69 is(visibleCount, aCount, "command list count");
michael@0 70 }
michael@0 71
michael@0 72 function checkContextUIMenuItemVisibility(aVisibleList)
michael@0 73 {
michael@0 74 let errors = 0;
michael@0 75 for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
michael@0 76 let item = ContextMenuUI.commands.childNodes[idx];
michael@0 77 if (aVisibleList.indexOf(item.id) != -1 && item.hidden) {
michael@0 78 // item should be visible
michael@0 79 errors++;
michael@0 80 info("should be visible:" + item.id);
michael@0 81 } else if (aVisibleList.indexOf(item.id) == -1 && !item.hidden) {
michael@0 82 // item should be hidden
michael@0 83 errors++;
michael@0 84 info("should be hidden:" + item.id);
michael@0 85 }
michael@0 86 }
michael@0 87 is(errors, 0, "context menu item list visibility");
michael@0 88 }
michael@0 89
michael@0 90 function checkMonoclePositionRange(aMonocle, aMinX, aMaxX, aMinY, aMaxY)
michael@0 91 {
michael@0 92 let monocle = null;
michael@0 93 if (aMonocle == "start")
michael@0 94 monocle = SelectionHelperUI._startMark;
michael@0 95 else if (aMonocle == "end")
michael@0 96 monocle = SelectionHelperUI._endMark;
michael@0 97 else if (aMonocle == "caret")
michael@0 98 monocle = SelectionHelperUI._caretMark;
michael@0 99 else
michael@0 100 ok(false, "bad monocle id");
michael@0 101
michael@0 102 ok(monocle.xPos > aMinX && monocle.xPos < aMaxX,
michael@0 103 "X position is " + monocle.xPos + ", expected between " + aMinX + " and " + aMaxX);
michael@0 104 ok(monocle.yPos > aMinY && monocle.yPos < aMaxY,
michael@0 105 "Y position is " + monocle.yPos + ", expected between " + aMinY + " and " + aMaxY);
michael@0 106 }
michael@0 107
michael@0 108 /*
michael@0 109 * showNotification - displays a test notification with the current
michael@0 110 * browser and waits for the noticiation to be fully displayed.
michael@0 111 *
michael@0 112 * Usage: yield showNotification();
michael@0 113 */
michael@0 114 function showNotification()
michael@0 115 {
michael@0 116 return Task.spawn(function() {
michael@0 117 let strings = Strings.browser;
michael@0 118 var buttons = [
michael@0 119 {
michael@0 120 isDefault: false,
michael@0 121 label: strings.GetStringFromName("popupButtonAllowOnce2"),
michael@0 122 accessKey: "",
michael@0 123 callback: function() { }
michael@0 124 },
michael@0 125 {
michael@0 126 label: strings.GetStringFromName("popupButtonAlwaysAllow3"),
michael@0 127 accessKey: "",
michael@0 128 callback: function() { }
michael@0 129 },
michael@0 130 {
michael@0 131 label: strings.GetStringFromName("popupButtonNeverWarn3"),
michael@0 132 accessKey: "",
michael@0 133 callback: function() { }
michael@0 134 }
michael@0 135 ];
michael@0 136 let notificationBox = Browser.getNotificationBox();
michael@0 137 const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
michael@0 138 let note = notificationBox.appendNotification("test notification", "popup-blocked",
michael@0 139 "chrome://browser/skin/images/infobar-popup.png",
michael@0 140 priority, buttons);
michael@0 141 yield waitForEvent(notificationBox, "transitionend");
michael@0 142 throw new Task.Result(note);
michael@0 143 });
michael@0 144 }
michael@0 145
michael@0 146 function removeNotifications() {
michael@0 147 Browser.getNotificationBox().removeAllNotifications(true);
michael@0 148 }
michael@0 149
michael@0 150 function getSelection(aElement) {
michael@0 151 if (!aElement)
michael@0 152 return null;
michael@0 153
michael@0 154 // chrome text edit
michael@0 155 if (aElement instanceof Ci.nsIDOMXULTextBoxElement) {
michael@0 156 return aElement.QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
michael@0 157 .editor.selection;
michael@0 158 }
michael@0 159
michael@0 160 // editable content element
michael@0 161 if (aElement instanceof Ci.nsIDOMNSEditableElement) {
michael@0 162 return aElement.QueryInterface(Ci.nsIDOMNSEditableElement)
michael@0 163 .editor.selection;
michael@0 164 }
michael@0 165
michael@0 166 // document or window
michael@0 167 if (aElement instanceof HTMLDocument || aElement instanceof Window) {
michael@0 168 return aElement.getSelection();
michael@0 169 }
michael@0 170
michael@0 171 // browser
michael@0 172 return aElement.contentWindow.getSelection();
michael@0 173 }
michael@0 174
michael@0 175 function getTrimmedSelection(aElement) {
michael@0 176 let sel = getSelection(aElement);
michael@0 177 if (!sel)
michael@0 178 return "";
michael@0 179 return sel.toString().trim();
michael@0 180 }
michael@0 181
michael@0 182 /*
michael@0 183 * clearSelection(aTarget) - clears the current selection in
michael@0 184 * aTarget, shuts down the selection manager and purges all
michael@0 185 * message manager events to insure a reset state for the ui.
michael@0 186 */
michael@0 187 function clearSelection(aTarget) {
michael@0 188 SelectionHelperUI.closeEditSession(true);
michael@0 189 getSelection(aTarget).removeAllRanges();
michael@0 190 purgeEventQueue();
michael@0 191 }
michael@0 192
michael@0 193 // Hides the tab and context app bar if they are visible
michael@0 194 function hideContextUI()
michael@0 195 {
michael@0 196 purgeEventQueue();
michael@0 197
michael@0 198 return Task.spawn(function() {
michael@0 199 if (ContextUI.tabbarVisible) {
michael@0 200 let promise = waitForEvent(Elements.tray, "transitionend", null, Elements.tray);
michael@0 201 if (ContextUI.dismiss()) {
michael@0 202 yield promise;
michael@0 203 }
michael@0 204 }
michael@0 205
michael@0 206 if (ContextUI.contextAppbarVisible) {
michael@0 207 let promise = waitForEvent(Elements.contextappbar, "transitionend", null, Elements.contextappbar);
michael@0 208 ContextUI.dismissContextAppbar();
michael@0 209 yield promise;
michael@0 210 }
michael@0 211 });
michael@0 212 }
michael@0 213
michael@0 214 function showNavBar()
michael@0 215 {
michael@0 216 if (!ContextUI.navbarVisible) {
michael@0 217 let promise = waitForEvent(Elements.navbar, "transitionend");
michael@0 218 ContextUI.displayNavbar();
michael@0 219 return promise;
michael@0 220 }
michael@0 221 return Promise.resolve(null);
michael@0 222 }
michael@0 223
michael@0 224 function hideNavBar()
michael@0 225 {
michael@0 226 if (ContextUI.navbarVisible) {
michael@0 227 let promise = waitForEvent(Elements.navbar, "transitionend");
michael@0 228 ContextUI.dismissNavbar();
michael@0 229 return promise;
michael@0 230 }
michael@0 231 return Promise.resolve(null);
michael@0 232 }
michael@0 233
michael@0 234 function fireAppBarDisplayEvent()
michael@0 235 {
michael@0 236 let promise = waitForEvent(Elements.tray, "transitionend");
michael@0 237 let event = document.createEvent("Events");
michael@0 238 event.initEvent("MozEdgeUICompleted", true, false);
michael@0 239 gWindow.dispatchEvent(event);
michael@0 240 purgeEventQueue();
michael@0 241 return promise;
michael@0 242 }
michael@0 243
michael@0 244 /*=============================================================================
michael@0 245 General test helpers
michael@0 246 =============================================================================*/
michael@0 247 let gOpenedTabs = [];
michael@0 248
michael@0 249 function loadUriInActiveTab(aUri)
michael@0 250 {
michael@0 251 return Task.spawn(function() {
michael@0 252 let promise = waitForEvent(getBrowser(), "pageshow");
michael@0 253 BrowserUI.goToURI(aUri);
michael@0 254 yield waitForCondition(function () {
michael@0 255 return getBrowser().currentURI.spec == aUri
michael@0 256 }, "getBrowser().currentURI.spec == " + aUri);
michael@0 257 yield promise;
michael@0 258 });
michael@0 259 }
michael@0 260
michael@0 261 function navForward() {
michael@0 262 return Task.spawn(function() {
michael@0 263 let promise = waitForEvent(getBrowser(), "pageshow");
michael@0 264 EventUtils.synthesizeKey("VK_RIGHT", { altKey: true }, window);
michael@0 265 yield promise;
michael@0 266 });
michael@0 267 }
michael@0 268
michael@0 269 function navBackViaNavButton() {
michael@0 270 return Task.spawn(function() {
michael@0 271 let promise = waitForEvent(getBrowser(), "pageshow");
michael@0 272 let backButton = document.getElementById("overlay-back");
michael@0 273 sendElementTap(window, backButton);
michael@0 274 yield promise;
michael@0 275 });
michael@0 276 }
michael@0 277
michael@0 278 /**
michael@0 279 * Loads a URL in a new tab asynchronously.
michael@0 280 *
michael@0 281 * Usage:
michael@0 282 * Task.spawn(function() {
michael@0 283 * let tab = yield addTab("http://example.com/");
michael@0 284 * ok(Browser.selectedTab == tab, "the new tab is selected");
michael@0 285 * });
michael@0 286 *
michael@0 287 * @param aUrl the URL to load
michael@0 288 * @returns a task that resolves to the new tab object after the URL is loaded.
michael@0 289 */
michael@0 290 function addTab(aUrl) {
michael@0 291 return Task.spawn(function() {
michael@0 292 info("Opening "+aUrl+" in a new tab");
michael@0 293 let tab = Browser.addTab(aUrl, true);
michael@0 294 yield tab.pageShowPromise;
michael@0 295
michael@0 296 is(tab.browser.currentURI.spec, aUrl, aUrl + " is loaded");
michael@0 297
michael@0 298 yield hideContextUI();
michael@0 299
michael@0 300 gOpenedTabs.push(tab);
michael@0 301
michael@0 302 throw new Task.Result(tab);
michael@0 303 });
michael@0 304 }
michael@0 305
michael@0 306 /**
michael@0 307 * Cleans up tabs left open by addTab().
michael@0 308 * This is being called at runTests() after the test loop.
michael@0 309 */
michael@0 310 function cleanUpOpenedTabs() {
michael@0 311 let tab;
michael@0 312 while(tab = gOpenedTabs.shift()) {
michael@0 313 cleanupNotificationsForBrowser(tab.browser);
michael@0 314 Browser.closeTab(Browser.getTabFromChrome(tab.chromeTab), { forceClose: true })
michael@0 315 }
michael@0 316 }
michael@0 317
michael@0 318 function cleanupNotificationsForBrowser(aBrowser) {
michael@0 319 let notificationBox = Browser.getNotificationBox(aBrowser);
michael@0 320 notificationBox && notificationBox.removeAllNotifications(true);
michael@0 321 }
michael@0 322
michael@0 323 /**
michael@0 324 * Waits a specified number of miliseconds for a specified event to be
michael@0 325 * fired on a specified element.
michael@0 326 *
michael@0 327 * Usage:
michael@0 328 * let receivedEvent = waitForEvent(element, "eventName");
michael@0 329 * // Do some processing here that will cause the event to be fired
michael@0 330 * // ...
michael@0 331 * // Now yield until the Promise is fulfilled
michael@0 332 * yield receivedEvent;
michael@0 333 * if (receivedEvent && !(receivedEvent instanceof Error)) {
michael@0 334 * receivedEvent.msg == "eventName";
michael@0 335 * // ...
michael@0 336 * }
michael@0 337 *
michael@0 338 * @param aSubject the element that should receive the event
michael@0 339 * @param aEventName the event to wait for
michael@0 340 * @param aTimeoutMs the number of miliseconds to wait before giving up
michael@0 341 * @returns a Promise that resolves to the received event, or to an Error
michael@0 342 */
michael@0 343 function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
michael@0 344 let eventDeferred = Promise.defer();
michael@0 345 let timeoutMs = aTimeoutMs || kDefaultWait;
michael@0 346 let stack = new Error().stack;
michael@0 347 let timerID = setTimeout(function wfe_canceller() {
michael@0 348 aSubject.removeEventListener(aEventName, listener);
michael@0 349 eventDeferred.reject( new Error(aEventName+" event timeout at " + stack) );
michael@0 350 }, timeoutMs);
michael@0 351
michael@0 352 var listener = function (aEvent) {
michael@0 353 if (aTarget && aTarget !== aEvent.target)
michael@0 354 return;
michael@0 355
michael@0 356 // stop the timeout clock and resume
michael@0 357 clearTimeout(timerID);
michael@0 358 eventDeferred.resolve(aEvent);
michael@0 359 }
michael@0 360
michael@0 361 function cleanup(aEventOrError) {
michael@0 362 // unhook listener in case of success or failure
michael@0 363 aSubject.removeEventListener(aEventName, listener);
michael@0 364 return aEventOrError;
michael@0 365 }
michael@0 366 aSubject.addEventListener(aEventName, listener, false);
michael@0 367 return eventDeferred.promise.then(cleanup, cleanup);
michael@0 368 }
michael@0 369
michael@0 370 /**
michael@0 371 * Wait for an nsIMessageManager IPC message.
michael@0 372 */
michael@0 373 function waitForMessage(aName, aMessageManager) {
michael@0 374 let deferred = Promise.defer();
michael@0 375 let manager = aMessageManager || messageManager;
michael@0 376 function listener(aMessage) {
michael@0 377 deferred.resolve(aMessage);
michael@0 378 }
michael@0 379 manager.addMessageListener(aName, listener);
michael@0 380 function cleanup(aEventOrError) {
michael@0 381 manager.removeMessageListener(aName, listener);
michael@0 382 }
michael@0 383 return deferred.promise.then(cleanup, cleanup);
michael@0 384 }
michael@0 385
michael@0 386 /**
michael@0 387 * Waits a specified number of miliseconds.
michael@0 388 *
michael@0 389 * Usage:
michael@0 390 * let wait = yield waitForMs(2000);
michael@0 391 * ok(wait, "2 seconds should now have elapsed");
michael@0 392 *
michael@0 393 * @param aMs the number of miliseconds to wait for
michael@0 394 * @returns a Promise that resolves to true after the time has elapsed
michael@0 395 */
michael@0 396 function waitForMs(aMs) {
michael@0 397 info("Wating for " + aMs + "ms");
michael@0 398 let deferred = Promise.defer();
michael@0 399 let startTime = Date.now();
michael@0 400 setTimeout(done, aMs);
michael@0 401
michael@0 402 function done() {
michael@0 403 deferred.resolve(true);
michael@0 404 info("waitForMs finished waiting, waited for "
michael@0 405 + (Date.now() - startTime)
michael@0 406 + "ms");
michael@0 407 }
michael@0 408
michael@0 409 return deferred.promise;
michael@0 410 }
michael@0 411
michael@0 412 /**
michael@0 413 * Waits a specified number of miliseconds for a supplied callback to
michael@0 414 * return a truthy value.
michael@0 415 *
michael@0 416 * Usage:
michael@0 417 * let success = yield waitForCondition(myTestFunction);
michael@0 418 * if (success && !(success instanceof Error)) {
michael@0 419 * // ...
michael@0 420 * }
michael@0 421 *
michael@0 422 * @param aCondition the callback that must return a truthy value
michael@0 423 * @param aTimeoutMs the number of miliseconds to wait before giving up
michael@0 424 * @param aIntervalMs the number of miliseconds between calls to aCondition
michael@0 425 * @returns a Promise that resolves to true, or to an Error
michael@0 426 */
michael@0 427 function waitForCondition(aCondition, aTimeoutMs, aIntervalMs) {
michael@0 428 let deferred = Promise.defer();
michael@0 429 let timeoutMs = aTimeoutMs || kDefaultWait;
michael@0 430 let intervalMs = aIntervalMs || kDefaultInterval;
michael@0 431 let startTime = Date.now();
michael@0 432 let stack = new Error().stack;
michael@0 433
michael@0 434 function testCondition() {
michael@0 435 let now = Date.now();
michael@0 436 if((now - startTime) > timeoutMs) {
michael@0 437 deferred.reject( new Error("Timed out waiting for condition to be true at " + stack) );
michael@0 438 return;
michael@0 439 }
michael@0 440
michael@0 441 let condition;
michael@0 442 try {
michael@0 443 condition = aCondition();
michael@0 444 } catch (e) {
michael@0 445 deferred.reject( new Error("Got exception while attempting to test condition: " + e) );
michael@0 446 return;
michael@0 447 }
michael@0 448
michael@0 449 if (condition) {
michael@0 450 deferred.resolve(true);
michael@0 451 } else {
michael@0 452 setTimeout(testCondition, intervalMs);
michael@0 453 }
michael@0 454 }
michael@0 455
michael@0 456 setTimeout(testCondition, 0);
michael@0 457 return deferred.promise;
michael@0 458 }
michael@0 459
michael@0 460 /**
michael@0 461 * same as waitForCondition but with better test output.
michael@0 462 *
michael@0 463 * @param aCondition the callback that must return a truthy value
michael@0 464 * @param aTestMsg test condition message printed when the test succeeds or
michael@0 465 * fails. Defaults to the stringified version of aCondition.
michael@0 466 * @param aTimeoutMs the number of miliseconds to wait before giving up
michael@0 467 * @param aIntervalMs the number of miliseconds between calls to aCondition
michael@0 468 * @returns a Promise that resolves to true, or to an Error
michael@0 469 */
michael@0 470 function waitForCondition2(aCondition, aTestMsg, aTimeoutMs, aIntervalMs) {
michael@0 471 let deferred = Promise.defer();
michael@0 472 let msg = aTestMsg || aCondition;
michael@0 473 let timeoutMs = aTimeoutMs || kDefaultWait;
michael@0 474 let intervalMs = aIntervalMs || kDefaultInterval;
michael@0 475 let startTime = Date.now();
michael@0 476
michael@0 477 function testCondition() {
michael@0 478 let now = Date.now();
michael@0 479 if((now - startTime) > timeoutMs) {
michael@0 480 deferred.reject( new Error("Timed out waiting for " + msg) );
michael@0 481 return;
michael@0 482 }
michael@0 483
michael@0 484 let condition;
michael@0 485 try {
michael@0 486 condition = aCondition();
michael@0 487 } catch (e) {
michael@0 488 deferred.reject( new Error("Got exception while attempting to test '" + msg + "': " + e) );
michael@0 489 return;
michael@0 490 }
michael@0 491
michael@0 492 if (condition) {
michael@0 493 ok(true, msg);
michael@0 494 deferred.resolve(true);
michael@0 495 } else {
michael@0 496 setTimeout(testCondition, intervalMs);
michael@0 497 }
michael@0 498 }
michael@0 499
michael@0 500 setTimeout(testCondition, 0);
michael@0 501 return deferred.promise;
michael@0 502 }
michael@0 503
michael@0 504 /*
michael@0 505 * Waits for an image in a page to load. Wrapper around waitForCondition.
michael@0 506 *
michael@0 507 * @param aWindow the tab or window that contains the image.
michael@0 508 * @param aImageId the id of the image in the page.
michael@0 509 * @returns a Promise that resolves to true, or to an Error
michael@0 510 */
michael@0 511 function waitForImageLoad(aWindow, aImageId) {
michael@0 512 let elem = aWindow.document.getElementById(aImageId);
michael@0 513 return waitForCondition(function () {
michael@0 514 let request = elem.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
michael@0 515 if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
michael@0 516 return true;
michael@0 517 return false;
michael@0 518 }, 5000, 100);
michael@0 519 }
michael@0 520
michael@0 521 /**
michael@0 522 * Waits a specified number of miliseconds for an observer event.
michael@0 523 *
michael@0 524 * @param aObsEvent the observer event to wait for
michael@0 525 * @param aTimeoutMs the number of miliseconds to wait before giving up
michael@0 526 * @returns a Promise that resolves to true, or to an Error
michael@0 527 */
michael@0 528 function waitForObserver(aObsEvent, aTimeoutMs, aObsData) {
michael@0 529 try {
michael@0 530
michael@0 531 let deferred = Promise.defer();
michael@0 532 let timeoutMs = aTimeoutMs || kDefaultWait;
michael@0 533 let timerID = 0;
michael@0 534
michael@0 535 var observeWatcher = {
michael@0 536 onEvent: function () {
michael@0 537 clearTimeout(timerID);
michael@0 538 Services.obs.removeObserver(this, aObsEvent);
michael@0 539 deferred.resolve();
michael@0 540 },
michael@0 541
michael@0 542 onError: function () {
michael@0 543 clearTimeout(timerID);
michael@0 544 Services.obs.removeObserver(this, aObsEvent);
michael@0 545 deferred.reject(new Error(aObsEvent + " event timeout"));
michael@0 546 },
michael@0 547
michael@0 548 observe: function (aSubject, aTopic, aData) {
michael@0 549 if (aTopic == aObsEvent &&
michael@0 550 (!aObsData || (aObsData == aData))) {
michael@0 551 this.onEvent();
michael@0 552 }
michael@0 553 },
michael@0 554
michael@0 555 QueryInterface: function (aIID) {
michael@0 556 if (!aIID.equals(Ci.nsIObserver) &&
michael@0 557 !aIID.equals(Ci.nsISupportsWeakReference) &&
michael@0 558 !aIID.equals(Ci.nsISupports)) {
michael@0 559 throw Components.results.NS_ERROR_NO_INTERFACE;
michael@0 560 }
michael@0 561 return this;
michael@0 562 },
michael@0 563 }
michael@0 564
michael@0 565 timerID = setTimeout(function wfo_canceller() {
michael@0 566 observeWatcher.onError();
michael@0 567 }, timeoutMs);
michael@0 568
michael@0 569 Services.obs.addObserver(observeWatcher, aObsEvent, true);
michael@0 570 return deferred.promise;
michael@0 571
michael@0 572 } catch (ex) {
michael@0 573 info(ex.message);
michael@0 574 }
michael@0 575 }
michael@0 576
michael@0 577
michael@0 578 /*=============================================================================
michael@0 579 * Input mode helpers - these helpers notify observers to metro_precise_input
michael@0 580 * and metro_imprecise_input respectively, triggering the same behaviour as user touch or mouse input
michael@0 581 *
michael@0 582 * Usage: let promise = waitForObservers("metro_imprecise_input");
michael@0 583 * notifyImprecise();
michael@0 584 * yield promise; // you are now in imprecise mode
michael@0 585 *===========================================================================*/
michael@0 586 function notifyPrecise()
michael@0 587 {
michael@0 588 Services.obs.notifyObservers(null, "metro_precise_input", null);
michael@0 589 }
michael@0 590
michael@0 591 function notifyImprecise()
michael@0 592 {
michael@0 593 Services.obs.notifyObservers(null, "metro_imprecise_input", null);
michael@0 594 }
michael@0 595
michael@0 596 /*=============================================================================
michael@0 597 * Native input helpers - these helpers send input directly to the os
michael@0 598 * generating os level input events that get processed by widget and
michael@0 599 * apzc logic.
michael@0 600 *===========================================================================*/
michael@0 601 function synthesizeNativeMouse(aElement, aOffsetX, aOffsetY, aMsg) {
michael@0 602 let x = aOffsetX;
michael@0 603 let y = aOffsetY;
michael@0 604 if (aElement) {
michael@0 605 if (aElement.getBoundingClientRect) {
michael@0 606 let rect = aElement.getBoundingClientRect();
michael@0 607 x += rect.left;
michael@0 608 y += rect.top;
michael@0 609 } else if(aElement.left && aElement.top) {
michael@0 610 x += aElement.left;
michael@0 611 y += aElement.top;
michael@0 612 }
michael@0 613 }
michael@0 614 Browser.windowUtils.sendNativeMouseEvent(x, y, aMsg, 0, null);
michael@0 615 }
michael@0 616
michael@0 617 function synthesizeNativeMouseMove(aElement, aOffsetX, aOffsetY) {
michael@0 618 synthesizeNativeMouse(aElement,
michael@0 619 aOffsetX,
michael@0 620 aOffsetY,
michael@0 621 0x0001); // MOUSEEVENTF_MOVE
michael@0 622 }
michael@0 623
michael@0 624 function synthesizeNativeMouseLDown(aElement, aOffsetX, aOffsetY) {
michael@0 625 synthesizeNativeMouse(aElement,
michael@0 626 aOffsetX,
michael@0 627 aOffsetY,
michael@0 628 0x0002); // MOUSEEVENTF_LEFTDOWN
michael@0 629 }
michael@0 630
michael@0 631 function synthesizeNativeMouseLUp(aElement, aOffsetX, aOffsetY) {
michael@0 632 synthesizeNativeMouse(aElement,
michael@0 633 aOffsetX,
michael@0 634 aOffsetY,
michael@0 635 0x0004); // MOUSEEVENTF_LEFTUP
michael@0 636 }
michael@0 637
michael@0 638 function synthesizeNativeMouseRDown(aElement, aOffsetX, aOffsetY) {
michael@0 639 synthesizeNativeMouse(aElement,
michael@0 640 aOffsetX,
michael@0 641 aOffsetY,
michael@0 642 0x0008); // MOUSEEVENTF_RIGHTDOWN
michael@0 643 }
michael@0 644
michael@0 645 function synthesizeNativeMouseRUp(aElement, aOffsetX, aOffsetY) {
michael@0 646 synthesizeNativeMouse(aElement,
michael@0 647 aOffsetX,
michael@0 648 aOffsetY,
michael@0 649 0x0010); // MOUSEEVENTF_RIGHTUP
michael@0 650 }
michael@0 651
michael@0 652 function synthesizeNativeMouseMDown(aElement, aOffsetX, aOffsetY) {
michael@0 653 synthesizeNativeMouse(aElement,
michael@0 654 aOffsetX,
michael@0 655 aOffsetY,
michael@0 656 0x0020); // MOUSEEVENTF_MIDDLEDOWN
michael@0 657 }
michael@0 658
michael@0 659 function synthesizeNativeMouseMUp(aElement, aOffsetX, aOffsetY) {
michael@0 660 synthesizeNativeMouse(aElement,
michael@0 661 aOffsetX,
michael@0 662 aOffsetY,
michael@0 663 0x0040); // MOUSEEVENTF_MIDDLEUP
michael@0 664 }
michael@0 665
michael@0 666 // WARNING: these calls can trigger the soft keyboard on tablets, but not
michael@0 667 // on test slaves (bug 947428).
michael@0 668 // WARNING: When testing the apzc, be careful of bug 933990. Events sent
michael@0 669 // shortly after loading a page may get ignored.
michael@0 670
michael@0 671 function sendNativeLongTap(aElement, aX, aY) {
michael@0 672 let coords = logicalCoordsForElement(aElement, aX, aY);
michael@0 673 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, true);
michael@0 674 }
michael@0 675
michael@0 676 function sendNativeTap(aElement, aX, aY) {
michael@0 677 let coords = logicalCoordsForElement(aElement, aX, aY);
michael@0 678 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
michael@0 679 }
michael@0 680
michael@0 681 function sendNativeDoubleTap(aElement, aX, aY) {
michael@0 682 let coords = logicalCoordsForElement(aElement, aX, aY);
michael@0 683 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
michael@0 684 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
michael@0 685 }
michael@0 686
michael@0 687 function clearNativeTouchSequence() {
michael@0 688 Browser.windowUtils.clearNativeTouchSequence();
michael@0 689 }
michael@0 690
michael@0 691 /*=============================================================================
michael@0 692 * Synthesized event helpers - these helpers synthesize input events that get
michael@0 693 * dispatched directly to the dom. As such widget and apzc logic is bypassed.
michael@0 694 *===========================================================================*/
michael@0 695
michael@0 696 /*
michael@0 697 * logicalCoordsForElement - given coordinates relative to top-left of
michael@0 698 * given element, returns logical coordinates for window. If a non-numeric
michael@0 699 * X or Y value is given, a value for the center of the element in that
michael@0 700 * dimension is used.
michael@0 701 *
michael@0 702 * @param aElement element coordinates are relative to.
michael@0 703 * @param aX, aY relative coordinates.
michael@0 704 */
michael@0 705 function logicalCoordsForElement (aElement, aX, aY) {
michael@0 706 let coords = { x: null, y: null };
michael@0 707 let rect = aElement.getBoundingClientRect();
michael@0 708
michael@0 709 coords.x = isNaN(aX) ? rect.left + (rect.width / 2) : rect.left + aX;
michael@0 710 coords.y = isNaN(aY) ? rect.top + (rect.height / 2) : rect.top + aY;
michael@0 711
michael@0 712 return coords;
michael@0 713 }
michael@0 714
michael@0 715 function sendContextMenuMouseClickToElement(aWindow, aElement, aX, aY) {
michael@0 716 let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 717 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 718 let coords = logicalCoordsForElement(aElement, aX, aY);
michael@0 719
michael@0 720 utils.sendMouseEventToWindow("mousedown", coords.x, coords.y, 2, 1, 0);
michael@0 721 utils.sendMouseEventToWindow("mouseup", coords.x, coords.y, 2, 1, 0);
michael@0 722 utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0);
michael@0 723 }
michael@0 724
michael@0 725 function sendMouseClick(aWindow, aX, aY) {
michael@0 726 EventUtils.synthesizeMouseAtPoint(aX, aY, {}, aWindow);
michael@0 727 }
michael@0 728
michael@0 729 /*
michael@0 730 * sendContextMenuClick - simulates a press-hold touch input event. Event
michael@0 731 * is delivered to the main window of the application through the top-level
michael@0 732 * widget.
michael@0 733 *
michael@0 734 * @param aX, aY logical coordinates of the event.
michael@0 735 */
michael@0 736 function sendContextMenuClick(aX, aY) {
michael@0 737 let mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
michael@0 738 .getService(Components.interfaces.nsIWindowMediator);
michael@0 739 let mainwin = mediator.getMostRecentWindow("navigator:browser");
michael@0 740 let utils = mainwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 741 .getInterface(Components.interfaces.nsIDOMWindowUtils);
michael@0 742 utils.sendMouseEvent("contextmenu", aX, aY, 2, 1, 0, true,
michael@0 743 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
michael@0 744 }
michael@0 745
michael@0 746 /*
michael@0 747 * sendContextMenuClickToSelection - simulates a press-hold touch input event
michael@0 748 * selected text in a window.
michael@0 749 */
michael@0 750 function sendContextMenuClickToSelection(aWindow) {
michael@0 751 let selection = aWindow.getSelection();
michael@0 752 if (!selection || !selection.rangeCount) {
michael@0 753 ok(false, "no selection to tap!");
michael@0 754 return;
michael@0 755 }
michael@0 756 let range = selection.getRangeAt(0);
michael@0 757 let rect = range.getBoundingClientRect();
michael@0 758 let x = rect.left + (rect.width / 2);
michael@0 759 let y = rect.top + (rect.height / 2);
michael@0 760 let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 761 .getInterface(Components.interfaces.nsIDOMWindowUtils);
michael@0 762 utils.sendMouseEventToWindow("contextmenu", x, y, 2, 1, 0, true,
michael@0 763 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
michael@0 764 }
michael@0 765
michael@0 766 /*
michael@0 767 * sendContextMenuClickToWindow - simulates a press-hold touch input event.
michael@0 768 *
michael@0 769 * @param aWindow window used to retrieve dom window utils, and the
michael@0 770 * target window for the event.
michael@0 771 * @param aX, aY logical coordinates of the event relative to aWindow.
michael@0 772 */
michael@0 773 function sendContextMenuClickToWindow(aWindow, aX, aY) {
michael@0 774 let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 775 .getInterface(Components.interfaces.nsIDOMWindowUtils);
michael@0 776
michael@0 777 utils.sendMouseEventToWindow("contextmenu", aX, aY, 2, 1, 0, true,
michael@0 778 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
michael@0 779 }
michael@0 780
michael@0 781 function sendContextMenuClickToElement(aWindow, aElement, aX, aY) {
michael@0 782 let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 783 .getInterface(Components.interfaces.nsIDOMWindowUtils);
michael@0 784 let coords = logicalCoordsForElement(aElement, aX, aY);
michael@0 785 utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0, true,
michael@0 786 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
michael@0 787 }
michael@0 788
michael@0 789 /*
michael@0 790 * sendDoubleTap - simulates a double click or double tap.
michael@0 791 */
michael@0 792 function sendDoubleTap(aWindow, aX, aY) {
michael@0 793 EventUtils.synthesizeMouseAtPoint(aX, aY, {
michael@0 794 clickCount: 1,
michael@0 795 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
michael@0 796 }, aWindow);
michael@0 797
michael@0 798 EventUtils.synthesizeMouseAtPoint(aX, aY, {
michael@0 799 clickCount: 2,
michael@0 800 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
michael@0 801 }, aWindow);
michael@0 802 }
michael@0 803
michael@0 804 function sendTap(aWindow, aX, aY) {
michael@0 805 EventUtils.synthesizeMouseAtPoint(aX, aY, {
michael@0 806 clickCount: 1,
michael@0 807 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
michael@0 808 }, aWindow);
michael@0 809 }
michael@0 810
michael@0 811 function sendElementTap(aWindow, aElement, aX, aY) {
michael@0 812 let coords = logicalCoordsForElement(aElement, aX, aY);
michael@0 813 EventUtils.synthesizeMouseAtPoint(coords.x, coords.y, {
michael@0 814 clickCount: 1,
michael@0 815 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
michael@0 816 }, aWindow);
michael@0 817 }
michael@0 818
michael@0 819 /*
michael@0 820 * sendTouchDrag - sends a touch series composed of a touchstart,
michael@0 821 * touchmove, and touchend w3c event.
michael@0 822 */
michael@0 823 function sendTouchDrag(aWindow, aStartX, aStartY, aEndX, aEndY) {
michael@0 824 EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
michael@0 825 EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchmove" }, aWindow);
michael@0 826 EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchend" }, aWindow);
michael@0 827 }
michael@0 828
michael@0 829 /*
michael@0 830 * TouchDragAndHold - simulates a drag and hold sequence of events.
michael@0 831 */
michael@0 832 function TouchDragAndHold() {
michael@0 833 }
michael@0 834
michael@0 835 TouchDragAndHold.prototype = {
michael@0 836 _timeoutStep: 2,
michael@0 837 _numSteps: 50,
michael@0 838 _debug: false,
michael@0 839 _win: null,
michael@0 840 _native: false,
michael@0 841 _pointerId: 1,
michael@0 842 _dui: Components.interfaces.nsIDOMWindowUtils,
michael@0 843
michael@0 844 set useNativeEvents(aValue) {
michael@0 845 this._native = aValue;
michael@0 846 },
michael@0 847
michael@0 848 set stepTimeout(aValue) {
michael@0 849 this._timeoutStep = aValue;
michael@0 850 },
michael@0 851
michael@0 852 set numSteps(aValue) {
michael@0 853 this._numSteps = aValue;
michael@0 854 },
michael@0 855
michael@0 856 set nativePointerId(aValue) {
michael@0 857 this._pointerId = aValue;
michael@0 858 },
michael@0 859
michael@0 860 callback: function callback() {
michael@0 861 if (this._win == null)
michael@0 862 return;
michael@0 863
michael@0 864 if (this._debug) {
michael@0 865 SelectionHelperUI.debugDisplayDebugPoint(this._currentPoint.xPos,
michael@0 866 this._currentPoint.yPos, 5, "#FF0000", true);
michael@0 867 }
michael@0 868
michael@0 869 if (++this._step.steps >= this._numSteps) {
michael@0 870 if (this._native) {
michael@0 871 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
michael@0 872 this._endPoint.xPos, this._endPoint.yPos,
michael@0 873 1, 90);
michael@0 874 } else {
michael@0 875 EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
michael@0 876 { type: "touchmove" }, this._win);
michael@0 877 }
michael@0 878 this._defer.resolve();
michael@0 879 return;
michael@0 880 }
michael@0 881 this._currentPoint.xPos += this._step.x;
michael@0 882 this._currentPoint.yPos += this._step.y;
michael@0 883 if (this._debug) {
michael@0 884 info("[" + this._step.steps + "] touchmove " + this._currentPoint.xPos + " x " + this._currentPoint.yPos);
michael@0 885 }
michael@0 886
michael@0 887 if (this._native) {
michael@0 888 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
michael@0 889 this._currentPoint.xPos, this._currentPoint.yPos,
michael@0 890 1, 90);
michael@0 891 } else {
michael@0 892 EventUtils.synthesizeTouchAtPoint(this._currentPoint.xPos, this._currentPoint.yPos,
michael@0 893 { type: "touchmove" }, this._win);
michael@0 894 }
michael@0 895
michael@0 896 let self = this;
michael@0 897 setTimeout(function () { self.callback(); }, this._timeoutStep);
michael@0 898 },
michael@0 899
michael@0 900 start: function start(aWindow, aStartX, aStartY, aEndX, aEndY) {
michael@0 901 this._defer = Promise.defer();
michael@0 902 this._win = aWindow;
michael@0 903 this._utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 904 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 905 this._endPoint = { xPos: aEndX, yPos: aEndY };
michael@0 906 this._currentPoint = { xPos: aStartX, yPos: aStartY };
michael@0 907 this._step = { steps: 0, x: (aEndX - aStartX) / this._numSteps, y: (aEndY - aStartY) / this._numSteps };
michael@0 908 if (this._debug) {
michael@0 909 info("[0] touchstart " + aStartX + " x " + aStartY);
michael@0 910 }
michael@0 911 // flush layout, bug 914847
michael@0 912 this._utils.elementFromPoint(aStartX, aStartY, false, true);
michael@0 913 if (this._native) {
michael@0 914 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
michael@0 915 aStartX, aStartY, 1, 90);
michael@0 916 } else {
michael@0 917 EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
michael@0 918 }
michael@0 919 let self = this;
michael@0 920 setTimeout(function () { self.callback(); }, this._timeoutStep);
michael@0 921 return this._defer.promise;
michael@0 922 },
michael@0 923
michael@0 924 move: function move(aEndX, aEndY) {
michael@0 925 if (this._win == null)
michael@0 926 return;
michael@0 927 if (this._debug) {
michael@0 928 info("[0] continuation to " + aEndX + " x " + aEndY);
michael@0 929 }
michael@0 930 this._defer = Promise.defer();
michael@0 931 this._step = { steps: 0,
michael@0 932 x: (aEndX - this._endPoint.xPos) / this._numSteps,
michael@0 933 y: (aEndY - this._endPoint.yPos) / this._numSteps };
michael@0 934 this._endPoint = { xPos: aEndX, yPos: aEndY };
michael@0 935 let self = this;
michael@0 936 setTimeout(function () { self.callback(); }, this._timeoutStep);
michael@0 937 return this._defer.promise;
michael@0 938 },
michael@0 939
michael@0 940 end: function end() {
michael@0 941 if (this._debug) {
michael@0 942 info("[" + this._step.steps + "] touchend " + this._endPoint.xPos + " x " + this._endPoint.yPos);
michael@0 943 SelectionHelperUI.debugClearDebugPoints();
michael@0 944 }
michael@0 945 if (this._native) {
michael@0 946 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_REMOVE,
michael@0 947 this._endPoint.xPos, this._endPoint.yPos,
michael@0 948 1, 90);
michael@0 949 } else {
michael@0 950 EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
michael@0 951 { type: "touchend" }, this._win);
michael@0 952 }
michael@0 953 this._win = null;
michael@0 954 },
michael@0 955 };
michael@0 956
michael@0 957 /*=============================================================================
michael@0 958 System utilities
michael@0 959 =============================================================================*/
michael@0 960
michael@0 961 /*
michael@0 962 * emptyClipboard - clear the windows clipboard.
michael@0 963 */
michael@0 964 function emptyClipboard() {
michael@0 965 Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard)
michael@0 966 .emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
michael@0 967 }
michael@0 968
michael@0 969 /*
michael@0 970 * purgeEventQueue - purges the event queue on the calling thread.
michael@0 971 * Pumps latent in-process message manager events awaiting delivery.
michael@0 972 */
michael@0 973 function purgeEventQueue() {
michael@0 974 let thread = Services.tm.currentThread;
michael@0 975 while (thread.hasPendingEvents()) {
michael@0 976 if (!thread.processNextEvent(true))
michael@0 977 break;
michael@0 978 }
michael@0 979 }
michael@0 980
michael@0 981 /*=============================================================================
michael@0 982 Test-running helpers
michael@0 983 =============================================================================*/
michael@0 984 let gCurrentTest = null;
michael@0 985 let gTests = [];
michael@0 986
michael@0 987 function runTests() {
michael@0 988 waitForExplicitFinish();
michael@0 989
michael@0 990 Task.spawn(function() {
michael@0 991 while((gCurrentTest = gTests.shift())){
michael@0 992 try {
michael@0 993 if ('function' == typeof gCurrentTest.setUp) {
michael@0 994 info("SETUP " + gCurrentTest.desc);
michael@0 995 yield Task.spawn(gCurrentTest.setUp.bind(gCurrentTest));
michael@0 996 }
michael@0 997 try {
michael@0 998 info("RUN " + gCurrentTest.desc);
michael@0 999 yield Task.spawn(gCurrentTest.run.bind(gCurrentTest));
michael@0 1000 } finally {
michael@0 1001 if ('function' == typeof gCurrentTest.tearDown) {
michael@0 1002 info("TEARDOWN " + gCurrentTest.desc);
michael@0 1003 yield Task.spawn(gCurrentTest.tearDown.bind(gCurrentTest));
michael@0 1004 }
michael@0 1005 }
michael@0 1006 } catch (ex) {
michael@0 1007 ok(false, "runTests: Task failed - " + ex + ' at ' + ex.stack);
michael@0 1008 } finally {
michael@0 1009 info("END " + gCurrentTest.desc);
michael@0 1010 }
michael@0 1011 }
michael@0 1012
michael@0 1013 try {
michael@0 1014 cleanUpOpenedTabs();
michael@0 1015
michael@0 1016 let badTabs = [];
michael@0 1017 Browser.tabs.forEach(function(item, index, array) {
michael@0 1018 let location = item.browser.currentURI.spec;
michael@0 1019 if (index == 0 && location == "about:blank" || location == "about:start") {
michael@0 1020 return;
michael@0 1021 }
michael@0 1022 ok(false, "Left over tab after test: '" + location + "'");
michael@0 1023 badTabs.push(item);
michael@0 1024 });
michael@0 1025
michael@0 1026 badTabs.forEach(function(item, index, array) {
michael@0 1027 Browser.closeTab(item, { forceClose: true });
michael@0 1028 });
michael@0 1029 } catch (ex) {
michael@0 1030 ok(false, "Cleanup tabs failed - " + ex);
michael@0 1031 }
michael@0 1032
michael@0 1033 finish();
michael@0 1034 });
michael@0 1035 }
michael@0 1036
michael@0 1037 // wrap a method with a spy that records how and how many times it gets called
michael@0 1038 // the spy is returned; use spy.restore() to put the original back
michael@0 1039 function spyOnMethod(aObj, aMethod) {
michael@0 1040 let origFunc = aObj[aMethod];
michael@0 1041 let spy = function() {
michael@0 1042 let callArguments = Array.slice(arguments);
michael@0 1043 spy.callCount++;
michael@0 1044 spy.calledWith = callArguments;
michael@0 1045 spy.argsForCall.push(callArguments);
michael@0 1046 return (spy.returnValue = origFunc.apply(aObj, arguments));
michael@0 1047 };
michael@0 1048 spy.callCount = 0;
michael@0 1049 spy.argsForCall = [];
michael@0 1050 spy.restore = function() {
michael@0 1051 return (aObj[aMethod] = origFunc);
michael@0 1052 };
michael@0 1053 return (aObj[aMethod] = spy);
michael@0 1054 }
michael@0 1055
michael@0 1056 // replace a method with a stub that records how and how many times it gets called
michael@0 1057 // the stub is returned; use stub.restore() to put the original back
michael@0 1058 function stubMethod(aObj, aMethod) {
michael@0 1059 let origFunc = aObj[aMethod];
michael@0 1060 let func = function() {
michael@0 1061 func.calledWith = Array.slice(arguments);
michael@0 1062 func.callCount++;
michael@0 1063 };
michael@0 1064 func.callCount = 0;
michael@0 1065 func.restore = function() {
michael@0 1066 return (aObj[aMethod] = origFunc);
michael@0 1067 };
michael@0 1068 aObj[aMethod] = func;
michael@0 1069 return func;
michael@0 1070 }

mercurial