toolkit/components/thumbnails/test/head.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/thumbnails/test/head.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,396 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +let tmp = {};
     1.8 +Cu.import("resource://gre/modules/PageThumbs.jsm", tmp);
     1.9 +Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", tmp);
    1.10 +Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
    1.11 +Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
    1.12 +Cu.import("resource://gre/modules/FileUtils.jsm", tmp);
    1.13 +Cu.import("resource://gre/modules/osfile.jsm", tmp);
    1.14 +let {PageThumbs, BackgroundPageThumbs, NewTabUtils, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
    1.15 +
    1.16 +Cu.import("resource://gre/modules/PlacesUtils.jsm");
    1.17 +
    1.18 +let oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
    1.19 +Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
    1.20 +
    1.21 +registerCleanupFunction(function () {
    1.22 +  while (gBrowser.tabs.length > 1)
    1.23 +    gBrowser.removeTab(gBrowser.tabs[1]);
    1.24 +  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref)
    1.25 +});
    1.26 +
    1.27 +/**
    1.28 + * Provide the default test function to start our test runner.
    1.29 + */
    1.30 +function test() {
    1.31 +  TestRunner.run();
    1.32 +}
    1.33 +
    1.34 +/**
    1.35 + * The test runner that controls the execution flow of our tests.
    1.36 + */
    1.37 +let TestRunner = {
    1.38 +  /**
    1.39 +   * Starts the test runner.
    1.40 +   */
    1.41 +  run: function () {
    1.42 +    waitForExplicitFinish();
    1.43 +
    1.44 +    SessionStore.promiseInitialized.then(function () {
    1.45 +      this._iter = runTests();
    1.46 +      if (this._iter) {
    1.47 +        this.next();
    1.48 +      } else {
    1.49 +        finish();
    1.50 +      }
    1.51 +    }.bind(this));
    1.52 +  },
    1.53 +
    1.54 +  /**
    1.55 +   * Runs the next available test or finishes if there's no test left.
    1.56 +   * @param aValue This value will be passed to the yielder via the runner's
    1.57 +   *               iterator.
    1.58 +   */
    1.59 +  next: function (aValue) {
    1.60 +    try {
    1.61 +      let value = TestRunner._iter.send(aValue);
    1.62 +      if (value && typeof value.then == "function") {
    1.63 +        value.then(result => {
    1.64 +          next(result);
    1.65 +        }, error => {
    1.66 +          ok(false, error + "\n" + error.stack);
    1.67 +        });
    1.68 +      }
    1.69 +    } catch (e if e instanceof StopIteration) {
    1.70 +      finish();
    1.71 +    }
    1.72 +  }
    1.73 +};
    1.74 +
    1.75 +/**
    1.76 + * Continues the current test execution.
    1.77 + * @param aValue This value will be passed to the yielder via the runner's
    1.78 + *               iterator.
    1.79 + */
    1.80 +function next(aValue) {
    1.81 +  TestRunner.next(aValue);
    1.82 +}
    1.83 +
    1.84 +/**
    1.85 + * Creates a new tab with the given URI.
    1.86 + * @param aURI The URI that's loaded in the tab.
    1.87 + * @param aCallback The function to call when the tab has loaded.
    1.88 + */
    1.89 +function addTab(aURI, aCallback) {
    1.90 +  let tab = gBrowser.selectedTab = gBrowser.addTab(aURI);
    1.91 +  whenLoaded(tab.linkedBrowser, aCallback);
    1.92 +}
    1.93 +
    1.94 +/**
    1.95 + * Loads a new URI into the currently selected tab.
    1.96 + * @param aURI The URI to load.
    1.97 + */
    1.98 +function navigateTo(aURI) {
    1.99 +  let browser = gBrowser.selectedTab.linkedBrowser;
   1.100 +  whenLoaded(browser);
   1.101 +  browser.loadURI(aURI);
   1.102 +}
   1.103 +
   1.104 +/**
   1.105 + * Continues the current test execution when a load event for the given element
   1.106 + * has been received.
   1.107 + * @param aElement The DOM element to listen on.
   1.108 + * @param aCallback The function to call when the load event was dispatched.
   1.109 + */
   1.110 +function whenLoaded(aElement, aCallback = next) {
   1.111 +  aElement.addEventListener("load", function onLoad() {
   1.112 +    aElement.removeEventListener("load", onLoad, true);
   1.113 +    executeSoon(aCallback);
   1.114 +  }, true);
   1.115 +}
   1.116 +
   1.117 +/**
   1.118 + * Captures a screenshot for the currently selected tab, stores it in the cache,
   1.119 + * retrieves it from the cache and compares pixel color values.
   1.120 + * @param aRed The red component's intensity.
   1.121 + * @param aGreen The green component's intensity.
   1.122 + * @param aBlue The blue component's intensity.
   1.123 + * @param aMessage The info message to print when comparing the pixel color.
   1.124 + */
   1.125 +function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
   1.126 +  let browser = gBrowser.selectedBrowser;
   1.127 +  // We'll get oranges if the expiration filter removes the file during the
   1.128 +  // test.
   1.129 +  dontExpireThumbnailURLs([browser.currentURI.spec]);
   1.130 +
   1.131 +  // Capture the screenshot.
   1.132 +  PageThumbs.captureAndStore(browser, function () {
   1.133 +    retrieveImageDataForURL(browser.currentURI.spec, function ([r, g, b]) {
   1.134 +      is("" + [r,g,b], "" + [aRed, aGreen, aBlue], aMessage);
   1.135 +      next();
   1.136 +    });
   1.137 +  });
   1.138 +}
   1.139 +
   1.140 +/**
   1.141 + * For a given URL, loads the corresponding thumbnail
   1.142 + * to a canvas and passes its image data to the callback.
   1.143 + * @param aURL The url associated with the thumbnail.
   1.144 + * @param aCallback The function to pass the image data to.
   1.145 + */
   1.146 +function retrieveImageDataForURL(aURL, aCallback) {
   1.147 +  let width = 100, height = 100;
   1.148 +  let thumb = PageThumbs.getThumbnailURL(aURL, width, height);
   1.149 +  // create a tab with a chrome:// URL so it can host the thumbnail image.
   1.150 +  // Note that we tried creating the element directly in the top-level chrome
   1.151 +  // document, but this caused a strange problem:
   1.152 +  // * call this with the url of an image.
   1.153 +  // * immediately change the image content.
   1.154 +  // * call this again with the same url (now holding different content)
   1.155 +  // The original image data would be used.  Maybe the img hadn't been
   1.156 +  // collected yet and the platform noticed the same URL, so reused the
   1.157 +  // content?  Not sure - but this solves the problem.
   1.158 +  addTab("chrome://global/content/mozilla.xhtml", () => {
   1.159 +    let doc = gBrowser.selectedBrowser.contentDocument;
   1.160 +    let htmlns = "http://www.w3.org/1999/xhtml";
   1.161 +    let img = doc.createElementNS(htmlns, "img");
   1.162 +    img.setAttribute("src", thumb);
   1.163 +
   1.164 +    whenLoaded(img, function () {
   1.165 +      let canvas = document.createElementNS(htmlns, "canvas");
   1.166 +      canvas.setAttribute("width", width);
   1.167 +      canvas.setAttribute("height", height);
   1.168 +
   1.169 +      // Draw the image to a canvas and compare the pixel color values.
   1.170 +      let ctx = canvas.getContext("2d");
   1.171 +      ctx.drawImage(img, 0, 0, width, height);
   1.172 +      let result = ctx.getImageData(0, 0, 100, 100).data;
   1.173 +      gBrowser.removeTab(gBrowser.selectedTab);
   1.174 +      aCallback(result);
   1.175 +    });
   1.176 +  });
   1.177 +}
   1.178 +
   1.179 +/**
   1.180 + * Returns the file of the thumbnail with the given URL.
   1.181 + * @param aURL The URL of the thumbnail.
   1.182 + */
   1.183 +function thumbnailFile(aURL) {
   1.184 +  return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
   1.185 +}
   1.186 +
   1.187 +/**
   1.188 + * Checks if a thumbnail for the given URL exists.
   1.189 + * @param aURL The url associated to the thumbnail.
   1.190 + */
   1.191 +function thumbnailExists(aURL) {
   1.192 +  let file = thumbnailFile(aURL);
   1.193 +  return file.exists() && file.fileSize;
   1.194 +}
   1.195 +
   1.196 +/**
   1.197 + * Removes the thumbnail for the given URL.
   1.198 + * @param aURL The URL associated with the thumbnail.
   1.199 + */
   1.200 +function removeThumbnail(aURL) {
   1.201 +  let file = thumbnailFile(aURL);
   1.202 +  file.remove(false);
   1.203 +}
   1.204 +
   1.205 +/**
   1.206 + * Asynchronously adds visits to a page, invoking a callback function when done.
   1.207 + *
   1.208 + * @param aPlaceInfo
   1.209 + *        One of the following: a string spec, an nsIURI, an object describing
   1.210 + *        the Place as described below, or an array of any such types.  An
   1.211 + *        object describing a Place must look like this:
   1.212 + *          { uri: nsIURI of the page,
   1.213 + *            [optional] transition: one of the TRANSITION_* from
   1.214 + *                       nsINavHistoryService,
   1.215 + *            [optional] title: title of the page,
   1.216 + *            [optional] visitDate: visit date in microseconds from the epoch
   1.217 + *            [optional] referrer: nsIURI of the referrer for this visit
   1.218 + *          }
   1.219 + * @param [optional] aCallback
   1.220 + *        Function to be invoked on completion.
   1.221 + */
   1.222 +function addVisits(aPlaceInfo, aCallback) {
   1.223 +  let places = [];
   1.224 +  if (aPlaceInfo instanceof Ci.nsIURI) {
   1.225 +    places.push({ uri: aPlaceInfo });
   1.226 +  }
   1.227 +  else if (Array.isArray(aPlaceInfo)) {
   1.228 +    places = places.concat(aPlaceInfo);
   1.229 +  } else {
   1.230 +    places.push(aPlaceInfo)
   1.231 +  }
   1.232 +
   1.233 +  // Create mozIVisitInfo for each entry.
   1.234 +  let now = Date.now();
   1.235 +  for (let i = 0; i < places.length; i++) {
   1.236 +    if (typeof(places[i] == "string")) {
   1.237 +      places[i] = { uri: Services.io.newURI(places[i], "", null) };
   1.238 +    }
   1.239 +    if (!places[i].title) {
   1.240 +      places[i].title = "test visit for " + places[i].uri.spec;
   1.241 +    }
   1.242 +    places[i].visits = [{
   1.243 +      transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
   1.244 +                                                         : places[i].transition,
   1.245 +      visitDate: places[i].visitDate || (now++) * 1000,
   1.246 +      referrerURI: places[i].referrer
   1.247 +    }];
   1.248 +  }
   1.249 +
   1.250 +  PlacesUtils.asyncHistory.updatePlaces(
   1.251 +    places,
   1.252 +    {
   1.253 +      handleError: function AAV_handleError() {
   1.254 +        throw("Unexpected error in adding visit.");
   1.255 +      },
   1.256 +      handleResult: function () {},
   1.257 +      handleCompletion: function UP_handleCompletion() {
   1.258 +        if (aCallback)
   1.259 +          aCallback();
   1.260 +      }
   1.261 +    }
   1.262 +  );
   1.263 +}
   1.264 +
   1.265 +/**
   1.266 + * Calls addVisits, and then forces the newtab module to repopulate its links.
   1.267 + * See addVisits for parameter descriptions.
   1.268 + */
   1.269 +function addVisitsAndRepopulateNewTabLinks(aPlaceInfo, aCallback) {
   1.270 +  addVisits(aPlaceInfo, () => NewTabUtils.links.populateCache(aCallback, true));
   1.271 +}
   1.272 +
   1.273 +/**
   1.274 + * Calls a given callback when the thumbnail for a given URL has been found
   1.275 + * on disk. Keeps trying until the thumbnail has been created.
   1.276 + *
   1.277 + * @param aURL The URL of the thumbnail's page.
   1.278 + * @param [optional] aCallback
   1.279 + *        Function to be invoked on completion.
   1.280 + */
   1.281 +function whenFileExists(aURL, aCallback = next) {
   1.282 +  let callback = aCallback;
   1.283 +  if (!thumbnailExists(aURL)) {
   1.284 +    callback = function () whenFileExists(aURL, aCallback);
   1.285 +  }
   1.286 +
   1.287 +  executeSoon(callback);
   1.288 +}
   1.289 +
   1.290 +/**
   1.291 + * Calls a given callback when the given file has been removed.
   1.292 + * Keeps trying until the file is removed.
   1.293 + *
   1.294 + * @param aFile The file that is being removed
   1.295 + * @param [optional] aCallback
   1.296 + *        Function to be invoked on completion.
   1.297 + */
   1.298 +function whenFileRemoved(aFile, aCallback) {
   1.299 +  let callback = aCallback;
   1.300 +  if (aFile.exists()) {
   1.301 +    callback = function () whenFileRemoved(aFile, aCallback);
   1.302 +  }
   1.303 +
   1.304 +  executeSoon(callback || next);
   1.305 +}
   1.306 +
   1.307 +function wait(aMillis) {
   1.308 +  setTimeout(next, aMillis);
   1.309 +}
   1.310 +
   1.311 +/**
   1.312 + * Makes sure that a given list of URLs is not implicitly expired.
   1.313 + *
   1.314 + * @param aURLs The list of URLs that should not be expired.
   1.315 + */
   1.316 +function dontExpireThumbnailURLs(aURLs) {
   1.317 +  let dontExpireURLs = (cb) => cb(aURLs);
   1.318 +  PageThumbs.addExpirationFilter(dontExpireURLs);
   1.319 +
   1.320 +  registerCleanupFunction(function () {
   1.321 +    PageThumbs.removeExpirationFilter(dontExpireURLs);
   1.322 +  });
   1.323 +}
   1.324 +
   1.325 +function bgCapture(aURL, aOptions) {
   1.326 +  bgCaptureWithMethod("capture", aURL, aOptions);
   1.327 +}
   1.328 +
   1.329 +function bgCaptureIfMissing(aURL, aOptions) {
   1.330 +  bgCaptureWithMethod("captureIfMissing", aURL, aOptions);
   1.331 +}
   1.332 +
   1.333 +function bgCaptureWithMethod(aMethodName, aURL, aOptions = {}) {
   1.334 +  // We'll get oranges if the expiration filter removes the file during the
   1.335 +  // test.
   1.336 +  dontExpireThumbnailURLs([aURL]);
   1.337 +  if (!aOptions.onDone)
   1.338 +    aOptions.onDone = next;
   1.339 +  BackgroundPageThumbs[aMethodName](aURL, aOptions);
   1.340 +}
   1.341 +
   1.342 +function bgTestPageURL(aOpts = {}) {
   1.343 +  let TEST_PAGE_URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_background.sjs";
   1.344 +  return TEST_PAGE_URL + "?" + encodeURIComponent(JSON.stringify(aOpts));
   1.345 +}
   1.346 +
   1.347 +function bgAddCrashObserver() {
   1.348 +  let crashed = false;
   1.349 +  Services.obs.addObserver(function crashObserver(subject, topic, data) {
   1.350 +    is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
   1.351 +    ok(subject instanceof Components.interfaces.nsIPropertyBag2,
   1.352 +       'Subject implements nsIPropertyBag2.');
   1.353 +    // we might see this called as the process terminates due to previous tests.
   1.354 +    // We are only looking for "abnormal" exits...
   1.355 +    if (!subject.hasKey("abnormal")) {
   1.356 +      info("This is a normal termination and isn't the one we are looking for...");
   1.357 +      return;
   1.358 +    }
   1.359 +    Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
   1.360 +    crashed = true;
   1.361 +
   1.362 +    var dumpID;
   1.363 +    if ('nsICrashReporter' in Components.interfaces) {
   1.364 +      dumpID = subject.getPropertyAsAString('dumpID');
   1.365 +      ok(dumpID, "dumpID is present and not an empty string");
   1.366 +    }
   1.367 +
   1.368 +    if (dumpID) {
   1.369 +      var minidumpDirectory = getMinidumpDirectory();
   1.370 +      removeFile(minidumpDirectory, dumpID + '.dmp');
   1.371 +      removeFile(minidumpDirectory, dumpID + '.extra');
   1.372 +    }
   1.373 +  }, 'ipc:content-shutdown', false);
   1.374 +  return {
   1.375 +    get crashed() crashed
   1.376 +  };
   1.377 +}
   1.378 +
   1.379 +function bgInjectCrashContentScript() {
   1.380 +  const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js";
   1.381 +  let thumbnailBrowser = BackgroundPageThumbs._thumbBrowser;
   1.382 +  let mm = thumbnailBrowser.messageManager;
   1.383 +  mm.loadFrameScript(TEST_CONTENT_HELPER, false);
   1.384 +  return mm;
   1.385 +}
   1.386 +
   1.387 +function getMinidumpDirectory() {
   1.388 +  var dir = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile);
   1.389 +  dir.append("minidumps");
   1.390 +  return dir;
   1.391 +}
   1.392 +
   1.393 +function removeFile(directory, filename) {
   1.394 +  var file = directory.clone();
   1.395 +  file.append(filename);
   1.396 +  if (file.exists()) {
   1.397 +    file.remove(false);
   1.398 +  }
   1.399 +}

mercurial