summaryrefslogtreecommitdiffstats
path: root/browser/components/shell/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/shell/test
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/shell/test')
-rw-r--r--browser/components/shell/test/browser.ini49
-rw-r--r--browser/components/shell/test/browser_1119088.js170
-rw-r--r--browser/components/shell/test/browser_420786.js105
-rw-r--r--browser/components/shell/test/browser_633221.js15
-rw-r--r--browser/components/shell/test/browser_doesAppNeedPin.js54
-rw-r--r--browser/components/shell/test/browser_headless_screenshot_1.js74
-rw-r--r--browser/components/shell/test/browser_headless_screenshot_2.js48
-rw-r--r--browser/components/shell/test/browser_headless_screenshot_3.js59
-rw-r--r--browser/components/shell/test/browser_headless_screenshot_4.js31
-rw-r--r--browser/components/shell/test/browser_headless_screenshot_cross_origin.js9
-rw-r--r--browser/components/shell/test/browser_headless_screenshot_redirect.js14
-rw-r--r--browser/components/shell/test/browser_setDefaultBrowser.js90
-rw-r--r--browser/components/shell/test/browser_setDefaultPDFHandler.js221
-rw-r--r--browser/components/shell/test/browser_setDesktopBackgroundPreview.js91
-rw-r--r--browser/components/shell/test/head.js159
-rw-r--r--browser/components/shell/test/headless.html6
-rw-r--r--browser/components/shell/test/headless_cross_origin.html7
-rw-r--r--browser/components/shell/test/headless_iframe.html6
-rw-r--r--browser/components/shell/test/headless_redirect.html0
-rw-r--r--browser/components/shell/test/headless_redirect.html^headers^2
-rwxr-xr-xbrowser/components/shell/test/mac_desktop_image.py168
-rw-r--r--browser/components/shell/test/unit/test_macOS_showSecurityPreferences.js30
-rw-r--r--browser/components/shell/test/unit/xpcshell.ini6
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"