summaryrefslogtreecommitdiffstats
path: root/browser/components/shell
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/shell')
-rw-r--r--browser/components/shell/HeadlessShell.jsm231
-rw-r--r--browser/components/shell/ScreenshotChild.jsm54
-rw-r--r--browser/components/shell/ShellService.jsm170
-rw-r--r--browser/components/shell/WindowsDefaultBrowser.cpp174
-rw-r--r--browser/components/shell/WindowsDefaultBrowser.h21
-rw-r--r--browser/components/shell/content/setDesktopBackground.js252
-rw-r--r--browser/components/shell/content/setDesktopBackground.xhtml95
-rw-r--r--browser/components/shell/jar.mn7
-rw-r--r--browser/components/shell/moz.build79
-rw-r--r--browser/components/shell/nsGNOMEShellDBusHelper.cpp494
-rw-r--r--browser/components/shell/nsGNOMEShellDBusHelper.h38
-rw-r--r--browser/components/shell/nsGNOMEShellSearchProvider.cpp443
-rw-r--r--browser/components/shell/nsGNOMEShellSearchProvider.h139
-rw-r--r--browser/components/shell/nsGNOMEShellService.cpp520
-rw-r--r--browser/components/shell/nsGNOMEShellService.h43
-rw-r--r--browser/components/shell/nsIGNOMEShellService.idl19
-rw-r--r--browser/components/shell/nsIMacShellService.idl27
-rw-r--r--browser/components/shell/nsIShellService.idl69
-rw-r--r--browser/components/shell/nsIWindowsShellService.idl15
-rw-r--r--browser/components/shell/nsMacShellService.cpp282
-rw-r--r--browser/components/shell/nsMacShellService.h33
-rw-r--r--browser/components/shell/nsShellService.h11
-rw-r--r--browser/components/shell/nsToolkitShellService.h21
-rw-r--r--browser/components/shell/nsWindowsShellService.cpp626
-rw-r--r--browser/components/shell/nsWindowsShellService.h38
-rw-r--r--browser/components/shell/search-provider-files/README20
-rw-r--r--browser/components/shell/search-provider-files/firefox-search-provider.ini5
-rw-r--r--browser/components/shell/search-provider-files/firefox.desktop273
-rw-r--r--browser/components/shell/test/.eslintrc.js5
-rw-r--r--browser/components/shell/test/browser.ini11
-rw-r--r--browser/components/shell/test/browser_1119088.js172
-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_setDesktopBackgroundPreview.js66
-rw-r--r--browser/components/shell/test/chrome.ini8
-rw-r--r--browser/components/shell/test/headless.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.py171
-rw-r--r--browser/components/shell/test/test_headless_screenshot.html155
-rw-r--r--browser/components/shell/test/unit/test_macOS_showSecurityPreferences.js30
-rw-r--r--browser/components/shell/test/unit/xpcshell.ini5
42 files changed, 4950 insertions, 0 deletions
diff --git a/browser/components/shell/HeadlessShell.jsm b/browser/components/shell/HeadlessShell.jsm
new file mode 100644
index 0000000000..533e0efd0d
--- /dev/null
+++ b/browser/components/shell/HeadlessShell.jsm
@@ -0,0 +1,231 @@
+/* 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/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["HeadlessShell", "ScreenshotParent"];
+
+const { E10SUtils } = ChromeUtils.import(
+ "resource://gre/modules/E10SUtils.jsm"
+);
+const { HiddenFrame } = ChromeUtils.import(
+ "resource://gre/modules/HiddenFrame.jsm"
+);
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// Refrences to the progress listeners to keep them from being gc'ed
+// before they are called.
+const progressListeners = new Set();
+
+class ScreenshotParent extends JSWindowActorParent {
+ takeScreenshot(params) {
+ return this.sendQuery("TakeScreenshot", params);
+ }
+}
+
+ChromeUtils.registerWindowActor("Screenshot", {
+ parent: {
+ moduleURI: "resource:///modules/HeadlessShell.jsm",
+ },
+ child: {
+ moduleURI: "resource:///modules/ScreenshotChild.jsm",
+ messages: ["TakeScreenshot"],
+ },
+});
+
+function loadContentWindow(browser, url) {
+ let uri;
+ try {
+ uri = Services.io.newURI(url);
+ } catch (e) {
+ let msg = `Invalid URL passed to loadContentWindow(): ${url}`;
+ Cu.reportError(msg);
+ return Promise.reject(new Error(msg));
+ }
+
+ const principal = Services.scriptSecurityManager.getSystemPrincipal();
+ return new Promise((resolve, reject) => {
+ let oa = E10SUtils.predictOriginAttributes({
+ browser,
+ });
+ let loadURIOptions = {
+ triggeringPrincipal: principal,
+ remoteType: E10SUtils.getRemoteTypeForURI(
+ url,
+ true,
+ false,
+ E10SUtils.DEFAULT_REMOTE_TYPE,
+ null,
+ oa
+ ),
+ };
+ browser.loadURI(uri.spec, loadURIOptions);
+ let { webProgress } = browser;
+
+ let progressListener = {
+ onLocationChange(progress, request, location, flags) {
+ // Ignore inner-frame events
+ if (!progress.isTopLevel) {
+ return;
+ }
+ // Ignore events that don't change the document
+ if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
+ return;
+ }
+ // Ignore the initial about:blank, unless about:blank is requested
+ if (location.spec == "about:blank" && uri.spec != "about:blank") {
+ return;
+ }
+
+ progressListeners.delete(progressListener);
+ webProgress.removeProgressListener(progressListener);
+ resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ progressListeners.add(progressListener);
+ webProgress.addProgressListener(
+ progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+ });
+}
+
+async function takeScreenshot(
+ fullWidth,
+ fullHeight,
+ contentWidth,
+ contentHeight,
+ path,
+ url
+) {
+ let frame;
+ try {
+ frame = new HiddenFrame();
+ let windowlessBrowser = await frame.get();
+
+ let doc = windowlessBrowser.document;
+ let browser = doc.createXULElement("browser");
+ browser.setAttribute("remote", "true");
+ browser.setAttribute("type", "content");
+ browser.setAttribute(
+ "style",
+ `width: ${contentWidth}px; min-width: ${contentWidth}px; height: ${contentHeight}px; min-height: ${contentHeight}px;`
+ );
+ doc.documentElement.appendChild(browser);
+
+ await loadContentWindow(browser, url);
+
+ let actor = browser.browsingContext.currentWindowGlobal.getActor(
+ "Screenshot"
+ );
+ let blob = await actor.takeScreenshot({
+ fullWidth,
+ fullHeight,
+ });
+
+ let reader = await new Promise(resolve => {
+ let fr = new FileReader();
+ fr.onloadend = () => resolve(fr);
+ fr.readAsArrayBuffer(blob);
+ });
+
+ await OS.File.writeAtomic(path, new Uint8Array(reader.result), {
+ flush: true,
+ });
+ dump("Screenshot saved to: " + path + "\n");
+ } catch (e) {
+ dump("Failure taking screenshot: " + e + "\n");
+ } finally {
+ if (frame) {
+ frame.destroy();
+ }
+ }
+}
+
+let HeadlessShell = {
+ async handleCmdLineArgs(cmdLine, URLlist) {
+ try {
+ // Don't quit even though we don't create a window
+ Services.startup.enterLastWindowClosingSurvivalArea();
+
+ // Default options
+ let fullWidth = true;
+ let fullHeight = true;
+ // Most common screen resolution of Firefox users
+ let contentWidth = 1366;
+ let contentHeight = 768;
+
+ // Parse `window-size`
+ try {
+ var dimensionsStr = cmdLine.handleFlagWithParam("window-size", true);
+ } catch (e) {
+ dump("expected format: --window-size width[,height]\n");
+ return;
+ }
+ if (dimensionsStr) {
+ let success;
+ let dimensions = dimensionsStr.split(",", 2);
+ if (dimensions.length == 1) {
+ success = dimensions[0] > 0;
+ if (success) {
+ fullWidth = false;
+ fullHeight = true;
+ contentWidth = dimensions[0];
+ }
+ } else {
+ success = dimensions[0] > 0 && dimensions[1] > 0;
+ if (success) {
+ fullWidth = false;
+ fullHeight = false;
+ contentWidth = dimensions[0];
+ contentHeight = dimensions[1];
+ }
+ }
+
+ if (!success) {
+ dump("expected format: --window-size width[,height]\n");
+ return;
+ }
+ }
+
+ // Only command line argument left should be `screenshot`
+ // There could still be URLs however
+ try {
+ var path = cmdLine.handleFlagWithParam("screenshot", true);
+ if (!cmdLine.length && !URLlist.length) {
+ URLlist.push(path); // Assume the user wanted to specify a URL
+ path = OS.Path.join(cmdLine.workingDirectory.path, "screenshot.png");
+ }
+ } catch (e) {
+ path = OS.Path.join(cmdLine.workingDirectory.path, "screenshot.png");
+ cmdLine.handleFlag("screenshot", true); // Remove `screenshot`
+ }
+
+ for (let i = 0; i < cmdLine.length; ++i) {
+ URLlist.push(cmdLine.getArgument(i)); // Assume that all remaining arguments are URLs
+ }
+
+ if (URLlist.length == 1) {
+ await takeScreenshot(
+ fullWidth,
+ fullHeight,
+ contentWidth,
+ contentHeight,
+ path,
+ URLlist[0]
+ );
+ } else {
+ dump("expected exactly one URL when using `screenshot`\n");
+ }
+ } finally {
+ Services.startup.exitLastWindowClosingSurvivalArea();
+ Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+ }
+ },
+};
diff --git a/browser/components/shell/ScreenshotChild.jsm b/browser/components/shell/ScreenshotChild.jsm
new file mode 100644
index 0000000000..18e18b2d2d
--- /dev/null
+++ b/browser/components/shell/ScreenshotChild.jsm
@@ -0,0 +1,54 @@
+/* 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/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["ScreenshotChild"];
+
+class ScreenshotChild extends JSWindowActorChild {
+ receiveMessage(message) {
+ if (message.name == "TakeScreenshot") {
+ return this.takeScreenshot(message.data);
+ }
+ return null;
+ }
+
+ async takeScreenshot(params) {
+ if (this.document.readyState != "complete") {
+ await new Promise(resolve =>
+ this.contentWindow.addEventListener("load", resolve, { once: true })
+ );
+ }
+
+ let { fullWidth, fullHeight } = params;
+ let { contentWindow } = this;
+
+ let canvas = contentWindow.document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "html:canvas"
+ );
+ let context = canvas.getContext("2d");
+ let width = contentWindow.innerWidth;
+ let height = contentWindow.innerHeight;
+ if (fullWidth) {
+ width += contentWindow.scrollMaxX - contentWindow.scrollMinX;
+ }
+ if (fullHeight) {
+ height += contentWindow.scrollMaxY - contentWindow.scrollMinY;
+ }
+
+ canvas.width = width;
+ canvas.height = height;
+ context.drawWindow(
+ contentWindow,
+ 0,
+ 0,
+ width,
+ height,
+ "rgb(255, 255, 255)"
+ );
+
+ return new Promise(resolve => canvas.toBlob(resolve));
+ }
+}
diff --git a/browser/components/shell/ShellService.jsm b/browser/components/shell/ShellService.jsm
new file mode 100644
index 0000000000..5a1377abea
--- /dev/null
+++ b/browser/components/shell/ShellService.jsm
@@ -0,0 +1,170 @@
+/* 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/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["ShellService"];
+
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm"
+);
+
+/**
+ * Internal functionality to save and restore the docShell.allow* properties.
+ */
+let ShellServiceInternal = {
+ /**
+ * Used to determine whether or not to offer "Set as desktop background"
+ * functionality. Even if shell service is available it is not
+ * guaranteed that it is able to set the background for every desktop
+ * which is especially true for Linux with its many different desktop
+ * environments.
+ */
+ get canSetDesktopBackground() {
+ if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
+ return true;
+ }
+
+ if (AppConstants.platform == "linux") {
+ if (this.shellService) {
+ let linuxShellService = this.shellService.QueryInterface(
+ Ci.nsIGNOMEShellService
+ );
+ return linuxShellService.canSetDesktopBackground;
+ }
+ }
+
+ return false;
+ },
+
+ isDefaultBrowserOptOut() {
+ if (AppConstants.platform == "win") {
+ let optOutValue = WindowsRegistry.readRegKey(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox",
+ "DefaultBrowserOptOut"
+ );
+ WindowsRegistry.removeRegKey(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox",
+ "DefaultBrowserOptOut"
+ );
+ if (optOutValue == "True") {
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", false);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Used to determine whether or not to show a "Set Default Browser"
+ * query dialog. This attribute is true if the application is starting
+ * up and "browser.shell.checkDefaultBrowser" is true, otherwise it
+ * is false.
+ */
+ _checkedThisSession: false,
+ get shouldCheckDefaultBrowser() {
+ // If we've already checked, the browser has been started and this is a
+ // new window open, and we don't want to check again.
+ if (this._checkedThisSession) {
+ return false;
+ }
+
+ if (!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser")) {
+ return false;
+ }
+
+ if (this.isDefaultBrowserOptOut()) {
+ return false;
+ }
+
+ return true;
+ },
+
+ set shouldCheckDefaultBrowser(shouldCheck) {
+ Services.prefs.setBoolPref(
+ "browser.shell.checkDefaultBrowser",
+ !!shouldCheck
+ );
+ },
+
+ isDefaultBrowser(startupCheck, forAllTypes) {
+ // If this is the first browser window, maintain internal state that we've
+ // checked this session (so that subsequent window opens don't show the
+ // default browser dialog).
+ if (startupCheck) {
+ this._checkedThisSession = true;
+ }
+ if (this.shellService) {
+ return this.shellService.isDefaultBrowser(forAllTypes);
+ }
+ return false;
+ },
+
+ setAsDefault() {
+ let claimAllTypes = true;
+ let setAsDefaultError = false;
+ if (AppConstants.platform == "win") {
+ try {
+ // In Windows 8+, the UI for selecting default protocol is much
+ // nicer than the UI for setting file type associations. So we
+ // only show the protocol association screen on Windows 8+.
+ // Windows 8 is version 6.2.
+ let version = Services.sysinfo.getProperty("version");
+ claimAllTypes = parseFloat(version) < 6.2;
+ } catch (ex) {}
+ }
+ try {
+ ShellService.setDefaultBrowser(claimAllTypes, false);
+ } catch (ex) {
+ setAsDefaultError = true;
+ Cu.reportError(ex);
+ }
+ // Here BROWSER_IS_USER_DEFAULT and BROWSER_SET_USER_DEFAULT_ERROR appear
+ // to be inverse of each other, but that is only because this function is
+ // called when the browser is set as the default. During startup we record
+ // the BROWSER_IS_USER_DEFAULT value without recording BROWSER_SET_USER_DEFAULT_ERROR.
+ Services.telemetry
+ .getHistogramById("BROWSER_IS_USER_DEFAULT")
+ .add(!setAsDefaultError);
+ Services.telemetry
+ .getHistogramById("BROWSER_SET_DEFAULT_ERROR")
+ .add(setAsDefaultError);
+ },
+};
+
+XPCOMUtils.defineLazyServiceGetter(
+ ShellServiceInternal,
+ "shellService",
+ "@mozilla.org/browser/shell-service;1",
+ Ci.nsIShellService
+);
+
+/**
+ * The external API exported by this module.
+ */
+var ShellService = new Proxy(ShellServiceInternal, {
+ get(target, name) {
+ if (name in target) {
+ return target[name];
+ }
+ if (target.shellService) {
+ return target.shellService[name];
+ }
+ Services.console.logStringMessage(
+ `${name} not found in ShellService: ${target.shellService}`
+ );
+ return undefined;
+ },
+});
diff --git a/browser/components/shell/WindowsDefaultBrowser.cpp b/browser/components/shell/WindowsDefaultBrowser.cpp
new file mode 100644
index 0000000000..00e9e645e6
--- /dev/null
+++ b/browser/components/shell/WindowsDefaultBrowser.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/**
+ * This file exists so that LaunchModernSettingsDialogDefaultApps can be called
+ * without linking to libxul.
+ */
+#include "WindowsDefaultBrowser.h"
+
+#include "city.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+// Needed for access to IApplicationActivationManager
+// This must be before any other includes that might include shlobj.h
+#ifdef _WIN32_WINNT
+# undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#define INITGUID
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WIN8
+#include <shlobj.h>
+
+#include <lm.h>
+#include <shlwapi.h>
+#include <wchar.h>
+#include <windows.h>
+
+#define APP_REG_NAME_BASE L"Firefox-"
+
+static bool IsWindowsLogonConnected() {
+ WCHAR userName[UNLEN + 1];
+ DWORD size = mozilla::ArrayLength(userName);
+ if (!GetUserNameW(userName, &size)) {
+ return false;
+ }
+
+ LPUSER_INFO_24 info;
+ if (NetUserGetInfo(nullptr, userName, 24, (LPBYTE*)&info) != NERR_Success) {
+ return false;
+ }
+ bool connected = info->usri24_internet_identity;
+ NetApiBufferFree(info);
+
+ return connected;
+}
+
+static bool SettingsAppBelievesConnected() {
+ const wchar_t* keyPath = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations";
+ const wchar_t* valueName = L"IsConnectedAtLogon";
+
+ uint32_t value = 0;
+ DWORD size = sizeof(uint32_t);
+ LSTATUS ls = RegGetValueW(HKEY_CURRENT_USER, keyPath, valueName, RRF_RT_ANY,
+ nullptr, &value, &size);
+ if (ls != ERROR_SUCCESS) {
+ return false;
+ }
+
+ return !!value;
+}
+
+// Generates the install directory without a trailing path separator.
+bool GetInstallDirectory(mozilla::UniquePtr<wchar_t[]>& installPath) {
+ installPath = mozilla::GetFullBinaryPath();
+ // It's not safe to use PathRemoveFileSpecW with strings longer than MAX_PATH
+ // (including null terminator).
+ if (wcslen(installPath.get()) >= MAX_PATH) {
+ return false;
+ }
+ PathRemoveFileSpecW(installPath.get());
+ return true;
+}
+
+bool GetAppRegName(mozilla::UniquePtr<wchar_t[]>& aAppRegName) {
+ mozilla::UniquePtr<wchar_t[]> appDirStr;
+ bool success = GetInstallDirectory(appDirStr);
+ if (!success) {
+ return success;
+ }
+
+ uint64_t hash = CityHash64(reinterpret_cast<char*>(appDirStr.get()),
+ wcslen(appDirStr.get()) * sizeof(wchar_t));
+
+ const wchar_t* format = L"%s%I64X";
+ int bufferSize = _scwprintf(format, APP_REG_NAME_BASE, hash);
+ ++bufferSize; // Extra byte for terminating null
+ aAppRegName = mozilla::MakeUnique<wchar_t[]>(bufferSize);
+
+ _snwprintf_s(aAppRegName.get(), bufferSize, _TRUNCATE, format,
+ APP_REG_NAME_BASE, hash);
+
+ return success;
+}
+
+bool LaunchControlPanelDefaultPrograms() {
+ // Build the path control.exe path safely
+ WCHAR controlEXEPath[MAX_PATH + 1] = {'\0'};
+ if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
+ return false;
+ }
+ LPCWSTR controlEXE = L"control.exe";
+ if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) {
+ return false;
+ }
+ if (!PathAppendW(controlEXEPath, controlEXE)) {
+ return false;
+ }
+
+ const wchar_t* paramFormat =
+ L"control.exe /name Microsoft.DefaultPrograms "
+ L"/page pageDefaultProgram\\pageAdvancedSettings?pszAppName=%s";
+ mozilla::UniquePtr<wchar_t[]> appRegName;
+ GetAppRegName(appRegName);
+ int bufferSize = _scwprintf(paramFormat, appRegName.get());
+ ++bufferSize; // Extra byte for terminating null
+ mozilla::UniquePtr<wchar_t[]> params =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+ _snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
+ appRegName.get());
+
+ STARTUPINFOW si = {sizeof(si), 0};
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWDEFAULT;
+ PROCESS_INFORMATION pi = {0};
+ if (!CreateProcessW(controlEXEPath, params.get(), nullptr, nullptr, FALSE, 0,
+ nullptr, nullptr, &si, &pi)) {
+ return false;
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ return true;
+}
+
+bool LaunchModernSettingsDialogDefaultApps() {
+ if (!mozilla::IsWindowsBuildOrLater(14965) && !IsWindowsLogonConnected() &&
+ SettingsAppBelievesConnected()) {
+ // Use the classic Control Panel to work around a bug of older
+ // builds of Windows 10.
+ return LaunchControlPanelDefaultPrograms();
+ }
+
+ IApplicationActivationManager* pActivator;
+ HRESULT hr = CoCreateInstance(
+ CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC,
+ IID_IApplicationActivationManager, (void**)&pActivator);
+
+ if (SUCCEEDED(hr)) {
+ DWORD pid;
+ hr = pActivator->ActivateApplication(
+ L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
+ if (SUCCEEDED(hr)) {
+ // Do not check error because we could at least open
+ // the "Default apps" setting.
+ pActivator->ActivateApplication(
+ L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults"
+ L"&target=SystemSettings_DefaultApps_Browser",
+ AO_NONE, &pid);
+ }
+ pActivator->Release();
+ return SUCCEEDED(hr);
+ }
+ return true;
+}
diff --git a/browser/components/shell/WindowsDefaultBrowser.h b/browser/components/shell/WindowsDefaultBrowser.h
new file mode 100644
index 0000000000..cfdc4e0f48
--- /dev/null
+++ b/browser/components/shell/WindowsDefaultBrowser.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/**
+ * This file exists so that LaunchModernSettingsDialogDefaultApps can be called
+ * without linking to libxul.
+ */
+
+#ifndef windowsdefaultbrowser_h____
+#define windowsdefaultbrowser_h____
+
+#include "mozilla/UniquePtr.h"
+
+bool GetInstallDirectory(mozilla::UniquePtr<wchar_t[]>& installPath);
+bool GetAppRegName(mozilla::UniquePtr<wchar_t[]>& aAppRegName);
+bool LaunchControlPanelDefaultPrograms();
+bool LaunchModernSettingsDialogDefaultApps();
+
+#endif // windowsdefaultbrowser_h____
diff --git a/browser/components/shell/content/setDesktopBackground.js b/browser/components/shell/content/setDesktopBackground.js
new file mode 100644
index 0000000000..2ecf125206
--- /dev/null
+++ b/browser/components/shell/content/setDesktopBackground.js
@@ -0,0 +1,252 @@
+/* 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/. */
+
+/* import-globals-from ../../../base/content/utilityOverlay.js */
+
+var gSetBackground = {
+ _position: AppConstants.platform == "macosx" ? "STRETCH" : "",
+ _backgroundColor: AppConstants.platform != "macosx" ? 0 : undefined,
+ _screenWidth: 0,
+ _screenHeight: 0,
+ _image: null,
+ _canvas: null,
+ _imageName: null,
+
+ get _shell() {
+ return Cc["@mozilla.org/browser/shell-service;1"].getService(
+ Ci.nsIShellService
+ );
+ },
+
+ load() {
+ this._canvas = document.getElementById("screen");
+ this._screenWidth = screen.width;
+ this._screenHeight = screen.height;
+ // Cap ratio to 4 so the dialog width doesn't get ridiculous. Highest
+ // regular screens seem to be 32:9 (3.56) according to Wikipedia.
+ let screenRatio = Math.min(this._screenWidth / this._screenHeight, 4);
+ this._canvas.width = this._canvas.height * screenRatio;
+ document.getElementById("preview-unavailable").style.width =
+ this._canvas.width + "px";
+
+ if (AppConstants.platform == "macosx") {
+ document
+ .getElementById("SetDesktopBackgroundDialog")
+ .getButton("accept").hidden = true;
+ } else {
+ let multiMonitors = false;
+ if (AppConstants.platform == "linux") {
+ // getMonitors only ever returns the primary monitor on Linux, so just
+ // always show the option
+ multiMonitors = true;
+ } else {
+ const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+ const monitors = gfxInfo.getMonitors();
+ multiMonitors = monitors.length > 1;
+ }
+
+ if (
+ !multiMonitors ||
+ AppConstants.isPlatformAndVersionAtMost("win", 6.1)
+ ) {
+ // Hide span option if < Win8 since that's when it was introduced.
+ document.getElementById("spanPosition").hidden = true;
+ }
+ }
+
+ document.addEventListener("dialogaccept", function() {
+ gSetBackground.setDesktopBackground();
+ });
+ // make sure that the correct dimensions will be used
+ setTimeout(
+ function(self) {
+ self.init(window.arguments[0], window.arguments[1]);
+ },
+ 0,
+ this
+ );
+ },
+
+ init(aImage, aImageName) {
+ this._image = aImage;
+ this._imageName = aImageName;
+
+ // set the size of the coordinate space
+ this._canvas.width = this._canvas.clientWidth;
+ this._canvas.height = this._canvas.clientHeight;
+
+ var ctx = this._canvas.getContext("2d");
+ ctx.scale(
+ this._canvas.clientWidth / this._screenWidth,
+ this._canvas.clientHeight / this._screenHeight
+ );
+
+ if (AppConstants.platform != "macosx") {
+ this._initColor();
+ } else {
+ // Make sure to reset the button state in case the user has already
+ // set an image as their desktop background.
+ var setDesktopBackground = document.getElementById(
+ "setDesktopBackground"
+ );
+ setDesktopBackground.hidden = false;
+ var bundle = document.getElementById("backgroundBundle");
+ setDesktopBackground.label = bundle.getString("DesktopBackgroundSet");
+ setDesktopBackground.disabled = false;
+
+ document.getElementById("showDesktopPreferences").hidden = true;
+ }
+ this.updatePosition();
+ },
+
+ setDesktopBackground() {
+ if (AppConstants.platform != "macosx") {
+ Services.xulStore.persist(
+ document.getElementById("menuPosition"),
+ "value"
+ );
+ this._shell.desktopBackgroundColor = this._hexStringToLong(
+ this._backgroundColor
+ );
+ } else {
+ Services.obs.addObserver(this, "shell:desktop-background-changed");
+
+ var bundle = document.getElementById("backgroundBundle");
+ var setDesktopBackground = document.getElementById(
+ "setDesktopBackground"
+ );
+ setDesktopBackground.disabled = true;
+ setDesktopBackground.label = bundle.getString(
+ "DesktopBackgroundDownloading"
+ );
+ }
+ this._shell.setDesktopBackground(
+ this._image,
+ Ci.nsIShellService["BACKGROUND_" + this._position],
+ this._imageName
+ );
+ },
+
+ updatePosition() {
+ var ctx = this._canvas.getContext("2d");
+ ctx.clearRect(0, 0, this._screenWidth, this._screenHeight);
+ document.getElementById("preview-unavailable").hidden = true;
+
+ if (AppConstants.platform != "macosx") {
+ this._position = document.getElementById("menuPosition").value;
+ }
+
+ switch (this._position) {
+ case "TILE":
+ ctx.save();
+ ctx.fillStyle = ctx.createPattern(this._image, "repeat");
+ ctx.fillRect(0, 0, this._screenWidth, this._screenHeight);
+ ctx.restore();
+ break;
+ case "STRETCH":
+ ctx.drawImage(this._image, 0, 0, this._screenWidth, this._screenHeight);
+ break;
+ case "CENTER": {
+ let x = (this._screenWidth - this._image.naturalWidth) / 2;
+ let y = (this._screenHeight - this._image.naturalHeight) / 2;
+ ctx.drawImage(this._image, x, y);
+ break;
+ }
+ case "FILL": {
+ // Try maxing width first, overflow height.
+ let widthRatio = this._screenWidth / this._image.naturalWidth;
+ let width = this._image.naturalWidth * widthRatio;
+ let height = this._image.naturalHeight * widthRatio;
+ if (height < this._screenHeight) {
+ // Height less than screen, max height and overflow width.
+ let heightRatio = this._screenHeight / this._image.naturalHeight;
+ width = this._image.naturalWidth * heightRatio;
+ height = this._image.naturalHeight * heightRatio;
+ }
+ let x = (this._screenWidth - width) / 2;
+ let y = (this._screenHeight - height) / 2;
+ ctx.drawImage(this._image, x, y, width, height);
+ break;
+ }
+ case "FIT": {
+ // Try maxing width first, top and bottom borders.
+ let widthRatio = this._screenWidth / this._image.naturalWidth;
+ let width = this._image.naturalWidth * widthRatio;
+ let height = this._image.naturalHeight * widthRatio;
+ let x = 0;
+ let y = (this._screenHeight - height) / 2;
+ if (height > this._screenHeight) {
+ // Height overflow, maximise height, side borders.
+ let heightRatio = this._screenHeight / this._image.naturalHeight;
+ width = this._image.naturalWidth * heightRatio;
+ height = this._image.naturalHeight * heightRatio;
+ x = (this._screenWidth - width) / 2;
+ y = 0;
+ }
+ ctx.drawImage(this._image, x, y, width, height);
+ break;
+ }
+ case "SPAN": {
+ document.getElementById("preview-unavailable").hidden = false;
+ ctx.fillStyle = "#222";
+ ctx.fillRect(0, 0, this._screenWidth, this._screenHeight);
+ ctx.stroke();
+ }
+ }
+ },
+};
+
+if (AppConstants.platform != "macosx") {
+ gSetBackground._initColor = function() {
+ var color = this._shell.desktopBackgroundColor;
+
+ const rMask = 4294901760;
+ const gMask = 65280;
+ const bMask = 255;
+ var r = (color & rMask) >> 16;
+ var g = (color & gMask) >> 8;
+ var b = color & bMask;
+ this.updateColor(this._rgbToHex(r, g, b));
+
+ var colorpicker = document.getElementById("desktopColor");
+ colorpicker.value = this._backgroundColor;
+ };
+
+ gSetBackground.updateColor = function(aColor) {
+ this._backgroundColor = aColor;
+ this._canvas.style.backgroundColor = aColor;
+ };
+
+ // Converts a color string in the format "#RRGGBB" to an integer.
+ gSetBackground._hexStringToLong = function(aString) {
+ return (
+ (parseInt(aString.substring(1, 3), 16) << 16) |
+ (parseInt(aString.substring(3, 5), 16) << 8) |
+ parseInt(aString.substring(5, 7), 16)
+ );
+ };
+
+ gSetBackground._rgbToHex = function(aR, aG, aB) {
+ return (
+ "#" +
+ [aR, aG, aB]
+ .map(aInt => aInt.toString(16).replace(/^(.)$/, "0$1"))
+ .join("")
+ .toUpperCase()
+ );
+ };
+} else {
+ gSetBackground.observe = function(aSubject, aTopic, aData) {
+ if (aTopic == "shell:desktop-background-changed") {
+ document.getElementById("setDesktopBackground").hidden = true;
+ document.getElementById("showDesktopPreferences").hidden = false;
+
+ Services.obs.removeObserver(this, "shell:desktop-background-changed");
+ }
+ };
+
+ gSetBackground.showDesktopPrefs = function() {
+ this._shell.QueryInterface(Ci.nsIMacShellService).showDesktopPreferences();
+ };
+}
diff --git a/browser/components/shell/content/setDesktopBackground.xhtml b/browser/components/shell/content/setDesktopBackground.xhtml
new file mode 100644
index 0000000000..53cd586a3c
--- /dev/null
+++ b/browser/components/shell/content/setDesktopBackground.xhtml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
+
+# 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/.
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/setDesktopBackground.css" type="text/css"?>
+
+
+<!DOCTYPE dialog [
+#ifdef XP_MACOSX
+#include ../../../base/content/browser-doctype.inc
+#endif
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ windowtype="Shell:SetDesktopBackground"
+ onload="gSetBackground.load();"
+ data-l10n-id="set-desktop-background-window"
+ style="width: 30em;">
+
+<linkset>
+ <html:link rel="localization" href="browser/setDesktopBackground.ftl"/>
+</linkset>
+
+<dialog id="SetDesktopBackgroundDialog"
+#ifndef XP_MACOSX
+ buttons="accept,cancel"
+#else
+ buttons="accept"
+#endif
+ buttonidaccept="set-desktop-background-accept">
+
+#ifdef XP_MACOSX
+#include ../../../base/content/macWindow.inc.xhtml
+#else
+ <script src="chrome://browser/content/utilityOverlay.js"/>
+#endif
+
+ <stringbundle id="backgroundBundle"
+ src="chrome://browser/locale/shellservice.properties"/>
+ <script src="chrome://browser/content/setDesktopBackground.js"/>
+ <script src="chrome://global/content/contentAreaUtils.js"/>
+
+#ifndef XP_MACOSX
+ <hbox align="center">
+ <label data-l10n-id="set-background-position"/>
+ <menulist id="menuPosition"
+ oncommand="gSetBackground.updatePosition();">
+ <menupopup>
+ <menuitem data-l10n-id="set-background-center" value="CENTER"/>
+ <menuitem data-l10n-id="set-background-tile" value="TILE"/>
+ <menuitem data-l10n-id="set-background-stretch" value="STRETCH"/>
+ <menuitem data-l10n-id="set-background-fill" value="FILL"/>
+ <menuitem data-l10n-id="set-background-fit" value="FIT"/>
+ <menuitem data-l10n-id="set-background-span" value="SPAN" id="spanPosition"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <label data-l10n-id="set-background-color"/>
+ <html:input id="desktopColor"
+ type="color"
+ onchange="gSetBackground.updateColor(this.value);"/>
+ </hbox>
+#endif
+
+ <vbox align="center">
+ <!-- default to 16:9, will be adjusted to match user's actual screen -->
+ <stack>
+ <html:canvas id="screen" width="202" height="114" role="presentation"/>
+ <vbox pack="center">
+ <html:p id="preview-unavailable" hidden="" data-l10n-id="set-background-preview-unavailable"></html:p>
+ </vbox>
+ </stack>
+ <image id="monitor-base"/>
+ </vbox>
+
+#ifdef XP_MACOSX
+ <separator/>
+
+ <hbox pack="end">
+ <button id="setDesktopBackground"
+ data-l10n-id="set-desktop-background-accept"
+ oncommand="gSetBackground.setDesktopBackground();"/>
+ <button id="showDesktopPreferences"
+ data-l10n-id="open-desktop-prefs"
+ oncommand="gSetBackground.showDesktopPrefs();"
+ hidden="true"/>
+ </hbox>
+#endif
+
+</dialog>
+</window>
diff --git a/browser/components/shell/jar.mn b/browser/components/shell/jar.mn
new file mode 100644
index 0000000000..0816cddd68
--- /dev/null
+++ b/browser/components/shell/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+* content/browser/setDesktopBackground.xhtml (content/setDesktopBackground.xhtml)
+ content/browser/setDesktopBackground.js (content/setDesktopBackground.js)
diff --git a/browser/components/shell/moz.build b/browser/components/shell/moz.build
new file mode 100644
index 0000000000..eec5f0a879
--- /dev/null
+++ b/browser/components/shell/moz.build
@@ -0,0 +1,79 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=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/.
+
+# For BinaryPath::GetLong for Windows
+LOCAL_INCLUDES += ["/xpcom/build"]
+
+BROWSER_CHROME_MANIFESTS += ["test/browser.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["test/chrome.ini"]
+XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"]
+
+JAR_MANIFESTS += ["jar.mn"]
+
+XPIDL_SOURCES += [
+ "nsIShellService.idl",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ XPIDL_SOURCES += [
+ "nsIMacShellService.idl",
+ ]
+
+ SOURCES += [
+ "nsMacShellService.cpp",
+ ]
+
+ LOCAL_INCLUDES += [
+ # For CocoaFileUtils
+ "/xpcom/io"
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ XPIDL_SOURCES += [
+ "nsIGNOMEShellService.idl",
+ ]
+
+ SOURCES += [
+ "nsGNOMEShellService.cpp",
+ ]
+ if CONFIG["MOZ_ENABLE_DBUS"]:
+ SOURCES += [
+ "nsGNOMEShellDBusHelper.cpp",
+ "nsGNOMEShellSearchProvider.cpp",
+ ]
+ include("/ipc/chromium/chromium-config.mozbuild")
+
+elif CONFIG["OS_ARCH"] == "WINNT":
+ XPIDL_SOURCES += [
+ "nsIWindowsShellService.idl",
+ ]
+ SOURCES += [
+ "nsWindowsShellService.cpp",
+ "WindowsDefaultBrowser.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "../../../other-licenses/nsis/Contrib/CityHash/cityhash",
+ ]
+
+XPIDL_MODULE = "shellservice"
+
+if SOURCES:
+ FINAL_LIBRARY = "browsercomps"
+
+EXTRA_JS_MODULES += [
+ "HeadlessShell.jsm",
+ "ScreenshotChild.jsm",
+ "ShellService.jsm",
+]
+
+for var in ("MOZ_APP_NAME", "MOZ_APP_VERSION"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+if CONFIG["MOZ_ENABLE_DBUS"]:
+ CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Shell Integration")
diff --git a/browser/components/shell/nsGNOMEShellDBusHelper.cpp b/browser/components/shell/nsGNOMEShellDBusHelper.cpp
new file mode 100644
index 0000000000..35fbba6a21
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellDBusHelper.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#include "nsGNOMEShellSearchProvider.h"
+
+#include "nsPrintfCString.h"
+#include "RemoteUtils.h"
+#include "nsServiceManagerUtils.h"
+
+static bool GetGnomeSearchTitle(const char* aSearchedTerm,
+ nsAutoCString& aGnomeSearchTitle) {
+ static nsCOMPtr<nsIStringBundle> bundle;
+ if (!bundle) {
+ nsCOMPtr<nsIStringBundleService> sbs =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (NS_WARN_IF(!sbs)) {
+ return false;
+ }
+
+ sbs->CreateBundle("chrome://browser/locale/browser.properties",
+ getter_AddRefs(bundle));
+ if (NS_WARN_IF(!bundle)) {
+ return false;
+ }
+ }
+
+ AutoTArray<nsString, 1> formatStrings;
+ CopyUTF8toUTF16(nsCString(aSearchedTerm), *formatStrings.AppendElement());
+
+ nsAutoString gnomeSearchTitle;
+ bundle->FormatStringFromName("gnomeSearchProviderSearch", formatStrings,
+ gnomeSearchTitle);
+ AppendUTF16toUTF8(gnomeSearchTitle, aGnomeSearchTitle);
+ return true;
+}
+
+static const char* introspect_template =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN\"\n"
+ "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<node>\n"
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
+ " <method name=\"Introspect\">\n"
+ " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name=\"org.gnome.Shell.SearchProvider2\">\n"
+ " <method name=\"GetInitialResultSet\">\n"
+ " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n"
+ " <arg type=\"as\" name=\"results\" direction=\"out\" />\n"
+ " </method>\n"
+ " <method name=\"GetSubsearchResultSet\">\n"
+ " <arg type=\"as\" name=\"previous_results\" direction=\"in\" />\n"
+ " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n"
+ " <arg type=\"as\" name=\"results\" direction=\"out\" />\n"
+ " </method>\n"
+ " <method name=\"GetResultMetas\">\n"
+ " <arg type=\"as\" name=\"identifiers\" direction=\"in\" />\n"
+ " <arg type=\"aa{sv}\" name=\"metas\" direction=\"out\" />\n"
+ " </method>\n"
+ " <method name=\"ActivateResult\">\n"
+ " <arg type=\"s\" name=\"identifier\" direction=\"in\" />\n"
+ " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n"
+ " <arg type=\"u\" name=\"timestamp\" direction=\"in\" />\n"
+ " </method>\n"
+ " <method name=\"LaunchSearch\">\n"
+ " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n"
+ " <arg type=\"u\" name=\"timestamp\" direction=\"in\" />\n"
+ " </method>\n"
+ "</interface>\n"
+ "</node>\n";
+
+DBusHandlerResult DBusIntrospect(DBusConnection* aConnection,
+ DBusMessage* aMsg) {
+ DBusMessage* reply;
+
+ reply = dbus_message_new_method_return(aMsg);
+ if (!reply) {
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspect_template,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(aConnection, reply, nullptr);
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+int DBusGetIndexFromIDKey(const char* aIDKey) {
+ // ID is NN:URL where NN is index to our current history
+ // result container.
+ char tmp[] = {aIDKey[0], aIDKey[1], '\0'};
+ return atoi(tmp);
+}
+
+DBusHandlerResult DBusHandleInitialResultSet(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg) {
+ DBusMessage* reply;
+ char** stringArray = nullptr;
+ int elements;
+
+ if (!dbus_message_get_args(aMsg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+ &stringArray, &elements, DBUS_TYPE_INVALID) ||
+ elements == 0) {
+ reply = dbus_message_new_error(aMsg, DBUS_BUS_NAME, "Wrong argument");
+ dbus_connection_send(aSearchResult->GetDBusConnection(), reply, nullptr);
+ dbus_message_unref(reply);
+ } else {
+ aSearchResult->SetReply(dbus_message_new_method_return(aMsg));
+ aSearchResult->SetSearchTerm(stringArray[0]);
+ GetGNOMEShellHistoryService()->QueryHistory(aSearchResult);
+ // DBus reply will be send asynchronously by
+ // nsGNOMEShellHistorySearchResult::SendDBusSearchResultReply()
+ // when GetGNOMEShellHistoryService() has the results.
+ }
+
+ if (stringArray) {
+ dbus_free_string_array(stringArray);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusHandlerResult DBusHandleSubsearchResultSet(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg) {
+ DBusMessage* reply;
+
+ char **unusedArray = nullptr, **stringArray = nullptr;
+ int unusedNum, elements;
+
+ if (!dbus_message_get_args(aMsg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+ &unusedArray, &unusedNum, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING, &stringArray, &elements,
+ DBUS_TYPE_INVALID) ||
+ elements == 0) {
+ reply = dbus_message_new_error(aMsg, DBUS_BUS_NAME, "Wrong argument");
+ dbus_connection_send(aSearchResult->GetDBusConnection(), reply, nullptr);
+ dbus_message_unref(reply);
+ } else {
+ aSearchResult->SetReply(dbus_message_new_method_return(aMsg));
+ aSearchResult->SetSearchTerm(stringArray[0]);
+ GetGNOMEShellHistoryService()->QueryHistory(aSearchResult);
+ // DBus reply will be send asynchronously by
+ // nsGNOMEShellHistorySearchResult::SendDBusSearchResultReply()
+ // when GetGNOMEShellHistoryService() has the results.
+ }
+
+ if (unusedArray) {
+ dbus_free_string_array(unusedArray);
+ }
+ if (stringArray) {
+ dbus_free_string_array(stringArray);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void appendStringDictionary(DBusMessageIter* aIter, const char* aKey,
+ const char* aValue) {
+ DBusMessageIter iterDict, iterVar;
+ dbus_message_iter_open_container(aIter, DBUS_TYPE_DICT_ENTRY, nullptr,
+ &iterDict);
+ dbus_message_iter_append_basic(&iterDict, DBUS_TYPE_STRING, &aKey);
+ dbus_message_iter_open_container(&iterDict, DBUS_TYPE_VARIANT, "s", &iterVar);
+ dbus_message_iter_append_basic(&iterVar, DBUS_TYPE_STRING, &aValue);
+ dbus_message_iter_close_container(&iterDict, &iterVar);
+ dbus_message_iter_close_container(aIter, &iterDict);
+}
+
+/*
+ "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width,
+ height, rowstride, has-alpha,
+ bits-per-sample, channels,
+ image data
+*/
+static void DBusAppendIcon(GnomeHistoryIcon* aIcon, DBusMessageIter* aIter) {
+ DBusMessageIter iterDict, iterVar, iterStruct;
+ dbus_message_iter_open_container(aIter, DBUS_TYPE_DICT_ENTRY, nullptr,
+ &iterDict);
+ const char* key = "icon-data";
+ dbus_message_iter_append_basic(&iterDict, DBUS_TYPE_STRING, &key);
+ dbus_message_iter_open_container(&iterDict, DBUS_TYPE_VARIANT, "(iiibiiay)",
+ &iterVar);
+ dbus_message_iter_open_container(&iterVar, DBUS_TYPE_STRUCT, nullptr,
+ &iterStruct);
+
+ int width = aIcon->GetWidth();
+ int height = aIcon->GetHeight();
+ dbus_message_iter_append_basic(&iterStruct, DBUS_TYPE_INT32, &width);
+ dbus_message_iter_append_basic(&iterStruct, DBUS_TYPE_INT32, &height);
+ int rowstride = width * 4;
+ dbus_message_iter_append_basic(&iterStruct, DBUS_TYPE_INT32, &rowstride);
+ int hasAlpha = true;
+ dbus_message_iter_append_basic(&iterStruct, DBUS_TYPE_BOOLEAN, &hasAlpha);
+ int bitsPerSample = 8;
+ dbus_message_iter_append_basic(&iterStruct, DBUS_TYPE_INT32, &bitsPerSample);
+ int channels = 4;
+ dbus_message_iter_append_basic(&iterStruct, DBUS_TYPE_INT32, &channels);
+
+ DBusMessageIter iterArray;
+ dbus_message_iter_open_container(&iterStruct, DBUS_TYPE_ARRAY, "y",
+ &iterArray);
+ unsigned char* array = aIcon->GetData();
+ dbus_message_iter_append_fixed_array(&iterArray, DBUS_TYPE_BYTE, &array,
+ width * height * 4);
+ dbus_message_iter_close_container(&iterStruct, &iterArray);
+
+ dbus_message_iter_close_container(&iterVar, &iterStruct);
+ dbus_message_iter_close_container(&iterDict, &iterVar);
+ dbus_message_iter_close_container(aIter, &iterDict);
+}
+
+/* Appends history search results to the DBUS reply.
+
+ We can return those fields at GetResultMetas:
+
+ "id": the result ID
+ "name": the display name for the result
+ "icon": a serialized GIcon (see g_icon_serialize()), or alternatively,
+ "gicon": a textual representation of a GIcon (see g_icon_to_string()),
+ or alternativly,
+ "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width,
+ height, rowstride, has-alpha, bits-per-sample, and image data
+ "description": an optional short description (1-2 lines)
+*/
+static void DBusAppendResultID(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult,
+ DBusMessageIter* aIter, const char* aID) {
+ nsCOMPtr<nsINavHistoryContainerResultNode> container =
+ aSearchResult->GetSearchResultContainer();
+
+ int index = DBusGetIndexFromIDKey(aID);
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ container->GetChild(index, getter_AddRefs(child));
+ nsAutoCString title;
+ if (NS_FAILED(child->GetTitle(title))) {
+ return;
+ }
+
+ if (title.IsEmpty()) {
+ if (NS_FAILED(child->GetUri(title)) || title.IsEmpty()) {
+ return;
+ }
+ }
+
+ const char* titleStr = title.get();
+ appendStringDictionary(aIter, "id", aID);
+ appendStringDictionary(aIter, "name", titleStr);
+
+ GnomeHistoryIcon* icon = aSearchResult->GetHistoryIcon(index);
+ if (icon) {
+ DBusAppendIcon(icon, aIter);
+ } else {
+ appendStringDictionary(aIter, "gicon", "text-html");
+ }
+}
+
+/* Search the web for: "searchTerm" to the DBUS reply.
+ */
+static void DBusAppendSearchID(DBusMessageIter* aIter, const char* aID) {
+ /* aID contains:
+
+ KEYWORD_SEARCH_STRING:ssssss
+
+ KEYWORD_SEARCH_STRING is a 'special:search' keyword
+ ssssss is a searched term, must be at least one character long
+ */
+
+ // aID contains only 'KEYWORD_SEARCH_STRING:' so we're missing searched
+ // string.
+ if (strlen(aID) <= KEYWORD_SEARCH_STRING_LEN + 1) {
+ return;
+ }
+
+ appendStringDictionary(aIter, "id", KEYWORD_SEARCH_STRING);
+
+ // Extract ssssss part from aID
+ auto searchTerm = nsAutoCStringN<32>(aID + KEYWORD_SEARCH_STRING_LEN + 1);
+ nsAutoCString gnomeSearchTitle;
+ if (GetGnomeSearchTitle(searchTerm.get(), gnomeSearchTitle)) {
+ appendStringDictionary(aIter, "name", gnomeSearchTitle.get());
+ // TODO: When running on flatpak/snap we may need to use
+ // icon like org.mozilla.Firefox or so.
+ appendStringDictionary(aIter, "gicon", "firefox");
+ }
+}
+
+DBusHandlerResult DBusHandleResultMetas(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg) {
+ DBusMessage* reply;
+ char** stringArray;
+ int elements;
+
+ if (!dbus_message_get_args(aMsg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+ &stringArray, &elements, DBUS_TYPE_INVALID) ||
+ elements == 0) {
+ reply = dbus_message_new_error(aMsg, DBUS_BUS_NAME, "Wrong argument");
+ } else {
+ reply = dbus_message_new_method_return(aMsg);
+
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(reply, &iter);
+ DBusMessageIter iterArray;
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}",
+ &iterArray);
+
+ DBusMessageIter iterArray2;
+ for (int i = 0; i < elements; i++) {
+ dbus_message_iter_open_container(&iterArray, DBUS_TYPE_ARRAY, "{sv}",
+ &iterArray2);
+ if (strncmp(stringArray[i], KEYWORD_SEARCH_STRING,
+ KEYWORD_SEARCH_STRING_LEN) == 0) {
+ DBusAppendSearchID(&iterArray2, stringArray[i]);
+ } else {
+ DBusAppendResultID(aSearchResult, &iterArray2, stringArray[i]);
+ }
+ dbus_message_iter_close_container(&iterArray, &iterArray2);
+ }
+
+ dbus_message_iter_close_container(&iter, &iterArray);
+ dbus_free_string_array(stringArray);
+ }
+
+ dbus_connection_send(aSearchResult->GetDBusConnection(), reply, nullptr);
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+} // namespace mozilla
+
+static void ActivateResultID(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult,
+ const char* aResultID, uint32_t aTimeStamp) {
+ char* commandLine = nullptr;
+ int tmp;
+
+ if (strncmp(aResultID, KEYWORD_SEARCH_STRING, KEYWORD_SEARCH_STRING_LEN) ==
+ 0) {
+ const char* urlList[3] = {"unused", "--search",
+ aSearchResult->GetSearchTerm().get()};
+ commandLine = ConstructCommandLine(3, (char**)urlList, nullptr, &tmp);
+ } else {
+ int keyIndex = atoi(aResultID);
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ aSearchResult->GetSearchResultContainer()->GetChild(keyIndex,
+ getter_AddRefs(child));
+ if (!child) {
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult rv = child->GetUri(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ const char* urlList[2] = {"unused", uri.get()};
+ commandLine = ConstructCommandLine(2, (char**)urlList, nullptr, &tmp);
+ }
+
+ if (commandLine) {
+ aSearchResult->HandleCommandLine(commandLine, aTimeStamp);
+ free(commandLine);
+ }
+}
+
+static void DBusLaunchWithAllResults(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult,
+ uint32_t aTimeStamp) {
+ uint32_t childCount = 0;
+ nsresult rv =
+ aSearchResult->GetSearchResultContainer()->GetChildCount(&childCount);
+ if (NS_FAILED(rv) || childCount == 0) {
+ return;
+ }
+
+ if (childCount > MAX_SEARCH_RESULTS_NUM) {
+ childCount = MAX_SEARCH_RESULTS_NUM;
+ }
+
+ // Allocate space for all found results, "unused", "--search" and
+ // potential search request.
+ char** urlList = (char**)moz_xmalloc(sizeof(char*) * (childCount + 3));
+ int urlListElements = 0;
+
+ urlList[urlListElements++] = strdup("unused");
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ aSearchResult->GetSearchResultContainer()->GetChild(i,
+ getter_AddRefs(child));
+
+ if (!IsHistoryResultNodeURI(child)) {
+ continue;
+ }
+
+ nsAutoCString uri;
+ nsresult rv = child->GetUri(uri);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ urlList[urlListElements++] = strdup(uri.get());
+ }
+
+ // When there isn't any uri to open pass search at least.
+ if (!childCount) {
+ urlList[urlListElements++] = strdup("--search");
+ urlList[urlListElements++] = strdup(aSearchResult->GetSearchTerm().get());
+ }
+
+ int tmp;
+ char* commandLine =
+ ConstructCommandLine(urlListElements, urlList, nullptr, &tmp);
+ if (commandLine) {
+ aSearchResult->HandleCommandLine(commandLine, aTimeStamp);
+ free(commandLine);
+ }
+
+ for (int i = 0; i < urlListElements; i++) {
+ free(urlList[i]);
+ }
+ free(urlList);
+}
+
+DBusHandlerResult DBusActivateResult(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg) {
+ DBusMessage* reply;
+ char* resultID;
+ char** stringArray;
+ int elements;
+ uint32_t timestamp;
+
+ if (!dbus_message_get_args(aMsg, nullptr, DBUS_TYPE_STRING, &resultID,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &stringArray,
+ &elements, DBUS_TYPE_UINT32, &timestamp,
+ DBUS_TYPE_INVALID) ||
+ resultID == nullptr) {
+ reply = dbus_message_new_error(aMsg, DBUS_BUS_NAME, "Wrong argument");
+ } else {
+ reply = dbus_message_new_method_return(aMsg);
+ ActivateResultID(aSearchResult, resultID, timestamp);
+ dbus_free_string_array(stringArray);
+ }
+
+ dbus_connection_send(aSearchResult->GetDBusConnection(), reply, nullptr);
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusHandlerResult DBusLaunchSearch(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg) {
+ DBusMessage* reply;
+ char** stringArray;
+ int elements;
+ uint32_t timestamp;
+
+ if (!dbus_message_get_args(aMsg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+ &stringArray, &elements, DBUS_TYPE_UINT32,
+ &timestamp, DBUS_TYPE_INVALID) ||
+ elements == 0) {
+ reply = dbus_message_new_error(aMsg, DBUS_BUS_NAME, "Wrong argument");
+ } else {
+ reply = dbus_message_new_method_return(aMsg);
+ DBusLaunchWithAllResults(aSearchResult, timestamp);
+ dbus_free_string_array(stringArray);
+ }
+
+ dbus_connection_send(aSearchResult->GetDBusConnection(), reply, nullptr);
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+bool IsHistoryResultNodeURI(nsINavHistoryResultNode* aHistoryNode) {
+ uint32_t type;
+ nsresult rv = aHistoryNode->GetType(&type);
+ if (NS_FAILED(rv) || type != nsINavHistoryResultNode::RESULT_TYPE_URI)
+ return false;
+
+ nsAutoCString title;
+ rv = aHistoryNode->GetTitle(title);
+ if (NS_SUCCEEDED(rv) && !title.IsEmpty()) {
+ return true;
+ }
+
+ rv = aHistoryNode->GetUri(title);
+ return NS_SUCCEEDED(rv) && !title.IsEmpty();
+}
diff --git a/browser/components/shell/nsGNOMEShellDBusHelper.h b/browser/components/shell/nsGNOMEShellDBusHelper.h
new file mode 100644
index 0000000000..78522da20d
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellDBusHelper.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef __nsGNOMEShellDBusHelper_h__
+#define __nsGNOMEShellDBusHelper_h__
+
+#include "mozilla/DBusHelpers.h"
+#include "nsIStringBundle.h"
+#include "nsINavHistoryService.h"
+
+#define MAX_SEARCH_RESULTS_NUM 9
+#define KEYWORD_SEARCH_STRING "special:search"
+#define KEYWORD_SEARCH_STRING_LEN 14
+
+#define DBUS_BUS_NAME "org.mozilla.Firefox.SearchProvider"
+#define DBUS_OBJECT_PATH "/org/mozilla/Firefox/SearchProvider"
+
+class nsGNOMEShellHistorySearchResult;
+
+DBusHandlerResult DBusIntrospect(DBusConnection* aConnection,
+ DBusMessage* aMsg);
+DBusHandlerResult DBusHandleInitialResultSet(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg);
+DBusHandlerResult DBusHandleSubsearchResultSet(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg);
+DBusHandlerResult DBusHandleResultMetas(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg);
+DBusHandlerResult DBusActivateResult(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg);
+DBusHandlerResult DBusLaunchSearch(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, DBusMessage* aMsg);
+bool IsHistoryResultNodeURI(nsINavHistoryResultNode* aHistoryNode);
+
+#endif // __nsGNOMEShellDBusHelper_h__
diff --git a/browser/components/shell/nsGNOMEShellSearchProvider.cpp b/browser/components/shell/nsGNOMEShellSearchProvider.cpp
new file mode 100644
index 0000000000..40e6685134
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellSearchProvider.cpp
@@ -0,0 +1,443 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#include "nsGNOMEShellSearchProvider.h"
+
+#include "nsIWidget.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIFaviconService.h"
+#include "RemoteUtils.h"
+#include "base/message_loop.h" // for MessageLoop
+#include "base/task.h" // for NewRunnableMethod, etc
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "nsIIOService.h"
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+class AsyncFaviconDataReady final : public nsIFaviconDataCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFAVICONDATACALLBACK
+
+ AsyncFaviconDataReady(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult,
+ int aIconIndex, int aTimeStamp)
+ : mSearchResult(aSearchResult),
+ mIconIndex(aIconIndex),
+ mTimeStamp(aTimeStamp){};
+
+ private:
+ ~AsyncFaviconDataReady() {}
+
+ RefPtr<nsGNOMEShellHistorySearchResult> mSearchResult;
+ int mIconIndex;
+ int mTimeStamp;
+};
+
+NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback)
+
+// Inspired by SurfaceToPackedBGRA
+static UniquePtr<uint8_t[]> SurfaceToPackedRGBA(DataSourceSurface* aSurface) {
+ IntSize size = aSurface->GetSize();
+ CheckedInt<size_t> bufferSize =
+ CheckedInt<size_t>(size.width * 4) * CheckedInt<size_t>(size.height);
+ if (!bufferSize.isValid()) {
+ return nullptr;
+ }
+ UniquePtr<uint8_t[]> imageBuffer(new (std::nothrow)
+ uint8_t[bufferSize.value()]);
+ if (!imageBuffer) {
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return nullptr;
+ }
+
+ // Convert BGRA to RGBA
+ uint32_t* aSrc = (uint32_t*)map.mData;
+ uint32_t* aDst = (uint32_t*)imageBuffer.get();
+ for (int i = 0; i < size.width * size.height; i++, aDst++, aSrc++) {
+ *aDst = *aSrc & 0xff00ff00;
+ *aDst |= (*aSrc & 0xff) << 16;
+ *aDst |= (*aSrc & 0xff0000) >> 16;
+ }
+
+ aSurface->Unmap();
+
+ return imageBuffer;
+}
+
+NS_IMETHODIMP
+AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
+ const uint8_t* aData,
+ const nsACString& aMimeType,
+ uint16_t aWidth) {
+ // This is a callback from some previous search so we don't want it
+ if (mTimeStamp != mSearchResult->GetTimeStamp() || !aData || !aDataLen) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Decode the image from the format it was returned to us in (probably PNG)
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
+ nsresult rv = imgtool->DecodeImageFromBuffer(
+ reinterpret_cast<const char*>(aData), aDataLen, aMimeType,
+ getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SourceSurface> surface = container->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+
+ if (!surface || surface->GetFormat() != SurfaceFormat::B8G8R8A8) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Allocate a new buffer that we own.
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ UniquePtr<uint8_t[]> data = SurfaceToPackedRGBA(dataSurface);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mSearchResult->SetHistoryIcon(mTimeStamp, std::move(data),
+ surface->GetSize().width,
+ surface->GetSize().height, mIconIndex);
+ return NS_OK;
+}
+
+DBusHandlerResult nsGNOMEShellSearchProvider::HandleSearchResultSet(
+ DBusMessage* aMsg, bool aInitialSearch) {
+ // Discard any existing search results.
+ mSearchResult = nullptr;
+
+ RefPtr<nsGNOMEShellHistorySearchResult> newSearch =
+ new nsGNOMEShellHistorySearchResult(this, mConnection,
+ mSearchResultTimeStamp);
+ mSearchResultTimeStamp++;
+ newSearch->SetTimeStamp(mSearchResultTimeStamp);
+
+ // Send the search request over DBus. We'll get reply over DBus it will be
+ // set to mSearchResult by nsGNOMEShellSearchProvider::SetSearchResult().
+ return aInitialSearch
+ ? DBusHandleInitialResultSet(newSearch.forget(), aMsg)
+ : DBusHandleSubsearchResultSet(newSearch.forget(), aMsg);
+}
+
+DBusHandlerResult nsGNOMEShellSearchProvider::HandleResultMetas(
+ DBusMessage* aMsg) {
+ if (!mSearchResult) {
+ NS_WARNING("Missing nsGNOMEShellHistorySearchResult.");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ return DBusHandleResultMetas(mSearchResult, aMsg);
+}
+
+DBusHandlerResult nsGNOMEShellSearchProvider::ActivateResult(
+ DBusMessage* aMsg) {
+ if (!mSearchResult) {
+ NS_WARNING("Missing nsGNOMEShellHistorySearchResult.");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ return DBusActivateResult(mSearchResult, aMsg);
+}
+
+DBusHandlerResult nsGNOMEShellSearchProvider::LaunchSearch(DBusMessage* aMsg) {
+ if (!mSearchResult) {
+ NS_WARNING("Missing nsGNOMEShellHistorySearchResult.");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ return DBusLaunchSearch(mSearchResult, aMsg);
+}
+
+DBusHandlerResult nsGNOMEShellSearchProvider::HandleDBusMessage(
+ DBusConnection* aConnection, DBusMessage* aMsg) {
+ NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection.");
+
+ const char* method = dbus_message_get_member(aMsg);
+ const char* iface = dbus_message_get_interface(aMsg);
+
+ if ((strcmp("Introspect", method) == 0) &&
+ (strcmp("org.freedesktop.DBus.Introspectable", iface) == 0)) {
+ return DBusIntrospect(mConnection, aMsg);
+ }
+
+ if (strcmp("org.gnome.Shell.SearchProvider2", iface) == 0) {
+ if (strcmp("GetInitialResultSet", method) == 0) {
+ return HandleSearchResultSet(aMsg, /* aInitialSearch */ true);
+ }
+ if (strcmp("GetSubsearchResultSet", method) == 0) {
+ return HandleSearchResultSet(aMsg, /* aInitialSearch */ false);
+ }
+ if (strcmp("GetResultMetas", method) == 0) {
+ return HandleResultMetas(aMsg);
+ }
+ if (strcmp("ActivateResult", method) == 0) {
+ return ActivateResult(aMsg);
+ }
+ if (strcmp("LaunchSearch", method) == 0) {
+ return LaunchSearch(aMsg);
+ }
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+void nsGNOMEShellSearchProvider::UnregisterDBusInterface(
+ DBusConnection* aConnection) {
+ NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection.");
+ // Not implemented
+}
+
+static DBusHandlerResult message_handler(DBusConnection* conn,
+ DBusMessage* aMsg, void* user_data) {
+ auto interface = static_cast<nsGNOMEShellSearchProvider*>(user_data);
+ return interface->HandleDBusMessage(conn, aMsg);
+}
+
+static void unregister(DBusConnection* conn, void* user_data) {
+ auto interface = static_cast<nsGNOMEShellSearchProvider*>(user_data);
+ interface->UnregisterDBusInterface(conn);
+}
+
+static DBusObjectPathVTable remoteHandlersTable = {
+ .unregister_function = unregister,
+ .message_function = message_handler,
+};
+
+nsresult nsGNOMEShellSearchProvider::Startup() {
+ if (mConnection && dbus_connection_get_is_connected(mConnection)) {
+ // We're already connected so we don't need to reconnect
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ mConnection =
+ already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SESSION, nullptr));
+ if (!mConnection) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+
+ DBusError err;
+ dbus_error_init(&err);
+ dbus_bus_request_name(mConnection, DBUS_BUS_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ &err);
+ // The interface is already owned - there is another application/profile
+ // instance already running.
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ mConnection = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!dbus_connection_register_object_path(mConnection, DBUS_OBJECT_PATH,
+ &remoteHandlersTable, this)) {
+ mConnection = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mSearchResultTimeStamp = 0;
+ return NS_OK;
+}
+
+void nsGNOMEShellSearchProvider::Shutdown() {
+ if (!mConnection) {
+ return;
+ }
+
+ dbus_connection_unregister_object_path(mConnection, DBUS_OBJECT_PATH);
+
+ // dbus_connection_unref() will be called by RefPtr here.
+ mConnection = nullptr;
+}
+
+bool nsGNOMEShellSearchProvider::SetSearchResult(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult) {
+ MOZ_ASSERT(!mSearchResult);
+
+ if (mSearchResultTimeStamp != aSearchResult->GetTimeStamp()) {
+ NS_WARNING("Time stamp mismatch.");
+ return false;
+ }
+ mSearchResult = aSearchResult;
+ return true;
+}
+
+static void DispatchSearchResults(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult,
+ nsCOMPtr<nsINavHistoryContainerResultNode> aHistResultContainer) {
+ aSearchResult->ReceiveSearchResultContainer(aHistResultContainer);
+}
+
+nsresult nsGNOMEShellHistoryService::QueryHistory(
+ RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult) {
+ if (!mHistoryService) {
+ mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+ if (!mHistoryService) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsINavHistoryQuery> histQuery;
+ rv = mHistoryService->GetNewQuery(getter_AddRefs(histQuery));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histQuery->SetSearchTerms(
+ NS_ConvertUTF8toUTF16(aSearchResult->GetSearchTerm()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
+ rv = mHistoryService->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histQueryOpts->SetSortingMode(
+ nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histQueryOpts->SetMaxResults(MAX_SEARCH_RESULTS_NUM);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryResult> histResult;
+ rv = mHistoryService->ExecuteQuery(histQuery, histQueryOpts,
+ getter_AddRefs(histResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryContainerResultNode> resultContainer;
+
+ rv = histResult->GetRoot(getter_AddRefs(resultContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = resultContainer->SetContainerOpen(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Simulate async searching by delayed reply. This search API will
+ // likely become async in the future and we want to be sure to not rely on
+ // its current synchronous behavior.
+ MOZ_ASSERT(MessageLoop::current());
+ MessageLoop::current()->PostTask(
+ NewRunnableFunction("Gnome shell search results", &DispatchSearchResults,
+ aSearchResult, resultContainer));
+
+ return NS_OK;
+}
+
+static void DBusGetIDKeyForURI(int aIndex, nsAutoCString& aUri,
+ nsAutoCString& aIDKey) {
+ // Compose ID as NN:URL where NN is index to our current history
+ // result container.
+ aIDKey = nsPrintfCString("%.2d:%s", aIndex, aUri.get());
+}
+
+void nsGNOMEShellHistorySearchResult::HandleSearchResultReply() {
+ MOZ_ASSERT(mReply);
+
+ uint32_t childCount = 0;
+ nsresult rv = mHistResultContainer->GetChildCount(&childCount);
+
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(mReply, &iter);
+ DBusMessageIter iterArray;
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &iterArray);
+
+ if (NS_SUCCEEDED(rv) && childCount > 0) {
+ // Obtain the favicon service and get the favicon for the specified page
+ nsCOMPtr<nsIFaviconService> favIconSvc(
+ do_GetService("@mozilla.org/browser/favicon-service;1"));
+ nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
+
+ if (childCount > MAX_SEARCH_RESULTS_NUM) {
+ childCount = MAX_SEARCH_RESULTS_NUM;
+ }
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ mHistResultContainer->GetChild(i, getter_AddRefs(child));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ if (!IsHistoryResultNodeURI(child)) {
+ continue;
+ }
+
+ nsAutoCString uri;
+ child->GetUri(uri);
+
+ nsCOMPtr<nsIURI> iconIri;
+ ios->NewURI(uri, nullptr, nullptr, getter_AddRefs(iconIri));
+ nsCOMPtr<nsIFaviconDataCallback> callback =
+ new AsyncFaviconDataReady(this, i, mTimeStamp);
+ favIconSvc->GetFaviconDataForPage(iconIri, callback, 0);
+
+ nsAutoCString idKey;
+ DBusGetIDKeyForURI(i, uri, idKey);
+
+ const char* id = idKey.get();
+ dbus_message_iter_append_basic(&iterArray, DBUS_TYPE_STRING, &id);
+ }
+ }
+
+ nsPrintfCString searchString("%s:%s", KEYWORD_SEARCH_STRING,
+ mSearchTerm.get());
+ const char* search = searchString.get();
+ dbus_message_iter_append_basic(&iterArray, DBUS_TYPE_STRING, &search);
+
+ dbus_message_iter_close_container(&iter, &iterArray);
+
+ dbus_connection_send(mConnection, mReply, nullptr);
+ dbus_message_unref(mReply);
+
+ mReply = nullptr;
+}
+
+void nsGNOMEShellHistorySearchResult::ReceiveSearchResultContainer(
+ nsCOMPtr<nsINavHistoryContainerResultNode> aHistResultContainer) {
+ // Propagate search results to nsGNOMEShellSearchProvider.
+ // SetSearchResult() checks this is up-to-date search (our time stamp matches
+ // latest requested search timestamp).
+ if (mSearchProvider->SetSearchResult(this)) {
+ mHistResultContainer = aHistResultContainer;
+ HandleSearchResultReply();
+ }
+}
+
+void nsGNOMEShellHistorySearchResult::SetHistoryIcon(int aTimeStamp,
+ UniquePtr<uint8_t[]> aData,
+ int aWidth, int aHeight,
+ int aIconIndex) {
+ MOZ_ASSERT(mTimeStamp == aTimeStamp);
+ MOZ_RELEASE_ASSERT(aIconIndex < MAX_SEARCH_RESULTS_NUM);
+ mHistoryIcons[aIconIndex].Set(mTimeStamp, std::move(aData), aWidth, aHeight);
+}
+
+GnomeHistoryIcon* nsGNOMEShellHistorySearchResult::GetHistoryIcon(
+ int aIconIndex) {
+ MOZ_RELEASE_ASSERT(aIconIndex < MAX_SEARCH_RESULTS_NUM);
+ if (mHistoryIcons[aIconIndex].GetTimeStamp() == mTimeStamp &&
+ mHistoryIcons[aIconIndex].IsLoaded()) {
+ return mHistoryIcons + aIconIndex;
+ }
+ return nullptr;
+}
+
+nsGNOMEShellHistoryService* GetGNOMEShellHistoryService() {
+ static nsGNOMEShellHistoryService gGNOMEShellHistoryService;
+ return &gGNOMEShellHistoryService;
+}
diff --git a/browser/components/shell/nsGNOMEShellSearchProvider.h b/browser/components/shell/nsGNOMEShellSearchProvider.h
new file mode 100644
index 0000000000..8d7ee802e0
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellSearchProvider.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef __nsGNOMEShellSearchProvider_h__
+#define __nsGNOMEShellSearchProvider_h__
+
+#include "mozilla/DBusHelpers.h"
+#include "nsINavHistoryService.h"
+#include "nsUnixRemoteServer.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsGNOMEShellDBusHelper.h"
+
+class nsGNOMEShellSearchProvider;
+
+class GnomeHistoryIcon {
+ public:
+ GnomeHistoryIcon() : mTimeStamp(-1), mWidth(0), mHeight(0){};
+
+ // From which search is this icon
+ void Set(int aTimeStamp, mozilla::UniquePtr<uint8_t[]> aData, int aWidth,
+ int aHeight) {
+ mTimeStamp = aTimeStamp;
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mData = std::move(aData);
+ }
+
+ bool IsLoaded() { return mData && mWidth > 0 && mHeight > 0; }
+ int GetTimeStamp() { return mTimeStamp; }
+ uint8_t* GetData() { return mData.get(); }
+ int GetWidth() { return mWidth; }
+ int GetHeight() { return mHeight; }
+
+ private:
+ int mTimeStamp;
+ mozilla::UniquePtr<uint8_t[]> mData;
+ int mWidth;
+ int mHeight;
+};
+
+// nsGNOMEShellHistorySearchResult is a container with contains search results
+// which are files asynchronously by nsGNOMEShellHistoryService.
+// The search results can be opened by Firefox then.
+class nsGNOMEShellHistorySearchResult : public nsUnixRemoteServer {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsGNOMEShellHistorySearchResult)
+
+ nsGNOMEShellHistorySearchResult(nsGNOMEShellSearchProvider* aSearchProvider,
+ DBusConnection* aConnection, int aTimeStamp)
+ : mSearchProvider(aSearchProvider),
+ mReply(nullptr),
+ mConnection(aConnection),
+ mTimeStamp(aTimeStamp){};
+
+ void SetReply(DBusMessage* aReply) { mReply = aReply; }
+ void SetSearchTerm(const char* aSearchTerm) {
+ mSearchTerm = nsAutoCString(aSearchTerm);
+ }
+ DBusConnection* GetDBusConnection() { return mConnection; }
+ void SetTimeStamp(int aTimeStamp) { mTimeStamp = aTimeStamp; }
+ int GetTimeStamp() { return mTimeStamp; }
+ nsAutoCString& GetSearchTerm() { return mSearchTerm; }
+
+ // Receive (asynchronously) history search results from history service.
+ // This is called asynchronously by nsGNOMEShellHistoryService
+ // when we have search results available.
+ void ReceiveSearchResultContainer(
+ nsCOMPtr<nsINavHistoryContainerResultNode> aHistResultContainer);
+
+ nsCOMPtr<nsINavHistoryContainerResultNode> GetSearchResultContainer() {
+ return mHistResultContainer;
+ }
+ void HandleCommandLine(const char* aBuffer, uint32_t aTimestamp) {
+ nsUnixRemoteServer::HandleCommandLine(aBuffer, aTimestamp);
+ }
+
+ void SetHistoryIcon(int aTimeStamp, mozilla::UniquePtr<uint8_t[]> aData,
+ int aWidth, int aHeight, int aIconIndex);
+ GnomeHistoryIcon* GetHistoryIcon(int aIconIndex);
+
+ private:
+ void HandleSearchResultReply();
+
+ ~nsGNOMEShellHistorySearchResult() = default;
+
+ private:
+ nsGNOMEShellSearchProvider* mSearchProvider;
+ nsCOMPtr<nsINavHistoryContainerResultNode> mHistResultContainer;
+ nsAutoCString mSearchTerm;
+ DBusMessage* mReply;
+ DBusConnection* mConnection;
+ int mTimeStamp;
+ GnomeHistoryIcon mHistoryIcons[MAX_SEARCH_RESULTS_NUM];
+};
+
+class nsGNOMEShellHistoryService {
+ public:
+ nsresult QueryHistory(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult);
+
+ private:
+ nsCOMPtr<nsINavHistoryService> mHistoryService;
+};
+
+class nsGNOMEShellSearchProvider {
+ public:
+ nsGNOMEShellSearchProvider()
+ : mConnection(nullptr), mSearchResultTimeStamp(0) {}
+ ~nsGNOMEShellSearchProvider() { Shutdown(); }
+
+ nsresult Startup();
+ void Shutdown();
+
+ DBusHandlerResult HandleDBusMessage(DBusConnection* aConnection,
+ DBusMessage* msg);
+ void UnregisterDBusInterface(DBusConnection* aConnection);
+
+ bool SetSearchResult(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult);
+
+ private:
+ DBusHandlerResult HandleSearchResultSet(DBusMessage* msg,
+ bool aInitialSearch);
+ DBusHandlerResult HandleResultMetas(DBusMessage* msg);
+ DBusHandlerResult ActivateResult(DBusMessage* msg);
+ DBusHandlerResult LaunchSearch(DBusMessage* msg);
+
+ // The connection is owned by DBus library
+ RefPtr<DBusConnection> mConnection;
+ RefPtr<nsGNOMEShellHistorySearchResult> mSearchResult;
+ int mSearchResultTimeStamp;
+};
+
+nsGNOMEShellHistoryService* GetGNOMEShellHistoryService();
+
+#endif // __nsGNOMEShellSearchProvider_h__
diff --git a/browser/components/shell/nsGNOMEShellService.cpp b/browser/components/shell/nsGNOMEShellService.cpp
new file mode 100644
index 0000000000..c485a29955
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellService.cpp
@@ -0,0 +1,520 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+
+#include "nsCOMPtr.h"
+#include "nsGNOMEShellService.h"
+#include "nsShellService.h"
+#include "nsIFile.h"
+#include "nsIProperties.h"
+#include "nsDirectoryServiceDefs.h"
+#include "prenv.h"
+#include "nsString.h"
+#include "nsIGIOService.h"
+#include "nsIGSettingsService.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIImageLoadingContent.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/dom/Element.h"
+#if defined(MOZ_WIDGET_GTK)
+# include "nsIImageToPixbuf.h"
+#endif
+#include "nsXULAppAPI.h"
+#include "gfxPlatform.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using namespace mozilla;
+
+struct ProtocolAssociation {
+ const char* name;
+ bool essential;
+};
+
+struct MimeTypeAssociation {
+ const char* mimeType;
+ const char* extensions;
+};
+
+static const ProtocolAssociation appProtocols[] = {
+ // clang-format off
+ { "http", true },
+ { "https", true },
+ { "ftp", false },
+ { "chrome", false }
+ // clang-format on
+};
+
+static const MimeTypeAssociation appTypes[] = {
+ // clang-format off
+ { "text/html", "htm html shtml" },
+ { "application/xhtml+xml", "xhtml xht" }
+ // clang-format on
+};
+
+#define kDesktopBGSchema "org.gnome.desktop.background"
+#define kDesktopImageGSKey "picture-uri"
+#define kDesktopOptionGSKey "picture-options"
+#define kDesktopDrawBGGSKey "draw-background"
+#define kDesktopColorGSKey "primary-color"
+
+static bool IsRunningAsASnap() {
+ // SNAP holds the path to the snap, use SNAP_NAME
+ // which is easier to parse.
+ const char* snap_name = PR_GetEnv("SNAP_NAME");
+
+ // return early if not set.
+ if (snap_name == nullptr) {
+ return false;
+ }
+
+ // snap_name as defined on https://snapcraft.io/firefox
+ return (strcmp(snap_name, "firefox") == 0);
+}
+
+nsresult nsGNOMEShellService::Init() {
+ nsresult rv;
+
+ if (gfxPlatform::IsHeadless()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // GSettings or GIO _must_ be available, or we do not allow
+ // CreateInstance to succeed.
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+
+ if (!giovfs && !gsettings) return NS_ERROR_NOT_AVAILABLE;
+
+#ifdef MOZ_ENABLE_DBUS
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop && strstr(currentDesktop, "GNOME") != nullptr &&
+ Preferences::GetBool("browser.gnome-search-provider.enabled", false)) {
+ mSearchProvider.Startup();
+ }
+#endif
+
+ // Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use
+ // the locale encoding. If it's not set, they use UTF-8.
+ mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr;
+
+ if (GetAppPathFromLauncher()) return NS_OK;
+
+ nsCOMPtr<nsIProperties> dirSvc(
+ do_GetService("@mozilla.org/file/directory_service;1"));
+ NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIFile> appPath;
+ rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(appPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return appPath->GetNativePath(mAppPath);
+}
+
+NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService,
+ nsIToolkitShellService)
+
+bool nsGNOMEShellService::GetAppPathFromLauncher() {
+ gchar* tmp;
+
+ const char* launcher = PR_GetEnv("MOZ_APP_LAUNCHER");
+ if (!launcher) return false;
+
+ if (g_path_is_absolute(launcher)) {
+ mAppPath = launcher;
+ tmp = g_path_get_basename(launcher);
+ gchar* fullpath = g_find_program_in_path(tmp);
+ if (fullpath && mAppPath.Equals(fullpath)) mAppIsInPath = true;
+ g_free(fullpath);
+ } else {
+ tmp = g_find_program_in_path(launcher);
+ if (!tmp) return false;
+ mAppPath = tmp;
+ mAppIsInPath = true;
+ }
+
+ g_free(tmp);
+ return true;
+}
+
+bool nsGNOMEShellService::KeyMatchesAppName(const char* aKeyValue) const {
+ gchar* commandPath;
+ if (mUseLocaleFilenames) {
+ gchar* nativePath =
+ g_filename_from_utf8(aKeyValue, -1, nullptr, nullptr, nullptr);
+ if (!nativePath) {
+ NS_ERROR("Error converting path to filesystem encoding");
+ return false;
+ }
+
+ commandPath = g_find_program_in_path(nativePath);
+ g_free(nativePath);
+ } else {
+ commandPath = g_find_program_in_path(aKeyValue);
+ }
+
+ if (!commandPath) return false;
+
+ bool matches = mAppPath.Equals(commandPath);
+ g_free(commandPath);
+ return matches;
+}
+
+bool nsGNOMEShellService::CheckHandlerMatchesAppName(
+ const nsACString& handler) const {
+ gint argc;
+ gchar** argv;
+ nsAutoCString command(handler);
+
+ // The string will be something of the form: [/path/to/]browser "%s"
+ // We want to remove all of the parameters and get just the binary name.
+
+ if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) {
+ command.Assign(argv[0]);
+ g_strfreev(argv);
+ }
+
+ if (!KeyMatchesAppName(command.get()))
+ return false; // the handler is set to another app
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::IsDefaultBrowser(bool aForAllTypes,
+ bool* aIsDefaultBrowser) {
+ *aIsDefaultBrowser = false;
+
+ if (IsRunningAsASnap()) {
+ const gchar* argv[] = {"xdg-settings", "check", "default-web-browser",
+ "firefox.desktop", nullptr};
+ GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDERR_TO_DEV_NULL);
+ gchar* output = nullptr;
+ gint exit_status = 0;
+ if (!g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr,
+ &output, nullptr, &exit_status, nullptr)) {
+ return NS_OK;
+ }
+ if (exit_status != 0) {
+ g_free(output);
+ return NS_OK;
+ }
+ if (strcmp(output, "yes\n") == 0) {
+ *aIsDefaultBrowser = true;
+ }
+ g_free(output);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ nsAutoCString handler;
+ nsCOMPtr<nsIGIOMimeApp> gioApp;
+
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (!appProtocols[i].essential) continue;
+
+ if (giovfs) {
+ handler.Truncate();
+ nsCOMPtr<nsIHandlerApp> handlerApp;
+ giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name),
+ getter_AddRefs(handlerApp));
+ gioApp = do_QueryInterface(handlerApp);
+ if (!gioApp) return NS_OK;
+
+ gioApp->GetCommand(handler);
+
+ if (!CheckHandlerMatchesAppName(handler))
+ return NS_OK; // the handler is set to another app
+ }
+ }
+
+ *aIsDefaultBrowser = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) {
+#ifdef DEBUG
+ if (aForAllUsers)
+ NS_WARNING(
+ "Setting the default browser for all users is not yet supported");
+#endif
+
+ if (IsRunningAsASnap()) {
+ const gchar* argv[] = {"xdg-settings", "set", "default-web-browser",
+ "firefox.desktop", nullptr};
+ GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL);
+ g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (giovfs) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES,
+ getter_AddRefs(brandBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString brandShortName;
+ brandBundle->GetStringFromName("brandShortName", brandShortName);
+
+ // use brandShortName as the application id.
+ NS_ConvertUTF16toUTF8 id(brandShortName);
+ nsCOMPtr<nsIGIOMimeApp> appInfo;
+ rv = giovfs->FindAppFromCommand(mAppPath, getter_AddRefs(appInfo));
+ if (NS_FAILED(rv)) {
+ // Application was not found in the list of installed applications
+ // provided by OS. Fallback to create appInfo from command and name.
+ rv = giovfs->CreateAppFromCommand(mAppPath, id, getter_AddRefs(appInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // set handler for the protocols
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (appProtocols[i].essential || aClaimAllTypes) {
+ appInfo->SetAsDefaultForURIScheme(
+ nsDependentCString(appProtocols[i].name));
+ }
+ }
+
+ // set handler for .html and xhtml files and MIME types:
+ if (aClaimAllTypes) {
+ // Add mime types for html, xhtml extension and set app to just created
+ // appinfo.
+ for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) {
+ appInfo->SetAsDefaultForMimeType(
+ nsDependentCString(appTypes[i].mimeType));
+ appInfo->SetAsDefaultForFileExtensions(
+ nsDependentCString(appTypes[i].extensions));
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult) {
+ // setting desktop background is currently only supported
+ // for Gnome or desktops using the same GSettings keys
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop && strstr(currentDesktop, "GNOME") != nullptr) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID");
+ if (gnomeSession) {
+ *aResult = true;
+ } else {
+ *aResult = false;
+ }
+
+ return NS_OK;
+}
+
+static nsresult WriteImage(const nsCString& aPath, imgIContainer* aImage) {
+#if !defined(MOZ_WIDGET_GTK)
+ return NS_ERROR_NOT_AVAILABLE;
+#else
+ nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
+ do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
+ if (!imgToPixbuf) return NS_ERROR_NOT_AVAILABLE;
+
+ GdkPixbuf* pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage);
+ if (!pixbuf) return NS_ERROR_NOT_AVAILABLE;
+
+ gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr);
+
+ g_object_unref(pixbuf);
+ return res ? NS_OK : NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDesktopBackground(dom::Element* aElement,
+ int32_t aPosition,
+ const nsACString& aImageName) {
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent =
+ do_QueryInterface(aElement, &rv);
+ if (!imageContent) return rv;
+
+ // get the image container
+ nsCOMPtr<imgIRequest> request;
+ rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (!request) return rv;
+ nsCOMPtr<imgIContainer> container;
+ rv = request->GetImage(getter_AddRefs(container));
+ if (!container) return rv;
+
+ // Set desktop wallpaper filling style
+ nsAutoCString options;
+ if (aPosition == BACKGROUND_TILE)
+ options.AssignLiteral("wallpaper");
+ else if (aPosition == BACKGROUND_STRETCH)
+ options.AssignLiteral("stretched");
+ else if (aPosition == BACKGROUND_FILL)
+ options.AssignLiteral("zoom");
+ else if (aPosition == BACKGROUND_FIT)
+ options.AssignLiteral("scaled");
+ else if (aPosition == BACKGROUND_SPAN)
+ options.AssignLiteral("spanned");
+ else
+ options.AssignLiteral("centered");
+
+ // Write the background file to the home directory.
+ nsAutoCString filePath(PR_GetEnv("HOME"));
+
+ // get the product brand name from localized strings
+ nsAutoString brandName;
+ nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID;
+ nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID));
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES,
+ getter_AddRefs(brandBundle));
+ if (NS_SUCCEEDED(rv) && brandBundle) {
+ rv = brandBundle->GetStringFromName("brandShortName", brandName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // build the file name
+ filePath.Append('/');
+ filePath.Append(NS_ConvertUTF16toUTF8(brandName));
+ filePath.AppendLiteral("_wallpaper.png");
+
+ // write the image to a file in the home dir
+ rv = WriteImage(filePath, container);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema),
+ getter_AddRefs(background_settings));
+ if (background_settings) {
+ gchar* file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr);
+ if (!file_uri) return NS_ERROR_FAILURE;
+
+ background_settings->SetString(nsLiteralCString(kDesktopOptionGSKey),
+ options);
+
+ background_settings->SetString(nsLiteralCString(kDesktopImageGSKey),
+ nsDependentCString(file_uri));
+ g_free(file_uri);
+ background_settings->SetBoolean(nsLiteralCString(kDesktopDrawBGGSKey),
+ true);
+ return rv;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+#define COLOR_16_TO_8_BIT(_c) ((_c) >> 8)
+#define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c))
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t* aColor) {
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ nsAutoCString background;
+
+ if (gsettings) {
+ gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema),
+ getter_AddRefs(background_settings));
+ if (background_settings) {
+ background_settings->GetString(nsLiteralCString(kDesktopColorGSKey),
+ background);
+ }
+ }
+
+ if (background.IsEmpty()) {
+ *aColor = 0;
+ return NS_OK;
+ }
+
+ GdkColor color;
+ gboolean success = gdk_color_parse(background.get(), &color);
+
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ *aColor = COLOR_16_TO_8_BIT(color.red) << 16 |
+ COLOR_16_TO_8_BIT(color.green) << 8 | COLOR_16_TO_8_BIT(color.blue);
+ return NS_OK;
+}
+
+static void ColorToCString(uint32_t aColor, nsCString& aResult) {
+ // The #rrrrggggbbbb format is used to match gdk_color_to_string()
+ aResult.SetLength(13);
+ char* buf = aResult.BeginWriting();
+ if (!buf) return;
+
+ uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff);
+ uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff);
+ uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff);
+
+ snprintf(buf, 14, "#%04x%04x%04x", red, green, blue);
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) {
+ NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits");
+ nsAutoCString colorString;
+ ColorToCString(aColor, colorString);
+
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema),
+ getter_AddRefs(background_settings));
+ if (background_settings) {
+ background_settings->SetString(nsLiteralCString(kDesktopColorGSKey),
+ colorString);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/browser/components/shell/nsGNOMEShellService.h b/browser/components/shell/nsGNOMEShellService.h
new file mode 100644
index 0000000000..c1a48135e8
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellService.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsgnomeshellservice_h____
+#define nsgnomeshellservice_h____
+
+#include "nsIGNOMEShellService.h"
+#include "nsToolkitShellService.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#ifdef MOZ_ENABLE_DBUS
+# include "nsGNOMEShellSearchProvider.h"
+#endif
+
+class nsGNOMEShellService final : public nsIGNOMEShellService,
+ public nsToolkitShellService {
+ public:
+ nsGNOMEShellService() : mAppIsInPath(false) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIGNOMESHELLSERVICE
+
+ nsresult Init();
+
+ private:
+ ~nsGNOMEShellService() {}
+
+ bool KeyMatchesAppName(const char* aKeyValue) const;
+ bool CheckHandlerMatchesAppName(const nsACString& handler) const;
+
+#ifdef MOZ_ENABLE_DBUS
+ nsGNOMEShellSearchProvider mSearchProvider;
+#endif
+ bool GetAppPathFromLauncher();
+ bool mUseLocaleFilenames;
+ nsCString mAppPath;
+ bool mAppIsInPath;
+};
+
+#endif // nsgnomeshellservice_h____
diff --git a/browser/components/shell/nsIGNOMEShellService.idl b/browser/components/shell/nsIGNOMEShellService.idl
new file mode 100644
index 0000000000..842ce5e8a7
--- /dev/null
+++ b/browser/components/shell/nsIGNOMEShellService.idl
@@ -0,0 +1,19 @@
+/* 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/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(2ce5c803-edcd-443d-98eb-ceba86d02d13)]
+interface nsIGNOMEShellService : nsIShellService
+{
+ /**
+ * Used to determine whether or not to offer "Set as desktop background"
+ * functionality. Even if shell service is available it is not
+ * guaranteed that it is able to set the background for every desktop
+ * which is especially true for Linux with its many different desktop
+ * environments.
+ */
+ readonly attribute boolean canSetDesktopBackground;
+};
+
diff --git a/browser/components/shell/nsIMacShellService.idl b/browser/components/shell/nsIMacShellService.idl
new file mode 100644
index 0000000000..52f97ab4a5
--- /dev/null
+++ b/browser/components/shell/nsIMacShellService.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(387fdc80-0077-4b60-a0d9-d9e80a83ba64)]
+interface nsIMacShellService : nsIShellService
+{
+ /**
+ * Opens the desktop preferences, e.g. for after setting the background.
+ */
+ void showDesktopPreferences();
+
+ /**
+ * @param aPaneID used by macOS to identify the pane to open.
+ * Example arguments:
+ * * "" - use the default Security and Privacy pane.
+ * * General
+ * * Privacy
+ * * Privacy_AllFiles
+ * * Privacy_Camera
+ * * Privacy_Microphone
+ */
+ void showSecurityPreferences(in ACString aPaneID);
+};
diff --git a/browser/components/shell/nsIShellService.idl b/browser/components/shell/nsIShellService.idl
new file mode 100644
index 0000000000..a3fa434123
--- /dev/null
+++ b/browser/components/shell/nsIShellService.idl
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+webidl Element;
+
+[scriptable, uuid(2d1a95e4-5bd8-4eeb-b0a8-c1455fd2a357)]
+interface nsIShellService : nsISupports
+{
+ /**
+ * Determines whether or not Firefox is the "Default Browser."
+ * This is simply whether or not Firefox is registered to handle
+ * http links.
+ *
+ * @param aForAllTypes true if the check should be made for HTTP and HTML.
+ * false if the check should be made for HTTP only.
+ * This parameter may be ignored on some platforms.
+ */
+ boolean isDefaultBrowser([optional] in boolean aForAllTypes);
+
+ /**
+ * Registers Firefox as the "Default Browser."
+ *
+ * @param aClaimAllTypes Register Firefox as the handler for
+ * additional protocols (ftp, chrome etc)
+ * and web documents (.html, .xhtml etc).
+ * @param aForAllUsers Whether or not Firefox should attempt
+ * to become the default browser for all
+ * users on a multi-user system.
+ */
+ void setDefaultBrowser(in boolean aClaimAllTypes, in boolean aForAllUsers);
+
+ /**
+ * Flags for positioning/sizing of the Desktop Background image.
+ */
+ const long BACKGROUND_TILE = 1;
+ const long BACKGROUND_STRETCH = 2;
+ const long BACKGROUND_CENTER = 3;
+ const long BACKGROUND_FILL = 4;
+ const long BACKGROUND_FIT = 5;
+ const long BACKGROUND_SPAN = 6;
+
+ /**
+ * Sets the desktop background image using either the HTML <IMG>
+ * element supplied or the background image of the element supplied.
+ *
+ * @param aImageElement Either a HTML <IMG> element or an element with
+ * a background image from which to source the
+ * background image.
+ * @param aPosition How to place the image on the desktop
+ * @param aImageName The image name. Equivalent to the leaf name of the
+ * location.href.
+ */
+ void setDesktopBackground(in Element aElement,
+ in long aPosition,
+ in ACString aImageName);
+
+ /**
+ * The desktop background color, visible when no background image is
+ * used, or if the background image is centered and does not fill the
+ * entire screen. A rgb value, where (r << 16 | g << 8 | b)
+ */
+ attribute unsigned long desktopBackgroundColor;
+};
diff --git a/browser/components/shell/nsIWindowsShellService.idl b/browser/components/shell/nsIWindowsShellService.idl
new file mode 100644
index 0000000000..4a4e5e484b
--- /dev/null
+++ b/browser/components/shell/nsIWindowsShellService.idl
@@ -0,0 +1,15 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(fb9b59db-5a91-4e67-92b6-35e7d6e6d3fd)]
+interface nsIWindowsShellService : nsISupports
+{
+ void createShortcut(in nsIFile aBinary, in Array<AString> aArguments,
+ in AString aDescription, in nsIFile aIconFile, in AString aAppUserModelId,
+ in nsIFile aTarget);
+};
diff --git a/browser/components/shell/nsMacShellService.cpp b/browser/components/shell/nsMacShellService.cpp
new file mode 100644
index 0000000000..c07b46392e
--- /dev/null
+++ b/browser/components/shell/nsMacShellService.cpp
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "CocoaFileUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIImageLoadingContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIContent.h"
+#include "nsICookieJarSettings.h"
+#include "nsIObserverService.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsMacShellService.h"
+#include "nsIProperties.h"
+#include "nsServiceManagerUtils.h"
+#include "nsShellService.h"
+#include "nsString.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+#include "nsIPrefService.h"
+#include "mozilla/dom/Element.h"
+#include "DesktopBackgroundImage.h"
+
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <ApplicationServices/ApplicationServices.h>
+
+using mozilla::dom::Element;
+using mozilla::widget::SetDesktopImage;
+
+#define NETWORK_PREFPANE "/System/Library/PreferencePanes/Network.prefPane"_ns
+#define DESKTOP_PREFPANE \
+ nsLiteralCString( \
+ "/System/Library/PreferencePanes/DesktopScreenEffectsPref.prefPane")
+
+#define SAFARI_BUNDLE_IDENTIFIER "com.apple.Safari"
+
+NS_IMPL_ISUPPORTS(nsMacShellService, nsIMacShellService, nsIShellService,
+ nsIToolkitShellService, nsIWebProgressListener)
+
+NS_IMETHODIMP
+nsMacShellService::IsDefaultBrowser(bool aForAllTypes,
+ bool* aIsDefaultBrowser) {
+ *aIsDefaultBrowser = false;
+
+ CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
+ if (!firefoxID) {
+ // CFBundleGetIdentifier is expected to return nullptr only if the specified
+ // bundle doesn't have a bundle identifier in its plist. In this case, that
+ // means a failure, since our bundle does have an identifier.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the default http handler's bundle ID (or nullptr if it has not been
+ // explicitly set)
+ CFStringRef defaultBrowserID =
+ ::LSCopyDefaultHandlerForURLScheme(CFSTR("http"));
+ if (defaultBrowserID) {
+ *aIsDefaultBrowser =
+ ::CFStringCompare(firefoxID, defaultBrowserID, 0) == kCFCompareEqualTo;
+ ::CFRelease(defaultBrowserID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) {
+ // Note: We don't support aForAllUsers on Mac OS X.
+
+ CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
+ if (!firefoxID) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("http"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("https"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aClaimAllTypes) {
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("ftp"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll,
+ firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDesktopBackground(Element* aElement, int32_t aPosition,
+ const nsACString& aImageName) {
+ // Note: We don't support aPosition on OS X.
+
+ // Get the image URI:
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent =
+ do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> imageURI;
+ rv = imageContent->GetCurrentURI(getter_AddRefs(imageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIURI* docURI = aElement->OwnerDoc()->GetDocumentURI();
+ if (!docURI) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIProperties> fileLocator(
+ do_GetService("@mozilla.org/file/directory_service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current user's "Pictures" folder (That's ~/Pictures):
+ fileLocator->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(mBackgroundFile));
+ if (!mBackgroundFile) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsAutoString fileNameUnicode;
+ CopyUTF8toUTF16(aImageName, fileNameUnicode);
+
+ // and add the imgage file name itself:
+ mBackgroundFile->Append(fileNameUnicode);
+
+ // Download the image; the desktop background will be set in OnStateChange()
+ nsCOMPtr<nsIWebBrowserPersist> wbp(do_CreateInstance(
+ "@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
+ nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE;
+
+ wbp->SetPersistFlags(flags);
+ wbp->SetProgressListener(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetContainer();
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
+ if (docShell) {
+ loadContext = do_QueryInterface(docShell);
+ }
+
+ auto referrerInfo =
+ mozilla::MakeRefPtr<mozilla::dom::ReferrerInfo>(*aElement);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ aElement->OwnerDoc()->CookieJarSettings();
+ return wbp->SaveURI(imageURI, aElement->NodePrincipal(), 0, referrerInfo,
+ cookieJarSettings, nullptr, nullptr, mBackgroundFile,
+ nsIContentPolicy::TYPE_IMAGE, loadContext);
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus) && (aStateFlags & STATE_STOP) &&
+ (aRequest == nullptr)) {
+ nsCOMPtr<nsIObserverService> os(
+ do_GetService("@mozilla.org/observer-service;1"));
+ if (os)
+ os->NotifyObservers(nullptr, "shell:desktop-background-changed", nullptr);
+
+ bool exists = false;
+ nsresult rv = mBackgroundFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) {
+ return NS_OK;
+ }
+
+ SetDesktopImage(mBackgroundFile);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::ShowDesktopPreferences() {
+ nsCOMPtr<nsIFile> lf;
+ nsresult rv =
+ NS_NewNativeLocalFile(DESKTOP_PREFPANE, true, getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ lf->Exists(&exists);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+ return lf->Launch();
+}
+
+NS_IMETHODIMP
+nsMacShellService::GetDesktopBackgroundColor(uint32_t* aColor) {
+ // This method and |SetDesktopBackgroundColor| has no meaning on Mac OS X.
+ // The mac desktop preferences UI uses pictures for the few solid colors it
+ // supports.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDesktopBackgroundColor(uint32_t aColor) {
+ // This method and |GetDesktopBackgroundColor| has no meaning on Mac OS X.
+ // The mac desktop preferences UI uses pictures for the few solid colors it
+ // supports.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMacShellService::ShowSecurityPreferences(const nsACString& aPaneID) {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ CFStringRef paneID = ::CFStringCreateWithBytes(
+ kCFAllocatorDefault, (const UInt8*)PromiseFlatCString(aPaneID).get(),
+ aPaneID.Length(), kCFStringEncodingUTF8, false);
+
+ if (paneID) {
+ CFStringRef format =
+ CFSTR("x-apple.systempreferences:com.apple.preference.security?%@");
+ if (format) {
+ CFStringRef urlStr =
+ CFStringCreateWithFormat(kCFAllocatorDefault, NULL, format, paneID);
+ if (urlStr) {
+ CFURLRef url = ::CFURLCreateWithString(NULL, urlStr, NULL);
+ rv = CocoaFileUtils::OpenURL(url);
+
+ ::CFRelease(urlStr);
+ }
+
+ ::CFRelease(format);
+ }
+
+ ::CFRelease(paneID);
+ }
+ return rv;
+}
diff --git a/browser/components/shell/nsMacShellService.h b/browser/components/shell/nsMacShellService.h
new file mode 100644
index 0000000000..bb08c72b4e
--- /dev/null
+++ b/browser/components/shell/nsMacShellService.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsmacshellservice_h____
+#define nsmacshellservice_h____
+
+#include "nsToolkitShellService.h"
+#include "nsIMacShellService.h"
+#include "nsIWebProgressListener.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+class nsMacShellService : public nsIMacShellService,
+ public nsToolkitShellService,
+ public nsIWebProgressListener {
+ public:
+ nsMacShellService(){};
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIMACSHELLSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ protected:
+ virtual ~nsMacShellService(){};
+
+ private:
+ nsCOMPtr<nsIFile> mBackgroundFile;
+};
+
+#endif // nsmacshellservice_h____
diff --git a/browser/components/shell/nsShellService.h b/browser/components/shell/nsShellService.h
new file mode 100644
index 0000000000..346a16ad69
--- /dev/null
+++ b/browser/components/shell/nsShellService.h
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#define PREF_CHECKDEFAULTBROWSER "browser.shell.checkDefaultBrowser"
+#define PREF_DEFAULTBROWSERCHECKCOUNT "browser.shell.defaultBrowserCheckCount"
+
+#define SHELLSERVICE_PROPERTIES \
+ "chrome://browser/locale/shellservice.properties"
+#define BRAND_PROPERTIES "chrome://branding/locale/brand.properties"
diff --git a/browser/components/shell/nsToolkitShellService.h b/browser/components/shell/nsToolkitShellService.h
new file mode 100644
index 0000000000..552f3cd65d
--- /dev/null
+++ b/browser/components/shell/nsToolkitShellService.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nstoolkitshellservice_h____
+#define nstoolkitshellservice_h____
+
+#include "nsIToolkitShellService.h"
+
+class nsToolkitShellService : public nsIToolkitShellService {
+ public:
+ NS_IMETHOD IsDefaultBrowser(bool aForAllTypes, bool* aIsDefaultBrowser) = 0;
+
+ NS_IMETHODIMP IsDefaultApplication(bool* aIsDefaultBrowser) {
+ // Only care about the http(s) protocol. This only matters on Windows.
+ return IsDefaultBrowser(false, aIsDefaultBrowser);
+ }
+};
+
+#endif // nstoolkitshellservice_h____
diff --git a/browser/components/shell/nsWindowsShellService.cpp b/browser/components/shell/nsWindowsShellService.cpp
new file mode 100644
index 0000000000..87f10cb80f
--- /dev/null
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsWindowsShellService.h"
+
+#include "BinaryPath.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "mozilla/RefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIContent.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIOutputStream.h"
+#include "nsIPrefService.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsShellService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIWindowsRegKey.h"
+#include "nsUnicharUtils.h"
+#include "nsIURLFormatter.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/gfx/2D.h"
+#include "WindowsDefaultBrowser.h"
+
+#include "windows.h"
+#include "shellapi.h"
+#include <propvarutil.h>
+#include <propkey.h>
+
+#include <shlobj.h>
+#include "WinUtils.h"
+
+#include <mbstring.h>
+
+#undef ACCESS_READ
+
+#ifndef MAX_BUF
+# define MAX_BUF 4096
+#endif
+
+#define REG_SUCCEEDED(val) (val == ERROR_SUCCESS)
+
+#define REG_FAILED(val) (val != ERROR_SUCCESS)
+
+#ifdef DEBUG
+# define NS_ENSURE_HRESULT(hres, ret) \
+ do { \
+ HRESULT result = hres; \
+ if (MOZ_UNLIKELY(FAILED(result))) { \
+ mozilla::SmprintfPointer msg = mozilla::Smprintf( \
+ "NS_ENSURE_HRESULT(%s, %s) failed with " \
+ "result 0x%" PRIX32, \
+ #hres, #ret, static_cast<uint32_t>(result)); \
+ NS_WARNING(msg.get()); \
+ return ret; \
+ } \
+ } while (false)
+#else
+# define NS_ENSURE_HRESULT(hres, ret) \
+ if (MOZ_UNLIKELY(FAILED(hres))) return ret
+#endif
+
+using mozilla::IsWin8OrLater;
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIToolkitShellService,
+ nsIShellService, nsIWindowsShellService)
+
+static nsresult OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName,
+ HKEY* aKey) {
+ const nsString& flatName = PromiseFlatString(aKeyName);
+
+ DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey);
+ switch (res) {
+ case ERROR_SUCCESS:
+ break;
+ case ERROR_ACCESS_DENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case ERROR_FILE_NOT_FOUND:
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+nsresult GetHelperPath(nsAutoString& aPath) {
+ nsresult rv;
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> appHelper;
+ rv = directoryService->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(appHelper));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->SetNativeLeafName("uninstall"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->AppendNative("helper.exe"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->GetPath(aPath);
+
+ aPath.Insert(L'"', 0);
+ aPath.Append(L'"');
+ return rv;
+}
+
+nsresult LaunchHelper(nsAutoString& aPath) {
+ STARTUPINFOW si = {sizeof(si), 0};
+ PROCESS_INFORMATION pi = {0};
+
+ if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE, 0,
+ nullptr, nullptr, &si, &pi)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ return NS_OK;
+}
+
+static bool IsPathDefaultForClass(
+ const RefPtr<IApplicationAssociationRegistration>& pAAR, wchar_t* exePath,
+ LPCWSTR aClassName) {
+ // Make sure the Prog ID matches what we have
+ LPWSTR registeredApp;
+ bool isProtocol = *aClassName != L'.';
+ ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION;
+ HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE,
+ &registeredApp);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ LPCWSTR progID = isProtocol ? L"FirefoxURL" : L"FirefoxHTML";
+ bool isDefault = !wcsnicmp(registeredApp, progID, wcslen(progID));
+
+ nsAutoString regAppName(registeredApp);
+ CoTaskMemFree(registeredApp);
+
+ if (isDefault) {
+ // Make sure the application path for this progID is this installation.
+ regAppName.AppendLiteral("\\shell\\open\\command");
+ HKEY theKey;
+ nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, regAppName, &theKey);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ wchar_t cmdFromReg[MAX_BUF] = L"";
+ DWORD len = sizeof(cmdFromReg);
+ DWORD res = ::RegQueryValueExW(theKey, nullptr, nullptr, nullptr,
+ (LPBYTE)cmdFromReg, &len);
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res)) {
+ return false;
+ }
+
+ wchar_t fullCmd[MAX_BUF] = L"";
+ _snwprintf(fullCmd, MAX_BUF, L"\"%s\" -osint -url \"%%1\"", exePath);
+
+ isDefault = _wcsicmp(fullCmd, cmdFromReg) == 0;
+ }
+
+ return isDefault;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::IsDefaultBrowser(bool aForAllTypes,
+ bool* aIsDefaultBrowser) {
+ *aIsDefaultBrowser = false;
+
+ RefPtr<IApplicationAssociationRegistration> pAAR;
+ HRESULT hr = CoCreateInstance(
+ CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR));
+ if (FAILED(hr)) {
+ return NS_OK;
+ }
+
+ wchar_t exePath[MAXPATHLEN] = L"";
+ nsresult rv = BinaryPath::GetLong(exePath);
+
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L"http");
+ if (*aIsDefaultBrowser && aForAllTypes) {
+ *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L".html");
+ }
+ return NS_OK;
+}
+
+nsresult nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI() {
+ IApplicationAssociationRegistrationUI* pAARUI;
+ HRESULT hr = CoCreateInstance(
+ CLSID_ApplicationAssociationRegistrationUI, NULL, CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistrationUI, (void**)&pAARUI);
+ if (SUCCEEDED(hr)) {
+ mozilla::UniquePtr<wchar_t[]> appRegName;
+ GetAppRegName(appRegName);
+ hr = pAARUI->LaunchAdvancedAssociationUI(appRegName.get());
+ pAARUI->Release();
+ }
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsWindowsShellService::LaunchControlPanelDefaultPrograms() {
+ return ::LaunchControlPanelDefaultPrograms() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() {
+ return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsWindowsShellService::InvokeHTTPOpenAsVerb() {
+ nsCOMPtr<nsIURLFormatter> formatter(
+ do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
+ if (!formatter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString urlStr;
+ nsresult rv = formatter->FormatURLPref(u"app.support.baseURL"_ns, urlStr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!StringBeginsWith(urlStr, u"https://"_ns)) {
+ return NS_ERROR_FAILURE;
+ }
+ urlStr.AppendLiteral("win10-default-browser");
+
+ SHELLEXECUTEINFOW seinfo = {sizeof(SHELLEXECUTEINFOW)};
+ seinfo.lpVerb = L"openas";
+ seinfo.lpFile = urlStr.get();
+ seinfo.nShow = SW_SHOWNORMAL;
+ if (!ShellExecuteExW(&seinfo)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsWindowsShellService::LaunchHTTPHandlerPane() {
+ OPENASINFO info;
+ info.pcszFile = L"http";
+ info.pcszClass = nullptr;
+ info.oaifInFlags =
+ OAIF_FORCE_REGISTRATION | OAIF_URL_PROTOCOL | OAIF_REGISTER_EXT;
+
+ HRESULT hr = SHOpenWithDialog(nullptr, &info);
+ if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes,
+ bool aForAllUsers) {
+ nsAutoString appHelperPath;
+ if (NS_FAILED(GetHelperPath(appHelperPath))) return NS_ERROR_FAILURE;
+
+ if (aForAllUsers) {
+ appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal");
+ } else {
+ appHelperPath.AppendLiteral(" /SetAsDefaultAppUser");
+ }
+
+ nsresult rv = LaunchHelper(appHelperPath);
+ if (NS_SUCCEEDED(rv) && IsWin8OrLater()) {
+ if (aClaimAllTypes) {
+ if (IsWin10OrLater()) {
+ rv = LaunchModernSettingsDialogDefaultApps();
+ } else {
+ rv = LaunchControlPanelDefaultsSelectionUI();
+ }
+ // The above call should never really fail, but just in case
+ // fall back to showing the HTTP association screen only.
+ if (NS_FAILED(rv)) {
+ if (IsWin10OrLater()) {
+ rv = InvokeHTTPOpenAsVerb();
+ } else {
+ rv = LaunchHTTPHandlerPane();
+ }
+ }
+ } else {
+ // Windows 10 blocks attempts to load the
+ // HTTP Handler association dialog.
+ if (IsWin10OrLater()) {
+ rv = LaunchModernSettingsDialogDefaultApps();
+ } else {
+ rv = LaunchHTTPHandlerPane();
+ }
+
+ // The above call should never really fail, but just in case
+ // fall back to showing control panel for all defaults
+ if (NS_FAILED(rv)) {
+ rv = LaunchControlPanelDefaultsSelectionUI();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return rv;
+}
+
+static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage) {
+ nsresult rv;
+
+ RefPtr<gfx::SourceSurface> surface = aImage->GetFrame(
+ imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ // For either of the following formats we want to set the biBitCount member
+ // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap
+ // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored
+ // for the BI_RGB value we use for the biCompression member.
+ MOZ_ASSERT(surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
+
+ RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ int32_t bytesPerPixel = 4 * sizeof(uint8_t);
+ uint32_t bytesPerRow = bytesPerPixel * width;
+
+ // initialize these bitmap structs which we will later
+ // serialize directly to the head of the bitmap file
+ BITMAPINFOHEADER bmi;
+ bmi.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.biWidth = width;
+ bmi.biHeight = height;
+ bmi.biPlanes = 1;
+ bmi.biBitCount = (WORD)bytesPerPixel * 8;
+ bmi.biCompression = BI_RGB;
+ bmi.biSizeImage = bytesPerRow * height;
+ bmi.biXPelsPerMeter = 0;
+ bmi.biYPelsPerMeter = 0;
+ bmi.biClrUsed = 0;
+ bmi.biClrImportant = 0;
+
+ BITMAPFILEHEADER bf;
+ bf.bfType = 0x4D42; // 'BM'
+ bf.bfReserved1 = 0;
+ bf.bfReserved2 = 0;
+ bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+ bf.bfSize = bf.bfOffBits + bmi.biSizeImage;
+
+ // get a file output stream
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gfx::DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // write the bitmap headers and rgb pixel data to the file
+ rv = NS_ERROR_FAILURE;
+ if (stream) {
+ uint32_t written;
+ stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written);
+ if (written == sizeof(BITMAPFILEHEADER)) {
+ stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written);
+ if (written == sizeof(BITMAPINFOHEADER)) {
+ // write out the image data backwards because the desktop won't
+ // show bitmaps with negative heights for top-to-bottom
+ uint32_t i = map.mStride * height;
+ do {
+ i -= map.mStride;
+ stream->Write(((const char*)map.mData) + i, bytesPerRow, &written);
+ if (written == bytesPerRow) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ } while (i != 0);
+ }
+ }
+
+ stream->Close();
+ }
+
+ dataSurface->Unmap();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDesktopBackground(dom::Element* aElement,
+ int32_t aPosition,
+ const nsACString& aImageName) {
+ if (!aElement || !aElement->IsHTMLElement(nsGkAtoms::img)) {
+ // XXX write background loading stuff!
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent =
+ do_QueryInterface(aElement, &rv);
+ if (!imageContent) return rv;
+
+ // get the image container
+ nsCOMPtr<imgIRequest> request;
+ rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (!request) return rv;
+
+ nsCOMPtr<imgIContainer> container;
+ rv = request->GetImage(getter_AddRefs(container));
+ if (!container) return NS_ERROR_FAILURE;
+
+ // get the file name from localized strings
+ nsCOMPtr<nsIStringBundleService> bundleService(
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> shellBundle;
+ rv = bundleService->CreateBundle(SHELLSERVICE_PROPERTIES,
+ getter_AddRefs(shellBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // e.g. "Desktop Background.bmp"
+ nsAutoString fileLeafName;
+ rv = shellBundle->GetStringFromName("desktopBackgroundLeafNameWin",
+ fileLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the profile root directory
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // eventually, the path is "%APPDATA%\Mozilla\Firefox\Desktop Background.bmp"
+ rv = file->Append(fileLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = file->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the bitmap to a file in the profile directory.
+ // We have to write old bitmap format for Windows 7 wallpapar support.
+ rv = WriteBitmap(file, container);
+
+ // if the file was written successfully, set it as the system wallpaper
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ u"Control Panel\\Desktop"_ns,
+ nsIWindowsRegKey::ACCESS_SET_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString tile;
+ nsAutoString style;
+ switch (aPosition) {
+ case BACKGROUND_TILE:
+ style.Assign('0');
+ tile.Assign('1');
+ break;
+ case BACKGROUND_CENTER:
+ style.Assign('0');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_STRETCH:
+ style.Assign('2');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_FILL:
+ style.AssignLiteral("10");
+ tile.Assign('0');
+ break;
+ case BACKGROUND_FIT:
+ style.Assign('6');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_SPAN:
+ style.AssignLiteral("22");
+ tile.Assign('0');
+ break;
+ }
+
+ rv = regKey->WriteStringValue(u"TileWallpaper"_ns, tile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = regKey->WriteStringValue(u"WallpaperStyle"_ns, style);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = regKey->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(),
+ SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor) {
+ uint32_t color = ::GetSysColor(COLOR_DESKTOP);
+ *aColor =
+ (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor) {
+ int aParameters[2] = {COLOR_BACKGROUND, COLOR_DESKTOP};
+ BYTE r = (aColor >> 16);
+ BYTE g = (aColor << 16) >> 24;
+ BYTE b = (aColor << 24) >> 24;
+ COLORREF colors[2] = {RGB(r, g, b), RGB(r, g, b)};
+
+ ::SetSysColors(sizeof(aParameters) / sizeof(int), aParameters, colors);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ u"Control Panel\\Colors"_ns,
+ nsIWindowsRegKey::ACCESS_SET_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wchar_t rgb[12];
+ _snwprintf(rgb, 12, L"%u %u %u", r, g, b);
+
+ rv = regKey->WriteStringValue(u"Background"_ns, nsDependentString(rgb));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return regKey->Close();
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
+ const nsTArray<nsString>& aArguments,
+ const nsAString& aDescription,
+ nsIFile* aIconFile,
+ const nsAString& aAppUserModelId,
+ nsIFile* aTarget) {
+ NS_ENSURE_ARG(aBinary);
+ NS_ENSURE_ARG(aTarget);
+
+ RefPtr<IShellLinkW> link;
+ HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, getter_AddRefs(link));
+ NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
+
+ nsString path(aBinary->NativePath());
+ link->SetPath(path.get());
+
+ if (!aDescription.IsEmpty()) {
+ link->SetDescription(PromiseFlatString(aDescription).get());
+ }
+
+ // TODO: Properly escape quotes in the string, see bug 1604287.
+ nsString arguments;
+ for (auto& arg : aArguments) {
+ arguments.AppendPrintf("\"%S\" ", arg.get());
+ }
+
+ link->SetArguments(arguments.get());
+
+ if (aIconFile) {
+ nsString icon(aIconFile->NativePath());
+ link->SetIconLocation(icon.get(), 0);
+ }
+
+ if (!aAppUserModelId.IsEmpty()) {
+ RefPtr<IPropertyStore> propStore;
+ hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore));
+ NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
+
+ PROPVARIANT pv;
+ if (FAILED(InitPropVariantFromString(
+ PromiseFlatString(aAppUserModelId).get(), &pv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = propStore->SetValue(PKEY_AppUserModel_ID, pv);
+ PropVariantClear(&pv);
+ NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
+
+ hr = propStore->Commit();
+ NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
+ }
+
+ RefPtr<IPersistFile> persist;
+ hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist));
+ NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
+
+ nsString target(aTarget->NativePath());
+ hr = persist->Save(target.get(), TRUE);
+ NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsWindowsShellService::nsWindowsShellService() {}
+
+nsWindowsShellService::~nsWindowsShellService() {}
diff --git a/browser/components/shell/nsWindowsShellService.h b/browser/components/shell/nsWindowsShellService.h
new file mode 100644
index 0000000000..6127f687bd
--- /dev/null
+++ b/browser/components/shell/nsWindowsShellService.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nswindowsshellservice_h____
+#define nswindowsshellservice_h____
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsToolkitShellService.h"
+#include "nsIShellService.h"
+#include "nsIWindowsShellService.h"
+
+#include <windows.h>
+#include <ole2.h>
+
+class nsWindowsShellService : public nsIShellService,
+ public nsToolkitShellService,
+ public nsIWindowsShellService {
+ virtual ~nsWindowsShellService();
+
+ public:
+ nsWindowsShellService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIWINDOWSSHELLSERVICE
+
+ protected:
+ nsresult LaunchControlPanelDefaultsSelectionUI();
+ nsresult LaunchControlPanelDefaultPrograms();
+ nsresult LaunchModernSettingsDialogDefaultApps();
+ nsresult InvokeHTTPOpenAsVerb();
+ nsresult LaunchHTTPHandlerPane();
+};
+
+#endif // nswindowsshellservice_h____
diff --git a/browser/components/shell/search-provider-files/README b/browser/components/shell/search-provider-files/README
new file mode 100644
index 0000000000..81ea16d9b6
--- /dev/null
+++ b/browser/components/shell/search-provider-files/README
@@ -0,0 +1,20 @@
+In order to get gnome shell search provider registered and active
+you need to install the firefox-search-provider.ini and firefox.desktop files system wide.
+
+The locations may be distro specific, for instance Fedora and Ubuntu expect
+the files at:
+
+/usr/share/gnome-shell/search-providers/firefox-search-provider.ini
+/usr/share/applications/firefox.desktop
+
+firefox.desktop is a system-wide Firefox launcher. It may come with your
+distribution or you can use this one. Update name of firefox desktop file at firefox-search-provider.ini
+according your actual file at /usr/share/applications.
+
+firefox-search-provider.ini registers Firefox as a search provider.
+When the file is correctly installed you can see Firefox as a searchable application
+at Settings -> Search at Gnome controll center.
+
+Gnome shell search provider is active only when Firefox is running. When it's active
+you can see it as org.mozilla.Firefox.SearchProvider D-Bus service.
+
diff --git a/browser/components/shell/search-provider-files/firefox-search-provider.ini b/browser/components/shell/search-provider-files/firefox-search-provider.ini
new file mode 100644
index 0000000000..3868e3d528
--- /dev/null
+++ b/browser/components/shell/search-provider-files/firefox-search-provider.ini
@@ -0,0 +1,5 @@
+[Shell Search Provider]
+DesktopId=firefox.desktop
+BusName=org.mozilla.Firefox.SearchProvider
+ObjectPath=/org/mozilla/Firefox/SearchProvider
+Version=2
diff --git a/browser/components/shell/search-provider-files/firefox.desktop b/browser/components/shell/search-provider-files/firefox.desktop
new file mode 100644
index 0000000000..118aae6b73
--- /dev/null
+++ b/browser/components/shell/search-provider-files/firefox.desktop
@@ -0,0 +1,273 @@
+[Desktop Entry]
+Version=1.0
+Name=Firefox
+GenericName=Web Browser
+GenericName[ca]=Navegador web
+GenericName[cs]=Webový prohlížeč
+GenericName[es]=Navegador web
+GenericName[fa]=مرورگر اینترنتی
+GenericName[fi]=WWW-selain
+GenericName[fr]=Navigateur Web
+GenericName[hu]=Webböngésző
+GenericName[it]=Browser Web
+GenericName[ja]=ウェブ・ブラウザ
+GenericName[ko]=웹 브라우저
+GenericName[nb]=Nettleser
+GenericName[nl]=Webbrowser
+GenericName[nn]=Nettlesar
+GenericName[no]=Nettleser
+GenericName[pl]=Przeglądarka WWW
+GenericName[pt]=Navegador Web
+GenericName[pt_BR]=Navegador Web
+GenericName[sk]=Internetový prehliadač
+GenericName[sv]=Webbläsare
+Comment=Browse the Web
+Comment[ca]=Navegueu per el web
+Comment[cs]=Prohlížení stránek World Wide Webu
+Comment[de]=Im Internet surfen
+Comment[es]=Navegue por la web
+Comment[fa]=صفحات شبکه جهانی اینترنت را مرور نمایید
+Comment[fi]=Selaa Internetin WWW-sivuja
+Comment[fr]=Navigue sur Internet
+Comment[hu]=A világháló böngészése
+Comment[it]=Esplora il web
+Comment[ja]=ウェブを閲覧します
+Comment[ko]=웹을 돌아 다닙니다
+Comment[nb]=Surf på nettet
+Comment[nl]=Verken het internet
+Comment[nn]=Surf på nettet
+Comment[no]=Surf på nettet
+Comment[pl]=Przeglądanie stron WWW
+Comment[pt]=Navegue na Internet
+Comment[pt_BR]=Navegue na Internet
+Comment[sk]=Prehliadanie internetu
+Comment[sv]=Surfa på webben
+Exec=firefox %u
+Icon=firefox
+Terminal=false
+Type=Application
+MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;
+StartupNotify=true
+Categories=Network;WebBrowser;
+Keywords=web;browser;internet;
+Actions=new-window;new-private-window;
+
+X-Desktop-File-Install-Version=0.24
+
+[Desktop Action new-window]
+Name=Open a New Window
+Name[ach]=Dirica manyen
+Name[af]=Nuwe venster
+Name[an]=Nueva finestra
+Name[ar]=نافذة جديدة
+Name[as]=নতুন উইন্ডো
+Name[ast]=Ventana nueva
+Name[az]=Yeni Pəncərə
+Name[be]=Новае акно
+Name[bg]=Нов прозорец
+Name[bn_BD]=নতুন উইন্ডো (N)
+Name[bn_IN]=নতুন উইন্ডো
+Name[br]=Prenestr nevez
+Name[brx]=गोदान उइन्ड'(N)
+Name[bs]=Novi prozor
+Name[ca]=Finestra nova
+Name[cak]=K'ak'a' tzuwäch
+Name[cs]=Nové okno
+Name[cy]=Ffenestr Newydd
+Name[da]=Nyt vindue
+Name[de]=Neues Fenster
+Name[dsb]=Nowe wokno
+Name[el]=Νέο παράθυρο
+Name[en_GB]=New Window
+Name[en_US]=New Window
+Name[en_ZA]=New Window
+Name[eo]=Nova fenestro
+Name[es_AR]=Nueva ventana
+Name[es_CL]=Nueva ventana
+Name[es_ES]=Nueva ventana
+Name[es_MX]=Nueva ventana
+Name[et]=Uus aken
+Name[eu]=Leiho berria
+Name[fa]=پنجره جدید‌
+Name[ff]=Henorde Hesere
+Name[fi]=Uusi ikkuna
+Name[fr]=Nouvelle fenêtre
+Name[fy_NL]=Nij finster
+Name[ga_IE]=Fuinneog Nua
+Name[gd]=Uinneag ùr
+Name[gl]=Nova xanela
+Name[gn]=Ovetã pyahu
+Name[gu_IN]=નવી વિન્ડો
+Name[he]=חלון חדש
+Name[hi_IN]=नया विंडो
+Name[hr]=Novi prozor
+Name[hsb]=Nowe wokno
+Name[hu]=Új ablak
+Name[hy_AM]=Նոր Պատուհան
+Name[id]=Jendela Baru
+Name[is]=Nýr gluggi
+Name[it]=Nuova finestra
+Name[ja]=新しいウィンドウ
+Name[ja_JP-mac]=新規ウインドウ
+Name[ka]=ახალი ფანჯარა
+Name[kk]=Жаңа терезе
+Name[km]=បង្អួច​​​ថ្មី
+Name[kn]=ಹೊಸ ಕಿಟಕಿ
+Name[ko]=새 창
+Name[kok]=नवें जनेल
+Name[ks]=نئئ وِنڈو
+Name[lij]=Neuvo barcon
+Name[lo]=ຫນ້າຕ່າງໃຫມ່
+Name[lt]=Naujas langas
+Name[ltg]=Jauns lūgs
+Name[lv]=Jauns logs
+Name[mai]=नव विंडो
+Name[mk]=Нов прозорец
+Name[ml]=പുതിയ ജാലകം
+Name[mr]=नवीन पटल
+Name[ms]=Tetingkap Baru
+Name[my]=ဝင်းဒိုးအသစ်
+Name[nb_NO]=Nytt vindu
+Name[ne_NP]=नयाँ सञ्झ्याल
+Name[nl]=Nieuw venster
+Name[nn_NO]=Nytt vindauge
+Name[or]=ନୂତନ ୱିଣ୍ଡୋ
+Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ
+Name[pl]=Nowe okno
+Name[pt_BR]=Nova janela
+Name[pt_PT]=Nova janela
+Name[rm]=Nova fanestra
+Name[ro]=Fereastră nouă
+Name[ru]=Новое окно
+Name[sat]=नावा विंडो (N)
+Name[si]=නව කවුළුවක්
+Name[sk]=Nové okno
+Name[sl]=Novo okno
+Name[son]=Zanfun taaga
+Name[sq]=Dritare e Re
+Name[sr]=Нови прозор
+Name[sv_SE]=Nytt fönster
+Name[ta]=புதிய சாளரம்
+Name[te]=కొత్త విండో
+Name[th]=หน้าต่างใหม่
+Name[tr]=Yeni pencere
+Name[tsz]=Eraatarakua jimpani
+Name[uk]=Нове вікно
+Name[ur]=نیا دریچہ
+Name[uz]=Yangi oyna
+Name[vi]=Cửa sổ mới
+Name[wo]=Palanteer bu bees
+Name[xh]=Ifestile entsha
+Name[zh_CN]=新建窗口
+Name[zh_TW]=開新視窗
+
+
+Exec=firefox --new-window %u
+
+[Desktop Action new-private-window]
+Name=Open a New Private Window
+Name[ach]=Dirica manyen me mung
+Name[af]=Nuwe privaatvenster
+Name[an]=Nueva finestra privada
+Name[ar]=نافذة خاصة جديدة
+Name[as]=নতুন ব্যক্তিগত উইন্ডো
+Name[ast]=Ventana privada nueva
+Name[az]=Yeni Məxfi Pəncərə
+Name[be]=Новае акно адасаблення
+Name[bg]=Нов прозорец за поверително сърфиране
+Name[bn_BD]=নতুন ব্যক্তিগত উইন্ডো
+Name[bn_IN]=নতুন ব্যক্তিগত উইন্ডো
+Name[br]=Prenestr merdeiñ prevez nevez
+Name[brx]=गोदान प्राइभेट उइन्ड'
+Name[bs]=Novi privatni prozor
+Name[ca]=Finestra privada nova
+Name[cak]=K'ak'a' ichinan tzuwäch
+Name[cs]=Nové anonymní okno
+Name[cy]=Ffenestr Breifat Newydd
+Name[da]=Nyt privat vindue
+Name[de]=Neues privates Fenster
+Name[dsb]=Nowe priwatne wokno
+Name[el]=Νέο παράθυρο ιδιωτικής περιήγησης
+Name[en_GB]=New Private Window
+Name[en_US]=New Private Window
+Name[en_ZA]=New Private Window
+Name[eo]=Nova privata fenestro
+Name[es_AR]=Nueva ventana privada
+Name[es_CL]=Nueva ventana privada
+Name[es_ES]=Nueva ventana privada
+Name[es_MX]=Nueva ventana privada
+Name[et]=Uus privaatne aken
+Name[eu]=Leiho pribatu berria
+Name[fa]=پنجره ناشناس جدید
+Name[ff]=Henorde Suturo Hesere
+Name[fi]=Uusi yksityinen ikkuna
+Name[fr]=Nouvelle fenêtre de navigation privée
+Name[fy_NL]=Nij priveefinster
+Name[ga_IE]=Fuinneog Nua Phríobháideach
+Name[gd]=Uinneag phrìobhaideach ùr
+Name[gl]=Nova xanela privada
+Name[gn]=Ovetã ñemi pyahu
+Name[gu_IN]=નવી ખાનગી વિન્ડો
+Name[he]=חלון פרטי חדש
+Name[hi_IN]=नयी निजी विंडो
+Name[hr]=Novi privatni prozor
+Name[hsb]=Nowe priwatne wokno
+Name[hu]=Új privát ablak
+Name[hy_AM]=Սկսել Գաղտնի դիտարկում
+Name[id]=Jendela Mode Pribadi Baru
+Name[is]=Nýr huliðsgluggi
+Name[it]=Nuova finestra anonima
+Name[ja]=新しいプライベートウィンドウ
+Name[ja_JP-mac]=新規プライベートウインドウ
+Name[ka]=ახალი პირადი ფანჯარა
+Name[kk]=Жаңа жекелік терезе
+Name[km]=បង្អួច​ឯកជន​ថ្មី
+Name[kn]=ಹೊಸ ಖಾಸಗಿ ಕಿಟಕಿ
+Name[ko]=새 사생활 보호 모드
+Name[kok]=नवो खाजगी विंडो
+Name[ks]=نْو پرایوٹ وینڈو&amp;
+Name[lij]=Neuvo barcon privou
+Name[lo]=ເປີດຫນ້າຕ່າງສວນຕົວຂື້ນມາໃຫມ່
+Name[lt]=Naujas privataus naršymo langas
+Name[ltg]=Jauns privatais lūgs
+Name[lv]=Jauns privātais logs
+Name[mai]=नया निज विंडो (W)
+Name[mk]=Нов приватен прозорец
+Name[ml]=പുതിയ സ്വകാര്യ ജാലകം
+Name[mr]=नवीन वैयक्तिक पटल
+Name[ms]=Tetingkap Persendirian Baharu
+Name[my]=New Private Window
+Name[nb_NO]=Nytt privat vindu
+Name[ne_NP]=नयाँ निजी सञ्झ्याल
+Name[nl]=Nieuw privévenster
+Name[nn_NO]=Nytt privat vindauge
+Name[or]=ନୂତନ ବ୍ୟକ୍ତିଗତ ୱିଣ୍ଡୋ
+Name[pa_IN]=ਨਵੀਂ ਪ੍ਰਾਈਵੇਟ ਵਿੰਡੋ
+Name[pl]=Nowe okno prywatne
+Name[pt_BR]=Nova janela privativa
+Name[pt_PT]=Nova janela privada
+Name[rm]=Nova fanestra privata
+Name[ro]=Fereastră privată nouă
+Name[ru]=Новое приватное окно
+Name[sat]=नावा निजेराक् विंडो (W )
+Name[si]=නව පුද්ගලික කවුළුව (W)
+Name[sk]=Nové okno v režime Súkromné prehliadanie
+Name[sl]=Novo zasebno okno
+Name[son]=Sutura zanfun taaga
+Name[sq]=Dritare e Re Private
+Name[sr]=Нови приватан прозор
+Name[sv_SE]=Nytt privat fönster
+Name[ta]=புதிய தனிப்பட்ட சாளரம்
+Name[te]=కొత్త ఆంతరంగిక విండో
+Name[th]=หน้าต่างส่วนตัวใหม่
+Name[tr]=Yeni gizli pencere
+Name[tsz]=Juchiiti eraatarakua jimpani
+Name[uk]=Приватне вікно
+Name[ur]=نیا نجی دریچہ
+Name[uz]=Yangi maxfiy oyna
+Name[vi]=Cửa sổ riêng tư mới
+Name[wo]=Panlanteeru biir bu bees
+Name[xh]=Ifestile yangasese entsha
+Name[zh_CN]=新建隐私浏览窗口
+Name[zh_TW]=新增隱私視窗
+Exec=firefox --private-window %u
diff --git a/browser/components/shell/test/.eslintrc.js b/browser/components/shell/test/.eslintrc.js
new file mode 100644
index 0000000000..1779fd7f1c
--- /dev/null
+++ b/browser/components/shell/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/browser-test"],
+};
diff --git a/browser/components/shell/test/browser.ini b/browser/components/shell/test/browser.ini
new file mode 100644
index 0000000000..e567f0cf11
--- /dev/null
+++ b/browser/components/shell/test/browser.ini
@@ -0,0 +1,11 @@
+[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_setDesktopBackgroundPreview.js]
diff --git a/browser/components/shell/test/browser_1119088.js b/browser/components/shell/test/browser_1119088.js
new file mode 100644
index 0000000000..492af305d3
--- /dev/null
+++ b/browser/components/shell/test/browser_1119088.js
@@ -0,0 +1,172 @@
+// 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";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ FileUtils: "resource://gre/modules/FileUtils.jsm",
+});
+
+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 = Cc["@mozilla.org/uuid-generator;1"].getService(
+ Ci.nsIUUIDGenerator
+ );
+ 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_setDesktopBackgroundPreview.js b/browser/components/shell/test/browser_setDesktopBackgroundPreview.js
new file mode 100644
index 0000000000..a16fb30459
--- /dev/null
+++ b/browser/components/shell/test/browser_setDesktopBackgroundPreview.js
@@ -0,0 +1,66 @@
+/* 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
+ */
+
+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");
+ document.getElementById("context-setDesktopBackground").click();
+
+ // Need to explicitly close the menu (and wait for it), otherwise it fails
+ // verify/later tests
+ const menuClosed = BrowserTestUtils.waitForPopupEvent(menu, "hidden");
+ menu.hidePopup();
+
+ 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/chrome.ini b/browser/components/shell/test/chrome.ini
new file mode 100644
index 0000000000..8b4c9153f9
--- /dev/null
+++ b/browser/components/shell/test/chrome.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ headless.html
+ headless_redirect.html
+ headless_redirect.html^headers^
+
+[test_headless_screenshot.html]
+skip-if = (toolkit == 'android') || (os == 'win') #Bug 1429950 , Bug 1583315
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_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..ca7bc0b297
--- /dev/null
+++ b/browser/components/shell/test/mac_desktop_image.py
@@ -0,0 +1,171 @@
+#!/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.
+"""
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+#
+# 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
+
+import argparse
+import logging
+import os
+import sys
+
+
+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/test_headless_screenshot.html b/browser/components/shell/test/test_headless_screenshot.html
new file mode 100644
index 0000000000..4c5f5e4dcb
--- /dev/null
+++ b/browser/components/shell/test/test_headless_screenshot.html
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1378010
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1378010</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ const {Subprocess} = ChromeUtils.import("resource://gre/modules/Subprocess.jsm");
+ const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+ SimpleTest.requestLongerTimeout(2);
+
+ const screenshotPath = OS.Path.join(OS.Constants.Path.tmpDir, "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 = OS.Path.join(OS.Constants.Path.tmpDir, "headless_test_screenshot_profile");
+ const prefsPath = OS.Path.join(profilePath, mochiPrefsName);
+ const firefoxArgs = ["-profile", profilePath, "-no-remote"];
+
+ await OS.File.makeDir(profilePath);
+ await OS.File.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",
+ },
+ });
+ 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 OS.File.removeDir(profilePath);
+ }
+
+ async function testFileCreationPositive(args, path) {
+ await runFirefox(args);
+
+ let saved = await OS.File.exists(path);
+ ok(saved, "A screenshot should be saved as " + path);
+ if (!saved) {
+ return;
+ }
+
+ let info = await OS.File.stat(path);
+ ok(info.size > 0, "Screenshot should not be an empty file");
+ await OS.File.remove(path);
+ }
+
+ async function testFileCreationNegative(args, path) {
+ await runFirefox(args);
+
+ let saved = await OS.File.exists(path);
+ ok(!saved, "A screenshot should not be saved");
+ await OS.File.remove(path, { ignoreAbsent: true });
+ }
+
+ async function testWindowSizePositive(width, height) {
+ let size = width + "";
+ if (height) {
+ size += "," + height;
+ }
+
+ await runFirefox(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", screenshotPath, "-window-size", size]);
+
+ let saved = await OS.File.exists(screenshotPath);
+ ok(saved, "A screenshot should be saved in the tmp directory");
+ if (!saved) {
+ return;
+ }
+
+ let data = await OS.File.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.onloadend = 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 OS.File.remove(screenshotPath);
+ }
+
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ // Test all four basic variations of the "screenshot" argument
+ // when a file path is specified.
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", screenshotPath], screenshotPath);
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", `-screenshot=${screenshotPath}`], screenshotPath);
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "--screenshot", screenshotPath], screenshotPath);
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", `--screenshot=${screenshotPath}`], screenshotPath);
+
+ // Test variations of the "screenshot" argument when a file path
+ // isn't specified.
+ await testFileCreationPositive(["-screenshot", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html"], "screenshot.png");
+ await testFileCreationPositive(["http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot"], "screenshot.png");
+ await testFileCreationPositive(["--screenshot", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html"], "screenshot.png");
+ await testFileCreationPositive(["http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "--screenshot"], "screenshot.png");
+
+ // Test invalid URL arguments (either no argument or too many arguments).
+ await testFileCreationNegative(["-screenshot"], "screenshot.png");
+ await testFileCreationNegative(["http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "http://mochi.test:8888/headless.html", "-screenshot"], "screenshot.png");
+
+ // Test all four basic variations of the "window-size" argument.
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size", "800"], "screenshot.png");
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size=800"], "screenshot.png");
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "--window-size", "800"], "screenshot.png");
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "--window-size=800"], "screenshot.png");
+
+ // Test other variations of the "window-size" argument.
+ await testWindowSizePositive(800, 600);
+ await testWindowSizePositive(1234);
+ await testFileCreationNegative(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size", "hello"], "screenshot.png");
+ await testFileCreationNegative(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless.html", "-screenshot", "-window-size", "800,"], "screenshot.png");
+
+ // Test when the requested URL redirects
+ await testFileCreationPositive(["-url", "http://mochi.test:8888/chrome/browser/components/shell/test/headless_redirect.html", "-screenshot", screenshotPath], screenshotPath);
+
+ SimpleTest.finish();
+ })();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378010">Mozilla Bug 1378010</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
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..69e09db70a
--- /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_task(async function setup() {
+ 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..27dfb2a452
--- /dev/null
+++ b/browser/components/shell/test/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+firefox-appdir = browser
+
+[test_macOS_showSecurityPreferences.js]
+skip-if = toolkit != "cocoa"