diff options
Diffstat (limited to '')
23 files changed, 1414 insertions, 0 deletions
diff --git a/browser/components/shell/test/browser.ini b/browser/components/shell/test/browser.ini new file mode 100644 index 0000000000..e3193bc466 --- /dev/null +++ b/browser/components/shell/test/browser.ini @@ -0,0 +1,49 @@ +[DEFAULT] + +[browser_420786.js] +skip-if = os != "linux" +[browser_633221.js] +skip-if = os != "linux" +[browser_1119088.js] +support-files = + mac_desktop_image.py +skip-if = os != "mac" || verify +[browser_doesAppNeedPin.js] +[browser_setDefaultBrowser.js] +[browser_setDefaultPDFHandler.js] +run-if = os == "win" && os_version != "6.1" +reason = Test is Windows 10+. +[browser_setDesktopBackgroundPreview.js] +[browser_headless_screenshot_1.js] +support-files = + head.js + headless.html +skip-if = os == 'win' || ccov || tsan #Bug 1429950 , Bug 1583315, Bug 1696109, Bug 1701449 +[browser_headless_screenshot_2.js] +support-files = + head.js + headless.html +skip-if = os == 'win' || ccov|| tsan #Bug 1429950 , Bug 1583315, Bug 1696109, Bug 1701449 +[browser_headless_screenshot_3.js] +support-files = + head.js + headless.html +skip-if = os == 'win' || ccov || tsan #Bug 1429950 , Bug 1583315, Bug 1696109, Bug 1701449 +[browser_headless_screenshot_4.js] +support-files = + head.js + headless.html +skip-if = os == 'win' || ccov || tsan #Bug 1429950 , Bug 1583315, Bug 1696109, Bug 1701449 +[browser_headless_screenshot_redirect.js] +support-files = + head.js + headless.html + headless_redirect.html + headless_redirect.html^headers^ +skip-if = os == 'win' || ccov || tsan #Bug 1429950 , Bug 1583315, Bug 1696109, Bug 1701449 +[browser_headless_screenshot_cross_origin.js] +support-files = + head.js + headless_cross_origin.html + headless_iframe.html +skip-if = os == 'win' || ccov || tsan #Bug 1429950 , Bug 1583315, Bug 1696109, Bug 1701449 diff --git a/browser/components/shell/test/browser_1119088.js b/browser/components/shell/test/browser_1119088.js new file mode 100644 index 0000000000..fd95fb94af --- /dev/null +++ b/browser/components/shell/test/browser_1119088.js @@ -0,0 +1,170 @@ +// Where we save the desktop background to (~/Pictures). +const NS_OSX_PICTURE_DOCUMENTS_DIR = "Pct"; + +// Paths used to run the CLI command (python script) that is used to +// 1) check the desktop background image matches what we set it to via +// nsIShellService::setDesktopBackground() and +// 2) revert the desktop background image to the OS default +const kPythonPath = "/usr/bin/python"; +const kDesktopCheckerScriptPath = + "browser/browser/components/shell/test/mac_desktop_image.py"; +const kDefaultBackgroundImage_10_14 = + "/Library/Desktop Pictures/Solid Colors/Teal.png"; +const kDefaultBackgroundImage_10_15 = + "/System/Library/Desktop Pictures/Solid Colors/Teal.png"; + +ChromeUtils.defineESModuleGetters(this, { + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", +}); + +function getPythonExecutableFile() { + let python = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + python.initWithPath(kPythonPath); + return python; +} + +function createProcess() { + return Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); +} + +// Use a CLI command to set the desktop background to |imagePath|. Returns the +// exit code of the CLI command which reflects whether or not the background +// image was successfully set. Returns 0 on success. +function setDesktopBackgroundCLI(imagePath) { + let setBackgroundProcess = createProcess(); + setBackgroundProcess.init(getPythonExecutableFile()); + let args = [ + kDesktopCheckerScriptPath, + "--verbose", + "--set-background-image", + imagePath, + ]; + setBackgroundProcess.run(true, args, args.length); + return setBackgroundProcess.exitValue; +} + +// Check the desktop background is |imagePath| using a CLI command. +// Returns the exit code of the CLI command which reflects whether or not +// the provided image path matches the path of the current desktop background +// image. A return value of 0 indicates success/match. +function checkDesktopBackgroundCLI(imagePath) { + let checkBackgroundProcess = createProcess(); + checkBackgroundProcess.init(getPythonExecutableFile()); + let args = [ + kDesktopCheckerScriptPath, + "--verbose", + "--check-background-image", + imagePath, + ]; + checkBackgroundProcess.run(true, args, args.length); + return checkBackgroundProcess.exitValue; +} + +// Use the python script to set/check the desktop background is |imagePath| +function setAndCheckDesktopBackgroundCLI(imagePath) { + Assert.ok(FileUtils.File(imagePath).exists(), `${imagePath} exists`); + + let setExitCode = setDesktopBackgroundCLI(imagePath); + Assert.ok(setExitCode == 0, `Setting background via CLI to ${imagePath}`); + + let checkExitCode = checkDesktopBackgroundCLI(imagePath); + Assert.ok(checkExitCode == 0, `Checking background via CLI is ${imagePath}`); +} + +// Restore the automation default background image. i.e., the default used +// in the automated test environment, not the OS default. +function restoreDefaultBackground() { + let defaultBackgroundPath; + if (AppConstants.isPlatformAndVersionAtLeast("macosx", 19)) { + defaultBackgroundPath = kDefaultBackgroundImage_10_15; + } else { + defaultBackgroundPath = kDefaultBackgroundImage_10_14; + } + setAndCheckDesktopBackgroundCLI(defaultBackgroundPath); +} + +/** + * Tests "Set As Desktop Background" platform implementation on macOS. + * + * Sets the desktop background image to the browser logo from the about:logo + * page and verifies it was set successfully. Setting the desktop background + * (which uses the nsIShellService::setDesktopBackground() interface method) + * downloads the image to ~/Pictures using a unique file name and sets the + * desktop background to the downloaded file leaving the download in place. + * After setDesktopBackground() is called, the test uses a python script to + * validate that the current desktop background is in fact set to the + * downloaded logo. + */ +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:logo", + }, + async browser => { + let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( + Ci.nsIDirectoryServiceProvider + ); + let uuidGenerator = Services.uuid; + let shellSvc = Cc["@mozilla.org/browser/shell-service;1"].getService( + Ci.nsIShellService + ); + + // Ensure we are starting with the default background. Log a + // failure if we can not set the background to the default, but + // ignore the case where the background is not already set as that + // that may be due to a previous test failure. + restoreDefaultBackground(); + + // Generate a UUID (with non-alphanumberic characters removed) to build + // up a filename for the desktop background. Use a UUID to distinguish + // between runs so we won't be confused by images that were not properly + // cleaned up after previous runs. + let uuid = uuidGenerator + .generateUUID() + .toString() + .replace(/\W/g, ""); + + // Set the background image path to be $HOME/Pictures/<UUID>.png. + // nsIShellService.setDesktopBackground() downloads the image to this + // path and then sets it as the desktop background image, leaving the + // image in place. + let backgroundImage = dirSvc.getFile(NS_OSX_PICTURE_DOCUMENTS_DIR, {}); + backgroundImage.append(uuid + ".png"); + if (backgroundImage.exists()) { + backgroundImage.remove(false); + } + + // For simplicity, we're going to reach in and access the image on the + // page directly, which means the page shouldn't be running in a remote + // browser. Thankfully, about:logo runs in the parent process for now. + Assert.ok( + !gBrowser.selectedBrowser.isRemoteBrowser, + "image can be accessed synchronously from the parent process" + ); + let image = gBrowser.selectedBrowser.contentDocument.images[0]; + + info(`Setting/saving desktop background to ${backgroundImage.path}`); + + // Saves the file in ~/Pictures + shellSvc.setDesktopBackground(image, 0, backgroundImage.leafName); + + await BrowserTestUtils.waitForCondition(() => backgroundImage.exists()); + info(`${backgroundImage.path} downloaded`); + Assert.ok( + FileUtils.File(backgroundImage.path).exists(), + `${backgroundImage.path} exists` + ); + + // Check that the desktop background image is the image we set above. + let exitCode = checkDesktopBackgroundCLI(backgroundImage.path); + Assert.ok(exitCode == 0, `background should be ${backgroundImage.path}`); + + // Restore the background image to the Mac default. + restoreDefaultBackground(); + + // We no longer need the downloaded image. + backgroundImage.remove(false); + } + ); +}); diff --git a/browser/components/shell/test/browser_420786.js b/browser/components/shell/test/browser_420786.js new file mode 100644 index 0000000000..3443011189 --- /dev/null +++ b/browser/components/shell/test/browser_420786.js @@ -0,0 +1,105 @@ +const DG_BACKGROUND = "/desktop/gnome/background"; +const DG_IMAGE_KEY = DG_BACKGROUND + "/picture_filename"; +const DG_OPTION_KEY = DG_BACKGROUND + "/picture_options"; +const DG_DRAW_BG_KEY = DG_BACKGROUND + "/draw_background"; + +const GS_BG_SCHEMA = "org.gnome.desktop.background"; +const GS_IMAGE_KEY = "picture-uri"; +const GS_OPTION_KEY = "picture-options"; +const GS_DRAW_BG_KEY = "draw-background"; + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:logo", + }, + browser => { + var brandName = Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandShortName"); + + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( + Ci.nsIDirectoryServiceProvider + ); + var homeDir = dirSvc.getFile("Home", {}); + + var wpFile = homeDir.clone(); + wpFile.append(brandName + "_wallpaper.png"); + + // Backup the existing wallpaper so that this test doesn't change the user's + // settings. + var wpFileBackup = homeDir.clone(); + wpFileBackup.append(brandName + "_wallpaper.png.backup"); + + if (wpFileBackup.exists()) { + wpFileBackup.remove(false); + } + + if (wpFile.exists()) { + wpFile.copyTo(null, wpFileBackup.leafName); + } + + var shell = Cc["@mozilla.org/browser/shell-service;1"].getService( + Ci.nsIShellService + ); + + // For simplicity, we're going to reach in and access the image on the + // page directly, which means the page shouldn't be running in a remote + // browser. Thankfully, about:logo runs in the parent process for now. + Assert.ok( + !gBrowser.selectedBrowser.isRemoteBrowser, + "image can be accessed synchronously from the parent process" + ); + + var image = content.document.images[0]; + + let checkWallpaper, restoreSettings; + try { + // Try via GSettings first + const gsettings = Cc["@mozilla.org/gsettings-service;1"] + .getService(Ci.nsIGSettingsService) + .getCollectionForSchema(GS_BG_SCHEMA); + + const prevImage = gsettings.getString(GS_IMAGE_KEY); + const prevOption = gsettings.getString(GS_OPTION_KEY); + const prevDrawBG = gsettings.getBoolean(GS_DRAW_BG_KEY); + + checkWallpaper = function(position, expectedGSettingsPosition) { + shell.setDesktopBackground(image, position, ""); + ok(wpFile.exists(), "Wallpaper was written to disk"); + is( + gsettings.getString(GS_IMAGE_KEY), + encodeURI("file://" + wpFile.path), + "Wallpaper file GSettings key is correct" + ); + is( + gsettings.getString(GS_OPTION_KEY), + expectedGSettingsPosition, + "Wallpaper position GSettings key is correct" + ); + }; + + restoreSettings = function() { + gsettings.setString(GS_IMAGE_KEY, prevImage); + gsettings.setString(GS_OPTION_KEY, prevOption); + gsettings.setBoolean(GS_DRAW_BG_KEY, prevDrawBG); + }; + } catch (e) {} + + checkWallpaper(Ci.nsIShellService.BACKGROUND_TILE, "wallpaper"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_STRETCH, "stretched"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_CENTER, "centered"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_FILL, "zoom"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_FIT, "scaled"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_SPAN, "spanned"); + + restoreSettings(); + + // Restore files + if (wpFileBackup.exists()) { + wpFileBackup.moveTo(null, wpFile.leafName); + } + } + ); +}); diff --git a/browser/components/shell/test/browser_633221.js b/browser/components/shell/test/browser_633221.js new file mode 100644 index 0000000000..b61aeba6fa --- /dev/null +++ b/browser/components/shell/test/browser_633221.js @@ -0,0 +1,15 @@ +const { ShellService } = ChromeUtils.import( + "resource:///modules/ShellService.jsm" +); + +function test() { + ShellService.setDefaultBrowser(true, false); + ok( + ShellService.isDefaultBrowser(true, false), + "we got here and are the default browser" + ); + ok( + ShellService.isDefaultBrowser(true, true), + "we got here and are the default browser" + ); +} diff --git a/browser/components/shell/test/browser_doesAppNeedPin.js b/browser/components/shell/test/browser_doesAppNeedPin.js new file mode 100644 index 0000000000..1ecb6cc108 --- /dev/null +++ b/browser/components/shell/test/browser_doesAppNeedPin.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +XPCOMUtils.defineLazyModuleGetters(this, { + ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm", + ExperimentFakes: "resource://testing-common/NimbusTestUtils.jsm", + NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm", +}); + +registerCleanupFunction(() => { + ExperimentAPI._store._deleteForTests("shellService"); +}); + +let defaultValue; +add_task(async function default_need() { + defaultValue = await ShellService.doesAppNeedPin(); + + Assert.ok(defaultValue !== undefined, "Got a default app need pin value"); +}); + +add_task(async function remote_disable() { + if (defaultValue === false) { + info("Default pin already false, so nothing to test"); + return; + } + + let doCleanup = await ExperimentFakes.enrollWithRollout({ + featureId: NimbusFeatures.shellService.featureId, + value: { disablePin: true, enabled: true }, + }); + + Assert.equal( + await ShellService.doesAppNeedPin(), + false, + "Pinning disabled via nimbus" + ); + + await doCleanup(); +}); + +add_task(async function restore_default() { + if (defaultValue === undefined) { + info("No default pin value set, so nothing to test"); + return; + } + + ExperimentAPI._store._deleteForTests("shellService"); + + Assert.equal( + await ShellService.doesAppNeedPin(), + defaultValue, + "Pinning restored to original" + ); +}); diff --git a/browser/components/shell/test/browser_headless_screenshot_1.js b/browser/components/shell/test/browser_headless_screenshot_1.js new file mode 100644 index 0000000000..bde8c53e5e --- /dev/null +++ b/browser/components/shell/test/browser_headless_screenshot_1.js @@ -0,0 +1,74 @@ +"use strict"; + +add_task(async function() { + // Test all four basic variations of the "screenshot" argument + // when a file path is specified. + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + screenshotPath, + ], + screenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + `-screenshot=${screenshotPath}`, + ], + screenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "--screenshot", + screenshotPath, + ], + screenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + `--screenshot=${screenshotPath}`, + ], + screenshotPath + ); + + // Test when the requested URL redirects + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless_redirect.html", + "-screenshot", + screenshotPath, + ], + screenshotPath + ); + + // Test with additional command options + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + screenshotPath, + "-attach-console", + ], + screenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-attach-console", + "-screenshot", + screenshotPath, + "-headless", + ], + screenshotPath + ); +}); diff --git a/browser/components/shell/test/browser_headless_screenshot_2.js b/browser/components/shell/test/browser_headless_screenshot_2.js new file mode 100644 index 0000000000..063fcfe01e --- /dev/null +++ b/browser/components/shell/test/browser_headless_screenshot_2.js @@ -0,0 +1,48 @@ +"use strict"; +add_task(async function() { + const cwdScreenshotPath = PathUtils.join( + Services.dirsvc.get("CurWorkD", Ci.nsIFile).path, + "screenshot.png" + ); + + // Test variations of the "screenshot" argument when a file path + // isn't specified. + await testFileCreationPositive( + [ + "-screenshot", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + ], + cwdScreenshotPath + ); + await testFileCreationPositive( + [ + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + ], + cwdScreenshotPath + ); + await testFileCreationPositive( + [ + "--screenshot", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + ], + cwdScreenshotPath + ); + await testFileCreationPositive( + [ + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "--screenshot", + ], + cwdScreenshotPath + ); + + // Test with additional command options + await testFileCreationPositive( + [ + "--screenshot", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-attach-console", + ], + cwdScreenshotPath + ); +}); diff --git a/browser/components/shell/test/browser_headless_screenshot_3.js b/browser/components/shell/test/browser_headless_screenshot_3.js new file mode 100644 index 0000000000..32c0f91768 --- /dev/null +++ b/browser/components/shell/test/browser_headless_screenshot_3.js @@ -0,0 +1,59 @@ +"use strict"; + +add_task(async function() { + const cwdScreenshotPath = PathUtils.join( + Services.dirsvc.get("CurWorkD", Ci.nsIFile).path, + "screenshot.png" + ); + + // Test invalid URL arguments (either no argument or too many arguments). + await testFileCreationNegative(["-screenshot"], cwdScreenshotPath); + await testFileCreationNegative( + [ + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "http://mochi.test:8888/headless.html", + "-screenshot", + ], + cwdScreenshotPath + ); + + // Test all four basic variations of the "window-size" argument. + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + "-window-size", + "800", + ], + cwdScreenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + "-window-size=800", + ], + cwdScreenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + "--window-size", + "800", + ], + cwdScreenshotPath + ); + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + "--window-size=800", + ], + cwdScreenshotPath + ); +}); diff --git a/browser/components/shell/test/browser_headless_screenshot_4.js b/browser/components/shell/test/browser_headless_screenshot_4.js new file mode 100644 index 0000000000..0e3493ef67 --- /dev/null +++ b/browser/components/shell/test/browser_headless_screenshot_4.js @@ -0,0 +1,31 @@ +"use strict"; + +add_task(async function() { + const cwdScreenshotPath = PathUtils.join( + Services.dirsvc.get("CurWorkD", Ci.nsIFile).path, + "screenshot.png" + ); + // Test other variations of the "window-size" argument. + await testWindowSizePositive(800, 600); + await testWindowSizePositive(1234); + await testFileCreationNegative( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + "-window-size", + "hello", + ], + cwdScreenshotPath + ); + await testFileCreationNegative( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + "-window-size", + "800,", + ], + cwdScreenshotPath + ); +}); diff --git a/browser/components/shell/test/browser_headless_screenshot_cross_origin.js b/browser/components/shell/test/browser_headless_screenshot_cross_origin.js new file mode 100644 index 0000000000..aca7fb2cbe --- /dev/null +++ b/browser/components/shell/test/browser_headless_screenshot_cross_origin.js @@ -0,0 +1,9 @@ +"use strict"; + +add_task(async function() { + // Test cross origin iframes work. + await testGreen( + "http://mochi.test:8888/browser/browser/components/shell/test/headless_cross_origin.html", + screenshotPath + ); +}); diff --git a/browser/components/shell/test/browser_headless_screenshot_redirect.js b/browser/components/shell/test/browser_headless_screenshot_redirect.js new file mode 100644 index 0000000000..d34dcf6e69 --- /dev/null +++ b/browser/components/shell/test/browser_headless_screenshot_redirect.js @@ -0,0 +1,14 @@ +"use strict"; + +add_task(async function() { + // Test when the requested URL redirects + await testFileCreationPositive( + [ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless_redirect.html", + "-screenshot", + screenshotPath, + ], + screenshotPath + ); +}); diff --git a/browser/components/shell/test/browser_setDefaultBrowser.js b/browser/components/shell/test/browser_setDefaultBrowser.js new file mode 100644 index 0000000000..8a8b4d5913 --- /dev/null +++ b/browser/components/shell/test/browser_setDefaultBrowser.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +XPCOMUtils.defineLazyModuleGetters(this, { + ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm", + ExperimentFakes: "resource://testing-common/NimbusTestUtils.jsm", + NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm", + sinon: "resource://testing-common/Sinon.jsm", +}); + +const userChoiceStub = sinon + .stub(ShellService, "setAsDefaultUserChoice") + .resolves(); +const setDefaultStub = sinon.stub(); +const shellStub = sinon + .stub(ShellService, "shellService") + .value({ setDefaultBrowser: setDefaultStub }); +registerCleanupFunction(() => { + userChoiceStub.restore(); + shellStub.restore(); + + ExperimentAPI._store._deleteForTests("shellService"); +}); + +let defaultUserChoice; +add_task(async function need_user_choice() { + ShellService.setDefaultBrowser(); + defaultUserChoice = userChoiceStub.called; + + Assert.ok( + defaultUserChoice !== undefined, + "Decided which default browser method to use" + ); + Assert.equal( + setDefaultStub.notCalled, + defaultUserChoice, + "Only one default behavior was used" + ); +}); + +add_task(async function remote_disable() { + if (defaultUserChoice === false) { + info("Default behavior already not user choice, so nothing to test"); + return; + } + + userChoiceStub.resetHistory(); + setDefaultStub.resetHistory(); + let doCleanup = await ExperimentFakes.enrollWithRollout({ + featureId: NimbusFeatures.shellService.featureId, + value: { + setDefaultBrowserUserChoice: false, + enabled: true, + }, + }); + + ShellService.setDefaultBrowser(); + + Assert.ok( + userChoiceStub.notCalled, + "Set default with user choice disabled via nimbus" + ); + Assert.ok(setDefaultStub.called, "Used plain set default insteead"); + + await doCleanup(); +}); + +add_task(async function restore_default() { + if (defaultUserChoice === undefined) { + info("No default user choice behavior set, so nothing to test"); + return; + } + + userChoiceStub.resetHistory(); + setDefaultStub.resetHistory(); + ExperimentAPI._store._deleteForTests("shellService"); + + ShellService.setDefaultBrowser(); + + Assert.equal( + userChoiceStub.called, + defaultUserChoice, + "Set default with user choice restored to original" + ); + Assert.equal( + setDefaultStub.notCalled, + defaultUserChoice, + "Plain set default behavior restored to original" + ); +}); diff --git a/browser/components/shell/test/browser_setDefaultPDFHandler.js b/browser/components/shell/test/browser_setDefaultPDFHandler.js new file mode 100644 index 0000000000..2041ac275d --- /dev/null +++ b/browser/components/shell/test/browser_setDefaultPDFHandler.js @@ -0,0 +1,221 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +XPCOMUtils.defineLazyModuleGetters(this, { + ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm", + ExperimentFakes: "resource://testing-common/NimbusTestUtils.jsm", + NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm", + sinon: "resource://testing-common/Sinon.jsm", +}); + +XPCOMUtils.defineLazyServiceGetter( + this, + "XreDirProvider", + "@mozilla.org/xre/directory-provider;1", + "nsIXREDirProvider" +); + +const _callExternalDefaultBrowserAgentStub = sinon + .stub(ShellService, "_callExternalDefaultBrowserAgent") + .callsFake(async () => ({ + async wait() { + return { exitCode: 0 }; + }, + })); + +const _userChoiceImpossibleTelemetryResultStub = sinon + .stub(ShellService, "_userChoiceImpossibleTelemetryResult") + .callsFake(() => null); + +// Ensure we don't fall back to a real implementation. +const setDefaultStub = sinon.stub(); +// We'll dynamically update this as needed during the tests. +const queryCurrentDefaultHandlerForStub = sinon.stub(); +const shellStub = sinon.stub(ShellService, "shellService").value({ + setDefaultBrowser: setDefaultStub, + queryCurrentDefaultHandlerFor: queryCurrentDefaultHandlerForStub, +}); + +registerCleanupFunction(() => { + _callExternalDefaultBrowserAgentStub.restore(); + _userChoiceImpossibleTelemetryResultStub.restore(); + shellStub.restore(); + + ExperimentAPI._store._deleteForTests("shellService"); +}); + +add_task(async function ready() { + await ExperimentAPI.ready(); +}); + +// Everything here is Windows 10+. +Assert.ok( + AppConstants.isPlatformAndVersionAtLeast("win", "10"), + "Windows version 10+" +); + +add_task(async function remoteEnableWithPDF() { + let doCleanup = await ExperimentFakes.enrollWithRollout({ + featureId: NimbusFeatures.shellService.featureId, + value: { + setDefaultBrowserUserChoice: true, + setDefaultPDFHandlerOnlyReplaceBrowsers: false, + setDefaultPDFHandler: true, + enabled: true, + }, + }); + + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), + true + ); + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), + true + ); + + _callExternalDefaultBrowserAgentStub.resetHistory(); + ShellService.setDefaultBrowser(); + + const aumi = XreDirProvider.getInstallHash(); + Assert.ok(_callExternalDefaultBrowserAgentStub.called); + Assert.deepEqual(_callExternalDefaultBrowserAgentStub.firstCall.args, [ + { + arguments: [ + "set-default-browser-user-choice", + aumi, + ".pdf", + "FirefoxPDF", + ], + }, + ]); + + await doCleanup(); +}); + +add_task(async function remoteEnableWithPDF_testOnlyReplaceBrowsers() { + let doCleanup = await ExperimentFakes.enrollWithRollout({ + featureId: NimbusFeatures.shellService.featureId, + value: { + setDefaultBrowserUserChoice: true, + setDefaultPDFHandlerOnlyReplaceBrowsers: true, + setDefaultPDFHandler: true, + enabled: true, + }, + }); + + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), + true + ); + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), + true + ); + Assert.equal( + NimbusFeatures.shellService.getVariable( + "setDefaultPDFHandlerOnlyReplaceBrowsers" + ), + true + ); + + const aumi = XreDirProvider.getInstallHash(); + + // We'll take the default from a missing association or a known browser. + for (let progId of ["", "MSEdgePDF"]) { + queryCurrentDefaultHandlerForStub.callsFake(() => progId); + + _callExternalDefaultBrowserAgentStub.resetHistory(); + ShellService.setDefaultBrowser(); + + Assert.ok(_callExternalDefaultBrowserAgentStub.called); + Assert.deepEqual( + _callExternalDefaultBrowserAgentStub.firstCall.args, + [ + { + arguments: [ + "set-default-browser-user-choice", + aumi, + ".pdf", + "FirefoxPDF", + ], + }, + ], + `Will take default from missing association or known browser with ProgID '${progId}'` + ); + } + + // But not from a non-browser. + queryCurrentDefaultHandlerForStub.callsFake(() => "Acrobat.Document.DC"); + + _callExternalDefaultBrowserAgentStub.resetHistory(); + ShellService.setDefaultBrowser(); + + Assert.ok(_callExternalDefaultBrowserAgentStub.called); + Assert.deepEqual( + _callExternalDefaultBrowserAgentStub.firstCall.args, + [{ arguments: ["set-default-browser-user-choice", aumi] }], + `Will not take default from non-browser` + ); + + await doCleanup(); +}); + +add_task(async function remoteEnableWithoutPDF() { + let doCleanup = await ExperimentFakes.enrollWithRollout({ + featureId: NimbusFeatures.shellService.featureId, + value: { + setDefaultBrowserUserChoice: true, + setDefaultPDFHandler: false, + enabled: true, + }, + }); + + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), + true + ); + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), + false + ); + + _callExternalDefaultBrowserAgentStub.resetHistory(); + ShellService.setDefaultBrowser(); + + const aumi = XreDirProvider.getInstallHash(); + Assert.ok(_callExternalDefaultBrowserAgentStub.called); + Assert.deepEqual(_callExternalDefaultBrowserAgentStub.firstCall.args, [ + { arguments: ["set-default-browser-user-choice", aumi] }, + ]); + + await doCleanup(); +}); + +add_task(async function remoteDisable() { + let doCleanup = await ExperimentFakes.enrollWithRollout({ + featureId: NimbusFeatures.shellService.featureId, + value: { + setDefaultBrowserUserChoice: false, + setDefaultPDFHandler: true, + enabled: false, + }, + }); + + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), + false + ); + Assert.equal( + NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), + true + ); + + _callExternalDefaultBrowserAgentStub.resetHistory(); + ShellService.setDefaultBrowser(); + + Assert.ok(_callExternalDefaultBrowserAgentStub.notCalled); + Assert.ok(setDefaultStub.called); + + await doCleanup(); +}); diff --git a/browser/components/shell/test/browser_setDesktopBackgroundPreview.js b/browser/components/shell/test/browser_setDesktopBackgroundPreview.js new file mode 100644 index 0000000000..fcdf566a8b --- /dev/null +++ b/browser/components/shell/test/browser_setDesktopBackgroundPreview.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check whether the preview image for setDesktopBackground is rendered + * correctly, without stretching + */ + +const { ShellService } = ChromeUtils.import( + "resource:///modules/ShellService.jsm" +); + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:logo", + }, + async browser => { + const dialogLoad = BrowserTestUtils.domWindowOpened(null, async win => { + await BrowserTestUtils.waitForEvent(win, "load"); + Assert.equal( + win.document.documentElement.getAttribute("windowtype"), + "Shell:SetDesktopBackground", + "Opened correct window" + ); + return true; + }); + + const image = content.document.images[0]; + EventUtils.synthesizeMouseAtCenter(image, { type: "contextmenu" }); + + const menu = document.getElementById("contentAreaContextMenu"); + await BrowserTestUtils.waitForPopupEvent(menu, "shown"); + const menuClosed = BrowserTestUtils.waitForPopupEvent(menu, "hidden"); + + const menuItem = document.getElementById("context-setDesktopBackground"); + try { + menu.activateItem(menuItem); + } catch (ex) { + ok( + menuItem.hidden, + "should only fail to activate when menu item is hidden" + ); + ok( + !ShellService.canSetDesktopBackground, + "Should only hide when not able to set the desktop background" + ); + is( + AppConstants.platform, + "linux", + "Should always be able to set desktop background on non-linux platforms" + ); + todo(false, "Skipping test on this configuration"); + + menu.hidePopup(); + await menuClosed; + return; + } + + await menuClosed; + + const win = await dialogLoad; + + /* setDesktopBackground.js does a setTimeout to wait for correct + dimensions. If we don't wait here we could read the preview dimensions + before they're changed to match the screen */ + await TestUtils.waitForTick(); + + const canvas = win.document.getElementById("screen"); + const screenRatio = screen.width / screen.height; + const previewRatio = canvas.clientWidth / canvas.clientHeight; + + info(`Screen dimensions are ${screen.width}x${screen.height}`); + info(`Screen's raw ratio is ${screenRatio}`); + info( + `Preview dimensions are ${canvas.clientWidth}x${canvas.clientHeight}` + ); + info(`Preview's raw ratio is ${previewRatio}`); + + Assert.ok( + previewRatio < screenRatio + 0.01 && previewRatio > screenRatio - 0.01, + "Preview's aspect ratio is within ±.01 of screen's" + ); + + win.close(); + + await menuClosed; + } + ); +}); diff --git a/browser/components/shell/test/head.js b/browser/components/shell/test/head.js new file mode 100644 index 0000000000..ab7e6d0879 --- /dev/null +++ b/browser/components/shell/test/head.js @@ -0,0 +1,159 @@ +"use strict"; + +const { Subprocess } = ChromeUtils.importESModule( + "resource://gre/modules/Subprocess.sys.mjs" +); + +const TEMP_DIR = Services.dirsvc.get("TmpD", Ci.nsIFile).path; + +const screenshotPath = PathUtils.join(TEMP_DIR, "headless_test_screenshot.png"); + +async function runFirefox(args) { + const XRE_EXECUTABLE_FILE = "XREExeF"; + const firefoxExe = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).path; + const NS_APP_PREFS_50_FILE = "PrefF"; + const mochiPrefsFile = Services.dirsvc.get(NS_APP_PREFS_50_FILE, Ci.nsIFile); + const mochiPrefsPath = mochiPrefsFile.path; + const mochiPrefsName = mochiPrefsFile.leafName; + const profilePath = PathUtils.join( + TEMP_DIR, + "headless_test_screenshot_profile" + ); + const prefsPath = PathUtils.join(profilePath, mochiPrefsName); + const firefoxArgs = ["-profile", profilePath, "-no-remote"]; + + await IOUtils.makeDirectory(profilePath); + await IOUtils.copy(mochiPrefsPath, prefsPath); + let proc = await Subprocess.call({ + command: firefoxExe, + arguments: firefoxArgs.concat(args), + // Disable leak detection to avoid intermittent failure bug 1331152. + environmentAppend: true, + environment: { + ASAN_OPTIONS: + "detect_leaks=0:quarantine_size=50331648:malloc_context_size=5", + // Don't enable Marionette. + MOZ_MARIONETTE: null, + }, + }); + let stdout; + while ((stdout = await proc.stdout.readString())) { + dump(`>>> ${stdout}\n`); + } + let { exitCode } = await proc.wait(); + is(exitCode, 0, "Firefox process should exit with code 0"); + await IOUtils.remove(profilePath, { recursive: true }); +} + +async function testFileCreationPositive(args, path) { + await runFirefox(args); + + let saved = IOUtils.exists(path); + ok(saved, "A screenshot should be saved as " + path); + if (!saved) { + return; + } + + let info = await IOUtils.stat(path); + ok(info.size > 0, "Screenshot should not be an empty file"); + await IOUtils.remove(path); +} + +async function testFileCreationNegative(args, path) { + await runFirefox(args); + + let saved = await IOUtils.exists(path); + ok(!saved, "A screenshot should not be saved"); + await IOUtils.remove(path); +} + +async function testWindowSizePositive(width, height) { + let size = String(width); + if (height) { + size += "," + height; + } + + await runFirefox([ + "-url", + "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", + "-screenshot", + screenshotPath, + "-window-size", + size, + ]); + + let saved = await IOUtils.exists(screenshotPath); + ok(saved, "A screenshot should be saved in the tmp directory"); + if (!saved) { + return; + } + + let data = await IOUtils.read(screenshotPath); + await new Promise((resolve, reject) => { + let blob = new Blob([data], { type: "image/png" }); + let reader = new FileReader(); + reader.onloadend = function() { + let screenshot = new Image(); + screenshot.onload = function() { + is( + screenshot.width, + width, + "Screenshot should be " + width + " pixels wide" + ); + if (height) { + is( + screenshot.height, + height, + "Screenshot should be " + height + " pixels tall" + ); + } + resolve(); + }; + screenshot.src = reader.result; + }; + reader.readAsDataURL(blob); + }); + await IOUtils.remove(screenshotPath); +} + +async function testGreen(url, path) { + await runFirefox(["-url", url, `--screenshot=${path}`]); + + let saved = await IOUtils.exists(path); + ok(saved, "A screenshot should be saved in the tmp directory"); + if (!saved) { + return; + } + + let data = await IOUtils.read(path); + let image = await new Promise((resolve, reject) => { + let blob = new Blob([data], { type: "image/png" }); + let reader = new FileReader(); + reader.onloadend = function() { + let screenshot = new Image(); + screenshot.onload = function() { + resolve(screenshot); + }; + screenshot.src = reader.result; + }; + reader.readAsDataURL(blob); + }); + let canvas = document.createElement("canvas"); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + let ctx = canvas.getContext("2d"); + ctx.drawImage(image, 0, 0); + let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + let rgba = imageData.data; + + let found = false; + for (let i = 0; i < rgba.length; i += 4) { + if (rgba[i] === 0 && rgba[i + 1] === 255 && rgba[i + 2] === 0) { + found = true; + break; + } + } + ok(found, "There should be a green pixel in the screenshot."); + + await IOUtils.remove(path); +} diff --git a/browser/components/shell/test/headless.html b/browser/components/shell/test/headless.html new file mode 100644 index 0000000000..bbde895077 --- /dev/null +++ b/browser/components/shell/test/headless.html @@ -0,0 +1,6 @@ +<html> +<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head> +<body style="background-color: rgb(0, 255, 0); color: rgb(0, 0, 255)"> +Hi +</body> +</html> diff --git a/browser/components/shell/test/headless_cross_origin.html b/browser/components/shell/test/headless_cross_origin.html new file mode 100644 index 0000000000..3bb09aa5d8 --- /dev/null +++ b/browser/components/shell/test/headless_cross_origin.html @@ -0,0 +1,7 @@ +<html> +<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head> +<body> +<iframe width="300" height="200" src="http://example.com/browser/browser/components/shell/test/headless_iframe.html"></iframe> +Hi +</body> +</html> diff --git a/browser/components/shell/test/headless_iframe.html b/browser/components/shell/test/headless_iframe.html new file mode 100644 index 0000000000..f318cbe0f3 --- /dev/null +++ b/browser/components/shell/test/headless_iframe.html @@ -0,0 +1,6 @@ +<html> +<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head> +<body style="background-color: rgb(0, 255, 0);"> +Hi +</body> +</html> diff --git a/browser/components/shell/test/headless_redirect.html b/browser/components/shell/test/headless_redirect.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/browser/components/shell/test/headless_redirect.html diff --git a/browser/components/shell/test/headless_redirect.html^headers^ b/browser/components/shell/test/headless_redirect.html^headers^ new file mode 100644 index 0000000000..1d7db20ea5 --- /dev/null +++ b/browser/components/shell/test/headless_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved Temporarily +Location: headless.html
\ No newline at end of file diff --git a/browser/components/shell/test/mac_desktop_image.py b/browser/components/shell/test/mac_desktop_image.py new file mode 100755 index 0000000000..e3ccc77190 --- /dev/null +++ b/browser/components/shell/test/mac_desktop_image.py @@ -0,0 +1,168 @@ +#!/usr/bin/python +# 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/. */ + +""" +mac_desktop_image.py + +Mac-specific utility to get/set the desktop background image or check that +the current background image path matches a provided path. + +Depends on Objective-C python binding imports which are in the python import +paths by default when using macOS's /usr/bin/python. + +Includes generous amount of logging to aid debugging for use in automated tests. +""" + +import argparse +import logging +import os +import sys + +# +# These Objective-C bindings imports are included in the import path by default +# for the Mac-bundled python installed in /usr/bin/python. They're needed to +# call the Objective-C API's to set and retrieve the current desktop background +# image. +# +from AppKit import NSScreen, NSWorkspace +from Cocoa import NSURL + + +def main(): + parser = argparse.ArgumentParser( + description="Utility to print, set, or " + + "check the path to image being used as " + + "the desktop background image. By " + + "default, prints the path to the " + + "current desktop background image." + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="print verbose debugging information", + default=False, + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-s", + "--set-background-image", + dest="newBackgroundImagePath", + required=False, + help="path to the new background image to set. A zero " + + "exit code indicates no errors occurred.", + default=None, + ) + group.add_argument( + "-c", + "--check-background-image", + dest="checkBackgroundImagePath", + required=False, + help="check if the provided background image path " + + "matches the provided path. A zero exit code " + + "indicates the paths match.", + default=None, + ) + args = parser.parse_args() + + # Using logging for verbose output + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.CRITICAL) + logger = logging.getLogger("desktopImage") + + # Print what we're going to do + if args.checkBackgroundImagePath is not None: + logger.debug( + "checking provided desktop image %s matches current " + "image" % args.checkBackgroundImagePath + ) + elif args.newBackgroundImagePath is not None: + logger.debug("setting image to %s " % args.newBackgroundImagePath) + else: + logger.debug("retrieving desktop image path") + + focussedScreen = NSScreen.mainScreen() + if not focussedScreen: + raise RuntimeError("mainScreen error") + + ws = NSWorkspace.sharedWorkspace() + if not ws: + raise RuntimeError("sharedWorkspace error") + + # If we're just checking the image path, check it and then return. + # A successful exit code (0) indicates the paths match. + if args.checkBackgroundImagePath is not None: + # Get existing desktop image path and resolve it + existingImageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) + existingImagePath = existingImageURL.path() + existingImagePathReal = os.path.realpath(existingImagePath) + logger.debug("existing desktop image: %s" % existingImagePath) + logger.debug("existing desktop image realpath: %s" % existingImagePath) + + # Resolve the path we're going to check + checkImagePathReal = os.path.realpath(args.checkBackgroundImagePath) + logger.debug("check desktop image: %s" % args.checkBackgroundImagePath) + logger.debug("check desktop image realpath: %s" % checkImagePathReal) + + if existingImagePathReal == checkImagePathReal: + print("desktop image path matches provided path") + return True + + print("desktop image path does NOT match provided path") + return False + + # Log the current desktop image + if args.verbose: + existingImageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) + logger.debug("existing desktop image: %s" % existingImageURL.path()) + + # Set the desktop image + if args.newBackgroundImagePath is not None: + newImagePath = args.newBackgroundImagePath + if not os.path.exists(newImagePath): + logger.critical("%s does not exist" % newImagePath) + return False + if not os.access(newImagePath, os.R_OK): + logger.critical("%s is not readable" % newImagePath) + return False + + logger.debug("new desktop image to set: %s" % newImagePath) + newImageURL = NSURL.fileURLWithPath_(newImagePath) + logger.debug("new desktop image URL to set: %s" % newImageURL) + + status = False + (status, error) = ws.setDesktopImageURL_forScreen_options_error_( + newImageURL, focussedScreen, None, None + ) + if not status: + raise RuntimeError("setDesktopImageURL error") + + # Print the current desktop image + imageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) + imagePath = imageURL.path() + imagePathReal = os.path.realpath(imagePath) + logger.debug("updated desktop image URL: %s" % imageURL) + logger.debug("updated desktop image path: %s" % imagePath) + logger.debug("updated desktop image path (resolved): %s" % imagePathReal) + print(imagePathReal) + return True + + +def getCurrentDesktopImageURL(focussedScreen, workspace, logger): + imageURL = workspace.desktopImageURLForScreen_(focussedScreen) + if not imageURL: + raise RuntimeError("desktopImageURLForScreen returned invalid URL") + if not imageURL.isFileURL(): + logger.warning("desktop image URL is not a file URL") + return imageURL + + +if __name__ == "__main__": + if not main(): + sys.exit(1) + else: + sys.exit(0) diff --git a/browser/components/shell/test/unit/test_macOS_showSecurityPreferences.js b/browser/components/shell/test/unit/test_macOS_showSecurityPreferences.js new file mode 100644 index 0000000000..8550f0331a --- /dev/null +++ b/browser/components/shell/test/unit/test_macOS_showSecurityPreferences.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the macOS ShowSecurityPreferences shell service method. + */ + +function killSystemPreferences() { + let killallFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + killallFile.initWithPath("/usr/bin/killall"); + let sysPrefsArg = ["System Preferences"]; + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(killallFile); + process.run(true, sysPrefsArg, 1); + return process.exitValue; +} + +add_setup(async function() { + info("Ensure System Preferences isn't already running"); + killSystemPreferences(); +}); + +add_task(async function test_prefsOpen() { + let shellSvc = Cc["@mozilla.org/browser/shell-service;1"].getService( + Ci.nsIMacShellService + ); + shellSvc.showSecurityPreferences("Privacy_AllFiles"); + + equal(killSystemPreferences(), 0, "Ensure System Preferences was started"); +}); diff --git a/browser/components/shell/test/unit/xpcshell.ini b/browser/components/shell/test/unit/xpcshell.ini new file mode 100644 index 0000000000..383dcb3475 --- /dev/null +++ b/browser/components/shell/test/unit/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +skip-if = toolkit == 'android' # bug 1730213 +firefox-appdir = browser + +[test_macOS_showSecurityPreferences.js] +skip-if = toolkit != "cocoa" |