toolkit/components/thumbnails/test/head.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* Any copyright is dedicated to the Public Domain.
     2    http://creativecommons.org/publicdomain/zero/1.0/ */
     4 let tmp = {};
     5 Cu.import("resource://gre/modules/PageThumbs.jsm", tmp);
     6 Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", tmp);
     7 Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
     8 Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
     9 Cu.import("resource://gre/modules/FileUtils.jsm", tmp);
    10 Cu.import("resource://gre/modules/osfile.jsm", tmp);
    11 let {PageThumbs, BackgroundPageThumbs, NewTabUtils, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
    13 Cu.import("resource://gre/modules/PlacesUtils.jsm");
    15 let oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
    16 Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
    18 registerCleanupFunction(function () {
    19   while (gBrowser.tabs.length > 1)
    20     gBrowser.removeTab(gBrowser.tabs[1]);
    21   Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref)
    22 });
    24 /**
    25  * Provide the default test function to start our test runner.
    26  */
    27 function test() {
    28   TestRunner.run();
    29 }
    31 /**
    32  * The test runner that controls the execution flow of our tests.
    33  */
    34 let TestRunner = {
    35   /**
    36    * Starts the test runner.
    37    */
    38   run: function () {
    39     waitForExplicitFinish();
    41     SessionStore.promiseInitialized.then(function () {
    42       this._iter = runTests();
    43       if (this._iter) {
    44         this.next();
    45       } else {
    46         finish();
    47       }
    48     }.bind(this));
    49   },
    51   /**
    52    * Runs the next available test or finishes if there's no test left.
    53    * @param aValue This value will be passed to the yielder via the runner's
    54    *               iterator.
    55    */
    56   next: function (aValue) {
    57     try {
    58       let value = TestRunner._iter.send(aValue);
    59       if (value && typeof value.then == "function") {
    60         value.then(result => {
    61           next(result);
    62         }, error => {
    63           ok(false, error + "\n" + error.stack);
    64         });
    65       }
    66     } catch (e if e instanceof StopIteration) {
    67       finish();
    68     }
    69   }
    70 };
    72 /**
    73  * Continues the current test execution.
    74  * @param aValue This value will be passed to the yielder via the runner's
    75  *               iterator.
    76  */
    77 function next(aValue) {
    78   TestRunner.next(aValue);
    79 }
    81 /**
    82  * Creates a new tab with the given URI.
    83  * @param aURI The URI that's loaded in the tab.
    84  * @param aCallback The function to call when the tab has loaded.
    85  */
    86 function addTab(aURI, aCallback) {
    87   let tab = gBrowser.selectedTab = gBrowser.addTab(aURI);
    88   whenLoaded(tab.linkedBrowser, aCallback);
    89 }
    91 /**
    92  * Loads a new URI into the currently selected tab.
    93  * @param aURI The URI to load.
    94  */
    95 function navigateTo(aURI) {
    96   let browser = gBrowser.selectedTab.linkedBrowser;
    97   whenLoaded(browser);
    98   browser.loadURI(aURI);
    99 }
   101 /**
   102  * Continues the current test execution when a load event for the given element
   103  * has been received.
   104  * @param aElement The DOM element to listen on.
   105  * @param aCallback The function to call when the load event was dispatched.
   106  */
   107 function whenLoaded(aElement, aCallback = next) {
   108   aElement.addEventListener("load", function onLoad() {
   109     aElement.removeEventListener("load", onLoad, true);
   110     executeSoon(aCallback);
   111   }, true);
   112 }
   114 /**
   115  * Captures a screenshot for the currently selected tab, stores it in the cache,
   116  * retrieves it from the cache and compares pixel color values.
   117  * @param aRed The red component's intensity.
   118  * @param aGreen The green component's intensity.
   119  * @param aBlue The blue component's intensity.
   120  * @param aMessage The info message to print when comparing the pixel color.
   121  */
   122 function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
   123   let browser = gBrowser.selectedBrowser;
   124   // We'll get oranges if the expiration filter removes the file during the
   125   // test.
   126   dontExpireThumbnailURLs([browser.currentURI.spec]);
   128   // Capture the screenshot.
   129   PageThumbs.captureAndStore(browser, function () {
   130     retrieveImageDataForURL(browser.currentURI.spec, function ([r, g, b]) {
   131       is("" + [r,g,b], "" + [aRed, aGreen, aBlue], aMessage);
   132       next();
   133     });
   134   });
   135 }
   137 /**
   138  * For a given URL, loads the corresponding thumbnail
   139  * to a canvas and passes its image data to the callback.
   140  * @param aURL The url associated with the thumbnail.
   141  * @param aCallback The function to pass the image data to.
   142  */
   143 function retrieveImageDataForURL(aURL, aCallback) {
   144   let width = 100, height = 100;
   145   let thumb = PageThumbs.getThumbnailURL(aURL, width, height);
   146   // create a tab with a chrome:// URL so it can host the thumbnail image.
   147   // Note that we tried creating the element directly in the top-level chrome
   148   // document, but this caused a strange problem:
   149   // * call this with the url of an image.
   150   // * immediately change the image content.
   151   // * call this again with the same url (now holding different content)
   152   // The original image data would be used.  Maybe the img hadn't been
   153   // collected yet and the platform noticed the same URL, so reused the
   154   // content?  Not sure - but this solves the problem.
   155   addTab("chrome://global/content/mozilla.xhtml", () => {
   156     let doc = gBrowser.selectedBrowser.contentDocument;
   157     let htmlns = "http://www.w3.org/1999/xhtml";
   158     let img = doc.createElementNS(htmlns, "img");
   159     img.setAttribute("src", thumb);
   161     whenLoaded(img, function () {
   162       let canvas = document.createElementNS(htmlns, "canvas");
   163       canvas.setAttribute("width", width);
   164       canvas.setAttribute("height", height);
   166       // Draw the image to a canvas and compare the pixel color values.
   167       let ctx = canvas.getContext("2d");
   168       ctx.drawImage(img, 0, 0, width, height);
   169       let result = ctx.getImageData(0, 0, 100, 100).data;
   170       gBrowser.removeTab(gBrowser.selectedTab);
   171       aCallback(result);
   172     });
   173   });
   174 }
   176 /**
   177  * Returns the file of the thumbnail with the given URL.
   178  * @param aURL The URL of the thumbnail.
   179  */
   180 function thumbnailFile(aURL) {
   181   return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
   182 }
   184 /**
   185  * Checks if a thumbnail for the given URL exists.
   186  * @param aURL The url associated to the thumbnail.
   187  */
   188 function thumbnailExists(aURL) {
   189   let file = thumbnailFile(aURL);
   190   return file.exists() && file.fileSize;
   191 }
   193 /**
   194  * Removes the thumbnail for the given URL.
   195  * @param aURL The URL associated with the thumbnail.
   196  */
   197 function removeThumbnail(aURL) {
   198   let file = thumbnailFile(aURL);
   199   file.remove(false);
   200 }
   202 /**
   203  * Asynchronously adds visits to a page, invoking a callback function when done.
   204  *
   205  * @param aPlaceInfo
   206  *        One of the following: a string spec, an nsIURI, an object describing
   207  *        the Place as described below, or an array of any such types.  An
   208  *        object describing a Place must look like this:
   209  *          { uri: nsIURI of the page,
   210  *            [optional] transition: one of the TRANSITION_* from
   211  *                       nsINavHistoryService,
   212  *            [optional] title: title of the page,
   213  *            [optional] visitDate: visit date in microseconds from the epoch
   214  *            [optional] referrer: nsIURI of the referrer for this visit
   215  *          }
   216  * @param [optional] aCallback
   217  *        Function to be invoked on completion.
   218  */
   219 function addVisits(aPlaceInfo, aCallback) {
   220   let places = [];
   221   if (aPlaceInfo instanceof Ci.nsIURI) {
   222     places.push({ uri: aPlaceInfo });
   223   }
   224   else if (Array.isArray(aPlaceInfo)) {
   225     places = places.concat(aPlaceInfo);
   226   } else {
   227     places.push(aPlaceInfo)
   228   }
   230   // Create mozIVisitInfo for each entry.
   231   let now = Date.now();
   232   for (let i = 0; i < places.length; i++) {
   233     if (typeof(places[i] == "string")) {
   234       places[i] = { uri: Services.io.newURI(places[i], "", null) };
   235     }
   236     if (!places[i].title) {
   237       places[i].title = "test visit for " + places[i].uri.spec;
   238     }
   239     places[i].visits = [{
   240       transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
   241                                                          : places[i].transition,
   242       visitDate: places[i].visitDate || (now++) * 1000,
   243       referrerURI: places[i].referrer
   244     }];
   245   }
   247   PlacesUtils.asyncHistory.updatePlaces(
   248     places,
   249     {
   250       handleError: function AAV_handleError() {
   251         throw("Unexpected error in adding visit.");
   252       },
   253       handleResult: function () {},
   254       handleCompletion: function UP_handleCompletion() {
   255         if (aCallback)
   256           aCallback();
   257       }
   258     }
   259   );
   260 }
   262 /**
   263  * Calls addVisits, and then forces the newtab module to repopulate its links.
   264  * See addVisits for parameter descriptions.
   265  */
   266 function addVisitsAndRepopulateNewTabLinks(aPlaceInfo, aCallback) {
   267   addVisits(aPlaceInfo, () => NewTabUtils.links.populateCache(aCallback, true));
   268 }
   270 /**
   271  * Calls a given callback when the thumbnail for a given URL has been found
   272  * on disk. Keeps trying until the thumbnail has been created.
   273  *
   274  * @param aURL The URL of the thumbnail's page.
   275  * @param [optional] aCallback
   276  *        Function to be invoked on completion.
   277  */
   278 function whenFileExists(aURL, aCallback = next) {
   279   let callback = aCallback;
   280   if (!thumbnailExists(aURL)) {
   281     callback = function () whenFileExists(aURL, aCallback);
   282   }
   284   executeSoon(callback);
   285 }
   287 /**
   288  * Calls a given callback when the given file has been removed.
   289  * Keeps trying until the file is removed.
   290  *
   291  * @param aFile The file that is being removed
   292  * @param [optional] aCallback
   293  *        Function to be invoked on completion.
   294  */
   295 function whenFileRemoved(aFile, aCallback) {
   296   let callback = aCallback;
   297   if (aFile.exists()) {
   298     callback = function () whenFileRemoved(aFile, aCallback);
   299   }
   301   executeSoon(callback || next);
   302 }
   304 function wait(aMillis) {
   305   setTimeout(next, aMillis);
   306 }
   308 /**
   309  * Makes sure that a given list of URLs is not implicitly expired.
   310  *
   311  * @param aURLs The list of URLs that should not be expired.
   312  */
   313 function dontExpireThumbnailURLs(aURLs) {
   314   let dontExpireURLs = (cb) => cb(aURLs);
   315   PageThumbs.addExpirationFilter(dontExpireURLs);
   317   registerCleanupFunction(function () {
   318     PageThumbs.removeExpirationFilter(dontExpireURLs);
   319   });
   320 }
   322 function bgCapture(aURL, aOptions) {
   323   bgCaptureWithMethod("capture", aURL, aOptions);
   324 }
   326 function bgCaptureIfMissing(aURL, aOptions) {
   327   bgCaptureWithMethod("captureIfMissing", aURL, aOptions);
   328 }
   330 function bgCaptureWithMethod(aMethodName, aURL, aOptions = {}) {
   331   // We'll get oranges if the expiration filter removes the file during the
   332   // test.
   333   dontExpireThumbnailURLs([aURL]);
   334   if (!aOptions.onDone)
   335     aOptions.onDone = next;
   336   BackgroundPageThumbs[aMethodName](aURL, aOptions);
   337 }
   339 function bgTestPageURL(aOpts = {}) {
   340   let TEST_PAGE_URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_background.sjs";
   341   return TEST_PAGE_URL + "?" + encodeURIComponent(JSON.stringify(aOpts));
   342 }
   344 function bgAddCrashObserver() {
   345   let crashed = false;
   346   Services.obs.addObserver(function crashObserver(subject, topic, data) {
   347     is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
   348     ok(subject instanceof Components.interfaces.nsIPropertyBag2,
   349        'Subject implements nsIPropertyBag2.');
   350     // we might see this called as the process terminates due to previous tests.
   351     // We are only looking for "abnormal" exits...
   352     if (!subject.hasKey("abnormal")) {
   353       info("This is a normal termination and isn't the one we are looking for...");
   354       return;
   355     }
   356     Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
   357     crashed = true;
   359     var dumpID;
   360     if ('nsICrashReporter' in Components.interfaces) {
   361       dumpID = subject.getPropertyAsAString('dumpID');
   362       ok(dumpID, "dumpID is present and not an empty string");
   363     }
   365     if (dumpID) {
   366       var minidumpDirectory = getMinidumpDirectory();
   367       removeFile(minidumpDirectory, dumpID + '.dmp');
   368       removeFile(minidumpDirectory, dumpID + '.extra');
   369     }
   370   }, 'ipc:content-shutdown', false);
   371   return {
   372     get crashed() crashed
   373   };
   374 }
   376 function bgInjectCrashContentScript() {
   377   const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js";
   378   let thumbnailBrowser = BackgroundPageThumbs._thumbBrowser;
   379   let mm = thumbnailBrowser.messageManager;
   380   mm.loadFrameScript(TEST_CONTENT_HELPER, false);
   381   return mm;
   382 }
   384 function getMinidumpDirectory() {
   385   var dir = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile);
   386   dir.append("minidumps");
   387   return dir;
   388 }
   390 function removeFile(directory, filename) {
   391   var file = directory.clone();
   392   file.append(filename);
   393   if (file.exists()) {
   394     file.remove(false);
   395   }
   396 }

mercurial