browser/base/content/test/newtab/head.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/base/content/test/newtab/head.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,606 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
     1.8 +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource";
     1.9 +
    1.10 +Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
    1.11 +// start with no directory links by default
    1.12 +Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}");
    1.13 +
    1.14 +let tmp = {};
    1.15 +Cu.import("resource://gre/modules/Promise.jsm", tmp);
    1.16 +Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
    1.17 +Cc["@mozilla.org/moz/jssubscript-loader;1"]
    1.18 +  .getService(Ci.mozIJSSubScriptLoader)
    1.19 +  .loadSubScript("chrome://browser/content/sanitize.js", tmp);
    1.20 +let {Promise, NewTabUtils, Sanitizer} = tmp;
    1.21 +
    1.22 +let uri = Services.io.newURI("about:newtab", null, null);
    1.23 +let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
    1.24 +
    1.25 +let isMac = ("nsILocalFileMac" in Ci);
    1.26 +let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
    1.27 +let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
    1.28 +let gWindow = window;
    1.29 +
    1.30 +// The tests assume all three rows of sites are shown, but the window may be too
    1.31 +// short to actually show three rows.  Resize it if necessary.
    1.32 +let requiredInnerHeight =
    1.33 +  40 + 32 + // undo container + bottom margin
    1.34 +  44 + 32 + // search bar + bottom margin
    1.35 +  (3 * (150 + 32)) + // 3 rows * (tile height + title and bottom margin)
    1.36 +  100; // breathing room
    1.37 +
    1.38 +let oldInnerHeight = null;
    1.39 +if (gBrowser.contentWindow.innerHeight < requiredInnerHeight) {
    1.40 +  oldInnerHeight = gBrowser.contentWindow.innerHeight;
    1.41 +  info("Changing browser inner height from " + oldInnerHeight + " to " +
    1.42 +       requiredInnerHeight);
    1.43 +  gBrowser.contentWindow.innerHeight = requiredInnerHeight;
    1.44 +  let screenHeight = {};
    1.45 +  Cc["@mozilla.org/gfx/screenmanager;1"].
    1.46 +    getService(Ci.nsIScreenManager).
    1.47 +    primaryScreen.
    1.48 +    GetAvailRectDisplayPix({}, {}, {}, screenHeight);
    1.49 +  screenHeight = screenHeight.value;
    1.50 +  if (screenHeight < gBrowser.contentWindow.outerHeight) {
    1.51 +    info("Warning: Browser outer height is now " +
    1.52 +         gBrowser.contentWindow.outerHeight + ", which is larger than the " +
    1.53 +         "available screen height, " + screenHeight +
    1.54 +         ". That may cause problems.");
    1.55 +  }
    1.56 +}
    1.57 +
    1.58 +registerCleanupFunction(function () {
    1.59 +  while (gWindow.gBrowser.tabs.length > 1)
    1.60 +    gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
    1.61 +
    1.62 +  if (oldInnerHeight)
    1.63 +    gBrowser.contentWindow.innerHeight = oldInnerHeight;
    1.64 +
    1.65 +  Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
    1.66 +  Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE);
    1.67 +});
    1.68 +
    1.69 +/**
    1.70 + * Provide the default test function to start our test runner.
    1.71 + */
    1.72 +function test() {
    1.73 +  TestRunner.run();
    1.74 +}
    1.75 +
    1.76 +/**
    1.77 + * The test runner that controls the execution flow of our tests.
    1.78 + */
    1.79 +let TestRunner = {
    1.80 +  /**
    1.81 +   * Starts the test runner.
    1.82 +   */
    1.83 +  run: function () {
    1.84 +    waitForExplicitFinish();
    1.85 +
    1.86 +    this._iter = runTests();
    1.87 +    this.next();
    1.88 +  },
    1.89 +
    1.90 +  /**
    1.91 +   * Runs the next available test or finishes if there's no test left.
    1.92 +   */
    1.93 +  next: function () {
    1.94 +    try {
    1.95 +      TestRunner._iter.next();
    1.96 +    } catch (e if e instanceof StopIteration) {
    1.97 +      TestRunner.finish();
    1.98 +    }
    1.99 +  },
   1.100 +
   1.101 +  /**
   1.102 +   * Finishes all tests and cleans up.
   1.103 +   */
   1.104 +  finish: function () {
   1.105 +    function cleanupAndFinish() {
   1.106 +      clearHistory(function () {
   1.107 +        whenPagesUpdated(finish);
   1.108 +        NewTabUtils.restore();
   1.109 +      });
   1.110 +    }
   1.111 +
   1.112 +    let callbacks = NewTabUtils.links._populateCallbacks;
   1.113 +    let numCallbacks = callbacks.length;
   1.114 +
   1.115 +    if (numCallbacks)
   1.116 +      callbacks.splice(0, numCallbacks, cleanupAndFinish);
   1.117 +    else
   1.118 +      cleanupAndFinish();
   1.119 +  }
   1.120 +};
   1.121 +
   1.122 +/**
   1.123 + * Returns the selected tab's content window.
   1.124 + * @return The content window.
   1.125 + */
   1.126 +function getContentWindow() {
   1.127 +  return gWindow.gBrowser.selectedBrowser.contentWindow;
   1.128 +}
   1.129 +
   1.130 +/**
   1.131 + * Returns the selected tab's content document.
   1.132 + * @return The content document.
   1.133 + */
   1.134 +function getContentDocument() {
   1.135 +  return gWindow.gBrowser.selectedBrowser.contentDocument;
   1.136 +}
   1.137 +
   1.138 +/**
   1.139 + * Returns the newtab grid of the selected tab.
   1.140 + * @return The newtab grid.
   1.141 + */
   1.142 +function getGrid() {
   1.143 +  return getContentWindow().gGrid;
   1.144 +}
   1.145 +
   1.146 +/**
   1.147 + * Returns the cell at the given index of the selected tab's newtab grid.
   1.148 + * @param aIndex The cell index.
   1.149 + * @return The newtab cell.
   1.150 + */
   1.151 +function getCell(aIndex) {
   1.152 +  return getGrid().cells[aIndex];
   1.153 +}
   1.154 +
   1.155 +/**
   1.156 + * Allows to provide a list of links that is used to construct the grid.
   1.157 + * @param aLinksPattern the pattern (see below)
   1.158 + *
   1.159 + * Example: setLinks("1,2,3")
   1.160 + * Result: [{url: "http://example.com/#1", title: "site#1"},
   1.161 + *          {url: "http://example.com/#2", title: "site#2"}
   1.162 + *          {url: "http://example.com/#3", title: "site#3"}]
   1.163 + */
   1.164 +function setLinks(aLinks) {
   1.165 +  let links = aLinks;
   1.166 +
   1.167 +  if (typeof links == "string") {
   1.168 +    links = aLinks.split(/\s*,\s*/).map(function (id) {
   1.169 +      return {url: "http://example.com/#" + id, title: "site#" + id};
   1.170 +    });
   1.171 +  }
   1.172 +
   1.173 +  // Call populateCache() once to make sure that all link fetching that is
   1.174 +  // currently in progress has ended. We clear the history, fill it with the
   1.175 +  // given entries and call populateCache() now again to make sure the cache
   1.176 +  // has the desired contents.
   1.177 +  NewTabUtils.links.populateCache(function () {
   1.178 +    clearHistory(function () {
   1.179 +      fillHistory(links, function () {
   1.180 +        NewTabUtils.links.populateCache(function () {
   1.181 +          NewTabUtils.allPages.update();
   1.182 +          TestRunner.next();
   1.183 +        }, true);
   1.184 +      });
   1.185 +    });
   1.186 +  });
   1.187 +}
   1.188 +
   1.189 +function clearHistory(aCallback) {
   1.190 +  Services.obs.addObserver(function observe(aSubject, aTopic, aData) {
   1.191 +    Services.obs.removeObserver(observe, aTopic);
   1.192 +    executeSoon(aCallback);
   1.193 +  }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
   1.194 +
   1.195 +  PlacesUtils.history.removeAllPages();
   1.196 +}
   1.197 +
   1.198 +function fillHistory(aLinks, aCallback) {
   1.199 +  let numLinks = aLinks.length;
   1.200 +  if (!numLinks) {
   1.201 +    if (aCallback)
   1.202 +      executeSoon(aCallback);
   1.203 +    return;
   1.204 +  }
   1.205 +
   1.206 +  let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
   1.207 +
   1.208 +  // Important: To avoid test failures due to clock jitter on Windows XP, call
   1.209 +  // Date.now() once here, not each time through the loop.
   1.210 +  let now = Date.now() * 1000;
   1.211 +
   1.212 +  for (let i = 0; i < aLinks.length; i++) {
   1.213 +    let link = aLinks[i];
   1.214 +    let place = {
   1.215 +      uri: makeURI(link.url),
   1.216 +      title: link.title,
   1.217 +      // Links are secondarily sorted by visit date descending, so decrease the
   1.218 +      // visit date as we progress through the array so that links appear in the
   1.219 +      // grid in the order they're present in the array.
   1.220 +      visits: [{visitDate: now - i, transitionType: transitionLink}]
   1.221 +    };
   1.222 +
   1.223 +    PlacesUtils.asyncHistory.updatePlaces(place, {
   1.224 +      handleError: function () ok(false, "couldn't add visit to history"),
   1.225 +      handleResult: function () {},
   1.226 +      handleCompletion: function () {
   1.227 +        if (--numLinks == 0 && aCallback)
   1.228 +          aCallback();
   1.229 +      }
   1.230 +    });
   1.231 +  }
   1.232 +}
   1.233 +
   1.234 +/**
   1.235 + * Allows to specify the list of pinned links (that have a fixed position in
   1.236 + * the grid.
   1.237 + * @param aLinksPattern the pattern (see below)
   1.238 + *
   1.239 + * Example: setPinnedLinks("3,,1")
   1.240 + * Result: 'http://example.com/#3' is pinned in the first cell. 'http://example.com/#1' is
   1.241 + *         pinned in the third cell.
   1.242 + */
   1.243 +function setPinnedLinks(aLinks) {
   1.244 +  let links = aLinks;
   1.245 +
   1.246 +  if (typeof links == "string") {
   1.247 +    links = aLinks.split(/\s*,\s*/).map(function (id) {
   1.248 +      if (id)
   1.249 +        return {url: "http://example.com/#" + id, title: "site#" + id};
   1.250 +    });
   1.251 +  }
   1.252 +
   1.253 +  let string = Cc["@mozilla.org/supports-string;1"]
   1.254 +                 .createInstance(Ci.nsISupportsString);
   1.255 +  string.data = JSON.stringify(links);
   1.256 +  Services.prefs.setComplexValue("browser.newtabpage.pinned",
   1.257 +                                 Ci.nsISupportsString, string);
   1.258 +
   1.259 +  NewTabUtils.pinnedLinks.resetCache();
   1.260 +  NewTabUtils.allPages.update();
   1.261 +}
   1.262 +
   1.263 +/**
   1.264 + * Restore the grid state.
   1.265 + */
   1.266 +function restore() {
   1.267 +  whenPagesUpdated();
   1.268 +  NewTabUtils.restore();
   1.269 +}
   1.270 +
   1.271 +/**
   1.272 + * Creates a new tab containing 'about:newtab'.
   1.273 + */
   1.274 +function addNewTabPageTab() {
   1.275 +  let tab = gWindow.gBrowser.selectedTab = gWindow.gBrowser.addTab("about:newtab");
   1.276 +  let browser = tab.linkedBrowser;
   1.277 +
   1.278 +  function whenNewTabLoaded() {
   1.279 +    if (NewTabUtils.allPages.enabled) {
   1.280 +      // Continue when the link cache has been populated.
   1.281 +      NewTabUtils.links.populateCache(function () {
   1.282 +        executeSoon(TestRunner.next);
   1.283 +      });
   1.284 +    } else {
   1.285 +      // It's important that we call next() asynchronously.
   1.286 +      // 'yield addNewTabPageTab()' would fail if next() is called
   1.287 +      // synchronously because the iterator is already executing.
   1.288 +      executeSoon(TestRunner.next);
   1.289 +    }
   1.290 +  }
   1.291 +
   1.292 +  // The new tab page might have been preloaded in the background.
   1.293 +  if (browser.contentDocument.readyState == "complete") {
   1.294 +    whenNewTabLoaded();
   1.295 +    return;
   1.296 +  }
   1.297 +
   1.298 +  // Wait for the new tab page to be loaded.
   1.299 +  browser.addEventListener("load", function onLoad() {
   1.300 +    browser.removeEventListener("load", onLoad, true);
   1.301 +    whenNewTabLoaded();
   1.302 +  }, true);
   1.303 +}
   1.304 +
   1.305 +/**
   1.306 + * Compares the current grid arrangement with the given pattern.
   1.307 + * @param the pattern (see below)
   1.308 + * @param the array of sites to compare with (optional)
   1.309 + *
   1.310 + * Example: checkGrid("3p,2,,1p")
   1.311 + * Result: We expect the first cell to contain the pinned site 'http://example.com/#3'.
   1.312 + *         The second cell contains 'http://example.com/#2'. The third cell is empty.
   1.313 + *         The fourth cell contains the pinned site 'http://example.com/#4'.
   1.314 + */
   1.315 +function checkGrid(aSitesPattern, aSites) {
   1.316 +  let length = aSitesPattern.split(",").length;
   1.317 +  let sites = (aSites || getGrid().sites).slice(0, length);
   1.318 +  let current = sites.map(function (aSite) {
   1.319 +    if (!aSite)
   1.320 +      return "";
   1.321 +
   1.322 +    let pinned = aSite.isPinned();
   1.323 +    let pinButton = aSite.node.querySelector(".newtab-control-pin");
   1.324 +    let hasPinnedAttr = pinButton.hasAttribute("pinned");
   1.325 +
   1.326 +    if (pinned != hasPinnedAttr)
   1.327 +      ok(false, "invalid state (site.isPinned() != site[pinned])");
   1.328 +
   1.329 +    return aSite.url.replace(/^http:\/\/example\.com\/#(\d+)$/, "$1") + (pinned ? "p" : "");
   1.330 +  });
   1.331 +
   1.332 +  is(current, aSitesPattern, "grid status = " + aSitesPattern);
   1.333 +}
   1.334 +
   1.335 +/**
   1.336 + * Blocks a site from the grid.
   1.337 + * @param aIndex The cell index.
   1.338 + */
   1.339 +function blockCell(aIndex) {
   1.340 +  whenPagesUpdated();
   1.341 +  getCell(aIndex).site.block();
   1.342 +}
   1.343 +
   1.344 +/**
   1.345 + * Pins a site on a given position.
   1.346 + * @param aIndex The cell index.
   1.347 + * @param aPinIndex The index the defines where the site should be pinned.
   1.348 + */
   1.349 +function pinCell(aIndex, aPinIndex) {
   1.350 +  getCell(aIndex).site.pin(aPinIndex);
   1.351 +}
   1.352 +
   1.353 +/**
   1.354 + * Unpins the given cell's site.
   1.355 + * @param aIndex The cell index.
   1.356 + */
   1.357 +function unpinCell(aIndex) {
   1.358 +  whenPagesUpdated();
   1.359 +  getCell(aIndex).site.unpin();
   1.360 +}
   1.361 +
   1.362 +/**
   1.363 + * Simulates a drag and drop operation.
   1.364 + * @param aSourceIndex The cell index containing the dragged site.
   1.365 + * @param aDestIndex The cell index of the drop target.
   1.366 + */
   1.367 +function simulateDrop(aSourceIndex, aDestIndex) {
   1.368 +  let src = getCell(aSourceIndex).site.node;
   1.369 +  let dest = getCell(aDestIndex).node;
   1.370 +
   1.371 +  // Drop 'src' onto 'dest' and continue testing when all newtab
   1.372 +  // pages have been updated (i.e. the drop operation is completed).
   1.373 +  startAndCompleteDragOperation(src, dest, whenPagesUpdated);
   1.374 +}
   1.375 +
   1.376 +/**
   1.377 + * Simulates a drag and drop operation. Instead of rearranging a site that is
   1.378 + * is already contained in the newtab grid, this is used to simulate dragging
   1.379 + * an external link onto the grid e.g. the text from the URL bar.
   1.380 + * @param aDestIndex The cell index of the drop target.
   1.381 + */
   1.382 +function simulateExternalDrop(aDestIndex) {
   1.383 +  let dest = getCell(aDestIndex).node;
   1.384 +
   1.385 +  // Create an iframe that contains the external link we'll drag.
   1.386 +  createExternalDropIframe().then(iframe => {
   1.387 +    let link = iframe.contentDocument.getElementById("link");
   1.388 +
   1.389 +    // Drop 'link' onto 'dest'.
   1.390 +    startAndCompleteDragOperation(link, dest, () => {
   1.391 +      // Wait until the drop operation is complete
   1.392 +      // and all newtab pages have been updated.
   1.393 +      whenPagesUpdated(() => {
   1.394 +        // Clean up and remove the iframe.
   1.395 +        iframe.remove();
   1.396 +        // Continue testing.
   1.397 +        TestRunner.next();
   1.398 +      });
   1.399 +    });
   1.400 +  });
   1.401 +}
   1.402 +
   1.403 +/**
   1.404 + * Starts and complete a drag-and-drop operation.
   1.405 + * @param aSource The node that is being dragged.
   1.406 + * @param aDest The node we're dragging aSource onto.
   1.407 + * @param aCallback The function that is called when we're done.
   1.408 + */
   1.409 +function startAndCompleteDragOperation(aSource, aDest, aCallback) {
   1.410 +  // Start by pressing the left mouse button.
   1.411 +  synthesizeNativeMouseLDown(aSource);
   1.412 +
   1.413 +  // Move the mouse in 5px steps until the drag operation starts.
   1.414 +  let offset = 0;
   1.415 +  let interval = setInterval(() => {
   1.416 +    synthesizeNativeMouseDrag(aSource, offset += 5);
   1.417 +  }, 10);
   1.418 +
   1.419 +  // When the drag operation has started we'll move
   1.420 +  // the dragged element to its target position.
   1.421 +  aSource.addEventListener("dragstart", function onDragStart() {
   1.422 +    aSource.removeEventListener("dragstart", onDragStart);
   1.423 +    clearInterval(interval);
   1.424 +
   1.425 +    // Place the cursor above the drag target.
   1.426 +    synthesizeNativeMouseMove(aDest);
   1.427 +  });
   1.428 +
   1.429 +  // As soon as the dragged element hovers the target, we'll drop it.
   1.430 +  aDest.addEventListener("dragenter", function onDragEnter() {
   1.431 +    aDest.removeEventListener("dragenter", onDragEnter);
   1.432 +
   1.433 +    // Finish the drop operation.
   1.434 +    synthesizeNativeMouseLUp(aDest);
   1.435 +    aCallback();
   1.436 +  });
   1.437 +}
   1.438 +
   1.439 +/**
   1.440 + * Helper function that creates a temporary iframe in the about:newtab
   1.441 + * document. This will contain a link we can drag to the test the dropping
   1.442 + * of links from external documents.
   1.443 + */
   1.444 +function createExternalDropIframe() {
   1.445 +  const url = "data:text/html;charset=utf-8," +
   1.446 +              "<a id='link' href='http://example.com/%2399'>link</a>";
   1.447 +
   1.448 +  let deferred = Promise.defer();
   1.449 +  let doc = getContentDocument();
   1.450 +  let iframe = doc.createElement("iframe");
   1.451 +  iframe.setAttribute("src", url);
   1.452 +  iframe.style.width = "50px";
   1.453 +  iframe.style.height = "50px";
   1.454 +
   1.455 +  let margin = doc.getElementById("newtab-margin-top");
   1.456 +  margin.appendChild(iframe);
   1.457 +
   1.458 +  iframe.addEventListener("load", function onLoad() {
   1.459 +    iframe.removeEventListener("load", onLoad);
   1.460 +    executeSoon(() => deferred.resolve(iframe));
   1.461 +  });
   1.462 +
   1.463 +  return deferred.promise;
   1.464 +}
   1.465 +
   1.466 +/**
   1.467 + * Fires a synthetic 'mousedown' event on the current about:newtab page.
   1.468 + * @param aElement The element used to determine the cursor position.
   1.469 + */
   1.470 +function synthesizeNativeMouseLDown(aElement) {
   1.471 +  if (isLinux) {
   1.472 +    let win = aElement.ownerDocument.defaultView;
   1.473 +    EventUtils.synthesizeMouseAtCenter(aElement, {type: "mousedown"}, win);
   1.474 +  } else {
   1.475 +    let msg = isWindows ? 2 : 1;
   1.476 +    synthesizeNativeMouseEvent(aElement, msg);
   1.477 +  }
   1.478 +}
   1.479 +
   1.480 +/**
   1.481 + * Fires a synthetic 'mouseup' event on the current about:newtab page.
   1.482 + * @param aElement The element used to determine the cursor position.
   1.483 + */
   1.484 +function synthesizeNativeMouseLUp(aElement) {
   1.485 +  let msg = isWindows ? 4 : (isMac ? 2 : 7);
   1.486 +  synthesizeNativeMouseEvent(aElement, msg);
   1.487 +}
   1.488 +
   1.489 +/**
   1.490 + * Fires a synthetic mouse drag event on the current about:newtab page.
   1.491 + * @param aElement The element used to determine the cursor position.
   1.492 + * @param aOffsetX The left offset that is added to the position.
   1.493 + */
   1.494 +function synthesizeNativeMouseDrag(aElement, aOffsetX) {
   1.495 +  let msg = isMac ? 6 : 1;
   1.496 +  synthesizeNativeMouseEvent(aElement, msg, aOffsetX);
   1.497 +}
   1.498 +
   1.499 +/**
   1.500 + * Fires a synthetic 'mousemove' event on the current about:newtab page.
   1.501 + * @param aElement The element used to determine the cursor position.
   1.502 + */
   1.503 +function synthesizeNativeMouseMove(aElement) {
   1.504 +  let msg = isMac ? 5 : 1;
   1.505 +  synthesizeNativeMouseEvent(aElement, msg);
   1.506 +}
   1.507 +
   1.508 +/**
   1.509 + * Fires a synthetic mouse event on the current about:newtab page.
   1.510 + * @param aElement The element used to determine the cursor position.
   1.511 + * @param aOffsetX The left offset that is added to the position (optional).
   1.512 + * @param aOffsetY The top offset that is added to the position (optional).
   1.513 + */
   1.514 +function synthesizeNativeMouseEvent(aElement, aMsg, aOffsetX = 0, aOffsetY = 0) {
   1.515 +  let rect = aElement.getBoundingClientRect();
   1.516 +  let win = aElement.ownerDocument.defaultView;
   1.517 +  let x = aOffsetX + win.mozInnerScreenX + rect.left + rect.width / 2;
   1.518 +  let y = aOffsetY + win.mozInnerScreenY + rect.top + rect.height / 2;
   1.519 +
   1.520 +  let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.521 +                 .getInterface(Ci.nsIDOMWindowUtils);
   1.522 +
   1.523 +  let scale = utils.screenPixelsPerCSSPixel;
   1.524 +  utils.sendNativeMouseEvent(x * scale, y * scale, aMsg, 0, null);
   1.525 +}
   1.526 +
   1.527 +/**
   1.528 + * Sends a custom drag event to a given DOM element.
   1.529 + * @param aEventType The drag event's type.
   1.530 + * @param aTarget The DOM element that the event is dispatched to.
   1.531 + * @param aData The event's drag data (optional).
   1.532 + */
   1.533 +function sendDragEvent(aEventType, aTarget, aData) {
   1.534 +  let event = createDragEvent(aEventType, aData);
   1.535 +  let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
   1.536 +  let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
   1.537 +  windowUtils.dispatchDOMEventViaPresShell(aTarget, event, true);
   1.538 +}
   1.539 +
   1.540 +/**
   1.541 + * Creates a custom drag event.
   1.542 + * @param aEventType The drag event's type.
   1.543 + * @param aData The event's drag data (optional).
   1.544 + * @return The drag event.
   1.545 + */
   1.546 +function createDragEvent(aEventType, aData) {
   1.547 +  let dataTransfer = new (getContentWindow()).DataTransfer("dragstart", false);
   1.548 +  dataTransfer.mozSetDataAt("text/x-moz-url", aData, 0);
   1.549 +  let event = getContentDocument().createEvent("DragEvents");
   1.550 +  event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
   1.551 +                      false, false, false, false, 0, null, dataTransfer);
   1.552 +
   1.553 +  return event;
   1.554 +}
   1.555 +
   1.556 +/**
   1.557 + * Resumes testing when all pages have been updated.
   1.558 + * @param aCallback Called when done. If not specified, TestRunner.next is used.
   1.559 + * @param aOnlyIfHidden If true, this resumes testing only when an update that
   1.560 + *                      applies to pre-loaded, hidden pages is observed.  If
   1.561 + *                      false, this resumes testing when any update is observed.
   1.562 + */
   1.563 +function whenPagesUpdated(aCallback, aOnlyIfHidden=false) {
   1.564 +  let page = {
   1.565 +    update: function (onlyIfHidden=false) {
   1.566 +      if (onlyIfHidden == aOnlyIfHidden) {
   1.567 +        NewTabUtils.allPages.unregister(this);
   1.568 +        executeSoon(aCallback || TestRunner.next);
   1.569 +      }
   1.570 +    }
   1.571 +  };
   1.572 +
   1.573 +  NewTabUtils.allPages.register(page);
   1.574 +  registerCleanupFunction(function () {
   1.575 +    NewTabUtils.allPages.unregister(page);
   1.576 +  });
   1.577 +}
   1.578 +
   1.579 +/**
   1.580 + * Waits a small amount of time for search events to stop occurring in the
   1.581 + * newtab page.
   1.582 + *
   1.583 + * newtab pages receive some search events around load time that are difficult
   1.584 + * to predict.  There are two categories of such events: (1) "State" events
   1.585 + * triggered by engine notifications like engine-changed, due to the search
   1.586 + * service initializing itself on app startup.  This can happen when a test is
   1.587 + * the first test to run.  (2) "State" events triggered by the newtab page
   1.588 + * itself when gSearch first sets itself up.  newtab preloading makes these a
   1.589 + * pain to predict.
   1.590 + */
   1.591 +function whenSearchInitDone() {
   1.592 +  info("Waiting for initial search events...");
   1.593 +  let numTicks = 0;
   1.594 +  function reset(event) {
   1.595 +    info("Got initial search event " + event.detail.type +
   1.596 +         ", waiting for more...");
   1.597 +    numTicks = 0;
   1.598 +  }
   1.599 +  let eventName = "ContentSearchService";
   1.600 +  getContentWindow().addEventListener(eventName, reset);
   1.601 +  let interval = window.setInterval(() => {
   1.602 +    if (++numTicks >= 100) {
   1.603 +      info("Done waiting for initial search events");
   1.604 +      window.clearInterval(interval);
   1.605 +      getContentWindow().removeEventListener(eventName, reset);
   1.606 +      TestRunner.next();
   1.607 +    }
   1.608 +  }, 0);
   1.609 +}

mercurial