Wed, 31 Dec 2014 06:09:35 +0100
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 }