summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/tests/browser')
-rw-r--r--toolkit/components/places/tests/browser/1601563-1.html20
-rw-r--r--toolkit/components/places/tests/browser/1601563-2.html3
-rw-r--r--toolkit/components/places/tests/browser/399606-history.go-0.html13
-rw-r--r--toolkit/components/places/tests/browser/399606-httprefresh.html8
-rw-r--r--toolkit/components/places/tests/browser/399606-location.reload.html13
-rw-r--r--toolkit/components/places/tests/browser/399606-location.replace.html13
-rw-r--r--toolkit/components/places/tests/browser/399606-window.location.href.html14
-rw-r--r--toolkit/components/places/tests/browser/399606-window.location.html14
-rw-r--r--toolkit/components/places/tests/browser/461710_link_page-2.html13
-rw-r--r--toolkit/components/places/tests/browser/461710_link_page-3.html13
-rw-r--r--toolkit/components/places/tests/browser/461710_link_page.html13
-rw-r--r--toolkit/components/places/tests/browser/461710_visited_page.html9
-rw-r--r--toolkit/components/places/tests/browser/begin.html10
-rw-r--r--toolkit/components/places/tests/browser/browser.ini88
-rw-r--r--toolkit/components/places/tests/browser/browser_bug1601563.js40
-rw-r--r--toolkit/components/places/tests/browser/browser_bug399606.js50
-rw-r--r--toolkit/components/places/tests/browser/browser_bug461710.js89
-rw-r--r--toolkit/components/places/tests/browser/browser_bug646422.js44
-rw-r--r--toolkit/components/places/tests/browser/browser_bug680727.js127
-rw-r--r--toolkit/components/places/tests/browser/browser_double_redirect.js83
-rw-r--r--toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js43
-rw-r--r--toolkit/components/places/tests/browser/browser_history_post.js35
-rw-r--r--toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js177
-rw-r--r--toolkit/components/places/tests/browser/browser_notfound.js38
-rw-r--r--toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js41
-rw-r--r--toolkit/components/places/tests/browser/browser_redirect.js149
-rw-r--r--toolkit/components/places/tests/browser/browser_redirect_self.js51
-rw-r--r--toolkit/components/places/tests/browser/browser_settitle.js48
-rw-r--r--toolkit/components/places/tests/browser/browser_visited_notfound.js60
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri.js100
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri_nohistory.js44
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js63
-rw-r--r--toolkit/components/places/tests/browser/empty_page.html8
-rw-r--r--toolkit/components/places/tests/browser/favicon-normal16.pngbin0 -> 286 bytes
-rw-r--r--toolkit/components/places/tests/browser/favicon-normal32.pngbin0 -> 344 bytes
-rw-r--r--toolkit/components/places/tests/browser/favicon.html13
-rw-r--r--toolkit/components/places/tests/browser/final.html10
-rw-r--r--toolkit/components/places/tests/browser/head.js74
-rw-r--r--toolkit/components/places/tests/browser/history_post.html12
-rw-r--r--toolkit/components/places/tests/browser/history_post.sjs5
-rw-r--r--toolkit/components/places/tests/browser/previews/browser.ini11
-rw-r--r--toolkit/components/places/tests/browser/previews/browser_thumbnails.js174
-rw-r--r--toolkit/components/places/tests/browser/redirect-target.html1
-rw-r--r--toolkit/components/places/tests/browser/redirect.sjs13
-rw-r--r--toolkit/components/places/tests/browser/redirect_once.sjs13
-rw-r--r--toolkit/components/places/tests/browser/redirect_self.sjs27
-rw-r--r--toolkit/components/places/tests/browser/redirect_thrice.sjs9
-rw-r--r--toolkit/components/places/tests/browser/redirect_twice.sjs9
-rw-r--r--toolkit/components/places/tests/browser/redirect_twice_perma.sjs9
-rw-r--r--toolkit/components/places/tests/browser/title1.html12
-rw-r--r--toolkit/components/places/tests/browser/title2.html13
51 files changed, 1939 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/browser/1601563-1.html b/toolkit/components/places/tests/browser/1601563-1.html
new file mode 100644
index 0000000000..4f92778561
--- /dev/null
+++ b/toolkit/components/places/tests/browser/1601563-1.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>First title</title>
+<iframe srcdoc=""></iframe>
+<script>
+onload = function() {
+ // This iframe doc shouldn't override our title.
+ let doc = document.querySelector("iframe").contentDocument;
+ doc.open();
+ doc.write("<title>This is not your title</title>Hello");
+ doc.close();
+
+ if (doc.title == "This is not your title") {
+ // Now navigate away so that the test has something to wait for to ensure the
+ // relevant code has run.
+ let link = document.createElement("a");
+ link.href = window.location.href.replace("-1.html", "-2.html");
+ link.click();
+ }
+}
+</script>
diff --git a/toolkit/components/places/tests/browser/1601563-2.html b/toolkit/components/places/tests/browser/1601563-2.html
new file mode 100644
index 0000000000..b1c000cd5a
--- /dev/null
+++ b/toolkit/components/places/tests/browser/1601563-2.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>Second title</title>
+Nothing to see here.
diff --git a/toolkit/components/places/tests/browser/399606-history.go-0.html b/toolkit/components/places/tests/browser/399606-history.go-0.html
new file mode 100644
index 0000000000..6e36aa23de
--- /dev/null
+++ b/toolkit/components/places/tests/browser/399606-history.go-0.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>history.go(0)</title>
+<script>
+setTimeout(function() {
+ history.go(0);
+}, 1000);
+</script>
+</head>
+<body>
+Testing history.go(0)
+</body>
+</html>
diff --git a/toolkit/components/places/tests/browser/399606-httprefresh.html b/toolkit/components/places/tests/browser/399606-httprefresh.html
new file mode 100644
index 0000000000..e43455ee05
--- /dev/null
+++ b/toolkit/components/places/tests/browser/399606-httprefresh.html
@@ -0,0 +1,8 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+
+<meta http-equiv="refresh" content="1">
+<title>httprefresh</title>
+</head><body>
+Testing httprefresh
+</body></html>
diff --git a/toolkit/components/places/tests/browser/399606-location.reload.html b/toolkit/components/places/tests/browser/399606-location.reload.html
new file mode 100644
index 0000000000..54eefab1c3
--- /dev/null
+++ b/toolkit/components/places/tests/browser/399606-location.reload.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>location.reload()</title>
+<script>
+setTimeout(function() {
+ location.reload();
+}, 100);
+</script>
+</head>
+<body>
+Testing location.reload();
+</body>
+</html>
diff --git a/toolkit/components/places/tests/browser/399606-location.replace.html b/toolkit/components/places/tests/browser/399606-location.replace.html
new file mode 100644
index 0000000000..8a72c96722
--- /dev/null
+++ b/toolkit/components/places/tests/browser/399606-location.replace.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>location.replace</title>
+<script>
+setTimeout(function() {
+ location.replace(window.location.href);
+}, 1000);
+</script>
+</head>
+<body>
+Testing location.replace
+</body>
+</html>
diff --git a/toolkit/components/places/tests/browser/399606-window.location.href.html b/toolkit/components/places/tests/browser/399606-window.location.href.html
new file mode 100644
index 0000000000..490b08e40c
--- /dev/null
+++ b/toolkit/components/places/tests/browser/399606-window.location.href.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>window.location.href</title>
+<script>
+setTimeout(function() {
+ // eslint-disable-next-line no-self-assign
+ window.location.href = window.location.href;
+}, 1000);
+</script>
+</head>
+<body>
+Testing window.location.href
+</body>
+</html>
diff --git a/toolkit/components/places/tests/browser/399606-window.location.html b/toolkit/components/places/tests/browser/399606-window.location.html
new file mode 100644
index 0000000000..b84366cf8d
--- /dev/null
+++ b/toolkit/components/places/tests/browser/399606-window.location.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>window.location</title>
+<script>
+setTimeout(function() {
+ // eslint-disable-next-line no-self-assign
+ window.location = window.location;
+}, 1000);
+</script>
+</head>
+<body>
+Testing window.location
+</body>
+</html>
diff --git a/toolkit/components/places/tests/browser/461710_link_page-2.html b/toolkit/components/places/tests/browser/461710_link_page-2.html
new file mode 100644
index 0000000000..726373f83e
--- /dev/null
+++ b/toolkit/components/places/tests/browser/461710_link_page-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Link page 2</title>
+ <style type="text/css">
+ a:link { color: #0000ff; }
+ a:visited { color: #ff0000; }
+ </style>
+ </head>
+ <body>
+ <p><a href="461710_visited_page.html" id="link">Link to the second visited page</a></p>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/461710_link_page-3.html b/toolkit/components/places/tests/browser/461710_link_page-3.html
new file mode 100644
index 0000000000..d465cf79c6
--- /dev/null
+++ b/toolkit/components/places/tests/browser/461710_link_page-3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Link page 3</title>
+ <style type="text/css">
+ a:link { color: #0000ff; }
+ a:visited { color: #ff0000; }
+ </style>
+ </head>
+ <body>
+ <p><a href="461710_visited_page.html" id="link">Link to the third visited page</a></p>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/461710_link_page.html b/toolkit/components/places/tests/browser/461710_link_page.html
new file mode 100644
index 0000000000..05189fa41b
--- /dev/null
+++ b/toolkit/components/places/tests/browser/461710_link_page.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Link page</title>
+ <style type="text/css">
+ a:link { color: #0000ff; }
+ a:visited { color: #ff0000; }
+ </style>
+ </head>
+ <body>
+ <p><a href="461710_visited_page.html" id="link">Link to the visited page</a></p>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/461710_visited_page.html b/toolkit/components/places/tests/browser/461710_visited_page.html
new file mode 100644
index 0000000000..3ff52f69c8
--- /dev/null
+++ b/toolkit/components/places/tests/browser/461710_visited_page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Visited page</title>
+ </head>
+ <body>
+ <p>This page is marked as visited</p>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/begin.html b/toolkit/components/places/tests/browser/begin.html
new file mode 100644
index 0000000000..da4c16dd25
--- /dev/null
+++ b/toolkit/components/places/tests/browser/begin.html
@@ -0,0 +1,10 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+ <body>
+ <a id="clickme" href="redirect_twice.sjs">Redirect twice</a>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/browser.ini b/toolkit/components/places/tests/browser/browser.ini
new file mode 100644
index 0000000000..33526ebb20
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser.ini
@@ -0,0 +1,88 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_bug399606.js]
+https_first_disabled = true
+support-files =
+ 399606-history.go-0.html
+ 399606-httprefresh.html
+ 399606-location.reload.html
+ 399606-location.replace.html
+ 399606-window.location.html
+ 399606-window.location.href.html
+[browser_bug461710.js]
+https_first_disabled = true
+support-files =
+ 461710_link_page-2.html
+ 461710_link_page-3.html
+ 461710_link_page.html
+ 461710_visited_page.html
+[browser_bug646422.js]
+https_first_disabled = true
+[browser_bug680727.js]
+https_first_disabled = true
+skip-if = verify
+[browser_bug1601563.js]
+https_first_disabled = true
+support-files =
+ 1601563-1.html
+ 1601563-2.html
+[browser_double_redirect.js]
+https_first_disabled = true
+support-files =
+ begin.html
+ final.html
+ redirect_once.sjs
+ redirect_twice.sjs
+[browser_favicon_privatebrowsing_perwindowpb.js]
+[browser_history_post.js]
+https_first_disabled = true
+support-files =
+ history_post.html
+ history_post.sjs
+[browser_notfound.js]
+[browser_onvisit_title_null_for_navigation.js]
+https_first_disabled = true
+skip-if = verify
+support-files =
+ empty_page.html
+[browser_redirect.js]
+support-files =
+ redirect.sjs
+ redirect-target.html
+[browser_redirect_self.js]
+support-files =
+ redirect_self.sjs
+[browser_multi_redirect_frecency.js]
+https_first_disabled = true
+support-files =
+ final.html
+ redirect_once.sjs
+ redirect_thrice.sjs
+ redirect_twice.sjs
+ redirect_twice_perma.sjs
+[browser_settitle.js]
+https_first_disabled = true
+support-files =
+ title1.html
+ title2.html
+[browser_visited_notfound.js]
+[browser_visituri.js]
+https_first_disabled = true
+support-files =
+ begin.html
+ final.html
+ redirect_once.sjs
+ redirect_twice.sjs
+[browser_visituri_nohistory.js]
+support-files =
+ begin.html
+ final.html
+ favicon-normal16.png
+ favicon-normal32.png
+[browser_visituri_privatebrowsing_perwindowpb.js]
+support-files =
+ begin.html
+ favicon.html
+ final.html
diff --git a/toolkit/components/places/tests/browser/browser_bug1601563.js b/toolkit/components/places/tests/browser/browser_bug1601563.js
new file mode 100644
index 0000000000..41e278ee54
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_bug1601563.js
@@ -0,0 +1,40 @@
+const PREFIX =
+ "http://example.com/tests/toolkit/components/places/tests/browser/1601563";
+
+function titleUpdate(pageUrl) {
+ let lastTitle = null;
+ return PlacesTestUtils.waitForNotification("page-title-changed", events => {
+ if (pageUrl != events[0].url) {
+ return false;
+ }
+ lastTitle = events[0].title;
+ return true;
+ }).then(() => {
+ return lastTitle;
+ });
+}
+
+add_task(async function () {
+ registerCleanupFunction(PlacesUtils.history.clear);
+ const FIRST_URL = PREFIX + "-1.html";
+ const SECOND_URL = PREFIX + "-2.html";
+ let firstTitlePromise = titleUpdate(FIRST_URL);
+ let secondTitlePromise = titleUpdate(SECOND_URL);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, FIRST_URL);
+
+ let firstTitle = await firstTitlePromise;
+ is(firstTitle, "First title", "First title should match the page");
+
+ let secondTitle = await secondTitlePromise;
+ is(secondTitle, "Second title", "Second title should match the page");
+
+ let entry = await PlacesUtils.history.fetch(FIRST_URL);
+ is(
+ entry.title,
+ firstTitle,
+ "Should not override first title with document.open()ed frame"
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/places/tests/browser/browser_bug399606.js b/toolkit/components/places/tests/browser/browser_bug399606.js
new file mode 100644
index 0000000000..f593d68528
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_bug399606.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function () {
+ registerCleanupFunction(PlacesUtils.history.clear);
+
+ const URIS = [
+ "http://example.com/tests/toolkit/components/places/tests/browser/399606-window.location.href.html",
+ "http://example.com/tests/toolkit/components/places/tests/browser/399606-history.go-0.html",
+ "http://example.com/tests/toolkit/components/places/tests/browser/399606-location.replace.html",
+ "http://example.com/tests/toolkit/components/places/tests/browser/399606-location.reload.html",
+ "http://example.com/tests/toolkit/components/places/tests/browser/399606-httprefresh.html",
+ "http://example.com/tests/toolkit/components/places/tests/browser/399606-window.location.html",
+ ];
+
+ // Create and add history observer.
+ let count = 0;
+ let expectedURI = null;
+ function onVisitsListener(aEvents) {
+ for (let event of aEvents) {
+ info("Received onVisits: " + event.url);
+ if (event.url == expectedURI) {
+ count++;
+ }
+ }
+ }
+
+ async function promiseLoadedThreeTimes(uri) {
+ count = 0;
+ expectedURI = uri;
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ PlacesObservers.addListener(["page-visited"], onVisitsListener);
+ BrowserTestUtils.loadURIString(gBrowser, uri);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
+ PlacesObservers.removeListener(["page-visited"], onVisitsListener);
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ for (let uri of URIS) {
+ await promiseLoadedThreeTimes(uri);
+ is(
+ count,
+ 1,
+ "'page-visited' has been received right number of times for " + uri
+ );
+ }
+});
diff --git a/toolkit/components/places/tests/browser/browser_bug461710.js b/toolkit/components/places/tests/browser/browser_bug461710.js
new file mode 100644
index 0000000000..6815860929
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_bug461710.js
@@ -0,0 +1,89 @@
+const kRed = "rgb(255, 0, 0)";
+const kBlue = "rgb(0, 0, 255)";
+
+const prefix =
+ "http://example.com/tests/toolkit/components/places/tests/browser/461710_";
+
+add_task(async function () {
+ registerCleanupFunction(PlacesUtils.history.clear);
+ let normalWindow = await BrowserTestUtils.openNewBrowserWindow();
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let tests = [
+ {
+ private: false,
+ topic: "uri-visit-saved",
+ subtest: "visited_page.html",
+ },
+ {
+ private: false,
+ subtest: "link_page.html",
+ color: kRed,
+ message: "Visited link coloring should work outside of private mode",
+ },
+ {
+ private: true,
+ subtest: "link_page-2.html",
+ color: kBlue,
+ message: "Visited link coloring should not work inside of private mode",
+ },
+ {
+ private: false,
+ subtest: "link_page-3.html",
+ color: kRed,
+ message: "Visited link coloring should work outside of private mode",
+ },
+ ];
+
+ let uri = Services.io.newURI(prefix + tests[0].subtest);
+ for (let test of tests) {
+ info(test.subtest);
+ let promise = null;
+ if (test.topic) {
+ promise = TestUtils.topicObserved(test.topic, subject =>
+ uri.equals(subject.QueryInterface(Ci.nsIURI))
+ );
+ }
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: test.private ? privateWindow.gBrowser : normalWindow.gBrowser,
+ url: prefix + test.subtest,
+ },
+ async function (browser) {
+ if (promise) {
+ await promise;
+ }
+
+ if (test.color) {
+ // In e10s waiting for visited-status-resolution is not enough to ensure links
+ // have been updated, because it only tells us that messages to update links
+ // have been dispatched. We must still wait for the actual links to update.
+ await TestUtils.waitForCondition(async function () {
+ let color = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ let elem = content.document.getElementById("link");
+ return content.windowUtils.getVisitedDependentComputedStyle(
+ elem,
+ "",
+ "color"
+ );
+ }
+ );
+ return color == test.color;
+ }, test.message);
+ // The harness will consider the test as failed overall if there were no
+ // passes or failures, so record it as a pass.
+ ok(true, test.message);
+ }
+ }
+ );
+ }
+
+ let promisePBExit = TestUtils.topicObserved("last-pb-context-exited");
+ await BrowserTestUtils.closeWindow(privateWindow);
+ await promisePBExit;
+ await BrowserTestUtils.closeWindow(normalWindow);
+});
diff --git a/toolkit/components/places/tests/browser/browser_bug646422.js b/toolkit/components/places/tests/browser/browser_bug646422.js
new file mode 100644
index 0000000000..cb6512ed4e
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_bug646422.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for Bug 646224. Make sure that after changing the URI via
+ * history.pushState, the history service has a title stored for the new URI.
+ **/
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com"
+ );
+
+ const newTitlePromise = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => /new_page$/.test(events[0].url)
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let title = content.document.title;
+ content.history.pushState("", "", "new_page");
+ Assert.ok(title, "Content window should initially have a title.");
+ });
+
+ const events = await newTitlePromise;
+ const newtitle = events[0].title;
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ newtitle }],
+ async function (args) {
+ Assert.equal(
+ args.newtitle,
+ content.document.title,
+ "Title after pushstate."
+ );
+ }
+ );
+
+ await PlacesUtils.history.clear();
+ gBrowser.removeTab(tab);
+});
diff --git a/toolkit/components/places/tests/browser/browser_bug680727.js b/toolkit/components/places/tests/browser/browser_bug680727.js
new file mode 100644
index 0000000000..2fe2377d34
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_bug680727.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Ensure that clicking the button in the Offline mode neterror page updates
+ global history. See bug 680727. */
+/* TEST_PATH=toolkit/components/places/tests/browser/browser_bug680727.js make -C $(OBJDIR) mochitest-browser-chrome */
+
+const kUniqueURI = Services.io.newURI("http://mochi.test:8888/#bug_680727");
+var proxyPrefValue;
+var ourTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Tests always connect to localhost, and per bug 87717, localhost is now
+ // reachable in offline mode. To avoid this, disable any proxy.
+ proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+ Services.prefs.setIntPref("network.proxy.type", 0);
+
+ // Clear network cache.
+ Services.cache2.clear();
+
+ // Go offline, expecting the error page.
+ Services.io.offline = true;
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser).then(tab => {
+ ourTab = tab;
+ BrowserTestUtils.browserLoaded(
+ ourTab.linkedBrowser,
+ false,
+ null,
+ true
+ ).then(errorListener);
+ BrowserTestUtils.loadURIString(ourTab.linkedBrowser, kUniqueURI.spec);
+ });
+}
+
+// ------------------------------------------------------------------------------
+// listen to loading the neterror page. (offline mode)
+function errorListener() {
+ ok(Services.io.offline, "Services.io.offline is true.");
+
+ // This is an error page.
+ SpecialPowers.spawn(ourTab.linkedBrowser, [kUniqueURI.spec], function (uri) {
+ Assert.equal(
+ content.document.documentURI.substring(0, 27),
+ "about:neterror?e=netOffline",
+ "Document URI is the error page."
+ );
+
+ // But location bar should show the original request.
+ Assert.equal(
+ content.location.href,
+ uri,
+ "Docshell URI is the original URI."
+ );
+ }).then(() => {
+ // Global history does not record URI of a failed request.
+ PlacesTestUtils.promiseAsyncUpdates().then(() => {
+ PlacesUtils.history.hasVisits(kUniqueURI).then(isVisited => {
+ errorAsyncListener(kUniqueURI, isVisited);
+ });
+ });
+ });
+}
+
+function errorAsyncListener(aURI, aIsVisited) {
+ ok(
+ kUniqueURI.equals(aURI) && !aIsVisited,
+ "The neterror page is not listed in global history."
+ );
+
+ Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
+
+ // Now press the "Try Again" button, with offline mode off.
+ Services.io.offline = false;
+
+ BrowserTestUtils.browserLoaded(ourTab.linkedBrowser, false, null, true).then(
+ reloadListener
+ );
+
+ SpecialPowers.spawn(ourTab.linkedBrowser, [], function () {
+ Assert.ok(
+ content.document.querySelector("#netErrorButtonContainer > .try-again"),
+ "The error page has got a .try-again element"
+ );
+ content.document
+ .querySelector("#netErrorButtonContainer > .try-again")
+ .click();
+ });
+}
+
+// ------------------------------------------------------------------------------
+// listen to reload of neterror.
+function reloadListener() {
+ // This listener catches "DOMContentLoaded" on being called
+ // nsIWPL::onLocationChange(...). That is right *AFTER*
+ // IHistory::VisitURI(...) is called.
+ ok(!Services.io.offline, "Services.io.offline is false.");
+
+ SpecialPowers.spawn(ourTab.linkedBrowser, [kUniqueURI.spec], function (uri) {
+ // This is not an error page.
+ Assert.equal(
+ content.document.documentURI,
+ uri,
+ "Document URI is not the offline-error page, but the original URI."
+ );
+ }).then(() => {
+ // Check if global history remembers the successfully-requested URI.
+ PlacesTestUtils.promiseAsyncUpdates().then(() => {
+ PlacesUtils.history.hasVisits(kUniqueURI).then(isVisited => {
+ reloadAsyncListener(kUniqueURI, isVisited);
+ });
+ });
+ });
+}
+
+function reloadAsyncListener(aURI, aIsVisited) {
+ ok(kUniqueURI.equals(aURI) && aIsVisited, "We have visited the URI.");
+ PlacesUtils.history.clear().then(finish);
+}
+
+registerCleanupFunction(async function () {
+ Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
+ Services.io.offline = false;
+ BrowserTestUtils.removeTab(ourTab);
+});
diff --git a/toolkit/components/places/tests/browser/browser_double_redirect.js b/toolkit/components/places/tests/browser/browser_double_redirect.js
new file mode 100644
index 0000000000..435bd86f19
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_double_redirect.js
@@ -0,0 +1,83 @@
+// Test for bug 411966.
+// When a page redirects multiple times, from_visit should point to the
+// previous visit in the chain, not to the first visit in the chain.
+
+add_task(async function () {
+ await PlacesUtils.history.clear();
+
+ const BASE_URL =
+ "http://example.com/tests/toolkit/components/places/tests/browser/";
+ const TEST_URI = NetUtil.newURI(BASE_URL + "begin.html");
+ const FIRST_REDIRECTING_URI = NetUtil.newURI(BASE_URL + "redirect_twice.sjs");
+ const FINAL_URI = NetUtil.newURI(
+ "http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html"
+ );
+
+ let promiseVisits = new Promise(resolve => {
+ let observer = {
+ _notified: [],
+ onVisit(uri, id, time, referrerId, transition) {
+ info("Received onVisit: " + uri);
+ this._notified.push(uri);
+
+ if (uri != FINAL_URI.spec) {
+ return;
+ }
+
+ is(this._notified.length, 4);
+ PlacesObservers.removeListener(["page-visited"], this.handleEvents);
+
+ (async function () {
+ // Get all pages visited from the original typed one
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute(
+ `SELECT url FROM moz_historyvisits
+ JOIN moz_places h ON h.id = place_id
+ WHERE from_visit IN
+ (SELECT v.id FROM moz_historyvisits v
+ JOIN moz_places p ON p.id = v.place_id
+ WHERE p.url_hash = hash(:url) AND p.url = :url)
+ `,
+ { url: TEST_URI.spec }
+ );
+
+ is(rows.length, 1, "Found right number of visits");
+ let visitedUrl = rows[0].getResultByName("url");
+ // Check that redirect from_visit is not from the original typed one
+ is(
+ visitedUrl,
+ FIRST_REDIRECTING_URI.spec,
+ "Check referrer for " + visitedUrl
+ );
+
+ resolve();
+ })();
+ },
+ handleEvents(events) {
+ is(events.length, 1, "Right number of visits notified");
+ is(events[0].type, "page-visited");
+ let { url, visitId, visitTime, referringVisitId, transitionType } =
+ events[0];
+ this.onVisit(url, visitId, visitTime, referringVisitId, transitionType);
+ },
+ };
+ observer.handleEvents = observer.handleEvents.bind(observer);
+ PlacesObservers.addListener(["page-visited"], observer.handleEvents);
+ });
+
+ PlacesUtils.history.markPageAsTyped(TEST_URI);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_URI.spec,
+ },
+ async function (browser) {
+ // Load begin page, click link on page to record visits.
+ await BrowserTestUtils.synthesizeMouseAtCenter("#clickme", {}, browser);
+
+ await promiseVisits;
+ }
+ );
+
+ await PlacesUtils.history.clear();
+});
diff --git a/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js b/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js
new file mode 100644
index 0000000000..35450f0be6
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ const pageURI =
+ "http://example.org/tests/toolkit/components/places/tests/browser/favicon.html";
+ let windowsToClose = [];
+
+ registerCleanupFunction(function () {
+ windowsToClose.forEach(function (aWin) {
+ aWin.close();
+ });
+ });
+
+ function testOnWindow(aIsPrivate, aCallback) {
+ whenNewWindowLoaded({ private: aIsPrivate }, function (aWin) {
+ windowsToClose.push(aWin);
+ executeSoon(() => aCallback(aWin));
+ });
+ }
+
+ function waitForTabLoad(aWin, aCallback) {
+ BrowserTestUtils.browserLoaded(aWin.gBrowser.selectedBrowser).then(
+ aCallback
+ );
+ BrowserTestUtils.loadURIString(aWin.gBrowser.selectedBrowser, pageURI);
+ }
+
+ testOnWindow(true, function (win) {
+ waitForTabLoad(win, function () {
+ PlacesUtils.favicons.getFaviconURLForPage(
+ NetUtil.newURI(pageURI),
+ function (uri, dataLen, data, mimeType) {
+ is(uri, null, "No result should be found");
+ finish();
+ }
+ );
+ });
+ });
+}
diff --git a/toolkit/components/places/tests/browser/browser_history_post.js b/toolkit/components/places/tests/browser/browser_history_post.js
new file mode 100644
index 0000000000..a62592516f
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_history_post.js
@@ -0,0 +1,35 @@
+const PAGE_URI =
+ "http://example.com/tests/toolkit/components/places/tests/browser/history_post.html";
+const SJS_URI = NetUtil.newURI(
+ "http://example.com/tests/toolkit/components/places/tests/browser/history_post.sjs"
+);
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_URI },
+ async function (aBrowser) {
+ await SpecialPowers.spawn(aBrowser, [], async function () {
+ let doc = content.document;
+ let submit = doc.getElementById("submit");
+ let iframe = doc.getElementById("post_iframe");
+ let p = new Promise((resolve, reject) => {
+ iframe.addEventListener(
+ "load",
+ function () {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ submit.click();
+ await p;
+ });
+ let visited = await PlacesUtils.history.hasVisits(SJS_URI);
+ ok(!visited, "The POST page should not be added to history");
+ ok(
+ !(await PlacesTestUtils.isPageInDB(SJS_URI.spec)),
+ "The page should not be in the database"
+ );
+ }
+ );
+});
diff --git a/toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js b/toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js
new file mode 100644
index 0000000000..a406422a2f
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ROOT_URI =
+ "http://mochi.test:8888/tests/toolkit/components/places/tests/browser/";
+const REDIRECT_URI = Services.io.newURI(ROOT_URI + "redirect_thrice.sjs");
+const INTERMEDIATE_URI_1 = Services.io.newURI(
+ ROOT_URI + "redirect_twice_perma.sjs"
+);
+const INTERMEDIATE_URI_2 = Services.io.newURI(ROOT_URI + "redirect_once.sjs");
+const TARGET_URI = Services.io.newURI(
+ "http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html"
+);
+
+const REDIRECT_SOURCE_VISIT_BONUS = Services.prefs.getIntPref(
+ "places.frecency.redirectSourceVisitBonus"
+);
+const PERM_REDIRECT_VISIT_BONUS = Services.prefs.getIntPref(
+ "places.frecency.permRedirectVisitBonus"
+);
+const TYPED_VISIT_BONUS = Services.prefs.getIntPref(
+ "places.frecency.typedVisitBonus"
+);
+
+// Ensure that decay frecency doesn't kick in during tests (as a result
+// of idle-daily).
+Services.prefs.setCharPref("places.frecency.decayRate", "1.0");
+
+registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("places.frecency.decayRate");
+ await PlacesUtils.history.clear();
+});
+
+async function check_uri(uri, frecency, hidden) {
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
+ url: uri,
+ }),
+ frecency,
+ "Frecency of the page is the expected one"
+ );
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "hidden", {
+ url: uri,
+ }),
+ hidden,
+ "Hidden value of the page is the expected one"
+ );
+}
+
+async function waitVisitedNotifications() {
+ let redirectNotified = false;
+ await PlacesTestUtils.waitForNotification("page-visited", visits => {
+ is(visits.length, 1, "Was notified for the right number of visits.");
+ let { url } = visits[0];
+ info("Received 'page-visited': " + url);
+ if (url == REDIRECT_URI.spec) {
+ redirectNotified = true;
+ }
+ return url == TARGET_URI.spec;
+ });
+ return redirectNotified;
+}
+
+let firstRedirectBonus = 0;
+let nextRedirectBonus = 0;
+let targetBonus = 0;
+
+add_task(async function test_multiple_redirect() {
+ // The redirect source bonus overrides the link bonus.
+ let visitedPromise = waitVisitedNotifications();
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: REDIRECT_URI.spec,
+ },
+ async function () {
+ info("Waiting for onVisits");
+ let redirectNotified = await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ firstRedirectBonus += REDIRECT_SOURCE_VISIT_BONUS;
+ await check_uri(REDIRECT_URI, firstRedirectBonus, 1);
+ nextRedirectBonus += REDIRECT_SOURCE_VISIT_BONUS;
+ await check_uri(INTERMEDIATE_URI_1, nextRedirectBonus, 1);
+ await check_uri(INTERMEDIATE_URI_2, nextRedirectBonus, 1);
+ // TODO Bug 487813 - This should be TYPED_VISIT_BONUS, however as we don't
+ // currently track redirects across multiple redirects, we fallback to the
+ // PERM_REDIRECT_VISIT_BONUS.
+ targetBonus += PERM_REDIRECT_VISIT_BONUS;
+ await check_uri(TARGET_URI, targetBonus, 0);
+ }
+ );
+});
+
+add_task(async function test_multiple_redirect_typed() {
+ // The typed bonus wins because the redirect is permanent.
+ PlacesUtils.history.markPageAsTyped(REDIRECT_URI);
+ let visitedPromise = waitVisitedNotifications();
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: REDIRECT_URI.spec,
+ },
+ async function () {
+ info("Waiting for onVisits");
+ let redirectNotified = await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ firstRedirectBonus += TYPED_VISIT_BONUS;
+ await check_uri(REDIRECT_URI, firstRedirectBonus, 1);
+ nextRedirectBonus += REDIRECT_SOURCE_VISIT_BONUS;
+ await check_uri(INTERMEDIATE_URI_1, nextRedirectBonus, 1);
+ await check_uri(INTERMEDIATE_URI_2, nextRedirectBonus, 1);
+ // TODO Bug 487813 - This should be TYPED_VISIT_BONUS, however as we don't
+ // currently track redirects across multiple redirects, we fallback to the
+ // PERM_REDIRECT_VISIT_BONUS.
+ targetBonus += PERM_REDIRECT_VISIT_BONUS;
+ await check_uri(TARGET_URI, targetBonus, 0);
+ }
+ );
+});
+
+add_task(async function test_second_typed_visit() {
+ // The typed bonus wins because the redirect is permanent.
+ PlacesUtils.history.markPageAsTyped(REDIRECT_URI);
+ let visitedPromise = waitVisitedNotifications();
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: REDIRECT_URI.spec,
+ },
+ async function () {
+ info("Waiting for onVisits");
+ let redirectNotified = await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ firstRedirectBonus += TYPED_VISIT_BONUS;
+ await check_uri(REDIRECT_URI, firstRedirectBonus, 1);
+ nextRedirectBonus += REDIRECT_SOURCE_VISIT_BONUS;
+ await check_uri(INTERMEDIATE_URI_1, nextRedirectBonus, 1);
+ await check_uri(INTERMEDIATE_URI_2, nextRedirectBonus, 1);
+ // TODO Bug 487813 - This should be TYPED_VISIT_BONUS, however as we don't
+ // currently track redirects across multiple redirects, we fallback to the
+ // PERM_REDIRECT_VISIT_BONUS.
+ targetBonus += PERM_REDIRECT_VISIT_BONUS;
+ await check_uri(TARGET_URI, targetBonus, 0);
+ }
+ );
+});
+
+add_task(async function test_subsequent_link_visit() {
+ // Another non typed visit.
+ let visitedPromise = waitVisitedNotifications();
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: REDIRECT_URI.spec,
+ },
+ async function () {
+ info("Waiting for onVisits");
+ let redirectNotified = await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ firstRedirectBonus += REDIRECT_SOURCE_VISIT_BONUS;
+ await check_uri(REDIRECT_URI, firstRedirectBonus, 1);
+ nextRedirectBonus += REDIRECT_SOURCE_VISIT_BONUS;
+ await check_uri(INTERMEDIATE_URI_1, nextRedirectBonus, 1);
+ await check_uri(INTERMEDIATE_URI_2, nextRedirectBonus, 1);
+ // TODO Bug 487813 - This should be TYPED_VISIT_BONUS, however as we don't
+ // currently track redirects across multiple redirects, we fallback to the
+ // PERM_REDIRECT_VISIT_BONUS.
+ targetBonus += PERM_REDIRECT_VISIT_BONUS;
+ await check_uri(TARGET_URI, targetBonus, 0);
+ }
+ );
+});
diff --git a/toolkit/components/places/tests/browser/browser_notfound.js b/toolkit/components/places/tests/browser/browser_notfound.js
new file mode 100644
index 0000000000..6f53866018
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_notfound.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function () {
+ const TEST_URL = "http://mochi.test:8888/notFoundPage.html";
+
+ // Used to verify errors are not marked as typed.
+ PlacesUtils.history.markPageAsTyped(NetUtil.newURI(TEST_URL));
+
+ // Create and add history observer.
+ let visitedPromise = new Promise(resolve => {
+ function listener(aEvents) {
+ is(aEvents.length, 1, "Right number of visits notified");
+ is(aEvents[0].type, "page-visited");
+ let uri = NetUtil.newURI(aEvents[0].url);
+ PlacesObservers.removeListener(["page-visited"], listener);
+ info("Received 'page-visited': " + uri.spec);
+ fieldForUrl(uri, "frecency", function (aFrecency) {
+ is(aFrecency, 0, "Frecency should be 0");
+ fieldForUrl(uri, "hidden", function (aHidden) {
+ is(aHidden, 0, "Page should not be hidden");
+ fieldForUrl(uri, "typed", function (aTyped) {
+ is(aTyped, 0, "page should not be marked as typed");
+ resolve();
+ });
+ });
+ });
+ }
+ PlacesObservers.addListener(["page-visited"], listener);
+ });
+
+ let newTabPromise = BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ await Promise.all([visitedPromise, newTabPromise]);
+
+ await PlacesUtils.history.clear();
+ gBrowser.removeCurrentTab();
+});
diff --git a/toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js b/toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js
new file mode 100644
index 0000000000..a7c583975a
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js
@@ -0,0 +1,41 @@
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+add_task(async function checkTitleNotificationForNavigation() {
+ const EXPECTED_URL = Services.io.newURI(TEST_PATH + "empty_page.html");
+
+ const promiseVisit = PlacesTestUtils.waitForNotification(
+ "page-visited",
+ events => events[0].url === EXPECTED_URL.spec
+ );
+
+ const promiseTitle = PlacesTestUtils.waitForNotification(
+ "page-title-changed",
+ events => events[0].url === EXPECTED_URL.spec
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ EXPECTED_URL.spec
+ );
+
+ const visitEvents = await promiseVisit;
+ Assert.equal(visitEvents.length, 1, "Right number of visits notified");
+ Assert.equal(visitEvents[0].type, "page-visited");
+ info("'page-visited': " + visitEvents[0].url);
+ Assert.equal(visitEvents[0].lastKnownTitle, null, "Should not have a title");
+
+ const titleEvents = await promiseTitle;
+ Assert.equal(titleEvents.length, 1, "Right number of title changed notified");
+ Assert.equal(titleEvents[0].type, "page-title-changed");
+ info("'page-title-changed': " + titleEvents[0].url);
+ Assert.equal(
+ titleEvents[0].title,
+ "I am an empty page",
+ "Should have correct title in titlechanged notification"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/places/tests/browser/browser_redirect.js b/toolkit/components/places/tests/browser/browser_redirect.js
new file mode 100644
index 0000000000..912b817ad1
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_redirect.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ROOT_URI =
+ "http://mochi.test:8888/tests/toolkit/components/places/tests/browser/";
+const REDIRECT_URI = Services.io.newURI(ROOT_URI + "redirect.sjs");
+const TARGET_URI = Services.io.newURI(ROOT_URI + "redirect-target.html");
+
+const REDIRECT_SOURCE_VISIT_BONUS = Services.prefs.getIntPref(
+ "places.frecency.redirectSourceVisitBonus"
+);
+const LINK_VISIT_BONUS = Services.prefs.getIntPref(
+ "places.frecency.linkVisitBonus"
+);
+const TYPED_VISIT_BONUS = Services.prefs.getIntPref(
+ "places.frecency.typedVisitBonus"
+);
+
+// Ensure that decay frecency doesn't kick in during tests (as a result
+// of idle-daily).
+Services.prefs.setCharPref("places.frecency.decayRate", "1.0");
+
+registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("places.frecency.decayRate");
+ await PlacesUtils.history.clear();
+});
+
+let redirectSourceFrecency = 0;
+let redirectTargetFrecency = 0;
+
+async function check_uri(uri, frecency, hidden) {
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
+ url: uri,
+ }),
+ frecency,
+ "Frecency of the page is the expected one"
+ );
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "hidden", {
+ url: uri,
+ }),
+ hidden,
+ "Hidden value of the page is the expected one"
+ );
+}
+
+add_task(async function redirect_check_new_typed_visit() {
+ // Used to verify the redirect bonus overrides the typed bonus.
+ PlacesUtils.history.markPageAsTyped(REDIRECT_URI);
+
+ redirectSourceFrecency += REDIRECT_SOURCE_VISIT_BONUS;
+ redirectTargetFrecency += TYPED_VISIT_BONUS;
+ let redirectNotified = false;
+
+ let visitedPromise = PlacesTestUtils.waitForNotification(
+ "page-visited",
+ visits => {
+ is(visits.length, 1, "Was notified for the right number of visits.");
+ let { url } = visits[0];
+ info("Received 'page-visited': " + url);
+ if (url == REDIRECT_URI.spec) {
+ redirectNotified = true;
+ }
+ return url == TARGET_URI.spec;
+ }
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ REDIRECT_URI.spec
+ );
+ info("Waiting for onVisits");
+ await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ await check_uri(REDIRECT_URI, redirectSourceFrecency, 1);
+ await check_uri(TARGET_URI, redirectTargetFrecency, 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function redirect_check_second_typed_visit() {
+ // A second visit with a typed url.
+ PlacesUtils.history.markPageAsTyped(REDIRECT_URI);
+
+ redirectSourceFrecency += REDIRECT_SOURCE_VISIT_BONUS;
+ redirectTargetFrecency += TYPED_VISIT_BONUS;
+ let redirectNotified = false;
+
+ let visitedPromise = PlacesTestUtils.waitForNotification(
+ "page-visited",
+ visits => {
+ is(visits.length, 1, "Was notified for the right number of visits.");
+ let { url } = visits[0];
+ info("Received 'page-visited': " + url);
+ if (url == REDIRECT_URI.spec) {
+ redirectNotified = true;
+ }
+ return url == TARGET_URI.spec;
+ }
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ REDIRECT_URI.spec
+ );
+ info("Waiting for onVisits");
+ await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ await check_uri(REDIRECT_URI, redirectSourceFrecency, 1);
+ await check_uri(TARGET_URI, redirectTargetFrecency, 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function redirect_check_subsequent_link_visit() {
+ // Another visit, but this time as a visited url.
+ redirectSourceFrecency += REDIRECT_SOURCE_VISIT_BONUS;
+ redirectTargetFrecency += LINK_VISIT_BONUS;
+ let redirectNotified = false;
+
+ let visitedPromise = PlacesTestUtils.waitForNotification(
+ "page-visited",
+ visits => {
+ is(visits.length, 1, "Was notified for the right number of visits.");
+ let { url } = visits[0];
+ info("Received 'page-visited': " + url);
+ if (url == REDIRECT_URI.spec) {
+ redirectNotified = true;
+ }
+ return url == TARGET_URI.spec;
+ }
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ REDIRECT_URI.spec
+ );
+ info("Waiting for onVisits");
+ await visitedPromise;
+ ok(redirectNotified, "The redirect should have been notified");
+
+ await check_uri(REDIRECT_URI, redirectSourceFrecency, 1);
+ await check_uri(TARGET_URI, redirectTargetFrecency, 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/places/tests/browser/browser_redirect_self.js b/toolkit/components/places/tests/browser/browser_redirect_self.js
new file mode 100644
index 0000000000..7ed7ee0af0
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_redirect_self.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests a page that redirects to itself. On the initial visit the page should
+ * be marked as hidden, but then the second visit should unhide it.
+ * This ensures that that the history anti-flooding system doesn't skip the
+ * second visit.
+ */
+
+add_task(async function () {
+ await PlacesUtils.history.clear();
+ Cc["@mozilla.org/browser/history;1"]
+ .getService(Ci.mozIAsyncHistory)
+ .clearCache();
+ const url =
+ "http://mochi.test:8888/tests/toolkit/components/places/tests/browser/redirect_self.sjs";
+ let visitCount = 0;
+ function onVisitsListener(events) {
+ visitCount++;
+ Assert.equal(events.length, 1, "Right number of visits notified");
+ Assert.equal(events[0].url, url, "Got a visit for the expected url");
+ if (visitCount == 1) {
+ Assert.ok(events[0].hidden, "The visit should be hidden");
+ } else {
+ Assert.ok(!events[0].hidden, "The visit should not be hidden");
+ }
+ }
+ PlacesObservers.addListener(["page-visited"], onVisitsListener);
+ registerCleanupFunction(async function () {
+ PlacesObservers.removeListener(["page-visited"], onVisitsListener);
+ await PlacesUtils.history.clear();
+ });
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async browser => {
+ await TestUtils.waitForCondition(() => visitCount == 2);
+ // Check that the visit is not hidden in the database.
+ Assert.ok(
+ !(await PlacesTestUtils.getDatabaseValue("moz_places", "hidden", {
+ url,
+ })),
+ "The url should not be hidden in the database"
+ );
+ }
+ );
+});
diff --git a/toolkit/components/places/tests/browser/browser_settitle.js b/toolkit/components/places/tests/browser/browser_settitle.js
new file mode 100644
index 0000000000..3519891bbe
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_settitle.js
@@ -0,0 +1,48 @@
+var conn = PlacesUtils.history.DBConnection;
+
+/**
+ * Gets a single column value from either the places or historyvisits table.
+ */
+function getColumn(table, column, url) {
+ var stmt = conn.createStatement(
+ `SELECT ${column} FROM ${table} WHERE url_hash = hash(:val) AND url = :val`
+ );
+ try {
+ stmt.params.val = url;
+ stmt.executeStep();
+ return stmt.row[column];
+ } finally {
+ stmt.finalize();
+ }
+}
+
+add_task(async function () {
+ // Make sure titles are correctly saved for a URI with the proper
+ // notifications.
+ const titleChangedPromise =
+ PlacesTestUtils.waitForNotification("page-title-changed");
+
+ const url1 =
+ "http://example.com/tests/toolkit/components/places/tests/browser/title1.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, url1);
+
+ const url2 =
+ "http://example.com/tests/toolkit/components/places/tests/browser/title2.html";
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url2);
+ await loadPromise;
+
+ const events = await titleChangedPromise;
+ is(
+ events[0].url,
+ "http://example.com/tests/toolkit/components/places/tests/browser/title2.html"
+ );
+ is(events[0].title, "Some title");
+ is(events[0].pageGuid, getColumn("moz_places", "guid", events[0].url));
+
+ const title = getColumn("moz_places", "title", events[0].url);
+ is(title, events[0].title);
+
+ gBrowser.removeCurrentTab();
+ await PlacesUtils.history.clear();
+});
diff --git a/toolkit/components/places/tests/browser/browser_visited_notfound.js b/toolkit/components/places/tests/browser/browser_visited_notfound.js
new file mode 100644
index 0000000000..1b97c307e2
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_visited_notfound.js
@@ -0,0 +1,60 @@
+add_task(async function test() {
+ const TEST_URL = "http://mochi.test:8888/notFoundPage.html";
+ // Ensure that decay frecency doesn't kick in during tests (as a result
+ // of idle-daily).
+ Services.prefs.setCharPref("places.frecency.decayRate", "1.0");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("places.frecency.decayRate");
+ BrowserTestUtils.removeTab(tab);
+ await PlacesUtils.history.clear();
+ });
+
+ // First add a visit to the page, this will ensure that later we skip
+ // updating the frecency for a newly not-found page.
+ await PlacesTestUtils.addVisits({ uri: TEST_URL });
+ let frecency = await PlacesTestUtils.getDatabaseValue(
+ "moz_places",
+ "frecency",
+ { url: TEST_URL }
+ );
+ is(frecency, 100, "Check initial frecency");
+
+ // Used to verify errors are not marked as typed.
+ PlacesUtils.history.markPageAsTyped(NetUtil.newURI(TEST_URL));
+
+ let promiseVisit = new Promise(resolve => {
+ function onVisits(events) {
+ PlacesObservers.removeListener(["page-visited"], onVisits);
+ is(events.length, 1, "Right number of visits");
+ is(events[0].type, "page-visited");
+ is(events[0].url, TEST_URL, "Check visited url");
+ resolve();
+ }
+ PlacesObservers.addListener(["page-visited"], onVisits);
+ });
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, TEST_URL);
+ await promiseVisit;
+
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
+ url: TEST_URL,
+ }),
+ frecency,
+ "Frecency should be unchanged"
+ );
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "hidden", {
+ url: TEST_URL,
+ }),
+ 0,
+ "Page should not be hidden"
+ );
+ is(
+ await PlacesTestUtils.getDatabaseValue("moz_places", "typed", {
+ url: TEST_URL,
+ }),
+ 0,
+ "page should not be marked as typed"
+ );
+});
diff --git a/toolkit/components/places/tests/browser/browser_visituri.js b/toolkit/components/places/tests/browser/browser_visituri.js
new file mode 100644
index 0000000000..6633ac188b
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_visituri.js
@@ -0,0 +1,100 @@
+/**
+ * One-time observer callback.
+ */
+function promiseObserve(name, checkFn) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subject) {
+ if (checkFn(subject)) {
+ Services.obs.removeObserver(observer, name);
+ resolve();
+ }
+ }, name);
+ });
+}
+
+var conn = PlacesUtils.history.DBConnection;
+
+/**
+ * Gets a single column value from either the places or historyvisits table.
+ */
+function getColumn(table, column, fromColumnName, fromColumnValue) {
+ let sql = `SELECT ${column}
+ FROM ${table}
+ WHERE ${fromColumnName} = :val
+ ${fromColumnName == "url" ? "AND url_hash = hash(:val)" : ""}
+ LIMIT 1`;
+ let stmt = conn.createStatement(sql);
+ try {
+ stmt.params.val = fromColumnValue;
+ ok(stmt.executeStep(), "Expect to get a row");
+ return stmt.row[column];
+ } finally {
+ stmt.reset();
+ }
+}
+
+add_task(async function () {
+ // Make sure places visit chains are saved correctly with a redirect
+ // transitions.
+
+ // Part 1: observe history events that fire when a visit occurs.
+ // Make sure visits appear in order, and that the visit chain is correct.
+ var expectedUrls = [
+ "http://example.com/tests/toolkit/components/places/tests/browser/begin.html",
+ "http://example.com/tests/toolkit/components/places/tests/browser/redirect_twice.sjs",
+ "http://example.com/tests/toolkit/components/places/tests/browser/redirect_once.sjs",
+ "http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html",
+ ];
+ var currentIndex = 0;
+
+ function checkObserver(subject) {
+ var uri = subject.QueryInterface(Ci.nsIURI);
+ var expected = expectedUrls[currentIndex];
+ is(uri.spec, expected, "Saved URL visit " + uri.spec);
+
+ var placeId = getColumn("moz_places", "id", "url", uri.spec);
+ var fromVisitId = getColumn(
+ "moz_historyvisits",
+ "from_visit",
+ "place_id",
+ placeId
+ );
+
+ if (currentIndex == 0) {
+ is(fromVisitId, 0, "First visit has no from visit");
+ } else {
+ var lastVisitId = getColumn(
+ "moz_historyvisits",
+ "place_id",
+ "id",
+ fromVisitId
+ );
+ var fromVisitUrl = getColumn("moz_places", "url", "id", lastVisitId);
+ is(
+ fromVisitUrl,
+ expectedUrls[currentIndex - 1],
+ "From visit was " + expectedUrls[currentIndex - 1]
+ );
+ }
+
+ currentIndex++;
+ return currentIndex >= expectedUrls.length;
+ }
+ let visitUriPromise = promiseObserve("uri-visit-saved", checkObserver);
+
+ const testUrl =
+ "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, testUrl);
+
+ // Load begin page, click link on page to record visits.
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#clickme",
+ {},
+ gBrowser.selectedBrowser
+ );
+ await visitUriPromise;
+
+ await PlacesUtils.history.clear();
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/toolkit/components/places/tests/browser/browser_visituri_nohistory.js b/toolkit/components/places/tests/browser/browser_visituri_nohistory.js
new file mode 100644
index 0000000000..3708b388e5
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_visituri_nohistory.js
@@ -0,0 +1,44 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const INITIAL_URL =
+ "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
+const FINAL_URL =
+ "http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html";
+
+/**
+ * One-time observer callback.
+ */
+function promiseObserve(name) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subject) {
+ Services.obs.removeObserver(observer, name);
+ resolve(subject);
+ }, name);
+ });
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({ set: [["places.history.enabled", false]] });
+
+ let visitUriPromise = promiseObserve("uri-visit-saved");
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, INITIAL_URL);
+
+ await SpecialPowers.popPrefEnv();
+
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser
+ );
+ BrowserTestUtils.loadURIString(gBrowser, FINAL_URL);
+ await browserLoadedPromise;
+
+ let subject = await visitUriPromise;
+ let uri = subject.QueryInterface(Ci.nsIURI);
+ is(uri.spec, FINAL_URL, "received expected visit");
+
+ await PlacesUtils.history.clear();
+ gBrowser.removeCurrentTab();
+});
diff --git a/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js b/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
new file mode 100644
index 0000000000..75fd2aa46d
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const initialURL =
+ "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
+const finalURL =
+ "http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html";
+
+var observer;
+var visitSavedPromise;
+
+add_setup(async function () {
+ visitSavedPromise = new Promise(resolve => {
+ observer = {
+ observe(subject, topic, data) {
+ // The uri-visit-saved topic should only work when on normal mode.
+ if (topic == "uri-visit-saved") {
+ Services.obs.removeObserver(observer, "uri-visit-saved");
+
+ // The expected visit should be the finalURL because private mode
+ // should not register a visit with the initialURL.
+ let uri = subject.QueryInterface(Ci.nsIURI);
+ resolve(uri.spec);
+ }
+ },
+ };
+ });
+
+ Services.obs.addObserver(observer, "uri-visit-saved");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Note: The private window test must be the first one to run, since we'll listen
+// to the first uri-visit-saved notification, and we expect this test to not
+// fire any, so we'll just find the non-private window test notification.
+add_task(async function test_private_browsing_window() {
+ await testLoadInWindow({ private: true }, initialURL);
+});
+
+add_task(async function test_normal_window() {
+ await testLoadInWindow({ private: false }, finalURL);
+
+ let url = await visitSavedPromise;
+ Assert.equal(url, finalURL, "Check received expected visit");
+});
+
+async function testLoadInWindow(options, url) {
+ let win = await BrowserTestUtils.openNewBrowserWindow(options);
+
+ registerCleanupFunction(async function () {
+ await BrowserTestUtils.closeWindow(win);
+ });
+
+ let loadedPromise = BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser
+ );
+ BrowserTestUtils.loadURIString(win.gBrowser.selectedBrowser, url);
+ await loadedPromise;
+}
diff --git a/toolkit/components/places/tests/browser/empty_page.html b/toolkit/components/places/tests/browser/empty_page.html
new file mode 100644
index 0000000000..ac9d144cb4
--- /dev/null
+++ b/toolkit/components/places/tests/browser/empty_page.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>I am an empty page</title>
+ </head>
+ <body>Empty</body>
+</html>
diff --git a/toolkit/components/places/tests/browser/favicon-normal16.png b/toolkit/components/places/tests/browser/favicon-normal16.png
new file mode 100644
index 0000000000..62b69a3d03
--- /dev/null
+++ b/toolkit/components/places/tests/browser/favicon-normal16.png
Binary files differ
diff --git a/toolkit/components/places/tests/browser/favicon-normal32.png b/toolkit/components/places/tests/browser/favicon-normal32.png
new file mode 100644
index 0000000000..5535363c94
--- /dev/null
+++ b/toolkit/components/places/tests/browser/favicon-normal32.png
Binary files differ
diff --git a/toolkit/components/places/tests/browser/favicon.html b/toolkit/components/places/tests/browser/favicon.html
new file mode 100644
index 0000000000..a0f5ea9594
--- /dev/null
+++ b/toolkit/components/places/tests/browser/favicon.html
@@ -0,0 +1,13 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+ <head>
+ <link rel="shortcut icon" href="http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png">
+ </head>
+ <body>
+ OK we're done!
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/final.html b/toolkit/components/places/tests/browser/final.html
new file mode 100644
index 0000000000..ccd5819181
--- /dev/null
+++ b/toolkit/components/places/tests/browser/final.html
@@ -0,0 +1,10 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+ <body>
+ OK we're done!
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/head.js b/toolkit/components/places/tests/browser/head.js
new file mode 100644
index 0000000000..e4d0b5566b
--- /dev/null
+++ b/toolkit/components/places/tests/browser/head.js
@@ -0,0 +1,74 @@
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+const TRANSITION_LINK = PlacesUtils.history.TRANSITION_LINK;
+const TRANSITION_TYPED = PlacesUtils.history.TRANSITION_TYPED;
+const TRANSITION_BOOKMARK = PlacesUtils.history.TRANSITION_BOOKMARK;
+const TRANSITION_REDIRECT_PERMANENT =
+ PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT;
+const TRANSITION_REDIRECT_TEMPORARY =
+ PlacesUtils.history.TRANSITION_REDIRECT_TEMPORARY;
+const TRANSITION_EMBED = PlacesUtils.history.TRANSITION_EMBED;
+const TRANSITION_FRAMED_LINK = PlacesUtils.history.TRANSITION_FRAMED_LINK;
+const TRANSITION_DOWNLOAD = PlacesUtils.history.TRANSITION_DOWNLOAD;
+
+/**
+ * Returns a moz_places field value for a url.
+ *
+ * @param {nsIURI|String} aURI
+ * The URI or spec to get field for.
+ * @param {String} aFieldName
+ * The field name to get the value of.
+ * @param {Function} aCallback
+ * Callback function that will get the property value.
+ */
+function fieldForUrl(aURI, aFieldName, aCallback) {
+ let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+ let stmt = PlacesUtils.history.DBConnection.createAsyncStatement(
+ `SELECT ${aFieldName} FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url`
+ );
+ stmt.params.page_url = url;
+ stmt.executeAsync({
+ _value: -1,
+ handleResult(aResultSet) {
+ let row = aResultSet.getNextRow();
+ if (!row) {
+ ok(false, "The page should exist in the database");
+ }
+ this._value = row.getResultByName(aFieldName);
+ },
+ handleError() {},
+ handleCompletion(aReason) {
+ if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ ok(false, "The statement should properly succeed");
+ }
+ aCallback(this._value);
+ },
+ });
+ stmt.finalize();
+}
+
+/**
+ * Promise wrapper for fieldForUrl.
+ *
+ * @param {nsIURI|String} aURI
+ * The URI or spec to get field for.
+ * @param {String} aFieldName
+ * The field name to get the value of.
+ * @return {Promise}
+ * A promise that is resolved with the value of the field.
+ */
+function promiseFieldForUrl(aURI, aFieldName) {
+ return new Promise(resolve => {
+ function callback(result) {
+ resolve(result);
+ }
+ fieldForUrl(aURI, aFieldName, callback);
+ });
+}
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ BrowserTestUtils.waitForNewWindow().then(aCallback);
+ OpenBrowserWindow(aOptions);
+}
diff --git a/toolkit/components/places/tests/browser/history_post.html b/toolkit/components/places/tests/browser/history_post.html
new file mode 100644
index 0000000000..a579a9b8ae
--- /dev/null
+++ b/toolkit/components/places/tests/browser/history_post.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test post pages are not added to history</title>
+ </head>
+ <body>
+ <iframe name="post_iframe" id="post_iframe"></iframe>
+ <form method="post" action="http://example.com/tests/toolkit/components/places/tests/browser/history_post.sjs" target="post_iframe">
+ <input type="submit" id="submit"/>
+ </form>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/history_post.sjs b/toolkit/components/places/tests/browser/history_post.sjs
new file mode 100644
index 0000000000..08c1afe853
--- /dev/null
+++ b/toolkit/components/places/tests/browser/history_post.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.setStatusLine("1.0", 200, "OK");
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write("Ciao");
+}
diff --git a/toolkit/components/places/tests/browser/previews/browser.ini b/toolkit/components/places/tests/browser/previews/browser.ini
new file mode 100644
index 0000000000..dd77faa323
--- /dev/null
+++ b/toolkit/components/places/tests/browser/previews/browser.ini
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+prefs =
+ browser.pagethumbnails.capturing_disabled=false
+ places.previews.enabled=true
+ places.previews.log=true
+
+[browser_thumbnails.js]
diff --git a/toolkit/components/places/tests/browser/previews/browser_thumbnails.js b/toolkit/components/places/tests/browser/previews/browser_thumbnails.js
new file mode 100644
index 0000000000..ef776694c2
--- /dev/null
+++ b/toolkit/components/places/tests/browser/previews/browser_thumbnails.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests PlacesPreviews.jsm
+ */
+const { PlacesPreviews } = ChromeUtils.importESModule(
+ "resource://gre/modules/PlacesPreviews.sys.mjs"
+);
+const { PlacesTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PlacesTestUtils.sys.mjs"
+);
+
+const TEST_URL1 = "http://example.com/";
+const TEST_URL2 = "http://example.org/";
+
+/**
+ * Counts tombstone entries.
+ * @returns {integer} number of tombstone entries.
+ */
+async function countTombstones() {
+ await PlacesTestUtils.promiseAsyncUpdates();
+ let db = await PlacesUtils.promiseDBConnection();
+ return (
+ await db.execute("SELECT count(*) FROM moz_previews_tombstones")
+ )[0].getResultByIndex(0);
+}
+
+add_task(async function test_thumbnail() {
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ // Ensure tombstones table has been emptied.
+ await TestUtils.waitForCondition(async () => {
+ return (await countTombstones()) == 0;
+ });
+ PlacesPreviews.testSetDeletionTimeout(null);
+ });
+ // Sanity check initial state.
+ Assert.equal(await countTombstones(), 0, "There's no tombstone entries");
+
+ info("Test preview creation and storage.");
+ await BrowserTestUtils.withNewTab(TEST_URL1, async browser => {
+ await retryUpdatePreview(browser.currentURI.spec);
+ let filePath = PlacesPreviews.getPathForUrl(TEST_URL1);
+ Assert.ok(await IOUtils.exists(filePath), "The screenshot exists");
+ Assert.equal(
+ filePath.substring(filePath.lastIndexOf(".")),
+ PlacesPreviews.fileExtension,
+ "Check extension"
+ );
+ await testImageFile(filePath);
+ await testMozPageThumb(TEST_URL1);
+ });
+});
+
+add_task(async function test_page_removal() {
+ info("Store another preview and test page removal.");
+ await BrowserTestUtils.withNewTab(TEST_URL2, async browser => {
+ await retryUpdatePreview(browser.currentURI.spec);
+ let filePath = PlacesPreviews.getPathForUrl(TEST_URL2);
+ Assert.ok(await IOUtils.exists(filePath), "The screenshot exists");
+ });
+
+ // Set deletion time to a small value so it runs immediately.
+ PlacesPreviews.testSetDeletionTimeout(0);
+ info("Wait for deletion, check one preview is removed, not the other one.");
+ let promiseDeleted = new Promise(resolve => {
+ PlacesPreviews.once("places-preview-deleted", (topic, filePath) => {
+ resolve(filePath);
+ });
+ });
+ await PlacesUtils.history.remove(TEST_URL1);
+
+ let deletedFilePath = await promiseDeleted;
+ Assert.ok(
+ !(await IOUtils.exists(deletedFilePath)),
+ "Check deleted file has been removed"
+ );
+
+ info("Check tombstones table has been emptied.");
+ Assert.equal(await countTombstones(), 0, "There's no tombstone entries");
+
+ info("Check the other thumbnail has not been removed.");
+ let path = PlacesPreviews.getPathForUrl(TEST_URL2);
+ Assert.ok(await IOUtils.exists(path), "Check non-deleted url is still there");
+ await testImageFile(path);
+ await testMozPageThumb(TEST_URL2);
+});
+
+add_task(async function async_test_deleteOrphans() {
+ let path = PlacesPreviews.getPathForUrl(TEST_URL2);
+ Assert.ok(await IOUtils.exists(path), "Sanity check one preview exists");
+ // Create a file in the given path that doesn't have an entry in Places.
+ let fakePath = PathUtils.join(
+ PlacesPreviews.getPath(),
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." + PlacesPreviews.fileExtension
+ );
+ // File contents don't matter.
+ await IOUtils.writeJSON(fakePath, { test: true });
+ let promiseDeleted = new Promise(resolve => {
+ PlacesPreviews.once("places-preview-deleted", (topic, filePath) => {
+ resolve(filePath);
+ });
+ });
+
+ await PlacesPreviews.deleteOrphans();
+ let deletedFilePath = await promiseDeleted;
+ Assert.equal(deletedFilePath, fakePath, "Check orphan has been deleted");
+ Assert.equal(await countTombstones(), 0, "There's no tombstone entries left");
+ Assert.ok(
+ !(await IOUtils.exists(fakePath)),
+ "Ensure orphan has been deleted"
+ );
+
+ Assert.ok(await IOUtils.exists(path), "Ensure valid preview is still there");
+});
+
+async function testImageFile(path) {
+ info("Load the file and check its content type.");
+ const buffer = await IOUtils.read(path);
+ const fourcc = new TextDecoder("utf-8").decode(buffer.slice(8, 12));
+ Assert.equal(fourcc, "WEBP", "Check the stored preview is webp");
+}
+
+async function testMozPageThumb(url) {
+ info("Check moz-page-thumb protocol: " + PlacesPreviews.getPageThumbURL(url));
+ let { data, contentType } = await fetchImage(
+ PlacesPreviews.getPageThumbURL(url)
+ );
+ Assert.equal(
+ contentType,
+ PlacesPreviews.fileContentType,
+ "Check the content type"
+ );
+ const fourcc = data.slice(8, 12);
+ Assert.equal(fourcc, "WEBP", "Check the returned preview is webp");
+}
+
+function fetchImage(url) {
+ return new Promise((resolve, reject) => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(url),
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
+ },
+ (input, status, request) => {
+ if (!Components.isSuccessCode(status)) {
+ reject(new Error("unable to load image"));
+ return;
+ }
+
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ let contentType = request.QueryInterface(Ci.nsIChannel).contentType;
+ input.close();
+ resolve({ data, contentType });
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+ );
+ });
+}
+
+/**
+ * Sometimes on macOS fetching the preview fails for timeout/network reasons,
+ * this retries so the test doesn't intermittently fail over it.
+ * @param {string} url The url to store a preview for.
+ * @returns {Promise} resolved once a preview has been captured.
+ */
+function retryUpdatePreview(url) {
+ return TestUtils.waitForCondition(() => PlacesPreviews.update(url));
+}
diff --git a/toolkit/components/places/tests/browser/redirect-target.html b/toolkit/components/places/tests/browser/redirect-target.html
new file mode 100644
index 0000000000..3700263385
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect-target.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><body><p>Ciao!</p></body></html>
diff --git a/toolkit/components/places/tests/browser/redirect.sjs b/toolkit/components/places/tests/browser/redirect.sjs
new file mode 100644
index 0000000000..ab47335ffe
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect.sjs
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response) {
+ let page = "<!DOCTYPE html><html><body><p>Redirecting...</p></body></html>";
+
+ response.setStatusLine(request.httpVersion, "301", "Moved Permanently");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", page.length + "", false);
+ response.setHeader("Location", "redirect-target.html", false);
+ response.write(page);
+}
diff --git a/toolkit/components/places/tests/browser/redirect_once.sjs b/toolkit/components/places/tests/browser/redirect_once.sjs
new file mode 100644
index 0000000000..b9ccd0829a
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect_once.sjs
@@ -0,0 +1,13 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 301, "Found");
+ response.setHeader(
+ "Location",
+ "http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html",
+ false
+ );
+}
diff --git a/toolkit/components/places/tests/browser/redirect_self.sjs b/toolkit/components/places/tests/browser/redirect_self.sjs
new file mode 100644
index 0000000000..953afe5f26
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect_self.sjs
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Script that redirects to itself once.
+
+function handleRequest(request, response) {
+ if (
+ request.hasHeader("Cookie") &&
+ request.getHeader("Cookie").includes("redirect-self")
+ ) {
+ response.setStatusLine("1.0", 200, "OK");
+ // Expire the cookie.
+ response.setHeader(
+ "Set-Cookie",
+ "redirect-self=true; expires=Thu, 01 Jan 1970 00:00:00 GMT",
+ true
+ );
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write("OK");
+ } else {
+ response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+ response.setHeader("Set-Cookie", "redirect-self=true", true);
+ response.setHeader("Location", "redirect_self.sjs");
+ response.write("Moved Temporarily");
+ }
+}
diff --git a/toolkit/components/places/tests/browser/redirect_thrice.sjs b/toolkit/components/places/tests/browser/redirect_thrice.sjs
new file mode 100644
index 0000000000..55154a736e
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect_thrice.sjs
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", "redirect_twice_perma.sjs", false);
+}
diff --git a/toolkit/components/places/tests/browser/redirect_twice.sjs b/toolkit/components/places/tests/browser/redirect_twice.sjs
new file mode 100644
index 0000000000..099d20022e
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect_twice.sjs
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", "redirect_once.sjs", false);
+}
diff --git a/toolkit/components/places/tests/browser/redirect_twice_perma.sjs b/toolkit/components/places/tests/browser/redirect_twice_perma.sjs
new file mode 100644
index 0000000000..a40abd4170
--- /dev/null
+++ b/toolkit/components/places/tests/browser/redirect_twice_perma.sjs
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 301, "Found");
+ response.setHeader("Location", "redirect_once.sjs", false);
+}
diff --git a/toolkit/components/places/tests/browser/title1.html b/toolkit/components/places/tests/browser/title1.html
new file mode 100644
index 0000000000..3c98d693ec
--- /dev/null
+++ b/toolkit/components/places/tests/browser/title1.html
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+ <head>
+ </head>
+ <body>
+ title1.html
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/browser/title2.html b/toolkit/components/places/tests/browser/title2.html
new file mode 100644
index 0000000000..8830328796
--- /dev/null
+++ b/toolkit/components/places/tests/browser/title2.html
@@ -0,0 +1,13 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+ <head>
+ <title>Some title</title>
+ </head>
+ <body>
+ title2.html
+ </body>
+</html>