summaryrefslogtreecommitdiffstats
path: root/browser/components/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /browser/components/tests
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/tests')
-rw-r--r--browser/components/tests/browser/browser.ini9
-rw-r--r--browser/components/tests/browser/browser_bug538331.js228
-rw-r--r--browser/components/tests/browser/browser_contentpermissionprompt.js180
-rw-r--r--browser/components/tests/browser/browser_default_bookmark_toolbar_visibility.js82
-rw-r--r--browser/components/tests/browser/browser_initial_tab_remoteType.js211
-rw-r--r--browser/components/tests/browser/browser_startup_homepage.js122
-rw-r--r--browser/components/tests/browser/whats_new_page/active-update.xml1
-rw-r--r--browser/components/tests/browser/whats_new_page/browser.ini12
-rw-r--r--browser/components/tests/browser/whats_new_page/browser_whats_new_page.js108
-rw-r--r--browser/components/tests/browser/whats_new_page/updates/0/update.status1
-rw-r--r--browser/components/tests/startupRecorder.js236
-rw-r--r--browser/components/tests/testComponents.manifest5
-rw-r--r--browser/components/tests/unit/distribution.ini58
-rw-r--r--browser/components/tests/unit/head.js7
-rw-r--r--browser/components/tests/unit/test_browserGlue_migration_no_errors.js34
-rw-r--r--browser/components/tests/unit/test_browserGlue_migration_social_cleanup.js30
-rw-r--r--browser/components/tests/unit/test_distribution.js216
-rw-r--r--browser/components/tests/unit/test_distribution_cachedexistence.js131
-rw-r--r--browser/components/tests/unit/xpcshell.ini11
19 files changed, 1682 insertions, 0 deletions
diff --git a/browser/components/tests/browser/browser.ini b/browser/components/tests/browser/browser.ini
new file mode 100644
index 0000000000..5299b49511
--- /dev/null
+++ b/browser/components/tests/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+
+[browser_bug538331.js]
+skip-if = !updater
+reason = test depends on update channel
+[browser_contentpermissionprompt.js]
+[browser_default_bookmark_toolbar_visibility.js]
+[browser_initial_tab_remoteType.js]
+[browser_startup_homepage.js]
diff --git a/browser/components/tests/browser/browser_bug538331.js b/browser/components/tests/browser/browser_bug538331.js
new file mode 100644
index 0000000000..874b4aecbe
--- /dev/null
+++ b/browser/components/tests/browser/browser_bug538331.js
@@ -0,0 +1,228 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const PREF_MSTONE = "browser.startup.homepage_override.mstone";
+const PREF_OVERRIDE_URL = "startup.homepage_override_url";
+
+const DEFAULT_PREF_URL = "http://pref.example.com/";
+const DEFAULT_UPDATE_URL = "http://example.com/";
+
+const XML_EMPTY =
+ '<?xml version="1.0"?><updates xmlns=' +
+ '"http://www.mozilla.org/2005/app-update"></updates>';
+
+const XML_PREFIX =
+ '<updates xmlns="http://www.mozilla.org/2005/app-update"' +
+ '><update appVersion="1.0" buildID="20080811053724" ' +
+ 'channel="nightly" displayVersion="Version 1.0" ' +
+ 'installDate="1238441400314" isCompleteUpdate="true" ' +
+ 'name="Update Test 1.0" type="minor" detailsURL=' +
+ '"http://example.com/" previousAppVersion="1.0" ' +
+ 'serviceURL="https://example.com/" ' +
+ 'statusText="The Update was successfully installed" ' +
+ 'foregroundDownload="true"';
+
+const XML_SUFFIX =
+ '><patch type="complete" URL="http://example.com/" ' +
+ 'size="775" selected="true" state="succeeded"/>' +
+ "</update></updates>";
+
+// nsBrowserContentHandler.js defaultArgs tests
+const BCH_TESTS = [
+ {
+ description: "no mstone change and no update",
+ noMstoneChange: true,
+ },
+ {
+ description: "mstone changed and no update",
+ prefURL: DEFAULT_PREF_URL,
+ },
+ {
+ description: "no mstone change and update with 'showURL' for actions",
+ actions: "showURL",
+ noMstoneChange: true,
+ },
+ {
+ description: "update without actions",
+ prefURL: DEFAULT_PREF_URL,
+ },
+ {
+ description: "update with 'showURL' for actions",
+ actions: "showURL",
+ prefURL: DEFAULT_PREF_URL,
+ },
+ {
+ description: "update with 'showURL' for actions and openURL",
+ actions: "showURL",
+ openURL: DEFAULT_UPDATE_URL,
+ },
+ {
+ description: "update with 'extra showURL' for actions",
+ actions: "extra showURL",
+ prefURL: DEFAULT_PREF_URL,
+ },
+ {
+ description: "update with 'extra showURL' for actions and openURL",
+ actions: "extra showURL",
+ openURL: DEFAULT_UPDATE_URL,
+ },
+ {
+ description: "update with 'silent' for actions",
+ actions: "silent",
+ },
+ {
+ description: "update with 'silent showURL extra' for actions and openURL",
+ actions: "silent showURL extra",
+ },
+];
+
+add_task(async function test_bug538331() {
+ // Reset the startup page pref since it may have been set by other tests
+ // and we will assume it is (non-test) default.
+ await SpecialPowers.pushPrefEnv({
+ clear: [["browser.startup.page"]],
+ });
+
+ let originalMstone = Services.prefs.getCharPref(PREF_MSTONE);
+
+ // Set the preferences needed for the test: they will be cleared up
+ // after it runs.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_MSTONE, originalMstone],
+ [PREF_OVERRIDE_URL, DEFAULT_PREF_URL],
+ ],
+ });
+
+ registerCleanupFunction(async () => {
+ let activeUpdateFile = getActiveUpdateFile();
+ activeUpdateFile.remove(false);
+ reloadUpdateManagerData(true);
+ });
+
+ // Clear any pre-existing override in defaultArgs that are hanging around.
+ // This will also set the browser.startup.homepage_override.mstone preference
+ // if it isn't already set.
+ Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
+
+ for (let i = 0; i < BCH_TESTS.length; i++) {
+ let testCase = BCH_TESTS[i];
+ ok(
+ true,
+ "Test nsBrowserContentHandler " + (i + 1) + ": " + testCase.description
+ );
+
+ if (testCase.actions) {
+ let actionsXML = ' actions="' + testCase.actions + '"';
+ if (testCase.openURL) {
+ actionsXML += ' openURL="' + testCase.openURL + '"';
+ }
+ writeUpdatesToXMLFile(XML_PREFIX + actionsXML + XML_SUFFIX);
+ } else {
+ writeUpdatesToXMLFile(XML_EMPTY);
+ }
+
+ reloadUpdateManagerData(false);
+
+ let noOverrideArgs = Cc["@mozilla.org/browser/clh;1"].getService(
+ Ci.nsIBrowserHandler
+ ).defaultArgs;
+
+ let overrideArgs = "";
+ if (testCase.prefURL) {
+ overrideArgs = testCase.prefURL;
+ } else if (testCase.openURL) {
+ overrideArgs = testCase.openURL;
+ }
+
+ if (overrideArgs == "" && noOverrideArgs) {
+ overrideArgs = noOverrideArgs;
+ } else if (noOverrideArgs) {
+ overrideArgs += "|" + noOverrideArgs;
+ }
+
+ if (testCase.noMstoneChange === undefined) {
+ Services.prefs.setCharPref(PREF_MSTONE, "PreviousMilestone");
+ }
+
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"].getService(
+ Ci.nsIBrowserHandler
+ ).defaultArgs;
+ is(defaultArgs, overrideArgs, "correct value returned by defaultArgs");
+
+ if (testCase.noMstoneChange === undefined || !testCase.noMstoneChange) {
+ let newMstone = Services.prefs.getCharPref(PREF_MSTONE);
+ is(
+ originalMstone,
+ newMstone,
+ "preference " + PREF_MSTONE + " should have been updated"
+ );
+ }
+ }
+});
+
+/**
+ * Removes the updates.xml file and returns the nsIFile for the
+ * active-update.xml file.
+ *
+ * @return The nsIFile for the active-update.xml file.
+ */
+function getActiveUpdateFile() {
+ let updateRootDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
+ let updatesFile = updateRootDir.clone();
+ updatesFile.append("updates.xml");
+ if (updatesFile.exists()) {
+ // The following is non-fatal.
+ try {
+ updatesFile.remove(false);
+ } catch (e) {}
+ }
+ let activeUpdateFile = updateRootDir.clone();
+ activeUpdateFile.append("active-update.xml");
+ return activeUpdateFile;
+}
+
+/**
+ * Reloads the update xml files.
+ *
+ * @param skipFiles (optional)
+ * If true, the update xml files will not be read and the metadata will
+ * be reset. If false (the default), the update xml files will be read
+ * to populate the update metadata.
+ */
+function reloadUpdateManagerData(skipFiles = false) {
+ Cc["@mozilla.org/updates/update-manager;1"]
+ .getService(Ci.nsIUpdateManager)
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "um-reload-update-data", skipFiles ? "skip-files" : "");
+}
+
+/**
+ * Writes the updates specified to the active-update.xml file.
+ *
+ * @param aText
+ * The updates represented as a string to write to the active-update.xml
+ * file.
+ */
+function writeUpdatesToXMLFile(aText) {
+ const PERMS_FILE = 0o644;
+
+ const MODE_WRONLY = 0x02;
+ const MODE_CREATE = 0x08;
+ const MODE_TRUNCATE = 0x20;
+
+ let activeUpdateFile = getActiveUpdateFile();
+ if (!activeUpdateFile.exists()) {
+ activeUpdateFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ let flags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
+ fos.init(activeUpdateFile, flags, PERMS_FILE, 0);
+ fos.write(aText, aText.length);
+ fos.close();
+}
diff --git a/browser/components/tests/browser/browser_contentpermissionprompt.js b/browser/components/tests/browser/browser_contentpermissionprompt.js
new file mode 100644
index 0000000000..123ac100fe
--- /dev/null
+++ b/browser/components/tests/browser/browser_contentpermissionprompt.js
@@ -0,0 +1,180 @@
+/**
+ * These tests test nsBrowserGlue's nsIContentPermissionPrompt
+ * implementation behaviour with various types of
+ * nsIContentPermissionRequests.
+ */
+
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+ChromeUtils.import("resource://gre/modules/Integration.jsm", this);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "ContentPermissionPrompt",
+ "@mozilla.org/content-permission/prompt;1",
+ "nsIContentPermissionPrompt"
+);
+
+/**
+ * This is a partial implementation of nsIContentPermissionType.
+ *
+ * @param {string} type
+ * The string defining what type of permission is being requested.
+ * Example: "geo", "desktop-notification".
+ * @return nsIContentPermissionType implementation.
+ */
+function MockContentPermissionType(type) {
+ this.type = type;
+}
+
+MockContentPermissionType.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionType"]),
+ // We expose the wrappedJSObject so that we can be sure
+ // in some of our tests that we're passing the right
+ // nsIContentPermissionType around.
+ wrappedJSObject: this,
+};
+
+/**
+ * This is a partial implementation of nsIContentPermissionRequest.
+ *
+ * @param {Array<nsIContentPermissionType>} typesArray
+ * The types to assign to this nsIContentPermissionRequest,
+ * in order. You probably want to use MockContentPermissionType.
+ * @return nsIContentPermissionRequest implementation.
+ */
+function MockContentPermissionRequest(typesArray) {
+ this.types = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ for (let type of typesArray) {
+ this.types.appendElement(type);
+ }
+}
+
+MockContentPermissionRequest.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionRequest"]),
+ // We expose the wrappedJSObject so that we can be sure
+ // in some of our tests that we're passing the right
+ // nsIContentPermissionRequest around.
+ wrappedJSObject: this,
+ // For some of our tests, we want to make sure that the
+ // request is cancelled, so we add some instrumentation here
+ // to check that cancel() is called.
+ cancel() {
+ this.cancelled = true;
+ },
+ cancelled: false,
+ principal: Services.scriptSecurityManager.getSystemPrincipal(),
+};
+
+/**
+ * Tests that if the nsIContentPermissionRequest has an empty
+ * types array, that NS_ERROR_UNEXPECTED is thrown, and the
+ * request is cancelled.
+ */
+add_task(async function test_empty_types() {
+ let mockRequest = new MockContentPermissionRequest([]);
+ Assert.throws(
+ () => {
+ ContentPermissionPrompt.prompt(mockRequest);
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "Should have thrown NS_ERROR_UNEXPECTED."
+ );
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if the nsIContentPermissionRequest has more than
+ * one type, that NS_ERROR_UNEXPECTED is thrown, and the request
+ * is cancelled.
+ */
+add_task(async function test_multiple_types() {
+ let mockRequest = new MockContentPermissionRequest([
+ new MockContentPermissionType("test1"),
+ new MockContentPermissionType("test2"),
+ ]);
+
+ Assert.throws(() => {
+ ContentPermissionPrompt.prompt(mockRequest);
+ }, /NS_ERROR_UNEXPECTED/);
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if the nsIContentPermissionRequest has a type that
+ * does not implement nsIContentPermissionType that NS_NOINTERFACE
+ * is thrown, and the request is cancelled.
+ */
+add_task(async function test_not_permission_type() {
+ let mockRequest = new MockContentPermissionRequest([
+ { QueryInterface: ChromeUtils.generateQI([]) },
+ ]);
+
+ Assert.throws(() => {
+ ContentPermissionPrompt.prompt(mockRequest);
+ }, /NS_NOINTERFACE/);
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if the nsIContentPermissionRequest is for a type
+ * that is not recognized, that NS_ERROR_FAILURE is thrown and
+ * the request is cancelled.
+ */
+add_task(async function test_unrecognized_type() {
+ let mockRequest = new MockContentPermissionRequest([
+ new MockContentPermissionType("test1"),
+ ]);
+
+ Assert.throws(() => {
+ ContentPermissionPrompt.prompt(mockRequest);
+ }, /NS_ERROR_FAILURE/);
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if we meet the minimal requirements for a
+ * nsIContentPermissionRequest, that it will be passed to
+ * ContentPermissionIntegration's createPermissionPrompt
+ * method.
+ */
+add_task(async function test_working_request() {
+ let mockType = new MockContentPermissionType("test-permission-type");
+ let mockRequest = new MockContentPermissionRequest([mockType]);
+
+ // mockPermissionPrompt is what createPermissionPrompt
+ // will return. Returning some kind of object should be
+ // enough to convince nsBrowserGlue that everything went
+ // okay.
+ let didPrompt = false;
+ let mockPermissionPrompt = {
+ prompt() {
+ didPrompt = true;
+ },
+ };
+
+ let integration = base => ({
+ createPermissionPrompt(type, request) {
+ Assert.equal(type, "test-permission-type");
+ Assert.ok(
+ Object.is(request.wrappedJSObject, mockRequest.wrappedJSObject)
+ );
+ return mockPermissionPrompt;
+ },
+ });
+
+ // Register an integration so that we can capture the
+ // calls into ContentPermissionIntegration.
+ try {
+ Integration.contentPermission.register(integration);
+
+ ContentPermissionPrompt.prompt(mockRequest);
+ Assert.ok(!mockRequest.cancelled, "Should not have cancelled the request.");
+ Assert.ok(didPrompt, "Should have tried to show the prompt");
+ } finally {
+ Integration.contentPermission.unregister(integration);
+ }
+});
diff --git a/browser/components/tests/browser/browser_default_bookmark_toolbar_visibility.js b/browser/components/tests/browser/browser_default_bookmark_toolbar_visibility.js
new file mode 100644
index 0000000000..6dab7ed1ed
--- /dev/null
+++ b/browser/components/tests/browser/browser_default_bookmark_toolbar_visibility.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test PlacesUIUtils.maybeToggleBookmarkToolbarVisibility() code running for new profiles.
+ * Ensure that the bookmarks toolbar is hidden in a default configuration.
+ * If new default bookmarks are added to the toolbar then the threshold of > 3
+ * in NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE may need to be adjusted there.
+ */
+add_task(async function test_default_bookmark_toolbar_visibility() {
+ // The Bookmarks Toolbar visibility state should be set only after
+ // Places has notified that it's done initializing.
+ const browserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
+ Ci.nsIObserver
+ );
+
+ let placesInitCompleteObserved = TestUtils.topicObserved(
+ "places-browser-init-complete"
+ );
+
+ // If places-browser-init-complete has already notified, this will cause it
+ // to notify again. Otherwise, we wait until the notify is done.
+ browserGlue.observe(
+ null,
+ "browser-glue-test",
+ "places-browser-init-complete"
+ );
+
+ await placesInitCompleteObserved;
+
+ const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
+ let xulStore = Services.xulStore;
+
+ is(
+ xulStore.getValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed"),
+ "",
+ "Check that @collapsed isn't persisted"
+ );
+ ok(
+ document.getElementById("PersonalToolbar").collapsed,
+ "The bookmarks toolbar should be collapsed by default"
+ );
+});
+
+/**
+ * Ensure that the bookmarks toolbar is visible in a new profile
+ * if the toolbar has > 3 (NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE) bookmarks.
+ */
+add_task(async function test_bookmark_toolbar_visible_when_populated() {
+ const { Bookmarks } = ChromeUtils.import(
+ "resource://gre/modules/Bookmarks.jsm"
+ );
+ const { PlacesUIUtils } = ChromeUtils.import(
+ "resource:///modules/PlacesUIUtils.jsm"
+ );
+
+ let bookmark = {
+ type: Bookmarks.TYPE_BOOKMARK,
+ parentGuid: Bookmarks.toolbarGuid,
+ };
+ let bookmarksInserted = await Promise.all([
+ Bookmarks.insert(Object.assign({ url: "https://example.com/1" }, bookmark)),
+ Bookmarks.insert(Object.assign({ url: "https://example.com/2" }, bookmark)),
+ Bookmarks.insert(Object.assign({ url: "https://example.com/3" }, bookmark)),
+ Bookmarks.insert(Object.assign({ url: "https://example.com/4" }, bookmark)),
+ Bookmarks.insert(Object.assign({ url: "https://example.com/5" }, bookmark)),
+ Bookmarks.insert(Object.assign({ url: "https://example.com/6" }, bookmark)),
+ ]);
+
+ PlacesUIUtils.maybeToggleBookmarkToolbarVisibility();
+
+ const personalToolbar = document.getElementById("PersonalToolbar");
+ ok(
+ !personalToolbar.collapsed,
+ "The bookmarks toolbar should be visible since it has many bookmarks"
+ );
+
+ for (let insertedBookmark of bookmarksInserted) {
+ await Bookmarks.remove(insertedBookmark.guid);
+ }
+ personalToolbar.collapsed = true;
+});
diff --git a/browser/components/tests/browser/browser_initial_tab_remoteType.js b/browser/components/tests/browser/browser_initial_tab_remoteType.js
new file mode 100644
index 0000000000..a8b479dbbb
--- /dev/null
+++ b/browser/components/tests/browser/browser_initial_tab_remoteType.js
@@ -0,0 +1,211 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * These tests test that the initial browser tab has the right
+ * process type assigned to it on creation, which avoids needless
+ * process flips.
+ */
+
+"use strict";
+
+const PRIVILEGEDABOUT_PROCESS_PREF =
+ "browser.tabs.remote.separatePrivilegedContentProcess";
+const PRIVILEGEDABOUT_PROCESS_ENABLED = Services.prefs.getBoolPref(
+ PRIVILEGEDABOUT_PROCESS_PREF
+);
+
+const REMOTE_BROWSER_SHOWN = "remote-browser-shown";
+
+// When the privileged content process is enabled, we expect about:home
+// to load in it. Otherwise, it's in a normal web content process.
+const EXPECTED_ABOUTHOME_REMOTE_TYPE = PRIVILEGEDABOUT_PROCESS_ENABLED
+ ? E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE
+ : E10SUtils.DEFAULT_REMOTE_TYPE;
+
+/**
+ * Test helper function that takes an nsICommandLine, and passes it
+ * into the default command line handler for the browser. It expects
+ * a new browser window to open, and then checks that the expected page
+ * loads in the initial tab in the expected remote type, without doing
+ * unnecessary process flips. The helper function then closes the window.
+ *
+ * @param aCmdLine (nsICommandLine)
+ * The command line to be processed by the default
+ * nsICommandLineHandler
+ * @param aExpectedURL (string)
+ * The URL that the initial browser tab is expected to load.
+ * @param aRemoteType (string)
+ * The expected remoteType on the initial browser tab.
+ * @returns Promise
+ * Resolves once the checks have completed, and the opened window
+ * have been closed.
+ */
+async function assertOneRemoteBrowserShown(
+ aCmdLine,
+ aExpectedURL,
+ aRemoteType
+) {
+ let shownRemoteBrowsers = 0;
+ let observer = () => {
+ shownRemoteBrowsers++;
+ };
+ Services.obs.addObserver(observer, REMOTE_BROWSER_SHOWN);
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow({
+ url: aExpectedURL,
+ });
+
+ let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
+ Ci.nsICommandLineHandler
+ );
+ cmdLineHandler.handle(aCmdLine);
+
+ let newWin = await newWinPromise;
+
+ Services.obs.removeObserver(observer, REMOTE_BROWSER_SHOWN);
+
+ if (aRemoteType == E10SUtils.WEB_REMOTE_TYPE) {
+ Assert.ok(
+ E10SUtils.isWebRemoteType(newWin.gBrowser.selectedBrowser.remoteType)
+ );
+ } else {
+ Assert.equal(newWin.gBrowser.selectedBrowser.remoteType, aRemoteType);
+ }
+
+ Assert.equal(
+ shownRemoteBrowsers,
+ 1,
+ "Should have only shown 1 remote browser"
+ );
+ await BrowserTestUtils.closeWindow(newWin);
+}
+
+/**
+ * Constructs an object that implements an nsICommandLine that should
+ * cause the default nsICommandLineHandler to open aURL as the initial
+ * tab in a new window. The returns nsICommandLine is stateful, and
+ * shouldn't be reused.
+ *
+ * @param aURL (string)
+ * The URL to load in the initial tab of the new window.
+ * @returns nsICommandLine
+ */
+function constructOnePageCmdLine(aURL) {
+ return {
+ _arg: aURL,
+ _argCount: 1,
+
+ get length() {
+ return this._argCount;
+ },
+
+ getArgument(aIndex) {
+ if (aIndex == 0 && this._argCount) {
+ return this._arg;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ findFlag() {
+ return -1;
+ },
+
+ removeArguments() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ handleFlag() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ handleFlagWithParam() {
+ if (this._argCount) {
+ this._argCount = 0;
+ return this._arg;
+ }
+
+ return "";
+ },
+
+ get state() {
+ return 0;
+ },
+
+ STATE_INITIAL_LAUNCH: 0,
+ STATE_REMOTE_AUTO: 1,
+ STATE_REMOTE_EXPLICIT: 2,
+
+ preventDefault: false,
+
+ get workingDirectory() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ get windowContext() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ resolveFile() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ resolveURI() {
+ return Services.io.newURI(this._arg);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsICommandLine"]),
+ };
+}
+
+add_task(async function setup() {
+ NewTabPagePreloading.removePreloadedBrowser(window);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.newtab.preload", false],
+ ["browser.startup.homepage", "about:home"],
+ ["browser.startup.page", 1],
+ ],
+ });
+});
+
+/**
+ * This tests the default case, where no arguments are passed.
+ */
+add_task(async function test_default_args_and_homescreen() {
+ let cmdLine = Cu.createCommandLine();
+ await assertOneRemoteBrowserShown(
+ cmdLine,
+ "about:home",
+ EXPECTED_ABOUTHOME_REMOTE_TYPE
+ );
+});
+
+/**
+ * This tests the case where about:home is passed as the lone
+ * argument.
+ */
+add_task(async function test_abouthome_arg() {
+ const URI = "about:home";
+ let cmdLine = constructOnePageCmdLine(URI);
+ await assertOneRemoteBrowserShown(
+ cmdLine,
+ URI,
+ EXPECTED_ABOUTHOME_REMOTE_TYPE
+ );
+});
+
+/**
+ * This tests the case where example.com is passed as the lone
+ * argument.
+ */
+add_task(async function test_examplecom_arg() {
+ const URI = "http://example.com/";
+ let cmdLine = constructOnePageCmdLine(URI);
+ await assertOneRemoteBrowserShown(
+ cmdLine,
+ URI,
+ E10SUtils.DEFAULT_REMOTE_TYPE
+ );
+});
diff --git a/browser/components/tests/browser/browser_startup_homepage.js b/browser/components/tests/browser/browser_startup_homepage.js
new file mode 100644
index 0000000000..c6cb4543d5
--- /dev/null
+++ b/browser/components/tests/browser/browser_startup_homepage.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function checkArgs(message, expect, prefs = {}) {
+ info(`Setting prefs: ${JSON.stringify(prefs)}`);
+ await SpecialPowers.pushPrefEnv({
+ set: Object.entries(prefs).map(keyVal => {
+ if (typeof keyVal[1] == "object") {
+ keyVal[1] = JSON.stringify(keyVal[1]);
+ }
+ return keyVal;
+ }),
+ });
+
+ // Check the defaultArgs for startup behavior
+ Assert.equal(
+ Cc["@mozilla.org/browser/clh;1"]
+ .getService(Ci.nsIBrowserHandler)
+ .wrappedJSObject.getArgs(true),
+ expect,
+ message
+ );
+}
+
+add_task(async function test_once_expire() {
+ const url = "https://www.mozilla.org/";
+ await checkArgs("no expiration", url, {
+ "browser.startup.homepage_override.once": { url },
+ });
+
+ await checkArgs("expired", "about:blank", {
+ "browser.startup.homepage_override.once": { expire: 0, url },
+ });
+
+ await checkArgs("not expired", url, {
+ "browser.startup.homepage_override.once": { expire: Date.now() * 2, url },
+ });
+});
+
+add_task(async function test_once_invalid() {
+ await checkArgs("not json", "about:blank", {
+ "browser.startup.homepage_override.once": "https://not.json",
+ });
+
+ await checkArgs("not string", "about:blank", {
+ "browser.startup.homepage_override.once": { url: 5 },
+ });
+
+ await checkArgs("not https", "about:blank", {
+ "browser.startup.homepage_override.once": {
+ url: "http://www.mozilla.org/",
+ },
+ });
+
+ await checkArgs("not portless", "about:blank", {
+ "browser.startup.homepage_override.once": {
+ url: "https://www.mozilla.org:123/",
+ },
+ });
+
+ await checkArgs("invalid protocol", "about:blank", {
+ "browser.startup.homepage_override.once": {
+ url: "data:text/plain,hello world",
+ },
+ });
+
+ await checkArgs("invalid domain", "about:blank", {
+ "browser.startup.homepage_override.once": {
+ url: "https://wwwmozilla.org/",
+ },
+ });
+
+ await checkArgs(
+ "invalid second domain",
+ "https://valid.firefox.com/|https://mozilla.org/",
+ {
+ "browser.startup.homepage_override.once": {
+ url:
+ "https://valid.firefox.com|https://invalidfirefox.com|https://mozilla.org",
+ },
+ }
+ );
+});
+
+add_task(async function test_once() {
+ await checkArgs("initial test prefs (no homepage)", "about:blank");
+
+ const url = "https://www.mozilla.org/";
+ await checkArgs("override once", url, {
+ "browser.startup.homepage_override.once": { url },
+ });
+
+ await checkArgs("once cleared", "about:blank");
+
+ await checkArgs("formatted", "https://www.mozilla.org/en-US", {
+ "browser.startup.homepage_override.once": {
+ url: "https://www.mozilla.org/%LOCALE%",
+ },
+ });
+
+ await checkArgs("use homepage", "about:home", {
+ "browser.startup.page": 1,
+ });
+
+ await checkArgs("once with homepage", `${url}|about:home`, {
+ "browser.startup.homepage_override.once": { url },
+ });
+
+ await checkArgs("once cleared again", "about:home");
+
+ await checkArgs("prefer major version override", `about:welcome|about:home`, {
+ "browser.startup.homepage_override.mstone": "1.0",
+ "browser.startup.homepage_override.once": { url },
+ "startup.homepage_override_url": "about:welcome",
+ });
+
+ await checkArgs("once after major", `${url}|about:home`);
+
+ await checkArgs("once cleared yet again", "about:home");
+});
diff --git a/browser/components/tests/browser/whats_new_page/active-update.xml b/browser/components/tests/browser/whats_new_page/active-update.xml
new file mode 100644
index 0000000000..6e32eb1be2
--- /dev/null
+++ b/browser/components/tests/browser/whats_new_page/active-update.xml
@@ -0,0 +1 @@
+<?xml version="1.0"?><updates xmlns="http://www.mozilla.org/2005/app-update"><update xmlns="http://www.mozilla.org/2005/app-update" appVersion="99999999.0" buildID="20990101111111" channel="test" detailsURL="https://127.0.0.1/" displayVersion="1.0" installDate="1555716429454" isCompleteUpdate="true" name="What's New Page Test" previousAppVersion="60.0" serviceURL="https://127.0.0.1/update.xml" type="minor" platformVersion="99999999.0" actions="showURL" openURL="https://example.com/|https://example.com/"><patch size="1" type="complete" URL="https://127.0.0.1/complete.mar" selected="true" state="pending"/></update></updates>
diff --git a/browser/components/tests/browser/whats_new_page/browser.ini b/browser/components/tests/browser/whats_new_page/browser.ini
new file mode 100644
index 0000000000..a497578b77
--- /dev/null
+++ b/browser/components/tests/browser/whats_new_page/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = verify
+reason = This is a startup test. Verify runs tests multiple times after startup.
+support-files =
+ active-update.xml
+ updates/0/update.status
+prefs =
+ app.update.altUpdateDirPath='<test-root>/browser/components/tests/browser/whats_new_page'
+ app.update.disabledForTesting=false
+ browser.startup.homepage_override.mstone="60.0"
+
+[browser_whats_new_page.js]
diff --git a/browser/components/tests/browser/whats_new_page/browser_whats_new_page.js b/browser/components/tests/browser/whats_new_page/browser_whats_new_page.js
new file mode 100644
index 0000000000..2fdec5e7e1
--- /dev/null
+++ b/browser/components/tests/browser/whats_new_page/browser_whats_new_page.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function whats_new_page() {
+ // The test harness will use the current tab and remove the tab's history.
+ // Since the page that is tested is opened prior to the test harness taking
+ // over the current tab the active-update.xml specifies two pages to open by
+ // having 'https://example.com/|https://example.com/' for the value of openURL
+ // and then uses the first tab for the test.
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ // The test harness also changes the page to about:blank so go back to the
+ // page that was originally opened.
+ gBrowser.goBack();
+ // Wait for the page to go back to the original page.
+ await TestUtils.waitForCondition(
+ () =>
+ gBrowser.selectedBrowser &&
+ gBrowser.selectedBrowser.currentURI &&
+ gBrowser.selectedBrowser.currentURI.spec == "https://example.com/",
+ "Waiting for the expected page to reopen"
+ );
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ "https://example.com/",
+ "The what's new page's url should equal https://example.com/"
+ );
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ let um = Cc["@mozilla.org/updates/update-manager;1"].getService(
+ Ci.nsIUpdateManager
+ );
+ await TestUtils.waitForCondition(
+ () => !um.readyUpdate,
+ "Waiting for the ready update to be removed"
+ );
+ ok(!um.readyUpdate, "There should not be a ready update");
+ await TestUtils.waitForCondition(
+ () => !!um.getUpdateAt(0),
+ "Waiting for the ready update to be moved to the update history"
+ );
+ ok(!!um.getUpdateAt(0), "There should be an update in the update history");
+
+ // Leave no trace. Since this test modifies its support files put them back in
+ // their original state.
+ let alternatePath = Services.prefs.getCharPref("app.update.altUpdateDirPath");
+ let testRoot = Services.prefs.getCharPref("mochitest.testRoot");
+ let relativePath = alternatePath.substring("<test-root>".length);
+ if (AppConstants.platform == "win") {
+ relativePath = relativePath.replace(/\//g, "\\");
+ }
+ alternatePath = testRoot + relativePath;
+ let updateDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ updateDir.initWithPath(alternatePath);
+
+ let activeUpdateFile = updateDir.clone();
+ activeUpdateFile.append("active-update.xml");
+ await TestUtils.waitForCondition(
+ () => !activeUpdateFile.exists(),
+ "Waiting until the active-update.xml file does not exist"
+ );
+
+ let updatesFile = updateDir.clone();
+ updatesFile.append("updates.xml");
+ await TestUtils.waitForCondition(
+ () => updatesFile.exists(),
+ "Waiting until the updates.xml file exists"
+ );
+
+ let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ let flags =
+ FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE;
+
+ let stateSucceeded = "succeeded\n";
+ let updateStatusFile = updateDir.clone();
+ updateStatusFile.append("updates");
+ updateStatusFile.append("0");
+ updateStatusFile.append("update.status");
+ updateStatusFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ fos.init(updateStatusFile, flags, FileUtils.PERMS_FILE, 0);
+ fos.write(stateSucceeded, stateSucceeded.length);
+ fos.close();
+
+ let xmlContents =
+ '<?xml version="1.0"?><updates xmlns="http://www.mozilla.org/2005/' +
+ 'app-update"><update xmlns="http://www.mozilla.org/2005/app-update" ' +
+ 'appVersion="99999999.0" buildID="20990101111111" channel="test" ' +
+ 'detailsURL="https://127.0.0.1/" displayVersion="1.0" installDate="' +
+ '1555716429454" isCompleteUpdate="true" name="What\'s New Page Test" ' +
+ 'previousAppVersion="60.0" serviceURL="https://127.0.0.1/update.xml" ' +
+ 'type="minor" platformVersion="99999999.0" actions="showURL" ' +
+ 'openURL="https://example.com/|https://example.com/"><patch size="1" ' +
+ 'type="complete" URL="https://127.0.0.1/complete.mar" ' +
+ 'selected="true" state="pending"/></update></updates>\n';
+ activeUpdateFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ fos.init(activeUpdateFile, flags, FileUtils.PERMS_FILE, 0);
+ fos.write(xmlContents, xmlContents.length);
+ fos.close();
+
+ updatesFile.remove(false);
+ Cc["@mozilla.org/updates/update-manager;1"]
+ .getService(Ci.nsIUpdateManager)
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "um-reload-update-data", "");
+});
diff --git a/browser/components/tests/browser/whats_new_page/updates/0/update.status b/browser/components/tests/browser/whats_new_page/updates/0/update.status
new file mode 100644
index 0000000000..774a5c0df4
--- /dev/null
+++ b/browser/components/tests/browser/whats_new_page/updates/0/update.status
@@ -0,0 +1 @@
+succeeded
diff --git a/browser/components/tests/startupRecorder.js b/browser/components/tests/startupRecorder.js
new file mode 100644
index 0000000000..27f7db7f2d
--- /dev/null
+++ b/browser/components/tests/startupRecorder.js
@@ -0,0 +1,236 @@
+/* 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 Cm = Components.manager;
+Cm.QueryInterface(Ci.nsIServiceManager);
+
+const { ComponentUtils } = ChromeUtils.import(
+ "resource://gre/modules/ComponentUtils.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+let firstPaintNotification = "widget-first-paint";
+// widget-first-paint fires much later than expected on Linux.
+if (
+ AppConstants.platform == "linux" ||
+ Services.prefs.getBoolPref("browser.startup.preXulSkeletonUI", false)
+) {
+ firstPaintNotification = "xul-window-visible";
+}
+
+let win, canvas;
+let paints = [];
+let afterPaintListener = () => {
+ let width, height;
+ canvas.width = width = win.innerWidth;
+ canvas.height = height = win.innerHeight;
+ if (width < 1 || height < 1) {
+ return;
+ }
+ let ctx = canvas.getContext("2d", { alpha: false, willReadFrequently: true });
+
+ ctx.drawWindow(
+ win,
+ 0,
+ 0,
+ width,
+ height,
+ "white",
+ ctx.DRAWWINDOW_DO_NOT_FLUSH |
+ ctx.DRAWWINDOW_DRAW_VIEW |
+ ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
+ ctx.DRAWWINDOW_USE_WIDGET_LAYERS
+ );
+ paints.push({
+ data: ctx.getImageData(0, 0, width, height).data,
+ width,
+ height,
+ });
+};
+
+/**
+ * The startupRecorder component observes notifications at various stages of
+ * startup and records the set of JS components and modules that were already
+ * loaded at each of these points.
+ * The records are meant to be used by startup tests in
+ * browser/base/content/test/performance
+ * This component only exists in nightly and debug builds, it doesn't ship in
+ * our release builds.
+ */
+function startupRecorder() {
+ this.wrappedJSObject = this;
+ this.data = {
+ images: {
+ "image-drawing": new Set(),
+ "image-loading": new Set(),
+ },
+ code: {},
+ extras: {},
+ prefStats: {},
+ };
+ this.done = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+startupRecorder.prototype = {
+ classID: Components.ID("{11c095b2-e42e-4bdf-9dd0-aed87595f6a4}"),
+
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ record(name) {
+ ChromeUtils.addProfilerMarker("startupRecorder:" + name);
+ this.data.code[name] = {
+ components: Cu.loadedComponents,
+ modules: Cu.loadedModules,
+ services: Object.keys(Cc).filter(c => {
+ try {
+ return Cm.isServiceInstantiatedByContractID(c, Ci.nsISupports);
+ } catch (e) {
+ return false;
+ }
+ }),
+ };
+ this.data.extras[name] = {
+ hiddenWindowLoaded: Services.appShell.hasHiddenWindow,
+ };
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "app-startup") {
+ if (
+ !Services.prefs.getBoolPref("browser.startup.record", false) &&
+ !Services.prefs.getBoolPref("browser.startup.recordImages", false)
+ ) {
+ this._resolve();
+ this._resolve = null;
+ return;
+ }
+
+ // We can't ensure our observer will be called first or last, so the list of
+ // topics we observe here should avoid the topics used to trigger things
+ // during startup (eg. the topics observed by BrowserGlue.jsm).
+ let topics = [
+ "profile-do-change", // This catches stuff loaded during app-startup
+ "toplevel-window-ready", // Catches stuff from final-ui-startup
+ firstPaintNotification,
+ "sessionstore-windows-restored",
+ "browser-startup-idle-tasks-finished",
+ ];
+
+ if (Services.prefs.getBoolPref("browser.startup.recordImages", false)) {
+ // For code simplicify, recording images excludes the other startup
+ // recorder behaviors, so we can observe only the image topics.
+ topics = [
+ "image-loading",
+ "image-drawing",
+ "browser-startup-idle-tasks-finished",
+ ];
+ }
+ for (let t of topics) {
+ Services.obs.addObserver(this, t);
+ }
+ return;
+ }
+
+ // We only care about the first paint notification for browser windows, and
+ // not other types (for example, the gfx sanity test window)
+ if (topic == firstPaintNotification) {
+ // In the case we're handling xul-window-visible, we'll have been handed
+ // an nsIAppWindow instead of an nsIDOMWindow.
+ if (subject instanceof Ci.nsIAppWindow) {
+ subject = subject
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ }
+
+ if (
+ subject.document.documentElement.getAttribute("windowtype") !=
+ "navigator:browser"
+ ) {
+ return;
+ }
+ }
+
+ if (topic == "image-drawing" || topic == "image-loading") {
+ this.data.images[topic].add(data);
+ return;
+ }
+
+ Services.obs.removeObserver(this, topic);
+
+ if (topic == firstPaintNotification) {
+ // Because of the check for navigator:browser we made earlier, we know
+ // that if we got here, then the subject must be the first browser window.
+ win = subject;
+ canvas = win.document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "canvas"
+ );
+ canvas.mozOpaque = true;
+ afterPaintListener();
+ win.addEventListener("MozAfterPaint", afterPaintListener);
+ }
+
+ if (topic == "sessionstore-windows-restored") {
+ // We use idleDispatchToMainThread here to record the set of
+ // loaded scripts after we are fully done with startup and ready
+ // to react to user events.
+ Services.tm.dispatchToMainThread(
+ this.record.bind(this, "before handling user events")
+ );
+ } else if (topic == "browser-startup-idle-tasks-finished") {
+ if (Services.prefs.getBoolPref("browser.startup.recordImages", false)) {
+ Services.obs.removeObserver(this, "image-drawing");
+ Services.obs.removeObserver(this, "image-loading");
+ this._resolve();
+ this._resolve = null;
+ return;
+ }
+
+ this.record("before becoming idle");
+ win.removeEventListener("MozAfterPaint", afterPaintListener);
+ win = null;
+ this.data.frames = paints;
+ this.data.prefStats = {};
+ if (AppConstants.DEBUG) {
+ Services.prefs.readStats(
+ (key, value) => (this.data.prefStats[key] = value)
+ );
+ }
+ paints = null;
+
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ if (!env.exists("MOZ_PROFILER_STARTUP_PERFORMANCE_TEST")) {
+ this._resolve();
+ this._resolve = null;
+ return;
+ }
+
+ Services.profiler.getProfileDataAsync().then(profileData => {
+ this.data.profile = profileData;
+ // There's no equivalent StartProfiler call in this file because the
+ // profiler is started using the MOZ_PROFILER_STARTUP environment
+ // variable in browser/base/content/test/performance/browser.ini
+ Services.profiler.StopProfiler();
+
+ this._resolve();
+ this._resolve = null;
+ });
+ } else {
+ const topicsToNames = {
+ "profile-do-change": "before profile selection",
+ "toplevel-window-ready": "before opening first browser window",
+ };
+ topicsToNames[firstPaintNotification] = "before first paint";
+ this.record(topicsToNames[topic]);
+ }
+ },
+};
+
+this.NSGetFactory = ComponentUtils.generateNSGetFactory([startupRecorder]);
diff --git a/browser/components/tests/testComponents.manifest b/browser/components/tests/testComponents.manifest
new file mode 100644
index 0000000000..efd8bc9596
--- /dev/null
+++ b/browser/components/tests/testComponents.manifest
@@ -0,0 +1,5 @@
+# This component restricts its registration for the app-startup category
+# to the browser app so it doesn't get loaded in xpcshell.
+component {11c095b2-e42e-4bdf-9dd0-aed87595f6a4} startupRecorder.js
+contract @mozilla.org/test/startuprecorder;1 {11c095b2-e42e-4bdf-9dd0-aed87595f6a4}
+category app-startup startupRecorder service,@mozilla.org/test/startuprecorder;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
diff --git a/browser/components/tests/unit/distribution.ini b/browser/components/tests/unit/distribution.ini
new file mode 100644
index 0000000000..d7d2988083
--- /dev/null
+++ b/browser/components/tests/unit/distribution.ini
@@ -0,0 +1,58 @@
+# Distribution Configuration File
+# Test of distribution preferences
+
+[Global]
+id=disttest
+version=1.0
+about=Test distribution file
+about.en-US=Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè
+
+[Preferences]
+distribution.test.string="Test String"
+distribution.test.string.noquotes=Test String
+distribution.test.int=777
+distribution.test.bool.true=true
+distribution.test.bool.false=false
+distribution.test.empty=
+
+distribution.test.pref.locale="%LOCALE%"
+distribution.test.pref.language.reset="Preference Set"
+distribution.test.pref.locale.reset="Preference Set"
+distribution.test.pref.locale.set="Preference Set"
+distribution.test.pref.language.set="Preference Set"
+
+[Preferences-en]
+distribution.test.pref.language.en="en"
+distribution.test.pref.language.reset=
+distribution.test.pref.language.set="Language Set"
+distribution.test.pref.locale.set="Language Set"
+
+[Preferences-en-US]
+distribution.test.pref.locale.en-US="en-US"
+distribution.test.pref.locale.reset=
+distribution.test.pref.locale.set="Locale Set"
+
+
+[Preferences-de]
+distribution.test.pref.language.de="de"
+
+[LocalizablePreferences]
+distribution.test.locale="%LOCALE%"
+distribution.test.language.reset="Preference Set"
+distribution.test.locale.reset="Preference Set"
+distribution.test.locale.set="Preference Set"
+distribution.test.language.set="Preference Set"
+
+[LocalizablePreferences-en]
+distribution.test.language.en="en"
+distribution.test.language.reset=
+distribution.test.language.set="Language Set"
+distribution.test.locale.set="Language Set"
+
+[LocalizablePreferences-en-US]
+distribution.test.locale.en-US="en-US"
+distribution.test.locale.reset=
+distribution.test.locale.set="Locale Set"
+
+[LocalizablePreferences-de]
+distribution.test.language.de="de"
diff --git a/browser/components/tests/unit/head.js b/browser/components/tests/unit/head.js
new file mode 100644
index 0000000000..5ad9d5e7b6
--- /dev/null
+++ b/browser/components/tests/unit/head.js
@@ -0,0 +1,7 @@
+/* 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/. */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+var gProfD = do_get_profile().QueryInterface(Ci.nsIFile);
diff --git a/browser/components/tests/unit/test_browserGlue_migration_no_errors.js b/browser/components/tests/unit/test_browserGlue_migration_no_errors.js
new file mode 100644
index 0000000000..aed88c95fe
--- /dev/null
+++ b/browser/components/tests/unit/test_browserGlue_migration_no_errors.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Since various migrations in BrowserGlue are sometimes trivial, e.g. just
+ * clearing a pref, it does not feel necessary to write tests for each of those.
+ *
+ * However, ensuring we have at least some coverage to check for errors, e.g.
+ * typos, is a good idea, hence this test.
+ *
+ * If your migration is more complex that clearing a couple of prefs, you
+ * should consider adding your own BrowserGlue migration test.
+ */
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration";
+
+const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
+ Ci.nsIObserver
+);
+
+// Set the migration value to 1, to ensure that the migration code is called,
+// and so that this doesn't need updating every time we obsolete old tests.
+Services.prefs.setIntPref("browser.migration.version", 1);
+
+add_task(async function test_no_errors() {
+ // Simulate a migration.
+ gBrowserGlue.observe(
+ null,
+ TOPIC_BROWSERGLUE_TEST,
+ TOPICDATA_BROWSERGLUE_TEST
+ );
+
+ Assert.ok(true, "should have run the migration with no errors");
+});
diff --git a/browser/components/tests/unit/test_browserGlue_migration_social_cleanup.js b/browser/components/tests/unit/test_browserGlue_migration_social_cleanup.js
new file mode 100644
index 0000000000..00b59202a4
--- /dev/null
+++ b/browser/components/tests/unit/test_browserGlue_migration_social_cleanup.js
@@ -0,0 +1,30 @@
+const UI_VERSION = 69;
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration";
+
+var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
+ Ci.nsIObserver
+);
+
+Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
+
+add_task(async function test_check_cleanup_social_prefs() {
+ Services.prefs.setStringPref("social.manifest.example-com", "example.com");
+
+ // Simulate a migration.
+ gBrowserGlue.observe(
+ null,
+ TOPIC_BROWSERGLUE_TEST,
+ TOPICDATA_BROWSERGLUE_TEST
+ );
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue("social.manifest.example-com"),
+ "should have cleared old social preference 'social.manifest.example-com'"
+ );
+});
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.migration.version");
+ Services.prefs.clearUserPref("social.manifest.example-com");
+});
diff --git a/browser/components/tests/unit/test_distribution.js b/browser/components/tests/unit/test_distribution.js
new file mode 100644
index 0000000000..a0a046bd7d
--- /dev/null
+++ b/browser/components/tests/unit/test_distribution.js
@@ -0,0 +1,216 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that preferences are properly set by distribution.ini
+ */
+
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PromiseTestUtils.jsm"
+);
+
+// This test causes BrowserGlue to start but not fully initialise, when the
+// AddonManager shuts down BrowserGlue will then try to uninit which will
+// cause AutoComplete.jsm to throw an error.
+// TODO: Fix in https://bugzilla.mozilla.org/show_bug.cgi?id=1543112.
+PromiseTestUtils.allowMatchingRejectionsGlobally(/A request was aborted/);
+PromiseTestUtils.allowMatchingRejectionsGlobally(
+ /The operation failed for reasons unrelated/
+);
+
+const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+
+registerCleanupFunction(async function() {
+ // Remove the distribution dir, even if the test failed, otherwise all
+ // next tests will use it.
+ let folderPath = OS.Path.join(OS.Constants.Path.profileDir, "distribution");
+ await OS.File.removeDir(folderPath, { ignoreAbsent: true });
+ Assert.ok(!(await OS.File.exists(folderPath)));
+ Services.prefs.clearUserPref("distribution.testing.loadFromProfile");
+});
+
+add_task(async function() {
+ // Set special pref to load distribution.ini from the profile folder.
+ Services.prefs.setBoolPref("distribution.testing.loadFromProfile", true);
+
+ // Copy distribution.ini file to the profile dir.
+ let distroDir = gProfD.clone();
+ distroDir.leafName = "distribution";
+ let iniFile = distroDir.clone();
+ iniFile.append("distribution.ini");
+ if (iniFile.exists()) {
+ iniFile.remove(false);
+ print("distribution.ini already exists, did some test forget to cleanup?");
+ }
+
+ let testDistributionFile = do_get_cwd().clone();
+ testDistributionFile.append("distribution.ini");
+ testDistributionFile.copyTo(distroDir, "distribution.ini");
+ Assert.ok(testDistributionFile.exists());
+});
+
+add_task(async function() {
+ // Force distribution.
+ let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(
+ Ci.nsIObserver
+ );
+ glue.observe(
+ null,
+ TOPIC_BROWSERGLUE_TEST,
+ TOPICDATA_DISTRIBUTION_CUSTOMIZATION
+ );
+
+ var defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ Assert.equal(defaultBranch.getCharPref("distribution.id"), "disttest");
+ Assert.equal(defaultBranch.getCharPref("distribution.version"), "1.0");
+ Assert.equal(
+ defaultBranch.getStringPref("distribution.about"),
+ "Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè"
+ );
+
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.string"),
+ "Test String"
+ );
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.string.noquotes"),
+ "Test String"
+ );
+ Assert.equal(defaultBranch.getIntPref("distribution.test.int"), 777);
+ Assert.equal(defaultBranch.getBoolPref("distribution.test.bool.true"), true);
+ Assert.equal(
+ defaultBranch.getBoolPref("distribution.test.bool.false"),
+ false
+ );
+
+ Assert.throws(
+ () => defaultBranch.getCharPref("distribution.test.empty"),
+ /NS_ERROR_UNEXPECTED/
+ );
+ Assert.throws(
+ () => defaultBranch.getIntPref("distribution.test.empty"),
+ /NS_ERROR_UNEXPECTED/
+ );
+ Assert.throws(
+ () => defaultBranch.getBoolPref("distribution.test.empty"),
+ /NS_ERROR_UNEXPECTED/
+ );
+
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.pref.locale"),
+ "en-US"
+ );
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.pref.language.en"),
+ "en"
+ );
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.pref.locale.en-US"),
+ "en-US"
+ );
+ Assert.throws(
+ () => defaultBranch.getCharPref("distribution.test.pref.language.de"),
+ /NS_ERROR_UNEXPECTED/
+ );
+ // This value was never set because of the empty language specific pref
+ Assert.throws(
+ () => defaultBranch.getCharPref("distribution.test.pref.language.reset"),
+ /NS_ERROR_UNEXPECTED/
+ );
+ // This value was never set because of the empty locale specific pref
+ Assert.throws(
+ () => defaultBranch.getCharPref("distribution.test.pref.locale.reset"),
+ /NS_ERROR_UNEXPECTED/
+ );
+ // This value was overridden by a locale specific setting
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.pref.locale.set"),
+ "Locale Set"
+ );
+ // This value was overridden by a language specific setting
+ Assert.equal(
+ defaultBranch.getCharPref("distribution.test.pref.language.set"),
+ "Language Set"
+ );
+ // Language should not override locale
+ Assert.notEqual(
+ defaultBranch.getCharPref("distribution.test.pref.locale.set"),
+ "Language Set"
+ );
+
+ Assert.equal(
+ defaultBranch.getComplexValue(
+ "distribution.test.locale",
+ Ci.nsIPrefLocalizedString
+ ).data,
+ "en-US"
+ );
+ Assert.equal(
+ defaultBranch.getComplexValue(
+ "distribution.test.language.en",
+ Ci.nsIPrefLocalizedString
+ ).data,
+ "en"
+ );
+ Assert.equal(
+ defaultBranch.getComplexValue(
+ "distribution.test.locale.en-US",
+ Ci.nsIPrefLocalizedString
+ ).data,
+ "en-US"
+ );
+ Assert.throws(
+ () =>
+ defaultBranch.getComplexValue(
+ "distribution.test.language.de",
+ Ci.nsIPrefLocalizedString
+ ),
+ /NS_ERROR_UNEXPECTED/
+ );
+ // This value was never set because of the empty language specific pref
+ Assert.throws(
+ () =>
+ defaultBranch.getComplexValue(
+ "distribution.test.language.reset",
+ Ci.nsIPrefLocalizedString
+ ),
+ /NS_ERROR_UNEXPECTED/
+ );
+ // This value was never set because of the empty locale specific pref
+ Assert.throws(
+ () =>
+ defaultBranch.getComplexValue(
+ "distribution.test.locale.reset",
+ Ci.nsIPrefLocalizedString
+ ),
+ /NS_ERROR_UNEXPECTED/
+ );
+ // This value was overridden by a locale specific setting
+ Assert.equal(
+ defaultBranch.getComplexValue(
+ "distribution.test.locale.set",
+ Ci.nsIPrefLocalizedString
+ ).data,
+ "Locale Set"
+ );
+ // This value was overridden by a language specific setting
+ Assert.equal(
+ defaultBranch.getComplexValue(
+ "distribution.test.language.set",
+ Ci.nsIPrefLocalizedString
+ ).data,
+ "Language Set"
+ );
+ // Language should not override locale
+ Assert.notEqual(
+ defaultBranch.getComplexValue(
+ "distribution.test.locale.set",
+ Ci.nsIPrefLocalizedString
+ ).data,
+ "Language Set"
+ );
+});
diff --git a/browser/components/tests/unit/test_distribution_cachedexistence.js b/browser/components/tests/unit/test_distribution_cachedexistence.js
new file mode 100644
index 0000000000..f08a4ae592
--- /dev/null
+++ b/browser/components/tests/unit/test_distribution_cachedexistence.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that DistributionCustomizer correctly caches the existence
+ * of the distribution.ini file and just rechecks it after a version
+ * update.
+ */
+
+const PREF_CACHED_FILE_EXISTENCE = "distribution.iniFile.exists.value";
+const PREF_CACHED_FILE_APPVERSION = "distribution.iniFile.exists.appversion";
+const PREF_LOAD_FROM_PROFILE = "distribution.testing.loadFromProfile";
+
+const gTestDir = do_get_cwd();
+
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+add_task(async function() {
+ // Start with a clean slate of the prefs that control this feature.
+ Services.prefs.clearUserPref(PREF_CACHED_FILE_APPVERSION);
+ Services.prefs.clearUserPref(PREF_CACHED_FILE_EXISTENCE);
+ setupTest();
+
+ let { DistributionCustomizer } = ChromeUtils.import(
+ "resource:///modules/distribution.js"
+ );
+ let distribution = new DistributionCustomizer();
+
+ copyDistributionToProfile();
+
+ // Check that checking for distribution.ini returns the right value and sets up
+ // the cached prefs.
+ let exists = distribution._hasDistributionIni;
+
+ Assert.ok(exists);
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_CACHED_FILE_EXISTENCE, undefined),
+ true
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(PREF_CACHED_FILE_APPVERSION, "unknown"),
+ AppConstants.MOZ_APP_VERSION
+ );
+
+ // Check that calling _hasDistributionIni again will use the cached value. We do
+ // this by deleting the file and expecting it to still return true instead of false.
+ // Also, we need to delete _hasDistributionIni from the object because the getter
+ // was replaced with a stored value.
+ deleteDistribution();
+ delete distribution._hasDistributionIni;
+
+ exists = distribution._hasDistributionIni;
+ Assert.ok(exists);
+
+ // Now let's invalidate the PREF_CACHED_FILE_EXISTENCE pref to make sure the
+ // value gets recomputed correctly.
+ Services.prefs.clearUserPref(PREF_CACHED_FILE_EXISTENCE);
+ delete distribution._hasDistributionIni;
+ exists = distribution._hasDistributionIni;
+
+ // It now should return false, as well as storing false in the pref.
+ Assert.ok(!exists);
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_CACHED_FILE_EXISTENCE, undefined),
+ false
+ );
+
+ // Check now that it will use the new cached value instead of returning true in
+ // the presence of the file.
+ copyDistributionToProfile();
+ delete distribution._hasDistributionIni;
+ exists = distribution._hasDistributionIni;
+
+ Assert.ok(!exists);
+
+ // Now let's do the same, but invalidating the App Version, as if a version
+ // update occurred.
+ Services.prefs.setStringPref(PREF_CACHED_FILE_APPVERSION, "older version");
+ delete distribution._hasDistributionIni;
+ exists = distribution._hasDistributionIni;
+
+ Assert.ok(exists);
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_CACHED_FILE_EXISTENCE, undefined),
+ true
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(PREF_CACHED_FILE_APPVERSION, "unknown"),
+ AppConstants.MOZ_APP_VERSION
+ );
+});
+
+/*
+ * Helper functions
+ */
+function copyDistributionToProfile() {
+ // Copy distribution.ini file to the profile dir.
+ let distroDir = gProfD.clone();
+ distroDir.leafName = "distribution";
+ let iniFile = distroDir.clone();
+ iniFile.append("distribution.ini");
+ if (iniFile.exists()) {
+ iniFile.remove(false);
+ print("distribution.ini already exists, did some test forget to cleanup?");
+ }
+
+ let testDistributionFile = gTestDir.clone();
+ testDistributionFile.append("distribution.ini");
+ testDistributionFile.copyTo(distroDir, "distribution.ini");
+ Assert.ok(testDistributionFile.exists());
+}
+
+function deleteDistribution() {
+ let distroDir = gProfD.clone();
+ distroDir.leafName = "distribution";
+ let iniFile = distroDir.clone();
+ iniFile.append("distribution.ini");
+ iniFile.remove(false);
+}
+
+function setupTest() {
+ // Set special pref to load distribution.ini from the profile folder.
+ Services.prefs.setBoolPref(PREF_LOAD_FROM_PROFILE, true);
+}
+
+registerCleanupFunction(function() {
+ deleteDistribution();
+ Services.prefs.clearUserPref(PREF_LOAD_FROM_PROFILE);
+});
diff --git a/browser/components/tests/unit/xpcshell.ini b/browser/components/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..b341664db6
--- /dev/null
+++ b/browser/components/tests/unit/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head = head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ distribution.ini
+
+[test_distribution.js]
+[test_distribution_cachedexistence.js]
+[test_browserGlue_migration_no_errors.js]
+[test_browserGlue_migration_social_cleanup.js]