diff options
Diffstat (limited to 'toolkit/components/thumbnails/test')
43 files changed, 2240 insertions, 0 deletions
diff --git a/toolkit/components/thumbnails/test/authenticate.sjs b/toolkit/components/thumbnails/test/authenticate.sjs new file mode 100644 index 0000000000..5bc811db74 --- /dev/null +++ b/toolkit/components/thumbnails/test/authenticate.sjs @@ -0,0 +1,216 @@ +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 Error("Couldn't parse auth header: " + authHeader); + } + + var userpass = atob(match[1]); + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) { + throw Error("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 Error("Couldn't parse auth header: " + authHeader); + } + + userpass = atob(match[1]); + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) { + throw Error("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 (let 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 (let 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 (let 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>"); +} diff --git a/toolkit/components/thumbnails/test/background_red.html b/toolkit/components/thumbnails/test/background_red.html new file mode 100644 index 0000000000..95159dd297 --- /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 0000000000..168772443d --- /dev/null +++ b/toolkit/components/thumbnails/test/background_red_redirect.sjs @@ -0,0 +1,13 @@ +/* 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 0000000000..1e30bd3c67 --- /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 0000000000..8191b83de2 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser.ini @@ -0,0 +1,52 @@ +[DEFAULT] +support-files = + authenticate.sjs + background_red.html + background_red_redirect.sjs + background_red_scroll.html + sample_image_red_1920x1080.jpg + sample_image_green_1024x1024.jpg + sample_image_blue_300x600.jpg + head.js + privacy_cache_control.sjs + thumbnails_background.sjs + thumbnails_update.sjs + +[browser_thumbnails_bg_bad_url.js] +[browser_thumbnails_bg_crash_during_capture.js] +skip-if = !crashreporter + apple_silicon # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs +[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] +skip-if = verify +[browser_thumbnails_bg_no_cookies_stored.js] +skip-if = verify +[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_bg_image_capture.js] +[browser_thumbnails_bg_topsites.js] +[browser_thumbnails_bug726727.js] +[browser_thumbnails_bug727765.js] +[browser_thumbnails_bug818225.js] +skip-if = (verify && debug && (os == 'linux')) +[browser_thumbnails_capture.js] +[browser_thumbnails_capture_parent_process.js] +skip-if = true # bug 1314039 # Bug 1345418 +[browser_thumbnails_expiration.js] +[browser_thumbnails_fullViewport.js] +[browser_thumbnails_privacy.js] +[browser_thumbnails_redirect.js] +[browser_thumbnails_removed_tab.js] +[browser_thumbnails_storage.js] +[browser_thumbnails_storage_migrate3.js] +skip-if = true # Bug 1592079 +[browser_thumbnails_update.js] +skip-if = os == 'win' #Bug 1539947 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 0000000000..7ebb843cd3 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_bad_url.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_bad_url() { + let url = "invalid-protocol://ffggfsdfsdf/"; + ok(!thumbnailExists(url), "Thumbnail should not be cached already."); + let numCalls = 0; + let observer = bgAddPageThumbObserver(url); + await new Promise(resolve => { + 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" + ); + resolve(); + }, + }); + }); + + await Assert.rejects(observer, /page-thumbnail:error/); +}); 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 0000000000..1adb2f03f1 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_basic.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_basic() { + let url = "http://www.example.com/"; + ok(!thumbnailExists(url), "Thumbnail should not be cached yet."); + + let capturePromise = bgAddPageThumbObserver(url); + let [capturedURL] = await bgCapture(url); + is(capturedURL, url, "Captured URL should be URL passed to capture"); + await 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 0000000000..a5ccafc801 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_captureIfMissing.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_captureIfMissing() { + 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"); + + let url = "https://example.com/"; + let file = thumbnailFile(url); + ok(!file.exists(), "Thumbnail file should not already exist."); + + let [capturedURL] = await 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] = await 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 0000000000..872965d7b2 --- /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/ */ + +add_task(async function thumbnails_bg_crash_during_capture() { + // make a good capture first - this ensures we have the <browser> + let goodUrl = bgTestPageURL(); + await bgCapture(goodUrl); + ok(thumbnailExists(goodUrl), "Thumbnail should be cached after capture"); + removeThumbnail(goodUrl); + + // 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; + let failCapture = bgCapture(waitUrl, { + onDone: () => { + sawWaitUrlCapture = true; + ok( + !thumbnailExists(waitUrl), + "Thumbnail should not have been saved due to the crash" + ); + }, + }); + let goodCapture = 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); + }, + }); + + let crashPromise = bgAddPageThumbObserver(waitUrl).catch(err => { + ok(/page-thumbnail:error/.test(err), "Got the right kind of error"); + }); + let capturePromise = bgAddPageThumbObserver(goodUrl); + + info("Crashing the thumbnail content process."); + let crash = await BrowserTestUtils.crashFrame( + BackgroundPageThumbs._thumbBrowser, + false + ); + ok(crash.CrashTime, "Saw a crash from this test"); + + await crashPromise; + await Promise.all([failCapture, goodCapture, 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 0000000000..4808f39e1c --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_crash_while_idle.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_crash_while_idle() { + // make a good capture first - this ensures we have the <browser> + let goodUrl = bgTestPageURL(); + await bgCapture(goodUrl); + ok(thumbnailExists(goodUrl), "Thumbnail should be cached after capture"); + removeThumbnail(goodUrl); + + // Nothing is pending - crash the process. + info("Crashing the thumbnail content process."); + let crash = await BrowserTestUtils.crashFrame( + BackgroundPageThumbs._thumbBrowser, + false + ); + ok(crash.CrashTime, "Saw a crash from this test"); + + // Now queue another capture and ensure it recovers. + await bgCapture(goodUrl, { + onDone: () => { + ok( + thumbnailExists(goodUrl), + "We should have recovered and handled new capture requests" + ); + removeThumbnail(goodUrl); + }, + }); +}); 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 0000000000..609d344972 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_destroy_browser.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_destroy_browser() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]], + }); + + let url1 = "https://example.com/1"; + ok(!thumbnailExists(url1), "First file should not exist yet."); + + let url2 = "https://example.com/2"; + ok(!thumbnailExists(url2), "Second file should not exist yet."); + + let defaultTimeout = BackgroundPageThumbs._destroyBrowserTimeout; + BackgroundPageThumbs._destroyBrowserTimeout = 1000; + + await bgCapture(url1); + ok(thumbnailExists(url1), "First file should exist after capture."); + removeThumbnail(url1); + + // arbitrary wait - intermittent failures noted after 2 seconds + await TestUtils.waitForCondition( + () => { + return BackgroundPageThumbs._thumbBrowser === undefined; + }, + "BackgroundPageThumbs._thumbBrowser should eventually be discarded.", + 1000, + 5 + ); + + is( + BackgroundPageThumbs._thumbBrowser, + undefined, + "Thumb browser should be destroyed after timeout." + ); + BackgroundPageThumbs._destroyBrowserTimeout = defaultTimeout; + + await 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_image_capture.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_image_capture.js new file mode 100644 index 0000000000..03f30c544a --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_image_capture.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const BASE_URL = + "http://mochi.test:8888/browser/toolkit/components/thumbnails/"; + +/** + * These tests ensure that when trying to capture a url that is an image file, + * the image itself is captured instead of the the browser window displaying the + * image, and that the thumbnail maintains the image aspect ratio. + */ +add_task(async function thumbnails_bg_image_capture() { + // Test that malformed input causes _finishCurrentCapture to be called with + // the correct reason. + const emptyUrl = "data:text/plain,"; + await bgCapture(emptyUrl, { + isImage: true, + onDone: (url, reason) => { + // BackgroundPageThumbs.TEL_CAPTURE_DONE_LOAD_FAILED === 6 + is(reason, 6, "Should have the right failure reason"); + }, + }); + + for (const { url, color, width, height } of [ + { + url: BASE_URL + "test/sample_image_red_1920x1080.jpg", + color: [255, 0, 0], + width: 1920, + height: 1080, + }, + { + url: BASE_URL + "test/sample_image_green_1024x1024.jpg", + color: [0, 255, 0], + width: 1024, + height: 1024, + }, + { + url: BASE_URL + "test/sample_image_blue_300x600.jpg", + color: [0, 0, 255], + width: 300, + height: 600, + }, + ]) { + dontExpireThumbnailURLs([url]); + const capturedPromise = bgAddPageThumbObserver(url); + await bgCapture(url); + await capturedPromise; + ok(thumbnailExists(url), "The image thumbnail should exist after capture"); + + const thumb = PageThumbs.getThumbnailURL(url); + const htmlns = "http://www.w3.org/1999/xhtml"; + const img = document.createElementNS(htmlns, "img"); + img.src = thumb; + await BrowserTestUtils.waitForEvent(img, "load"); + + // 448px is the default max-width of an image thumbnail + const expectedWidth = Math.min(448, width); + // Tall images are clipped to {width}x{width} + const expectedHeight = Math.min( + (expectedWidth * height) / width, + expectedWidth + ); + // Fuzzy equality to account for rounding + ok( + Math.abs(img.naturalWidth - expectedWidth) <= 1, + "The thumbnail should have the right width" + ); + ok( + Math.abs(img.naturalHeight - expectedHeight) <= 1, + "The thumbnail should have the right height" + ); + + // Draw the image to a canvas and compare the pixel color values. + const canvas = document.createElementNS(htmlns, "canvas"); + canvas.width = expectedWidth; + canvas.height = expectedHeight; + const ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0, expectedWidth, expectedHeight); + const [r, g, b] = ctx.getImageData( + 0, + 0, + expectedWidth, + expectedHeight + ).data; + // Fuzzy equality to account for image encoding + ok( + Math.abs(r - color[0]) <= 2 && + Math.abs(g - color[1]) <= 2 && + Math.abs(b - color[2]) <= 2, + "The thumbnail should have the right color" + ); + + removeThumbnail(url); + } +}); 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 0000000000..e0dc7f353c --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_alert.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_no_alert() { + let url = + "data:text/html,<script>try { alert('yo!'); } catch (e) {}</script>"; + ok(!thumbnailExists(url), "Thumbnail file should not already exist."); + + let [capturedURL] = await 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 0000000000..e9dd51fa7d --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_auth_prompt.js @@ -0,0 +1,26 @@ +/* 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?) + */ +add_task(async function thumbnails_bg_no_auth_prompt() { + 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] = await 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 0000000000..136e761f37 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_sent.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_no_cookies_sent() { + // Visit the test page in the browser and tell it to set a cookie. + let url = bgTestPageURL({ setGreenCookie: true }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url, + }, + async browser => { + // The root element of the page shouldn't be green yet. + await SpecialPowers.spawn(browser, [], 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(); + await BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn(browser, [], 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.) + await bgCapture(url); + ok(thumbnailExists(url), "Thumbnail file should exist after capture."); + + let [r, g, b] = await retrieveImageDataForURL(url); + isnot( + [r, g, b].toString(), + [0, 255, 0].toString(), + "The captured page should not be green." + ); + removeThumbnail(url); + } + ); +}); 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 0000000000..6512745568 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js @@ -0,0 +1,39 @@ +/* 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. + */ +add_task(async function thumbnails_bg_no_cookies_stored() { + await 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."); + await 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. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url, + }, + async browser => { + // The root element of the page shouldn't be red. + await SpecialPowers.spawn(browser, [], function() { + Assert.notEqual( + content.document.documentElement.style.backgroundColor, + "rgb(255, 0, 0)", + "The page shouldn't be red." + ); + }); + } + ); +}); 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 0000000000..e4b3b42849 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_duplicates.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_no_duplicates() { + let url = "https://example.com/1"; + ok(!thumbnailExists(url), "Thumbnail file should not already exist."); + + let firstCapture = bgCapture(url, { + onDone: doneUrl => { + is(doneUrl, url, "called back with correct url"); + ok(thumbnailExists(url), "Thumbnail file should now exist."); + removeThumbnail(url); + }, + }); + + let secondCapture = bgCapture(url, { + onDone: doneUrl => { + is(doneUrl, url, "called back with correct url"); + ok(!thumbnailExists(url), "Thumbnail file should still be deleted."); + }, + }); + + await Promise.all([firstCapture, secondCapture]); +}); 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 0000000000..92e8711637 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_queueing.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_queueing() { + let urls = [ + "https://www.example.com/0", + "https://www.example.com/1", + // an item that will timeout to ensure timeouts work and we resume. + bgTestPageURL({ wait: 2002 }), + "https://www.example.com/2", + ]; + dontExpireThumbnailURLs(urls); + + let promises = []; + + for (let url of urls) { + ok(!thumbnailExists(url), "Thumbnail should not exist yet: " + url); + let isTimeoutTest = url.includes("wait"); + + let promise = bgCapture(url, { + timeout: isTimeoutTest ? 100 : 30000, + onDone: capturedURL => { + ok(!!urls.length, "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); + } + }, + }); + + promises.push(promise); + } + + await Promise.all(promises); +}); 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 0000000000..ae0096ff18 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_redirect.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_redirect() { + let finalURL = "https://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 = bgAddPageThumbObserver(originalURL); + let captureFinalPromise = bgAddPageThumbObserver(finalURL); + + let [capturedURL] = await bgCapture(originalURL); + is(capturedURL, originalURL, "Captured URL should be URL passed to capture"); + await captureOriginalPromise; + await 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 0000000000..4deabaf325 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_timeout.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bg_timeout() { + let url = bgTestPageURL({ wait: 30000 }); + ok(!thumbnailExists(url), "Thumbnail should not be cached already."); + let numCalls = 0; + let thumbnailErrorPromise = bgAddPageThumbObserver(url); + + await bgCapture(url, { + timeout: 0, + 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" + ); + }, + }); + + await Assert.rejects(thumbnailErrorPromise, /page-thumbnail:error/); +}); diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_topsites.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_topsites.js new file mode 100644 index 0000000000..fac37aff4a --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_topsites.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const image1x1 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="; +const image96x96 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAJCAYAAADNYymqAAAAKklEQVR42u3RMQEAAAjDMFCO9CGDg1RC00lN6awGAACADQAACAAAAXjXApPGFm+IdJG9AAAAAElFTkSuQmCC"; +const baseURL = "http://mozilla${i}.com/"; + +add_task(async function thumbnails_bg_topsites() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", + false, + ], + ], + }); + // Add 3 top sites - 2 visits each so it can pass frecency threshold of the top sites query + for (let i = 1; i <= 3; i++) { + await PlacesTestUtils.addVisits(baseURL.replace("${i}", i)); + await PlacesTestUtils.addVisits(baseURL.replace("${i}", i)); + } + + // Add favicon data for 2 of the top sites + let faviconData = new Map(); + faviconData.set("http://mozilla1.com/", image1x1); + faviconData.set("http://mozilla2.com/", image96x96); + await PlacesTestUtils.addFavicons(faviconData); + + // Sanity check that we've successfully added all 3 urls to top sites + let links = await NewTabUtils.activityStreamLinks.getTopSites(); + is( + links[0].url, + baseURL.replace("${i}", 3), + "Top site has been successfully added" + ); + is( + links[1].url, + baseURL.replace("${i}", 2), + "Top site has been successfully added" + ); + is( + links[2].url, + baseURL.replace("${i}", 1), + "Top site has been successfully added" + ); + + // Now, add a pinned site so we can also fetch a screenshot for that + const pinnedSite = { url: baseURL.replace("${i}", 4) }; + NewTabUtils.pinnedLinks.pin(pinnedSite, 0); + + // Check that the correct sites will capture screenshots + gBrowserThumbnails.clearTopSiteURLCache(); + let topSites = await gBrowserThumbnails._topSiteURLs; + ok( + topSites.includes("http://mozilla1.com/"), + "Top site did not have a rich icon - get a screenshot" + ); + ok( + topSites.includes("http://mozilla3.com/"), + "Top site did not have an icon - get a screenshot" + ); + ok( + topSites.includes("http://mozilla4.com/"), + "Site is pinned - get a screenshot" + ); + ok( + !topSites.includes("http://mozilla2.com/"), + "Top site had a rich icon - do not get a screenshot" + ); + + // Clean up + NewTabUtils.pinnedLinks.unpin(pinnedSite); + await PlacesUtils.history.clear(); +}); 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 0000000000..ae9e722268 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug726727.js @@ -0,0 +1,27 @@ +/* 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. + */ +add_task(async function thumbnails_bg_bug726727() { + // Create a tab that shows an error page. + await BrowserTestUtils.withNewTab( + { + gBrowser, + }, + async browser => { + let errorPageLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + null, + true + ); + BrowserTestUtils.loadURI(browser, "http://127.0.0.1:1"); + await errorPageLoaded; + let result = await PageThumbs.shouldStoreThumbnail(browser); + ok(!result, "we're not going to capture an error page"); + } + ); +}); 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 0000000000..8e953e0ffa --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug727765.js @@ -0,0 +1,38 @@ +/* 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. +add_task(async function thumbnails_bg_bug727765() { + // Create a tab with a page with a red background and scrollbars. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await captureAndCheckColor(255, 0, 0, "we have a red thumbnail"); + + // Check the thumbnail color of the bottom right pixel. + await whenFileExists(URL); + + let data = await retrieveImageDataForURL(URL); + let [r, g, b] = [].slice.call(data, -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); + } + ); +}); 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 0000000000..5f557c40a4 --- /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 +add_task(async function thumbnails_bg_bug818225() { + let path = PageThumbs.getThumbnailPath(URL); + await testIfExists(path, false, "Thumbnail file does not exist"); + await promiseAddVisitsAndRepopulateNewTabLinks(URL); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + gBrowserThumbnails.clearTopSiteURLCache(); + await whenFileExists(URL); + } + ); + + path = PageThumbs.getThumbnailPath(URL); + let expectedPath = PageThumbsStorageService.getFilePathForURL(URL); + is(path, expectedPath, "Thumbnail file has correct path"); + + await testIfExists(path, true, "Thumbnail file exists"); +}); + +function testIfExists(aPath, aExpected, aMessage) { + return IOUtils.exists(aPath).then( + function onSuccess(exists) { + is(exists, aExpected, aMessage); + }, + function onFailure(error) { + ok(false, `IOUtils.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 0000000000..6b2b894623 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_capture.js @@ -0,0 +1,37 @@ +/* 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. + */ +add_task(async function thumbnails_capture() { + // Create a tab with a red background. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "data:text/html,<body bgcolor=ff0000></body>", + }, + async browser => { + await captureAndCheckColor(255, 0, 0, "we have a red thumbnail"); + + // Load a page with a green background. + let loaded = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.loadURI( + browser, + "data:text/html,<body bgcolor=00ff00></body>" + ); + await loaded; + await captureAndCheckColor(0, 255, 0, "we have a green thumbnail"); + + // Load a page with a blue background. + loaded = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.loadURI( + browser, + "data:text/html,<body bgcolor=0000ff></body>" + ); + await loaded; + await captureAndCheckColor(0, 0, 255, "we have a blue thumbnail"); + } + ); +}); diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_capture_parent_process.js b/toolkit/components/thumbnails/test/browser_thumbnails_capture_parent_process.js new file mode 100644 index 0000000000..67cd6a79e2 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_capture_parent_process.js @@ -0,0 +1,27 @@ +/* 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, specifically for a parent process. + */ +add_task(async function thumbnails_capture() { + // Create a parent process tab with a red background. + // We do this by creating a parent process, then we update it to be a red page, + // before attempting to read the page colour. + await BrowserTestUtils.withNewTab( + { + gBrowser, + // about:robots seems to be an simple parent process url we can test against, + // but any parent process url would have worked, example, about:home or about:config + url: "about:robots", + }, + async browser => { + // Because about:robots is not generally a predictable testable page, + // we update its background to something we can test against. + browser.contentDocument.body.innerHTML = ""; + browser.contentDocument.body.style.backgroundColor = "#ff0000"; + await captureAndCheckColor(255, 0, 0, "we have a red 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 0000000000..8949b727d5 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_expiration.js @@ -0,0 +1,82 @@ +/* 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 = Cu.Sandbox(window, { wantGlobalProperties: ["ChromeUtils"] }); +Services.scriptloader.loadSubScript( + "resource://gre/modules/PageThumbs.jsm", + tmp +); + +const EXPIRATION_MIN_CHUNK_SIZE = 50; +const { PageThumbsExpiration } = tmp; + +add_task(async function thumbnails_expiration() { + // 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. + await createDummyThumbnail(URL1); + ok(thumbnailExists(URL1), "first thumbnail created"); + + await createDummyThumbnail(URL2); + ok(thumbnailExists(URL2), "second thumbnail created"); + + await createDummyThumbnail(URL3); + ok(thumbnailExists(URL3), "third thumbnail created"); + + // Remove the third thumbnail. + await 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. + await expireThumbnails([URL1]); + ok(thumbnailExists(URL1), "first thumbnail still exists"); + ok(!thumbnailExists(URL2), "second thumbnail has been removed"); + + // Remove all thumbnails. + await expireThumbnails([]); + ok(!thumbnailExists(URL1), "all thumbnails have been removed"); + + // Create some more files than the min chunk size. + for (let url of dummyURLs) { + await createDummyThumbnail(url); + } + + ok(dummyURLs.every(thumbnailExists), "all dummy thumbnails created"); + + // Expire thumbnails and expect 10 remaining. + await expireThumbnails([]); + let remainingURLs = dummyURLs.filter(thumbnailExists); + is(remainingURLs.length, 10, "10 dummy thumbnails remaining"); + + // Expire thumbnails again. All should be gone by now. + await 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; + } + return PageThumbsStorage.writeData(aURL, dummy); +} + +function expireThumbnails(aKeep) { + return PageThumbsExpiration.expireThumbnails(aKeep); +} diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_fullViewport.js b/toolkit/components/thumbnails/test/browser_thumbnails_fullViewport.js new file mode 100644 index 0000000000..92533bdd3d --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_fullViewport.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BUILDER_URL = "https://example.com/document-builder.sjs?html="; +const PAGE_MARKUP = ` +<html> + <head> + <style> + body { + background-color: rgb(0, 255, 0); + margin: 0; + } + + div { + background-color: rgb(255, 0, 0); + height: 100vh; + width: 100vw; + margin-top: 100vh; + } + </style> + </head> + <body> + <div id="bigredblock"></div> + </body> +</html> +`; +const PAGE_URL = BUILDER_URL + encodeURI(PAGE_MARKUP); + +/** + * These tests ensure that it's possible to capture the full viewport of + * a browser, and not just the top region. + */ +add_task(async function thumbnails_fullViewport_capture() { + // Create a tab with a green background. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE_URL, + }, + async browser => { + let canvas = PageThumbs.createCanvas(window); + await PageThumbs.captureToCanvas(browser, canvas, { + fullViewport: true, + }); + + // The red region isn't scrolled in yet, so we should get + // a green thumbnail. + let ctx = canvas.getContext("2d"); + let [r, g, b] = ctx.getImageData(0, 0, 1, 1).data; + Assert.equal(r, 0, "No red channel"); + Assert.equal(g, 255, "Full green channel"); + Assert.equal(b, 0, "No blue channel"); + + // Now scroll the red region into view. + await SpecialPowers.spawn(browser, [], () => { + let redblock = content.document.getElementById("bigredblock"); + redblock.scrollIntoView(true); + }); + + await PageThumbs.captureToCanvas(browser, canvas, { + fullViewport: true, + }); + + // The captured region should be red. + ctx = canvas.getContext("2d"); + [r, g, b] = ctx.getImageData(0, 0, 1, 1).data; + Assert.equal(r, 255, "Full red channel"); + Assert.equal(g, 0, "No green channel"); + Assert.equal(b, 0, "No blue channel"); + } + ); +}); 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 0000000000..84c7010418 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js @@ -0,0 +1,72 @@ +/* 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"; + +add_task(async function thumbnails_privacy() { + 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 }, + ]; + + await checkCombinations(positive, true); + await checkCombinations(negative, false); +}); + +async function checkCombinations(aCombinations, aResult) { + for (let combination of aCombinations) { + let url = combination.scheme + URL; + if (combination.cacheControl) { + url += "?" + combination.cacheControl; + } + + await SpecialPowers.pushPrefEnv({ + set: [[PREF_DISK_CACHE_SSL, combination.diskCacheSSL]], + }); + + // Add the test page as a top link so it has a chance to be thumbnailed + await promiseAddVisitsAndRepopulateNewTabLinks(url); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url, + }, + async browser => { + let msg = JSON.stringify(combination) + " == " + aResult; + let aIsSafeSite = await PageThumbs.shouldStoreThumbnail(browser); + Assert.equal(aIsSafeSite, aResult, msg); + } + ); + + await SpecialPowers.popPrefEnv(); + } +} 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 0000000000..b77b4011c3 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_redirect.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_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. + */ +add_task(async function thumbnails_redirect() { + dontExpireThumbnailURLs([URL, FINAL_URL]); + + // Kick off history by loading a tab first or the test fails in single mode. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + browser => {} + ); + + // Create a tab, redirecting to a page with a red background. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await captureAndCheckColor(255, 0, 0, "we have a red thumbnail"); + + // Wait until the referrer's thumbnail's file has been written. + await whenFileExists(URL); + let [r, g, b] = await retrieveImageDataForURL(URL); + is("" + [r, g, b], "255,0,0", "referrer has a red thumbnail"); + } + ); +}); diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_removed_tab.js b/toolkit/components/thumbnails/test/browser_thumbnails_removed_tab.js new file mode 100644 index 0000000000..9d2138d3d0 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_removed_tab.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function thumbnails_bug1775638() { + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + const canvas = document.createElement("canvas"); + const promise = PageThumbs.captureToCanvas(tab.linkedBrowser, canvas); + gBrowser.removeTab(tab); + is(await promise, canvas); +}); 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 0000000000..48c4f333c7 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js @@ -0,0 +1,99 @@ +/* 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"; + +const { Sanitizer } = ChromeUtils.import("resource:///modules/Sanitizer.jsm"); + +/** + * 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. + */ +add_task(async function thumbnails_storage() { + dontExpireThumbnailURLs([URL, URL_COPY]); + + await promiseClearHistory(); + await promiseAddVisitsAndRepopulateNewTabLinks(URL); + await promiseCreateThumbnail(); + + // Make sure Storage.copy() updates an existing file. + await PageThumbsStorage.copy(URL, URL_COPY); + let copy = new FileUtils.File( + PageThumbsStorageService.getFilePathForURL(URL_COPY) + ); + let mtime = (copy.lastModifiedTime -= 60); + + await PageThumbsStorage.copy(URL, URL_COPY); + isnot( + new FileUtils.File(PageThumbsStorageService.getFilePathForURL(URL_COPY)) + .lastModifiedTime, + mtime, + "thumbnail file was updated" + ); + + let file = new FileUtils.File( + PageThumbsStorageService.getFilePathForURL(URL) + ); + let fileCopy = new FileUtils.File( + PageThumbsStorageService.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()) { + await promiseClearHistory(); + } + info("History is clear"); + + info("Repopulating"); + await promiseAddVisitsAndRepopulateNewTabLinks(URL); + await promiseCreateThumbnail(); + + info("Clearing the last 10 minutes of browsing history"); + // Clear the last 10 minutes of browsing history. + await promiseClearHistory(true); + + info("Attempt to clear file"); + // Retry until the file is gone because Windows locks it sometimes. + await promiseClearFile(file, URL); + + info("Done"); +}); + +async function promiseClearFile(aFile, aURL) { + if (!aFile.exists()) { + return undefined; + } + // Re-add our URL to the history so that history observer's onDeleteURI() + // is called again. + await PlacesTestUtils.addVisits(makeURI(aURL)); + await promiseClearHistory(true); + // Then retry. + return promiseClearFile(aFile, aURL); +} + +function promiseClearHistory(aUseRange) { + let options = {}; + if (aUseRange) { + let usec = Date.now() * 1000; + options.range = [usec - 10 * 60 * 1000 * 1000, usec]; + options.ignoreTimespan = false; + } + return Sanitizer.sanitize(["history"], options); +} + +async function promiseCreateThumbnail() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + gBrowserThumbnails.clearTopSiteURLCache(); + await whenFileExists(URL); + } + ); +} 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 0000000000..76b45c5869 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js @@ -0,0 +1,95 @@ +/* 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 = Cu.Sandbox(window, { wantGlobalProperties: ["ChromeUtils"] }); +Services.scriptloader.loadSubScript( + "resource://gre/modules/PageThumbs.jsm", + tmp +); +var { PageThumbsStorageMigrator } = tmp; + +/** + * 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 = PageThumbsStorageService.getLeafNameForURL(URL); + let file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]); + writeDummyFile(file); + + name = PageThumbsStorageService.getLeafNameForURL(URL2); + file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]); + writeDummyFile(file); + + name = PageThumbsStorageService.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 = PageThumbsStorageService.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" + ); +} + +function changeLocation(aLocation, aNewDir) { + let oldDir = Services.dirsvc.get(aLocation, Ci.nsIFile); + Services.dirsvc.undefine(aLocation); + Services.dirsvc.set(aLocation, aNewDir); + + registerCleanupFunction(function() { + Services.dirsvc.undefine(aLocation); + Services.dirsvc.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 0000000000..943d044d22 --- /dev/null +++ b/toolkit/components/thumbnails/test/browser_thumbnails_update.js @@ -0,0 +1,211 @@ +/* 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 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 = PageThumbsStorageService.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 = PageThumbsStorageService.getFilePathForURL(url); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(fname); + return file.lastModifiedTime; +} + +/** + * Check functionality of a normal captureAndStoreIfStale request + */ +add_task(async function thumbnails_captureAndStoreIfStale_normal() { + const URL = + "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?simple"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + let numNotifications = 0; + + let observed = TestUtils.topicObserved( + "page-thumbnail:create", + (subject, data) => { + is(data, URL, "data is our test URL"); + + // Once we get the second notification, we saw the last captureAndStoreIsStale, + // and we can tear down. + if (++numNotifications == 2) { + return true; + } + + return false; + } + ); + await PageThumbs.captureAndStore(browser); + // We've got a capture so should have seen the observer. + is(numNotifications, 1, "got notification of item being created."); + + await PageThumbs.captureAndStoreIfStale(browser); + is( + numNotifications, + 1, + "still only 1 notification of item being created." + ); + + ensureThumbnailStale(URL); + await PageThumbs.captureAndStoreIfStale(browser); + await observed; + } + ); +}); + +/** + * Check functionality of captureAndStoreIfStale when there is an error response + * from the server. + */ +add_task(async function thumbnails_captureAndStoreIfStale_error_response() { + const URL = + "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?fail"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await 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); + BrowserTestUtils.loadURI(browser, URL); + await BrowserTestUtils.browserLoaded(browser); + + // 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; + await PageThumbs.captureAndStoreIfStale(gBrowser.selectedBrowser); + + ok(getThumbnailModifiedTime(URL) < now, "modified time should be < now"); + let [r, g, b] = await retrieveImageDataForURL(URL); + is("" + [r, g, b], "" + [0, 255, 0], "thumbnail is still green"); + } + ); +}); + +/** + * 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. + */ +add_task(async function thumbnails_captureAndStoreIfStale_non_error_response() { + const URL = + "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?ok"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await 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); + BrowserTestUtils.loadURI(browser, URL); + await BrowserTestUtils.browserLoaded(browser); + + // 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; + await PageThumbs.captureAndStoreIfStale(browser); + Assert.greater( + getThumbnailModifiedTime(URL), + now, + "modified time should be >= now" + ); + let [r, g, b] = await retrieveImageDataForURL(URL); + is("" + [r, g, b], "" + [255, 0, 0], "thumbnail is now red"); + } + ); +}); + +/** + * Check functionality of captureAndStore when there is an error response + * from the server. + */ +add_task(async function thumbnails_captureAndStore_error_response() { + const URL = + "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?fail"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await captureAndCheckColor(0, 255, 0, "we have a green thumbnail"); + } + ); + + // do it again - the server will return a 400, so the foreground service + // should not update it. + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await captureAndCheckColor(0, 255, 0, "we still have a green thumbnail"); + } + ); +}); + +/** + * Check functionality of captureAndStore when there is an OK response + * from the server. + */ +add_task(async function thumbnails_captureAndStore_ok_response() { + const URL = + "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?ok"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await captureAndCheckColor(0, 255, 0, "we have a green thumbnail"); + } + ); + + // do it again - the server will return a 200, so the foreground service + // should update it. + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: URL, + }, + async browser => { + await 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 0000000000..37ce42a558 --- /dev/null +++ b/toolkit/components/thumbnails/test/head.js @@ -0,0 +1,238 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +ChromeUtils.defineESModuleGetters(this, { + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", + SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(this, { + BackgroundPageThumbs: "resource://gre/modules/BackgroundPageThumbs.jsm", + PageThumbs: "resource://gre/modules/PageThumbs.jsm", + PageThumbsStorage: "resource://gre/modules/PageThumbs.jsm", +}); + +XPCOMUtils.defineLazyServiceGetter( + this, + "PageThumbsStorageService", + "@mozilla.org/thumbnails/pagethumbs-service;1", + "nsIPageThumbsStorageService" +); + +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 + ); +}); + +/** + * 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. + */ +async 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. + await PageThumbs.captureAndStore(browser); + let [r, g, b] = await retrieveImageDataForURL(browser.currentURI.spec); + is("" + [r, g, b], "" + [aRed, aGreen, aBlue], aMessage); +} + +/** + * 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. + * @returns Promise + */ +async function retrieveImageDataForURL(aURL) { + 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); + await BrowserTestUtils.waitForEvent(img, "load", true); + + 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); + return ctx.getImageData(0, 0, 100, 100).data; +} + +/** + * Returns the file of the thumbnail with the given URL. + * @param aURL The URL of the thumbnail. + */ +function thumbnailFile(aURL) { + return new FileUtils.File(PageThumbsStorageService.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. + */ +async function promiseAddVisitsAndRepopulateNewTabLinks(aPlaceInfo) { + await PlacesTestUtils.addVisits(makeURI(aPlaceInfo)); + await new Promise(resolve => { + NewTabUtils.links.populateCache(resolve, true); + }); +} + +/** + * Resolves a Promise 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. + * @returns Promise + */ +function whenFileExists(aURL) { + return TestUtils.waitForCondition( + () => { + return thumbnailExists(aURL); + }, + `Waiting for ${aURL} to exist.`, + 1000, + 50 + ); +} + +/** + * Resolves a Promise when the given file has been removed. + * Keeps trying until the file is removed. + * + * @param aFile The file that is being removed + * @returns Promise + */ +function whenFileRemoved(aFile) { + return TestUtils.waitForCondition( + () => { + return !aFile.exists(); + }, + `Waiting for ${aFile.leafName} to not exist.`, + 1000, + 50 + ); +} + +/** + * 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) { + return bgCaptureWithMethod("capture", aURL, aOptions); +} + +function bgCaptureIfMissing(aURL, aOptions) { + return bgCaptureWithMethod("captureIfMissing", aURL, aOptions); +} + +/** + * Queues a BackgroundPageThumbs capture with the supplied method. + * + * @param {String} aMethodName One of the method names on BackgroundPageThumbs + * for capturing thumbnails. Example: "capture", "captureIfMissing". + * @param {String} aURL The URL of the page to capture. + * @param {Object} aOptions The options object to pass to BackgroundPageThumbs. + * + * @returns {Promise} + * @resolves {Array} Resolves once the capture has completed with an Array of + * results. The first element of the Array is the URL of the captured page, + * and the second element is the completion reason from the BackgroundPageThumbs + * module. + */ +function bgCaptureWithMethod(aMethodName, aURL, aOptions = {}) { + // We'll get oranges if the expiration filter removes the file during the + // test. + dontExpireThumbnailURLs([aURL]); + + return new Promise(resolve => { + let wrappedDoneFn = aOptions.onDone; + aOptions.onDone = (url, doneReason) => { + if (wrappedDoneFn) { + wrappedDoneFn(url, doneReason); + } + resolve([url, doneReason]); + }; + + 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) { + 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"); + Services.obs.addObserver(observe, "page-thumbnail:error"); + }); +} 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 0000000000..576b27f1f3 --- /dev/null +++ b/toolkit/components/thumbnails/test/privacy_cache_control.sjs @@ -0,0 +1,17 @@ +/* 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/sample_image_blue_300x600.jpg b/toolkit/components/thumbnails/test/sample_image_blue_300x600.jpg Binary files differnew file mode 100644 index 0000000000..ad5b44ecbc --- /dev/null +++ b/toolkit/components/thumbnails/test/sample_image_blue_300x600.jpg diff --git a/toolkit/components/thumbnails/test/sample_image_green_1024x1024.jpg b/toolkit/components/thumbnails/test/sample_image_green_1024x1024.jpg Binary files differnew file mode 100644 index 0000000000..7d91079ecd --- /dev/null +++ b/toolkit/components/thumbnails/test/sample_image_green_1024x1024.jpg diff --git a/toolkit/components/thumbnails/test/sample_image_red_1920x1080.jpg b/toolkit/components/thumbnails/test/sample_image_red_1920x1080.jpg Binary files differnew file mode 100644 index 0000000000..7a2bec4d5e --- /dev/null +++ b/toolkit/components/thumbnails/test/sample_image_red_1920x1080.jpg 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 0000000000..0e0ec1f348 --- /dev/null +++ b/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js @@ -0,0 +1,49 @@ +"use strict"; + +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +// need profile so that PageThumbsStorageService can resolve the path to the underlying file +do_get_profile(); + +function run_test() { + // check the protocol handler implements the correct interface + let handler = Services.io.getProtocolHandler("moz-page-thumb"); + ok( + handler instanceof Ci.nsIProtocolHandler, + "moz-page-thumb handler provides a protocol handler interface" + ); + + // create a dummy loadinfo which we can hand to newChannel. + let dummyURI = Services.io.newURI("https://www.example.com/1"); + let dummyChannel = NetUtil.newChannel({ + uri: dummyURI, + loadUsingSystemPrincipal: true, + }); + let dummyLoadInfo = dummyChannel.loadInfo; + + // and check that the error cases work as specified + let badhost = Services.io.newURI( + "moz-page-thumb://wronghost/?url=http%3A%2F%2Fwww.mozilla.org%2F" + ); + Assert.throws( + () => handler.newChannel(badhost, dummyLoadInfo), + /NS_ERROR_NOT_AVAILABLE/i, + "moz-page-thumb object with wrong host must not resolve to a file path" + ); + + let badQuery = Services.io.newURI( + "moz-page-thumb://thumbnail/http%3A%2F%2Fwww.mozilla.org%2F" + ); + Assert.throws( + () => handler.newChannel(badQuery, dummyLoadInfo), + /NS_ERROR_NOT_AVAILABLE/i, + "moz-page-thumb object with malformed query parameters must not resolve to a file path" + ); + + let noURL = Services.io.newURI("moz-page-thumb://thumbnail/?badStuff"); + Assert.throws( + () => handler.newChannel(noURL, dummyLoadInfo), + /NS_ERROR_NOT_AVAILABLE/i, + "moz-page-thumb object without a URL parameter 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 0000000000..687e74765b --- /dev/null +++ b/toolkit/components/thumbnails/test/thumbnails_background.sjs @@ -0,0 +1,97 @@ +/* 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(";") + .includes("red") + ) { + resp.write( + '<html style="background: #f00;">' + setCookieScript + "</html>" + ); + resp.finish(); + return; + } + + if ( + req.hasHeader("Cookie") && + req + .getHeader("Cookie") + .split(";") + .includes("green") + ) { + 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 = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init( + function ding() { + resp.write("OK!"); + resp.finish(); + }, + opts.wait, + Ci.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_update.sjs b/toolkit/components/thumbnails/test/thumbnails_update.sjs new file mode 100644 index 0000000000..78716adff9 --- /dev/null +++ b/toolkit/components/thumbnails/test/thumbnails_update.sjs @@ -0,0 +1,57 @@ +/* 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 0000000000..de47db2398 --- /dev/null +++ b/toolkit/components/thumbnails/test/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = + +[test_thumbnails_interfaces.js] +skip-if = os == 'android' # xpcom interface not packaged |