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

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

mercurial