summaryrefslogtreecommitdiffstats
path: root/toolkit/components/thumbnails/test
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/thumbnails/test')
-rw-r--r--toolkit/components/thumbnails/test/.eslintrc.js8
-rw-r--r--toolkit/components/thumbnails/test/authenticate.sjs220
-rw-r--r--toolkit/components/thumbnails/test/background_red.html3
-rw-r--r--toolkit/components/thumbnails/test/background_red_redirect.sjs10
-rw-r--r--toolkit/components/thumbnails/test/background_red_scroll.html3
-rw-r--r--toolkit/components/thumbnails/test/browser.ini38
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js23
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js20
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js35
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_during_capture.js49
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js38
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js33
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js13
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js21
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js40
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js32
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js31
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js38
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js39
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js24
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js19
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js29
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js42
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_capture.js20
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_expiration.js97
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_privacy.js74
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_redirect.js30
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_storage.js112
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js102
-rw-r--r--toolkit/components/thumbnails/test/browser_thumbnails_update.js169
-rw-r--r--toolkit/components/thumbnails/test/head.js356
-rw-r--r--toolkit/components/thumbnails/test/privacy_cache_control.sjs16
-rw-r--r--toolkit/components/thumbnails/test/test_thumbnails_interfaces.js31
-rw-r--r--toolkit/components/thumbnails/test/thumbnails_background.sjs79
-rw-r--r--toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js31
-rw-r--r--toolkit/components/thumbnails/test/thumbnails_update.sjs56
-rw-r--r--toolkit/components/thumbnails/test/xpcshell.ini6
37 files changed, 1987 insertions, 0 deletions
diff --git a/toolkit/components/thumbnails/test/.eslintrc.js b/toolkit/components/thumbnails/test/.eslintrc.js
new file mode 100644
index 000000000..f6f8d62c2
--- /dev/null
+++ b/toolkit/components/thumbnails/test/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js",
+ "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/components/thumbnails/test/authenticate.sjs b/toolkit/components/thumbnails/test/authenticate.sjs
new file mode 100644
index 000000000..58da655cf
--- /dev/null
+++ b/toolkit/components/thumbnails/test/authenticate.sjs
@@ -0,0 +1,220 @@
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 200, "AlmostOK");
+ response.write("Error handling request: " + e);
+ }
+}
+
+
+function reallyHandleRequest(request, response) {
+ var match;
+ var requestAuth = true, requestProxyAuth = true;
+
+ // Allow the caller to drive how authentication is processed via the query.
+ // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
+ // The extra ? allows the user/pass/realm checks to succeed if the name is
+ // at the beginning of the query string.
+ var query = "?" + request.queryString;
+
+ var expected_user = "", expected_pass = "", realm = "mochitest";
+ var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
+ var huge = false, plugin = false, anonymous = false;
+ var authHeaderCount = 1;
+ // user=xxx
+ match = /[^_]user=([^&]*)/.exec(query);
+ if (match)
+ expected_user = match[1];
+
+ // pass=xxx
+ match = /[^_]pass=([^&]*)/.exec(query);
+ if (match)
+ expected_pass = match[1];
+
+ // realm=xxx
+ match = /[^_]realm=([^&]*)/.exec(query);
+ if (match)
+ realm = match[1];
+
+ // proxy_user=xxx
+ match = /proxy_user=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_user = match[1];
+
+ // proxy_pass=xxx
+ match = /proxy_pass=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_pass = match[1];
+
+ // proxy_realm=xxx
+ match = /proxy_realm=([^&]*)/.exec(query);
+ if (match)
+ proxy_realm = match[1];
+
+ // huge=1
+ match = /huge=1/.exec(query);
+ if (match)
+ huge = true;
+
+ // plugin=1
+ match = /plugin=1/.exec(query);
+ if (match)
+ plugin = true;
+
+ // multiple=1
+ match = /multiple=([^&]*)/.exec(query);
+ if (match)
+ authHeaderCount = match[1]+0;
+
+ // anonymous=1
+ match = /anonymous=1/.exec(query);
+ if (match)
+ anonymous = true;
+
+ // Look for an authentication header, if any, in the request.
+ //
+ // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ //
+ // This test only supports Basic auth. The value sent by the client is
+ // "username:password", obscured with base64 encoding.
+
+ var actual_user = "", actual_pass = "", authHeader, authPresent = false;
+ if (request.hasHeader("Authorization")) {
+ authPresent = true;
+ authHeader = request.getHeader("Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ actual_user = match[1];
+ actual_pass = match[2];
+ }
+
+ var proxy_actual_user = "", proxy_actual_pass = "";
+ if (request.hasHeader("Proxy-Authorization")) {
+ authHeader = request.getHeader("Proxy-Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ proxy_actual_user = match[1];
+ proxy_actual_pass = match[2];
+ }
+
+ // Don't request authentication if the credentials we got were what we
+ // expected.
+ if (expected_user == actual_user &&
+ expected_pass == actual_pass) {
+ requestAuth = false;
+ }
+ if (proxy_expected_user == proxy_actual_user &&
+ proxy_expected_pass == proxy_actual_pass) {
+ requestProxyAuth = false;
+ }
+
+ if (anonymous) {
+ if (authPresent) {
+ response.setStatusLine("1.0", 400, "Unexpected authorization header found");
+ } else {
+ response.setStatusLine("1.0", 200, "Authorization header not found");
+ }
+ } else {
+ if (requestProxyAuth) {
+ response.setStatusLine("1.0", 407, "Proxy authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
+ } else if (requestAuth) {
+ response.setStatusLine("1.0", 401, "Authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+ } else {
+ response.setStatusLine("1.0", 200, "OK");
+ }
+ }
+
+ response.setHeader("Content-Type", "application/xhtml+xml", false);
+ response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
+ response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
+ response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
+ response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
+
+ if (huge) {
+ response.write("<div style='display: none'>");
+ for (i = 0; i < 100000; i++) {
+ response.write("123456789\n");
+ }
+ response.write("</div>");
+ response.write("<span id='footnote'>This is a footnote after the huge content fill</span>");
+ }
+
+ if (plugin) {
+ response.write("<embed id='embedtest' style='width: 400px; height: 100px;' " +
+ "type='application/x-test'></embed>\n");
+ }
+
+ response.write("</html>");
+}
+
+
+// base64 decoder
+//
+// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa()
+// doesn't seem to exist. :-(
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+
+ var result = '';
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+
+ // Convert one by one.
+ for (var i = 0; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data[i] == base64Pad);
+ // Skip illegal characters and whitespace
+ if (c == -1) continue;
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding)
+ result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits)
+ throw Components.Exception('Corrupted base64 string');
+
+ return result;
+}
diff --git a/toolkit/components/thumbnails/test/background_red.html b/toolkit/components/thumbnails/test/background_red.html
new file mode 100644
index 000000000..95159dd29
--- /dev/null
+++ b/toolkit/components/thumbnails/test/background_red.html
@@ -0,0 +1,3 @@
+<html>
+ <body bgcolor=ff0000></body>
+</html>
diff --git a/toolkit/components/thumbnails/test/background_red_redirect.sjs b/toolkit/components/thumbnails/test/background_red_redirect.sjs
new file mode 100644
index 000000000..5f0852e19
--- /dev/null
+++ b/toolkit/components/thumbnails/test/background_red_redirect.sjs
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status.
+ aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently");
+
+ // Set redirect URI.
+ aResponse.setHeader("Location", "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/background_red.html");
+}
diff --git a/toolkit/components/thumbnails/test/background_red_scroll.html b/toolkit/components/thumbnails/test/background_red_scroll.html
new file mode 100644
index 000000000..1e30bd3c6
--- /dev/null
+++ b/toolkit/components/thumbnails/test/background_red_scroll.html
@@ -0,0 +1,3 @@
+<html>
+ <body bgcolor=ff0000 style="overflow: scroll;"></body>
+</html>
diff --git a/toolkit/components/thumbnails/test/browser.ini b/toolkit/components/thumbnails/test/browser.ini
new file mode 100644
index 000000000..3b87815ff
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser.ini
@@ -0,0 +1,38 @@
+[DEFAULT]
+support-files =
+ authenticate.sjs
+ background_red.html
+ background_red_redirect.sjs
+ background_red_scroll.html
+ head.js
+ privacy_cache_control.sjs
+ thumbnails_background.sjs
+ thumbnails_crash_content_helper.js
+ thumbnails_update.sjs
+
+[browser_thumbnails_bg_bad_url.js]
+[browser_thumbnails_bg_crash_during_capture.js]
+skip-if = !crashreporter
+[browser_thumbnails_bg_crash_while_idle.js]
+skip-if = !crashreporter
+[browser_thumbnails_bg_basic.js]
+[browser_thumbnails_bg_queueing.js]
+[browser_thumbnails_bg_timeout.js]
+[browser_thumbnails_bg_redirect.js]
+[browser_thumbnails_bg_destroy_browser.js]
+[browser_thumbnails_bg_no_cookies_sent.js]
+[browser_thumbnails_bg_no_cookies_stored.js]
+[browser_thumbnails_bg_no_auth_prompt.js]
+[browser_thumbnails_bg_no_alert.js]
+[browser_thumbnails_bg_no_duplicates.js]
+[browser_thumbnails_bg_captureIfMissing.js]
+[browser_thumbnails_bug726727.js]
+[browser_thumbnails_bug727765.js]
+[browser_thumbnails_bug818225.js]
+[browser_thumbnails_capture.js]
+[browser_thumbnails_expiration.js]
+[browser_thumbnails_privacy.js]
+[browser_thumbnails_redirect.js]
+[browser_thumbnails_storage.js]
+[browser_thumbnails_storage_migrate3.js]
+[browser_thumbnails_update.js]
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js
new file mode 100644
index 000000000..df8ef8d96
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let url = "invalid-protocol://ffggfsdfsdf/";
+ ok(!thumbnailExists(url), "Thumbnail should not be cached already.");
+ let numCalls = 0;
+ BackgroundPageThumbs.capture(url, {
+ onDone: function onDone(capturedURL) {
+ is(capturedURL, url, "Captured URL should be URL passed to capture");
+ is(numCalls++, 0, "onDone should be called only once");
+ ok(!thumbnailExists(url),
+ "Capture failed so thumbnail should not be cached");
+ next();
+ },
+ });
+ yield new Promise(resolve => {
+ bgAddPageThumbObserver(url).catch(function(err) {
+ ok(true, "page-thumbnail error produced");
+ resolve();
+ });
+ });
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js
new file mode 100644
index 000000000..027e0bfb7
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let url = "http://www.example.com/";
+ ok(!thumbnailExists(url), "Thumbnail should not be cached yet.");
+
+ let capturePromise = new Promise(resolve => {
+ bgAddPageThumbObserver(url).then(() => {
+ ok(true, `page-thumbnail created for ${url}`);
+ resolve();
+ });
+ });
+ let capturedURL = yield bgCapture(url);
+ is(capturedURL, url, "Captured URL should be URL passed to capture");
+ yield capturePromise;
+
+ ok(thumbnailExists(url), "Thumbnail should be cached after capture");
+ removeThumbnail(url);
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js
new file mode 100644
index 000000000..cd1f1c5c2
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let numNotifications = 0;
+ function observe(subject, topic, data) {
+ is(topic, "page-thumbnail:create", "got expected topic");
+ numNotifications += 1;
+ }
+
+ Services.obs.addObserver(observe, "page-thumbnail:create", false);
+
+ let url = "http://example.com/";
+ let file = thumbnailFile(url);
+ ok(!file.exists(), "Thumbnail file should not already exist.");
+
+ let capturedURL = yield bgCaptureIfMissing(url);
+ is(numNotifications, 1, "got notification of item being created.");
+ is(capturedURL, url, "Captured URL should be URL passed to capture");
+ ok(file.exists(url), "Thumbnail should be cached after capture");
+
+ let past = Date.now() - 1000000000;
+ let pastFudge = past + 30000;
+ file.lastModifiedTime = past;
+ ok(file.lastModifiedTime < pastFudge, "Last modified time should stick!");
+ capturedURL = yield bgCaptureIfMissing(url);
+ is(numNotifications, 1, "still only 1 notification of item being created.");
+ is(capturedURL, url, "Captured URL should be URL passed to second capture");
+ ok(file.exists(), "Thumbnail should remain cached after second capture");
+ ok(file.lastModifiedTime < pastFudge,
+ "File should not have been overwritten");
+
+ file.remove(false);
+ Services.obs.removeObserver(observe, "page-thumbnail:create");
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_during_capture.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_during_capture.js
new file mode 100644
index 000000000..db67a04a8
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_during_capture.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let crashObserver = bgAddCrashObserver();
+
+ // make a good capture first - this ensures we have the <browser>
+ let goodUrl = bgTestPageURL();
+ yield bgCapture(goodUrl);
+ ok(thumbnailExists(goodUrl), "Thumbnail should be cached after capture");
+ removeThumbnail(goodUrl);
+
+ // inject our content script.
+ let mm = bgInjectCrashContentScript();
+
+ // queue up 2 captures - the first has a wait, so this is the one that
+ // will die. The second one should immediately capture after the crash.
+ let waitUrl = bgTestPageURL({ wait: 30000 });
+ let sawWaitUrlCapture = false;
+ bgCapture(waitUrl, { onDone: () => {
+ sawWaitUrlCapture = true;
+ ok(!thumbnailExists(waitUrl), "Thumbnail should not have been saved due to the crash");
+ }});
+ bgCapture(goodUrl, { onDone: () => {
+ ok(sawWaitUrlCapture, "waitUrl capture should have finished first");
+ ok(thumbnailExists(goodUrl), "We should have recovered and completed the 2nd capture after the crash");
+ removeThumbnail(goodUrl);
+ // Test done.
+ ok(crashObserver.crashed, "Saw a crash from this test");
+ next();
+ }});
+ let crashPromise = new Promise(resolve => {
+ bgAddPageThumbObserver(waitUrl).catch(function(err) {
+ ok(true, `page-thumbnail error thrown for ${waitUrl}`);
+ resolve();
+ });
+ });
+ let capturePromise = new Promise(resolve => {
+ bgAddPageThumbObserver(goodUrl).then(() => {
+ ok(true, `page-thumbnail created for ${goodUrl}`);
+ resolve();
+ });
+ });
+
+ info("Crashing the thumbnail content process.");
+ mm.sendAsyncMessage("thumbnails-test:crash");
+ yield crashPromise;
+ yield capturePromise;
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js
new file mode 100644
index 000000000..8ff6a3509
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let crashObserver = bgAddCrashObserver();
+
+ // make a good capture first - this ensures we have the <browser>
+ let goodUrl = bgTestPageURL();
+ yield bgCapture(goodUrl);
+ ok(thumbnailExists(goodUrl), "Thumbnail should be cached after capture");
+ removeThumbnail(goodUrl);
+
+ // inject our content script.
+ let mm = bgInjectCrashContentScript();
+
+ // the observer for the crashing process is basically async, so it's hard
+ // to know when the <browser> has actually seen it. Easist is to just add
+ // our own observer.
+ Services.obs.addObserver(function onCrash() {
+ Services.obs.removeObserver(onCrash, "oop-frameloader-crashed");
+ // spin the event loop to ensure the BPT observer was called.
+ executeSoon(function() {
+ // Now queue another capture and ensure it recovers.
+ bgCapture(goodUrl, { onDone: () => {
+ ok(thumbnailExists(goodUrl), "We should have recovered and handled new capture requests");
+ removeThumbnail(goodUrl);
+ // Test done.
+ ok(crashObserver.crashed, "Saw a crash from this test");
+ next();
+ }});
+ });
+ }, "oop-frameloader-crashed", false);
+
+ // Nothing is pending - crash the process.
+ info("Crashing the thumbnail content process.");
+ mm.sendAsyncMessage("thumbnails-test:crash");
+ yield true;
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js
new file mode 100644
index 000000000..b83fdf583
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+
+ let url1 = "http://example.com/1";
+ ok(!thumbnailExists(url1), "First file should not exist yet.");
+
+ let url2 = "http://example.com/2";
+ ok(!thumbnailExists(url2), "Second file should not exist yet.");
+
+ let defaultTimeout = BackgroundPageThumbs._destroyBrowserTimeout;
+ BackgroundPageThumbs._destroyBrowserTimeout = 1000;
+
+ yield bgCapture(url1);
+ ok(thumbnailExists(url1), "First file should exist after capture.");
+ removeThumbnail(url1);
+
+ yield wait(2000);
+ is(BackgroundPageThumbs._thumbBrowser, undefined,
+ "Thumb browser should be destroyed after timeout.");
+ BackgroundPageThumbs._destroyBrowserTimeout = defaultTimeout;
+
+ yield bgCapture(url2);
+ ok(thumbnailExists(url2), "Second file should exist after capture.");
+ removeThumbnail(url2);
+
+ isnot(BackgroundPageThumbs._thumbBrowser, undefined,
+ "Thumb browser should exist immediately after capture.");
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js
new file mode 100644
index 000000000..5d6bd81f8
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let url = "data:text/html,<script>try { alert('yo!'); } catch (e) {}</script>";
+ ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
+
+ let capturedURL = yield bgCapture(url);
+ is(capturedURL, url, "Captured URL should be URL passed to capture.");
+ ok(thumbnailExists(url),
+ "Thumbnail file should exist even though it alerted.");
+ removeThumbnail(url);
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js
new file mode 100644
index 000000000..0eb9df7a9
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// the following tests attempt to display modal dialogs. The test just
+// relies on the fact that if the dialog was displayed the test will hang
+// and timeout. IOW - the tests would pass if the dialogs appear and are
+// manually closed by the user - so don't do that :) (obviously there is
+// noone available to do that when run via tbpl etc, so this should be safe,
+// and it's tricky to use the window-watcher to check a window *does not*
+// appear - how long should the watcher be active before assuming it's not
+// going to appear?)
+function* runTests() {
+ let url = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/authenticate.sjs?user=anyone";
+ ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
+
+ let capturedURL = yield bgCapture(url);
+ is(capturedURL, url, "Captured URL should be URL passed to capture.");
+ ok(thumbnailExists(url),
+ "Thumbnail file should exist even though it requires auth.");
+ removeThumbnail(url);
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js
new file mode 100644
index 000000000..afbedb382
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ // Visit the test page in the browser and tell it to set a cookie.
+ let url = bgTestPageURL({ setGreenCookie: true });
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = tab.linkedBrowser;
+
+ // The root element of the page shouldn't be green yet.
+ yield ContentTask.spawn(browser, null, function () {
+ Assert.notEqual(content.document.documentElement.style.backgroundColor,
+ "rgb(0, 255, 0)",
+ "The page shouldn't be green yet.");
+ });
+
+ // Cookie should be set now. Reload the page to verify. Its root element
+ // will be green if the cookie's set.
+ browser.reload();
+ yield BrowserTestUtils.browserLoaded(browser);
+ yield ContentTask.spawn(browser, null, function () {
+ Assert.equal(content.document.documentElement.style.backgroundColor,
+ "rgb(0, 255, 0)",
+ "The page should be green now.");
+ });
+
+ // Capture the page. Get the image data of the capture and verify it's not
+ // green. (Checking only the first pixel suffices.)
+ yield bgCapture(url);
+ ok(thumbnailExists(url), "Thumbnail file should exist after capture.");
+
+ retrieveImageDataForURL(url, function ([r, g, b]) {
+ isnot([r, g, b].toString(), [0, 255, 0].toString(),
+ "The captured page should not be green.");
+ gBrowser.removeTab(tab);
+ removeThumbnail(url);
+ next();
+ });
+ yield true;
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js
new file mode 100644
index 000000000..90a1a890b
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// check that if a page captured in the background attempts to set a cookie,
+// that cookie is not saved for subsequent requests.
+function* runTests() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["privacy.usercontext.about_newtab_segregation.enabled", true]]
+ });
+ let url = bgTestPageURL({
+ setRedCookie: true,
+ iframe: bgTestPageURL({ setRedCookie: true}),
+ xhr: bgTestPageURL({ setRedCookie: true})
+ });
+ ok(!thumbnailExists(url), "Thumbnail file should not exist before capture.");
+ yield bgCapture(url);
+ ok(thumbnailExists(url), "Thumbnail file should exist after capture.");
+ removeThumbnail(url);
+ // now load it up in a browser - it should *not* be red, otherwise the
+ // cookie above was saved.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = tab.linkedBrowser;
+
+ // The root element of the page shouldn't be red.
+ yield ContentTask.spawn(browser, null, function() {
+ Assert.notEqual(content.document.documentElement.style.backgroundColor,
+ "rgb(255, 0, 0)",
+ "The page shouldn't be red.");
+ });
+
+ gBrowser.removeTab(tab);
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js
new file mode 100644
index 000000000..31b504335
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let url = "http://example.com/1";
+ ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
+ let numCallbacks = 0;
+ let doneCallback = function(doneUrl) {
+ is(doneUrl, url, "called back with correct url");
+ numCallbacks += 1;
+ // We will delete the file after the first callback, then check it
+ // still doesn't exist on the second callback, which should give us
+ // confidence that we didn't end up with 2 different captures happening
+ // for the same url...
+ if (numCallbacks == 1) {
+ ok(thumbnailExists(url), "Thumbnail file should now exist.");
+ removeThumbnail(url);
+ return;
+ }
+ if (numCallbacks == 2) {
+ ok(!thumbnailExists(url), "Thumbnail file should still be deleted.");
+ // and that's all we expect, so we are done...
+ next();
+ return;
+ }
+ ok(false, "only expecting 2 callbacks");
+ }
+ BackgroundPageThumbs.capture(url, {onDone: doneCallback});
+ BackgroundPageThumbs.capture(url, {onDone: doneCallback});
+ yield true;
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js
new file mode 100644
index 000000000..1426f6f4e
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let urls = [
+ "http://www.example.com/0",
+ "http://www.example.com/1",
+ // an item that will timeout to ensure timeouts work and we resume.
+ bgTestPageURL({ wait: 2002 }),
+ "http://www.example.com/2",
+ ];
+ dontExpireThumbnailURLs(urls);
+ urls.forEach(url => {
+ ok(!thumbnailExists(url), "Thumbnail should not exist yet: " + url);
+ let isTimeoutTest = url.indexOf("wait") >= 0;
+ BackgroundPageThumbs.capture(url, {
+ timeout: isTimeoutTest ? 100 : 30000,
+ onDone: function onDone(capturedURL) {
+ ok(urls.length > 0, "onDone called, so URLs should still remain");
+ is(capturedURL, urls.shift(),
+ "Captured URL should be currently expected URL (i.e., " +
+ "capture() callbacks should be called in the correct order)");
+ if (isTimeoutTest) {
+ ok(!thumbnailExists(capturedURL),
+ "Thumbnail shouldn't exist for timed out capture");
+ } else {
+ ok(thumbnailExists(capturedURL),
+ "Thumbnail should be cached after capture");
+ removeThumbnail(url);
+ }
+ if (!urls.length)
+ // Test done.
+ next();
+ },
+ });
+ });
+ yield true;
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js
new file mode 100644
index 000000000..baa1b6d68
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let finalURL = "http://example.com/redirected";
+ let originalURL = bgTestPageURL({ redirect: finalURL });
+
+ ok(!thumbnailExists(originalURL),
+ "Thumbnail file for original URL should not exist yet.");
+ ok(!thumbnailExists(finalURL),
+ "Thumbnail file for final URL should not exist yet.");
+
+ let captureOriginalPromise = new Promise(resolve => {
+ bgAddPageThumbObserver(originalURL).then(() => {
+ ok(true, `page-thumbnail created for ${originalURL}`);
+ resolve();
+ });
+ });
+
+ let captureFinalPromise = new Promise(resolve => {
+ bgAddPageThumbObserver(finalURL).then(() => {
+ ok(true, `page-thumbnail created for ${finalURL}`);
+ resolve();
+ });
+ });
+
+ let capturedURL = yield bgCapture(originalURL);
+ is(capturedURL, originalURL,
+ "Captured URL should be URL passed to capture");
+ yield captureOriginalPromise;
+ yield captureFinalPromise;
+ ok(thumbnailExists(originalURL),
+ "Thumbnail for original URL should be cached");
+ ok(thumbnailExists(finalURL),
+ "Thumbnail for final URL should be cached");
+
+ removeThumbnail(originalURL);
+ removeThumbnail(finalURL);
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js
new file mode 100644
index 000000000..da05b4355
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function* runTests() {
+ let url = bgTestPageURL({ wait: 30000 });
+ ok(!thumbnailExists(url), "Thumbnail should not be cached already.");
+ let numCalls = 0;
+ BackgroundPageThumbs.capture(url, {
+ timeout: 0,
+ onDone: function onDone(capturedURL) {
+ is(capturedURL, url, "Captured URL should be URL passed to capture");
+ is(numCalls++, 0, "onDone should be called only once");
+ ok(!thumbnailExists(url),
+ "Capture timed out so thumbnail should not be cached");
+ next();
+ },
+ });
+ yield new Promise(resolve => {
+ bgAddPageThumbObserver(url).catch(function(err) {
+ ok(true, `page-thumbnail error thrown for ${url}`);
+ resolve();
+ });
+ });
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js b/toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js
new file mode 100644
index 000000000..f7f1f3deb
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * These tests ensure that capturing a sites's thumbnail, saving it and
+ * retrieving it from the cache works.
+ */
+function* runTests() {
+ // Create a tab that shows an error page.
+ let tab = gBrowser.addTab("http://127.0.0.1:1/");
+ let browser = tab.linkedBrowser;
+ yield browser.addEventListener("DOMContentLoaded", function onLoad() {
+ browser.removeEventListener("DOMContentLoaded", onLoad, false);
+ PageThumbs.shouldStoreThumbnail(browser, (aResult) => {
+ ok(!aResult, "we're not going to capture an error page");
+ executeSoon(next);
+ });
+ }, false);
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js b/toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js
new file mode 100644
index 000000000..c4faac685
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/" +
+ "test/background_red_scroll.html";
+
+function isRedThumbnailFuzz(r, g, b, expectedR, expectedB, expectedG, aFuzz)
+{
+ return (Math.abs(r - expectedR) <= aFuzz) &&
+ (Math.abs(g - expectedG) <= aFuzz) &&
+ (Math.abs(b - expectedB) <= aFuzz);
+}
+
+// Test for black borders caused by scrollbars.
+function* runTests() {
+ // Create a tab with a page with a red background and scrollbars.
+ yield addTab(URL);
+ yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
+
+ // Check the thumbnail color of the bottom right pixel.
+ yield whenFileExists(URL);
+ yield retrieveImageDataForURL(URL, function (aData) {
+ let [r, g, b] = [].slice.call(aData, -4);
+ let fuzz = 2; // Windows 8 x64 blends with the scrollbar a bit.
+ var message = "Expected red thumbnail rgb(255, 0, 0), got " + r + "," + g + "," + b;
+ ok(isRedThumbnailFuzz(r, g, b, 255, 0, 0, fuzz), message);
+ next();
+ });
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js b/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
new file mode 100644
index 000000000..a7e1caa04
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/" +
+ "test/background_red.html?" + Date.now();
+
+// Test PageThumbs API function getThumbnailPath
+function* runTests() {
+
+ let path = PageThumbs.getThumbnailPath(URL);
+ yield testIfExists(path, false, "Thumbnail file does not exist");
+
+ yield addVisitsAndRepopulateNewTabLinks(URL, next);
+ yield createThumbnail(URL);
+
+ path = PageThumbs.getThumbnailPath(URL);
+ let expectedPath = PageThumbsStorage.getFilePathForURL(URL);
+ is(path, expectedPath, "Thumbnail file has correct path");
+
+ yield testIfExists(path, true, "Thumbnail file exists");
+
+}
+
+function createThumbnail(aURL) {
+ addTab(aURL, function () {
+ whenFileExists(aURL, function () {
+ gBrowser.removeTab(gBrowser.selectedTab);
+ next();
+ });
+ });
+}
+
+function testIfExists(aPath, aExpected, aMessage) {
+ return OS.File.exists(aPath).then(
+ function onSuccess(exists) {
+ is(exists, aExpected, aMessage);
+ },
+ function onFailure(error) {
+ ok(false, "OS.File.exists() failed " + error);
+ }
+ );
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_capture.js b/toolkit/components/thumbnails/test/browser_thumbnails_capture.js
new file mode 100644
index 000000000..47d94d31b
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_capture.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * These tests ensure that capturing a sites's thumbnail, saving it and
+ * retrieving it from the cache works.
+ */
+function* runTests() {
+ // Create a tab with a red background.
+ yield addTab("data:text/html,<body bgcolor=ff0000></body>");
+ yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
+
+ // Load a page with a green background.
+ yield navigateTo("data:text/html,<body bgcolor=00ff00></body>");
+ yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
+
+ // Load a page with a blue background.
+ yield navigateTo("data:text/html,<body bgcolor=0000ff></body>");
+ yield captureAndCheckColor(0, 0, 255, "we have a blue thumbnail");
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_expiration.js b/toolkit/components/thumbnails/test/browser_thumbnails_expiration.js
new file mode 100644
index 000000000..4c73e17be
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_expiration.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/?t=" + Date.now();
+const URL1 = URL + "#1";
+const URL2 = URL + "#2";
+const URL3 = URL + "#3";
+
+var tmp = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("resource://gre/modules/PageThumbs.jsm", tmp);
+
+const EXPIRATION_MIN_CHUNK_SIZE = 50;
+const {PageThumbsExpiration} = tmp;
+
+function* runTests() {
+ // Create dummy URLs.
+ let dummyURLs = [];
+ for (let i = 0; i < EXPIRATION_MIN_CHUNK_SIZE + 10; i++) {
+ dummyURLs.push(URL + "#dummy" + i);
+ }
+
+ // Make sure our thumbnails aren't expired too early.
+ dontExpireThumbnailURLs([URL1, URL2, URL3].concat(dummyURLs));
+
+ // Create three thumbnails.
+ yield createDummyThumbnail(URL1);
+ ok(thumbnailExists(URL1), "first thumbnail created");
+
+ yield createDummyThumbnail(URL2);
+ ok(thumbnailExists(URL2), "second thumbnail created");
+
+ yield createDummyThumbnail(URL3);
+ ok(thumbnailExists(URL3), "third thumbnail created");
+
+ // Remove the third thumbnail.
+ yield expireThumbnails([URL1, URL2]);
+ ok(thumbnailExists(URL1), "first thumbnail still exists");
+ ok(thumbnailExists(URL2), "second thumbnail still exists");
+ ok(!thumbnailExists(URL3), "third thumbnail has been removed");
+
+ // Remove the second thumbnail.
+ yield expireThumbnails([URL1]);
+ ok(thumbnailExists(URL1), "first thumbnail still exists");
+ ok(!thumbnailExists(URL2), "second thumbnail has been removed");
+
+ // Remove all thumbnails.
+ yield expireThumbnails([]);
+ ok(!thumbnailExists(URL1), "all thumbnails have been removed");
+
+ // Create some more files than the min chunk size.
+ for (let url of dummyURLs) {
+ yield createDummyThumbnail(url);
+ }
+
+ ok(dummyURLs.every(thumbnailExists), "all dummy thumbnails created");
+
+ // Expire thumbnails and expect 10 remaining.
+ yield expireThumbnails([]);
+ let remainingURLs = dummyURLs.filter(thumbnailExists);
+ is(remainingURLs.length, 10, "10 dummy thumbnails remaining");
+
+ // Expire thumbnails again. All should be gone by now.
+ yield expireThumbnails([]);
+ remainingURLs = remainingURLs.filter(thumbnailExists);
+ is(remainingURLs.length, 0, "no dummy thumbnails remaining");
+}
+
+function createDummyThumbnail(aURL) {
+ info("Creating dummy thumbnail for " + aURL);
+ let dummy = new Uint8Array(10);
+ for (let i = 0; i < 10; ++i) {
+ dummy[i] = i;
+ }
+ PageThumbsStorage.writeData(aURL, dummy).then(
+ function onSuccess() {
+ info("createDummyThumbnail succeeded");
+ executeSoon(next);
+ },
+ function onFailure(error) {
+ ok(false, "createDummyThumbnail failed " + error);
+ }
+ );
+}
+
+function expireThumbnails(aKeep) {
+ PageThumbsExpiration.expireThumbnails(aKeep).then(
+ function onSuccess() {
+ info("expireThumbnails succeeded");
+ executeSoon(next);
+ },
+ function onFailure(error) {
+ ok(false, "expireThumbnails failed " + error);
+ }
+ );
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js b/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
new file mode 100644
index 000000000..e7dc7b4d5
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_DISK_CACHE_SSL = "browser.cache.disk_cache_ssl";
+const URL = "://example.com/browser/toolkit/components/thumbnails/" +
+ "test/privacy_cache_control.sjs";
+
+function* runTests() {
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_DISK_CACHE_SSL);
+ });
+
+ let positive = [
+ // A normal HTTP page without any Cache-Control header.
+ {scheme: "http", cacheControl: null, diskCacheSSL: false},
+
+ // A normal HTTP page with 'Cache-Control: private'.
+ {scheme: "http", cacheControl: "private", diskCacheSSL: false},
+
+ // Capture HTTPS pages if browser.cache.disk_cache_ssl == true.
+ {scheme: "https", cacheControl: null, diskCacheSSL: true},
+ {scheme: "https", cacheControl: "public", diskCacheSSL: true},
+ {scheme: "https", cacheControl: "private", diskCacheSSL: true}
+ ];
+
+ let negative = [
+ // Never capture pages with 'Cache-Control: no-store'.
+ {scheme: "http", cacheControl: "no-store", diskCacheSSL: false},
+ {scheme: "http", cacheControl: "no-store", diskCacheSSL: true},
+ {scheme: "https", cacheControl: "no-store", diskCacheSSL: false},
+ {scheme: "https", cacheControl: "no-store", diskCacheSSL: true},
+
+ // Don't capture HTTPS pages by default.
+ {scheme: "https", cacheControl: null, diskCacheSSL: false},
+ {scheme: "https", cacheControl: "public", diskCacheSSL: false},
+ {scheme: "https", cacheControl: "private", diskCacheSSL: false}
+ ];
+
+ yield checkCombinations(positive, true);
+ yield checkCombinations(negative, false);
+}
+
+function checkCombinations(aCombinations, aResult) {
+ let combi = aCombinations.shift();
+ if (!combi) {
+ next();
+ return;
+ }
+
+ let url = combi.scheme + URL;
+ if (combi.cacheControl)
+ url += "?" + combi.cacheControl;
+ Services.prefs.setBoolPref(PREF_DISK_CACHE_SSL, combi.diskCacheSSL);
+
+ // Add the test page as a top link so it has a chance to be thumbnailed
+ addVisitsAndRepopulateNewTabLinks(url, _ => {
+ testCombination(combi, url, aCombinations, aResult);
+ });
+}
+
+function testCombination(combi, url, aCombinations, aResult) {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = gBrowser.selectedBrowser;
+
+ whenLoaded(browser, () => {
+ let msg = JSON.stringify(combi) + " == " + aResult;
+ PageThumbs.shouldStoreThumbnail(browser, (aIsSafeSite) => {
+ is(aIsSafeSite, aResult, msg);
+ gBrowser.removeTab(tab);
+ // Continue with the next combination.
+ checkCombinations(aCombinations, aResult);
+ });
+ });
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_redirect.js b/toolkit/components/thumbnails/test/browser_thumbnails_redirect.js
new file mode 100644
index 000000000..482dbc803
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_redirect.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/" +
+ "test/background_red_redirect.sjs";
+// loading URL will redirect us to...
+const FINAL_URL = "http://mochi.test:8888/browser/toolkit/components/" +
+ "thumbnails/test/background_red.html";
+
+/**
+ * These tests ensure that we save and provide thumbnails for redirecting sites.
+ */
+function* runTests() {
+ dontExpireThumbnailURLs([URL, FINAL_URL]);
+
+ // Kick off history by loading a tab first or the test fails in single mode.
+ yield addTab(URL);
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ // Create a tab, redirecting to a page with a red background.
+ yield addTab(URL);
+ yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
+
+ // Wait until the referrer's thumbnail's file has been written.
+ yield whenFileExists(URL);
+ yield retrieveImageDataForURL(URL, function ([r, g, b]) {
+ is("" + [r, g, b], "255,0,0", "referrer has a red thumbnail");
+ next();
+ });
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_storage.js b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
new file mode 100644
index 000000000..972f956e5
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/";
+const URL_COPY = URL + "#copy";
+
+XPCOMUtils.defineLazyGetter(this, "Sanitizer", function () {
+ let tmp = {};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+ return tmp.Sanitizer;
+});
+
+/**
+ * These tests ensure that the thumbnail storage is working as intended.
+ * Newly captured thumbnails should be saved as files and they should as well
+ * be removed when the user sanitizes their history.
+ */
+function* runTests() {
+ yield Task.spawn(function*() {
+ dontExpireThumbnailURLs([URL, URL_COPY]);
+
+ yield promiseClearHistory();
+ yield promiseAddVisitsAndRepopulateNewTabLinks(URL);
+ yield promiseCreateThumbnail();
+
+ // Make sure Storage.copy() updates an existing file.
+ yield PageThumbsStorage.copy(URL, URL_COPY);
+ let copy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+ let mtime = copy.lastModifiedTime -= 60;
+
+ yield PageThumbsStorage.copy(URL, URL_COPY);
+ isnot(new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY)).lastModifiedTime, mtime,
+ "thumbnail file was updated");
+
+ let file = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL));
+ let fileCopy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+
+ // Clear the browser history. Retry until the files are gone because Windows
+ // locks them sometimes.
+ info("Clearing history");
+ while (file.exists() || fileCopy.exists()) {
+ yield promiseClearHistory();
+ }
+ info("History is clear");
+
+ info("Repopulating");
+ yield promiseAddVisitsAndRepopulateNewTabLinks(URL);
+ yield promiseCreateThumbnail();
+
+ info("Clearing the last 10 minutes of browsing history");
+ // Clear the last 10 minutes of browsing history.
+ yield promiseClearHistory(true);
+
+ info("Attempt to clear file");
+ // Retry until the file is gone because Windows locks it sometimes.
+ yield promiseClearFile(file, URL);
+
+ info("Done");
+ });
+}
+
+var promiseClearFile = Task.async(function*(aFile, aURL) {
+ if (!aFile.exists()) {
+ return undefined;
+ }
+ // Re-add our URL to the history so that history observer's onDeleteURI()
+ // is called again.
+ yield PlacesTestUtils.addVisits(makeURI(aURL));
+ yield promiseClearHistory(true);
+ // Then retry.
+ return promiseClearFile(aFile, aURL);
+});
+
+function promiseClearHistory(aUseRange) {
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let prefs = gPrefService.getBranch(s.prefDomain);
+ prefs.setBoolPref("history", true);
+ prefs.setBoolPref("downloads", false);
+ prefs.setBoolPref("cache", false);
+ prefs.setBoolPref("cookies", false);
+ prefs.setBoolPref("formdata", false);
+ prefs.setBoolPref("offlineApps", false);
+ prefs.setBoolPref("passwords", false);
+ prefs.setBoolPref("sessions", false);
+ prefs.setBoolPref("siteSettings", false);
+
+ if (aUseRange) {
+ let usec = Date.now() * 1000;
+ s.range = [usec - 10 * 60 * 1000 * 1000, usec];
+ s.ignoreTimespan = false;
+ }
+
+ return s.sanitize().then(() => {
+ s.range = null;
+ s.ignoreTimespan = true;
+ });
+}
+
+function promiseCreateThumbnail() {
+ return new Promise(resolve => {
+ addTab(URL, function () {
+ whenFileExists(URL, function () {
+ gBrowser.removeTab(gBrowser.selectedTab);
+ resolve();
+ });
+ });
+ });
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js b/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
new file mode 100644
index 000000000..e7f150f87
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/migration3";
+const URL2 = URL + "#2";
+const URL3 = URL + "#3";
+const THUMBNAIL_DIRECTORY = "thumbnails";
+const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
+
+var tmp = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("resource://gre/modules/PageThumbs.jsm", tmp);
+var {PageThumbsStorageMigrator} = tmp;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDirSvc",
+ "@mozilla.org/file/directory_service;1", "nsIProperties");
+
+/**
+ * This test makes sure we correctly migrate to thumbnail storage version 3.
+ * This means copying existing thumbnails from the roaming to the local profile
+ * directory and should just apply to Linux.
+ */
+function* runTests() {
+ // Prepare a local profile directory.
+ let localProfile = FileUtils.getDir("ProfD", ["local-test"], true);
+ changeLocation("ProfLD", localProfile);
+
+ let roaming = FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY], true);
+
+ // Set up some data in the roaming profile.
+ let name = PageThumbsStorage.getLeafNameForURL(URL);
+ let file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
+ writeDummyFile(file);
+
+ name = PageThumbsStorage.getLeafNameForURL(URL2);
+ file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
+ writeDummyFile(file);
+
+ name = PageThumbsStorage.getLeafNameForURL(URL3);
+ file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
+ writeDummyFile(file);
+
+ // Pretend to have one of the thumbnails
+ // already in place at the new storage site.
+ name = PageThumbsStorage.getLeafNameForURL(URL3);
+ file = FileUtils.getFile("ProfLD", [THUMBNAIL_DIRECTORY, name]);
+ writeDummyFile(file, "no-overwrite-plz");
+
+ // Kick off thumbnail storage migration.
+ PageThumbsStorageMigrator.migrateToVersion3(localProfile.path);
+ ok(true, "migration finished");
+
+ // Wait until the first thumbnail was moved to its new location.
+ yield whenFileExists(URL);
+ ok(true, "first thumbnail moved");
+
+ // Wait for the second thumbnail to be moved as well.
+ yield whenFileExists(URL2);
+ ok(true, "second thumbnail moved");
+
+ yield whenFileRemoved(roaming);
+ ok(true, "roaming thumbnail directory removed");
+
+ // Check that our existing thumbnail wasn't overwritten.
+ is(getFileContents(file), "no-overwrite-plz",
+ "existing thumbnail was not overwritten");
+
+ // Sanity check: ensure that, until it is removed, deprecated
+ // function |getFileForURL| points to the same path as
+ // |getFilePathForURL|.
+ if ("getFileForURL" in PageThumbsStorage) {
+ file = PageThumbsStorage.getFileForURL(URL);
+ is(file.path, PageThumbsStorage.getFilePathForURL(URL),
+ "Deprecated getFileForURL and getFilePathForURL return the same path");
+ }
+}
+
+function changeLocation(aLocation, aNewDir) {
+ let oldDir = gDirSvc.get(aLocation, Ci.nsILocalFile);
+ gDirSvc.undefine(aLocation);
+ gDirSvc.set(aLocation, aNewDir);
+
+ registerCleanupFunction(function () {
+ gDirSvc.undefine(aLocation);
+ gDirSvc.set(aLocation, oldDir);
+ });
+}
+
+function writeDummyFile(aFile, aContents) {
+ let fos = FileUtils.openSafeFileOutputStream(aFile);
+ let data = aContents || "dummy";
+ fos.write(data, data.length);
+ FileUtils.closeSafeFileOutputStream(fos);
+}
+
+function getFileContents(aFile) {
+ let istream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ istream.init(aFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ return NetUtil.readInputStreamToString(istream, istream.available());
+}
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_update.js b/toolkit/components/thumbnails/test/browser_thumbnails_update.js
new file mode 100644
index 000000000..971a2994e
--- /dev/null
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_update.js
@@ -0,0 +1,169 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * These tests check the auto-update facility of the thumbnail service.
+ */
+
+function* runTests() {
+ // A "trampoline" - a generator that iterates over sub-iterators
+ let tests = [
+ simpleCaptureTest,
+ capIfStaleErrorResponseUpdateTest,
+ capIfStaleGoodResponseUpdateTest,
+ regularCapErrorResponseUpdateTest,
+ regularCapGoodResponseUpdateTest
+ ];
+ for (let test of tests) {
+ info("Running subtest " + test.name);
+ for (let iterator of test())
+ yield iterator;
+ }
+}
+
+function ensureThumbnailStale(url) {
+ // We go behind the back of the thumbnail service and change the
+ // mtime of the file to be in the past.
+ let fname = PageThumbsStorage.getFilePathForURL(url);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(fname);
+ ok(file.exists(), fname + " should exist");
+ // Set it as very stale...
+ file.lastModifiedTime = Date.now() - 1000000000;
+}
+
+function getThumbnailModifiedTime(url) {
+ let fname = PageThumbsStorage.getFilePathForURL(url);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(fname);
+ return file.lastModifiedTime;
+}
+
+// The tests!
+/* Check functionality of a normal captureAndStoreIfStale request */
+function* simpleCaptureTest() {
+ let numNotifications = 0;
+ const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?simple";
+
+ function observe(subject, topic, data) {
+ is(topic, "page-thumbnail:create", "got expected topic");
+ is(data, URL, "data is our test URL");
+ if (++numNotifications == 2) {
+ // This is the final notification and signals test success...
+ Services.obs.removeObserver(observe, "page-thumbnail:create");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ next();
+ }
+ }
+
+ Services.obs.addObserver(observe, "page-thumbnail:create", false);
+ // Create a tab - we don't care what the content is.
+ yield addTab(URL);
+ let browser = gBrowser.selectedBrowser;
+
+ // Capture the screenshot.
+ PageThumbs.captureAndStore(browser, function () {
+ // We've got a capture so should have seen the observer.
+ is(numNotifications, 1, "got notification of item being created.");
+ // The capture is now "fresh" - so requesting the URL should not cause
+ // a new capture.
+ PageThumbs.captureAndStoreIfStale(browser, function() {
+ is(numNotifications, 1, "still only 1 notification of item being created.");
+
+ ensureThumbnailStale(URL);
+ // Ask for it to be updated.
+ PageThumbs.captureAndStoreIfStale(browser);
+ // But it's async, so wait - our observer above will call next() when
+ // the notification comes.
+ });
+ });
+ yield undefined // wait for callbacks to call 'next'...
+}
+
+/* Check functionality of captureAndStoreIfStale when there is an error response
+ from the server.
+ */
+function* capIfStaleErrorResponseUpdateTest() {
+ const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?fail";
+ yield addTab(URL);
+
+ yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
+ // update the thumbnail to be stale, then re-request it. The server will
+ // return a 400 response and a red thumbnail.
+ // The service should not save the thumbnail - so we (a) check the thumbnail
+ // remains green and (b) check the mtime of the file is < now.
+ ensureThumbnailStale(URL);
+ yield navigateTo(URL);
+ // now() returns a higher-precision value than the modified time of a file.
+ // As we set the thumbnail very stale, allowing 1 second of "slop" here
+ // works around this while still keeping the test valid.
+ let now = Date.now() - 1000 ;
+ PageThumbs.captureAndStoreIfStale(gBrowser.selectedBrowser, () => {
+ ok(getThumbnailModifiedTime(URL) < now, "modified time should be < now");
+ retrieveImageDataForURL(URL, function ([r, g, b]) {
+ is("" + [r, g, b], "" + [0, 255, 0], "thumbnail is still green");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ next();
+ });
+ });
+ yield undefined; // wait for callback to call 'next'...
+}
+
+/* Check functionality of captureAndStoreIfStale when there is a non-error
+ response from the server. This test is somewhat redundant - although it is
+ using a http:// URL instead of a data: url like most others.
+ */
+function* capIfStaleGoodResponseUpdateTest() {
+ const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?ok";
+ yield addTab(URL);
+ let browser = gBrowser.selectedBrowser;
+
+ yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
+ // update the thumbnail to be stale, then re-request it. The server will
+ // return a 200 response and a red thumbnail - so that new thumbnail should
+ // end up captured.
+ ensureThumbnailStale(URL);
+ yield navigateTo(URL);
+ // now() returns a higher-precision value than the modified time of a file.
+ // As we set the thumbnail very stale, allowing 1 second of "slop" here
+ // works around this while still keeping the test valid.
+ let now = Date.now() - 1000 ;
+ PageThumbs.captureAndStoreIfStale(browser, () => {
+ ok(getThumbnailModifiedTime(URL) >= now, "modified time should be >= now");
+ // the captureAndStoreIfStale request saw a 200 response with the red body,
+ // so we expect to see the red version here.
+ retrieveImageDataForURL(URL, function ([r, g, b]) {
+ is("" + [r, g, b], "" + [255, 0, 0], "thumbnail is now red");
+ next();
+ });
+ });
+ yield undefined; // wait for callback to call 'next'...
+}
+
+/* Check functionality of captureAndStore when there is an error response
+ from the server.
+ */
+function* regularCapErrorResponseUpdateTest() {
+ const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?fail";
+ yield addTab(URL);
+ yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ // do it again - the server will return a 400, so the foreground service
+ // should not update it.
+ yield addTab(URL);
+ yield captureAndCheckColor(0, 255, 0, "we still have a green thumbnail");
+}
+
+/* Check functionality of captureAndStore when there is an OK response
+ from the server.
+ */
+function* regularCapGoodResponseUpdateTest() {
+ const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?ok";
+ yield addTab(URL);
+ yield captureAndCheckColor(0, 255, 0, "we have a green thumbnail");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ // do it again - the server will return a 200, so the foreground service
+ // should update it.
+ yield addTab(URL);
+ yield captureAndCheckColor(255, 0, 0, "we now have a red thumbnail");
+}
diff --git a/toolkit/components/thumbnails/test/head.js b/toolkit/components/thumbnails/test/head.js
new file mode 100644
index 000000000..e8229508a
--- /dev/null
+++ b/toolkit/components/thumbnails/test/head.js
@@ -0,0 +1,356 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var tmp = {};
+Cu.import("resource://gre/modules/PageThumbs.jsm", tmp);
+Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", tmp);
+Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
+Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
+Cu.import("resource://gre/modules/FileUtils.jsm", tmp);
+Cu.import("resource://gre/modules/osfile.jsm", tmp);
+var {PageThumbs, BackgroundPageThumbs, NewTabUtils, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+
+registerCleanupFunction(function () {
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref)
+});
+
+/**
+ * Provide the default test function to start our test runner.
+ */
+function test() {
+ TestRunner.run();
+}
+
+/**
+ * The test runner that controls the execution flow of our tests.
+ */
+var TestRunner = {
+ /**
+ * Starts the test runner.
+ */
+ run: function () {
+ waitForExplicitFinish();
+
+ SessionStore.promiseInitialized.then(function () {
+ this._iter = runTests();
+ if (this._iter) {
+ this.next();
+ } else {
+ finish();
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Runs the next available test or finishes if there's no test left.
+ * @param aValue This value will be passed to the yielder via the runner's
+ * iterator.
+ */
+ next: function (aValue) {
+ let obj = TestRunner._iter.next(aValue);
+ if (obj.done) {
+ finish();
+ return;
+ }
+
+ let value = obj.value || obj;
+ if (value && typeof value.then == "function") {
+ value.then(result => {
+ next(result);
+ }, error => {
+ ok(false, error + "\n" + error.stack);
+ });
+ }
+ }
+};
+
+/**
+ * Continues the current test execution.
+ * @param aValue This value will be passed to the yielder via the runner's
+ * iterator.
+ */
+function next(aValue) {
+ TestRunner.next(aValue);
+}
+
+/**
+ * Creates a new tab with the given URI.
+ * @param aURI The URI that's loaded in the tab.
+ * @param aCallback The function to call when the tab has loaded.
+ */
+function addTab(aURI, aCallback) {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aURI);
+ whenLoaded(tab.linkedBrowser, aCallback);
+}
+
+/**
+ * Loads a new URI into the currently selected tab.
+ * @param aURI The URI to load.
+ */
+function navigateTo(aURI) {
+ let browser = gBrowser.selectedBrowser;
+ whenLoaded(browser);
+ browser.loadURI(aURI);
+}
+
+/**
+ * Continues the current test execution when a load event for the given element
+ * has been received.
+ * @param aElement The DOM element to listen on.
+ * @param aCallback The function to call when the load event was dispatched.
+ */
+function whenLoaded(aElement, aCallback = next) {
+ aElement.addEventListener("load", function onLoad() {
+ aElement.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
+
+/**
+ * Captures a screenshot for the currently selected tab, stores it in the cache,
+ * retrieves it from the cache and compares pixel color values.
+ * @param aRed The red component's intensity.
+ * @param aGreen The green component's intensity.
+ * @param aBlue The blue component's intensity.
+ * @param aMessage The info message to print when comparing the pixel color.
+ */
+function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
+ let browser = gBrowser.selectedBrowser;
+ // We'll get oranges if the expiration filter removes the file during the
+ // test.
+ dontExpireThumbnailURLs([browser.currentURI.spec]);
+
+ // Capture the screenshot.
+ PageThumbs.captureAndStore(browser, function () {
+ retrieveImageDataForURL(browser.currentURI.spec, function ([r, g, b]) {
+ is("" + [r, g, b], "" + [aRed, aGreen, aBlue], aMessage);
+ next();
+ });
+ });
+}
+
+/**
+ * For a given URL, loads the corresponding thumbnail
+ * to a canvas and passes its image data to the callback.
+ * Note, not compat with e10s!
+ * @param aURL The url associated with the thumbnail.
+ * @param aCallback The function to pass the image data to.
+ */
+function retrieveImageDataForURL(aURL, aCallback) {
+ let width = 100, height = 100;
+ let thumb = PageThumbs.getThumbnailURL(aURL, width, height);
+
+ let htmlns = "http://www.w3.org/1999/xhtml";
+ let img = document.createElementNS(htmlns, "img");
+ img.setAttribute("src", thumb);
+
+ whenLoaded(img, function () {
+ let canvas = document.createElementNS(htmlns, "canvas");
+ canvas.setAttribute("width", width);
+ canvas.setAttribute("height", height);
+
+ // Draw the image to a canvas and compare the pixel color values.
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(img, 0, 0, width, height);
+ let result = ctx.getImageData(0, 0, 100, 100).data;
+ aCallback(result);
+ });
+}
+
+/**
+ * Returns the file of the thumbnail with the given URL.
+ * @param aURL The URL of the thumbnail.
+ */
+function thumbnailFile(aURL) {
+ return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
+}
+
+/**
+ * Checks if a thumbnail for the given URL exists.
+ * @param aURL The url associated to the thumbnail.
+ */
+function thumbnailExists(aURL) {
+ let file = thumbnailFile(aURL);
+ return file.exists() && file.fileSize;
+}
+
+/**
+ * Removes the thumbnail for the given URL.
+ * @param aURL The URL associated with the thumbnail.
+ */
+function removeThumbnail(aURL) {
+ let file = thumbnailFile(aURL);
+ file.remove(false);
+}
+
+/**
+ * Calls addVisits, and then forces the newtab module to repopulate its links.
+ * See addVisits for parameter descriptions.
+ */
+function addVisitsAndRepopulateNewTabLinks(aPlaceInfo, aCallback) {
+ PlacesTestUtils.addVisits(makeURI(aPlaceInfo)).then(() => {
+ NewTabUtils.links.populateCache(aCallback, true);
+ });
+}
+function promiseAddVisitsAndRepopulateNewTabLinks(aPlaceInfo) {
+ return new Promise(resolve => addVisitsAndRepopulateNewTabLinks(aPlaceInfo, resolve));
+}
+
+/**
+ * Calls a given callback when the thumbnail for a given URL has been found
+ * on disk. Keeps trying until the thumbnail has been created.
+ *
+ * @param aURL The URL of the thumbnail's page.
+ * @param [optional] aCallback
+ * Function to be invoked on completion.
+ */
+function whenFileExists(aURL, aCallback = next) {
+ let callback = aCallback;
+ if (!thumbnailExists(aURL)) {
+ callback = () => whenFileExists(aURL, aCallback);
+ }
+
+ executeSoon(callback);
+}
+
+/**
+ * Calls a given callback when the given file has been removed.
+ * Keeps trying until the file is removed.
+ *
+ * @param aFile The file that is being removed
+ * @param [optional] aCallback
+ * Function to be invoked on completion.
+ */
+function whenFileRemoved(aFile, aCallback) {
+ let callback = aCallback;
+ if (aFile.exists()) {
+ callback = () => whenFileRemoved(aFile, aCallback);
+ }
+
+ executeSoon(callback || next);
+}
+
+function wait(aMillis) {
+ setTimeout(next, aMillis);
+}
+
+/**
+ * Makes sure that a given list of URLs is not implicitly expired.
+ *
+ * @param aURLs The list of URLs that should not be expired.
+ */
+function dontExpireThumbnailURLs(aURLs) {
+ let dontExpireURLs = (cb) => cb(aURLs);
+ PageThumbs.addExpirationFilter(dontExpireURLs);
+
+ registerCleanupFunction(function () {
+ PageThumbs.removeExpirationFilter(dontExpireURLs);
+ });
+}
+
+function bgCapture(aURL, aOptions) {
+ bgCaptureWithMethod("capture", aURL, aOptions);
+}
+
+function bgCaptureIfMissing(aURL, aOptions) {
+ bgCaptureWithMethod("captureIfMissing", aURL, aOptions);
+}
+
+function bgCaptureWithMethod(aMethodName, aURL, aOptions = {}) {
+ // We'll get oranges if the expiration filter removes the file during the
+ // test.
+ dontExpireThumbnailURLs([aURL]);
+ if (!aOptions.onDone)
+ aOptions.onDone = next;
+ BackgroundPageThumbs[aMethodName](aURL, aOptions);
+}
+
+function bgTestPageURL(aOpts = {}) {
+ let TEST_PAGE_URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_background.sjs";
+ return TEST_PAGE_URL + "?" + encodeURIComponent(JSON.stringify(aOpts));
+}
+
+function bgAddPageThumbObserver(url) {
+ return new Promise((resolve, reject) => {
+ function observe(subject, topic, data) { // jshint ignore:line
+ if (data === url) {
+ switch (topic) {
+ case "page-thumbnail:create":
+ resolve();
+ break;
+ case "page-thumbnail:error":
+ reject(new Error("page-thumbnail:error"));
+ break;
+ }
+ Services.obs.removeObserver(observe, "page-thumbnail:create");
+ Services.obs.removeObserver(observe, "page-thumbnail:error");
+ }
+ }
+ Services.obs.addObserver(observe, "page-thumbnail:create", false);
+ Services.obs.addObserver(observe, "page-thumbnail:error", false);
+ });
+}
+
+function bgAddCrashObserver() {
+ let crashed = false;
+ Services.obs.addObserver(function crashObserver(subject, topic, data) {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Components.interfaces.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ info("This is a normal termination and isn't the one we are looking for...");
+ return;
+ }
+ Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
+ crashed = true;
+
+ var dumpID;
+ if ('nsICrashReporter' in Components.interfaces) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+ }
+
+ if (dumpID) {
+ var minidumpDirectory = getMinidumpDirectory();
+ removeFile(minidumpDirectory, dumpID + '.dmp');
+ removeFile(minidumpDirectory, dumpID + '.extra');
+ }
+ }, 'ipc:content-shutdown', false);
+ return {
+ get crashed() {
+ return crashed;
+ }
+ };
+}
+
+function bgInjectCrashContentScript() {
+ const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js";
+ let thumbnailBrowser = BackgroundPageThumbs._thumbBrowser;
+ let mm = thumbnailBrowser.messageManager;
+ mm.loadFrameScript(TEST_CONTENT_HELPER, false);
+ return mm;
+}
+
+function getMinidumpDirectory() {
+ var dir = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile);
+ dir.append("minidumps");
+ return dir;
+}
+
+function removeFile(directory, filename) {
+ var file = directory.clone();
+ file.append(filename);
+ if (file.exists()) {
+ file.remove(false);
+ }
+}
diff --git a/toolkit/components/thumbnails/test/privacy_cache_control.sjs b/toolkit/components/thumbnails/test/privacy_cache_control.sjs
new file mode 100644
index 000000000..6c7c16edb
--- /dev/null
+++ b/toolkit/components/thumbnails/test/privacy_cache_control.sjs
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+
+ // Set Cache-Control header.
+ let value = aRequest.queryString;
+ if (value)
+ aResponse.setHeader("Cache-Control", value);
+
+ // Set the response body.
+ aResponse.write("<!DOCTYPE html><html><body></body></html>");
+}
diff --git a/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js b/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js
new file mode 100644
index 000000000..8272b2e06
--- /dev/null
+++ b/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js
@@ -0,0 +1,31 @@
+// tests to check that moz-page-thumb URLs correctly resolve as file:// URLS
+"use strict";
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// need profile so that PageThumbsStorage can resolve the path to the underlying file
+do_get_profile();
+
+function run_test() {
+ // first check the protocol handler implements the correct interface
+ let handler = Services.io.getProtocolHandler("moz-page-thumb");
+ ok(handler instanceof Ci.nsISubstitutingProtocolHandler,
+ "moz-page-thumb handler provides substituting interface");
+
+ // then check that the file URL resolution works
+ let uri = Services.io.newURI("moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F",
+ null, null);
+ ok(uri instanceof Ci.nsIFileURL, "moz-page-thumb:// is a FileURL");
+ ok(uri.file, "This moz-page-thumb:// object is backed by a file");
+
+ // and check that the error case works as specified
+ let bad = Services.io.newURI("moz-page-thumb://wronghost/?url=http%3A%2F%2Fwww.mozilla.org%2F",
+ null, null);
+ Assert.throws(() => handler.resolveURI(bad), /NS_ERROR_NOT_AVAILABLE/i,
+ "moz-page-thumb object with wrong host must not resolve to a file path");
+}
diff --git a/toolkit/components/thumbnails/test/thumbnails_background.sjs b/toolkit/components/thumbnails/test/thumbnails_background.sjs
new file mode 100644
index 000000000..f1cce96a0
--- /dev/null
+++ b/toolkit/components/thumbnails/test/thumbnails_background.sjs
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The timer never fires if it's not declared and set to this variable outside
+// handleRequest, as if it's getting GC'ed when handleRequest's scope goes away.
+// Shouldn't the timer thread hold a strong reference to it?
+var timer;
+
+function handleRequest(req, resp) {
+ resp.processAsync();
+ resp.setHeader("Cache-Control", "no-cache, no-store", false);
+ resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ let opts = {};
+ try {
+ opts = JSON.parse(decodeURIComponent(req.queryString));
+ }
+ catch (err) {}
+
+ let setCookieScript = "";
+ if (opts.setRedCookie) {
+ resp.setHeader("Set-Cookie", "red", false);
+ setCookieScript = '<script>document.cookie="red";</script>';
+ }
+
+ if (opts.setGreenCookie) {
+ resp.setHeader("Set-Cookie", "green", false);
+ setCookieScript = '<script>document.cookie="green";</script>';
+ }
+
+ if (opts.iframe) {
+ setCookieScript += '<iframe src="' + opts.iframe + '" />';
+ }
+
+ if (opts.xhr) {
+ setCookieScript += `
+ <script>
+ var req = new XMLHttpRequest();
+ req.open("GET", "${opts.xhr}", true);
+ req.send();
+ </script>
+ `;
+ }
+
+ if (req.hasHeader("Cookie") &&
+ req.getHeader("Cookie").split(";").indexOf("red") >= 0) {
+ resp.write('<html style="background: #f00;">' + setCookieScript + '</html>');
+ resp.finish();
+ return;
+ }
+
+ if (req.hasHeader("Cookie") &&
+ req.getHeader("Cookie").split(";").indexOf("green") >= 0) {
+ resp.write('<html style="background: #0f0;">' + setCookieScript + '</html>');
+ resp.finish();
+ return;
+ }
+
+ if (opts.redirect) {
+ resp.setHeader("Location", opts.redirect);
+ resp.setStatusLine(null, 303, null);
+ resp.finish();
+ return;
+ }
+
+ if (opts.wait) {
+ resp.write("Waiting " + opts.wait + " ms... ");
+ timer = Components.classes["@mozilla.org/timer;1"].
+ createInstance(Components.interfaces.nsITimer);
+ timer.init(function ding() {
+ resp.write("OK!");
+ resp.finish();
+ }, opts.wait, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+
+ resp.write("<pre>" + JSON.stringify(opts, undefined, 2) + "</pre>" + setCookieScript);
+ resp.finish();
+}
diff --git a/toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js b/toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js
new file mode 100644
index 000000000..935175f86
--- /dev/null
+++ b/toolkit/components/thumbnails/test/thumbnails_crash_content_helper.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Cu = Components.utils;
+
+// Ideally we would use CrashTestUtils.jsm, but that's only available for
+// xpcshell tests - so we just copy a ctypes crasher from it.
+Cu.import("resource://gre/modules/ctypes.jsm");
+var crash = function() { // this will crash when called.
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+};
+
+
+var TestHelper = {
+ init: function() {
+ addMessageListener("thumbnails-test:crash", this);
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "thumbnails-test:crash":
+ privateNoteIntentionalCrash();
+ crash();
+ break;
+ }
+ },
+}
+
+TestHelper.init();
diff --git a/toolkit/components/thumbnails/test/thumbnails_update.sjs b/toolkit/components/thumbnails/test/thumbnails_update.sjs
new file mode 100644
index 000000000..4d8ab406a
--- /dev/null
+++ b/toolkit/components/thumbnails/test/thumbnails_update.sjs
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This server-side script is used for browser_thumbnails_update. One of the
+// main things it must do in all cases is ensure a Cache-Control: no-store
+// header, so the foreground capture doesn't interfere with the testing.
+
+// If the querystring is "simple", then all it does it return some content -
+// it doesn't really matter what that content is.
+
+// Otherwise, its main role is that it must return different *content* for the
+// second request than it did for the first.
+// Also, it should be able to return an error response when requested for the
+// second response.
+// So the basic tests will be to grab the thumbnail, then request it to be
+// grabbed again:
+// * If the second request succeeded, the new thumbnail should exist.
+// * If the second request is an error, the new thumbnail should be ignored.
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setHeader("Content-Type", "text/html;charset=utf-8", false);
+ // we want to disable gBrowserThumbnails on-load capture for these responses,
+ // so set as a "no-store" response.
+ aResponse.setHeader("Cache-Control", "no-store");
+
+ // for the simple test - just return some content.
+ if (aRequest.queryString == "simple") {
+ aResponse.write("<html><body></body></html>");
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "Its simply OK");
+ return;
+ }
+
+ // it's one of the more complex tests where the first request for the given
+ // URL must return different content than the second, and possibly an error
+ // response for the second
+ let doneError = getState(aRequest.queryString);
+ if (!doneError) {
+ // first request - return a response with a green body and 200 response.
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK - It's green");
+ aResponse.write("<html><body bgcolor=00ff00></body></html>");
+ // set the state so the next request does the "second request" thing below.
+ setState(aRequest.queryString, "yep");
+ } else {
+ // second request - this will be done by the b/g service.
+ // We always return a red background, but depending on the query string we
+ // return either a 200 or 401 response.
+ if (aRequest.queryString == "fail")
+ aResponse.setStatusLine(aRequest.httpVersion, 401, "Oh no you don't");
+ else
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK - It's red");
+ aResponse.write("<html><body bgcolor=ff0000></body></html>");
+ // reset the error state incase this ends up being reused for the
+ // same url and querystring.
+ setState(aRequest.queryString, "");
+ }
+}
diff --git a/toolkit/components/thumbnails/test/xpcshell.ini b/toolkit/components/thumbnails/test/xpcshell.ini
new file mode 100644
index 000000000..4dae8cced
--- /dev/null
+++ b/toolkit/components/thumbnails/test/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head =
+tail =
+
+[test_thumbnails_interfaces.js]
+skip-if = os == 'android' # xpcom interface not packaged