diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /browser/components/tests | |
parent | Initial commit. (diff) | |
download | firefox-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')
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] |