summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/in-content
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/preferences/in-content')
-rw-r--r--browser/components/preferences/in-content/advanced.js770
-rw-r--r--browser/components/preferences/in-content/advanced.xul421
-rw-r--r--browser/components/preferences/in-content/applications.js1900
-rw-r--r--browser/components/preferences/in-content/applications.xul95
-rw-r--r--browser/components/preferences/in-content/containers.js73
-rw-r--r--browser/components/preferences/in-content/containers.xul54
-rw-r--r--browser/components/preferences/in-content/content.js294
-rw-r--r--browser/components/preferences/in-content/content.xul209
-rw-r--r--browser/components/preferences/in-content/jar.mn18
-rw-r--r--browser/components/preferences/in-content/main.js721
-rw-r--r--browser/components/preferences/in-content/main.xul301
-rw-r--r--browser/components/preferences/in-content/moz.build13
-rw-r--r--browser/components/preferences/in-content/preferences.js315
-rw-r--r--browser/components/preferences/in-content/preferences.xul224
-rw-r--r--browser/components/preferences/in-content/privacy.js712
-rw-r--r--browser/components/preferences/in-content/privacy.xul308
-rw-r--r--browser/components/preferences/in-content/search.js604
-rw-r--r--browser/components/preferences/in-content/search.xul86
-rw-r--r--browser/components/preferences/in-content/security.js302
-rw-r--r--browser/components/preferences/in-content/security.xul131
-rw-r--r--browser/components/preferences/in-content/subdialogs.js434
-rw-r--r--browser/components/preferences/in-content/sync.js673
-rw-r--r--browser/components/preferences/in-content/sync.xul359
-rw-r--r--browser/components/preferences/in-content/tests/.eslintrc.js7
-rw-r--r--browser/components/preferences/in-content/tests/browser.ini43
-rw-r--r--browser/components/preferences/in-content/tests/browser_advanced_update.js158
-rw-r--r--browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js76
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js24
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js43
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js92
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul33
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug410900.js46
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug705422.js144
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug731866.js52
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js52
-rw-r--r--browser/components/preferences/in-content/tests/browser_change_app_handler.js98
-rw-r--r--browser/components/preferences/in-content/tests/browser_connection.js99
-rw-r--r--browser/components/preferences/in-content/tests/browser_connection_bug388287.js125
-rw-r--r--browser/components/preferences/in-content/tests/browser_cookies_exceptions.js348
-rw-r--r--browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js103
-rw-r--r--browser/components/preferences/in-content/tests/browser_healthreport.js62
-rw-r--r--browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js20
-rw-r--r--browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js44
-rw-r--r--browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js45
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_1.js18
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_3.js17
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_4.js25
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_5.js17
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_8.js26
-rw-r--r--browser/components/preferences/in-content/tests/browser_proxy_backup.js65
-rw-r--r--browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js37
-rw-r--r--browser/components/preferences/in-content/tests/browser_searchsuggestions.js43
-rw-r--r--browser/components/preferences/in-content/tests/browser_security.js130
-rw-r--r--browser/components/preferences/in-content/tests/browser_subdialogs.js293
-rw-r--r--browser/components/preferences/in-content/tests/browser_telemetry.js52
-rw-r--r--browser/components/preferences/in-content/tests/head.js165
-rw-r--r--browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js330
-rw-r--r--browser/components/preferences/in-content/tests/subdialog.xul27
-rw-r--r--browser/components/preferences/in-content/tests/subdialog2.xul27
59 files changed, 12003 insertions, 0 deletions
diff --git a/browser/components/preferences/in-content/advanced.js b/browser/components/preferences/in-content/advanced.js
new file mode 100644
index 000000000..448a21dae
--- /dev/null
+++ b/browser/components/preferences/in-content/advanced.js
@@ -0,0 +1,770 @@
+/* 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/. */
+
+// Load DownloadUtils module for convertByteUnits
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
+
+var gAdvancedPane = {
+ _inited: false,
+
+ /**
+ * Brings the appropriate tab to the front and initializes various bits of UI.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gAdvancedPane));
+ }
+
+ this._inited = true;
+ var advancedPrefs = document.getElementById("advancedPrefs");
+
+ var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
+ if (preference.value !== null)
+ advancedPrefs.selectedIndex = preference.value;
+
+ if (AppConstants.MOZ_UPDATER) {
+ let onUnload = function () {
+ window.removeEventListener("unload", onUnload, false);
+ Services.prefs.removeObserver("app.update.", this);
+ }.bind(this);
+ window.addEventListener("unload", onUnload, false);
+ Services.prefs.addObserver("app.update.", this, false);
+ this.updateReadPrefs();
+ }
+ this.updateOfflineApps();
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ this.initSubmitCrashes();
+ }
+ this.initTelemetry();
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ this.initSubmitHealthReport();
+ }
+ this.updateOnScreenKeyboardVisibility();
+ this.updateCacheSizeInputField();
+ this.updateActualCacheSize();
+ this.updateActualAppCacheSize();
+
+ setEventListener("layers.acceleration.disabled", "change",
+ gAdvancedPane.updateHardwareAcceleration);
+ setEventListener("advancedPrefs", "select",
+ gAdvancedPane.tabSelectionChanged);
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ setEventListener("submitHealthReportBox", "command",
+ gAdvancedPane.updateSubmitHealthReport);
+ }
+
+ setEventListener("connectionSettings", "command",
+ gAdvancedPane.showConnections);
+ setEventListener("clearCacheButton", "command",
+ gAdvancedPane.clearCache);
+ setEventListener("clearOfflineAppCacheButton", "command",
+ gAdvancedPane.clearOfflineAppCache);
+ setEventListener("offlineNotifyExceptions", "command",
+ gAdvancedPane.showOfflineExceptions);
+ setEventListener("offlineAppsList", "select",
+ gAdvancedPane.offlineAppSelected);
+ let bundlePrefs = document.getElementById("bundlePreferences");
+ document.getElementById("offlineAppsList")
+ .style.height = bundlePrefs.getString("offlineAppsList.height");
+ setEventListener("offlineAppsListRemove", "command",
+ gAdvancedPane.removeOfflineApp);
+ if (AppConstants.MOZ_UPDATER) {
+ setEventListener("updateRadioGroup", "command",
+ gAdvancedPane.updateWritePrefs);
+ setEventListener("showUpdateHistory", "command",
+ gAdvancedPane.showUpdates);
+ }
+ setEventListener("viewCertificatesButton", "command",
+ gAdvancedPane.showCertificates);
+ setEventListener("viewSecurityDevicesButton", "command",
+ gAdvancedPane.showSecurityDevices);
+ setEventListener("cacheSize", "change",
+ gAdvancedPane.updateCacheSizePref);
+
+ if (AppConstants.MOZ_WIDGET_GTK) {
+ // GTK tabbox' allow the scroll wheel to change the selected tab,
+ // but we don't want this behavior for the in-content preferences.
+ let tabsElement = document.getElementById("tabsElement");
+ tabsElement.addEventListener("DOMMouseScroll", event => {
+ event.stopPropagation();
+ }, true);
+ }
+ },
+
+ /**
+ * Stores the identity of the current tab in preferences so that the selected
+ * tab can be persisted between openings of the preferences window.
+ */
+ tabSelectionChanged: function ()
+ {
+ if (!this._inited)
+ return;
+ var advancedPrefs = document.getElementById("advancedPrefs");
+ var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
+
+ // tabSelectionChanged gets called twice due to the selectedIndex being set
+ // by both the selectedItem and selectedPanel callstacks. This guard is used
+ // to prevent double-counting in Telemetry.
+ if (preference.valueFromPreferences != advancedPrefs.selectedIndex) {
+ Services.telemetry
+ .getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
+ .add(telemetryBucketForCategory("advanced"));
+ }
+
+ preference.valueFromPreferences = advancedPrefs.selectedIndex;
+ },
+
+ // GENERAL TAB
+
+ /*
+ * Preferences:
+ *
+ * accessibility.browsewithcaret
+ * - true enables keyboard navigation and selection within web pages using a
+ * visible caret, false uses normal keyboard navigation with no caret
+ * accessibility.typeaheadfind
+ * - when set to true, typing outside text areas and input boxes will
+ * automatically start searching for what's typed within the current
+ * document; when set to false, no search action happens
+ * ui.osk.enabled
+ * - when set to true, subject to other conditions, we may sometimes invoke
+ * an on-screen keyboard when a text input is focused.
+ * (Currently Windows-only, and depending on prefs, may be Windows-8-only)
+ * general.autoScroll
+ * - when set to true, clicking the scroll wheel on the mouse activates a
+ * mouse mode where moving the mouse down scrolls the document downward with
+ * speed correlated with the distance of the cursor from the original
+ * position at which the click occurred (and likewise with movement upward);
+ * if false, this behavior is disabled
+ * general.smoothScroll
+ * - set to true to enable finer page scrolling than line-by-line on page-up,
+ * page-down, and other such page movements
+ * layout.spellcheckDefault
+ * - an integer:
+ * 0 disables spellchecking
+ * 1 enables spellchecking, but only for multiline text fields
+ * 2 enables spellchecking for all text fields
+ */
+
+ /**
+ * Stores the original value of the spellchecking preference to enable proper
+ * restoration if unchanged (since we're mapping a tristate onto a checkbox).
+ */
+ _storedSpellCheck: 0,
+
+ /**
+ * Returns true if any spellchecking is enabled and false otherwise, caching
+ * the current value to enable proper pref restoration if the checkbox is
+ * never changed.
+ */
+ readCheckSpelling: function ()
+ {
+ var pref = document.getElementById("layout.spellcheckDefault");
+ this._storedSpellCheck = pref.value;
+
+ return (pref.value != 0);
+ },
+
+ /**
+ * Returns the value of the spellchecking preference represented by UI,
+ * preserving the preference's "hidden" value if the preference is
+ * unchanged and represents a value not strictly allowed in UI.
+ */
+ writeCheckSpelling: function ()
+ {
+ var checkbox = document.getElementById("checkSpelling");
+ if (checkbox.checked) {
+ if (this._storedSpellCheck == 2) {
+ return 2;
+ }
+ return 1;
+ }
+ return 0;
+ },
+
+ /**
+ * security.OCSP.enabled is an integer value for legacy reasons.
+ * A value of 1 means OCSP is enabled. Any other value means it is disabled.
+ */
+ readEnableOCSP: function ()
+ {
+ var preference = document.getElementById("security.OCSP.enabled");
+ // This is the case if the preference is the default value.
+ if (preference.value === undefined) {
+ return true;
+ }
+ return preference.value == 1;
+ },
+
+ /**
+ * See documentation for readEnableOCSP.
+ */
+ writeEnableOCSP: function ()
+ {
+ var checkbox = document.getElementById("enableOCSP");
+ return checkbox.checked ? 1 : 0;
+ },
+
+ /**
+ * When the user toggles the layers.acceleration.disabled pref,
+ * sync its new value to the gfx.direct2d.disabled pref too.
+ */
+ updateHardwareAcceleration: function()
+ {
+ if (AppConstants.platform = "win") {
+ var fromPref = document.getElementById("layers.acceleration.disabled");
+ var toPref = document.getElementById("gfx.direct2d.disabled");
+ toPref.value = fromPref.value;
+ }
+ },
+
+ // DATA CHOICES TAB
+
+ /**
+ * Set up or hide the Learn More links for various data collection options
+ */
+ _setupLearnMoreLink: function (pref, element) {
+ // set up the Learn More link with the correct URL
+ let url = Services.prefs.getCharPref(pref);
+ let el = document.getElementById(element);
+
+ if (url) {
+ el.setAttribute("href", url);
+ } else {
+ el.setAttribute("hidden", "true");
+ }
+ },
+
+ /**
+ *
+ */
+ initSubmitCrashes: function ()
+ {
+ this._setupLearnMoreLink("toolkit.crashreporter.infoURL",
+ "crashReporterLearnMore");
+ },
+
+ /**
+ * The preference/checkbox is configured in XUL.
+ *
+ * In all cases, set up the Learn More link sanely.
+ */
+ initTelemetry: function ()
+ {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ this._setupLearnMoreLink("toolkit.telemetry.infoURL", "telemetryLearnMore");
+ }
+ },
+
+ /**
+ * Set the status of the telemetry controls based on the input argument.
+ * @param {Boolean} aEnabled False disables the controls, true enables them.
+ */
+ setTelemetrySectionEnabled: function (aEnabled)
+ {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ // If FHR is disabled, additional data sharing should be disabled as well.
+ let disabled = !aEnabled;
+ document.getElementById("submitTelemetryBox").disabled = disabled;
+ if (disabled) {
+ // If we disable FHR, untick the telemetry checkbox.
+ Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
+ }
+ document.getElementById("telemetryDataDesc").disabled = disabled;
+ }
+ },
+
+ /**
+ * Initialize the health report service reference and checkbox.
+ */
+ initSubmitHealthReport: function () {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
+
+ let checkbox = document.getElementById("submitHealthReportBox");
+
+ if (Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED)) {
+ checkbox.setAttribute("disabled", "true");
+ return;
+ }
+
+ checkbox.checked = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED);
+ this.setTelemetrySectionEnabled(checkbox.checked);
+ }
+ },
+
+ /**
+ * Update the health report preference with state from checkbox.
+ */
+ updateSubmitHealthReport: function () {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ let checkbox = document.getElementById("submitHealthReportBox");
+ Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
+ this.setTelemetrySectionEnabled(checkbox.checked);
+ }
+ },
+
+ updateOnScreenKeyboardVisibility() {
+ if (AppConstants.platform == "win") {
+ let minVersion = Services.prefs.getBoolPref("ui.osk.require_win10") ? 10 : 6.2;
+ if (Services.vc.compare(Services.sysinfo.getProperty("version"), minVersion) >= 0) {
+ document.getElementById("useOnScreenKeyboard").hidden = false;
+ }
+ }
+ },
+
+ // NETWORK TAB
+
+ /*
+ * Preferences:
+ *
+ * browser.cache.disk.capacity
+ * - the size of the browser cache in KB
+ * - Only used if browser.cache.disk.smart_size.enabled is disabled
+ */
+
+ /**
+ * Displays a dialog in which proxy settings may be changed.
+ */
+ showConnections: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/connection.xul");
+ },
+
+ // Retrieves the amount of space currently used by disk cache
+ updateActualCacheSize: function ()
+ {
+ var actualSizeLabel = document.getElementById("actualDiskCacheSize");
+ var prefStrBundle = document.getElementById("bundlePreferences");
+
+ // Needs to root the observer since cache service keeps only a weak reference.
+ this.observer = {
+ onNetworkCacheDiskConsumption: function(consumption) {
+ var size = DownloadUtils.convertByteUnits(consumption);
+ // The XBL binding for the string bundle may have been destroyed if
+ // the page was closed before this callback was executed.
+ if (!prefStrBundle.getFormattedString) {
+ return;
+ }
+ actualSizeLabel.value = prefStrBundle.getFormattedString("actualDiskCacheSize", size);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsICacheStorageConsumptionObserver,
+ Components.interfaces.nsISupportsWeakReference
+ ])
+ };
+
+ actualSizeLabel.value = prefStrBundle.getString("actualDiskCacheSizeCalculated");
+
+ try {
+ var cacheService =
+ Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ cacheService.asyncGetDiskConsumption(this.observer);
+ } catch (e) {}
+ },
+
+ // Retrieves the amount of space currently used by offline cache
+ updateActualAppCacheSize: function ()
+ {
+ var visitor = {
+ onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
+ {
+ var actualSizeLabel = document.getElementById("actualAppCacheSize");
+ var sizeStrings = DownloadUtils.convertByteUnits(aConsumption);
+ var prefStrBundle = document.getElementById("bundlePreferences");
+ // The XBL binding for the string bundle may have been destroyed if
+ // the page was closed before this callback was executed.
+ if (!prefStrBundle.getFormattedString) {
+ return;
+ }
+ var sizeStr = prefStrBundle.getFormattedString("actualAppCacheSize", sizeStrings);
+ actualSizeLabel.value = sizeStr;
+ }
+ };
+
+ try {
+ var cacheService =
+ Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ var storage = cacheService.appCacheStorage(LoadContextInfo.default, null);
+ storage.asyncVisitStorage(visitor, false);
+ } catch (e) {}
+ },
+
+ updateCacheSizeUI: function (smartSizeEnabled)
+ {
+ document.getElementById("useCacheBefore").disabled = smartSizeEnabled;
+ document.getElementById("cacheSize").disabled = smartSizeEnabled;
+ document.getElementById("useCacheAfter").disabled = smartSizeEnabled;
+ },
+
+ readSmartSizeEnabled: function ()
+ {
+ // The smart_size.enabled preference element is inverted="true", so its
+ // value is the opposite of the actual pref value
+ var disabled = document.getElementById("browser.cache.disk.smart_size.enabled").value;
+ this.updateCacheSizeUI(!disabled);
+ },
+
+ /**
+ * Converts the cache size from units of KB to units of MB and stores it in
+ * the textbox element.
+ */
+ updateCacheSizeInputField()
+ {
+ let cacheSizeElem = document.getElementById("cacheSize");
+ let cachePref = document.getElementById("browser.cache.disk.capacity");
+ cacheSizeElem.value = cachePref.value / 1024;
+ if (cachePref.locked)
+ cacheSizeElem.disabled = true;
+ },
+
+ /**
+ * Updates the cache size preference once user enters a new value.
+ * We intentionally do not set preference="browser.cache.disk.capacity"
+ * onto the textbox directly, as that would update the pref at each keypress
+ * not only after the final value is entered.
+ */
+ updateCacheSizePref()
+ {
+ let cacheSizeElem = document.getElementById("cacheSize");
+ let cachePref = document.getElementById("browser.cache.disk.capacity");
+ // Converts the cache size as specified in UI (in MB) to KB.
+ let intValue = parseInt(cacheSizeElem.value, 10);
+ cachePref.value = isNaN(intValue) ? 0 : intValue * 1024;
+ },
+
+ /**
+ * Clears the cache.
+ */
+ clearCache: function ()
+ {
+ try {
+ var cache = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ cache.clear();
+ } catch (ex) {}
+ this.updateActualCacheSize();
+ },
+
+ /**
+ * Clears the application cache.
+ */
+ clearOfflineAppCache: function ()
+ {
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ OfflineAppCacheHelper.clear();
+
+ this.updateActualAppCacheSize();
+ this.updateOfflineApps();
+ },
+
+ readOfflineNotify: function()
+ {
+ var pref = document.getElementById("browser.offline-apps.notify");
+ var button = document.getElementById("offlineNotifyExceptions");
+ button.disabled = !pref.value;
+ return pref.value;
+ },
+
+ showOfflineExceptions: function()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : false,
+ prefilledHost : "",
+ permissionType : "offline-app",
+ manageCapability : Components.interfaces.nsIPermissionManager.DENY_ACTION,
+ windowTitle : bundlePreferences.getString("offlinepermissionstitle"),
+ introText : bundlePreferences.getString("offlinepermissionstext") };
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ // XXX: duplicated in browser.js
+ _getOfflineAppUsage(perm, groups) {
+ let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups) {
+ try {
+ groups = cacheService.getGroups();
+ } catch (ex) {
+ return 0;
+ }
+ }
+
+ let usage = 0;
+ for (let group of groups) {
+ let uri = Services.io.newURI(group, null, null);
+ if (perm.matchesURI(uri, true)) {
+ let cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ /**
+ * Updates the list of offline applications
+ */
+ updateOfflineApps: function ()
+ {
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+
+ var list = document.getElementById("offlineAppsList");
+ while (list.firstChild) {
+ list.removeChild(list.firstChild);
+ }
+
+ var groups;
+ try {
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ groups = cacheService.getGroups();
+ } catch (e) {
+ return;
+ }
+
+ var bundle = document.getElementById("bundlePreferences");
+
+ var enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ var perm = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ if (perm.type == "offline-app" &&
+ perm.capability != Components.interfaces.nsIPermissionManager.DEFAULT_ACTION &&
+ perm.capability != Components.interfaces.nsIPermissionManager.DENY_ACTION) {
+ var row = document.createElement("listitem");
+ row.id = "";
+ row.className = "offlineapp";
+ row.setAttribute("origin", perm.principal.origin);
+ var converted = DownloadUtils.
+ convertByteUnits(this._getOfflineAppUsage(perm, groups));
+ row.setAttribute("usage",
+ bundle.getFormattedString("offlineAppUsage",
+ converted));
+ list.appendChild(row);
+ }
+ }
+ },
+
+ offlineAppSelected: function()
+ {
+ var removeButton = document.getElementById("offlineAppsListRemove");
+ var list = document.getElementById("offlineAppsList");
+ if (list.selectedItem) {
+ removeButton.setAttribute("disabled", "false");
+ } else {
+ removeButton.setAttribute("disabled", "true");
+ }
+ },
+
+ removeOfflineApp: function()
+ {
+ var list = document.getElementById("offlineAppsList");
+ var item = list.selectedItem;
+ var origin = item.getAttribute("origin");
+ var principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+
+ var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var flags = prompts.BUTTON_TITLE_IS_STRING * prompts.BUTTON_POS_0 +
+ prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1;
+
+ var bundle = document.getElementById("bundlePreferences");
+ var title = bundle.getString("offlineAppRemoveTitle");
+ var prompt = bundle.getFormattedString("offlineAppRemovePrompt", [principal.URI.prePath]);
+ var confirm = bundle.getString("offlineAppRemoveConfirm");
+ var result = prompts.confirmEx(window, title, prompt, flags, confirm,
+ null, null, null, {});
+ if (result != 0)
+ return;
+
+ // get the permission
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+ var perm = pm.getPermissionObject(principal, "offline-app", true);
+ if (perm) {
+ // clear offline cache entries
+ try {
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ var groups = cacheService.getGroups();
+ for (var i = 0; i < groups.length; i++) {
+ var uri = Services.io.newURI(groups[i], null, null);
+ if (perm.matchesURI(uri, true)) {
+ var cache = cacheService.getActiveCache(groups[i]);
+ cache.discard();
+ }
+ }
+ } catch (e) {}
+
+ pm.removePermission(perm);
+ }
+ list.removeChild(item);
+ gAdvancedPane.offlineAppSelected();
+ this.updateActualAppCacheSize();
+ },
+
+ // UPDATE TAB
+
+ /*
+ * Preferences:
+ *
+ * app.update.enabled
+ * - true if updates to the application are enabled, false otherwise
+ * app.update.auto
+ * - true if updates should be automatically downloaded and installed and
+ * false if the user should be asked what he wants to do when an update is
+ * available
+ * extensions.update.enabled
+ * - true if updates to extensions and themes are enabled, false otherwise
+ * browser.search.update
+ * - true if updates to search engines are enabled, false otherwise
+ */
+
+ /**
+ * Selects the item of the radiogroup based on the pref values and locked
+ * states.
+ *
+ * UI state matrix for update preference conditions
+ *
+ * UI Components: Preferences
+ * Radiogroup i = app.update.enabled
+ * ii = app.update.auto
+ *
+ * Disabled states:
+ * Element pref value locked disabled
+ * radiogroup i t/f f false
+ * i t/f *t* *true*
+ * ii t/f f false
+ * ii t/f *t* *true*
+ */
+ updateReadPrefs: function ()
+ {
+ if (AppConstants.MOZ_UPDATER) {
+ var enabledPref = document.getElementById("app.update.enabled");
+ var autoPref = document.getElementById("app.update.auto");
+ var radiogroup = document.getElementById("updateRadioGroup");
+
+ if (!enabledPref.value) // Don't care for autoPref.value in this case.
+ radiogroup.value="manual"; // 3. Never check for updates.
+ else if (autoPref.value) // enabledPref.value && autoPref.value
+ radiogroup.value="auto"; // 1. Automatically install updates
+ else // enabledPref.value && !autoPref.value
+ radiogroup.value="checkOnly"; // 2. Check, but let me choose
+
+ var canCheck = Components.classes["@mozilla.org/updates/update-service;1"].
+ getService(Components.interfaces.nsIApplicationUpdateService).
+ canCheckForUpdates;
+ // canCheck is false if the enabledPref is false and locked,
+ // or the binary platform or OS version is not known.
+ // A locked pref is sufficient to disable the radiogroup.
+ radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
+
+ if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
+ // Check to see if the maintenance service is installed.
+ // If it is don't show the preference at all.
+ var installed;
+ try {
+ var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
+ .createInstance(Components.interfaces.nsIWindowsRegKey);
+ wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
+ "SOFTWARE\\Mozilla\\MaintenanceService",
+ wrk.ACCESS_READ | wrk.WOW64_64);
+ installed = wrk.readIntValue("Installed");
+ wrk.close();
+ } catch (e) {
+ }
+ if (installed != 1) {
+ document.getElementById("useService").hidden = true;
+ }
+ }
+ }
+ },
+
+ /**
+ * Sets the pref values based on the selected item of the radiogroup.
+ */
+ updateWritePrefs: function ()
+ {
+ if (AppConstants.MOZ_UPDATER) {
+ var enabledPref = document.getElementById("app.update.enabled");
+ var autoPref = document.getElementById("app.update.auto");
+ var radiogroup = document.getElementById("updateRadioGroup");
+ switch (radiogroup.value) {
+ case "auto": // 1. Automatically install updates for Desktop only
+ enabledPref.value = true;
+ autoPref.value = true;
+ break;
+ case "checkOnly": // 2. Check, but let me choose
+ enabledPref.value = true;
+ autoPref.value = false;
+ break;
+ case "manual": // 3. Never check for updates.
+ enabledPref.value = false;
+ autoPref.value = false;
+ }
+ }
+ },
+
+ /**
+ * Displays the history of installed updates.
+ */
+ showUpdates: function ()
+ {
+ gSubDialog.open("chrome://mozapps/content/update/history.xul");
+ },
+
+ // ENCRYPTION TAB
+
+ /*
+ * Preferences:
+ *
+ * security.default_personal_cert
+ * - a string:
+ * "Select Automatically" select a certificate automatically when a site
+ * requests one
+ * "Ask Every Time" present a dialog to the user so he can select
+ * the certificate to use on a site which
+ * requests one
+ */
+
+ /**
+ * Displays the user's certificates and associated options.
+ */
+ showCertificates: function ()
+ {
+ gSubDialog.open("chrome://pippki/content/certManager.xul");
+ },
+
+ /**
+ * Displays a dialog from which the user can manage his security devices.
+ */
+ showSecurityDevices: function ()
+ {
+ gSubDialog.open("chrome://pippki/content/device_manager.xul");
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ if (AppConstants.MOZ_UPDATER) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ this.updateReadPrefs();
+ break;
+ }
+ }
+ },
+};
diff --git a/browser/components/preferences/in-content/advanced.xul b/browser/components/preferences/in-content/advanced.xul
new file mode 100644
index 000000000..facaaeaa9
--- /dev/null
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -0,0 +1,421 @@
+# 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/.
+
+<!-- Advanced panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/advanced.js"/>
+
+<preferences id="advancedPreferences" hidden="true" data-category="paneAdvanced">
+ <preference id="browser.preferences.advanced.selectedTabIndex"
+ name="browser.preferences.advanced.selectedTabIndex"
+ type="int"/>
+
+ <!-- General tab -->
+ <preference id="accessibility.browsewithcaret"
+ name="accessibility.browsewithcaret"
+ type="bool"/>
+ <preference id="accessibility.typeaheadfind"
+ name="accessibility.typeaheadfind"
+ type="bool"/>
+ <preference id="accessibility.blockautorefresh"
+ name="accessibility.blockautorefresh"
+ type="bool"/>
+#ifdef XP_WIN
+ <preference id="ui.osk.enabled"
+ name="ui.osk.enabled"
+ type="bool"/>
+#endif
+
+ <preference id="general.autoScroll"
+ name="general.autoScroll"
+ type="bool"/>
+ <preference id="general.smoothScroll"
+ name="general.smoothScroll"
+ type="bool"/>
+ <preference id="layers.acceleration.disabled"
+ name="layers.acceleration.disabled"
+ type="bool"
+ inverted="true"/>
+#ifdef XP_WIN
+ <preference id="gfx.direct2d.disabled"
+ name="gfx.direct2d.disabled"
+ type="bool"
+ inverted="true"/>
+#endif
+ <preference id="layout.spellcheckDefault"
+ name="layout.spellcheckDefault"
+ type="int"/>
+
+#ifdef MOZ_TELEMETRY_REPORTING
+ <preference id="toolkit.telemetry.enabled"
+ name="toolkit.telemetry.enabled"
+ type="bool"/>
+#endif
+
+ <!-- Data Choices tab -->
+#ifdef MOZ_CRASHREPORTER
+ <preference id="browser.crashReports.unsubmittedCheck.autoSubmit2"
+ name="browser.crashReports.unsubmittedCheck.autoSubmit2"
+ type="bool"/>
+#endif
+
+ <!-- Network tab -->
+ <preference id="browser.cache.disk.capacity"
+ name="browser.cache.disk.capacity"
+ type="int"/>
+ <preference id="browser.offline-apps.notify"
+ name="browser.offline-apps.notify"
+ type="bool"/>
+
+ <preference id="browser.cache.disk.smart_size.enabled"
+ name="browser.cache.disk.smart_size.enabled"
+ inverted="true"
+ type="bool"/>
+
+ <!-- Update tab -->
+#ifdef MOZ_UPDATER
+ <preference id="app.update.enabled"
+ name="app.update.enabled"
+ type="bool"/>
+ <preference id="app.update.auto"
+ name="app.update.auto"
+ type="bool"/>
+
+ <preference id="app.update.disable_button.showUpdateHistory"
+ name="app.update.disable_button.showUpdateHistory"
+ type="bool"/>
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+ <preference id="app.update.service.enabled"
+ name="app.update.service.enabled"
+ type="bool"/>
+#endif
+#endif
+
+ <preference id="browser.search.update"
+ name="browser.search.update"
+ type="bool"/>
+
+ <!-- Certificates tab -->
+ <preference id="security.default_personal_cert"
+ name="security.default_personal_cert"
+ type="string"/>
+
+ <preference id="security.disable_button.openCertManager"
+ name="security.disable_button.openCertManager"
+ type="bool"/>
+
+ <preference id="security.disable_button.openDeviceManager"
+ name="security.disable_button.openDeviceManager"
+ type="bool"/>
+
+ <preference id="security.OCSP.enabled"
+ name="security.OCSP.enabled"
+ type="int"/>
+</preferences>
+
+#ifdef HAVE_SHELL_SERVICE
+ <stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
+ <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
+#endif
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+<hbox id="header-advanced"
+ class="header"
+ hidden="true"
+ data-category="paneAdvanced">
+ <label class="header-name" flex="1">&paneAdvanced.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<tabbox id="advancedPrefs"
+ handleCtrlTab="false"
+ handleCtrlPageUpDown="false"
+ flex="1"
+ data-category="paneAdvanced"
+ hidden="true">
+
+ <tabs id="tabsElement">
+ <tab id="generalTab" label="&generalTab.label;"/>
+#ifdef MOZ_DATA_REPORTING
+ <tab id="dataChoicesTab" label="&dataChoicesTab.label;"/>
+#endif
+ <tab id="networkTab" label="&networkTab.label;"/>
+ <tab id="updateTab" label="&updateTab.label;"/>
+ <tab id="encryptionTab" label="&certificateTab.label;"/>
+ </tabs>
+
+ <tabpanels flex="1">
+
+ <!-- General -->
+ <tabpanel id="generalPanel" orient="vertical">
+ <!-- Accessibility -->
+ <groupbox id="accessibilityGroup" align="start">
+ <caption><label>&accessibility.label;</label></caption>
+
+#ifdef XP_WIN
+ <checkbox id="useOnScreenKeyboard"
+ hidden="true"
+ label="&useOnScreenKeyboard.label;"
+ accesskey="&useOnScreenKeyboard.accesskey;"
+ preference="ui.osk.enabled"/>
+#endif
+ <checkbox id="useCursorNavigation"
+ label="&useCursorNavigation.label;"
+ accesskey="&useCursorNavigation.accesskey;"
+ preference="accessibility.browsewithcaret"/>
+ <checkbox id="searchStartTyping"
+ label="&searchStartTyping.label;"
+ accesskey="&searchStartTyping.accesskey;"
+ preference="accessibility.typeaheadfind"/>
+ <checkbox id="blockAutoRefresh"
+ label="&blockAutoRefresh.label;"
+ accesskey="&blockAutoRefresh.accesskey;"
+ preference="accessibility.blockautorefresh"/>
+ </groupbox>
+ <!-- Browsing -->
+ <groupbox id="browsingGroup" align="start">
+ <caption><label>&browsing.label;</label></caption>
+
+ <checkbox id="useAutoScroll"
+ label="&useAutoScroll.label;"
+ accesskey="&useAutoScroll.accesskey;"
+ preference="general.autoScroll"/>
+ <checkbox id="useSmoothScrolling"
+ label="&useSmoothScrolling.label;"
+ accesskey="&useSmoothScrolling.accesskey;"
+ preference="general.smoothScroll"/>
+ <checkbox id="allowHWAccel"
+ label="&allowHWAccel.label;"
+ accesskey="&allowHWAccel.accesskey;"
+ preference="layers.acceleration.disabled"/>
+ <checkbox id="checkSpelling"
+ label="&checkSpelling.label;"
+ accesskey="&checkSpelling.accesskey;"
+ onsyncfrompreference="return gAdvancedPane.readCheckSpelling();"
+ onsynctopreference="return gAdvancedPane.writeCheckSpelling();"
+ preference="layout.spellcheckDefault"/>
+ </groupbox>
+ </tabpanel>
+#ifdef MOZ_DATA_REPORTING
+ <!-- Data Choices -->
+ <tabpanel id="dataChoicesPanel" orient="vertical">
+#ifdef MOZ_TELEMETRY_REPORTING
+ <groupbox>
+ <caption>
+ <checkbox id="submitHealthReportBox" label="&enableHealthReport.label;"
+ accesskey="&enableHealthReport.accesskey;"/>
+ </caption>
+ <vbox>
+ <hbox class="indent">
+ <label flex="1">&healthReportDesc.label;</label>
+ <spacer flex="10"/>
+ <label id="FHRLearnMore"
+ class="text-link">&healthReportLearnMore.label;</label>
+ </hbox>
+ <hbox class="indent">
+ <groupbox flex="1">
+ <caption>
+ <checkbox id="submitTelemetryBox" preference="toolkit.telemetry.enabled"
+ label="&enableTelemetryData.label;"
+ accesskey="&enableTelemetryData.accesskey;"/>
+ </caption>
+ <hbox class="indent">
+ <label id="telemetryDataDesc" flex="1">&telemetryDesc.label;</label>
+ <spacer flex="10"/>
+ <label id="telemetryLearnMore"
+ class="text-link">&telemetryLearnMore.label;</label>
+ </hbox>
+ </groupbox>
+ </hbox>
+ </vbox>
+ </groupbox>
+#endif
+#ifdef MOZ_CRASHREPORTER
+ <groupbox>
+ <caption>
+ <checkbox id="automaticallySubmitCrashesBox"
+ preference="browser.crashReports.unsubmittedCheck.autoSubmit2"
+ label="&alwaysSubmitCrashReports.label;"
+ accesskey="&alwaysSubmitCrashReports.accesskey;"/>
+ </caption>
+ <hbox class="indent">
+ <label flex="1">&crashReporterDesc2.label;</label>
+ <spacer flex="10"/>
+ <label id="crashReporterLearnMore"
+ class="text-link">&crashReporterLearnMore.label;</label>
+ </hbox>
+ </groupbox>
+#endif
+ </tabpanel>
+#endif
+
+ <!-- Network -->
+ <tabpanel id="networkPanel" orient="vertical">
+
+ <!-- Connection -->
+ <groupbox id="connectionGroup">
+ <caption><label>&connection.label;</label></caption>
+
+ <hbox align="center">
+ <description flex="1" control="connectionSettings">&connectionDesc.label;</description>
+ <button id="connectionSettings" icon="network" label="&connectionSettings.label;"
+ accesskey="&connectionSettings.accesskey;"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Cache -->
+ <groupbox id="cacheGroup">
+ <caption><label>&httpCache.label;</label></caption>
+
+ <hbox align="center">
+ <label id="actualDiskCacheSize" flex="1"/>
+ <button id="clearCacheButton" icon="clear"
+ label="&clearCacheNow.label;" accesskey="&clearCacheNow.accesskey;"/>
+ </hbox>
+ <hbox>
+ <checkbox preference="browser.cache.disk.smart_size.enabled"
+ id="allowSmartSize"
+ onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();"
+ label="&overrideSmartCacheSize.label;"
+ accesskey="&overrideSmartCacheSize.accesskey;"/>
+ </hbox>
+ <hbox align="center" class="indent">
+ <label id="useCacheBefore" control="cacheSize"
+ accesskey="&limitCacheSizeBefore.accesskey;">
+ &limitCacheSizeBefore.label;
+ </label>
+ <textbox id="cacheSize" type="number" size="4" max="1024"
+ aria-labelledby="useCacheBefore cacheSize useCacheAfter"/>
+ <label id="useCacheAfter" flex="1">&limitCacheSizeAfter.label;</label>
+ </hbox>
+ </groupbox>
+
+ <!-- Offline apps -->
+ <groupbox id="offlineGroup">
+ <caption><label>&offlineStorage2.label;</label></caption>
+
+ <hbox align="center">
+ <label id="actualAppCacheSize" flex="1"/>
+ <button id="clearOfflineAppCacheButton" icon="clear"
+ label="&clearOfflineAppCacheNow.label;" accesskey="&clearOfflineAppCacheNow.accesskey;"/>
+ </hbox>
+ <hbox align="center">
+ <checkbox id="offlineNotify"
+ label="&offlineNotify.label;" accesskey="&offlineNotify.accesskey;"
+ preference="browser.offline-apps.notify"
+ onsyncfrompreference="return gAdvancedPane.readOfflineNotify();"/>
+ <spacer flex="1"/>
+ <button id="offlineNotifyExceptions"
+ label="&offlineNotifyExceptions.label;"
+ accesskey="&offlineNotifyExceptions.accesskey;"/>
+ </hbox>
+ <hbox>
+ <vbox flex="1">
+ <label id="offlineAppsListLabel">&offlineAppsList2.label;</label>
+ <listbox id="offlineAppsList"
+ flex="1"
+ aria-labelledby="offlineAppsListLabel">
+ </listbox>
+ </vbox>
+ <vbox pack="end">
+ <button id="offlineAppsListRemove"
+ disabled="true"
+ label="&offlineAppsListRemove.label;"
+ accesskey="&offlineAppsListRemove.accesskey;"/>
+ </vbox>
+ </hbox>
+ </groupbox>
+ </tabpanel>
+
+ <!-- Update -->
+ <tabpanel id="updatePanel" orient="vertical">
+#ifdef MOZ_UPDATER
+ <groupbox id="updateApp" align="start">
+ <caption><label>&updateApp.label;</label></caption>
+ <radiogroup id="updateRadioGroup" align="start">
+ <radio id="autoDesktop"
+ value="auto"
+ label="&updateAuto1.label;"
+ accesskey="&updateAuto1.accesskey;"/>
+ <radio value="checkOnly"
+ label="&updateCheck.label;"
+ accesskey="&updateCheck.accesskey;"/>
+ <radio value="manual"
+ label="&updateManual.label;"
+ accesskey="&updateManual.accesskey;"/>
+ </radiogroup>
+ <separator class="thin"/>
+ <hbox>
+ <button id="showUpdateHistory"
+ label="&updateHistory.label;"
+ accesskey="&updateHistory.accesskey;"
+ preference="app.update.disable_button.showUpdateHistory"/>
+ </hbox>
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+ <checkbox id="useService"
+ label="&useService.label;"
+ accesskey="&useService.accesskey;"
+ preference="app.update.service.enabled"/>
+#endif
+ </groupbox>
+#endif
+ <groupbox id="updateOthers" align="start">
+ <caption><label>&updateOthers.label;</label></caption>
+ <checkbox id="enableSearchUpdate"
+ label="&enableSearchUpdate.label;"
+ accesskey="&enableSearchUpdate.accesskey;"
+ preference="browser.search.update"/>
+ </groupbox>
+ </tabpanel>
+
+ <!-- Certificates -->
+ <tabpanel id="encryptionPanel" orient="vertical">
+ <groupbox id="certSelection" align="start">
+ <caption><label>&certSelection.label;</label></caption>
+ <description id="CertSelectionDesc" control="certSelection">&certSelection.description;</description>
+
+ <!--
+ The values on these radio buttons may look like l12y issues, but
+ they're not - this preference uses *those strings* as its values.
+ I KID YOU NOT.
+ -->
+ <radiogroup id="certSelection"
+ preftype="string"
+ preference="security.default_personal_cert"
+ aria-labelledby="CertSelectionDesc">
+ <radio label="&certs.auto;"
+ accesskey="&certs.auto.accesskey;"
+ value="Select Automatically"/>
+ <radio label="&certs.ask;"
+ accesskey="&certs.ask.accesskey;"
+ value="Ask Every Time"/>
+ </radiogroup>
+ </groupbox>
+ <separator/>
+ <checkbox id="enableOCSP"
+ label="&enableOCSP.label;"
+ accesskey="&enableOCSP.accesskey;"
+ onsyncfrompreference="return gAdvancedPane.readEnableOCSP();"
+ onsynctopreference="return gAdvancedPane.writeEnableOCSP();"
+ preference="security.OCSP.enabled"/>
+ <separator/>
+ <hbox>
+ <button id="viewCertificatesButton"
+ flex="1"
+ label="&viewCerts.label;"
+ accesskey="&viewCerts.accesskey;"
+ preference="security.disable_button.openCertManager"/>
+ <button id="viewSecurityDevicesButton"
+ flex="1"
+ label="&viewSecurityDevices.label;"
+ accesskey="&viewSecurityDevices.accesskey;"
+ preference="security.disable_button.openDeviceManager"/>
+ <hbox flex="10"/>
+ </hbox>
+ </tabpanel>
+ </tabpanels>
+</tabbox>
diff --git a/browser/components/preferences/in-content/applications.js b/browser/components/preferences/in-content/applications.js
new file mode 100644
index 000000000..6f2989657
--- /dev/null
+++ b/browser/components/preferences/in-content/applications.js
@@ -0,0 +1,1900 @@
+/* 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";
+
+// Constants & Enumeration Values
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+Components.utils.import('resource://gre/modules/AppConstants.jsm');
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_PDF = "application/pdf";
+
+const PREF_PDFJS_DISABLED = "pdfjs.disabled";
+const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
+
+const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
+
+// Preferences that affect which entries to show in the list.
+const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
+const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
+ "browser.download.hide_plugins_without_extensions";
+
+/*
+ * Preferences where we store handling information about the feed type.
+ *
+ * browser.feeds.handler
+ * - "bookmarks", "reader" (clarified further using the .default preference),
+ * or "ask" -- indicates the default handler being used to process feeds;
+ * "bookmarks" is obsolete; to specify that the handler is bookmarks,
+ * set browser.feeds.handler.default to "bookmarks";
+ *
+ * browser.feeds.handler.default
+ * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used
+ * to display feeds, either transiently (i.e., when the "use as default"
+ * checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
+ * or more permanently (i.e., the item displayed in the dropdown in Feeds
+ * preferences)
+ *
+ * browser.feeds.handler.webservice
+ * - the URL of the currently selected web service used to read feeds
+ *
+ * browser.feeds.handlers.application
+ * - nsILocalFile, stores the current client-side feed reading app if one has
+ * been chosen
+ */
+const PREF_FEED_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_FEED_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_FEED_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_FEED_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_FEED_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_FEED_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_FEED_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_FEED_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_FEED_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_FEED_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+// The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify
+// the actions the application can take with content of various types.
+// But since nsIHandlerInfo doesn't support plugins, there's no value
+// identifying the "use plugin" action, so we use this constant instead.
+const kActionUsePlugin = 5;
+
+const ICON_URL_APP = AppConstants.platform == "linux" ?
+ "moz-icon://dummy.exe?size=16" :
+ "chrome://browser/skin/preferences/application.png";
+
+// For CSS. Can be one of "ask", "save", "plugin" or "feed". If absent, the icon URL
+// was set by us to a custom handler icon and CSS should not try to override it.
+const APP_ICON_ATTR_NAME = "appHandlerIcon";
+
+// Utilities
+
+function getFileDisplayName(file) {
+ if (AppConstants.platform == "win") {
+ if (file instanceof Ci.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+ }
+ if (AppConstants.platform == "macosx") {
+ if (file instanceof Ci.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
+ }
+ return file.leafName;
+}
+
+function getLocalHandlerApp(aFile) {
+ var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ localHandlerApp.name = getFileDisplayName(aFile);
+ localHandlerApp.executable = aFile;
+
+ return localHandlerApp;
+}
+
+/**
+ * An enumeration of items in a JS array.
+ *
+ * FIXME: use ArrayConverter once it lands (bug 380839).
+ *
+ * @constructor
+ */
+function ArrayEnumerator(aItems) {
+ this._index = 0;
+ this._contents = aItems;
+}
+
+ArrayEnumerator.prototype = {
+ _index: 0,
+
+ hasMoreElements: function() {
+ return this._index < this._contents.length;
+ },
+
+ getNext: function() {
+ return this._contents[this._index++];
+ }
+};
+
+function isFeedType(t) {
+ return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
+}
+
+// HandlerInfoWrapper
+
+/**
+ * This object wraps nsIHandlerInfo with some additional functionality
+ * the Applications prefpane needs to display and allow modification of
+ * the list of handled types.
+ *
+ * We create an instance of this wrapper for each entry we might display
+ * in the prefpane, and we compose the instances from various sources,
+ * including plugins and the handler service.
+ *
+ * We don't implement all the original nsIHandlerInfo functionality,
+ * just the stuff that the prefpane needs.
+ *
+ * In theory, all of the custom functionality in this wrapper should get
+ * pushed down into nsIHandlerInfo eventually.
+ */
+function HandlerInfoWrapper(aType, aHandlerInfo) {
+ this._type = aType;
+ this.wrappedHandlerInfo = aHandlerInfo;
+}
+
+HandlerInfoWrapper.prototype = {
+ // The wrapped nsIHandlerInfo object. In general, this object is private,
+ // but there are a couple cases where callers access it directly for things
+ // we haven't (yet?) implemented, so we make it a public property.
+ wrappedHandlerInfo: null,
+
+
+ // Convenience Utils
+
+ _handlerSvc: Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService),
+
+ _prefSvc: Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch),
+
+ _categoryMgr: Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager),
+
+ element: function(aID) {
+ return document.getElementById(aID);
+ },
+
+
+ // nsIHandlerInfo
+
+ // The MIME type or protocol scheme.
+ _type: null,
+ get type() {
+ return this._type;
+ },
+
+ get description() {
+ if (this.wrappedHandlerInfo.description)
+ return this.wrappedHandlerInfo.description;
+
+ if (this.primaryExtension) {
+ var extension = this.primaryExtension.toUpperCase();
+ return this.element("bundlePreferences").getFormattedString("fileEnding",
+ [extension]);
+ }
+
+ return this.type;
+ },
+
+ get preferredApplicationHandler() {
+ return this.wrappedHandlerInfo.preferredApplicationHandler;
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
+
+ // Make sure the preferred handler is in the set of possible handlers.
+ if (aNewValue)
+ this.addPossibleApplicationHandler(aNewValue)
+ },
+
+ get possibleApplicationHandlers() {
+ return this.wrappedHandlerInfo.possibleApplicationHandlers;
+ },
+
+ addPossibleApplicationHandler: function(aNewHandler) {
+ var possibleApps = this.possibleApplicationHandlers.enumerate();
+ while (possibleApps.hasMoreElements()) {
+ if (possibleApps.getNext().equals(aNewHandler))
+ return;
+ }
+ this.possibleApplicationHandlers.appendElement(aNewHandler, false);
+ },
+
+ removePossibleApplicationHandler: function(aHandler) {
+ var defaultApp = this.preferredApplicationHandler;
+ if (defaultApp && aHandler.equals(defaultApp)) {
+ // If the app we remove was the default app, we must make sure
+ // it won't be used anymore
+ this.alwaysAskBeforeHandling = true;
+ this.preferredApplicationHandler = null;
+ }
+
+ var handlers = this.possibleApplicationHandlers;
+ for (var i = 0; i < handlers.length; ++i) {
+ var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
+ if (handler.equals(aHandler)) {
+ handlers.removeElementAt(i);
+ break;
+ }
+ }
+ },
+
+ get hasDefaultHandler() {
+ return this.wrappedHandlerInfo.hasDefaultHandler;
+ },
+
+ get defaultDescription() {
+ return this.wrappedHandlerInfo.defaultDescription;
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ // If we have an enabled plugin, then the action is to use that plugin.
+ if (this.pluginName && !this.isDisabledPluginType)
+ return kActionUsePlugin;
+
+ // If the action is to use a helper app, but we don't have a preferred
+ // handler app, then switch to using the system default, if any; otherwise
+ // fall back to saving to disk, which is the default action in nsMIMEInfo.
+ // Note: "save to disk" is an invalid value for protocol info objects,
+ // but the alwaysAskBeforeHandling getter will detect that situation
+ // and always return true in that case to override this invalid value.
+ if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
+ !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
+ if (this.wrappedHandlerInfo.hasDefaultHandler)
+ return Ci.nsIHandlerInfo.useSystemDefault;
+ return Ci.nsIHandlerInfo.saveToDisk;
+ }
+
+ return this.wrappedHandlerInfo.preferredAction;
+ },
+
+ set preferredAction(aNewValue) {
+ // If the action is to use the plugin,
+ // we must set the preferred action to "save to disk".
+ // But only if it's not currently the preferred action.
+ if ((aNewValue == kActionUsePlugin) &&
+ (this.preferredAction != Ci.nsIHandlerInfo.saveToDisk)) {
+ aNewValue = Ci.nsIHandlerInfo.saveToDisk;
+ }
+
+ // We don't modify the preferred action if the new action is to use a plugin
+ // because handler info objects don't understand our custom "use plugin"
+ // value. Also, leaving it untouched means that we can automatically revert
+ // to the old setting if the user ever removes the plugin.
+
+ if (aNewValue != kActionUsePlugin)
+ this.wrappedHandlerInfo.preferredAction = aNewValue;
+ },
+
+ get alwaysAskBeforeHandling() {
+ // If this type is handled only by a plugin, we can't trust the value
+ // in the handler info object, since it'll be a default based on the absence
+ // of any user configuration, and the default in that case is to always ask,
+ // even though we never ask for content handled by a plugin, so special case
+ // plugin-handled types by returning false here.
+ if (this.pluginName && this.handledOnlyByPlugin)
+ return false;
+
+ // If this is a protocol type and the preferred action is "save to disk",
+ // which is invalid for such types, then return true here to override that
+ // action. This could happen when the preferred action is to use a helper
+ // app, but the preferredApplicationHandler is invalid, and there isn't
+ // a default handler, so the preferredAction getter returns save to disk
+ // instead.
+ if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+ this.preferredAction == Ci.nsIHandlerInfo.saveToDisk)
+ return true;
+
+ return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
+ },
+
+
+ // nsIMIMEInfo
+
+ // The primary file extension associated with this type, if any.
+ //
+ // XXX Plugin objects contain an array of MimeType objects with "suffixes"
+ // properties; if this object has an associated plugin, shouldn't we check
+ // those properties for an extension?
+ get primaryExtension() {
+ try {
+ if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ this.wrappedHandlerInfo.primaryExtension)
+ return this.wrappedHandlerInfo.primaryExtension
+ } catch (ex) {}
+
+ return null;
+ },
+
+
+ // Plugin Handling
+
+ // A plugin that can handle this type, if any.
+ //
+ // Note: just because we have one doesn't mean it *will* handle the type.
+ // That depends on whether or not the type is in the list of types for which
+ // plugin handling is disabled.
+ plugin: null,
+
+ // Whether or not this type is only handled by a plugin or is also handled
+ // by some user-configured action as specified in the handler info object.
+ //
+ // Note: we can't just check if there's a handler info object for this type,
+ // because OS and user configuration is mixed up in the handler info object,
+ // so we always need to retrieve it for the OS info and can't tell whether
+ // it represents only OS-default information or user-configured information.
+ //
+ // FIXME: once handler info records are broken up into OS-provided records
+ // and user-configured records, stop using this boolean flag and simply
+ // check for the presence of a user-configured record to determine whether
+ // or not this type is only handled by a plugin. Filed as bug 395142.
+ handledOnlyByPlugin: undefined,
+
+ get isDisabledPluginType() {
+ return this._getDisabledPluginTypes().indexOf(this.type) != -1;
+ },
+
+ _getDisabledPluginTypes: function() {
+ var types = "";
+
+ if (this._prefSvc.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
+ types = this._prefSvc.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
+
+ // Only split if the string isn't empty so we don't end up with an array
+ // containing a single empty string.
+ if (types != "")
+ return types.split(",");
+
+ return [];
+ },
+
+ disablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ if (disabledPluginTypes.indexOf(this.type) == -1)
+ disabledPluginTypes.push(this.type);
+
+ this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ this._categoryMgr.deleteCategoryEntry("Gecko-Content-Viewers",
+ this.type,
+ false);
+ },
+
+ enablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ var type = this.type;
+ disabledPluginTypes = disabledPluginTypes.filter(v => v != type);
+
+ this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ this._categoryMgr.
+ addCategoryEntry("Gecko-Content-Viewers",
+ this.type,
+ "@mozilla.org/content/plugin/document-loader-factory;1",
+ false,
+ true);
+ },
+
+
+ // Storage
+
+ store: function() {
+ this._handlerSvc.store(this.wrappedHandlerInfo);
+ },
+
+
+ // Icons
+
+ get smallIcon() {
+ return this._getIcon(16);
+ },
+
+ _getIcon: function(aSize) {
+ if (this.primaryExtension)
+ return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
+
+ if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo)
+ return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
+
+ // FIXME: consider returning some generic icon when we can't get a URL for
+ // one (for example in the case of protocol schemes). Filed as bug 395141.
+ return null;
+ }
+
+};
+
+
+// Feed Handler Info
+
+/**
+ * This object implements nsIHandlerInfo for the feed types. It's a separate
+ * object because we currently store handling information for the feed type
+ * in a set of preferences rather than the nsIHandlerService-managed datastore.
+ *
+ * This object inherits from HandlerInfoWrapper in order to get functionality
+ * that isn't special to the feed type.
+ *
+ * XXX Should we inherit from HandlerInfoWrapper? After all, we override
+ * most of that wrapper's properties and methods, and we have to dance around
+ * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
+ * don't provide.
+ */
+
+function FeedHandlerInfo(aMIMEType) {
+ HandlerInfoWrapper.call(this, aMIMEType, null);
+}
+
+FeedHandlerInfo.prototype = {
+ __proto__: HandlerInfoWrapper.prototype,
+
+ // Convenience Utils
+
+ _converterSvc:
+ Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService),
+
+ _shellSvc: AppConstants.HAVE_SHELL_SERVICE ? getShellService() : null,
+
+ // nsIHandlerInfo
+
+ get description() {
+ return this.element("bundlePreferences").getString(this._appPrefLabel);
+ },
+
+ get preferredApplicationHandler() {
+ switch (this.element(this._prefSelectedReader).value) {
+ case "client":
+ var file = this.element(this._prefSelectedApp).value;
+ if (file)
+ return getLocalHandlerApp(file);
+
+ return null;
+
+ case "web":
+ var uri = this.element(this._prefSelectedWeb).value;
+ if (!uri)
+ return null;
+ return this._converterSvc.getWebContentHandlerByURI(this.type, uri);
+
+ case "bookmarks":
+ default:
+ // When the pref is set to bookmarks, we handle feeds internally,
+ // we don't forward them to a local or web handler app, so there is
+ // no preferred handler.
+ return null;
+ }
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ if (aNewValue instanceof Ci.nsILocalHandlerApp) {
+ this.element(this._prefSelectedApp).value = aNewValue.executable;
+ this.element(this._prefSelectedReader).value = "client";
+ }
+ else if (aNewValue instanceof Ci.nsIWebContentHandlerInfo) {
+ this.element(this._prefSelectedWeb).value = aNewValue.uri;
+ this.element(this._prefSelectedReader).value = "web";
+ // Make the web handler be the new "auto handler" for feeds.
+ // Note: we don't have to unregister the auto handler when the user picks
+ // a non-web handler (local app, Live Bookmarks, etc.) because the service
+ // only uses the "auto handler" when the selected reader is a web handler.
+ // We also don't have to unregister it when the user turns on "always ask"
+ // (i.e. preview in browser), since that also overrides the auto handler.
+ this._converterSvc.setAutoHandler(this.type, aNewValue);
+ }
+ },
+
+ _possibleApplicationHandlers: null,
+
+ get possibleApplicationHandlers() {
+ if (this._possibleApplicationHandlers)
+ return this._possibleApplicationHandlers;
+
+ // A minimal implementation of nsIMutableArray. It only supports the two
+ // methods its callers invoke, namely appendElement and nsIArray::enumerate.
+ this._possibleApplicationHandlers = {
+ _inner: [],
+ _removed: [],
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIMutableArray) ||
+ aIID.equals(Ci.nsIArray) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ get length() {
+ return this._inner.length;
+ },
+
+ enumerate: function() {
+ return new ArrayEnumerator(this._inner);
+ },
+
+ appendElement: function(aHandlerApp, aWeak) {
+ this._inner.push(aHandlerApp);
+ },
+
+ removeElementAt: function(aIndex) {
+ this._removed.push(this._inner[aIndex]);
+ this._inner.splice(aIndex, 1);
+ },
+
+ queryElementAt: function(aIndex, aInterface) {
+ return this._inner[aIndex].QueryInterface(aInterface);
+ }
+ };
+
+ // Add the selected local app if it's different from the OS default handler.
+ // Unlike for other types, we can store only one local app at a time for the
+ // feed type, since we store it in a preference that historically stores
+ // only a single path. But we display all the local apps the user chooses
+ // while the prefpane is open, only dropping the list when the user closes
+ // the prefpane, for maximum usability and consistency with other types.
+ var preferredAppFile = this.element(this._prefSelectedApp).value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ let defaultApp = this._defaultApplicationHandler;
+ if (!defaultApp || !defaultApp.equals(preferredApp))
+ this._possibleApplicationHandlers.appendElement(preferredApp, false);
+ }
+
+ // Add the registered web handlers. There can be any number of these.
+ var webHandlers = this._converterSvc.getContentHandlers(this.type);
+ for (let webHandler of webHandlers)
+ this._possibleApplicationHandlers.appendElement(webHandler, false);
+
+ return this._possibleApplicationHandlers;
+ },
+
+ __defaultApplicationHandler: undefined,
+ get _defaultApplicationHandler() {
+ if (typeof this.__defaultApplicationHandler != "undefined")
+ return this.__defaultApplicationHandler;
+
+ var defaultFeedReader = null;
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ try {
+ defaultFeedReader = this._shellSvc.defaultFeedReader;
+ }
+ catch (ex) {
+ // no default reader or _shellSvc is null
+ }
+ }
+
+ if (defaultFeedReader) {
+ let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsIHandlerApp);
+ handlerApp.name = getFileDisplayName(defaultFeedReader);
+ handlerApp.QueryInterface(Ci.nsILocalHandlerApp);
+ handlerApp.executable = defaultFeedReader;
+
+ this.__defaultApplicationHandler = handlerApp;
+ }
+ else {
+ this.__defaultApplicationHandler = null;
+ }
+
+ return this.__defaultApplicationHandler;
+ },
+
+ get hasDefaultHandler() {
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ try {
+ if (this._shellSvc.defaultFeedReader)
+ return true;
+ }
+ catch (ex) {
+ // no default reader or _shellSvc is null
+ }
+ }
+
+ return false;
+ },
+
+ get defaultDescription() {
+ if (this.hasDefaultHandler)
+ return this._defaultApplicationHandler.name;
+
+ // Should we instead return null?
+ return "";
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ switch (this.element(this._prefSelectedAction).value) {
+
+ case "bookmarks":
+ return Ci.nsIHandlerInfo.handleInternally;
+
+ case "reader": {
+ let preferredApp = this.preferredApplicationHandler;
+ let defaultApp = this._defaultApplicationHandler;
+
+ // If we have a valid preferred app, return useSystemDefault if it's
+ // the default app; otherwise return useHelperApp.
+ if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
+ if (defaultApp && defaultApp.equals(preferredApp))
+ return Ci.nsIHandlerInfo.useSystemDefault;
+
+ return Ci.nsIHandlerInfo.useHelperApp;
+ }
+
+ // The pref is set to "reader", but we don't have a valid preferred app.
+ // What do we do now? Not sure this is the best option (perhaps we
+ // should direct the user to the default app, if any), but for now let's
+ // direct the user to live bookmarks.
+ return Ci.nsIHandlerInfo.handleInternally;
+ }
+
+ // If the action is "ask", then alwaysAskBeforeHandling will override
+ // the action, so it doesn't matter what we say it is, it just has to be
+ // something that doesn't cause the controller to hide the type.
+ case "ask":
+ default:
+ return Ci.nsIHandlerInfo.handleInternally;
+ }
+ },
+
+ set preferredAction(aNewValue) {
+ switch (aNewValue) {
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ this.element(this._prefSelectedReader).value = "bookmarks";
+ break;
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ this.element(this._prefSelectedAction).value = "reader";
+ // The controller has already set preferredApplicationHandler
+ // to the new helper app.
+ break;
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ this.element(this._prefSelectedAction).value = "reader";
+ this.preferredApplicationHandler = this._defaultApplicationHandler;
+ break;
+ }
+ },
+
+ get alwaysAskBeforeHandling() {
+ return this.element(this._prefSelectedAction).value == "ask";
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ if (aNewValue == true)
+ this.element(this._prefSelectedAction).value = "ask";
+ else
+ this.element(this._prefSelectedAction).value = "reader";
+ },
+
+ // Whether or not we are currently storing the action selected by the user.
+ // We use this to suppress notification-triggered updates to the list when
+ // we make changes that may spawn such updates, specifically when we change
+ // the action for the feed type, which results in feed preference updates,
+ // which spawn "pref changed" notifications that would otherwise cause us
+ // to rebuild the view unnecessarily.
+ _storingAction: false,
+
+
+ // nsIMIMEInfo
+
+ get primaryExtension() {
+ return "xml";
+ },
+
+
+ // Storage
+
+ // Changes to the preferred action and handler take effect immediately
+ // (we write them out to the preferences right as they happen),
+ // so we when the controller calls store() after modifying the handlers,
+ // the only thing we need to store is the removal of possible handlers
+ // XXX Should we hold off on making the changes until this method gets called?
+ store: function() {
+ for (let app of this._possibleApplicationHandlers._removed) {
+ if (app instanceof Ci.nsILocalHandlerApp) {
+ let pref = this.element(PREF_FEED_SELECTED_APP);
+ var preferredAppFile = pref.value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ if (app.equals(preferredApp))
+ pref.reset();
+ }
+ }
+ else {
+ app.QueryInterface(Ci.nsIWebContentHandlerInfo);
+ this._converterSvc.removeContentHandler(app.contentType, app.uri);
+ }
+ }
+ this._possibleApplicationHandlers._removed = [];
+ },
+
+
+ // Icons
+
+ get smallIcon() {
+ return this._smallIcon;
+ }
+
+};
+
+var feedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
+ _prefSelectedApp: PREF_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/feedIcon16.png",
+ _appPrefLabel: "webFeed"
+}
+
+var videoFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
+ _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/videoFeedIcon16.png",
+ _appPrefLabel: "videoPodcastFeed"
+}
+
+var audioFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
+ _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/audioFeedIcon16.png",
+ _appPrefLabel: "audioPodcastFeed"
+}
+
+/**
+ * InternalHandlerInfoWrapper provides a basic mechanism to create an internal
+ * mime type handler that can be enabled/disabled in the applications preference
+ * menu.
+ */
+function InternalHandlerInfoWrapper(aMIMEType) {
+ var mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ var handlerInfo = mimeSvc.getFromTypeAndExtension(aMIMEType, null);
+
+ HandlerInfoWrapper.call(this, aMIMEType, handlerInfo);
+}
+
+InternalHandlerInfoWrapper.prototype = {
+ __proto__: HandlerInfoWrapper.prototype,
+
+ // Override store so we so we can notify any code listening for registration
+ // or unregistration of this handler.
+ store: function() {
+ HandlerInfoWrapper.prototype.store.call(this);
+ Services.obs.notifyObservers(null, this._handlerChanged, null);
+ },
+
+ get enabled() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get description() {
+ return this.element("bundlePreferences").getString(this._appPrefLabel);
+ }
+};
+
+var pdfHandlerInfo = {
+ __proto__: new InternalHandlerInfoWrapper(TYPE_PDF),
+ _handlerChanged: TOPIC_PDFJS_HANDLER_CHANGED,
+ _appPrefLabel: "portableDocumentFormat",
+ get enabled() {
+ return !Services.prefs.getBoolPref(PREF_PDFJS_DISABLED);
+ },
+};
+
+
+// Prefpane Controller
+
+var gApplicationsPane = {
+ // The set of types the app knows how to handle. A hash of HandlerInfoWrapper
+ // objects, indexed by type.
+ _handledTypes: {},
+
+ // The list of types we can show, sorted by the sort column/direction.
+ // An array of HandlerInfoWrapper objects. We build this list when we first
+ // load the data and then rebuild it when users change a pref that affects
+ // what types we can show or change the sort column/direction.
+ // Note: this isn't necessarily the list of types we *will* show; if the user
+ // provides a filter string, we'll only show the subset of types in this list
+ // that match that string.
+ _visibleTypes: [],
+
+ // A count of the number of times each visible type description appears.
+ // We use these counts to determine whether or not to annotate descriptions
+ // with their types to distinguish duplicate descriptions from each other.
+ // A hash of integer counts, indexed by string description.
+ _visibleTypeDescriptionCount: {},
+
+
+ // Convenience & Performance Shortcuts
+
+ // These get defined by init().
+ _brandShortName : null,
+ _prefsBundle : null,
+ _list : null,
+ _filter : null,
+
+ _prefSvc : Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch),
+
+ _mimeSvc : Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService),
+
+ _helperAppSvc : Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Ci.nsIExternalHelperAppService),
+
+ _handlerSvc : Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService),
+
+ _ioSvc : Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService),
+
+
+ // Initialization & Destruction
+
+ init: function() {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gApplicationsPane));
+ }
+
+ // Initialize shortcuts to some commonly accessed elements & values.
+ this._brandShortName =
+ document.getElementById("bundleBrand").getString("brandShortName");
+ this._prefsBundle = document.getElementById("bundlePreferences");
+ this._list = document.getElementById("handlersView");
+ this._filter = document.getElementById("filter");
+
+ // Observe preferences that influence what we display so we can rebuild
+ // the view when they change.
+ this._prefSvc.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false);
+ this._prefSvc.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_READER, this, false);
+
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_READER, this, false);
+
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_READER, this, false);
+
+
+ setEventListener("focusSearch1", "command", gApplicationsPane.focusFilterBox);
+ setEventListener("focusSearch2", "command", gApplicationsPane.focusFilterBox);
+ setEventListener("filter", "command", gApplicationsPane.filter);
+ setEventListener("handlersView", "select",
+ gApplicationsPane.onSelectionChanged);
+ setEventListener("typeColumn", "click", gApplicationsPane.sort);
+ setEventListener("actionColumn", "click", gApplicationsPane.sort);
+
+ // Listen for window unload so we can remove our preference observers.
+ window.addEventListener("unload", this, false);
+
+ // Figure out how we should be sorting the list. We persist sort settings
+ // across sessions, so we can't assume the default sort column/direction.
+ // XXX should we be using the XUL sort service instead?
+ if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
+ this._sortColumn = document.getElementById("actionColumn");
+ // The typeColumn element always has a sortDirection attribute,
+ // either because it was persisted or because the default value
+ // from the xul file was used. If we are sorting on the other
+ // column, we should remove it.
+ document.getElementById("typeColumn").removeAttribute("sortDirection");
+ }
+ else
+ this._sortColumn = document.getElementById("typeColumn");
+
+ // Load the data and build the list of handlers.
+ // By doing this in a timeout, we let the preferences dialog resize itself
+ // to an appropriate size before we add a bunch of items to the list.
+ // Otherwise, if there are many items, and the Applications prefpane
+ // is the one that gets displayed when the user first opens the dialog,
+ // the dialog might stretch too much in an attempt to fit them all in.
+ // XXX Shouldn't we perhaps just set a max-height on the richlistbox?
+ var _delayedPaneLoad = function(self) {
+ self._loadData();
+ self._rebuildVisibleTypes();
+ self._sortVisibleTypes();
+ self._rebuildView();
+
+ // Notify observers that the UI is now ready
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
+ notifyObservers(window, "app-handler-pane-loaded", null);
+ }
+ setTimeout(_delayedPaneLoad, 0, this);
+ },
+
+ destroy: function() {
+ window.removeEventListener("unload", this, false);
+ this._prefSvc.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this);
+ this._prefSvc.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_READER, this);
+
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_READER, this);
+
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_READER, this);
+ },
+
+
+ // nsISupports
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsIDOMEventListener ||
+ aIID.equals(Ci.nsISupports)))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ // Rebuild the list when there are changes to preferences that influence
+ // whether or not to show certain entries in the list.
+ if (aTopic == "nsPref:changed" && !this._storingAction) {
+ // These two prefs alter the list of visible types, so we have to rebuild
+ // that list when they change.
+ if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
+ aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
+ this._rebuildVisibleTypes();
+ this._sortVisibleTypes();
+ }
+
+ // All the prefs we observe can affect what we display, so we rebuild
+ // the view when any of them changes.
+ this._rebuildView();
+ }
+ },
+
+
+ // nsIDOMEventListener
+
+ handleEvent: function(aEvent) {
+ if (aEvent.type == "unload") {
+ this.destroy();
+ }
+ },
+
+
+ // Composed Model Construction
+
+ _loadData: function() {
+ this._loadFeedHandler();
+ this._loadInternalHandlers();
+ this._loadPluginHandlers();
+ this._loadApplicationHandlers();
+ },
+
+ _loadFeedHandler: function() {
+ this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo;
+ feedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_VIDEO_FEED] = videoFeedHandlerInfo;
+ videoFeedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_AUDIO_FEED] = audioFeedHandlerInfo;
+ audioFeedHandlerInfo.handledOnlyByPlugin = false;
+ },
+
+ /**
+ * Load higher level internal handlers so they can be turned on/off in the
+ * applications menu.
+ */
+ _loadInternalHandlers: function() {
+ var internalHandlers = [pdfHandlerInfo];
+ for (let internalHandler of internalHandlers) {
+ if (internalHandler.enabled) {
+ this._handledTypes[internalHandler.type] = internalHandler;
+ }
+ }
+ },
+
+ /**
+ * Load the set of handlers defined by plugins.
+ *
+ * Note: if there's more than one plugin for a given MIME type, we assume
+ * the last one is the one that the application will use. That may not be
+ * correct, but it's how we've been doing it for years.
+ *
+ * Perhaps we should instead query navigator.mimeTypes for the set of types
+ * supported by the application and then get the plugin from each MIME type's
+ * enabledPlugin property. But if there's a plugin for a type, we need
+ * to know about it even if it isn't enabled, since we're going to give
+ * the user an option to enable it.
+ *
+ * Also note that enabledPlugin does not get updated when
+ * plugin.disable_full_page_plugin_for_types changes, so even if we could use
+ * enabledPlugin to get the plugin that would be used, we'd still need to
+ * check the pref ourselves to find out if it's enabled.
+ */
+ _loadPluginHandlers: function() {
+ "use strict";
+
+ let mimeTypes = navigator.mimeTypes;
+
+ for (let mimeType of mimeTypes) {
+ let handlerInfoWrapper;
+ if (mimeType.type in this._handledTypes) {
+ handlerInfoWrapper = this._handledTypes[mimeType.type];
+ } else {
+ let wrappedHandlerInfo =
+ this._mimeSvc.getFromTypeAndExtension(mimeType.type, null);
+ handlerInfoWrapper = new HandlerInfoWrapper(mimeType.type, wrappedHandlerInfo);
+ handlerInfoWrapper.handledOnlyByPlugin = true;
+ this._handledTypes[mimeType.type] = handlerInfoWrapper;
+ }
+ handlerInfoWrapper.pluginName = mimeType.enabledPlugin.name;
+ }
+ },
+
+ /**
+ * Load the set of handlers defined by the application datastore.
+ */
+ _loadApplicationHandlers: function() {
+ var wrappedHandlerInfos = this._handlerSvc.enumerate();
+ while (wrappedHandlerInfos.hasMoreElements()) {
+ let wrappedHandlerInfo =
+ wrappedHandlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ let type = wrappedHandlerInfo.type;
+
+ let handlerInfoWrapper;
+ if (type in this._handledTypes)
+ handlerInfoWrapper = this._handledTypes[type];
+ else {
+ handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
+ this._handledTypes[type] = handlerInfoWrapper;
+ }
+
+ handlerInfoWrapper.handledOnlyByPlugin = false;
+ }
+ },
+
+
+ // View Construction
+
+ _rebuildVisibleTypes: function() {
+ // Reset the list of visible types and the visible type description counts.
+ this._visibleTypes = [];
+ this._visibleTypeDescriptionCount = {};
+
+ // Get the preferences that help determine what types to show.
+ var showPlugins = this._prefSvc.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
+ var hidePluginsWithoutExtensions =
+ this._prefSvc.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
+
+ for (let type in this._handledTypes) {
+ let handlerInfo = this._handledTypes[type];
+
+ // Hide plugins without associated extensions if so prefed so we don't
+ // show a whole bunch of obscure types handled by plugins on Mac.
+ // Note: though protocol types don't have extensions, we still show them;
+ // the pref is only meant to be applied to MIME types, since plugins are
+ // only associated with MIME types.
+ // FIXME: should we also check the "suffixes" property of the plugin?
+ // Filed as bug 395135.
+ if (hidePluginsWithoutExtensions && handlerInfo.handledOnlyByPlugin &&
+ handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ !handlerInfo.primaryExtension)
+ continue;
+
+ // Hide types handled only by plugins if so prefed.
+ if (handlerInfo.handledOnlyByPlugin && !showPlugins)
+ continue;
+
+ // We couldn't find any reason to exclude the type, so include it.
+ this._visibleTypes.push(handlerInfo);
+
+ if (handlerInfo.description in this._visibleTypeDescriptionCount)
+ this._visibleTypeDescriptionCount[handlerInfo.description]++;
+ else
+ this._visibleTypeDescriptionCount[handlerInfo.description] = 1;
+ }
+ },
+
+ _rebuildView: function() {
+ // Clear the list of entries.
+ while (this._list.childNodes.length > 1)
+ this._list.removeChild(this._list.lastChild);
+
+ var visibleTypes = this._visibleTypes;
+
+ // If the user is filtering the list, then only show matching types.
+ if (this._filter.value)
+ visibleTypes = visibleTypes.filter(this._matchesFilter, this);
+
+ for (let visibleType of visibleTypes) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("type", visibleType.type);
+ item.setAttribute("typeDescription", this._describeType(visibleType));
+ if (visibleType.smallIcon)
+ item.setAttribute("typeIcon", visibleType.smallIcon);
+ item.setAttribute("actionDescription",
+ this._describePreferredAction(visibleType));
+
+ if (!this._setIconClassForPreferredAction(visibleType, item)) {
+ item.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(visibleType));
+ }
+
+ this._list.appendChild(item);
+ }
+
+ this._selectLastSelectedType();
+ },
+
+ _matchesFilter: function(aType) {
+ var filterValue = this._filter.value.toLowerCase();
+ return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 ||
+ this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the type represented by the given
+ * handler info object. Normally this is just the description provided by
+ * the info object, but if more than one object presents the same description,
+ * then we annotate the duplicate descriptions with the type itself to help
+ * users distinguish between those types.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type being described
+ * @returns {string} a description of the type
+ */
+ _describeType: function(aHandlerInfo) {
+ if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1)
+ return this._prefsBundle.getFormattedString("typeDescriptionWithType",
+ [aHandlerInfo.description,
+ aHandlerInfo.type]);
+
+ return aHandlerInfo.description;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the preferred action to take on
+ * the type represented by the given handler info object.
+ *
+ * XXX Should this be part of the HandlerInfoWrapper interface? It would
+ * violate the separation of model and view, but it might make more sense
+ * nonetheless (f.e. it would make sortTypes easier).
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action
+ * is being described
+ * @returns {string} a description of the action
+ */
+ _describePreferredAction: function(aHandlerInfo) {
+ // alwaysAskBeforeHandling overrides the preferred action, so if that flag
+ // is set, then describe that behavior instead. For most types, this is
+ // the "alwaysAsk" string, but for the feed type we show something special.
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ if (isFeedType(aHandlerInfo.type))
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ return this._prefsBundle.getString("alwaysAsk");
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ return this._prefsBundle.getString("saveFile");
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ var preferredApp = aHandlerInfo.preferredApplicationHandler;
+ var name;
+ if (preferredApp instanceof Ci.nsILocalHandlerApp)
+ name = getFileDisplayName(preferredApp.executable);
+ else
+ name = preferredApp.name;
+ return this._prefsBundle.getFormattedString("useApp", [name]);
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ // For the feed type, handleInternally means live bookmarks.
+ if (isFeedType(aHandlerInfo.type)) {
+ return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+ [this._brandShortName]);
+ }
+
+ if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ }
+
+ // For other types, handleInternally looks like either useHelperApp
+ // or useSystemDefault depending on whether or not there's a preferred
+ // handler app.
+ if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler))
+ return aHandlerInfo.preferredApplicationHandler.name;
+
+ return aHandlerInfo.defaultDescription;
+
+ // XXX Why don't we say the app will handle the type internally?
+ // Is it because the app can't actually do that? But if that's true,
+ // then why would a preferredAction ever get set to this value
+ // in the first place?
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ return this._prefsBundle.getFormattedString("useDefault",
+ [aHandlerInfo.defaultDescription]);
+
+ case kActionUsePlugin:
+ return this._prefsBundle.getFormattedString("usePluginIn",
+ [aHandlerInfo.pluginName,
+ this._brandShortName]);
+ default:
+ throw new Error(`Unexpected preferredAction: ${aHandlerInfo.preferredAction}`);
+ }
+ },
+
+ _selectLastSelectedType: function() {
+ // If the list is disabled by the pref.downloads.disable_button.edit_actions
+ // preference being locked, then don't select the type, as that would cause
+ // it to appear selected, with a different background and an actions menu
+ // that makes it seem like you can choose an action for the type.
+ if (this._list.disabled)
+ return;
+
+ var lastSelectedType = this._list.getAttribute("lastSelectedType");
+ if (!lastSelectedType)
+ return;
+
+ var item = this._list.getElementsByAttribute("type", lastSelectedType)[0];
+ if (!item)
+ return;
+
+ this._list.selectedItem = item;
+ },
+
+ /**
+ * Whether or not the given handler app is valid.
+ *
+ * @param aHandlerApp {nsIHandlerApp} the handler app in question
+ *
+ * @returns {boolean} whether or not it's valid
+ */
+ isValidHandlerApp: function(aHandlerApp) {
+ if (!aHandlerApp)
+ return false;
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ return this._isValidHandlerExecutable(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
+ return aHandlerApp.uriTemplate;
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
+ return aHandlerApp.uri;
+
+ return false;
+ },
+
+ _isValidHandlerExecutable: function(aExecutable) {
+ let leafName;
+ if (AppConstants.platform == "win") {
+ leafName = `${AppConstants.MOZ_APP_NAME}.exe`;
+ } else if (AppConstants.platform == "macosx") {
+ leafName = AppConstants.MOZ_MACBUNDLE_NAME;
+ } else {
+ leafName = `${AppConstants.MOZ_APP_NAME}-bin`;
+ }
+ return aExecutable &&
+ aExecutable.exists() &&
+ aExecutable.isExecutable() &&
+// XXXben - we need to compare this with the running instance executable
+// just don't know how to do that via script...
+// XXXmano TBD: can probably add this to nsIShellService
+ aExecutable.leafName != leafName;
+ },
+
+ /**
+ * Rebuild the actions menu for the selected entry. Gets called by
+ * the richlistitem constructor when an entry in the list gets selected.
+ */
+ rebuildActionsMenu: function() {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+ var menu =
+ document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+ var menuPopup = menu.menupopup;
+
+ // Clear out existing items.
+ while (menuPopup.hasChildNodes())
+ menuPopup.removeChild(menuPopup.lastChild);
+
+ // Add the "Preview in Firefox" option for optional internal handlers.
+ if (handlerInfo instanceof InternalHandlerInfoWrapper) {
+ let internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ {
+ var askMenuItem = document.createElement("menuitem");
+ askMenuItem.setAttribute("action", Ci.nsIHandlerInfo.alwaysAsk);
+ let label;
+ if (isFeedType(handlerInfo.type))
+ label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ else
+ label = this._prefsBundle.getString("alwaysAsk");
+ askMenuItem.setAttribute("label", label);
+ askMenuItem.setAttribute("tooltiptext", label);
+ askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ menuPopup.appendChild(askMenuItem);
+ }
+
+ // Create a menu item for saving to disk.
+ // Note: this option isn't available to protocol types, since we don't know
+ // what it means to save a URL having a certain scheme to disk, nor is it
+ // available to feeds, since the feed code doesn't implement the capability.
+ if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+ !isFeedType(handlerInfo.type)) {
+ var saveMenuItem = document.createElement("menuitem");
+ saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
+ let label = this._prefsBundle.getString("saveFile");
+ saveMenuItem.setAttribute("label", label);
+ saveMenuItem.setAttribute("tooltiptext", label);
+ saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
+ menuPopup.appendChild(saveMenuItem);
+ }
+
+ // If this is the feed type, add a Live Bookmarks item.
+ if (isFeedType(handlerInfo.type)) {
+ let internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ // Add a separator to distinguish these items from the helper app items
+ // that follow them.
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+
+ // Create a menu item for the OS default application, if any.
+ if (handlerInfo.hasDefaultHandler) {
+ var defaultMenuItem = document.createElement("menuitem");
+ defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
+ let label = this._prefsBundle.getFormattedString("useDefault",
+ [handlerInfo.defaultDescription]);
+ defaultMenuItem.setAttribute("label", label);
+ defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
+ defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo));
+
+ menuPopup.appendChild(defaultMenuItem);
+ }
+
+ // Create menu items for possible handlers.
+ let preferredApp = handlerInfo.preferredApplicationHandler;
+ let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
+ var possibleAppMenuItems = [];
+ while (possibleApps.hasMoreElements()) {
+ let possibleApp = possibleApps.getNext();
+ if (!this.isValidHandlerApp(possibleApp))
+ continue;
+
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
+ let label;
+ if (possibleApp instanceof Ci.nsILocalHandlerApp)
+ label = getFileDisplayName(possibleApp.executable);
+ else
+ label = possibleApp.name;
+ label = this._prefsBundle.getFormattedString("useApp", [label]);
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp));
+
+ // Attach the handler app object to the menu item so we can use it
+ // to make changes to the datastore when the user selects the item.
+ menuItem.handlerApp = possibleApp;
+
+ menuPopup.appendChild(menuItem);
+ possibleAppMenuItems.push(menuItem);
+ }
+
+ // Create a menu item for the plugin.
+ if (handlerInfo.pluginName) {
+ var pluginMenuItem = document.createElement("menuitem");
+ pluginMenuItem.setAttribute("action", kActionUsePlugin);
+ let label = this._prefsBundle.getFormattedString("usePluginIn",
+ [handlerInfo.pluginName,
+ this._brandShortName]);
+ pluginMenuItem.setAttribute("label", label);
+ pluginMenuItem.setAttribute("tooltiptext", label);
+ pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
+ menuPopup.appendChild(pluginMenuItem);
+ }
+
+ // Create a menu item for selecting a local application.
+ let canOpenWithOtherApp = true;
+ if (AppConstants.platform == "win") {
+ // On Windows, selecting an application to open another application
+ // would be meaningless so we special case executables.
+ let executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
+ .getTypeFromExtension("exe");
+ canOpenWithOtherApp = handlerInfo.type != executableType;
+ }
+ if (canOpenWithOtherApp)
+ {
+ let menuItem = document.createElement("menuitem");
+ menuItem.className = "choose-app-item";
+ menuItem.addEventListener("command", function(e) {
+ gApplicationsPane.chooseApp(e);
+ });
+ let label = this._prefsBundle.getString("useOtherApp");
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Create a menu item for managing applications.
+ if (possibleAppMenuItems.length) {
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+ menuItem = document.createElement("menuitem");
+ menuItem.className = "manage-app-item";
+ menuItem.addEventListener("command", function(e) {
+ gApplicationsPane.manageApp(e);
+ });
+ menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Select the item corresponding to the preferred action. If the always
+ // ask flag is set, it overrides the preferred action. Otherwise we pick
+ // the item identified by the preferred action (when the preferred action
+ // is to use a helper app, we have to pick the specific helper app item).
+ if (handlerInfo.alwaysAskBeforeHandling)
+ menu.selectedItem = askMenuItem;
+ else switch (handlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.handleInternally:
+ menu.selectedItem = internalMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ menu.selectedItem = defaultMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.useHelperApp:
+ if (preferredApp)
+ menu.selectedItem =
+ possibleAppMenuItems.filter(v => v.handlerApp.equals(preferredApp))[0];
+ break;
+ case kActionUsePlugin:
+ menu.selectedItem = pluginMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.saveToDisk:
+ menu.selectedItem = saveMenuItem;
+ break;
+ }
+ },
+
+
+ // Sorting & Filtering
+
+ _sortColumn: null,
+
+ /**
+ * Sort the list when the user clicks on a column header.
+ */
+ sort: function (event) {
+ var column = event.target;
+
+ // If the user clicked on a new sort column, remove the direction indicator
+ // from the old column.
+ if (this._sortColumn && this._sortColumn != column)
+ this._sortColumn.removeAttribute("sortDirection");
+
+ this._sortColumn = column;
+
+ // Set (or switch) the sort direction indicator.
+ if (column.getAttribute("sortDirection") == "ascending")
+ column.setAttribute("sortDirection", "descending");
+ else
+ column.setAttribute("sortDirection", "ascending");
+
+ this._sortVisibleTypes();
+ this._rebuildView();
+ },
+
+ /**
+ * Sort the list of visible types by the current sort column/direction.
+ */
+ _sortVisibleTypes: function() {
+ if (!this._sortColumn)
+ return;
+
+ var t = this;
+
+ function sortByType(a, b) {
+ return t._describeType(a).toLowerCase().
+ localeCompare(t._describeType(b).toLowerCase());
+ }
+
+ function sortByAction(a, b) {
+ return t._describePreferredAction(a).toLowerCase().
+ localeCompare(t._describePreferredAction(b).toLowerCase());
+ }
+
+ switch (this._sortColumn.getAttribute("value")) {
+ case "type":
+ this._visibleTypes.sort(sortByType);
+ break;
+ case "action":
+ this._visibleTypes.sort(sortByAction);
+ break;
+ }
+
+ if (this._sortColumn.getAttribute("sortDirection") == "descending")
+ this._visibleTypes.reverse();
+ },
+
+ /**
+ * Filter the list when the user enters a filter term into the filter field.
+ */
+ filter: function() {
+ this._rebuildView();
+ },
+
+ focusFilterBox: function() {
+ this._filter.focus();
+ this._filter.select();
+ },
+
+
+ // Changes
+
+ onSelectAction: function(aActionItem) {
+ this._storingAction = true;
+
+ try {
+ this._storeAction(aActionItem);
+ }
+ finally {
+ this._storingAction = false;
+ }
+ },
+
+ _storeAction: function(aActionItem) {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ let action = parseInt(aActionItem.getAttribute("action"));
+
+ // Set the plugin state if we're enabling or disabling a plugin.
+ if (action == kActionUsePlugin)
+ handlerInfo.enablePluginType();
+ else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
+ handlerInfo.disablePluginType();
+
+ // Set the preferred application handler.
+ // We leave the existing preferred app in the list when we set
+ // the preferred action to something other than useHelperApp so that
+ // legacy datastores that don't have the preferred app in the list
+ // of possible apps still include the preferred app in the list of apps
+ // the user can choose to handle the type.
+ if (action == Ci.nsIHandlerInfo.useHelperApp)
+ handlerInfo.preferredApplicationHandler = aActionItem.handlerApp;
+
+ // Set the "always ask" flag.
+ if (action == Ci.nsIHandlerInfo.alwaysAsk)
+ handlerInfo.alwaysAskBeforeHandling = true;
+ else
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ // Set the preferred action.
+ handlerInfo.preferredAction = action;
+
+ handlerInfo.store();
+
+ // Make sure the handler info object is flagged to indicate that there is
+ // now some user configuration for the type.
+ handlerInfo.handledOnlyByPlugin = false;
+
+ // Update the action label and image to reflect the new preferred action.
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ typeItem.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(handlerInfo));
+ }
+ },
+
+ manageApp: function(aEvent) {
+ // Don't let the normal "on select action" handler get this event,
+ // as we handle it specially ourselves.
+ aEvent.stopPropagation();
+
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ let onComplete = () => {
+ // Rebuild the actions menu so that we revert to the previous selection,
+ // or "Always ask" if the previous default application has been removed
+ this.rebuildActionsMenu();
+
+ // update the richlistitem too. Will be visible when selecting another row
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ typeItem.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(handlerInfo));
+ }
+ };
+
+ gSubDialog.open("chrome://browser/content/preferences/applicationManager.xul",
+ "resizable=no", handlerInfo, onComplete);
+
+ },
+
+ chooseApp: function(aEvent) {
+ // Don't let the normal "on select action" handler get this event,
+ // as we handle it specially ourselves.
+ aEvent.stopPropagation();
+
+ var handlerApp;
+ let chooseAppCallback = function(aHandlerApp) {
+ // Rebuild the actions menu whether the user picked an app or canceled.
+ // If they picked an app, we want to add the app to the menu and select it.
+ // If they canceled, we want to go back to their previous selection.
+ this.rebuildActionsMenu();
+
+ // If the user picked a new app from the menu, select it.
+ if (aHandlerApp) {
+ let typeItem = this._list.selectedItem;
+ let actionsMenu =
+ document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+ let menuItems = actionsMenu.menupopup.childNodes;
+ for (let i = 0; i < menuItems.length; i++) {
+ let menuItem = menuItems[i];
+ if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) {
+ actionsMenu.selectedIndex = i;
+ this.onSelectAction(menuItem);
+ break;
+ }
+ }
+ }
+ }.bind(this);
+
+ if (AppConstants.platform == "win") {
+ var params = {};
+ var handlerInfo = this._handledTypes[this._list.selectedItem.type];
+
+ if (isFeedType(handlerInfo.type)) {
+ // MIME info will be null, create a temp object.
+ params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type,
+ handlerInfo.primaryExtension);
+ } else {
+ params.mimeInfo = handlerInfo.wrappedHandlerInfo;
+ }
+
+ params.title = this._prefsBundle.getString("fpTitleChooseApp");
+ params.description = handlerInfo.description;
+ params.filename = null;
+ params.handlerApp = null;
+
+ let onAppSelected = () => {
+ if (this.isValidHandlerApp(params.handlerApp)) {
+ handlerApp = params.handlerApp;
+
+ // Add the app to the type's list of possible handlers.
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+ }
+
+ chooseAppCallback(handlerApp);
+ };
+
+ gSubDialog.open("chrome://global/content/appPicker.xul",
+ null, params, onAppSelected);
+ } else {
+ let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
+ this._isValidHandlerExecutable(fp.file)) {
+ handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.name = getFileDisplayName(fp.file);
+ handlerApp.executable = fp.file;
+
+ // Add the app to the type's list of possible handlers.
+ let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+
+ chooseAppCallback(handlerApp);
+ }
+ }.bind(this);
+
+ // Prompt the user to pick an app. If they pick one, and it's a valid
+ // selection, then add it to the list of possible handlers.
+ fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterApps);
+ fp.open(fpCallback);
+ }
+ },
+
+ // Mark which item in the list was last selected so we can reselect it
+ // when we rebuild the list or when the user returns to the prefpane.
+ onSelectionChanged: function() {
+ if (this._list.selectedItem)
+ this._list.setAttribute("lastSelectedType",
+ this._list.selectedItem.getAttribute("type"));
+ },
+
+ _setIconClassForPreferredAction: function(aHandlerInfo, aElement) {
+ // If this returns true, the attribute that CSS sniffs for was set to something
+ // so you shouldn't manually set an icon URI.
+ // This removes the existing actionIcon attribute if any, even if returning false.
+ aElement.removeAttribute("actionIcon");
+
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ return true;
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "save");
+ return true;
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ if (isFeedType(aHandlerInfo.type)) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "feed");
+ return true;
+ } else if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ return true;
+ }
+ break;
+
+ case kActionUsePlugin:
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "plugin");
+ return true;
+ }
+ aElement.removeAttribute(APP_ICON_ATTR_NAME);
+ return false;
+ },
+
+ _getIconURLForPreferredAction: function(aHandlerInfo) {
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ return this._getIconURLForSystemDefault(aHandlerInfo);
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ let preferredApp = aHandlerInfo.preferredApplicationHandler;
+ if (this.isValidHandlerApp(preferredApp))
+ return this._getIconURLForHandlerApp(preferredApp);
+ // Explicit fall-through
+
+ // This should never happen, but if preferredAction is set to some weird
+ // value, then fall back to the generic application icon.
+ default:
+ return ICON_URL_APP;
+ }
+ },
+
+ _getIconURLForHandlerApp: function(aHandlerApp) {
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ return this._getIconURLForFile(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
+ return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
+ return this._getIconURLForWebApp(aHandlerApp.uri)
+
+ // We know nothing about other kinds of handler apps.
+ return "";
+ },
+
+ _getIconURLForFile: function(aFile) {
+ var fph = this._ioSvc.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ var urlSpec = fph.getURLSpecFromFile(aFile);
+
+ return "moz-icon://" + urlSpec + "?size=16";
+ },
+
+ _getIconURLForWebApp: function(aWebAppURITemplate) {
+ var uri = this._ioSvc.newURI(aWebAppURITemplate, null, null);
+
+ // Unfortunately we can't use the favicon service to get the favicon,
+ // because the service looks in the annotations table for a record with
+ // the exact URL we give it, and users won't have such records for URLs
+ // they don't visit, and users won't visit the web app's URL template,
+ // they'll only visit URLs derived from that template (i.e. with %s
+ // in the template replaced by the URL of the content being handled).
+
+ if (/^https?$/.test(uri.scheme) && this._prefSvc.getBoolPref("browser.chrome.favicons"))
+ return uri.prePath + "/favicon.ico";
+
+ return "";
+ },
+
+ _getIconURLForSystemDefault: function(aHandlerInfo) {
+ // Handler info objects for MIME types on some OSes implement a property bag
+ // interface from which we can get an icon for the default app, so if we're
+ // dealing with a MIME type on one of those OSes, then try to get the icon.
+ if ("wrappedHandlerInfo" in aHandlerInfo) {
+ let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo;
+
+ if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ wrappedHandlerInfo instanceof Ci.nsIPropertyBag) {
+ try {
+ let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
+ if (url)
+ return url + "?size=16";
+ }
+ catch (ex) {}
+ }
+ }
+
+ // If this isn't a MIME type object on an OS that supports retrieving
+ // the icon, or if we couldn't retrieve the icon for some other reason,
+ // then use a generic icon.
+ return ICON_URL_APP;
+ }
+
+};
diff --git a/browser/components/preferences/in-content/applications.xul b/browser/components/preferences/in-content/applications.xul
new file mode 100644
index 000000000..1d4723493
--- /dev/null
+++ b/browser/components/preferences/in-content/applications.xul
@@ -0,0 +1,95 @@
+# 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/.
+
+<!-- Applications panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/applications.js"/>
+
+<preferences id="feedsPreferences" hidden="true" data-category="paneApplications">
+ <preference id="browser.feeds.handler"
+ name="browser.feeds.handler"
+ type="string"/>
+ <preference id="browser.feeds.handler.default"
+ name="browser.feeds.handler.default"
+ type="string"/>
+ <preference id="browser.feeds.handlers.application"
+ name="browser.feeds.handlers.application"
+ type="file"/>
+ <preference id="browser.feeds.handlers.webservice"
+ name="browser.feeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.videoFeeds.handler"
+ name="browser.videoFeeds.handler"
+ type="string"/>
+ <preference id="browser.videoFeeds.handler.default"
+ name="browser.videoFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.videoFeeds.handlers.application"
+ name="browser.videoFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.videoFeeds.handlers.webservice"
+ name="browser.videoFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.audioFeeds.handler"
+ name="browser.audioFeeds.handler"
+ type="string"/>
+ <preference id="browser.audioFeeds.handler.default"
+ name="browser.audioFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.audioFeeds.handlers.application"
+ name="browser.audioFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.audioFeeds.handlers.webservice"
+ name="browser.audioFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="pref.downloads.disable_button.edit_actions"
+ name="pref.downloads.disable_button.edit_actions"
+ type="bool"/>
+</preferences>
+
+<keyset data-category="paneApplications">
+ <!-- Ctrl+f/k focus the search box in the Applications pane.
+ These <key>s have oncommand attributes because of bug 371900. -->
+ <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
+ <key key="&focusSearch2.key;" modifiers="accel" id="focusSearch2" oncommand=";"/>
+</keyset>
+
+<hbox id="header-applications"
+ class="header"
+ hidden="true"
+ data-category="paneApplications">
+ <label class="header-name" flex="1">&paneApplications.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<vbox id="applicationsContent"
+ data-category="paneApplications"
+ hidden="true"
+ flex="1">
+ <hbox>
+ <textbox id="filter" flex="1"
+ type="search"
+ placeholder="&filter.emptytext;"
+ aria-controls="handlersView"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <richlistbox id="handlersView" orient="vertical" persist="lastSelectedType"
+ preference="pref.downloads.disable_button.edit_actions"
+ flex="1">
+ <listheader equalsize="always">
+ <treecol id="typeColumn" label="&typeColumn.label;" value="type"
+ accesskey="&typeColumn.accesskey;" persist="sortDirection"
+ flex="1" sortDirection="ascending"/>
+ <treecol id="actionColumn" label="&actionColumn2.label;" value="action"
+ accesskey="&actionColumn2.accesskey;" persist="sortDirection"
+ flex="1"/>
+ </listheader>
+ </richlistbox>
+</vbox>
diff --git a/browser/components/preferences/in-content/containers.js b/browser/components/preferences/in-content/containers.js
new file mode 100644
index 000000000..758e45fff
--- /dev/null
+++ b/browser/components/preferences/in-content/containers.js
@@ -0,0 +1,73 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
+
+const containersBundle = Services.strings.createBundle("chrome://browser/locale/preferences/containers.properties");
+
+const defaultContainerIcon = "fingerprint";
+const defaultContainerColor = "blue";
+
+let gContainersPane = {
+
+ init() {
+ this._list = document.getElementById("containersView");
+
+ document.getElementById("backContainersLink").addEventListener("click", function () {
+ gotoPref("privacy");
+ });
+
+ this._rebuildView();
+ },
+
+ _rebuildView() {
+ const containers = ContextualIdentityService.getIdentities();
+ while (this._list.firstChild) {
+ this._list.firstChild.remove();
+ }
+ for (let container of containers) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("containerName", ContextualIdentityService.getUserContextLabel(container.userContextId));
+ item.setAttribute("containerIcon", container.icon);
+ item.setAttribute("containerColor", container.color);
+ item.setAttribute("userContextId", container.userContextId);
+
+ this._list.appendChild(item);
+ }
+ },
+
+ onRemoveClick(button) {
+ let userContextId = button.getAttribute("value");
+ ContextualIdentityService.remove(userContextId);
+ this._rebuildView();
+ },
+ onPeferenceClick(button) {
+ this.openPreferenceDialog(button.getAttribute("value"));
+ },
+
+ onAddButtonClick(button) {
+ this.openPreferenceDialog(null);
+ },
+
+ openPreferenceDialog(userContextId) {
+ let identity = {
+ name: "",
+ icon: defaultContainerIcon,
+ color: defaultContainerColor
+ };
+ let title;
+ if (userContextId) {
+ identity = ContextualIdentityService.getIdentityFromId(userContextId);
+ // This is required to get the translation string from defaults
+ identity.name = ContextualIdentityService.getUserContextLabel(identity.userContextId);
+ title = containersBundle.formatStringFromName("containers.updateContainerTitle", [identity.name], 1);
+ }
+
+ const params = { userContextId, identity, windowTitle: title };
+ gSubDialog.open("chrome://browser/content/preferences/containers.xul",
+ null, params);
+ }
+
+};
diff --git a/browser/components/preferences/in-content/containers.xul b/browser/components/preferences/in-content/containers.xul
new file mode 100644
index 000000000..e83bac1c3
--- /dev/null
+++ b/browser/components/preferences/in-content/containers.xul
@@ -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/.
+
+<!-- Containers panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/containers.js"/>
+
+<preferences id="containerPreferences" hidden="true" data-category="paneContainer">
+ <!-- Containers -->
+ <preference id="privacy.userContext.enabled"
+ name="privacy.userContext.enabled"
+ type="bool"/>
+
+</preferences>
+
+<hbox hidden="true"
+ class="container-header-links"
+ data-category="paneContainers">
+ <label class="text-link" id="backContainersLink" value="&backLink.label;" />
+</hbox>
+
+<hbox id="header-containers"
+ class="header"
+ hidden="true"
+ data-category="paneContainers">
+ <label class="header-name" flex="1">&paneContainers.title;</label>
+ <button class="help-button"
+ aria-label="&helpButton.label;"/>
+</hbox>
+
+<!-- Containers -->
+<groupbox id="browserContainersGroup" data-category="paneContainers" hidden="true">
+ <vbox id="browserContainersbox">
+
+ <richlistbox id="containersView" orient="vertical" persist="lastSelectedType"
+ flex="1">
+ <listheader equalsize="always">
+ <treecol id="typeColumn" label="&label.label;" value="type"
+ persist="sortDirection"
+ flex="1" sortDirection="ascending"/>
+ <treecol id="actionColumn" value="action"
+ persist="sortDirection"
+ flex="1"/>
+ </listheader>
+ </richlistbox>
+ </vbox>
+ <vbox>
+ <hbox flex="1">
+ <button onclick="gContainersPane.onAddButtonClick();" accesskey="&addButton.accesskey;" label="&addButton.label;"/>
+ </hbox>
+ </vbox>
+</groupbox>
diff --git a/browser/components/preferences/in-content/content.js b/browser/components/preferences/in-content/content.js
new file mode 100644
index 000000000..5ba334b02
--- /dev/null
+++ b/browser/components/preferences/in-content/content.js
@@ -0,0 +1,294 @@
+/* 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/. */
+
+XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function () {
+ try {
+ let alertsService = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+ // This will throw if manualDoNotDisturb isn't implemented.
+ alertsService.manualDoNotDisturb;
+ return alertsService;
+ } catch (ex) {
+ return undefined;
+ }
+});
+
+var gContentPane = {
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gContentPane));
+ }
+
+ // Initializes the fonts dropdowns displayed in this pane.
+ this._rebuildFonts();
+ var menulist = document.getElementById("defaultFont");
+ if (menulist.selectedIndex == -1) {
+ menulist.value = FontBuilder.readFontSelection(menulist);
+ }
+
+ // Show translation preferences if we may:
+ const prefName = "browser.translation.ui.show";
+ if (Services.prefs.getBoolPref(prefName)) {
+ let row = document.getElementById("translationBox");
+ row.removeAttribute("hidden");
+ // Showing attribution only for Bing Translator.
+ Components.utils.import("resource:///modules/translation/Translation.jsm");
+ if (Translation.translationEngine == "bing") {
+ document.getElementById("bingAttribution").removeAttribute("hidden");
+ }
+ }
+
+ if (AlertsServiceDND) {
+ let notificationsDoNotDisturbRow =
+ document.getElementById("notificationsDoNotDisturbRow");
+ notificationsDoNotDisturbRow.removeAttribute("hidden");
+ if (AlertsServiceDND.manualDoNotDisturb) {
+ let notificationsDoNotDisturb =
+ document.getElementById("notificationsDoNotDisturb");
+ notificationsDoNotDisturb.setAttribute("checked", true);
+ }
+ }
+
+ setEventListener("font.language.group", "change",
+ gContentPane._rebuildFonts);
+ setEventListener("notificationsPolicyButton", "command",
+ gContentPane.showNotificationExceptions);
+ setEventListener("popupPolicyButton", "command",
+ gContentPane.showPopupExceptions);
+ setEventListener("advancedFonts", "command",
+ gContentPane.configureFonts);
+ setEventListener("colors", "command",
+ gContentPane.configureColors);
+ setEventListener("chooseLanguage", "command",
+ gContentPane.showLanguages);
+ setEventListener("translationAttributionImage", "click",
+ gContentPane.openTranslationProviderAttribution);
+ setEventListener("translateButton", "command",
+ gContentPane.showTranslationExceptions);
+ setEventListener("notificationsDoNotDisturb", "command",
+ gContentPane.toggleDoNotDisturbNotifications);
+
+ let notificationInfoURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
+ document.getElementById("notificationsPolicyLearnMore").setAttribute("href",
+ notificationInfoURL);
+
+ let drmInfoURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
+ document.getElementById("playDRMContentLink").setAttribute("href", drmInfoURL);
+ let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
+ // Force-disable/hide on WinXP:
+ if (navigator.platform.toLowerCase().startsWith("win")) {
+ emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
+ }
+ if (!emeUIEnabled) {
+ // Don't want to rely on .hidden for the toplevel groupbox because
+ // of the pane hiding/showing code potentially interfering:
+ document.getElementById("drmGroup").setAttribute("style", "display: none !important");
+ }
+ },
+
+ // UTILITY FUNCTIONS
+
+ /**
+ * Utility function to enable/disable the button specified by aButtonID based
+ * on the value of the Boolean preference specified by aPreferenceID.
+ */
+ updateButtons: function (aButtonID, aPreferenceID)
+ {
+ var button = document.getElementById(aButtonID);
+ var preference = document.getElementById(aPreferenceID);
+ button.disabled = preference.value != true;
+ return undefined;
+ },
+
+ // BEGIN UI CODE
+
+ /*
+ * Preferences:
+ *
+ * dom.disable_open_during_load
+ * - true if popups are blocked by default, false otherwise
+ */
+
+ // NOTIFICATIONS
+
+ /**
+ * Displays the notifications exceptions dialog where specific site notification
+ * preferences can be set.
+ */
+ showNotificationExceptions()
+ {
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let params = { permissionType: "desktop-notification" };
+ params.windowTitle = bundlePreferences.getString("notificationspermissionstitle");
+ params.introText = bundlePreferences.getString("notificationspermissionstext4");
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ "resizable=yes", params);
+
+ try {
+ Services.telemetry
+ .getHistogramById("WEB_NOTIFICATION_EXCEPTIONS_OPENED").add();
+ } catch (e) {}
+ },
+
+
+ // POP-UPS
+
+ /**
+ * Displays the popup exceptions dialog where specific site popup preferences
+ * can be set.
+ */
+ showPopupExceptions: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible: false, sessionVisible: false, allowVisible: true,
+ prefilledHost: "", permissionType: "popup" }
+ params.windowTitle = bundlePreferences.getString("popuppermissionstitle");
+ params.introText = bundlePreferences.getString("popuppermissionstext");
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ "resizable=yes", params);
+ },
+
+ // FONTS
+
+ /**
+ * Populates the default font list in UI.
+ */
+ _rebuildFonts: function ()
+ {
+ var preferences = document.getElementById("contentPreferences");
+ // Ensure preferences are "visible" to ensure bindings work.
+ preferences.hidden = false;
+ // Force flush:
+ preferences.clientHeight;
+ var langGroupPref = document.getElementById("font.language.group");
+ this._selectDefaultLanguageGroup(langGroupPref.value,
+ this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
+ },
+
+ /**
+ *
+ */
+ _selectDefaultLanguageGroup: function (aLanguageGroup, aIsSerif)
+ {
+ const kFontNameFmtSerif = "font.name.serif.%LANG%";
+ const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
+ const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
+ const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
+ const kFontSizeFmtVariable = "font.size.variable.%LANG%";
+
+ var preferences = document.getElementById("contentPreferences");
+ var prefs = [{ format : aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif,
+ type : "fontname",
+ element : "defaultFont",
+ fonttype : aIsSerif ? "serif" : "sans-serif" },
+ { format : aIsSerif ? kFontNameListFmtSerif : kFontNameListFmtSansSerif,
+ type : "unichar",
+ element : null,
+ fonttype : aIsSerif ? "serif" : "sans-serif" },
+ { format : kFontSizeFmtVariable,
+ type : "int",
+ element : "defaultFontSize",
+ fonttype : null }];
+ for (var i = 0; i < prefs.length; ++i) {
+ var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
+ if (!preference) {
+ preference = document.createElement("preference");
+ var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
+ preference.id = name;
+ preference.setAttribute("name", name);
+ preference.setAttribute("type", prefs[i].type);
+ preferences.appendChild(preference);
+ }
+
+ if (!prefs[i].element)
+ continue;
+
+ var element = document.getElementById(prefs[i].element);
+ if (element) {
+ element.setAttribute("preference", preference.id);
+
+ if (prefs[i].fonttype)
+ FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+
+ preference.setElementValue(element);
+ }
+ }
+ },
+
+ /**
+ * Returns the type of the current default font for the language denoted by
+ * aLanguageGroup.
+ */
+ _readDefaultFontTypeForLanguage: function (aLanguageGroup)
+ {
+ const kDefaultFontType = "font.default.%LANG%";
+ var defaultFontTypePref = kDefaultFontType.replace(/%LANG%/, aLanguageGroup);
+ var preference = document.getElementById(defaultFontTypePref);
+ if (!preference) {
+ preference = document.createElement("preference");
+ preference.id = defaultFontTypePref;
+ preference.setAttribute("name", defaultFontTypePref);
+ preference.setAttribute("type", "string");
+ preference.setAttribute("onchange", "gContentPane._rebuildFonts();");
+ document.getElementById("contentPreferences").appendChild(preference);
+ }
+ return preference.value;
+ },
+
+ /**
+ * Displays the fonts dialog, where web page font names and sizes can be
+ * configured.
+ */
+ configureFonts: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/fonts.xul", "resizable=no");
+ },
+
+ /**
+ * Displays the colors dialog, where default web page/link/etc. colors can be
+ * configured.
+ */
+ configureColors: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/colors.xul", "resizable=no");
+ },
+
+ // LANGUAGES
+
+ /**
+ * Shows a dialog in which the preferred language for web content may be set.
+ */
+ showLanguages: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/languages.xul");
+ },
+
+ /**
+ * Displays the translation exceptions dialog where specific site and language
+ * translation preferences can be set.
+ */
+ showTranslationExceptions: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/translation.xul");
+ },
+
+ openTranslationProviderAttribution: function ()
+ {
+ Components.utils.import("resource:///modules/translation/Translation.jsm");
+ Translation.openProviderAttribution();
+ },
+
+ toggleDoNotDisturbNotifications: function (event)
+ {
+ AlertsServiceDND.manualDoNotDisturb = event.target.checked;
+ },
+};
diff --git a/browser/components/preferences/in-content/content.xul b/browser/components/preferences/in-content/content.xul
new file mode 100644
index 000000000..c646c16a2
--- /dev/null
+++ b/browser/components/preferences/in-content/content.xul
@@ -0,0 +1,209 @@
+# 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/.
+
+<!-- Content panel -->
+
+<preferences id="contentPreferences" hidden="true" data-category="paneContent">
+
+ <!-- DRM content -->
+ <preference id="media.eme.enabled"
+ name="media.eme.enabled"
+ type="bool"/>
+
+ <!-- Popups -->
+ <preference id="dom.disable_open_during_load"
+ name="dom.disable_open_during_load"
+ type="bool"/>
+
+ <!-- Fonts -->
+ <preference id="font.language.group"
+ name="font.language.group"
+ type="wstring"/>
+
+ <!-- Languages -->
+ <preference id="browser.translation.detectLanguage"
+ name="browser.translation.detectLanguage"
+ type="bool"/>
+</preferences>
+
+<script type="application/javascript"
+ src="chrome://mozapps/content/preferences/fontbuilder.js"/>
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/content.js"/>
+
+<hbox id="header-content"
+ class="header"
+ hidden="true"
+ data-category="paneContent">
+ <label class="header-name" flex="1">&paneContent.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<groupbox id="drmGroup" data-category="paneContent" hidden="true">
+ <caption><label>&drmContent.label;</label></caption>
+ <grid id="contentGrid2">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="contentRows-2">
+ <row id="playDRMContentRow">
+ <vbox align="start">
+ <checkbox id="playDRMContent" preference="media.eme.enabled"
+ label="&playDRMContent.label;" accesskey="&playDRMContent.accesskey;"/>
+ </vbox>
+ <hbox pack="end" align="center">
+ <label id="playDRMContentLink" class="text-link" value="&playDRMContent.learnMore.label;"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<groupbox id="notificationsGroup" data-category="paneContent" hidden="true">
+ <caption><label>&notificationsPolicy.label;</label></caption>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row id="notificationsPolicyRow" align="center">
+ <hbox align="start">
+ <label id="notificationsPolicy">&notificationsPolicyDesc3.label;</label>
+ <label id="notificationsPolicyLearnMore"
+ class="text-link"
+ value="&notificationsPolicyLearnMore.label;"/>
+ </hbox>
+ <hbox pack="end">
+ <button id="notificationsPolicyButton" label="&notificationsPolicyButton.label;"
+ accesskey="&notificationsPolicyButton.accesskey;"/>
+ </hbox>
+ </row>
+ <row id="notificationsDoNotDisturbRow" hidden="true">
+ <vbox align="start">
+ <checkbox id="notificationsDoNotDisturb" label="&notificationsDoNotDisturb.label;"
+ accesskey="&notificationsDoNotDisturb.accesskey;"/>
+ <label id="notificationsDoNotDisturbDetails"
+ class="indent"
+ value="&notificationsDoNotDisturbDetails.value;"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<groupbox id="miscGroup" data-category="paneContent" hidden="true">
+ <caption><label>&popups.label;</label></caption>
+ <grid id="contentGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="contentRows-1">
+ <row id="popupPolicyRow">
+ <vbox align="start">
+ <checkbox id="popupPolicy" preference="dom.disable_open_during_load"
+ label="&blockPopups.label;" accesskey="&blockPopups.accesskey;"
+ onsyncfrompreference="return gContentPane.updateButtons('popupPolicyButton',
+ 'dom.disable_open_during_load');"/>
+ </vbox>
+ <hbox pack="end">
+ <button id="popupPolicyButton" label="&popupExceptions.label;"
+ accesskey="&popupExceptions.accesskey;"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<!-- Fonts and Colors -->
+<groupbox id="fontsGroup" data-category="paneContent" hidden="true">
+ <caption><label>&fontsAndColors.label;</label></caption>
+
+ <grid id="fontsGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="fontsRows">
+ <row id="fontRow">
+ <hbox align="center">
+ <label control="defaultFont" accesskey="&defaultFont.accesskey;">&defaultFont.label;</label>
+ <menulist id="defaultFont" delayprefsave="true"/>
+ <label id="defaultFontSizeLabel" control="defaultFontSize" accesskey="&defaultSize.accesskey;">&defaultSize.label;</label>
+ <menulist id="defaultFontSize" delayprefsave="true">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <button id="advancedFonts" icon="select-font"
+ label="&advancedFonts.label;"
+ accesskey="&advancedFonts.accesskey;"/>
+ </row>
+ <row id="colorsRow">
+ <hbox/>
+ <button id="colors" icon="select-color"
+ label="&colors.label;"
+ accesskey="&colors.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<!-- Languages -->
+<groupbox id="languagesGroup" data-category="paneContent" hidden="true">
+ <caption><label>&languages.label;</label></caption>
+
+ <hbox id="languagesBox" align="center">
+ <description flex="1" control="chooseLanguage">&chooseLanguage.label;</description>
+ <button id="chooseLanguage"
+ label="&chooseButton.label;"
+ accesskey="&chooseButton.accesskey;"/>
+ </hbox>
+
+ <hbox id="translationBox" hidden="true">
+ <hbox align="center" flex="1">
+ <checkbox id="translate" preference="browser.translation.detectLanguage"
+ label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
+ onsyncfrompreference="return gContentPane.updateButtons('translateButton',
+ 'browser.translation.detectLanguage');"/>
+ <hbox id="bingAttribution" hidden="true">
+ <label>&translation.options.attribution.beforeLogo;</label>
+ <separator orient="vertical" class="thin"/>
+ <image id="translationAttributionImage" aria-label="Microsoft Translator"
+ src="chrome://browser/content/microsoft-translator-attribution.png"/>
+ <separator orient="vertical" class="thin"/>
+ <label>&translation.options.attribution.afterLogo;</label>
+ </hbox>
+ </hbox>
+ <button id="translateButton" label="&translateExceptions.label;"
+ accesskey="&translateExceptions.accesskey;"/>
+ </hbox>
+</groupbox>
diff --git a/browser/components/preferences/in-content/jar.mn b/browser/components/preferences/in-content/jar.mn
new file mode 100644
index 000000000..52f536e96
--- /dev/null
+++ b/browser/components/preferences/in-content/jar.mn
@@ -0,0 +1,18 @@
+# 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/preferences/in-content/preferences.js
+* content/browser/preferences/in-content/preferences.xul
+ content/browser/preferences/in-content/subdialogs.js
+
+ content/browser/preferences/in-content/main.js
+ content/browser/preferences/in-content/privacy.js
+ content/browser/preferences/in-content/containers.js
+ content/browser/preferences/in-content/advanced.js
+ content/browser/preferences/in-content/applications.js
+ content/browser/preferences/in-content/content.js
+ content/browser/preferences/in-content/sync.js
+ content/browser/preferences/in-content/security.js
+ content/browser/preferences/in-content/search.js
diff --git a/browser/components/preferences/in-content/main.js b/browser/components/preferences/in-content/main.js
new file mode 100644
index 000000000..4f20ba8c3
--- /dev/null
+++ b/browser/components/preferences/in-content/main.js
@@ -0,0 +1,721 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Downloads.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+Components.utils.import("resource:///modules/ShellService.jsm");
+Components.utils.import("resource:///modules/TransientPrefs.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+if (AppConstants.E10S_TESTING_ONLY) {
+ XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+}
+
+var gMainPane = {
+ /**
+ * Initialization of this.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gMainPane));
+ }
+
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ this.updateSetDefaultBrowser();
+ if (AppConstants.platform == "win") {
+ // In Windows 8 we launch the control panel since it's the only
+ // way to get all file type association prefs. So we don't know
+ // when the user will select the default. We refresh here periodically
+ // in case the default changes. On other Windows OS's defaults can also
+ // be set while the prefs are open.
+ window.setInterval(this.updateSetDefaultBrowser.bind(this), 1000);
+ }
+ }
+
+ // set up the "use current page" label-changing listener
+ this._updateUseCurrentButton();
+ window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false);
+
+ this.updateBrowserStartupLastSession();
+
+ if (AppConstants.platform == "win") {
+ // Functionality for "Show tabs in taskbar" on Windows 7 and up.
+ try {
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ let ver = parseFloat(sysInfo.getProperty("version"));
+ let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
+ showTabsInTaskbar.hidden = ver < 6.1;
+ } catch (ex) {}
+ }
+
+ // The "closing multiple tabs" and "opening multiple tabs might slow down
+ // &brandShortName;" warnings provide options for not showing these
+ // warnings again. When the user disabled them, we provide checkboxes to
+ // re-enable the warnings.
+ if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnClose"))
+ document.getElementById("warnCloseMultiple").hidden = true;
+ if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnOpen"))
+ document.getElementById("warnOpenMany").hidden = true;
+
+ setEventListener("browser.privatebrowsing.autostart", "change",
+ gMainPane.updateBrowserStartupLastSession);
+ setEventListener("browser.download.dir", "change",
+ gMainPane.displayDownloadDirPref);
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ setEventListener("setDefaultButton", "command",
+ gMainPane.setDefaultBrowser);
+ }
+ setEventListener("useCurrent", "command",
+ gMainPane.setHomePageToCurrent);
+ setEventListener("useBookmark", "command",
+ gMainPane.setHomePageToBookmark);
+ setEventListener("restoreDefaultHomePage", "command",
+ gMainPane.restoreDefaultHomePage);
+ setEventListener("chooseFolder", "command",
+ gMainPane.chooseFolder);
+
+ if (AppConstants.E10S_TESTING_ONLY) {
+ setEventListener("e10sAutoStart", "command",
+ gMainPane.enableE10SChange);
+ let e10sCheckbox = document.getElementById("e10sAutoStart");
+
+ let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+ let e10sTempPref = document.getElementById("e10sTempPref");
+ let e10sForceEnable = document.getElementById("e10sForceEnable");
+
+ let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
+
+ if (preffedOn) {
+ // The checkbox is checked if e10s is preffed on and enabled.
+ e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
+
+ // but if it's force disabled, then the checkbox is disabled.
+ e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
+ }
+ }
+
+ if (AppConstants.MOZ_DEV_EDITION) {
+ let uAppData = OS.Constants.Path.userApplicationDataDir;
+ let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
+
+ setEventListener("separateProfileMode", "command", gMainPane.separateProfileModeChange);
+ let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
+ setEventListener("getStarted", "click", gMainPane.onGetStarted);
+
+ OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
+ () => separateProfileModeCheckbox.checked = true);
+ }
+
+ // Notify observers that the UI is now ready
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "main-pane-loaded", null);
+ },
+
+ enableE10SChange: function ()
+ {
+ if (AppConstants.E10S_TESTING_ONLY) {
+ let e10sCheckbox = document.getElementById("e10sAutoStart");
+ let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+ let e10sTempPref = document.getElementById("e10sTempPref");
+
+ let prefsToChange;
+ if (e10sCheckbox.checked) {
+ // Enabling e10s autostart
+ prefsToChange = [e10sPref];
+ } else {
+ // Disabling e10s autostart
+ prefsToChange = [e10sPref];
+ if (e10sTempPref.value) {
+ prefsToChange.push(e10sTempPref);
+ }
+ }
+
+ let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
+ true, false);
+ if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
+ for (let prefToChange of prefsToChange) {
+ prefToChange.value = e10sCheckbox.checked;
+ }
+
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ }
+
+ // Revert the checkbox in case we didn't quit
+ e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
+ }
+ },
+
+ separateProfileModeChange: function ()
+ {
+ if (AppConstants.MOZ_DEV_EDITION) {
+ function quitApp() {
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
+ }
+ function revertCheckbox(error) {
+ separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
+ if (error) {
+ Cu.reportError("Failed to toggle separate profile mode: " + error);
+ }
+ }
+ function createOrRemoveSpecialDevEditionFile(onSuccess) {
+ let uAppData = OS.Constants.Path.userApplicationDataDir;
+ let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
+
+ if (separateProfileModeCheckbox.checked) {
+ OS.File.remove(ignoreSeparateProfile).then(onSuccess, revertCheckbox);
+ } else {
+ OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(onSuccess, revertCheckbox);
+ }
+ }
+
+ let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
+ let button_index = confirmRestartPrompt(separateProfileModeCheckbox.checked,
+ 0, false, true);
+ switch (button_index) {
+ case CONFIRM_RESTART_PROMPT_CANCEL:
+ revertCheckbox();
+ return;
+ case CONFIRM_RESTART_PROMPT_RESTART_NOW:
+ const Cc = Components.classes, Ci = Components.interfaces;
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (!cancelQuit.data) {
+ createOrRemoveSpecialDevEditionFile(quitApp);
+ return;
+ }
+
+ // Revert the checkbox in case we didn't quit
+ revertCheckbox();
+ return;
+ case CONFIRM_RESTART_PROMPT_RESTART_LATER:
+ createOrRemoveSpecialDevEditionFile();
+ return;
+ }
+ }
+ },
+
+ onGetStarted: function (aEvent) {
+ if (AppConstants.MOZ_DEV_EDITION) {
+ const Cc = Components.classes, Ci = Components.interfaces;
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ let win = wm.getMostRecentWindow("navigator:browser");
+
+ if (win) {
+ let accountsTab = win.gBrowser.addTab("about:accounts?action=signin&entrypoint=dev-edition-setup");
+ win.gBrowser.selectedTab = accountsTab;
+ }
+ }
+ },
+
+ // HOME PAGE
+
+ /*
+ * Preferences:
+ *
+ * browser.startup.homepage
+ * - the user's home page, as a string; if the home page is a set of tabs,
+ * this will be those URLs separated by the pipe character "|"
+ * browser.startup.page
+ * - what page(s) to show when the user starts the application, as an integer:
+ *
+ * 0: a blank page
+ * 1: the home page (as set by the browser.startup.homepage pref)
+ * 2: the last page the user visited (DEPRECATED)
+ * 3: windows and tabs from the last session (a.k.a. session restore)
+ *
+ * The deprecated option is not exposed in UI; however, if the user has it
+ * selected and doesn't change the UI for this preference, the deprecated
+ * option is preserved.
+ */
+
+ syncFromHomePref: function ()
+ {
+ let homePref = document.getElementById("browser.startup.homepage");
+
+ // If the pref is set to about:home or about:newtab, set the value to ""
+ // to show the placeholder text (about:home title) rather than
+ // exposing those URLs to users.
+ let defaultBranch = Services.prefs.getDefaultBranch("");
+ let defaultValue = defaultBranch.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ let currentValue = homePref.value.toLowerCase();
+ if (currentValue == "about:home" ||
+ (currentValue == defaultValue && currentValue == "about:newtab")) {
+ return "";
+ }
+
+ // If the pref is actually "", show about:blank. The actual home page
+ // loading code treats them the same, and we don't want the placeholder text
+ // to be shown.
+ if (homePref.value == "")
+ return "about:blank";
+
+ // Otherwise, show the actual pref value.
+ return undefined;
+ },
+
+ syncToHomePref: function (value)
+ {
+ // If the value is "", use about:home.
+ if (value == "")
+ return "about:home";
+
+ // Otherwise, use the actual textbox value.
+ return undefined;
+ },
+
+ /**
+ * Sets the home page to the current displayed page (or frontmost tab, if the
+ * most recent browser window contains multiple tabs), updating preference
+ * window UI to reflect this.
+ */
+ setHomePageToCurrent: function ()
+ {
+ let homePage = document.getElementById("browser.startup.homepage");
+ let tabs = this._getTabsForHomePage();
+ function getTabURI(t) {
+ return t.linkedBrowser.currentURI.spec;
+ }
+
+ // FIXME Bug 244192: using dangerous "|" joiner!
+ if (tabs.length)
+ homePage.value = tabs.map(getTabURI).join("|");
+ },
+
+ /**
+ * Displays a dialog in which the user can select a bookmark to use as home
+ * page. If the user selects a bookmark, that bookmark's name is displayed in
+ * UI and the bookmark's address is stored to the home page preference.
+ */
+ setHomePageToBookmark: function ()
+ {
+ var rv = { urls: null, names: null };
+ gSubDialog.open("chrome://browser/content/preferences/selectBookmark.xul",
+ "resizable=yes, modal=yes", rv,
+ this._setHomePageToBookmarkClosed.bind(this, rv));
+ },
+
+ _setHomePageToBookmarkClosed: function(rv, aEvent) {
+ if (aEvent.detail.button != "accept")
+ return;
+ if (rv.urls && rv.names) {
+ var homePage = document.getElementById("browser.startup.homepage");
+
+ // XXX still using dangerous "|" joiner!
+ homePage.value = rv.urls.join("|");
+ }
+ },
+
+ /**
+ * Switches the "Use Current Page" button between its singular and plural
+ * forms.
+ */
+ _updateUseCurrentButton: function () {
+ let useCurrent = document.getElementById("useCurrent");
+
+
+ let tabs = this._getTabsForHomePage();
+
+ if (tabs.length > 1)
+ useCurrent.label = useCurrent.getAttribute("label2");
+ else
+ useCurrent.label = useCurrent.getAttribute("label1");
+
+ // In this case, the button's disabled state is set by preferences.xml.
+ let prefName = "pref.browser.homepage.disable_button.current_page";
+ if (document.getElementById(prefName).locked)
+ return;
+
+ useCurrent.disabled = !tabs.length
+ },
+
+ _getTabsForHomePage: function ()
+ {
+ var win;
+ var tabs = [];
+
+ const Cc = Components.classes, Ci = Components.interfaces;
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ win = wm.getMostRecentWindow("navigator:browser");
+
+ if (win && win.document.documentElement
+ .getAttribute("windowtype") == "navigator:browser") {
+ // We should only include visible & non-pinned tabs
+
+ tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs);
+ tabs = tabs.filter(this.isNotAboutPreferences);
+ }
+
+ return tabs;
+ },
+
+ /**
+ * Check to see if a tab is not about:preferences
+ */
+ isNotAboutPreferences: function (aElement, aIndex, aArray)
+ {
+ return !aElement.linkedBrowser.currentURI.spec.startsWith("about:preferences");
+ },
+
+ /**
+ * Restores the default home page as the user's home page.
+ */
+ restoreDefaultHomePage: function ()
+ {
+ var homePage = document.getElementById("browser.startup.homepage");
+ homePage.value = homePage.defaultValue;
+ },
+
+ // DOWNLOADS
+
+ /*
+ * Preferences:
+ *
+ * browser.download.useDownloadDir - bool
+ * True - Save files directly to the folder configured via the
+ * browser.download.folderList preference.
+ * False - Always ask the user where to save a file and default to
+ * browser.download.lastDir when displaying a folder picker dialog.
+ * browser.download.dir - local file handle
+ * A local folder the user may have selected for downloaded files to be
+ * saved. Migration of other browser settings may also set this path.
+ * This folder is enabled when folderList equals 2.
+ * browser.download.lastDir - local file handle
+ * May contain the last folder path accessed when the user browsed
+ * via the file save-as dialog. (see contentAreaUtils.js)
+ * browser.download.folderList - int
+ * Indicates the location users wish to save downloaded files too.
+ * It is also used to display special file labels when the default
+ * download location is either the Desktop or the Downloads folder.
+ * Values:
+ * 0 - The desktop is the default download location.
+ * 1 - The system's downloads folder is the default download location.
+ * 2 - The default download location is elsewhere as specified in
+ * browser.download.dir.
+ * browser.download.downloadDir
+ * deprecated.
+ * browser.download.defaultFolder
+ * deprecated.
+ */
+
+ /**
+ * Enables/disables the folder field and Browse button based on whether a
+ * default download directory is being used.
+ */
+ readUseDownloadDir: function ()
+ {
+ var downloadFolder = document.getElementById("downloadFolder");
+ var chooseFolder = document.getElementById("chooseFolder");
+ var preference = document.getElementById("browser.download.useDownloadDir");
+ downloadFolder.disabled = !preference.value || preference.locked;
+ chooseFolder.disabled = !preference.value || preference.locked;
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ /**
+ * Displays a file picker in which the user can choose the location where
+ * downloads are automatically saved, updating preferences and UI in
+ * response to the choice, if one is made.
+ */
+ chooseFolder()
+ {
+ return this.chooseFolderTask().catch(Components.utils.reportError);
+ },
+ chooseFolderTask: Task.async(function* ()
+ {
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let title = bundlePreferences.getString("chooseDownloadFolderTitle");
+ let folderListPref = document.getElementById("browser.download.folderList");
+ let currentDirPref = yield this._indexToFolder(folderListPref.value);
+ let defDownloads = yield this._indexToFolder(1);
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(Components.interfaces.nsIFilePicker);
+
+ fp.init(window, title, Components.interfaces.nsIFilePicker.modeGetFolder);
+ fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
+ // First try to open what's currently configured
+ if (currentDirPref && currentDirPref.exists()) {
+ fp.displayDirectory = currentDirPref;
+ } // Try the system's download dir
+ else if (defDownloads && defDownloads.exists()) {
+ fp.displayDirectory = defDownloads;
+ } // Fall back to Desktop
+ else {
+ fp.displayDirectory = yield this._indexToFolder(0);
+ }
+
+ let result = yield new Promise(resolve => fp.open(resolve));
+ if (result != Components.interfaces.nsIFilePicker.returnOK) {
+ return;
+ }
+
+ let downloadDirPref = document.getElementById("browser.download.dir");
+ downloadDirPref.value = fp.file;
+ folderListPref.value = yield this._folderToIndex(fp.file);
+ // Note, the real prefs will not be updated yet, so dnld manager's
+ // userDownloadsDirectory may not return the right folder after
+ // this code executes. displayDownloadDirPref will be called on
+ // the assignment above to update the UI.
+ }),
+
+ /**
+ * Initializes the download folder display settings based on the user's
+ * preferences.
+ */
+ displayDownloadDirPref()
+ {
+ this.displayDownloadDirPrefTask().catch(Components.utils.reportError);
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ displayDownloadDirPrefTask: Task.async(function* ()
+ {
+ var folderListPref = document.getElementById("browser.download.folderList");
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var downloadFolder = document.getElementById("downloadFolder");
+ var currentDirPref = document.getElementById("browser.download.dir");
+
+ // Used in defining the correct path to the folder icon.
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var fph = ios.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ var iconUrlSpec;
+
+ // Display a 'pretty' label or the path in the UI.
+ if (folderListPref.value == 2) {
+ // Custom path selected and is configured
+ downloadFolder.label = this._getDisplayNameOfFile(currentDirPref.value);
+ iconUrlSpec = fph.getURLSpecFromFile(currentDirPref.value);
+ } else if (folderListPref.value == 1) {
+ // 'Downloads'
+ // In 1.5, this pointed to a folder we created called 'My Downloads'
+ // and was available as an option in the 1.5 drop down. On XP this
+ // was in My Documents, on OSX it was in User Docs. In 2.0, we did
+ // away with the drop down option, although the special label was
+ // still supported for the folder if it existed. Because it was
+ // not exposed it was rarely used.
+ // With 3.0, a new desktop folder - 'Downloads' was introduced for
+ // platforms and versions that don't support a default system downloads
+ // folder. See nsDownloadManager for details.
+ downloadFolder.label = bundlePreferences.getString("downloadsFolderName");
+ iconUrlSpec = fph.getURLSpecFromFile(yield this._indexToFolder(1));
+ } else {
+ // 'Desktop'
+ downloadFolder.label = bundlePreferences.getString("desktopFolderName");
+ iconUrlSpec = fph.getURLSpecFromFile(yield this._getDownloadsFolder("Desktop"));
+ }
+ downloadFolder.image = "moz-icon://" + iconUrlSpec + "?size=16";
+ }),
+
+ /**
+ * Returns the textual path of a folder in readable form.
+ */
+ _getDisplayNameOfFile: function (aFolder)
+ {
+ // TODO: would like to add support for 'Downloads on Macintosh HD'
+ // for OS X users.
+ return aFolder ? aFolder.path : "";
+ },
+
+ /**
+ * Returns the Downloads folder. If aFolder is "Desktop", then the Downloads
+ * folder returned is the desktop folder; otherwise, it is a folder whose name
+ * indicates that it is a download folder and whose path is as determined by
+ * the XPCOM directory service via the download manager's attribute
+ * defaultDownloadsDirectory.
+ *
+ * @throws if aFolder is not "Desktop" or "Downloads"
+ */
+ _getDownloadsFolder: Task.async(function* (aFolder)
+ {
+ switch (aFolder) {
+ case "Desktop":
+ var fileLoc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ return fileLoc.get("Desk", Components.interfaces.nsILocalFile);
+ case "Downloads":
+ let downloadsDir = yield Downloads.getSystemDownloadsDirectory();
+ return new FileUtils.File(downloadsDir);
+ }
+ throw "ASSERTION FAILED: folder type should be 'Desktop' or 'Downloads'";
+ }),
+
+ /**
+ * Determines the type of the given folder.
+ *
+ * @param aFolder
+ * the folder whose type is to be determined
+ * @returns integer
+ * 0 if aFolder is the Desktop or is unspecified,
+ * 1 if aFolder is the Downloads folder,
+ * 2 otherwise
+ */
+ _folderToIndex: Task.async(function* (aFolder)
+ {
+ if (!aFolder || aFolder.equals(yield this._getDownloadsFolder("Desktop")))
+ return 0;
+ else if (aFolder.equals(yield this._getDownloadsFolder("Downloads")))
+ return 1;
+ return 2;
+ }),
+
+ /**
+ * Converts an integer into the corresponding folder.
+ *
+ * @param aIndex
+ * an integer
+ * @returns the Desktop folder if aIndex == 0,
+ * the Downloads folder if aIndex == 1,
+ * the folder stored in browser.download.dir
+ */
+ _indexToFolder: Task.async(function* (aIndex)
+ {
+ switch (aIndex) {
+ case 0:
+ return yield this._getDownloadsFolder("Desktop");
+ case 1:
+ return yield this._getDownloadsFolder("Downloads");
+ }
+ var currentDirPref = document.getElementById("browser.download.dir");
+ return currentDirPref.value;
+ }),
+
+ /**
+ * Hide/show the "Show my windows and tabs from last time" option based
+ * on the value of the browser.privatebrowsing.autostart pref.
+ */
+ updateBrowserStartupLastSession: function()
+ {
+ let pbAutoStartPref = document.getElementById("browser.privatebrowsing.autostart");
+ let startupPref = document.getElementById("browser.startup.page");
+ let menu = document.getElementById("browserStartupPage");
+ let option = document.getElementById("browserStartupLastSession");
+ if (pbAutoStartPref.value) {
+ option.setAttribute("disabled", "true");
+ if (option.selected) {
+ menu.selectedItem = document.getElementById("browserStartupHomePage");
+ }
+ } else {
+ option.removeAttribute("disabled");
+ startupPref.updateElements(); // select the correct index in the startup menulist
+ }
+ },
+
+ // TABS
+
+ /*
+ * Preferences:
+ *
+ * browser.link.open_newwindow - int
+ * Determines where links targeting new windows should open.
+ * Values:
+ * 1 - Open in the current window or tab.
+ * 2 - Open in a new window.
+ * 3 - Open in a new tab in the most recent window.
+ * browser.tabs.loadInBackground - bool
+ * True - Whether browser should switch to a new tab opened from a link.
+ * browser.tabs.warnOnClose - bool
+ * True - If when closing a window with multiple tabs the user is warned and
+ * allowed to cancel the action, false to just close the window.
+ * browser.tabs.warnOnOpen - bool
+ * True - Whether the user should be warned when trying to open a lot of
+ * tabs at once (e.g. a large folder of bookmarks), allowing to
+ * cancel the action.
+ * browser.taskbar.previews.enable - bool
+ * True - Tabs are to be shown in Windows 7 taskbar.
+ * False - Only the window is to be shown in Windows 7 taskbar.
+ */
+
+ /**
+ * Determines where a link which opens a new window will open.
+ *
+ * @returns |true| if such links should be opened in new tabs
+ */
+ readLinkTarget: function() {
+ var openNewWindow = document.getElementById("browser.link.open_newwindow");
+ return openNewWindow.value != 2;
+ },
+
+ /**
+ * Determines where a link which opens a new window will open.
+ *
+ * @returns 2 if such links should be opened in new windows,
+ * 3 if such links should be opened in new tabs
+ */
+ writeLinkTarget: function() {
+ var linkTargeting = document.getElementById("linkTargeting");
+ return linkTargeting.checked ? 3 : 2;
+ },
+ /*
+ * Preferences:
+ *
+ * browser.shell.checkDefault
+ * - true if a default-browser check (and prompt to make it so if necessary)
+ * occurs at startup, false otherwise
+ */
+
+ /**
+ * Show button for setting browser as default browser or information that
+ * browser is already the default browser.
+ */
+ updateSetDefaultBrowser: function()
+ {
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ let shellSvc = getShellService();
+ let defaultBrowserBox = document.getElementById("defaultBrowserBox");
+ if (!shellSvc) {
+ defaultBrowserBox.hidden = true;
+ return;
+ }
+ let setDefaultPane = document.getElementById("setDefaultPane");
+ let isDefault = shellSvc.isDefaultBrowser(false, true);
+ setDefaultPane.selectedIndex = isDefault ? 1 : 0;
+ let alwaysCheck = document.getElementById("alwaysCheckDefault");
+ alwaysCheck.disabled = alwaysCheck.disabled ||
+ isDefault && alwaysCheck.checked;
+ }
+ },
+
+ /**
+ * Set browser as the operating system default browser.
+ */
+ setDefaultBrowser: function()
+ {
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ let alwaysCheckPref = document.getElementById("browser.shell.checkDefaultBrowser");
+ alwaysCheckPref.value = true;
+
+ let shellSvc = getShellService();
+ if (!shellSvc)
+ return;
+ try {
+ shellSvc.setDefaultBrowser(true, false);
+ } catch (ex) {
+ Cu.reportError(ex);
+ return;
+ }
+
+ let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
+ document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
+ }
+ },
+};
diff --git a/browser/components/preferences/in-content/main.xul b/browser/components/preferences/in-content/main.xul
new file mode 100644
index 000000000..526bbc714
--- /dev/null
+++ b/browser/components/preferences/in-content/main.xul
@@ -0,0 +1,301 @@
+# 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/.
+
+<!-- General panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/main.js"/>
+
+<preferences id="mainPreferences" hidden="true" data-category="paneGeneral">
+
+#ifdef E10S_TESTING_ONLY
+ <preference id="browser.tabs.remote.autostart"
+ name="browser.tabs.remote.autostart"
+ type="bool"/>
+ <preference id="e10sTempPref"
+ name="browser.tabs.remote.autostart.2"
+ type="bool"/>
+ <preference id="e10sForceEnable"
+ name="browser.tabs.remote.force-enable"
+ type="bool"/>
+#endif
+
+ <!-- Startup -->
+ <preference id="browser.startup.page"
+ name="browser.startup.page"
+ type="int"/>
+ <preference id="browser.startup.homepage"
+ name="browser.startup.homepage"
+ type="wstring"/>
+
+#ifdef HAVE_SHELL_SERVICE
+ <preference id="browser.shell.checkDefaultBrowser"
+ name="browser.shell.checkDefaultBrowser"
+ type="bool"/>
+
+ <preference id="pref.general.disable_button.default_browser"
+ name="pref.general.disable_button.default_browser"
+ type="bool"/>
+#endif
+
+ <preference id="pref.browser.homepage.disable_button.current_page"
+ name="pref.browser.homepage.disable_button.current_page"
+ type="bool"/>
+ <preference id="pref.browser.homepage.disable_button.bookmark_page"
+ name="pref.browser.homepage.disable_button.bookmark_page"
+ type="bool"/>
+ <preference id="pref.browser.homepage.disable_button.restore_default"
+ name="pref.browser.homepage.disable_button.restore_default"
+ type="bool"/>
+
+ <preference id="browser.privatebrowsing.autostart"
+ name="browser.privatebrowsing.autostart"
+ type="bool"/>
+
+ <!-- Downloads -->
+ <preference id="browser.download.useDownloadDir"
+ name="browser.download.useDownloadDir"
+ type="bool"/>
+
+ <preference id="browser.download.folderList"
+ name="browser.download.folderList"
+ type="int"/>
+ <preference id="browser.download.dir"
+ name="browser.download.dir"
+ type="file"/>
+ <!-- Tab preferences
+ Preferences:
+
+ browser.link.open_newwindow
+ 1 opens such links in the most recent window or tab,
+ 2 opens such links in a new window,
+ 3 opens such links in a new tab
+ browser.tabs.loadInBackground
+ - true if display should switch to a new tab which has been opened from a
+ link, false if display shouldn't switch
+ browser.tabs.warnOnClose
+ - true if when closing a window with multiple tabs the user is warned and
+ allowed to cancel the action, false to just close the window
+ browser.tabs.warnOnOpen
+ - true if the user should be warned if he attempts to open a lot of tabs at
+ once (e.g. a large folder of bookmarks), false otherwise
+ browser.taskbar.previews.enable
+ - true if tabs are to be shown in the Windows 7 taskbar
+ -->
+
+ <preference id="browser.link.open_newwindow"
+ name="browser.link.open_newwindow"
+ type="int"/>
+ <preference id="browser.tabs.loadInBackground"
+ name="browser.tabs.loadInBackground"
+ type="bool"
+ inverted="true"/>
+ <preference id="browser.tabs.warnOnClose"
+ name="browser.tabs.warnOnClose"
+ type="bool"/>
+ <preference id="browser.tabs.warnOnOpen"
+ name="browser.tabs.warnOnOpen"
+ type="bool"/>
+ <preference id="browser.sessionstore.restore_on_demand"
+ name="browser.sessionstore.restore_on_demand"
+ type="bool"/>
+#ifdef XP_WIN
+ <preference id="browser.taskbar.previews.enable"
+ name="browser.taskbar.previews.enable"
+ type="bool"/>
+#endif
+ <preference id="browser.ctrlTab.previews"
+ name="browser.ctrlTab.previews"
+ type="bool"/>
+</preferences>
+
+<hbox id="header-general"
+ class="header"
+ hidden="true"
+ data-category="paneGeneral">
+ <label class="header-name" flex="1">&paneGeneral.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<!-- Startup -->
+<groupbox id="startupGroup"
+ data-category="paneGeneral"
+ hidden="true">
+ <caption><label>&startup.label;</label></caption>
+
+#ifdef MOZ_DEV_EDITION
+ <vbox id="separateProfileBox">
+ <checkbox id="separateProfileMode"
+ label="&separateProfileMode.label;"/>
+ <hbox align="center" class="indent">
+ <label id="useFirefoxSync">&useFirefoxSync.label;</label>
+ <label id="getStarted" class="text-link">&getStarted.label;</label>
+ </hbox>
+ </vbox>
+#endif
+
+#ifdef E10S_TESTING_ONLY
+ <checkbox id="e10sAutoStart"
+ label="&e10sEnabled.label;"/>
+#endif
+
+#ifdef HAVE_SHELL_SERVICE
+ <vbox id="defaultBrowserBox">
+ <hbox align="center">
+ <checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
+ label="&alwaysCheckDefault2.label;" accesskey="&alwaysCheckDefault2.accesskey;"/>
+ </hbox>
+ <deck id="setDefaultPane">
+ <hbox align="center" class="indent">
+ <label id="isNotDefaultLabel" flex="1">&isNotDefault.label;</label>
+ <button id="setDefaultButton"
+ label="&setAsMyDefaultBrowser2.label;" accesskey="&setAsMyDefaultBrowser2.accesskey;"
+ preference="pref.general.disable_button.default_browser"/>
+ </hbox>
+ <hbox align="center" class="indent">
+ <label id="isDefaultLabel" flex="1">&isDefault.label;</label>
+ </hbox>
+ </deck>
+ <separator class="thin"/>
+ </vbox>
+#endif
+
+ <html:table id="startupTable">
+ <html:tr>
+ <html:td class="label-cell">
+ <label accesskey="&startupPage.accesskey;"
+ control="browserStartupPage">&startupPage.label;</label>
+ </html:td>
+ <html:td class="content-cell">
+ <menulist id="browserStartupPage"
+ class="content-cell-item"
+ preference="browser.startup.page">
+ <menupopup>
+ <menuitem label="&startupHomePage.label;"
+ value="1"
+ id="browserStartupHomePage"/>
+ <menuitem label="&startupBlankPage.label;"
+ value="0"
+ id="browserStartupBlank"/>
+ <menuitem label="&startupLastSession.label;"
+ value="3"
+ id="browserStartupLastSession"/>
+ </menupopup>
+ </menulist>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:td class="label-cell">
+ <label accesskey="&homepage.accesskey;"
+ control="browserHomePage">&homepage.label;</label>
+ </html:td>
+ <html:td class="content-cell">
+ <textbox id="browserHomePage"
+ class="padded uri-element content-cell-item"
+ type="autocomplete"
+ autocompletesearch="unifiedcomplete"
+ onsyncfrompreference="return gMainPane.syncFromHomePref();"
+ onsynctopreference="return gMainPane.syncToHomePref(this.value);"
+ placeholder="&abouthome.pageTitle;"
+ preference="browser.startup.homepage"/>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:td class="label-cell" />
+ <html:td class="content-cell homepage-buttons">
+ <button id="useCurrent"
+ class="content-cell-item"
+ label=""
+ accesskey="&useCurrentPage.accesskey;"
+ label1="&useCurrentPage.label;"
+ label2="&useMultiple.label;"
+ preference="pref.browser.homepage.disable_button.current_page"/>
+ <button id="useBookmark"
+ class="content-cell-item"
+ label="&chooseBookmark.label;"
+ accesskey="&chooseBookmark.accesskey;"
+ preference="pref.browser.homepage.disable_button.bookmark_page"/>
+ <button id="restoreDefaultHomePage"
+ class="content-cell-item"
+ label="&restoreDefault.label;"
+ accesskey="&restoreDefault.accesskey;"
+ preference="pref.browser.homepage.disable_button.restore_default"/>
+ </html:td>
+ </html:tr>
+ </html:table>
+</groupbox>
+
+<!-- Downloads -->
+<groupbox id="downloadsGroup"
+ data-category="paneGeneral"
+ hidden="true">
+ <caption><label>&downloads.label;</label></caption>
+
+ <radiogroup id="saveWhere"
+ preference="browser.download.useDownloadDir"
+ onsyncfrompreference="return gMainPane.readUseDownloadDir();">
+ <hbox id="saveToRow">
+ <radio id="saveTo"
+ value="true"
+ label="&saveTo.label;"
+ accesskey="&saveTo.accesskey;"
+ aria-labelledby="saveTo downloadFolder"/>
+ <filefield id="downloadFolder"
+ flex="1"
+ preference="browser.download.folderList"
+ preference-editable="true"
+ aria-labelledby="saveTo"
+ onsyncfrompreference="return gMainPane.displayDownloadDirPref();"/>
+ <button id="chooseFolder"
+#ifdef XP_MACOSX
+ accesskey="&chooseFolderMac.accesskey;"
+ label="&chooseFolderMac.label;"
+#else
+ accesskey="&chooseFolderWin.accesskey;"
+ label="&chooseFolderWin.label;"
+#endif
+ />
+ </hbox>
+ <hbox>
+ <radio id="alwaysAsk"
+ value="false"
+ label="&alwaysAsk.label;"
+ accesskey="&alwaysAsk.accesskey;"/>
+ </hbox>
+ </radiogroup>
+</groupbox>
+
+<!-- Tab preferences -->
+<groupbox data-category="paneGeneral"
+ hidden="true" align="start">
+ <caption><label>&tabsGroup.label;</label></caption>
+
+ <checkbox id="ctrlTabRecentlyUsedOrder" label="&ctrlTabRecentlyUsedOrder.label;"
+ accesskey="&ctrlTabRecentlyUsedOrder.accesskey;"
+ preference="browser.ctrlTab.previews"/>
+
+ <checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
+ accesskey="&newWindowsAsTabs.accesskey;"
+ preference="browser.link.open_newwindow"
+ onsyncfrompreference="return gMainPane.readLinkTarget();"
+ onsynctopreference="return gMainPane.writeLinkTarget();"/>
+
+ <checkbox id="warnCloseMultiple" label="&warnCloseMultipleTabs.label;"
+ accesskey="&warnCloseMultipleTabs.accesskey;"
+ preference="browser.tabs.warnOnClose"/>
+
+ <checkbox id="warnOpenMany" label="&warnOpenManyTabs.label;"
+ accesskey="&warnOpenManyTabs.accesskey;"
+ preference="browser.tabs.warnOnOpen"/>
+
+ <checkbox id="switchToNewTabs" label="&switchToNewTabs.label;"
+ accesskey="&switchToNewTabs.accesskey;"
+ preference="browser.tabs.loadInBackground"/>
+
+#ifdef XP_WIN
+ <checkbox id="showTabsInTaskbar" label="&showTabsInTaskbar.label;"
+ accesskey="&showTabsInTaskbar.accesskey;"
+ preference="browser.taskbar.previews.enable"/>
+#endif
+</groupbox>
diff --git a/browser/components/preferences/in-content/moz.build b/browser/components/preferences/in-content/moz.build
new file mode 100644
index 000000000..08a75bcf7
--- /dev/null
+++ b/browser/components/preferences/in-content/moz.build
@@ -0,0 +1,13 @@
+# -*- 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 var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
+ DEFINES[var] = CONFIG[var]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+ DEFINES['HAVE_SHELL_SERVICE'] = 1
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/components/preferences/in-content/preferences.js b/browser/components/preferences/in-content/preferences.js
new file mode 100644
index 000000000..e18ab4b04
--- /dev/null
+++ b/browser/components/preferences/in-content/preferences.js
@@ -0,0 +1,315 @@
+/* - 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 the files imported by the .xul files.
+/* import-globals-from subdialogs.js */
+/* import-globals-from advanced.js */
+/* import-globals-from main.js */
+/* import-globals-from search.js */
+/* import-globals-from content.js */
+/* import-globals-from privacy.js */
+/* import-globals-from applications.js */
+/* import-globals-from security.js */
+/* import-globals-from sync.js */
+/* import-globals-from ../../../base/content/utilityOverlay.js */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+var gLastHash = "";
+
+var gCategoryInits = new Map();
+function init_category_if_required(category) {
+ let categoryInfo = gCategoryInits.get(category);
+ if (!categoryInfo) {
+ throw "Unknown in-content prefs category! Can't init " + category;
+ }
+ if (categoryInfo.inited) {
+ return;
+ }
+ categoryInfo.init();
+}
+
+function register_module(categoryName, categoryObject) {
+ gCategoryInits.set(categoryName, {
+ inited: false,
+ init: function() {
+ categoryObject.init();
+ this.inited = true;
+ }
+ });
+}
+
+addEventListener("DOMContentLoaded", function onLoad() {
+ removeEventListener("DOMContentLoaded", onLoad);
+ init_all();
+});
+
+function init_all() {
+ document.documentElement.instantApply = true;
+
+ gSubDialog.init();
+ register_module("paneGeneral", gMainPane);
+ register_module("paneSearch", gSearchPane);
+ register_module("panePrivacy", gPrivacyPane);
+ register_module("paneContainers", gContainersPane);
+ register_module("paneAdvanced", gAdvancedPane);
+ register_module("paneApplications", gApplicationsPane);
+ register_module("paneContent", gContentPane);
+ register_module("paneSync", gSyncPane);
+ register_module("paneSecurity", gSecurityPane);
+
+ let categories = document.getElementById("categories");
+ categories.addEventListener("select", event => gotoPref(event.target.value));
+
+ document.documentElement.addEventListener("keydown", function(event) {
+ if (event.keyCode == KeyEvent.DOM_VK_TAB) {
+ categories.setAttribute("keyboard-navigation", "true");
+ }
+ });
+ categories.addEventListener("mousedown", function() {
+ this.removeAttribute("keyboard-navigation");
+ });
+
+ window.addEventListener("hashchange", onHashChange);
+ gotoPref();
+
+ init_dynamic_padding();
+
+ var initFinished = new CustomEvent("Initialized", {
+ 'bubbles': true,
+ 'cancelable': true
+ });
+ document.dispatchEvent(initFinished);
+
+ categories = categories.querySelectorAll("richlistitem.category");
+ for (let category of categories) {
+ let name = internalPrefCategoryNameToFriendlyName(category.value);
+ let helpSelector = `#header-${name} > .help-button`;
+ let helpButton = document.querySelector(helpSelector);
+ helpButton.setAttribute("href", getHelpLinkURL(category.getAttribute("helpTopic")));
+ }
+
+ // Wait until initialization of all preferences are complete before
+ // notifying observers that the UI is now ready.
+ Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
+}
+
+// Make the space above the categories list shrink on low window heights
+function init_dynamic_padding() {
+ let categories = document.getElementById("categories");
+ let catPadding = Number.parseInt(getComputedStyle(categories)
+ .getPropertyValue('padding-top'));
+ let fullHeight = categories.lastElementChild.getBoundingClientRect().bottom;
+ let mediaRule = `
+ @media (max-height: ${fullHeight}px) {
+ #categories {
+ padding-top: calc(100vh - ${fullHeight - catPadding}px);
+ }
+ }
+ `;
+ let mediaStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'html:style');
+ mediaStyle.setAttribute('type', 'text/css');
+ mediaStyle.appendChild(document.createCDATASection(mediaRule));
+ document.documentElement.appendChild(mediaStyle);
+}
+
+function telemetryBucketForCategory(category) {
+ switch (category) {
+ case "general":
+ case "search":
+ case "content":
+ case "applications":
+ case "privacy":
+ case "security":
+ case "sync":
+ return category;
+ case "advanced":
+ let advancedPaneTabs = document.getElementById("advancedPrefs");
+ switch (advancedPaneTabs.selectedTab.id) {
+ case "generalTab":
+ return "advancedGeneral";
+ case "dataChoicesTab":
+ return "advancedDataChoices";
+ case "networkTab":
+ return "advancedNetwork";
+ case "updateTab":
+ return "advancedUpdates";
+ case "encryptionTab":
+ return "advancedCerts";
+ }
+ // fall-through for unknown.
+ default:
+ return "unknown";
+ }
+}
+
+function onHashChange() {
+ gotoPref();
+}
+
+function gotoPref(aCategory) {
+ let categories = document.getElementById("categories");
+ const kDefaultCategoryInternalName = categories.firstElementChild.value;
+ let hash = document.location.hash;
+ let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
+ category = friendlyPrefCategoryNameToInternalName(category);
+
+ // Updating the hash (below) or changing the selected category
+ // will re-enter gotoPref.
+ if (gLastHash == category)
+ return;
+ let item = categories.querySelector(".category[value=" + category + "]");
+ if (!item) {
+ category = kDefaultCategoryInternalName;
+ item = categories.querySelector(".category[value=" + category + "]");
+ }
+
+ try {
+ init_category_if_required(category);
+ } catch (ex) {
+ Cu.reportError("Error initializing preference category " + category + ": " + ex);
+ throw ex;
+ }
+
+ let friendlyName = internalPrefCategoryNameToFriendlyName(category);
+ if (gLastHash || category != kDefaultCategoryInternalName) {
+ document.location.hash = friendlyName;
+ }
+ // Need to set the gLastHash before setting categories.selectedItem since
+ // the categories 'select' event will re-enter the gotoPref codepath.
+ gLastHash = category;
+ categories.selectedItem = item;
+ window.history.replaceState(category, document.title);
+ search(category, "data-category");
+ let mainContent = document.querySelector(".main-content");
+ mainContent.scrollTop = 0;
+
+ Services.telemetry
+ .getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
+ .add(telemetryBucketForCategory(friendlyName));
+}
+
+function search(aQuery, aAttribute) {
+ let mainPrefPane = document.getElementById("mainPrefPane");
+ let elements = mainPrefPane.children;
+ for (let element of elements) {
+ let attributeValue = element.getAttribute(aAttribute);
+ element.hidden = (attributeValue != aQuery);
+ }
+
+ let keysets = mainPrefPane.getElementsByTagName("keyset");
+ for (let element of keysets) {
+ let attributeValue = element.getAttribute(aAttribute);
+ if (attributeValue == aQuery)
+ element.removeAttribute("disabled");
+ else
+ element.setAttribute("disabled", true);
+ }
+}
+
+function helpButtonCommand() {
+ let pane = history.state;
+ let categories = document.getElementById("categories");
+ let helpTopic = categories.querySelector(".category[value=" + pane + "]")
+ .getAttribute("helpTopic");
+ openHelpLink(helpTopic);
+}
+
+function friendlyPrefCategoryNameToInternalName(aName) {
+ if (aName.startsWith("pane"))
+ return aName;
+ return "pane" + aName.substring(0, 1).toUpperCase() + aName.substr(1);
+}
+
+// This function is duplicated inside of utilityOverlay.js's openPreferences.
+function internalPrefCategoryNameToFriendlyName(aName) {
+ return (aName || "").replace(/^pane./, function(toReplace) { return toReplace[4].toLowerCase(); });
+}
+
+// Put up a confirm dialog with "ok to restart", "revert without restarting"
+// and "restart later" buttons and returns the index of the button chosen.
+// We can choose not to display the "restart later", or "revert" buttons,
+// altough the later still lets us revert by using the escape key.
+//
+// The constants are useful to interpret the return value of the function.
+const CONFIRM_RESTART_PROMPT_RESTART_NOW = 0;
+const CONFIRM_RESTART_PROMPT_CANCEL = 1;
+const CONFIRM_RESTART_PROMPT_RESTART_LATER = 2;
+function confirmRestartPrompt(aRestartToEnable, aDefaultButtonIndex,
+ aWantRevertAsCancelButton,
+ aWantRestartLaterButton) {
+ let brandName = document.getElementById("bundleBrand").getString("brandShortName");
+ let bundle = document.getElementById("bundlePreferences");
+ let msg = bundle.getFormattedString(aRestartToEnable ?
+ "featureEnableRequiresRestart" :
+ "featureDisableRequiresRestart",
+ [brandName]);
+ let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
+ let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+
+ // Set up the first (index 0) button:
+ let button0Text = bundle.getFormattedString("okToRestartButton", [brandName]);
+ let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING);
+
+
+ // Set up the second (index 1) button:
+ let button1Text = null;
+ if (aWantRevertAsCancelButton) {
+ button1Text = bundle.getString("revertNoRestartButton");
+ buttonFlags += (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_IS_STRING);
+ } else {
+ buttonFlags += (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_CANCEL);
+ }
+
+ // Set up the third (index 2) button:
+ let button2Text = null;
+ if (aWantRestartLaterButton) {
+ button2Text = bundle.getString("restartLater");
+ buttonFlags += (Services.prompt.BUTTON_POS_2 *
+ Services.prompt.BUTTON_TITLE_IS_STRING);
+ }
+
+ switch (aDefaultButtonIndex) {
+ case 0:
+ buttonFlags += Services.prompt.BUTTON_POS_0_DEFAULT;
+ break;
+ case 1:
+ buttonFlags += Services.prompt.BUTTON_POS_1_DEFAULT;
+ break;
+ case 2:
+ buttonFlags += Services.prompt.BUTTON_POS_2_DEFAULT;
+ break;
+ default:
+ break;
+ }
+
+ let buttonIndex = prompts.confirmEx(window, title, msg, buttonFlags,
+ button0Text, button1Text, button2Text,
+ null, {});
+
+ // If we have the second confirmation dialog for restart, see if the user
+ // cancels out at that point.
+ if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (cancelQuit.data) {
+ buttonIndex = CONFIRM_RESTART_PROMPT_CANCEL;
+ }
+ }
+ return buttonIndex;
+}
diff --git a/browser/components/preferences/in-content/preferences.xul b/browser/components/preferences/in-content/preferences.xul
new file mode 100644
index 000000000..e9664eaf4
--- /dev/null
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -0,0 +1,224 @@
+<?xml version="1.0"?>
+<!-- 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"?>
+
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://global/skin/in-content/common.css"?>
+<?xml-stylesheet
+ href="chrome://browser/skin/preferences/in-content/preferences.css"?>
+<?xml-stylesheet
+ href="chrome://browser/content/preferences/handlers.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/in-content/search.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/in-content/containers.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % globalPreferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
+<!ENTITY % preferencesDTD SYSTEM
+ "chrome://browser/locale/preferences/preferences.dtd">
+<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd">
+<!ENTITY % tabsDTD SYSTEM "chrome://browser/locale/preferences/tabs.dtd">
+<!ENTITY % searchDTD SYSTEM "chrome://browser/locale/preferences/search.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/preferences/sync.dtd">
+<!ENTITY % securityDTD SYSTEM
+ "chrome://browser/locale/preferences/security.dtd">
+<!ENTITY % containersDTD SYSTEM
+ "chrome://browser/locale/preferences/containers.dtd">
+<!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
+<!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
+<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+<!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd">
+<!ENTITY % applicationsDTD SYSTEM
+ "chrome://browser/locale/preferences/applications.dtd">
+<!ENTITY % advancedDTD SYSTEM
+ "chrome://browser/locale/preferences/advanced.dtd">
+%brandDTD;
+%globalPreferencesDTD;
+%preferencesDTD;
+%privacyDTD;
+%tabsDTD;
+%searchDTD;
+%syncBrandDTD;
+%syncDTD;
+%securityDTD;
+%containersDTD;
+%sanitizeDTD;
+%mainDTD;
+%aboutHomeDTD;
+%contentDTD;
+%applicationsDTD;
+%advancedDTD;
+]>
+
+#ifdef XP_WIN
+#define USE_WIN_TITLE_STYLE
+#endif
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ disablefastfind="true"
+#ifdef USE_WIN_TITLE_STYLE
+ title="&prefWindow.titleWin;">
+#else
+ title="&prefWindow.title;">
+#endif
+
+ <html:link rel="shortcut icon"
+ href="chrome://browser/skin/preferences/in-content/favicon.ico"/>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/preferences.js"/>
+ <script src="chrome://browser/content/preferences/in-content/subdialogs.js"/>
+
+ <stringbundle id="bundleBrand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <stringbundleset id="appManagerBundleset">
+ <stringbundle id="appManagerBundle"
+ src="chrome://browser/locale/preferences/applicationManager.properties"/>
+ </stringbundleset>
+
+ <stack flex="1">
+ <hbox flex="1">
+
+ <!-- category list -->
+ <richlistbox id="categories">
+ <richlistitem id="category-general"
+ class="category"
+ value="paneGeneral"
+ helpTopic="prefs-main"
+ tooltiptext="&paneGeneral.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneGeneral.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-search"
+ class="category"
+ value="paneSearch"
+ helpTopic="prefs-search"
+ tooltiptext="&paneSearch.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneSearch.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-content"
+ class="category"
+ value="paneContent"
+ helpTopic="prefs-content"
+ tooltiptext="&paneContent.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneContent.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-application"
+ class="category"
+ value="paneApplications"
+ helpTopic="prefs-applications"
+ tooltiptext="&paneApplications.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneApplications.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-privacy"
+ class="category"
+ value="panePrivacy"
+ helpTopic="prefs-privacy"
+ tooltiptext="&panePrivacy.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&panePrivacy.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-containers"
+ class="category"
+ value="paneContainers"
+ helpTopic="prefs-containers"
+ hidden="true"/>
+
+ <richlistitem id="category-security"
+ class="category"
+ value="paneSecurity"
+ helpTopic="prefs-security"
+ tooltiptext="&paneSecurity.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneSecurity.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-sync"
+ class="category"
+ value="paneSync"
+ helpTopic="prefs-weave"
+ tooltiptext="&paneSync.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneSync.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-advanced"
+ class="category"
+ value="paneAdvanced"
+ helpTopic="prefs-advanced-general"
+ tooltiptext="&paneAdvanced.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneAdvanced.title;</label>
+ </richlistitem>
+ </richlistbox>
+
+ <keyset>
+ <!-- Disable the findbar because it doesn't work properly.
+ Remove this keyset once bug 1094240 ("disablefastfind" attribute
+ broken in e10s mode) is fixed. -->
+ <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
+ </keyset>
+
+ <vbox class="main-content" flex="1">
+ <prefpane id="mainPrefPane">
+#include main.xul
+#include search.xul
+#include privacy.xul
+#include containers.xul
+#include advanced.xul
+#include applications.xul
+#include content.xul
+#include security.xul
+#include sync.xul
+ </prefpane>
+ </vbox>
+
+ </hbox>
+
+ <vbox id="dialogOverlay" align="center" pack="center">
+ <groupbox id="dialogBox"
+ orient="vertical"
+ pack="end"
+ role="dialog"
+ aria-labelledby="dialogTitle">
+ <caption flex="1" align="center">
+ <label id="dialogTitle" flex="1"></label>
+ <button id="dialogClose"
+ class="close-icon"
+ aria-label="&preferencesCloseButton.label;"/>
+ </caption>
+ <browser id="dialogFrame"
+ name="dialogFrame"
+ autoscroll="false"
+ disablehistory="true"/>
+ </groupbox>
+ </vbox>
+ </stack>
+</page>
diff --git a/browser/components/preferences/in-content/privacy.js b/browser/components/preferences/in-content/privacy.js
new file mode 100644
index 000000000..7dfc7de5a
--- /dev/null
+++ b/browser/components/preferences/in-content/privacy.js
@@ -0,0 +1,712 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
+ "resource://gre/modules/ContextualIdentityService.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+var gPrivacyPane = {
+
+ /**
+ * Whether the use has selected the auto-start private browsing mode in the UI.
+ */
+ _autoStartPrivateBrowsing: false,
+
+ /**
+ * Whether the prompt to restart Firefox should appear when changing the autostart pref.
+ */
+ _shouldPromptForRestart: true,
+
+ /**
+ * Show the Tracking Protection UI depending on the
+ * privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
+ */
+ _initTrackingProtection: function () {
+ if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) {
+ return;
+ }
+
+ let link = document.getElementById("trackingProtectionLearnMore");
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection";
+ link.setAttribute("href", url);
+
+ this.trackingProtectionReadPrefs();
+
+ document.getElementById("trackingprotectionbox").hidden = false;
+ document.getElementById("trackingprotectionpbmbox").hidden = true;
+ },
+
+ /**
+ * Linkify the Learn More link of the Private Browsing Mode Tracking
+ * Protection UI.
+ */
+ _initTrackingProtectionPBM: function () {
+ let link = document.getElementById("trackingProtectionPBMLearnMore");
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection-pbm";
+ link.setAttribute("href", url);
+ },
+
+ /**
+ * Initialize autocomplete to ensure prefs are in sync.
+ */
+ _initAutocomplete: function () {
+ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ },
+
+ /**
+ * Show the Containers UI depending on the privacy.userContext.ui.enabled pref.
+ */
+ _initBrowserContainers: function () {
+ if (!Services.prefs.getBoolPref("privacy.userContext.ui.enabled")) {
+ return;
+ }
+
+ let link = document.getElementById("browserContainersLearnMore");
+ link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "containers";
+
+ document.getElementById("browserContainersbox").hidden = false;
+
+ document.getElementById("browserContainersCheckbox").checked =
+ Services.prefs.getBoolPref("privacy.userContext.enabled");
+ },
+
+ _checkBrowserContainers: function(event) {
+ let checkbox = document.getElementById("browserContainersCheckbox");
+ if (checkbox.checked) {
+ Services.prefs.setBoolPref("privacy.userContext.enabled", true);
+ return;
+ }
+
+ let count = ContextualIdentityService.countContainerTabs();
+ if (count == 0) {
+ Services.prefs.setBoolPref("privacy.userContext.enabled", false);
+ return;
+ }
+
+ let bundlePreferences = document.getElementById("bundlePreferences");
+
+ let title = bundlePreferences.getString("disableContainersAlertTitle");
+ let message = PluralForm.get(count, bundlePreferences.getString("disableContainersMsg"))
+ .replace("#S", count)
+ let okButton = PluralForm.get(count, bundlePreferences.getString("disableContainersOkButton"))
+ .replace("#S", count)
+ let cancelButton = bundlePreferences.getString("disableContainersButton2");
+
+ let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
+
+ let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
+ okButton, cancelButton, null, null, {});
+ if (rv == 0) {
+ ContextualIdentityService.closeAllContainerTabs();
+ Services.prefs.setBoolPref("privacy.userContext.enabled", false);
+ return;
+ }
+
+ checkbox.checked = true;
+ },
+
+ /**
+ * Sets up the UI for the number of days of history to keep, and updates the
+ * label of the "Clear Now..." button.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gPrivacyPane));
+ }
+
+ this._updateSanitizeSettingsButton();
+ this.initializeHistoryMode();
+ this.updateHistoryModePane();
+ this.updatePrivacyMicroControls();
+ this.initAutoStartPrivateBrowsingReverter();
+ this._initTrackingProtection();
+ this._initTrackingProtectionPBM();
+ this._initAutocomplete();
+ this._initBrowserContainers();
+
+ setEventListener("privacy.sanitize.sanitizeOnShutdown", "change",
+ gPrivacyPane._updateSanitizeSettingsButton);
+ setEventListener("browser.privatebrowsing.autostart", "change",
+ gPrivacyPane.updatePrivacyMicroControls);
+ setEventListener("historyMode", "command", function () {
+ gPrivacyPane.updateHistoryModePane();
+ gPrivacyPane.updateHistoryModePrefs();
+ gPrivacyPane.updatePrivacyMicroControls();
+ gPrivacyPane.updateAutostart();
+ });
+ setEventListener("historyRememberClear", "click", function () {
+ gPrivacyPane.clearPrivateDataNow(false);
+ return false;
+ });
+ setEventListener("historyRememberCookies", "click", function () {
+ gPrivacyPane.showCookies();
+ return false;
+ });
+ setEventListener("historyDontRememberClear", "click", function () {
+ gPrivacyPane.clearPrivateDataNow(true);
+ return false;
+ });
+ setEventListener("doNotTrackSettings", "click", function () {
+ gPrivacyPane.showDoNotTrackSettings();
+ return false;
+ });
+ setEventListener("privateBrowsingAutoStart", "command",
+ gPrivacyPane.updateAutostart);
+ setEventListener("cookieExceptions", "command",
+ gPrivacyPane.showCookieExceptions);
+ setEventListener("showCookiesButton", "command",
+ gPrivacyPane.showCookies);
+ setEventListener("clearDataSettings", "command",
+ gPrivacyPane.showClearPrivateDataSettings);
+ setEventListener("trackingProtectionRadioGroup", "command",
+ gPrivacyPane.trackingProtectionWritePrefs);
+ setEventListener("trackingProtectionExceptions", "command",
+ gPrivacyPane.showTrackingProtectionExceptions);
+ setEventListener("changeBlockList", "command",
+ gPrivacyPane.showBlockLists);
+ setEventListener("changeBlockListPBM", "command",
+ gPrivacyPane.showBlockLists);
+ setEventListener("browserContainersCheckbox", "command",
+ gPrivacyPane._checkBrowserContainers);
+ setEventListener("browserContainersSettings", "command",
+ gPrivacyPane.showContainerSettings);
+ },
+
+ // TRACKING PROTECTION MODE
+
+ /**
+ * Selects the right item of the Tracking Protection radiogroup.
+ */
+ trackingProtectionReadPrefs() {
+ let enabledPref = document.getElementById("privacy.trackingprotection.enabled");
+ let pbmPref = document.getElementById("privacy.trackingprotection.pbmode.enabled");
+ let radiogroup = document.getElementById("trackingProtectionRadioGroup");
+
+ // Global enable takes precedence over enabled in Private Browsing.
+ if (enabledPref.value) {
+ radiogroup.value = "always";
+ } else if (pbmPref.value) {
+ radiogroup.value = "private";
+ } else {
+ radiogroup.value = "never";
+ }
+ },
+
+ /**
+ * Sets the pref values based on the selected item of the radiogroup.
+ */
+ trackingProtectionWritePrefs() {
+ let enabledPref = document.getElementById("privacy.trackingprotection.enabled");
+ let pbmPref = document.getElementById("privacy.trackingprotection.pbmode.enabled");
+ let radiogroup = document.getElementById("trackingProtectionRadioGroup");
+
+ switch (radiogroup.value) {
+ case "always":
+ enabledPref.value = true;
+ pbmPref.value = true;
+ break;
+ case "private":
+ enabledPref.value = false;
+ pbmPref.value = true;
+ break;
+ case "never":
+ enabledPref.value = false;
+ pbmPref.value = false;
+ break;
+ }
+ },
+
+ // HISTORY MODE
+
+ /**
+ * The list of preferences which affect the initial history mode settings.
+ * If the auto start private browsing mode pref is active, the initial
+ * history mode would be set to "Don't remember anything".
+ * If ALL of these preferences are set to the values that correspond
+ * to keeping some part of history, and the auto-start
+ * private browsing mode is not active, the initial history mode would be
+ * set to "Remember everything".
+ * Otherwise, the initial history mode would be set to "Custom".
+ *
+ * Extensions adding their own preferences can set values here if needed.
+ */
+ prefsForKeepingHistory: {
+ "places.history.enabled": true, // History is enabled
+ "browser.formfill.enable": true, // Form information is saved
+ "network.cookie.cookieBehavior": 0, // All cookies are enabled
+ "network.cookie.lifetimePolicy": 0, // Cookies use supplied lifetime
+ "privacy.sanitize.sanitizeOnShutdown": false, // Private date is NOT cleared on shutdown
+ },
+
+ /**
+ * The list of control IDs which are dependent on the auto-start private
+ * browsing setting, such that in "Custom" mode they would be disabled if
+ * the auto-start private browsing checkbox is checked, and enabled otherwise.
+ *
+ * Extensions adding their own controls can append their IDs to this array if needed.
+ */
+ dependentControls: [
+ "rememberHistory",
+ "rememberForms",
+ "keepUntil",
+ "keepCookiesUntil",
+ "alwaysClear",
+ "clearDataSettings"
+ ],
+
+ /**
+ * Check whether preferences values are set to keep history
+ *
+ * @param aPrefs an array of pref names to check for
+ * @returns boolean true if all of the prefs are set to keep history,
+ * false otherwise
+ */
+ _checkHistoryValues: function(aPrefs) {
+ for (let pref of Object.keys(aPrefs)) {
+ if (document.getElementById(pref).value != aPrefs[pref])
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Initialize the history mode menulist based on the privacy preferences
+ */
+ initializeHistoryMode: function PPP_initializeHistoryMode()
+ {
+ let mode;
+ let getVal = aPref => document.getElementById(aPref).value;
+
+ if (this._checkHistoryValues(this.prefsForKeepingHistory)) {
+ if (getVal("browser.privatebrowsing.autostart"))
+ mode = "dontremember";
+ else
+ mode = "remember";
+ }
+ else
+ mode = "custom";
+
+ document.getElementById("historyMode").value = mode;
+ },
+
+ /**
+ * Update the selected pane based on the history mode menulist
+ */
+ updateHistoryModePane: function PPP_updateHistoryModePane()
+ {
+ let selectedIndex = -1;
+ switch (document.getElementById("historyMode").value) {
+ case "remember":
+ selectedIndex = 0;
+ break;
+ case "dontremember":
+ selectedIndex = 1;
+ break;
+ case "custom":
+ selectedIndex = 2;
+ break;
+ }
+ document.getElementById("historyPane").selectedIndex = selectedIndex;
+ },
+
+ /**
+ * Update the private browsing auto-start pref and the history mode
+ * micro-management prefs based on the history mode menulist
+ */
+ updateHistoryModePrefs: function PPP_updateHistoryModePrefs()
+ {
+ let pref = document.getElementById("browser.privatebrowsing.autostart");
+ switch (document.getElementById("historyMode").value) {
+ case "remember":
+ if (pref.value)
+ pref.value = false;
+
+ // select the remember history option if needed
+ let rememberHistoryCheckbox = document.getElementById("rememberHistory");
+ if (!rememberHistoryCheckbox.checked)
+ rememberHistoryCheckbox.checked = true;
+
+ // select the remember forms history option
+ document.getElementById("browser.formfill.enable").value = true;
+
+ // select the allow cookies option
+ document.getElementById("network.cookie.cookieBehavior").value = 0;
+ // select the cookie lifetime policy option
+ document.getElementById("network.cookie.lifetimePolicy").value = 0;
+
+ // select the clear on close option
+ document.getElementById("privacy.sanitize.sanitizeOnShutdown").value = false;
+ break;
+ case "dontremember":
+ if (!pref.value)
+ pref.value = true;
+ break;
+ }
+ },
+
+ /**
+ * Update the privacy micro-management controls based on the
+ * value of the private browsing auto-start checkbox.
+ */
+ updatePrivacyMicroControls: function PPP_updatePrivacyMicroControls()
+ {
+ if (document.getElementById("historyMode").value == "custom") {
+ let disabled = this._autoStartPrivateBrowsing =
+ document.getElementById("privateBrowsingAutoStart").checked;
+ this.dependentControls.forEach(function (aElement) {
+ let control = document.getElementById(aElement);
+ let preferenceId = control.getAttribute("preference");
+ if (!preferenceId) {
+ let dependentControlId = control.getAttribute("control");
+ if (dependentControlId) {
+ let dependentControl = document.getElementById(dependentControlId);
+ preferenceId = dependentControl.getAttribute("preference");
+ }
+ }
+
+ let preference = preferenceId ? document.getElementById(preferenceId) : {};
+ control.disabled = disabled || preference.locked;
+ });
+
+ // adjust the cookie controls status
+ this.readAcceptCookies();
+ let lifetimePolicy = document.getElementById("network.cookie.lifetimePolicy").value;
+ if (lifetimePolicy != Ci.nsICookieService.ACCEPT_NORMALLY &&
+ lifetimePolicy != Ci.nsICookieService.ACCEPT_SESSION &&
+ lifetimePolicy != Ci.nsICookieService.ACCEPT_FOR_N_DAYS) {
+ lifetimePolicy = Ci.nsICookieService.ACCEPT_NORMALLY;
+ }
+ document.getElementById("keepCookiesUntil").value = disabled ? 2 : lifetimePolicy;
+
+ // adjust the checked state of the sanitizeOnShutdown checkbox
+ document.getElementById("alwaysClear").checked = disabled ? false :
+ document.getElementById("privacy.sanitize.sanitizeOnShutdown").value;
+
+ // adjust the checked state of the remember history checkboxes
+ document.getElementById("rememberHistory").checked = disabled ? false :
+ document.getElementById("places.history.enabled").value;
+ document.getElementById("rememberForms").checked = disabled ? false :
+ document.getElementById("browser.formfill.enable").value;
+
+ if (!disabled) {
+ // adjust the Settings button for sanitizeOnShutdown
+ this._updateSanitizeSettingsButton();
+ }
+ }
+ },
+
+ // PRIVATE BROWSING
+
+ /**
+ * Initialize the starting state for the auto-start private browsing mode pref reverter.
+ */
+ initAutoStartPrivateBrowsingReverter: function PPP_initAutoStartPrivateBrowsingReverter()
+ {
+ let mode = document.getElementById("historyMode");
+ let autoStart = document.getElementById("privateBrowsingAutoStart");
+ this._lastMode = mode.selectedIndex;
+ this._lastCheckState = autoStart.hasAttribute('checked');
+ },
+
+ _lastMode: null,
+ _lastCheckState: null,
+ updateAutostart: function PPP_updateAutostart() {
+ let mode = document.getElementById("historyMode");
+ let autoStart = document.getElementById("privateBrowsingAutoStart");
+ let pref = document.getElementById("browser.privatebrowsing.autostart");
+ if ((mode.value == "custom" && this._lastCheckState == autoStart.checked) ||
+ (mode.value == "remember" && !this._lastCheckState) ||
+ (mode.value == "dontremember" && this._lastCheckState)) {
+ // These are all no-op changes, so we don't need to prompt.
+ this._lastMode = mode.selectedIndex;
+ this._lastCheckState = autoStart.hasAttribute('checked');
+ return;
+ }
+
+ if (!this._shouldPromptForRestart) {
+ // We're performing a revert. Just let it happen.
+ return;
+ }
+
+ let buttonIndex = confirmRestartPrompt(autoStart.checked, 1,
+ true, false);
+ if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
+ pref.value = autoStart.hasAttribute('checked');
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ return;
+ }
+
+ this._shouldPromptForRestart = false;
+
+ if (this._lastCheckState) {
+ autoStart.checked = "checked";
+ } else {
+ autoStart.removeAttribute('checked');
+ }
+ pref.value = autoStart.hasAttribute('checked');
+ mode.selectedIndex = this._lastMode;
+ mode.doCommand();
+
+ this._shouldPromptForRestart = true;
+ },
+
+ /**
+ * Displays fine-grained, per-site preferences for tracking protection.
+ */
+ showTrackingProtectionExceptions() {
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let params = {
+ permissionType: "trackingprotection",
+ hideStatusColumn: true,
+ windowTitle: bundlePreferences.getString("trackingprotectionpermissionstitle"),
+ introText: bundlePreferences.getString("trackingprotectionpermissionstext"),
+ };
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Displays container panel for customising and adding containers.
+ */
+ showContainerSettings() {
+ gotoPref("containers");
+ },
+
+ /**
+ * Displays the available block lists for tracking protection.
+ */
+ showBlockLists: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ let brandName = document.getElementById("bundleBrand")
+ .getString("brandShortName");
+ var params = { brandShortName: brandName,
+ windowTitle: bundlePreferences.getString("blockliststitle"),
+ introText: bundlePreferences.getString("blockliststext") };
+ gSubDialog.open("chrome://browser/content/preferences/blocklists.xul",
+ null, params);
+ },
+
+ /**
+ * Displays the Do Not Track settings dialog.
+ */
+ showDoNotTrackSettings() {
+ gSubDialog.open("chrome://browser/content/preferences/donottrack.xul",
+ "resizable=no");
+ },
+
+ // HISTORY
+
+ /*
+ * Preferences:
+ *
+ * places.history.enabled
+ * - whether history is enabled or not
+ * browser.formfill.enable
+ * - true if entries in forms and the search bar should be saved, false
+ * otherwise
+ */
+
+ // COOKIES
+
+ /*
+ * Preferences:
+ *
+ * network.cookie.cookieBehavior
+ * - determines how the browser should handle cookies:
+ * 0 means enable all cookies
+ * 1 means reject all third party cookies
+ * 2 means disable all cookies
+ * 3 means reject third party cookies unless at least one is already set for the eTLD
+ * see netwerk/cookie/src/nsCookieService.cpp for details
+ * network.cookie.lifetimePolicy
+ * - determines how long cookies are stored:
+ * 0 means keep cookies until they expire
+ * 2 means keep cookies until the browser is closed
+ */
+
+ /**
+ * Reads the network.cookie.cookieBehavior preference value and
+ * enables/disables the rest of the cookie UI accordingly, returning true
+ * if cookies are enabled.
+ */
+ readAcceptCookies: function ()
+ {
+ var pref = document.getElementById("network.cookie.cookieBehavior");
+ var acceptThirdPartyLabel = document.getElementById("acceptThirdPartyLabel");
+ var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+ var keepUntil = document.getElementById("keepUntil");
+ var menu = document.getElementById("keepCookiesUntil");
+
+ // enable the rest of the UI for anything other than "disable all cookies"
+ var acceptCookies = (pref.value != 2);
+
+ acceptThirdPartyLabel.disabled = acceptThirdPartyMenu.disabled = !acceptCookies;
+ keepUntil.disabled = menu.disabled = this._autoStartPrivateBrowsing || !acceptCookies;
+
+ return acceptCookies;
+ },
+
+ /**
+ * Enables/disables the "keep until" label and menulist in response to the
+ * "accept cookies" checkbox being checked or unchecked.
+ */
+ writeAcceptCookies: function ()
+ {
+ var accept = document.getElementById("acceptCookies");
+ var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+
+ // if we're enabling cookies, automatically select 'accept third party always'
+ if (accept.checked)
+ acceptThirdPartyMenu.selectedIndex = 0;
+
+ return accept.checked ? 0 : 2;
+ },
+
+ /**
+ * Converts between network.cookie.cookieBehavior and the third-party cookie UI
+ */
+ readAcceptThirdPartyCookies: function ()
+ {
+ var pref = document.getElementById("network.cookie.cookieBehavior");
+ switch (pref.value)
+ {
+ case 0:
+ return "always";
+ case 1:
+ return "never";
+ case 2:
+ return "never";
+ case 3:
+ return "visited";
+ default:
+ return undefined;
+ }
+ },
+
+ writeAcceptThirdPartyCookies: function ()
+ {
+ var accept = document.getElementById("acceptThirdPartyMenu").selectedItem;
+ switch (accept.value)
+ {
+ case "always":
+ return 0;
+ case "visited":
+ return 3;
+ case "never":
+ return 1;
+ default:
+ return undefined;
+ }
+ },
+
+ /**
+ * Displays fine-grained, per-site preferences for cookies.
+ */
+ showCookieExceptions: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible : true,
+ sessionVisible : true,
+ allowVisible : true,
+ prefilledHost : "",
+ permissionType : "cookie",
+ windowTitle : bundlePreferences.getString("cookiepermissionstitle"),
+ introText : bundlePreferences.getString("cookiepermissionstext") };
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Displays all the user's cookies in a dialog.
+ */
+ showCookies: function (aCategory)
+ {
+ gSubDialog.open("chrome://browser/content/preferences/cookies.xul");
+ },
+
+ // CLEAR PRIVATE DATA
+
+ /*
+ * Preferences:
+ *
+ * privacy.sanitize.sanitizeOnShutdown
+ * - true if the user's private data is cleared on startup according to the
+ * Clear Private Data settings, false otherwise
+ */
+
+ /**
+ * Displays the Clear Private Data settings dialog.
+ */
+ showClearPrivateDataSettings: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/sanitize.xul", "resizable=no");
+ },
+
+
+ /**
+ * Displays a dialog from which individual parts of private data may be
+ * cleared.
+ */
+ clearPrivateDataNow: function (aClearEverything) {
+ var ts = document.getElementById("privacy.sanitize.timeSpan");
+ var timeSpanOrig = ts.value;
+
+ if (aClearEverything) {
+ ts.value = 0;
+ }
+
+ gSubDialog.open("chrome://browser/content/sanitize.xul", "resizable=no", null, () => {
+ // reset the timeSpan pref
+ if (aClearEverything) {
+ ts.value = timeSpanOrig;
+ }
+
+ Services.obs.notifyObservers(null, "clear-private-data", null);
+ });
+ },
+
+ /**
+ * Enables or disables the "Settings..." button depending
+ * on the privacy.sanitize.sanitizeOnShutdown preference value
+ */
+ _updateSanitizeSettingsButton: function () {
+ var settingsButton = document.getElementById("clearDataSettings");
+ var sanitizeOnShutdownPref = document.getElementById("privacy.sanitize.sanitizeOnShutdown");
+
+ settingsButton.disabled = !sanitizeOnShutdownPref.value;
+ },
+
+ // CONTAINERS
+
+ /*
+ * preferences:
+ *
+ * privacy.userContext.enabled
+ * - true if containers is enabled
+ */
+
+ /**
+ * Enables/disables the Settings button used to configure containers
+ */
+ readBrowserContainersCheckbox: function ()
+ {
+ var pref = document.getElementById("privacy.userContext.enabled");
+ var settings = document.getElementById("browserContainersSettings");
+
+ settings.disabled = !pref.value;
+ }
+
+};
diff --git a/browser/components/preferences/in-content/privacy.xul b/browser/components/preferences/in-content/privacy.xul
new file mode 100644
index 000000000..6ac6c88a4
--- /dev/null
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -0,0 +1,308 @@
+# 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/.
+
+<!-- Privacy panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/privacy.js"/>
+
+<preferences id="privacyPreferences" hidden="true" data-category="panePrivacy">
+
+ <!-- Tracking -->
+ <preference id="privacy.trackingprotection.enabled"
+ name="privacy.trackingprotection.enabled"
+ type="bool"/>
+ <preference id="privacy.trackingprotection.pbmode.enabled"
+ name="privacy.trackingprotection.pbmode.enabled"
+ type="bool"/>
+
+ <!-- XXX button prefs -->
+ <preference id="pref.privacy.disable_button.cookie_exceptions"
+ name="pref.privacy.disable_button.cookie_exceptions"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.view_cookies"
+ name="pref.privacy.disable_button.view_cookies"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.change_blocklist"
+ name="pref.privacy.disable_button.change_blocklist"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.tracking_protection_exceptions"
+ name="pref.privacy.disable_button.tracking_protection_exceptions"
+ type="bool"/>
+
+ <!-- Location Bar -->
+ <preference id="browser.urlbar.autocomplete.enabled"
+ name="browser.urlbar.autocomplete.enabled"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.bookmark"
+ name="browser.urlbar.suggest.bookmark"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.history"
+ name="browser.urlbar.suggest.history"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.openpage"
+ name="browser.urlbar.suggest.openpage"
+ type="bool"/>
+
+ <!-- History -->
+ <preference id="places.history.enabled"
+ name="places.history.enabled"
+ type="bool"/>
+ <preference id="browser.formfill.enable"
+ name="browser.formfill.enable"
+ type="bool"/>
+ <!-- Cookies -->
+ <preference id="network.cookie.cookieBehavior"
+ name="network.cookie.cookieBehavior"
+ type="int"/>
+ <preference id="network.cookie.lifetimePolicy"
+ name="network.cookie.lifetimePolicy"
+ type="int"/>
+ <preference id="network.cookie.blockFutureCookies"
+ name="network.cookie.blockFutureCookies"
+ type="bool"/>
+ <!-- Clear Private Data -->
+ <preference id="privacy.sanitize.sanitizeOnShutdown"
+ name="privacy.sanitize.sanitizeOnShutdown"
+ type="bool"/>
+ <preference id="privacy.sanitize.timeSpan"
+ name="privacy.sanitize.timeSpan"
+ type="int"/>
+ <!-- Private Browsing -->
+ <preference id="browser.privatebrowsing.autostart"
+ name="browser.privatebrowsing.autostart"
+ type="bool"/>
+</preferences>
+
+<hbox id="header-privacy"
+ class="header"
+ hidden="true"
+ data-category="panePrivacy">
+ <label class="header-name" flex="1">&panePrivacy.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<!-- Tracking -->
+<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
+ <vbox id="trackingprotectionbox" hidden="true">
+ <hbox align="start">
+ <vbox>
+ <caption><label>&trackingProtectionHeader.label;
+ <label id="trackingProtectionLearnMore" class="text-link"
+ value="&trackingProtectionLearnMore.label;"/>
+ </label></caption>
+ <radiogroup id="trackingProtectionRadioGroup">
+ <radio value="always"
+ label="&trackingProtectionAlways.label;"
+ accesskey="&trackingProtectionAlways.accesskey;"/>
+ <radio value="private"
+ label="&trackingProtectionPrivate.label;"
+ accesskey="&trackingProtectionPrivate.accesskey;"/>
+ <radio value="never"
+ label="&trackingProtectionNever.label;"
+ accesskey="&trackingProtectionNever.accesskey;"/>
+ </radiogroup>
+ </vbox>
+ <spacer flex="1" />
+ <vbox>
+ <button id="trackingProtectionExceptions"
+ label="&trackingProtectionExceptions.label;"
+ accesskey="&trackingProtectionExceptions.accesskey;"
+ preference="pref.privacy.disable_button.tracking_protection_exceptions"/>
+ <button id="changeBlockList"
+ label="&changeBlockList.label;"
+ accesskey="&changeBlockList.accesskey;"
+ preference="pref.privacy.disable_button.change_blocklist"/>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox id="trackingprotectionpbmbox">
+ <caption><label>&tracking.label;</label></caption>
+ <hbox align="center">
+ <checkbox id="trackingProtectionPBM"
+ preference="privacy.trackingprotection.pbmode.enabled"
+ accesskey="&trackingProtectionPBM5.accesskey;"
+ label="&trackingProtectionPBM5.label;" />
+ <label id="trackingProtectionPBMLearnMore"
+ class="text-link"
+ value="&trackingProtectionPBMLearnMore.label;"/>
+ <spacer flex="1" />
+ <button id="changeBlockListPBM"
+ label="&changeBlockList.label;" accesskey="&changeBlockList.accesskey;"
+ preference="pref.privacy.disable_button.change_blocklist"/>
+ </hbox>
+ </vbox>
+ <vbox>
+ <description>&doNotTrack.pre.label;<label
+ class="text-link" id="doNotTrackSettings"
+ >&doNotTrack.settings.label;</label>&doNotTrack.post.label;</description>
+ </vbox>
+</groupbox>
+
+<!-- History -->
+<groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
+ <caption><label>&history.label;</label></caption>
+ <hbox align="center">
+ <label id="historyModeLabel"
+ control="historyMode"
+ accesskey="&historyHeader.pre.accesskey;">&historyHeader.pre.label;
+ </label>
+ <menulist id="historyMode">
+ <menupopup>
+ <menuitem label="&historyHeader.remember.label;" value="remember"/>
+ <menuitem label="&historyHeader.dontremember.label;" value="dontremember"/>
+ <menuitem label="&historyHeader.custom.label;" value="custom"/>
+ </menupopup>
+ </menulist>
+ <label>&historyHeader.post.label;</label>
+ </hbox>
+ <deck id="historyPane">
+ <vbox id="historyRememberPane">
+ <hbox align="center" flex="1">
+ <vbox flex="1">
+ <description>&rememberDescription.label;</description>
+ <separator class="thin"/>
+ <description>&rememberActions.pre.label;<label
+ class="text-link" id="historyRememberClear"
+ >&rememberActions.clearHistory.label;</label>&rememberActions.middle.label;<label
+ class="text-link" id="historyRememberCookies"
+ >&rememberActions.removeCookies.label;</label>&rememberActions.post.label;</description>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox id="historyDontRememberPane">
+ <hbox align="center" flex="1">
+ <vbox flex="1">
+ <description>&dontrememberDescription.label;</description>
+ <separator class="thin"/>
+ <description>&dontrememberActions.pre.label;<label
+ class="text-link" id="historyDontRememberClear"
+ >&dontrememberActions.clearHistory.label;</label>&dontrememberActions.post.label;</description>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox id="historyCustomPane">
+ <separator class="thin"/>
+ <vbox>
+ <vbox align="start">
+ <checkbox id="privateBrowsingAutoStart"
+ label="&privateBrowsingPermanent2.label;"
+ accesskey="&privateBrowsingPermanent2.accesskey;"
+ preference="browser.privatebrowsing.autostart"/>
+ </vbox>
+ <vbox class="indent">
+ <vbox align="start">
+ <checkbox id="rememberHistory"
+ label="&rememberHistory2.label;"
+ accesskey="&rememberHistory2.accesskey;"
+ preference="places.history.enabled"/>
+ <checkbox id="rememberForms"
+ label="&rememberSearchForm.label;"
+ accesskey="&rememberSearchForm.accesskey;"
+ preference="browser.formfill.enable"/>
+ </vbox>
+ <hbox id="cookiesBox">
+ <checkbox id="acceptCookies" label="&acceptCookies.label;"
+ preference="network.cookie.cookieBehavior"
+ accesskey="&acceptCookies.accesskey;"
+ onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
+ onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
+ <spacer flex="1" />
+ <button id="cookieExceptions"
+ label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
+ preference="pref.privacy.disable_button.cookie_exceptions"/>
+ </hbox>
+ <hbox id="acceptThirdPartyRow"
+ class="indent"
+ align="center">
+ <label id="acceptThirdPartyLabel" control="acceptThirdPartyMenu"
+ accesskey="&acceptThirdParty.pre.accesskey;">&acceptThirdParty.pre.label;</label>
+ <menulist id="acceptThirdPartyMenu" preference="network.cookie.cookieBehavior"
+ onsyncfrompreference="return gPrivacyPane.readAcceptThirdPartyCookies();"
+ onsynctopreference="return gPrivacyPane.writeAcceptThirdPartyCookies();">
+ <menupopup>
+ <menuitem label="&acceptThirdParty.always.label;" value="always"/>
+ <menuitem label="&acceptThirdParty.visited.label;" value="visited"/>
+ <menuitem label="&acceptThirdParty.never.label;" value="never"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <hbox id="keepRow"
+ class="indent"
+ align="center">
+ <label id="keepUntil"
+ control="keepCookiesUntil"
+ accesskey="&keepUntil.accesskey;">&keepUntil.label;</label>
+ <menulist id="keepCookiesUntil"
+ preference="network.cookie.lifetimePolicy">
+ <menupopup>
+ <menuitem label="&expire.label;" value="0"/>
+ <menuitem label="&close.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <button id="showCookiesButton"
+ label="&showCookies.label;" accesskey="&showCookies.accesskey;"
+ preference="pref.privacy.disable_button.view_cookies"/>
+ </hbox>
+ <hbox id="clearDataBox"
+ align="center">
+ <checkbox id="alwaysClear"
+ preference="privacy.sanitize.sanitizeOnShutdown"
+ label="&clearOnClose.label;"
+ accesskey="&clearOnClose.accesskey;"/>
+ <spacer flex="1"/>
+ <button id="clearDataSettings" label="&clearOnCloseSettings.label;"
+ accesskey="&clearOnCloseSettings.accesskey;"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+ </deck>
+</groupbox>
+
+<!-- Location Bar -->
+<groupbox id="locationBarGroup"
+ data-category="panePrivacy"
+ hidden="true">
+ <caption><label>&locationBar.label;</label></caption>
+ <label id="locationBarSuggestionLabel">&locbar.suggest.label;</label>
+ <checkbox id="historySuggestion" label="&locbar.history.label;"
+ accesskey="&locbar.history.accesskey;"
+ preference="browser.urlbar.suggest.history"/>
+ <checkbox id="bookmarkSuggestion" label="&locbar.bookmarks.label;"
+ accesskey="&locbar.bookmarks.accesskey;"
+ preference="browser.urlbar.suggest.bookmark"/>
+ <checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
+ accesskey="&locbar.openpage.accesskey;"
+ preference="browser.urlbar.suggest.openpage"/>
+ <label class="text-link" onclick="gotoPref('search')">
+ &suggestionSettings.label;
+ </label>
+</groupbox>
+
+<!-- Containers -->
+<groupbox id="browserContainersGroup" data-category="panePrivacy" hidden="true">
+ <vbox id="browserContainersbox" hidden="true">
+ <caption><label>&browserContainersHeader.label;
+ <label id="browserContainersLearnMore" class="text-link"
+ value="&browserContainersLearnMore.label;"/>
+ </label></caption>
+ <hbox align="start">
+ <vbox>
+ <checkbox id="browserContainersCheckbox"
+ label="&browserContainersEnabled.label;"
+ accesskey="&browserContainersEnabled.accesskey;"
+ preference="privacy.userContext.enabled"
+ onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
+ </vbox>
+ <spacer flex="1"/>
+ <vbox>
+ <button id="browserContainersSettings"
+ label="&browserContainersSettings.label;"
+ accesskey="&browserContainersSettings.accesskey;"/>
+ </vbox>
+ </hbox>
+ </vbox>
+</groupbox>
diff --git a/browser/components/preferences/in-content/search.js b/browser/components/preferences/in-content/search.js
new file mode 100644
index 000000000..55aa2c18c
--- /dev/null
+++ b/browser/components/preferences/in-content/search.js
@@ -0,0 +1,604 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const ENGINE_FLAVOR = "text/x-moz-search-engine";
+
+var gEngineView = null;
+
+var gSearchPane = {
+
+ /**
+ * Initialize autocomplete to ensure prefs are in sync.
+ */
+ _initAutocomplete: function () {
+ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ },
+
+ init: function ()
+ {
+ gEngineView = new EngineView(new EngineStore());
+ document.getElementById("engineList").view = gEngineView;
+ this.buildDefaultEngineDropDown();
+
+ let addEnginesLink = document.getElementById("addEngines");
+ let searchEnginesURL = Services.wm.getMostRecentWindow('navigator:browser')
+ .BrowserSearch.searchEnginesURL;
+ addEnginesLink.setAttribute("href", searchEnginesURL);
+
+ window.addEventListener("click", this, false);
+ window.addEventListener("command", this, false);
+ window.addEventListener("dragstart", this, false);
+ window.addEventListener("keypress", this, false);
+ window.addEventListener("select", this, false);
+ window.addEventListener("blur", this, true);
+
+ Services.obs.addObserver(this, "browser-search-engine-modified", false);
+ window.addEventListener("unload", () => {
+ Services.obs.removeObserver(this, "browser-search-engine-modified", false);
+ });
+
+ this._initAutocomplete();
+
+ let suggestsPref =
+ document.getElementById("browser.search.suggest.enabled");
+ suggestsPref.addEventListener("change", () => {
+ this.updateSuggestsCheckbox();
+ });
+ this.updateSuggestsCheckbox();
+ },
+
+ updateSuggestsCheckbox() {
+ let suggestsPref =
+ document.getElementById("browser.search.suggest.enabled");
+ let permanentPB =
+ Services.prefs.getBoolPref("browser.privatebrowsing.autostart");
+ let urlbarSuggests = document.getElementById("urlBarSuggestion");
+ urlbarSuggests.disabled = !suggestsPref.value || permanentPB;
+
+ let urlbarSuggestsPref =
+ document.getElementById("browser.urlbar.suggest.searches");
+ urlbarSuggests.checked = urlbarSuggestsPref.value;
+ if (urlbarSuggests.disabled) {
+ urlbarSuggests.checked = false;
+ }
+
+ let permanentPBLabel =
+ document.getElementById("urlBarSuggestionPermanentPBLabel");
+ permanentPBLabel.hidden = urlbarSuggests.hidden || !permanentPB;
+ },
+
+ buildDefaultEngineDropDown: function() {
+ // This is called each time something affects the list of engines.
+ let list = document.getElementById("defaultEngine");
+ // Set selection to the current default engine.
+ let currentEngine = Services.search.currentEngine.name;
+
+ // If the current engine isn't in the list any more, select the first item.
+ let engines = gEngineView._engineStore._engines;
+ if (!engines.some(e => e.name == currentEngine))
+ currentEngine = engines[0].name;
+
+ // Now clean-up and rebuild the list.
+ list.removeAllItems();
+ gEngineView._engineStore._engines.forEach(e => {
+ let item = list.appendItem(e.name);
+ item.setAttribute("class", "menuitem-iconic searchengine-menuitem menuitem-with-favicon");
+ if (e.iconURI) {
+ item.setAttribute("image", e.iconURI.spec);
+ }
+ item.engine = e;
+ if (e.name == currentEngine)
+ list.selectedItem = item;
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "click":
+ if (aEvent.target.id != "engineChildren" &&
+ !aEvent.target.classList.contains("searchEngineAction")) {
+ let engineList = document.getElementById("engineList");
+ // We don't want to toggle off selection while editing keyword
+ // so proceed only when the input field is hidden.
+ // We need to check that engineList.view is defined here
+ // because the "click" event listener is on <window> and the
+ // view might have been destroyed if the pane has been navigated
+ // away from.
+ if (engineList.inputField.hidden && engineList.view) {
+ let selection = engineList.view.selection;
+ if (selection.count > 0) {
+ selection.toggleSelect(selection.currentIndex);
+ }
+ engineList.blur();
+ }
+ }
+ break;
+ case "command":
+ switch (aEvent.target.id) {
+ case "":
+ if (aEvent.target.parentNode &&
+ aEvent.target.parentNode.parentNode &&
+ aEvent.target.parentNode.parentNode.id == "defaultEngine") {
+ gSearchPane.setDefaultEngine();
+ }
+ break;
+ case "restoreDefaultSearchEngines":
+ gSearchPane.onRestoreDefaults();
+ break;
+ case "removeEngineButton":
+ Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
+ break;
+ }
+ break;
+ case "dragstart":
+ if (aEvent.target.id == "engineChildren") {
+ onDragEngineStart(aEvent);
+ }
+ break;
+ case "keypress":
+ if (aEvent.target.id == "engineList") {
+ gSearchPane.onTreeKeyPress(aEvent);
+ }
+ break;
+ case "select":
+ if (aEvent.target.id == "engineList") {
+ gSearchPane.onTreeSelect();
+ }
+ break;
+ case "blur":
+ if (aEvent.target.id == "engineList" &&
+ aEvent.target.inputField == document.getBindingParent(aEvent.originalTarget)) {
+ gSearchPane.onInputBlur();
+ }
+ break;
+ }
+ },
+
+ observe: function(aEngine, aTopic, aVerb) {
+ if (aTopic == "browser-search-engine-modified") {
+ aEngine.QueryInterface(Components.interfaces.nsISearchEngine);
+ switch (aVerb) {
+ case "engine-added":
+ gEngineView._engineStore.addEngine(aEngine);
+ gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
+ gSearchPane.buildDefaultEngineDropDown();
+ break;
+ case "engine-changed":
+ gEngineView._engineStore.reloadIcons();
+ gEngineView.invalidate();
+ break;
+ case "engine-removed":
+ gSearchPane.remove(aEngine);
+ break;
+ case "engine-current":
+ // If the user is going through the drop down using up/down keys, the
+ // dropdown may still be open (eg. on Windows) when engine-current is
+ // fired, so rebuilding the list unconditionally would get in the way.
+ let selectedEngine =
+ document.getElementById("defaultEngine").selectedItem.engine;
+ if (selectedEngine.name != aEngine.name)
+ gSearchPane.buildDefaultEngineDropDown();
+ break;
+ case "engine-default":
+ // Not relevant
+ break;
+ }
+ }
+ },
+
+ onInputBlur: function(aEvent) {
+ let tree = document.getElementById("engineList");
+ if (!tree.hasAttribute("editing"))
+ return;
+
+ // Accept input unless discarded.
+ let accept = aEvent.charCode != KeyEvent.DOM_VK_ESCAPE;
+ tree.stopEditing(accept);
+ },
+
+ onTreeSelect: function() {
+ document.getElementById("removeEngineButton").disabled =
+ !gEngineView.isEngineSelectedAndRemovable();
+ },
+
+ onTreeKeyPress: function(aEvent) {
+ let index = gEngineView.selectedIndex;
+ let tree = document.getElementById("engineList");
+ if (tree.hasAttribute("editing"))
+ return;
+
+ if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) {
+ // Space toggles the checkbox.
+ let newValue = !gEngineView._engineStore.engines[index].shown;
+ gEngineView.setCellValue(index, tree.columns.getFirstColumn(),
+ newValue.toString());
+ // Prevent page from scrolling on the space key.
+ aEvent.preventDefault();
+ }
+ else {
+ let isMac = Services.appinfo.OS == "Darwin";
+ if ((isMac && aEvent.keyCode == KeyEvent.DOM_VK_RETURN) ||
+ (!isMac && aEvent.keyCode == KeyEvent.DOM_VK_F2)) {
+ tree.startEditing(index, tree.columns.getLastColumn());
+ } else if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
+ (isMac && aEvent.shiftKey &&
+ aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE &&
+ gEngineView.isEngineSelectedAndRemovable())) {
+ // Delete and Shift+Backspace (Mac) removes selected engine.
+ Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
+ }
+ }
+ },
+
+ onRestoreDefaults: function() {
+ let num = gEngineView._engineStore.restoreDefaultEngines();
+ gEngineView.rowCountChanged(0, num);
+ gEngineView.invalidate();
+ },
+
+ showRestoreDefaults: function(aEnable) {
+ document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable;
+ },
+
+ remove: function(aEngine) {
+ let index = gEngineView._engineStore.removeEngine(aEngine);
+ gEngineView.rowCountChanged(index, -1);
+ gEngineView.invalidate();
+ gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
+ gEngineView.ensureRowIsVisible(gEngineView.currentIndex);
+ document.getElementById("engineList").focus();
+ },
+
+ editKeyword: Task.async(function* (aEngine, aNewKeyword) {
+ let keyword = aNewKeyword.trim();
+ if (keyword) {
+ let eduplicate = false;
+ let dupName = "";
+
+ // Check for duplicates in Places keywords.
+ let bduplicate = !!(yield PlacesUtils.keywords.fetch(keyword));
+
+ // Check for duplicates in changes we haven't committed yet
+ let engines = gEngineView._engineStore.engines;
+ for (let engine of engines) {
+ if (engine.alias == keyword &&
+ engine.name != aEngine.name) {
+ eduplicate = true;
+ dupName = engine.name;
+ break;
+ }
+ }
+
+ // Notify the user if they have chosen an existing engine/bookmark keyword
+ if (eduplicate || bduplicate) {
+ let strings = document.getElementById("engineManagerBundle");
+ let dtitle = strings.getString("duplicateTitle");
+ let bmsg = strings.getString("duplicateBookmarkMsg");
+ let emsg = strings.getFormattedString("duplicateEngineMsg", [dupName]);
+
+ Services.prompt.alert(window, dtitle, eduplicate ? emsg : bmsg);
+ return false;
+ }
+ }
+
+ gEngineView._engineStore.changeEngine(aEngine, "alias", keyword);
+ gEngineView.invalidate();
+ return true;
+ }),
+
+ saveOneClickEnginesList: function () {
+ let hiddenList = [];
+ for (let engine of gEngineView._engineStore.engines) {
+ if (!engine.shown)
+ hiddenList.push(engine.name);
+ }
+ document.getElementById("browser.search.hiddenOneOffs").value =
+ hiddenList.join(",");
+ },
+
+ setDefaultEngine: function () {
+ Services.search.currentEngine =
+ document.getElementById("defaultEngine").selectedItem.engine;
+ }
+};
+
+function onDragEngineStart(event) {
+ var selectedIndex = gEngineView.selectedIndex;
+ var tree = document.getElementById("engineList");
+ var row = { }, col = { }, child = { };
+ tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, child);
+ if (selectedIndex >= 0 && !gEngineView.isCheckBox(row.value, col.value)) {
+ event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
+ event.dataTransfer.effectAllowed = "move";
+ }
+}
+
+
+function EngineStore() {
+ let pref = document.getElementById("browser.search.hiddenOneOffs").value;
+ this.hiddenList = pref ? pref.split(",") : [];
+
+ this._engines = Services.search.getVisibleEngines().map(this._cloneEngine, this);
+ this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine, this);
+
+ // check if we need to disable the restore defaults button
+ var someHidden = this._defaultEngines.some(e => e.hidden);
+ gSearchPane.showRestoreDefaults(someHidden);
+}
+EngineStore.prototype = {
+ _engines: null,
+ _defaultEngines: null,
+
+ get engines() {
+ return this._engines;
+ },
+ set engines(val) {
+ this._engines = val;
+ return val;
+ },
+
+ _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
+ return this._engines.indexOf(aEngine);
+ },
+
+ _getEngineByName: function ES_getEngineByName(aName) {
+ return this._engines.find(engine => engine.name == aName);
+ },
+
+ _cloneEngine: function ES_cloneEngine(aEngine) {
+ var clonedObj={};
+ for (var i in aEngine)
+ clonedObj[i] = aEngine[i];
+ clonedObj.originalEngine = aEngine;
+ clonedObj.shown = this.hiddenList.indexOf(clonedObj.name) == -1;
+ return clonedObj;
+ },
+
+ // Callback for Array's some(). A thisObj must be passed to some()
+ _isSameEngine: function ES_isSameEngine(aEngineClone) {
+ return aEngineClone.originalEngine == this.originalEngine;
+ },
+
+ addEngine: function ES_addEngine(aEngine) {
+ this._engines.push(this._cloneEngine(aEngine));
+ },
+
+ moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
+ if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
+ throw new Error("ES_moveEngine: invalid aNewIndex!");
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("ES_moveEngine: invalid engine?");
+
+ if (index == aNewIndex)
+ return; // nothing to do
+
+ // Move the engine in our internal store
+ var removedEngine = this._engines.splice(index, 1)[0];
+ this._engines.splice(aNewIndex, 0, removedEngine);
+
+ Services.search.moveEngine(aEngine.originalEngine, aNewIndex);
+ },
+
+ removeEngine: function ES_removeEngine(aEngine) {
+ if (this._engines.length == 1) {
+ throw new Error("Cannot remove last engine!");
+ }
+
+ let engineName = aEngine.name;
+ let index = this._engines.findIndex(element => element.name == engineName);
+
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ let removedEngine = this._engines.splice(index, 1)[0];
+
+ if (this._defaultEngines.some(this._isSameEngine, removedEngine))
+ gSearchPane.showRestoreDefaults(true);
+ gSearchPane.buildDefaultEngineDropDown();
+ return index;
+ },
+
+ restoreDefaultEngines: function ES_restoreDefaultEngines() {
+ var added = 0;
+
+ for (var i = 0; i < this._defaultEngines.length; ++i) {
+ var e = this._defaultEngines[i];
+
+ // If the engine is already in the list, just move it.
+ if (this._engines.some(this._isSameEngine, e)) {
+ this.moveEngine(this._getEngineByName(e.name), i);
+ } else {
+ // Otherwise, add it back to our internal store
+
+ // The search service removes the alias when an engine is hidden,
+ // so clear any alias we may have cached before unhiding the engine.
+ e.alias = "";
+
+ this._engines.splice(i, 0, e);
+ let engine = e.originalEngine;
+ engine.hidden = false;
+ Services.search.moveEngine(engine, i);
+ added++;
+ }
+ }
+ Services.search.resetToOriginalDefaultEngine();
+ gSearchPane.showRestoreDefaults(false);
+ gSearchPane.buildDefaultEngineDropDown();
+ return added;
+ },
+
+ changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ this._engines[index][aProp] = aNewValue;
+ aEngine.originalEngine[aProp] = aNewValue;
+ },
+
+ reloadIcons: function ES_reloadIcons() {
+ this._engines.forEach(function (e) {
+ e.uri = e.originalEngine.uri;
+ });
+ }
+};
+
+function EngineView(aEngineStore) {
+ this._engineStore = aEngineStore;
+}
+EngineView.prototype = {
+ _engineStore: null,
+ tree: null,
+
+ get lastIndex() {
+ return this.rowCount - 1;
+ },
+ get selectedIndex() {
+ var seln = this.selection;
+ if (seln.getRangeCount() > 0) {
+ var min = {};
+ seln.getRangeAt(0, min, {});
+ return min.value;
+ }
+ return -1;
+ },
+ get selectedEngine() {
+ return this._engineStore.engines[this.selectedIndex];
+ },
+
+ // Helpers
+ rowCountChanged: function (index, count) {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function () {
+ this.tree.invalidate();
+ },
+
+ ensureRowIsVisible: function (index) {
+ this.tree.ensureRowIsVisible(index);
+ },
+
+ getSourceIndexFromDrag: function (dataTransfer) {
+ return parseInt(dataTransfer.getData(ENGINE_FLAVOR));
+ },
+
+ isCheckBox: function(index, column) {
+ return column.id == "engineShown";
+ },
+
+ isEngineSelectedAndRemovable: function() {
+ return this.selectedIndex != -1 && this.lastIndex != 0;
+ },
+
+ // nsITreeView
+ get rowCount() {
+ return this._engineStore.engines.length;
+ },
+
+ getImageSrc: function(index, column) {
+ if (column.id == "engineName") {
+ if (this._engineStore.engines[index].iconURI)
+ return this._engineStore.engines[index].iconURI.spec;
+
+ if (window.devicePixelRatio > 1)
+ return "chrome://browser/skin/search-engine-placeholder@2x.png";
+ return "chrome://browser/skin/search-engine-placeholder.png";
+ }
+
+ return "";
+ },
+
+ getCellText: function(index, column) {
+ if (column.id == "engineName")
+ return this._engineStore.engines[index].name;
+ else if (column.id == "engineKeyword")
+ return this._engineStore.engines[index].alias;
+ return "";
+ },
+
+ setTree: function(tree) {
+ this.tree = tree;
+ },
+
+ canDrop: function(targetIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ return (sourceIndex != -1 &&
+ sourceIndex != targetIndex &&
+ sourceIndex != targetIndex + orientation);
+ },
+
+ drop: function(dropIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ var sourceEngine = this._engineStore.engines[sourceIndex];
+
+ const nsITreeView = Components.interfaces.nsITreeView;
+ if (dropIndex > sourceIndex) {
+ if (orientation == nsITreeView.DROP_BEFORE)
+ dropIndex--;
+ } else if (orientation == nsITreeView.DROP_AFTER) {
+ dropIndex++;
+ }
+
+ this._engineStore.moveEngine(sourceEngine, dropIndex);
+ gSearchPane.showRestoreDefaults(true);
+ gSearchPane.buildDefaultEngineDropDown();
+
+ // Redraw, and adjust selection
+ this.invalidate();
+ this.selection.select(dropIndex);
+ },
+
+ selection: null,
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(index, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function(index) { return false; },
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(parentIndex, index) { return false; },
+ getLevel: function(index) { return 0; },
+ getProgressMode: function(index, column) { },
+ getCellValue: function(index, column) {
+ if (column.id == "engineShown")
+ return this._engineStore.engines[index].shown;
+ return undefined;
+ },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(column) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(index, column) { return column.id != "engineName"; },
+ isSelectable: function(index, column) { return false; },
+ setCellValue: function(index, column, value) {
+ if (column.id == "engineShown") {
+ this._engineStore.engines[index].shown = value == "true";
+ gEngineView.invalidate();
+ gSearchPane.saveOneClickEnginesList();
+ }
+ },
+ setCellText: function(index, column, value) {
+ if (column.id == "engineKeyword") {
+ gSearchPane.editKeyword(this._engineStore.engines[index], value)
+ .then(valid => {
+ if (!valid)
+ document.getElementById("engineList").startEditing(index, column);
+ });
+ }
+ },
+ performAction: function(action) { },
+ performActionOnRow: function(action, index) { },
+ performActionOnCell: function(action, index, column) { }
+};
diff --git a/browser/components/preferences/in-content/search.xul b/browser/components/preferences/in-content/search.xul
new file mode 100644
index 000000000..95c7acd85
--- /dev/null
+++ b/browser/components/preferences/in-content/search.xul
@@ -0,0 +1,86 @@
+ <preferences id="searchPreferences" hidden="true" data-category="paneSearch">
+
+ <preference id="browser.search.suggest.enabled"
+ name="browser.search.suggest.enabled"
+ type="bool"/>
+
+ <preference id="browser.urlbar.suggest.searches"
+ name="browser.urlbar.suggest.searches"
+ type="bool"/>
+
+ <preference id="browser.search.hiddenOneOffs"
+ name="browser.search.hiddenOneOffs"
+ type="unichar"/>
+
+ </preferences>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/search.js"/>
+
+ <stringbundle id="engineManagerBundle" src="chrome://browser/locale/engineManager.properties"/>
+
+ <hbox id="header-search"
+ class="header"
+ hidden="true"
+ data-category="paneSearch">
+ <label class="header-name" flex="1">&paneSearch.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+ </hbox>
+
+ <!-- Default Search Engine -->
+ <groupbox id="defaultEngineGroup" align="start" data-category="paneSearch">
+ <caption label="&defaultSearchEngine.label;"/>
+ <label>&chooseYourDefaultSearchEngine.label;</label>
+ <menulist id="defaultEngine">
+ <menupopup/>
+ </menulist>
+ <checkbox id="suggestionsInSearchFieldsCheckbox"
+ label="&provideSearchSuggestions.label;"
+ accesskey="&provideSearchSuggestions.accesskey;"
+ preference="browser.search.suggest.enabled"/>
+ <vbox class="indent">
+ <checkbox id="urlBarSuggestion" label="&showURLBarSuggestions.label;"
+ accesskey="&showURLBarSuggestions.accesskey;"
+ preference="browser.urlbar.suggest.searches"/>
+ <hbox id="urlBarSuggestionPermanentPBLabel"
+ align="center" class="indent">
+ <label flex="1">&urlBarSuggestionsPermanentPB.label;</label>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <groupbox id="oneClickSearchProvidersGroup" data-category="paneSearch">
+ <caption label="&oneClickSearchEngines.label;"/>
+ <label>&chooseWhichOneToDisplay.label;</label>
+
+ <tree id="engineList" flex="1" rows="8" hidecolumnpicker="true" editable="true"
+ seltype="single">
+ <treechildren id="engineChildren" flex="1"/>
+ <treecols>
+ <treecol id="engineShown" type="checkbox" editable="true" sortable="false"/>
+ <treecol id="engineName" flex="4" label="&engineNameColumn.label;" sortable="false"/>
+ <treecol id="engineKeyword" flex="1" label="&engineKeywordColumn.label;" editable="true"
+ sortable="false"/>
+ </treecols>
+ </tree>
+
+ <hbox>
+ <button id="restoreDefaultSearchEngines"
+ label="&restoreDefaultSearchEngines.label;"
+ accesskey="&restoreDefaultSearchEngines.accesskey;"
+ />
+ <spacer flex="1"/>
+ <button id="removeEngineButton"
+ class="searchEngineAction"
+ label="&removeEngine.label;"
+ accesskey="&removeEngine.accesskey;"
+ disabled="true"
+ />
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox id="addEnginesBox" pack="start">
+ <label id="addEngines" class="text-link" value="&addMoreSearchEngines.label;"/>
+ </hbox>
+ </groupbox>
diff --git a/browser/components/preferences/in-content/security.js b/browser/components/preferences/in-content/security.js
new file mode 100644
index 000000000..a8ad28c7e
--- /dev/null
+++ b/browser/components/preferences/in-content/security.js
@@ -0,0 +1,302 @@
+/* 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/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var gSecurityPane = {
+ _pane: null,
+
+ /**
+ * Initializes master password UI.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gSecurityPane));
+ }
+
+ this._pane = document.getElementById("paneSecurity");
+ this._initMasterPasswordUI();
+ this._initSafeBrowsing();
+
+ setEventListener("addonExceptions", "command",
+ gSecurityPane.showAddonExceptions);
+ setEventListener("passwordExceptions", "command",
+ gSecurityPane.showPasswordExceptions);
+ setEventListener("useMasterPassword", "command",
+ gSecurityPane.updateMasterPasswordButton);
+ setEventListener("changeMasterPassword", "command",
+ gSecurityPane.changeMasterPassword);
+ setEventListener("showPasswords", "command",
+ gSecurityPane.showPasswords);
+ },
+
+ // ADD-ONS
+
+ /*
+ * Preferences:
+ *
+ * xpinstall.whitelist.required
+ * - true if a site must be added to a site whitelist before extensions
+ * provided by the site may be installed from it, false if the extension
+ * may be directly installed after a confirmation dialog
+ */
+
+ /**
+ * Enables/disables the add-ons Exceptions button depending on whether
+ * or not add-on installation warnings are displayed.
+ */
+ readWarnAddonInstall: function ()
+ {
+ var warn = document.getElementById("xpinstall.whitelist.required");
+ var exceptions = document.getElementById("addonExceptions");
+
+ exceptions.disabled = !warn.value;
+
+ // don't override the preference value
+ return undefined;
+ },
+
+ /**
+ * Displays the exceptions lists for add-on installation warnings.
+ */
+ showAddonExceptions: function ()
+ {
+ var bundlePrefs = document.getElementById("bundlePreferences");
+
+ var params = this._addonParams;
+ if (!params.windowTitle || !params.introText) {
+ params.windowTitle = bundlePrefs.getString("addons_permissions_title");
+ params.introText = bundlePrefs.getString("addonspermissionstext");
+ }
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Parameters for the add-on install permissions dialog.
+ */
+ _addonParams:
+ {
+ blockVisible: false,
+ sessionVisible: false,
+ allowVisible: true,
+ prefilledHost: "",
+ permissionType: "install"
+ },
+
+ // PASSWORDS
+
+ /*
+ * Preferences:
+ *
+ * signon.rememberSignons
+ * - true if passwords are remembered, false otherwise
+ */
+
+ /**
+ * Enables/disables the Exceptions button used to configure sites where
+ * passwords are never saved. When browser is set to start in Private
+ * Browsing mode, the "Remember passwords" UI is useless, so we disable it.
+ */
+ readSavePasswords: function ()
+ {
+ var pref = document.getElementById("signon.rememberSignons");
+ var excepts = document.getElementById("passwordExceptions");
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("savePasswords").disabled = true;
+ excepts.disabled = true;
+ return false;
+ }
+ excepts.disabled = !pref.value;
+ // don't override pref value in UI
+ return undefined;
+ },
+
+ /**
+ * Displays a dialog in which the user can view and modify the list of sites
+ * where passwords are never saved.
+ */
+ showPasswordExceptions: function ()
+ {
+ var bundlePrefs = document.getElementById("bundlePreferences");
+ var params = {
+ blockVisible: true,
+ sessionVisible: false,
+ allowVisible: false,
+ hideStatusColumn: true,
+ prefilledHost: "",
+ permissionType: "login-saving",
+ windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"),
+ introText: bundlePrefs.getString("savedLoginsExceptions_desc")
+ };
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Initializes master password UI: the "use master password" checkbox, selects
+ * the master password button to show, and enables/disables it as necessary.
+ * The master password is controlled by various bits of NSS functionality, so
+ * the UI for it can't be controlled by the normal preference bindings.
+ */
+ _initMasterPasswordUI: function ()
+ {
+ var noMP = !LoginHelper.isMasterPasswordSet();
+
+ var button = document.getElementById("changeMasterPassword");
+ button.disabled = noMP;
+
+ var checkbox = document.getElementById("useMasterPassword");
+ checkbox.checked = !noMP;
+ },
+
+ _initSafeBrowsing() {
+ let enableSafeBrowsing = document.getElementById("enableSafeBrowsing");
+ let blockDownloads = document.getElementById("blockDownloads");
+ let blockUncommonUnwanted = document.getElementById("blockUncommonUnwanted");
+
+ let safeBrowsingPhishingPref = document.getElementById("browser.safebrowsing.phishing.enabled");
+ let safeBrowsingMalwarePref = document.getElementById("browser.safebrowsing.malware.enabled");
+
+ let blockDownloadsPref = document.getElementById("browser.safebrowsing.downloads.enabled");
+ let malwareTable = document.getElementById("urlclassifier.malwareTable");
+
+ let blockUnwantedPref = document.getElementById("browser.safebrowsing.downloads.remote.block_potentially_unwanted");
+ let blockUncommonPref = document.getElementById("browser.safebrowsing.downloads.remote.block_uncommon");
+
+ enableSafeBrowsing.addEventListener("command", function() {
+ safeBrowsingPhishingPref.value = enableSafeBrowsing.checked;
+ safeBrowsingMalwarePref.value = enableSafeBrowsing.checked;
+
+ if (enableSafeBrowsing.checked) {
+ blockDownloads.removeAttribute("disabled");
+ if (blockDownloads.checked) {
+ blockUncommonUnwanted.removeAttribute("disabled");
+ }
+ } else {
+ blockDownloads.setAttribute("disabled", "true");
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+ });
+
+ blockDownloads.addEventListener("command", function() {
+ blockDownloadsPref.value = blockDownloads.checked;
+ if (blockDownloads.checked) {
+ blockUncommonUnwanted.removeAttribute("disabled");
+ } else {
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+ });
+
+ blockUncommonUnwanted.addEventListener("command", function() {
+ blockUnwantedPref.value = blockUncommonUnwanted.checked;
+ blockUncommonPref.value = blockUncommonUnwanted.checked;
+
+ let malware = malwareTable.value
+ .split(",")
+ .filter(x => x !== "goog-unwanted-shavar" && x !== "test-unwanted-simple");
+
+ if (blockUncommonUnwanted.checked) {
+ malware.push("goog-unwanted-shavar");
+ malware.push("test-unwanted-simple");
+ }
+
+ // sort alphabetically to keep the pref consistent
+ malware.sort();
+
+ malwareTable.value = malware.join(",");
+ });
+
+ // set initial values
+
+ enableSafeBrowsing.checked = safeBrowsingPhishingPref.value && safeBrowsingMalwarePref.value;
+ if (!enableSafeBrowsing.checked) {
+ blockDownloads.setAttribute("disabled", "true");
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+
+ blockDownloads.checked = blockDownloadsPref.value;
+ if (!blockDownloadsPref.value) {
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+
+ blockUncommonUnwanted.checked = blockUnwantedPref.value && blockUncommonPref.value;
+ },
+
+ /**
+ * Enables/disables the master password button depending on the state of the
+ * "use master password" checkbox, and prompts for master password removal if
+ * one is set.
+ */
+ updateMasterPasswordButton: function ()
+ {
+ var checkbox = document.getElementById("useMasterPassword");
+ var button = document.getElementById("changeMasterPassword");
+ button.disabled = !checkbox.checked;
+
+ // unchecking the checkbox should try to immediately remove the master
+ // password, because it's impossible to non-destructively remove the master
+ // password used to encrypt all the passwords without providing it (by
+ // design), and it would be extremely odd to pop up that dialog when the
+ // user closes the prefwindow and saves his settings
+ if (!checkbox.checked)
+ this._removeMasterPassword();
+ else
+ this.changeMasterPassword();
+
+ this._initMasterPasswordUI();
+ },
+
+ /**
+ * Displays the "remove master password" dialog to allow the user to remove
+ * the current master password. When the dialog is dismissed, master password
+ * UI is automatically updated.
+ */
+ _removeMasterPassword: function ()
+ {
+ var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+ getService(Ci.nsIPKCS11ModuleDB);
+ if (secmodDB.isFIPSEnabled) {
+ var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ var bundle = document.getElementById("bundlePreferences");
+ promptService.alert(window,
+ bundle.getString("pw_change_failed_title"),
+ bundle.getString("pw_change2empty_in_fips_mode"));
+ this._initMasterPasswordUI();
+ }
+ else {
+ gSubDialog.open("chrome://mozapps/content/preferences/removemp.xul",
+ null, null, this._initMasterPasswordUI.bind(this));
+ }
+ },
+
+ /**
+ * Displays a dialog in which the master password may be changed.
+ */
+ changeMasterPassword: function ()
+ {
+ gSubDialog.open("chrome://mozapps/content/preferences/changemp.xul",
+ "resizable=no", null, this._initMasterPasswordUI.bind(this));
+ },
+
+ /**
+ * Shows the sites where the user has saved passwords and the associated login
+ * information.
+ */
+ showPasswords: function ()
+ {
+ gSubDialog.open("chrome://passwordmgr/content/passwordManager.xul");
+ }
+
+};
diff --git a/browser/components/preferences/in-content/security.xul b/browser/components/preferences/in-content/security.xul
new file mode 100644
index 000000000..a10576c25
--- /dev/null
+++ b/browser/components/preferences/in-content/security.xul
@@ -0,0 +1,131 @@
+# 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/.
+
+<!-- Security panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/security.js"/>
+
+<preferences id="securityPreferences" hidden="true" data-category="paneSecurity">
+ <!-- XXX buttons -->
+ <preference id="pref.privacy.disable_button.view_passwords"
+ name="pref.privacy.disable_button.view_passwords"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.view_passwords_exceptions"
+ name="pref.privacy.disable_button.view_passwords_exceptions"
+ type="bool"/>
+
+ <!-- Add-ons, malware, phishing -->
+ <preference id="xpinstall.whitelist.required"
+ name="xpinstall.whitelist.required"
+ type="bool"/>
+
+ <preference id="browser.safebrowsing.malware.enabled"
+ name="browser.safebrowsing.malware.enabled"
+ type="bool"/>
+ <preference id="browser.safebrowsing.phishing.enabled"
+ name="browser.safebrowsing.phishing.enabled"
+ type="bool"/>
+
+ <preference id="browser.safebrowsing.downloads.enabled"
+ name="browser.safebrowsing.downloads.enabled"
+ type="bool"/>
+
+ <preference id="urlclassifier.malwareTable"
+ name="urlclassifier.malwareTable"
+ type="string"/>
+
+ <preference id="browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ name="browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ type="bool"/>
+ <preference id="browser.safebrowsing.downloads.remote.block_uncommon"
+ name="browser.safebrowsing.downloads.remote.block_uncommon"
+ type="bool"/>
+
+ <!-- Passwords -->
+ <preference id="signon.rememberSignons" name="signon.rememberSignons" type="bool"/>
+
+</preferences>
+
+<hbox id="header-security"
+ class="header"
+ hidden="true"
+ data-category="paneSecurity">
+ <label class="header-name" flex="1">&paneSecurity.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<!-- addons, forgery (phishing) UI -->
+<groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
+ <caption><label>&general.label;</label></caption>
+
+ <hbox id="addonInstallBox">
+ <checkbox id="warnAddonInstall"
+ label="&warnAddonInstall.label;"
+ accesskey="&warnAddonInstall.accesskey;"
+ preference="xpinstall.whitelist.required"
+ onsyncfrompreference="return gSecurityPane.readWarnAddonInstall();"/>
+ <spacer flex="1"/>
+ <button id="addonExceptions"
+ label="&addonExceptions.label;"
+ accesskey="&addonExceptions.accesskey;"/>
+ </hbox>
+
+ <separator class="thin"/>
+ <vbox align="start">
+ <checkbox id="enableSafeBrowsing"
+ label="&enableSafeBrowsing.label;"
+ accesskey="&enableSafeBrowsing.accesskey;" />
+ <vbox class="indent">
+ <checkbox id="blockDownloads"
+ label="&blockDownloads.label;"
+ accesskey="&blockDownloads.accesskey;" />
+ <checkbox id="blockUncommonUnwanted"
+ label="&blockUncommonUnwanted.label;"
+ accesskey="&blockUncommonUnwanted.accesskey;" />
+ </vbox>
+ </vbox>
+</groupbox>
+
+<!-- Passwords -->
+<groupbox id="passwordsGroup" orient="vertical" data-category="paneSecurity" hidden="true">
+ <caption><label>&logins.label;</label></caption>
+
+ <hbox id="savePasswordsBox">
+ <checkbox id="savePasswords"
+ label="&rememberLogins.label;" accesskey="&rememberLogins.accesskey;"
+ preference="signon.rememberSignons"
+ onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
+ <spacer flex="1"/>
+ <button id="passwordExceptions"
+ label="&passwordExceptions.label;"
+ accesskey="&passwordExceptions.accesskey;"
+ preference="pref.privacy.disable_button.view_passwords_exceptions"/>
+ </hbox>
+ <grid id="passwordGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="passwordRows">
+ <row id="masterPasswordRow">
+ <hbox id="masterPasswordBox">
+ <checkbox id="useMasterPassword"
+ label="&useMasterPassword.label;"
+ accesskey="&useMasterPassword.accesskey;"/>
+ <spacer flex="1"/>
+ </hbox>
+ <button id="changeMasterPassword"
+ label="&changeMasterPassword.label;"
+ accesskey="&changeMasterPassword.accesskey;"/>
+ </row>
+ <row id="showPasswordRow">
+ <hbox id="showPasswordsBox"/>
+ <button id="showPasswords"
+ label="&savedLogins.label;" accesskey="&savedLogins.accesskey;"
+ preference="pref.privacy.disable_button.view_passwords"/>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
diff --git a/browser/components/preferences/in-content/subdialogs.js b/browser/components/preferences/in-content/subdialogs.js
new file mode 100644
index 000000000..bb8d0048f
--- /dev/null
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -0,0 +1,434 @@
+/* - 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 gSubDialog = {
+ _closingCallback: null,
+ _closingEvent: null,
+ _isClosing: false,
+ _frame: null,
+ _overlay: null,
+ _box: null,
+ _injectedStyleSheets: [
+ "chrome://browser/skin/preferences/preferences.css",
+ "chrome://global/skin/in-content/common.css",
+ "chrome://browser/skin/preferences/in-content/preferences.css",
+ "chrome://browser/skin/preferences/in-content/dialog.css",
+ ],
+ _resizeObserver: null,
+
+ init: function() {
+ this._frame = document.getElementById("dialogFrame");
+ this._overlay = document.getElementById("dialogOverlay");
+ this._box = document.getElementById("dialogBox");
+ this._closeButton = document.getElementById("dialogClose");
+ },
+
+ updateTitle: function(aEvent) {
+ if (aEvent.target != gSubDialog._frame.contentDocument)
+ return;
+ document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
+ },
+
+ injectXMLStylesheet: function(aStylesheetURL) {
+ let contentStylesheet = this._frame.contentDocument.createProcessingInstruction(
+ 'xml-stylesheet',
+ 'href="' + aStylesheetURL + '" type="text/css"'
+ );
+ this._frame.contentDocument.insertBefore(contentStylesheet,
+ this._frame.contentDocument.documentElement);
+ },
+
+ open: function(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+ // If we're already open/opening on this URL, do nothing.
+ if (this._openedURL == aURL && !this._isClosing) {
+ return;
+ }
+ // If we're open on some (other) URL or we're closing, open when closing has finished.
+ if (this._openedURL || this._isClosing) {
+ if (!this._isClosing) {
+ this.close();
+ }
+ let args = Array.from(arguments);
+ this._closingPromise.then(() => {
+ this.open.apply(this, args);
+ });
+ return;
+ }
+ this._addDialogEventListeners();
+
+ let features = (aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
+ let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
+ if (aClosingCallback) {
+ this._closingCallback = aClosingCallback.bind(dialog);
+ }
+
+ this._closingEvent = null;
+ this._isClosing = false;
+ this._openedURL = aURL;
+
+ features = features.replace(/,/g, "&");
+ let featureParams = new URLSearchParams(features.toLowerCase());
+ this._box.setAttribute("resizable", featureParams.has("resizable") &&
+ featureParams.get("resizable") != "no" &&
+ featureParams.get("resizable") != "0");
+ },
+
+ close: function(aEvent = null) {
+ if (this._isClosing) {
+ return;
+ }
+ this._isClosing = true;
+ this._closingPromise = new Promise(resolve => {
+ this._resolveClosePromise = resolve;
+ });
+
+ if (this._closingCallback) {
+ try {
+ this._closingCallback.call(null, aEvent);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ this._closingCallback = null;
+ }
+
+ this._removeDialogEventListeners();
+
+ this._overlay.style.visibility = "";
+ // Clear the sizing inline styles.
+ this._frame.removeAttribute("style");
+ // Clear the sizing attributes
+ this._box.removeAttribute("width");
+ this._box.removeAttribute("height");
+ this._box.style.removeProperty("min-height");
+ this._box.style.removeProperty("min-width");
+
+ setTimeout(() => {
+ // Unload the dialog after the event listeners run so that the load of about:blank isn't
+ // cancelled by the ESC <key>.
+ let onBlankLoad = e => {
+ if (this._frame.contentWindow.location.href == "about:blank") {
+ this._frame.removeEventListener("load", onBlankLoad);
+ // We're now officially done closing, so update the state to reflect that.
+ delete this._openedURL;
+ this._isClosing = false;
+ this._resolveClosePromise();
+ }
+ };
+ this._frame.addEventListener("load", onBlankLoad);
+ this._frame.loadURI("about:blank");
+ }, 0);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "command":
+ this._frame.contentWindow.close();
+ break;
+ case "dialogclosing":
+ this._onDialogClosing(aEvent);
+ break;
+ case "DOMTitleChanged":
+ this.updateTitle(aEvent);
+ break;
+ case "DOMFrameContentLoaded":
+ this._onContentLoaded(aEvent);
+ break;
+ case "load":
+ this._onLoad(aEvent);
+ break;
+ case "unload":
+ this._onUnload(aEvent);
+ break;
+ case "keydown":
+ this._onKeyDown(aEvent);
+ break;
+ case "focus":
+ this._onParentWinFocus(aEvent);
+ break;
+ }
+ },
+
+ /* Private methods */
+
+ _onUnload: function(aEvent) {
+ if (aEvent.target.location.href == this._openedURL) {
+ this._frame.contentWindow.close();
+ }
+ },
+
+ _onContentLoaded: function(aEvent) {
+ if (aEvent.target != this._frame || aEvent.target.contentWindow.location == "about:blank") {
+ return;
+ }
+
+ for (let styleSheetURL of this._injectedStyleSheets) {
+ this.injectXMLStylesheet(styleSheetURL);
+ }
+
+ // Provide the ability for the dialog to know that it is being loaded "in-content".
+ this._frame.contentDocument.documentElement.setAttribute("subdialog", "true");
+
+ this._frame.contentWindow.addEventListener("dialogclosing", this);
+
+ let oldResizeBy = this._frame.contentWindow.resizeBy;
+ this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
+ // Only handle resizeByHeight currently.
+ let frameHeight = gSubDialog._frame.clientHeight;
+ let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
+
+ gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
+ gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
+
+ oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
+ };
+
+ // Make window.close calls work like dialog closing.
+ let oldClose = this._frame.contentWindow.close;
+ this._frame.contentWindow.close = function() {
+ var closingEvent = gSubDialog._closingEvent;
+ if (!closingEvent) {
+ closingEvent = new CustomEvent("dialogclosing", {
+ bubbles: true,
+ detail: { button: null },
+ });
+
+ gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
+ }
+
+ gSubDialog.close(closingEvent);
+ oldClose.call(gSubDialog._frame.contentWindow);
+ };
+
+ // XXX: Hack to make focus during the dialog's load functions work. Make the element visible
+ // sooner in DOMContentLoaded but mostly invisible instead of changing visibility just before
+ // the dialog's load event.
+ this._overlay.style.visibility = "visible";
+ this._overlay.style.opacity = "0.01";
+ },
+
+ _onLoad: function(aEvent) {
+ if (aEvent.target.contentWindow.location == "about:blank") {
+ return;
+ }
+
+ // Do this on load to wait for the CSS to load and apply before calculating the size.
+ let docEl = this._frame.contentDocument.documentElement;
+
+ let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
+ let groupBoxTitleHeight = groupBoxTitle.clientHeight +
+ parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
+
+ let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
+ // These are deduced from styles which we don't change, so it's safe to get them now:
+ let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
+ let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
+ let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
+ let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
+
+ // The difference between the frame and box shouldn't change, either:
+ let boxRect = this._box.getBoundingClientRect();
+ let frameRect = this._frame.getBoundingClientRect();
+ let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
+
+ // Then determine and set a bunch of width stuff:
+ let frameMinWidth = docEl.style.width || docEl.scrollWidth + "px";
+ let frameWidth = docEl.getAttribute("width") ? docEl.getAttribute("width") + "px" :
+ frameMinWidth;
+ this._frame.style.width = frameWidth;
+ this._box.style.minWidth = "calc(" +
+ (boxHorizontalBorder + boxHorizontalPadding) +
+ "px + " + frameMinWidth + ")";
+
+ // Now do the same but for the height. We need to do this afterwards because otherwise
+ // XUL assumes we'll optimize for height and gives us "wrong" values which then are no
+ // longer correct after we set the width:
+ let frameMinHeight = docEl.style.height || docEl.scrollHeight + "px";
+ let frameHeight = docEl.getAttribute("height") ? docEl.getAttribute("height") + "px" :
+ frameMinHeight;
+
+ // Now check if the frame height we calculated is possible at this window size,
+ // accounting for titlebar, padding/border and some spacing.
+ let maxHeight = window.innerHeight - frameSizeDifference - 30;
+ // Do this with a frame height in pixels...
+ let comparisonFrameHeight;
+ if (frameHeight.endsWith("em")) {
+ let fontSize = parseFloat(getComputedStyle(this._frame).fontSize);
+ comparisonFrameHeight = parseFloat(frameHeight, 10) * fontSize;
+ } else if (frameHeight.endsWith("px")) {
+ comparisonFrameHeight = parseFloat(frameHeight, 10);
+ } else {
+ Cu.reportError("This dialog (" + this._frame.contentWindow.location.href + ") " +
+ "set a height in non-px-non-em units ('" + frameHeight + "'), " +
+ "which is likely to lead to bad sizing in in-content preferences. " +
+ "Please consider changing this.");
+ comparisonFrameHeight = parseFloat(frameHeight);
+ }
+
+ if (comparisonFrameHeight > maxHeight) {
+ // If the height is bigger than that of the window, we should let the contents scroll:
+ frameHeight = maxHeight + "px";
+ frameMinHeight = maxHeight + "px";
+ let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
+ for (let container of containers) {
+ container.classList.add("doScroll");
+ }
+ }
+
+ this._frame.style.height = frameHeight;
+ this._box.style.minHeight = "calc(" +
+ (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
+ "px + " + frameMinHeight + ")";
+
+ this._overlay.style.visibility = "visible";
+ this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
+
+ if (this._box.getAttribute("resizable") == "true") {
+ this._resizeObserver = new MutationObserver(this._onResize);
+ this._resizeObserver.observe(this._box, {attributes: true});
+ }
+
+ this._trapFocus();
+ },
+
+ _onResize: function(mutations) {
+ let frame = gSubDialog._frame;
+ // The width and height styles are needed for the initial
+ // layout of the frame, but afterward they need to be removed
+ // or their presence will restrict the contents of the <browser>
+ // from resizing to a smaller size.
+ frame.style.removeProperty("width");
+ frame.style.removeProperty("height");
+
+ let docEl = frame.contentDocument.documentElement;
+ let persistedAttributes = docEl.getAttribute("persist");
+ if (!persistedAttributes ||
+ (!persistedAttributes.includes("width") &&
+ !persistedAttributes.includes("height"))) {
+ return;
+ }
+
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "width") {
+ docEl.setAttribute("width", docEl.scrollWidth);
+ } else if (mutation.attributeName == "height") {
+ docEl.setAttribute("height", docEl.scrollHeight);
+ }
+ }
+ },
+
+ _onDialogClosing: function(aEvent) {
+ this._frame.contentWindow.removeEventListener("dialogclosing", this);
+ this._closingEvent = aEvent;
+ },
+
+ _onKeyDown: function(aEvent) {
+ if (aEvent.currentTarget == window && aEvent.keyCode == aEvent.DOM_VK_ESCAPE &&
+ !aEvent.defaultPrevented) {
+ this.close(aEvent);
+ return;
+ }
+ if (aEvent.keyCode != aEvent.DOM_VK_TAB ||
+ aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
+ return;
+ }
+
+ let fm = Services.focus;
+
+ function isLastFocusableElement(el) {
+ // XXXgijs unfortunately there is no way to get the last focusable element without asking
+ // the focus manager to move focus to it.
+ let rv = el == fm.moveFocus(gSubDialog._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
+ fm.setFocus(el, 0);
+ return rv;
+ }
+
+ let forward = !aEvent.shiftKey;
+ // check if focus is leaving the frame (incl. the close button):
+ if ((aEvent.target == this._closeButton && !forward) ||
+ (isLastFocusableElement(aEvent.originalTarget) && forward)) {
+ aEvent.preventDefault();
+ aEvent.stopImmediatePropagation();
+ let parentWin = this._getBrowser().ownerGlobal;
+ if (forward) {
+ fm.moveFocus(parentWin, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
+ } else {
+ // Somehow, moving back 'past' the opening doc is not trivial. Cheat by doing it in 2 steps:
+ fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
+ fm.moveFocus(parentWin, null, fm.MOVEFOCUS_BACKWARD, fm.FLAG_BYKEY);
+ }
+ }
+ },
+
+ _onParentWinFocus: function(aEvent) {
+ // Explicitly check for the focus target of |window| to avoid triggering this when the window
+ // is refocused
+ if (aEvent.target != this._closeButton && aEvent.target != window) {
+ this._closeButton.focus();
+ }
+ },
+
+ _addDialogEventListeners: function() {
+ // Make the close button work.
+ this._closeButton.addEventListener("command", this);
+
+ // DOMTitleChanged isn't fired on the frame, only on the chromeEventHandler
+ let chromeBrowser = this._getBrowser();
+ chromeBrowser.addEventListener("DOMTitleChanged", this, true);
+
+ // Similarly DOMFrameContentLoaded only fires on the top window
+ window.addEventListener("DOMFrameContentLoaded", this, true);
+
+ // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
+ // otherwise there is a flicker of the stylesheet applying.
+ this._frame.addEventListener("load", this);
+
+ chromeBrowser.addEventListener("unload", this, true);
+ // Ensure we get <esc> keypresses even if nothing in the subdialog is focusable
+ // (happens on OS X when only text inputs and lists are focusable, and
+ // the subdialog only has checkboxes/radiobuttons/buttons)
+ window.addEventListener("keydown", this, true);
+ },
+
+ _removeDialogEventListeners: function() {
+ let chromeBrowser = this._getBrowser();
+ chromeBrowser.removeEventListener("DOMTitleChanged", this, true);
+ chromeBrowser.removeEventListener("unload", this, true);
+
+ this._closeButton.removeEventListener("command", this);
+
+ window.removeEventListener("DOMFrameContentLoaded", this, true);
+ this._frame.removeEventListener("load", this);
+ this._frame.contentWindow.removeEventListener("dialogclosing", this);
+ window.removeEventListener("keydown", this, true);
+ if (this._resizeObserver) {
+ this._resizeObserver.disconnect();
+ this._resizeObserver = null;
+ }
+ this._untrapFocus();
+ },
+
+ _trapFocus: function() {
+ let fm = Services.focus;
+ fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, 0);
+ this._frame.contentDocument.addEventListener("keydown", this, true);
+ this._closeButton.addEventListener("keydown", this);
+
+ window.addEventListener("focus", this, true);
+ },
+
+ _untrapFocus: function() {
+ this._frame.contentDocument.removeEventListener("keydown", this, true);
+ this._closeButton.removeEventListener("keydown", this);
+ window.removeEventListener("focus", this);
+ },
+
+ _getBrowser: function() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ },
+};
diff --git a/browser/components/preferences/in-content/sync.js b/browser/components/preferences/in-content/sync.js
new file mode 100644
index 000000000..27f7cd48c
--- /dev/null
+++ b/browser/components/preferences/in-content/sync.js
@@ -0,0 +1,673 @@
+/* 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/. */
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
+ return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+
+const PAGE_NO_ACCOUNT = 0;
+const PAGE_HAS_ACCOUNT = 1;
+const PAGE_NEEDS_UPDATE = 2;
+const FXA_PAGE_LOGGED_OUT = 3;
+const FXA_PAGE_LOGGED_IN = 4;
+
+// Indexes into the "login status" deck.
+// We are in a successful verified state - everything should work!
+const FXA_LOGIN_VERIFIED = 0;
+// We have logged in to an unverified account.
+const FXA_LOGIN_UNVERIFIED = 1;
+// We are logged in locally, but the server rejected our credentials.
+const FXA_LOGIN_FAILED = 2;
+
+var gSyncPane = {
+ prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
+ "engine.tabs", "engine.history"],
+
+ get page() {
+ return document.getElementById("weavePrefsDeck").selectedIndex;
+ },
+
+ set page(val) {
+ document.getElementById("weavePrefsDeck").selectedIndex = val;
+ },
+
+ get _usingCustomServer() {
+ return Weave.Svc.Prefs.isSet("serverURL");
+ },
+
+ needsUpdate: function () {
+ this.page = PAGE_NEEDS_UPDATE;
+ let label = document.getElementById("loginError");
+ label.textContent = Weave.Utils.getErrorString(Weave.Status.login);
+ label.className = "error";
+ },
+
+ init: function () {
+ this._setupEventListeners();
+
+ // If the Service hasn't finished initializing, wait for it.
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+
+ if (xps.ready) {
+ this._init();
+ return;
+ }
+
+ // it may take some time before we can determine what provider to use
+ // and the state of that provider, so show the "please wait" page.
+ this._showLoadPage(xps);
+
+ let onUnload = function () {
+ window.removeEventListener("unload", onUnload, false);
+ try {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ } catch (e) {}
+ };
+
+ let onReady = function () {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ window.removeEventListener("unload", onUnload, false);
+ this._init();
+ }.bind(this);
+
+ Services.obs.addObserver(onReady, "weave:service:ready", false);
+ window.addEventListener("unload", onUnload, false);
+
+ xps.ensureLoaded();
+ },
+
+ _showLoadPage: function (xps) {
+ let username;
+ try {
+ username = Services.prefs.getCharPref("services.sync.username");
+ } catch (e) {}
+ if (!username) {
+ this.page = FXA_PAGE_LOGGED_OUT;
+ } else if (xps.fxAccountsEnabled) {
+ // Use cached values while we wait for the up-to-date values
+ let cachedComputerName;
+ try {
+ cachedComputerName = Services.prefs.getCharPref("services.sync.client.name");
+ }
+ catch (e) {
+ cachedComputerName = "";
+ }
+ document.getElementById("fxaEmailAddress1").textContent = username;
+ this._populateComputerName(cachedComputerName);
+ this.page = FXA_PAGE_LOGGED_IN;
+ } else { // Old Sync
+ this.page = PAGE_HAS_ACCOUNT;
+ }
+ },
+
+ _init: function () {
+ let topics = ["weave:service:login:error",
+ "weave:service:login:finish",
+ "weave:service:start-over:finish",
+ "weave:service:setup-complete",
+ "weave:service:logout:finish",
+ FxAccountsCommon.ONVERIFIED_NOTIFICATION,
+ FxAccountsCommon.ONLOGIN_NOTIFICATION,
+ FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
+ ];
+ // Add the observers now and remove them on unload
+ // XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+ // of `this`. Fix in a followup. (bug 583347)
+ topics.forEach(function (topic) {
+ Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
+ }, this);
+
+ window.addEventListener("unload", function() {
+ topics.forEach(function (topic) {
+ Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
+ }, gSyncPane);
+ }, false);
+
+ XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => {
+ return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
+ });
+
+ XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => {
+ return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+ });
+
+ let url = Services.prefs.getCharPref("identity.mobilepromo.android") + "sync-preferences";
+ document.getElementById("fxaMobilePromo-android").setAttribute("href", url);
+ document.getElementById("fxaMobilePromo-android-hasFxaAccount").setAttribute("href", url);
+ url = Services.prefs.getCharPref("identity.mobilepromo.ios") + "sync-preferences";
+ document.getElementById("fxaMobilePromo-ios").setAttribute("href", url);
+ document.getElementById("fxaMobilePromo-ios-hasFxaAccount").setAttribute("href", url);
+
+ document.getElementById("tosPP-small-ToS").setAttribute("href", gSyncUtils.tosURL);
+ document.getElementById("tosPP-normal-ToS").setAttribute("href", gSyncUtils.tosURL);
+ document.getElementById("tosPP-small-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
+ document.getElementById("tosPP-normal-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
+
+ fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(url => {
+ document.getElementById("verifiedManage").setAttribute("href", url);
+ });
+
+ this.updateWeavePrefs();
+
+ this._initProfileImageUI();
+ },
+
+ _toggleComputerNameControls: function(editMode) {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ textbox.disabled = !editMode;
+ document.getElementById("fxaChangeDeviceName").hidden = editMode;
+ document.getElementById("fxaCancelChangeDeviceName").hidden = !editMode;
+ document.getElementById("fxaSaveChangeDeviceName").hidden = !editMode;
+ },
+
+ _focusComputerNameTextbox: function() {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ let valLength = textbox.value.length;
+ textbox.focus();
+ textbox.setSelectionRange(valLength, valLength);
+ },
+
+ _blurComputerNameTextbox: function() {
+ document.getElementById("fxaSyncComputerName").blur();
+ },
+
+ _focusAfterComputerNameTextbox: function() {
+ // Focus the most appropriate element that's *not* the "computer name" box.
+ Services.focus.moveFocus(window,
+ document.getElementById("fxaSyncComputerName"),
+ Services.focus.MOVEFOCUS_FORWARD, 0);
+ },
+
+ _updateComputerNameValue: function(save) {
+ if (save) {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ Weave.Service.clientsEngine.localName = textbox.value;
+ }
+ this._populateComputerName(Weave.Service.clientsEngine.localName);
+ },
+
+ _setupEventListeners: function() {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gSyncPane));
+ }
+
+ setEventListener("noAccountSetup", "click", function (aEvent) {
+ aEvent.stopPropagation();
+ gSyncPane.openSetup(null);
+ });
+ setEventListener("noAccountPair", "click", function (aEvent) {
+ aEvent.stopPropagation();
+ gSyncPane.openSetup('pair');
+ });
+ setEventListener("syncChangePassword", "command",
+ () => gSyncUtils.changePassword());
+ setEventListener("syncResetPassphrase", "command",
+ () => gSyncUtils.resetPassphrase());
+ setEventListener("syncReset", "command", gSyncPane.resetSync);
+ setEventListener("syncAddDeviceLabel", "click", function () {
+ gSyncPane.openAddDevice();
+ return false;
+ });
+ setEventListener("syncEnginesList", "select", function () {
+ if (this.selectedCount)
+ this.clearSelection();
+ });
+ setEventListener("syncComputerName", "change", function (e) {
+ gSyncUtils.changeName(e.target);
+ });
+ setEventListener("fxaChangeDeviceName", "command", function () {
+ this._toggleComputerNameControls(true);
+ this._focusComputerNameTextbox();
+ });
+ setEventListener("fxaCancelChangeDeviceName", "command", function () {
+ // We explicitly blur the textbox because of bug 75324, then after
+ // changing the state of the buttons, force focus to whatever the focus
+ // manager thinks should be next (which on the mac, depends on an OSX
+ // keyboard access preference)
+ this._blurComputerNameTextbox();
+ this._toggleComputerNameControls(false);
+ this._updateComputerNameValue(false);
+ this._focusAfterComputerNameTextbox();
+ });
+ setEventListener("fxaSaveChangeDeviceName", "command", function () {
+ // Work around bug 75324 - see above.
+ this._blurComputerNameTextbox();
+ this._toggleComputerNameControls(false);
+ this._updateComputerNameValue(true);
+ this._focusAfterComputerNameTextbox();
+ });
+ setEventListener("unlinkDevice", "click", function () {
+ gSyncPane.startOver(true);
+ return false;
+ });
+ setEventListener("loginErrorUpdatePass", "click", function () {
+ gSyncPane.updatePass();
+ return false;
+ });
+ setEventListener("loginErrorResetPass", "click", function () {
+ gSyncPane.resetPass();
+ return false;
+ });
+ setEventListener("loginErrorStartOver", "click", function () {
+ gSyncPane.startOver(true);
+ return false;
+ });
+ setEventListener("noFxaSignUp", "command", function () {
+ gSyncPane.signUp();
+ return false;
+ });
+ setEventListener("noFxaSignIn", "command", function () {
+ gSyncPane.signIn();
+ return false;
+ });
+ setEventListener("fxaUnlinkButton", "command", function () {
+ gSyncPane.unlinkFirefoxAccount(true);
+ });
+ setEventListener("verifyFxaAccount", "command",
+ gSyncPane.verifyFirefoxAccount);
+ setEventListener("unverifiedUnlinkFxaAccount", "command", function () {
+ /* no warning as account can't have previously synced */
+ gSyncPane.unlinkFirefoxAccount(false);
+ });
+ setEventListener("rejectReSignIn", "command",
+ gSyncPane.reSignIn);
+ setEventListener("rejectUnlinkFxaAccount", "command", function () {
+ gSyncPane.unlinkFirefoxAccount(true);
+ });
+ setEventListener("fxaSyncComputerName", "keypress", function (e) {
+ if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
+ document.getElementById("fxaSaveChangeDeviceName").click();
+ } else if (e.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+ document.getElementById("fxaCancelChangeDeviceName").click();
+ }
+ });
+ },
+
+ _initProfileImageUI: function () {
+ try {
+ if (Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled")) {
+ document.getElementById("fxaProfileImage").hidden = false;
+ }
+ } catch (e) { }
+ },
+
+ updateWeavePrefs: function () {
+ let service = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ // service.fxAccountsEnabled is false iff sync is already configured for
+ // the legacy provider.
+ if (service.fxAccountsEnabled) {
+ let displayNameLabel = document.getElementById("fxaDisplayName");
+ let fxaEmailAddress1Label = document.getElementById("fxaEmailAddress1");
+ fxaEmailAddress1Label.hidden = false;
+ displayNameLabel.hidden = true;
+
+ let profileInfoEnabled;
+ try {
+ profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
+ } catch (ex) {}
+
+ // determine the fxa status...
+ this._showLoadPage(service);
+
+ fxAccounts.getSignedInUser().then(data => {
+ if (!data) {
+ this.page = FXA_PAGE_LOGGED_OUT;
+ return false;
+ }
+ this.page = FXA_PAGE_LOGGED_IN;
+ // We are logged in locally, but maybe we are in a state where the
+ // server rejected our credentials (eg, password changed on the server)
+ let fxaLoginStatus = document.getElementById("fxaLoginStatus");
+ let syncReady;
+ // Not Verfied implies login error state, so check that first.
+ if (!data.verified) {
+ fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
+ syncReady = false;
+ // So we think we are logged in, so login problems are next.
+ // (Although if the Sync identity manager is still initializing, we
+ // ignore login errors and assume all will eventually be good.)
+ // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
+ // All other login failures are assumed to be transient and should go
+ // away by themselves, so aren't reflected here.
+ } else if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
+ syncReady = false;
+ // Else we must be golden (or in an error state we expect to magically
+ // resolve itself)
+ } else {
+ fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
+ syncReady = true;
+ }
+ fxaEmailAddress1Label.textContent = data.email;
+ document.getElementById("fxaEmailAddress2").textContent = data.email;
+ document.getElementById("fxaEmailAddress3").textContent = data.email;
+ this._populateComputerName(Weave.Service.clientsEngine.localName);
+ let engines = document.getElementById("fxaSyncEngines")
+ for (let checkbox of engines.querySelectorAll("checkbox")) {
+ checkbox.disabled = !syncReady;
+ }
+ document.getElementById("fxaChangeDeviceName").disabled = !syncReady;
+
+ // Clear the profile image (if any) of the previously logged in account.
+ document.getElementById("fxaProfileImage").style.removeProperty("list-style-image");
+
+ // If the account is verified the next promise in the chain will
+ // fetch profile data.
+ return data.verified;
+ }).then(isVerified => {
+ if (isVerified) {
+ return fxAccounts.getSignedInUserProfile();
+ }
+ return null;
+ }).then(data => {
+ let fxaLoginStatus = document.getElementById("fxaLoginStatus");
+ if (data && profileInfoEnabled) {
+ if (data.displayName) {
+ fxaLoginStatus.setAttribute("hasName", true);
+ displayNameLabel.hidden = false;
+ displayNameLabel.textContent = data.displayName;
+ } else {
+ fxaLoginStatus.removeAttribute("hasName");
+ }
+ if (data.avatar) {
+ let bgImage = "url(\"" + data.avatar + "\")";
+ let profileImageElement = document.getElementById("fxaProfileImage");
+ profileImageElement.style.listStyleImage = bgImage;
+
+ let img = new Image();
+ img.onerror = () => {
+ // Clear the image if it has trouble loading. Since this callback is asynchronous
+ // we check to make sure the image is still the same before we clear it.
+ if (profileImageElement.style.listStyleImage === bgImage) {
+ profileImageElement.style.removeProperty("list-style-image");
+ }
+ };
+ img.src = data.avatar;
+ }
+ } else {
+ fxaLoginStatus.removeAttribute("hasName");
+ }
+ }, err => {
+ FxAccountsCommon.log.error(err);
+ }).catch(err => {
+ // If we get here something's really busted
+ Cu.reportError(String(err));
+ });
+
+ // If fxAccountEnabled is false and we are in a "not configured" state,
+ // then fxAccounts is probably fully disabled rather than just unconfigured,
+ // so handle this case. This block can be removed once we remove support
+ // for fxAccounts being disabled.
+ } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
+ Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+ this.page = PAGE_NO_ACCOUNT;
+ // else: sync was previously configured for the legacy provider, so we
+ // make the "old" panels available.
+ } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
+ Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ this.needsUpdate();
+ } else {
+ this.page = PAGE_HAS_ACCOUNT;
+ document.getElementById("accountName").textContent = Weave.Service.identity.account;
+ document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+ document.getElementById("tosPP-normal").hidden = this._usingCustomServer;
+ }
+ },
+
+ startOver: function (showDialog) {
+ if (showDialog) {
+ let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
+ Services.prompt.BUTTON_POS_1_DEFAULT;
+ let buttonChoice =
+ Services.prompt.confirmEx(window,
+ this._stringBundle.GetStringFromName("syncUnlink.title"),
+ this._stringBundle.GetStringFromName("syncUnlink.label"),
+ flags,
+ this._stringBundle.GetStringFromName("syncUnlinkConfirm.label"),
+ null, null, null, {});
+
+ // If the user selects cancel, just bail
+ if (buttonChoice == 1)
+ return;
+ }
+
+ Weave.Service.startOver();
+ this.updateWeavePrefs();
+ },
+
+ updatePass: function () {
+ if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
+ gSyncUtils.changePassword();
+ else
+ gSyncUtils.updatePassphrase();
+ },
+
+ resetPass: function () {
+ if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
+ gSyncUtils.resetPassword();
+ else
+ gSyncUtils.resetPassphrase();
+ },
+
+ _getEntryPoint: function () {
+ let params = new URLSearchParams(document.URL.split("#")[0].split("?")[1] || "");
+ return params.get("entrypoint") || "preferences";
+ },
+
+ _openAboutAccounts: function(action) {
+ let entryPoint = this._getEntryPoint();
+ let params = new URLSearchParams();
+ if (action) {
+ params.set("action", action);
+ }
+ params.set("entrypoint", entryPoint);
+
+ this.replaceTabWithUrl("about:accounts?" + params);
+ },
+
+ /**
+ * Invoke the Sync setup wizard.
+ *
+ * @param wizardType
+ * Indicates type of wizard to launch:
+ * null -- regular set up wizard
+ * "pair" -- pair a device first
+ * "reset" -- reset sync
+ */
+ openSetup: function (wizardType) {
+ let service = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+
+ if (service.fxAccountsEnabled) {
+ this._openAboutAccounts();
+ } else {
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://browser/content/sync/setup.xul",
+ "weaveSetup", "centerscreen,chrome,resizable=no",
+ wizardType);
+ }
+ }
+ },
+
+ openContentInBrowser: function(url, options) {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!win) {
+ // no window to use, so use _openLink to create a new one. We don't
+ // always use that as it prefers to open a new window rather than use
+ // an existing one.
+ gSyncUtils._openLink(url);
+ return;
+ }
+ win.switchToTabHavingURI(url, true, options);
+ },
+
+ // Replace the current tab with the specified URL.
+ replaceTabWithUrl(url) {
+ // Get the <browser> element hosting us.
+ let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ // And tell it to load our URL.
+ browser.loadURI(url);
+ },
+
+ signUp: function() {
+ this._openAboutAccounts("signup");
+ },
+
+ signIn: function() {
+ this._openAboutAccounts("signin");
+ },
+
+ reSignIn: function() {
+ this._openAboutAccounts("reauth");
+ },
+
+
+ clickOrSpaceOrEnterPressed: function(event) {
+ // Note: charCode is deprecated, but 'char' not yet implemented.
+ // Replace charCode with char when implemented, see Bug 680830
+ return ((event.type == "click" && event.button == 0) ||
+ (event.type == "keypress" &&
+ (event.charCode == KeyEvent.DOM_VK_SPACE || event.keyCode == KeyEvent.DOM_VK_RETURN)));
+ },
+
+ openChangeProfileImage: function(event) {
+ if (this.clickOrSpaceOrEnterPressed(event)) {
+ fxAccounts.promiseAccountsChangeProfileURI(this._getEntryPoint(), "avatar")
+ .then(url => {
+ this.openContentInBrowser(url, {
+ replaceQueryString: true
+ });
+ });
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ },
+
+ openManageFirefoxAccount: function(event) {
+ if (this.clickOrSpaceOrEnterPressed(event)) {
+ this.manageFirefoxAccount();
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ },
+
+ manageFirefoxAccount: function() {
+ fxAccounts.promiseAccountsManageURI(this._getEntryPoint())
+ .then(url => {
+ this.openContentInBrowser(url, {
+ replaceQueryString: true
+ });
+ });
+ },
+
+ verifyFirefoxAccount: function() {
+ let showVerifyNotification = (data) => {
+ let isError = !data;
+ let maybeNot = isError ? "Not" : "";
+ let sb = this._accountsStringBundle;
+ let title = sb.GetStringFromName("verification" + maybeNot + "SentTitle");
+ let email = !isError && data ? data.email : "";
+ let body = sb.formatStringFromName("verification" + maybeNot + "SentBody", [email], 1);
+ new Notification(title, { body })
+ }
+
+ let onError = () => {
+ showVerifyNotification();
+ };
+
+ let onSuccess = data => {
+ if (data) {
+ showVerifyNotification(data);
+ } else {
+ onError();
+ }
+ };
+
+ fxAccounts.resendVerificationEmail()
+ .then(fxAccounts.getSignedInUser, onError)
+ .then(onSuccess, onError);
+ },
+
+ openOldSyncSupportPage: function() {
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
+ this.openContentInBrowser(url);
+ },
+
+ unlinkFirefoxAccount: function(confirm) {
+ if (confirm) {
+ // We use a string bundle shared with aboutAccounts.
+ let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ let disconnectLabel = sb.GetStringFromName("disconnect.label");
+ let title = sb.GetStringFromName("disconnect.verify.title");
+ let body = sb.GetStringFromName("disconnect.verify.bodyHeading") +
+ "\n\n" +
+ sb.GetStringFromName("disconnect.verify.bodyText");
+ let ps = Services.prompt;
+ let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
+ (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
+ ps.BUTTON_POS_1_DEFAULT;
+
+ let factory = Cc["@mozilla.org/prompter;1"]
+ .getService(Ci.nsIPromptFactory);
+ let prompt = factory.getPrompt(window, Ci.nsIPrompt);
+ let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
+ bag.setPropertyAsBool("allowTabModal", true);
+
+ let pressed = prompt.confirmEx(title, body, buttonFlags,
+ disconnectLabel, null, null, null, {});
+
+ if (pressed != 0) { // 0 is the "continue" button
+ return;
+ }
+ }
+ fxAccounts.signOut().then(() => {
+ this.updateWeavePrefs();
+ });
+ },
+
+ openAddDevice: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return;
+
+ let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+ if (win)
+ win.focus();
+ else
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
+ "syncAddDevice", "centerscreen,chrome,resizable=no");
+ },
+
+ resetSync: function () {
+ this.openSetup("reset");
+ },
+
+ _populateComputerName(value) {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ if (!textbox.hasAttribute("placeholder")) {
+ textbox.setAttribute("placeholder",
+ Weave.Utils.getDefaultDeviceName());
+ }
+ textbox.value = value;
+ },
+};
diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul
new file mode 100644
index 000000000..f1aebf2aa
--- /dev/null
+++ b/browser/components/preferences/in-content/sync.xul
@@ -0,0 +1,359 @@
+# 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/.
+
+<!-- Sync panel -->
+
+<preferences id="syncEnginePrefs" hidden="true" data-category="paneSync">
+ <preference id="engine.addons"
+ name="services.sync.engine.addons"
+ type="bool"/>
+ <preference id="engine.bookmarks"
+ name="services.sync.engine.bookmarks"
+ type="bool"/>
+ <preference id="engine.history"
+ name="services.sync.engine.history"
+ type="bool"/>
+ <preference id="engine.tabs"
+ name="services.sync.engine.tabs"
+ type="bool"/>
+ <preference id="engine.prefs"
+ name="services.sync.engine.prefs"
+ type="bool"/>
+ <preference id="engine.passwords"
+ name="services.sync.engine.passwords"
+ type="bool"/>
+</preferences>
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/sync.js"/>
+<script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+
+<hbox id="header-sync"
+ class="header"
+ hidden="true"
+ data-category="paneSync">
+ <label class="header-name" flex="1">&paneSync.title;</label>
+ <html:a class="help-button text-link" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
+ <!-- These panels are for the "legacy" sync provider -->
+ <vbox id="noAccount" align="center">
+ <spacer flex="1"/>
+ <description id="syncDesc">
+ &weaveDesc.label;
+ </description>
+ <separator/>
+ <label id="noAccountSetup" class="text-link">
+ &setupButton.label;
+ </label>
+ <vbox id="pairDevice">
+ <separator/>
+ <label id="noAccountPair" class="text-link">
+ &pairDevice.label;
+ </label>
+ </vbox>
+ <spacer flex="3"/>
+ </vbox>
+
+ <vbox id="hasAccount">
+ <groupbox class="syncGroupBox">
+ <!-- label is set to account name -->
+ <caption id="accountCaption" align="center">
+ <image id="accountCaptionImage"/>
+ <label id="accountName"/>
+ </caption>
+
+ <hbox>
+ <button type="menu"
+ label="&manageAccount.label;"
+ accesskey="&manageAccount.accesskey;">
+ <menupopup>
+ <menuitem id="syncChangePassword" label="&changePassword2.label;"/>
+ <menuitem id="syncResetPassphrase" label="&myRecoveryKey.label;"/>
+ <menuseparator/>
+ <menuitem id="syncReset" label="&resetSync2.label;"/>
+ </menupopup>
+ </button>
+ </hbox>
+
+ <hbox>
+ <label id="syncAddDeviceLabel"
+ class="text-link">
+ &pairDevice.label;
+ </label>
+ </hbox>
+
+ <vbox>
+ <label>&syncMy.label;</label>
+ <richlistbox id="syncEnginesList"
+ orient="vertical">
+ <richlistitem>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ preference="engine.addons"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ preference="engine.bookmarks"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ preference="engine.passwords"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ preference="engine.prefs"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ preference="engine.history"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ preference="engine.tabs"/>
+ </richlistitem>
+ </richlistbox>
+ </vbox>
+ </groupbox>
+
+ <groupbox class="syncGroupBox">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label control="syncComputerName">
+ &syncDeviceName.label;
+ </label>
+ <textbox id="syncComputerName"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <label id="unlinkDevice" class="text-link">
+ &unlinkDevice.label;
+ </label>
+ </hbox>
+ </groupbox>
+ <vbox id="tosPP-normal">
+ <label id="tosPP-normal-ToS" class="text-link">
+ &prefs.tosLink.label;
+ </label>
+ <label id="tosPP-normal-PP" class="text-link">
+ &prefs.ppLink.label;
+ </label>
+ </vbox>
+ </vbox>
+
+ <vbox id="needsUpdate" align="center" pack="center">
+ <hbox>
+ <label id="loginError"/>
+ <label id="loginErrorUpdatePass" class="text-link">
+ &updatePass.label;
+ </label>
+ <label id="loginErrorResetPass" class="text-link">
+ &resetPass.label;
+ </label>
+ </hbox>
+ <label id="loginErrorStartOver" class="text-link">
+ &unlinkDevice.label;
+ </label>
+ </vbox>
+
+ <!-- These panels are for the Firefox Accounts identity provider -->
+ <vbox id="noFxaAccount">
+ <hbox>
+ <vbox id="fxaContentWrapper">
+ <groupbox id="noFxaGroup">
+ <vbox>
+ <label id="noFxaCaption">&signedOut.caption;</label>
+ <description id="noFxaDescription" flex="1">&signedOut.description;</description>
+ <hbox class="fxaAccountBox">
+ <vbox>
+ <image class="fxaFirefoxLogo"/>
+ </vbox>
+ <vbox flex="1">
+ <label id="signedOutAccountBoxTitle">&signedOut.accountBox.title;</label>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="noFxaSignUp" label="&signedOut.accountBox.create;" accesskey="&signedOut.accountBox.create.accesskey;"></button>
+ <button id="noFxaSignIn" label="&signedOut.accountBox.signin;" accesskey="&signedOut.accountBox.signin.accesskey;"></button>
+ </hbox>
+ </vbox>
+ </hbox>
+ </vbox>
+ </groupbox>
+ </vbox>
+ <vbox>
+ <image class="fxaSyncIllustration"/>
+ </vbox>
+ </hbox>
+ <label class="fxaMobilePromo">
+ &mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
+ --><label id="fxaMobilePromo-android"
+ class="androidLink text-link"><!--
+ -->&mobilePromo3.androidLink;</label><!--
+ -->&mobilePromo3.iOSBefore;<!--
+ --><label id="fxaMobilePromo-ios"
+ class="iOSLink text-link"><!--
+ -->&mobilePromo3.iOSLink;</label><!--
+ -->&mobilePromo3.end;
+ </label>
+ </vbox>
+
+ <vbox id="hasFxaAccount">
+ <hbox>
+ <vbox id="fxaContentWrapper">
+ <groupbox id="fxaGroup">
+ <caption><label>&syncBrand.fxAccount.label;</label></caption>
+ <deck id="fxaLoginStatus">
+
+ <!-- logged in and verified and all is good -->
+ <hbox id="fxaLoginVerified" class="fxaAccountBox">
+ <vbox align="center" pack="center">
+ <image id="fxaProfileImage" class="actionable"
+ role="button"
+ onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
+ onkeypress="gSyncPane.openChangeProfileImage(event);"
+ tooltiptext="&profilePicture.tooltip;"/>
+ </vbox>
+ <vbox flex="1" pack="center">
+ <label id="fxaDisplayName" hidden="true"/>
+ <label id="fxaEmailAddress1"/>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="fxaUnlinkButton" label="&disconnect.label;" accesskey="&disconnect.accesskey;"/>
+ <html:a id="verifiedManage" target="_blank"
+ accesskey="&verifiedManage.accesskey;"
+ onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
+ -->&verifiedManage.label;</html:a>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- logged in to an unverified account -->
+ <hbox id="fxaLoginUnverified" class="fxaAccountBox">
+ <vbox>
+ <image id="fxaProfileImage"/>
+ </vbox>
+ <vbox flex="1">
+ <hbox>
+ <vbox><image id="fxaLoginRejectedWarning"/></vbox>
+ <description flex="1">
+ &signedInUnverified.beforename.label;
+ <label id="fxaEmailAddress2"/>
+ &signedInUnverified.aftername.label;
+ </description>
+ </hbox>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="verifyFxaAccount" accesskey="&verify.accesskey;">&verify.label;</button>
+ <button id="unverifiedUnlinkFxaAccount" accesskey="&forget.accesskey;">&forget.label;</button>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- logged in locally but server rejected credentials -->
+ <hbox id="fxaLoginRejected" class="fxaAccountBox">
+ <vbox>
+ <image id="fxaProfileImage"/>
+ </vbox>
+ <vbox flex="1">
+ <hbox>
+ <vbox><image id="fxaLoginRejectedWarning"/></vbox>
+ <description flex="1">
+ &signedInLoginFailure.beforename.label;
+ <label id="fxaEmailAddress3"/>
+ &signedInLoginFailure.aftername.label;
+ </description>
+ </hbox>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="rejectReSignIn" accessky="&signIn.accesskey;">&signIn.label;</button>
+ <button id="rejectUnlinkFxaAccount" accesskey="&forget.accesskey;">&forget.label;</button>
+ </hbox>
+ </vbox>
+ </hbox>
+ </deck>
+ </groupbox>
+ <groupbox id="syncOptions">
+ <caption><label>&signedIn.engines.label;</label></caption>
+ <hbox id="fxaSyncEngines">
+ <vbox align="start" flex="1">
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ preference="engine.tabs"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ preference="engine.bookmarks"/>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ preference="engine.passwords"/>
+ </vbox>
+ <vbox align="start" flex="1">
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ preference="engine.history"/>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ preference="engine.addons"/>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ preference="engine.prefs"/>
+ </vbox>
+ <spacer/>
+ </hbox>
+ </groupbox>
+ </vbox>
+ <vbox>
+ <image class="fxaSyncIllustration"/>
+ </vbox>
+ </hbox>
+ <groupbox>
+ <caption>
+ <label control="fxaSyncComputerName">
+ &fxaSyncDeviceName.label;
+ </label>
+ </caption>
+ <hbox id="fxaDeviceName">
+ <textbox id="fxaSyncComputerName" disabled="true"/>
+ <hbox>
+ <button id="fxaChangeDeviceName"
+ label="&changeSyncDeviceName.label;"
+ accesskey="&changeSyncDeviceName.accesskey;"/>
+ <button id="fxaCancelChangeDeviceName"
+ label="&cancelChangeSyncDeviceName.label;"
+ accesskey="&cancelChangeSyncDeviceName.accesskey;"
+ hidden="true"/>
+ <button id="fxaSaveChangeDeviceName"
+ label="&saveChangeSyncDeviceName.label;"
+ accesskey="&saveChangeSyncDeviceName.accesskey;"
+ hidden="true"/>
+ </hbox>
+ </hbox>
+ </groupbox>
+ <label class="fxaMobilePromo">
+ &mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
+ --><label class="androidLink text-link" id="fxaMobilePromo-android-hasFxaAccount"><!--
+ -->&mobilePromo3.androidLink;</label><!--
+ -->&mobilePromo3.iOSBefore;<!--
+ --><label class="iOSLink text-link" id="fxaMobilePromo-ios-hasFxaAccount"><!--
+ -->&mobilePromo3.iOSLink;</label><!--
+ -->&mobilePromo3.end;
+ </label>
+ <vbox id="tosPP-small" align="start">
+ <label id="tosPP-small-ToS" class="text-link">
+ &prefs.tosLink.label;
+ </label>
+ <label id="tosPP-small-PP" class="text-link">
+ &fxaPrivacyNotice.link.label;
+ </label>
+ </vbox>
+ </vbox>
+</deck>
diff --git a/browser/components/preferences/in-content/tests/.eslintrc.js b/browser/components/preferences/in-content/tests/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/preferences/in-content/tests/browser.ini b/browser/components/preferences/in-content/tests/browser.ini
new file mode 100644
index 000000000..6cba02599
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -0,0 +1,43 @@
+[DEFAULT]
+support-files =
+ head.js
+ privacypane_tests_perwindow.js
+
+[browser_advanced_update.js]
+[browser_basic_rebuild_fonts_test.js]
+[browser_bug410900.js]
+[browser_bug705422.js]
+[browser_bug731866.js]
+[browser_bug795764_cachedisabled.js]
+[browser_bug1018066_resetScrollPosition.js]
+[browser_bug1020245_openPreferences_to_paneContent.js]
+[browser_bug1184989_prevent_scrolling_when_preferences_flipped.js]
+support-files =
+ browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
+[browser_change_app_handler.js]
+skip-if = os != "win" # This test tests the windows-specific app selection dialog, so can't run on non-Windows
+[browser_connection.js]
+[browser_connection_bug388287.js]
+[browser_cookies_exceptions.js]
+[browser_defaultbrowser_alwayscheck.js]
+[browser_healthreport.js]
+skip-if = true || !healthreport # Bug 1185403 for the "true"
+[browser_homepages_filter_aboutpreferences.js]
+[browser_notifications_do_not_disturb.js]
+[browser_permissions_urlFieldHidden.js]
+[browser_proxy_backup.js]
+[browser_privacypane_1.js]
+[browser_privacypane_3.js]
+[browser_privacypane_4.js]
+[browser_privacypane_5.js]
+[browser_privacypane_8.js]
+[browser_sanitizeOnShutdown_prefLocked.js]
+[browser_searchsuggestions.js]
+[browser_security.js]
+[browser_subdialogs.js]
+support-files =
+ subdialog.xul
+ subdialog2.xul
+[browser_telemetry.js]
+# Skip this test on Android as FHR and Telemetry are separate systems there.
+skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
diff --git a/browser/components/preferences/in-content/tests/browser_advanced_update.js b/browser/components/preferences/in-content/tests/browser_advanced_update.js
new file mode 100644
index 000000000..e9d0e8652
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_advanced_update.js
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+const mockUpdateManager = {
+ contractId: "@mozilla.org/updates/update-manager;1",
+
+ _mockClassId: uuidGenerator.generateUUID(),
+
+ _originalClassId: "",
+
+ _originalFactory: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager]),
+
+ createInstance: function(outer, iiD) {
+ if (outer) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(iiD);
+ },
+
+ register: function () {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ if (!registrar.isCIDRegistered(this._mockClassId)) {
+ this._originalClassId = registrar.contractIDToCID(this.contractId);
+ this._originalFactory = Cm.getClassObject(Cc[this.contractId], Ci.nsIFactory);
+ registrar.unregisterFactory(this._originalClassId, this._originalFactory);
+ registrar.registerFactory(this._mockClassId, "Unregister after testing", this.contractId, this);
+ }
+ },
+
+ unregister: function () {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(this._mockClassId, this);
+ registrar.registerFactory(this._originalClassId, "", this.contractId, this._originalFactory);
+ },
+
+ get updateCount() {
+ return this._updates.length;
+ },
+
+ getUpdateAt: function (index) {
+ return this._updates[index];
+ },
+
+ _updates: [
+ {
+ name: "Firefox Developer Edition 49.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20160728004010",
+ type: "minor",
+ installDate: 1469763105156,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/"
+ },
+ {
+ name: "Firefox Developer Edition 43.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20150929004011",
+ type: "minor",
+ installDate: 1443585886224,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/"
+ },
+ {
+ name: "Firefox Developer Edition 42.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20150920004018",
+ type: "major",
+ installDate: 1442818147544,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/"
+ }
+ ]
+};
+
+function resetPreferences() {
+ Services.prefs.clearUserPref("browser.search.update");
+}
+
+function formatInstallDate(sec) {
+ var date = new Date(sec);
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric' };
+ return date.toLocaleString(locale, dtOptions);
+}
+
+registerCleanupFunction(resetPreferences);
+
+add_task(function*() {
+ yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab", { leaveOpen: true });
+ resetPreferences();
+ Services.prefs.setBoolPref("browser.search.update", false);
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let enableSearchUpdate = doc.getElementById("enableSearchUpdate");
+ is_element_visible(enableSearchUpdate, "Check search update preference is visible");
+
+ // Ensure that the update pref dialog reflects the actual pref value.
+ ok(!enableSearchUpdate.checked, "Ensure search updates are disabled");
+ Services.prefs.setBoolPref("browser.search.update", true);
+ ok(enableSearchUpdate.checked, "Ensure search updates are enabled");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ mockUpdateManager.register();
+
+ yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let showBtn = doc.getElementById("showUpdateHistory");
+ let dialogOverlay = doc.getElementById("dialogOverlay");
+
+ // Test the dialog window opens
+ is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
+ showBtn.doCommand();
+ yield promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
+ is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
+
+ let dialogFrame = doc.getElementById("dialogFrame");
+ let frameDoc = dialogFrame.contentDocument;
+ let updates = frameDoc.querySelectorAll("update");
+
+ // Test the update history numbers are correct
+ is(updates.length, mockUpdateManager.updateCount, "The update count is incorrect.");
+
+ // Test the updates are displayed correctly
+ let update = null;
+ let updateData = null;
+ for (let i = 0; i < updates.length; ++i) {
+ update = updates[i];
+ updateData = mockUpdateManager.getUpdateAt(i);
+
+ is(update.name, updateData.name + " (" + updateData.buildID + ")", "Wrong update name");
+ is(update.type, updateData.type == "major" ? "New Version" : "Security Update", "Wrong update type");
+ is(update.installDate, formatInstallDate(updateData.installDate), "Wrong update installDate");
+ is(update.detailsURL, updateData.detailsURL, "Wrong update detailsURL");
+ is(update.status, updateData.statusText, "Wrong update status");
+ }
+
+ // Test the dialog window closes
+ let closeBtn = doc.getElementById("dialogClose");
+ closeBtn.doCommand();
+ is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
+
+ mockUpdateManager.unregister();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
new file mode 100644
index 000000000..32c1bd726
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
@@ -0,0 +1,76 @@
+Services.prefs.setBoolPref("browser.preferences.instantApply", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+});
+
+add_task(function*() {
+ yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+ let doc = gBrowser.contentDocument;
+ var langGroup = Services.prefs.getComplexValue("font.language.group", Ci.nsIPrefLocalizedString).data
+ is(doc.getElementById("font.language.group").value, langGroup,
+ "Language group should be set correctly.");
+
+ let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
+ let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
+ let fontFamilyField = doc.getElementById("defaultFont");
+ is(fontFamilyField.value, fontFamily, "Font family should be set correctly.");
+
+ let defaultFontSize = Services.prefs.getIntPref("font.size.variable." + langGroup);
+ let fontSizeField = doc.getElementById("defaultFontSize");
+ is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
+
+ doc.getElementById("advancedFonts").click();
+ let win = yield promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
+ doc = win.document;
+
+ // Simulate a dumb font backend.
+ win.FontBuilder._enumerator = {
+ _list: ["MockedFont1", "MockedFont2", "MockedFont3"],
+ EnumerateFonts: function(lang, type, list) {
+ return this._list;
+ },
+ EnumerateAllFonts: function() {
+ return this._list;
+ },
+ getDefaultFont: function() { return null; },
+ getStandardFamilyName: function(name) { return name; },
+ };
+ win.FontBuilder._allFonts = null;
+ win.FontBuilder._langGroupSupported = false;
+
+ let langGroupElement = doc.getElementById("font.language.group");
+ let selectLangsField = doc.getElementById("selectLangs");
+ let serifField = doc.getElementById("serif");
+ let armenian = "x-armn";
+ let western = "x-western";
+
+ langGroupElement.value = armenian;
+ selectLangsField.value = armenian;
+ is(serifField.value, "", "Font family should not be set.");
+
+ langGroupElement.value = western;
+ selectLangsField.value = western;
+
+ // Simulate a font backend supporting language-specific enumeration.
+ // NB: FontBuilder has cached the return value from EnumerateAllFonts(),
+ // so _allFonts will always have 3 elements regardless of subsequent
+ // _list changes.
+ win.FontBuilder._enumerator._list = ["MockedFont2"];
+
+ langGroupElement.value = armenian;
+ selectLangsField.value = armenian;
+ is(serifField.value, "MockedFont2", "Font family should be set.");
+
+ langGroupElement.value = western;
+ selectLangsField.value = western;
+
+ // Simulate a system that has no fonts for the specified language.
+ win.FontBuilder._enumerator._list = [];
+
+ langGroupElement.value = armenian;
+ selectLangsField.value = armenian;
+ is(serifField.value, "", "Font family should not be set.");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
new file mode 100644
index 000000000..9d938fdd4
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var originalWindowHeight;
+registerCleanupFunction(function() {
+ window.resizeTo(window.outerWidth, originalWindowHeight);
+ while (gBrowser.tabs[1])
+ gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function*() {
+ originalWindowHeight = window.outerHeight;
+ window.resizeTo(window.outerWidth, 300);
+ let prefs = yield openPreferencesViaOpenPreferencesAPI("paneApplications", undefined, {leaveOpen: true});
+ is(prefs.selectedPane, "paneApplications", "Applications pane was selected");
+ let mainContent = gBrowser.contentDocument.querySelector(".main-content");
+ mainContent.scrollTop = 50;
+ is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels");
+
+ gBrowser.contentWindow.gotoPref("paneGeneral");
+ is(mainContent.scrollTop, 0,
+ "Switching to a different category should reset the scroll position");
+});
+
diff --git a/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js b/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
new file mode 100644
index 000000000..bc2c6d800
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Services.prefs.setBoolPref("browser.preferences.instantApply", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+});
+
+add_task(function*() {
+ let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent");
+ is(prefs.selectedPane, "paneContent", "Content pane was selected");
+ prefs = yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab");
+ is(prefs.selectedPane, "paneAdvanced", "Advanced pane was selected");
+ is(prefs.selectedAdvancedTab, "updateTab", "The update tab within the advanced prefs should be selected");
+ prefs = yield openPreferencesViaHash("privacy");
+ is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected when hash is 'privacy'");
+ prefs = yield openPreferencesViaOpenPreferencesAPI("nonexistant-category");
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected by default when a nonexistant-category is requested");
+ prefs = yield openPreferencesViaHash("nonexistant-category");
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected when hash is a nonexistant-category");
+ prefs = yield openPreferencesViaHash();
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
+});
+
+function openPreferencesViaHash(aPane) {
+ let deferred = Promise.defer();
+ gBrowser.selectedTab = gBrowser.addTab("about:preferences" + (aPane ? "#" + aPane : ""));
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ newTabBrowser.addEventListener("Initialized", function PrefInit() {
+ newTabBrowser.removeEventListener("Initialized", PrefInit, true);
+ newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
+ newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ gBrowser.removeCurrentTab();
+ deferred.resolve({selectedPane: selectedPane});
+ });
+ }, true);
+
+ return deferred.promise;
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js
new file mode 100644
index 000000000..0972b2de4
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js
@@ -0,0 +1,92 @@
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+add_task(function* () {
+ waitForExplicitFinish();
+
+ const tabURL = getRootDirectory(gTestPath) + "browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul";
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: tabURL }, function* (browser) {
+ let doc = browser.contentDocument;
+ let container = doc.getElementById("container");
+
+ // Test button
+ let button = doc.getElementById("button");
+ button.focus();
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(container, "button");
+
+ // Test checkbox
+ let checkbox = doc.getElementById("checkbox");
+ checkbox.focus();
+ EventUtils.synthesizeKey(" ", {});
+ ok(checkbox.checked, "Checkbox is checked");
+ yield checkPageScrolling(container, "checkbox");
+
+ // Test listbox
+ let listbox = doc.getElementById("listbox");
+ let listitem = doc.getElementById("listitem");
+ listbox.focus();
+ EventUtils.synthesizeKey(" ", {});
+ ok(listitem.selected, "Listitem is selected");
+ yield checkPageScrolling(container, "listbox");
+
+ // Test radio
+ let radiogroup = doc.getElementById("radiogroup");
+ radiogroup.focus();
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(container, "radio");
+ });
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:preferences#search" }, function* (browser) {
+ let doc = browser.contentDocument;
+ let container = doc.getElementsByClassName("main-content")[0];
+
+ // Test search
+ let engineList = doc.getElementById("engineList");
+ engineList.focus();
+ EventUtils.synthesizeKey(" ", {});
+ is(engineList.view.selection.currentIndex, 0, "Search engineList is selected");
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(container, "search engineList");
+ });
+
+ // Test session restore
+ const CRASH_URL = "about:mozilla";
+ const CRASH_FAVICON = "chrome://branding/content/icon32.png";
+ const CRASH_SHENTRY = {url: CRASH_URL};
+ const CRASH_TAB = {entries: [CRASH_SHENTRY], image: CRASH_FAVICON};
+ const CRASH_STATE = {windows: [{tabs: [CRASH_TAB]}]};
+
+ const TAB_URL = "about:sessionrestore";
+ const TAB_FORMDATA = {url: TAB_URL, id: {sessionData: CRASH_STATE}};
+ const TAB_SHENTRY = {url: TAB_URL};
+ const TAB_STATE = {entries: [TAB_SHENTRY], formdata: TAB_FORMDATA};
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // Fake a post-crash tab
+ ss.setTabState(tab, JSON.stringify(TAB_STATE));
+
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let doc = tab.linkedBrowser.contentDocument;
+
+ // Make body scrollable
+ doc.body.style.height = (doc.body.clientHeight + 100) + "px";
+
+ let tabList = doc.getElementById("tabList");
+ tabList.focus();
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(doc.documentElement, "session restore");
+
+ gBrowser.removeCurrentTab();
+ finish();
+});
+
+function checkPageScrolling(container, type) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ is(container.scrollTop, 0, "Page should not scroll when " + type + " flipped");
+ resolve();
+ }, 0);
+ });
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
new file mode 100644
index 000000000..59b644c8f
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+ XUL Widget Test for Bug 1184989
+ -->
+<page title="Bug 1184989 Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox id="container" style="height: 200px; overflow: auto;">
+ <vbox style="height: 500px;">
+ <hbox>
+ <button id="button" label="button" />
+ </hbox>
+
+ <hbox>
+ <checkbox id="checkbox" label="checkbox" />
+ </hbox>
+
+ <hbox style="height: 50px;">
+ <listbox id="listbox">
+ <listitem id="listitem" label="listitem" />
+ <listitem label="listitem" />
+ </listbox>
+ </hbox>
+
+ <hbox>
+ <radiogroup id="radiogroup">
+ <radio id="radio" label="radio" />
+ </radiogroup>
+ </hbox>
+ </vbox>
+</vbox>
+
+</page>
diff --git a/browser/components/preferences/in-content/tests/browser_bug410900.js b/browser/components/preferences/in-content/tests/browser_bug410900.js
new file mode 100644
index 000000000..5b100966d
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug410900.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // Setup a phony handler to ensure the app pane will be populated.
+ var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handler.name = "App pane alive test";
+ handler.uriTemplate = "http://test.mozilla.org/%s";
+
+ var extps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ var info = extps.getProtocolHandlerInfo("apppanetest");
+ info.possibleApplicationHandlers.appendElement(handler, false);
+
+ var hserv = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hserv.store(info);
+
+ openPreferencesViaOpenPreferencesAPI("applications", null, {leaveOpen: true}).then(
+ () => runTest(gBrowser.selectedBrowser.contentWindow)
+ );
+}
+
+function runTest(win) {
+ var rbox = win.document.getElementById("handlersView");
+ ok(rbox, "handlersView is present");
+
+ var items = rbox && rbox.getElementsByTagName("richlistitem");
+ ok(items && items.length > 0, "App handler list populated");
+
+ var handlerAdded = false;
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].getAttribute('type') == "apppanetest")
+ handlerAdded = true;
+ }
+ ok(handlerAdded, "apppanetest protocol handler was successfully added");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug705422.js b/browser/components/preferences/in-content/tests/browser_bug705422.js
new file mode 100644
index 000000000..24732083b
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug705422.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+ // Allow all cookies, then actually set up the test
+ SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, initTest);
+}
+
+function initTest() {
+ const searchTerm = "example";
+ const dummyTerm = "elpmaxe";
+
+ var cm = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager);
+
+ // delete all cookies (might be left over from other tests)
+ cm.removeAll();
+
+ // data for cookies
+ var vals = [[searchTerm+".com", dummyTerm, dummyTerm], // match
+ [searchTerm+".org", dummyTerm, dummyTerm], // match
+ [dummyTerm+".com", searchTerm, dummyTerm], // match
+ [dummyTerm+".edu", searchTerm+dummyTerm, dummyTerm], // match
+ [dummyTerm+".net", dummyTerm, searchTerm], // match
+ [dummyTerm+".org", dummyTerm, searchTerm+dummyTerm], // match
+ [dummyTerm+".int", dummyTerm, dummyTerm]]; // no match
+
+ // matches must correspond to above data
+ const matches = 6;
+
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
+ .getService(Components.interfaces.nsICookieService);
+ var v;
+ // inject cookies
+ for (v in vals) {
+ let [host, name, value] = vals[v];
+ var cookieUri = ios.newURI("http://"+host, null, null);
+ cookieSvc.setCookieString(cookieUri, null, name+"="+value+";", null);
+ }
+
+ // open cookie manager
+ var cmd = window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {});
+
+ // when it has loaded, run actual tests
+ cmd.addEventListener("load", function() { executeSoon(function() { runTest(cmd, searchTerm, vals.length, matches); }); }, false);
+}
+
+function isDisabled(win, expectation) {
+ var disabled = win.document.getElementById("removeAllCookies").disabled;
+ is(disabled, expectation, "Remove all cookies button has correct state: "+(expectation?"disabled":"enabled"));
+}
+
+function runTest(win, searchTerm, cookies, matches) {
+ var cm = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager);
+
+
+ // number of cookies should match injected cookies
+ var injectedCookies = 0,
+ injectedEnumerator = cm.enumerator;
+ while (injectedEnumerator.hasMoreElements()) {
+ injectedCookies++;
+ injectedEnumerator.getNext();
+ }
+ is(injectedCookies, cookies, "Number of cookies match injected cookies");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+ // filter cookies and count matches
+ win.gCookiesWindow.setFilter(searchTerm);
+ is(win.gCookiesWindow._view.rowCount, matches, "Correct number of cookies shown after filter is applied");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+
+ // select first cookie and delete
+ var tree = win.document.getElementById("cookiesList");
+ var deleteButton = win.document.getElementById("removeSelectedCookies");
+ var rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "cell");
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {}, win);
+ EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win);
+
+ // count cookies should be matches-1
+ is(win.gCookiesWindow._view.rowCount, matches-1, "Deleted selected cookie");
+
+ // select two adjacent cells and delete
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {}, win);
+ var eventObj = {};
+ if (navigator.platform.indexOf("Mac") >= 0)
+ eventObj.metaKey = true;
+ else
+ eventObj.ctrlKey = true;
+ rect = tree.treeBoxObject.getCoordsForCellItem(1, tree.columns[0], "cell");
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, eventObj, win);
+ EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win);
+
+ // count cookies should be matches-3
+ is(win.gCookiesWindow._view.rowCount, matches-3, "Deleted selected two adjacent cookies");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+ // delete all cookies and count
+ var deleteAllButton = win.document.getElementById("removeAllCookies");
+ EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
+ is(win.gCookiesWindow._view.rowCount, 0, "Deleted all matching cookies");
+
+ // "delete all cookies" should be disabled
+ isDisabled(win, true);
+
+ // clear filter and count should be cookies-matches
+ win.gCookiesWindow.setFilter("");
+ is(win.gCookiesWindow._view.rowCount, cookies-matches, "Unmatched cookies remain");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+ // delete all cookies and count should be 0
+ EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
+ is(win.gCookiesWindow._view.rowCount, 0, "Deleted all cookies");
+
+ // check that datastore is also at 0
+ var remainingCookies = 0,
+ remainingEnumerator = cm.enumerator;
+ while (remainingEnumerator.hasMoreElements()) {
+ remainingCookies++;
+ remainingEnumerator.getNext();
+ }
+ is(remainingCookies, 0, "Zero cookies remain");
+
+ // "delete all cookies" should be disabled
+ isDisabled(win, true);
+
+ // clean up
+ win.close();
+ finish();
+}
+
diff --git a/browser/components/preferences/in-content/tests/browser_bug731866.js b/browser/components/preferences/in-content/tests/browser_bug731866.js
new file mode 100644
index 000000000..c1031d412
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug731866.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ open_preferences(runTest);
+}
+
+var gElements;
+
+function checkElements(expectedPane) {
+ for (let element of gElements) {
+ // keyset and preferences elements fail is_element_visible checks because they are never visible.
+ // special-case the drmGroup item because its visibility depends on pref + OS version
+ if (element.nodeName == "keyset" ||
+ element.nodeName == "preferences" ||
+ element.id === "drmGroup") {
+ continue;
+ }
+ let attributeValue = element.getAttribute("data-category");
+ let suffix = " (id=" + element.id + ")";
+ if (attributeValue == "pane" + expectedPane) {
+ is_element_visible(element, expectedPane + " elements should be visible" + suffix);
+ } else {
+ is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
+ }
+ }
+}
+
+function runTest(win) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+
+ let tab = win.document;
+ gElements = tab.getElementById("mainPrefPane").children;
+
+ let panes = [
+ "General", "Search", "Content", "Applications",
+ "Privacy", "Security", "Sync", "Advanced",
+ ];
+
+ for (let pane of panes) {
+ win.gotoPref("pane" + pane);
+ checkElements(pane);
+ }
+
+ gBrowser.removeCurrentTab();
+ win.close();
+ finish();
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
new file mode 100644
index 000000000..21f92db8d
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ let prefs = [
+ "browser.cache.offline.enable",
+ "browser.cache.disk.enable",
+ "browser.cache.memory.enable",
+ ];
+
+ registerCleanupFunction(function() {
+ for (let pref of prefs) {
+ Services.prefs.clearUserPref(pref);
+ }
+ });
+
+ for (let pref of prefs) {
+ Services.prefs.setBoolPref(pref, false);
+ }
+
+ open_preferences(runTest);
+}
+
+function runTest(win) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+
+ let tab = win.document;
+ let elements = tab.getElementById("mainPrefPane").children;
+
+ // Test if advanced pane is opened correctly
+ win.gotoPref("paneAdvanced");
+ for (let element of elements) {
+ if (element.nodeName == "preferences") {
+ continue;
+ }
+ let attributeValue = element.getAttribute("data-category");
+ if (attributeValue == "paneAdvanced") {
+ is_element_visible(element, "Advanced elements should be visible");
+ } else {
+ is_element_hidden(element, "Non-Advanced elements should be hidden");
+ }
+ }
+
+ gBrowser.removeCurrentTab();
+ win.close();
+ finish();
+}
diff --git a/browser/components/preferences/in-content/tests/browser_change_app_handler.js b/browser/components/preferences/in-content/tests/browser_change_app_handler.js
new file mode 100644
index 000000000..f66cdfd37
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_change_app_handler.js
@@ -0,0 +1,98 @@
+var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+
+SimpleTest.requestCompleteLog();
+
+function setupFakeHandler() {
+ let info = gMimeSvc.getFromTypeAndExtension("text/plain", "foo.txt");
+ ok(info.possibleLocalHandlers.length, "Should have at least one known handler");
+ let handler = info.possibleLocalHandlers.queryElementAt(0, Ci.nsILocalHandlerApp);
+
+ let infoToModify = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ infoToModify.possibleApplicationHandlers.appendElement(handler, false);
+
+ gHandlerSvc.store(infoToModify);
+}
+
+add_task(function*() {
+ setupFakeHandler();
+ yield openPreferencesViaOpenPreferencesAPI("applications", null, {leaveOpen: true});
+ info("Preferences page opened on the applications pane.");
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+ let ourItem = container.querySelector("richlistitem[type='text/x-test-handler']");
+ ok(ourItem, "handlersView is present");
+ ourItem.scrollIntoView();
+ container.selectItem(ourItem);
+ ok(ourItem.selected, "Should be able to select our item.");
+
+ let list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu"));
+ info("Got list after item was selected");
+
+ let chooseItem = list.firstChild.querySelector(".choose-app-item");
+ let dialogLoadedPromise = promiseLoadSubDialog("chrome://global/content/appPicker.xul");
+ let cmdEvent = win.document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
+ chooseItem.dispatchEvent(cmdEvent);
+
+ let dialog = yield dialogLoadedPromise;
+ info("Dialog loaded");
+
+ let dialogDoc = dialog.document;
+ let dialogList = dialogDoc.getElementById("app-picker-listbox");
+ dialogList.selectItem(dialogList.firstChild);
+ let selectedApp = dialogList.firstChild.handlerApp;
+ dialogDoc.documentElement.acceptDialog();
+
+ // Verify results are correct in mime service:
+ let mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ ok(mimeInfo.preferredApplicationHandler.equals(selectedApp), "App should be set as preferred.");
+
+ // Check that we display this result:
+ list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu"));
+ info("Got list after item was selected");
+ ok(list.selectedItem, "Should have a selected item");
+ ok(mimeInfo.preferredApplicationHandler.equals(list.selectedItem.handlerApp),
+ "App should be visible as preferred item.");
+
+
+ // Now try to 'manage' this list:
+ dialogLoadedPromise = promiseLoadSubDialog("chrome://browser/content/preferences/applicationManager.xul");
+
+ let manageItem = list.firstChild.querySelector(".manage-app-item");
+ cmdEvent = win.document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
+ manageItem.dispatchEvent(cmdEvent);
+
+ dialog = yield dialogLoadedPromise;
+ info("Dialog loaded the second time");
+
+ dialogDoc = dialog.document;
+ dialogList = dialogDoc.getElementById("appList");
+ let itemToRemove = dialogList.querySelector('listitem[label="' + selectedApp.name + '"]');
+ dialogList.selectItem(itemToRemove);
+ let itemsBefore = dialogList.children.length;
+ dialogDoc.getElementById("remove").click();
+ ok(!itemToRemove.parentNode, "Item got removed from DOM");
+ is(dialogList.children.length, itemsBefore - 1, "Item got removed");
+ dialogDoc.documentElement.acceptDialog();
+
+ // Verify results are correct in mime service:
+ mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ ok(!mimeInfo.preferredApplicationHandler, "App should no longer be set as preferred.");
+
+ // Check that we display this result:
+ list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu"));
+ ok(list.selectedItem, "Should have a selected item");
+ ok(!list.selectedItem.handlerApp,
+ "No app should be visible as preferred item.");
+
+ gBrowser.removeCurrentTab();
+});
+
+registerCleanupFunction(function() {
+ let infoToModify = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ gHandlerSvc.remove(infoToModify);
+});
+
diff --git a/browser/components/preferences/in-content/tests/browser_connection.js b/browser/components/preferences/in-content/tests/browser_connection.js
new file mode 100644
index 000000000..50438aed1
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_connection.js
@@ -0,0 +1,99 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // network.proxy.type needs to be backed up and restored because mochitest
+ // changes this setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function() {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.no_proxies_on");
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+ });
+
+ let connectionURL = "chrome://browser/content/preferences/connection.xul";
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ so it has to be opened as a sub dialog of the main pref tab.
+ Open the main tab here.
+ */
+ open_preferences(Task.async(function* tabOpened(aContentWindow) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+ let dialog = yield openAndLoadSubDialog(connectionURL);
+ let dialogClosingPromise = waitForEvent(dialog.document.documentElement, "dialogclosing");
+
+ ok(dialog, "connection window opened");
+ runConnectionTests(dialog);
+ dialog.document.documentElement.acceptDialog();
+
+ let dialogClosingEvent = yield dialogClosingPromise;
+ ok(dialogClosingEvent, "connection window closed");
+ // runConnectionTests will have changed this pref - make sure it was
+ // sanitized correctly when the dialog was accepted
+ is(Services.prefs.getCharPref("network.proxy.no_proxies_on"),
+ ".a.com,.b.com,.c.com", "no_proxies_on pref has correct value");
+ gBrowser.removeCurrentTab();
+ finish();
+ }));
+}
+
+// run a bunch of tests on the window containing connection.xul
+function runConnectionTests(win) {
+ let doc = win.document;
+ let networkProxyNone = doc.getElementById("networkProxyNone");
+ let networkProxyNonePref = doc.getElementById("network.proxy.no_proxies_on");
+ let networkProxyTypePref = doc.getElementById("network.proxy.type");
+
+ // make sure the networkProxyNone textbox is formatted properly
+ is(networkProxyNone.getAttribute("multiline"), "true",
+ "networkProxyNone textbox is multiline");
+ is(networkProxyNone.getAttribute("rows"), "2",
+ "networkProxyNone textbox has two rows");
+
+ // check if sanitizing the given input for the no_proxies_on pref results in
+ // expected string
+ function testSanitize(input, expected, errorMessage) {
+ networkProxyNonePref.value = input;
+ win.gConnectionsDialog.sanitizeNoProxiesPref();
+ is(networkProxyNonePref.value, expected, errorMessage);
+ }
+
+ // change this pref so proxy exceptions are actually configurable
+ networkProxyTypePref.value = 1;
+ is(networkProxyNone.disabled, false, "networkProxyNone textbox is enabled");
+
+ testSanitize(".a.com", ".a.com",
+ "sanitize doesn't mess up single filter");
+ testSanitize(".a.com, .b.com, .c.com", ".a.com, .b.com, .c.com",
+ "sanitize doesn't mess up multiple comma/space sep filters");
+ testSanitize(".a.com\n.b.com", ".a.com,.b.com",
+ "sanitize turns line break into comma");
+ testSanitize(".a.com,\n.b.com", ".a.com,.b.com",
+ "sanitize doesn't add duplicate comma after comma");
+ testSanitize(".a.com\n,.b.com", ".a.com,.b.com",
+ "sanitize doesn't add duplicate comma before comma");
+ testSanitize(".a.com,\n,.b.com", ".a.com,,.b.com",
+ "sanitize doesn't add duplicate comma surrounded by commas");
+ testSanitize(".a.com, \n.b.com", ".a.com, .b.com",
+ "sanitize doesn't add comma after comma/space");
+ testSanitize(".a.com\n .b.com", ".a.com, .b.com",
+ "sanitize adds comma before space");
+ testSanitize(".a.com\n\n\n;;\n;\n.b.com", ".a.com,.b.com",
+ "sanitize only adds one comma per substring of bad chars");
+ testSanitize(".a.com,,.b.com", ".a.com,,.b.com",
+ "duplicate commas from user are untouched");
+ testSanitize(".a.com\n.b.com\n.c.com,\n.d.com,\n.e.com",
+ ".a.com,.b.com,.c.com,.d.com,.e.com",
+ "sanitize replaces things globally");
+
+ // will check that this was sanitized properly after window closes
+ networkProxyNonePref.value = ".a.com;.b.com\n.c.com";
+}
diff --git a/browser/components/preferences/in-content/tests/browser_connection_bug388287.js b/browser/components/preferences/in-content/tests/browser_connection_bug388287.js
new file mode 100644
index 000000000..5a348876e
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_connection_bug388287.js
@@ -0,0 +1,125 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ const connectionURL = "chrome://browser/content/preferences/connection.xul";
+ let closeable = false;
+ let finalTest = false;
+
+ // The changed preferences need to be backed up and restored because this mochitest
+ // changes them setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function() {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ for (let proxyType of ["http", "ssl", "ftp", "socks"]) {
+ Services.prefs.clearUserPref("network.proxy." + proxyType);
+ Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
+ if (proxyType == "http") {
+ continue;
+ }
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType + "_port");
+ }
+ });
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ so it has to be opened as a sub dialog of the main pref tab.
+ Open the main tab here.
+ */
+ open_preferences(Task.async(function* tabOpened(aContentWindow) {
+ let dialog, dialogClosingPromise;
+ let doc, proxyTypePref, sharePref, httpPref, httpPortPref, ftpPref, ftpPortPref;
+
+ // Convenient function to reset the variables for the new window
+ function* setDoc() {
+ if (closeable) {
+ let dialogClosingEvent = yield dialogClosingPromise;
+ ok(dialogClosingEvent, "Connection dialog closed");
+ }
+
+ if (finalTest) {
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+
+ dialog = yield openAndLoadSubDialog(connectionURL);
+ dialogClosingPromise = waitForEvent(dialog.document.documentElement, "dialogclosing");
+
+ doc = dialog.document;
+ proxyTypePref = doc.getElementById("network.proxy.type");
+ sharePref = doc.getElementById("network.proxy.share_proxy_settings");
+ httpPref = doc.getElementById("network.proxy.http");
+ httpPortPref = doc.getElementById("network.proxy.http_port");
+ ftpPref = doc.getElementById("network.proxy.ftp");
+ ftpPortPref = doc.getElementById("network.proxy.ftp_port");
+ }
+
+ // This batch of tests should not close the dialog
+ yield setDoc();
+
+ // Testing HTTP port 0 with share on
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // Testing HTTP port 0 + FTP port 80 with share off
+ sharePref.value = false;
+ ftpPref.value = "localhost";
+ ftpPortPref.value = 80;
+ doc.documentElement.acceptDialog();
+
+ // Testing HTTP port 80 + FTP port 0 with share off
+ httpPortPref.value = 80;
+ ftpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // From now on, the dialog should close since we are giving it legitimate inputs.
+ // The test will timeout if the onbeforeaccept kicks in erroneously.
+ closeable = true;
+
+ // Both ports 80, share on
+ httpPortPref.value = 80;
+ ftpPortPref.value = 80;
+ doc.documentElement.acceptDialog();
+
+ // HTTP 80, FTP 0, with share on
+ yield setDoc();
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ ftpPref.value = "localhost";
+ httpPref.value = "localhost";
+ httpPortPref.value = 80;
+ ftpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // HTTP host empty, port 0 with share on
+ yield setDoc();
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "";
+ httpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // HTTP 0, but in no proxy mode
+ yield setDoc();
+ proxyTypePref.value = 0;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 0;
+
+ // This is the final test, don't spawn another connection window
+ finalTest = true;
+ doc.documentElement.acceptDialog();
+ yield setDoc();
+ }));
+}
diff --git a/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js b/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
new file mode 100644
index 000000000..89313d736
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
@@ -0,0 +1,348 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(3);
+ testRunner.runTests();
+}
+
+var testRunner = {
+
+ tests:
+ [
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnAllow.doCommand();
+ is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
+ is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission text should be set correctly");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnBlock.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
+ "permission should change to deny in UI");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnAllow.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission should revert back to allow");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnRemove.doCommand();
+ is(params.tree.view.rowCount, 0, "exception should be removed");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "deleted" }],
+ },
+ {
+ expectPermObservancesDuringTestFunction: true,
+ test: function(params) {
+ let uri = params.ioService.newURI("http://test.com", null, null);
+ params.pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
+ is(params.tree.view.rowCount, 0, "adding unrelated permission should not change display");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "popup", origin: "http://test.com", data: "added",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ cleanUp: function(params) {
+ let uri = params.ioService.newURI("http://test.com", null, null);
+ params.pm.remove(uri, "popup");
+ },
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
+ is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission text should be set correctly");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnBlock.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
+ "permission should change to deny in UI");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission should revert back to allow");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnRemove.doCommand();
+ is(params.tree.view.rowCount, 0, "exception should be removed");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "deleted" }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
+ is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission text should be set correctly");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnBlock.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
+ "permission should change to deny in UI");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission should revert back to allow");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnRemove.doCommand();
+ is(params.tree.view.rowCount, 0, "exception should be removed");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "deleted" }],
+ },
+ {
+ expectPermObservancesDuringTestFunction: true,
+ test(params) {
+ for (let URL of ["http://a", "http://z", "http://b"]) {
+ let URI = params.ioService.newURI(URL, null, null);
+ params.pm.add(URI, "cookie", Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+
+ is(params.tree.view.rowCount, 3, "Three permissions should be present");
+ is(params.tree.view.getCellText(0, params.nameCol), "http://a",
+ "site should be sorted. 'a' should be first");
+ is(params.tree.view.getCellText(1, params.nameCol), "http://b",
+ "site should be sorted. 'b' should be second");
+ is(params.tree.view.getCellText(2, params.nameCol), "http://z",
+ "site should be sorted. 'z' should be third");
+
+ // Sort descending then check results in cleanup since sorting isn't synchronous.
+ EventUtils.synthesizeMouseAtCenter(params.doc.getElementById("siteCol"), {},
+ params.doc.defaultView);
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://a", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION },
+ { type: "cookie", origin: "http://z", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION },
+ { type: "cookie", origin: "http://b", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ cleanUp(params) {
+ is(params.tree.view.getCellText(0, params.nameCol), "http://z",
+ "site should be sorted. 'z' should be first");
+ is(params.tree.view.getCellText(1, params.nameCol), "http://b",
+ "site should be sorted. 'b' should be second");
+ is(params.tree.view.getCellText(2, params.nameCol), "http://a",
+ "site should be sorted. 'a' should be third");
+
+ for (let URL of ["http://a", "http://z", "http://b"]) {
+ let uri = params.ioService.newURI(URL, null, null);
+ params.pm.remove(uri, "cookie");
+ }
+ },
+ },
+ ],
+
+ _currentTest: -1,
+
+ runTests: function() {
+ this._currentTest++;
+
+ info("Running test #" + (this._currentTest + 1) + "\n");
+ let that = this;
+ let p = this.runCurrentTest(this._currentTest + 1);
+ p.then(function() {
+ if (that._currentTest == that.tests.length - 1) {
+ finish();
+ }
+ else {
+ that.runTests();
+ }
+ });
+ },
+
+ runCurrentTest: function(testNumber) {
+ return new Promise(function(resolve, reject) {
+
+ let helperFunctions = {
+ windowLoad: function(win) {
+ let doc = win.document;
+ let params = {
+ doc,
+ tree: doc.getElementById("permissionsTree"),
+ nameCol: doc.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(0),
+ statusCol: doc.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(1),
+ url: doc.getElementById("url"),
+ btnAllow: doc.getElementById("btnAllow"),
+ btnBlock: doc.getElementById("btnBlock"),
+ btnApplyChanges: doc.getElementById("btnApplyChanges"),
+ btnRemove: doc.getElementById("removePermission"),
+ pm: Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager),
+ ioService: Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService),
+ allowText: win.gPermissionManager._getCapabilityString(
+ Ci.nsIPermissionManager.ALLOW_ACTION),
+ denyText: win.gPermissionManager._getCapabilityString(
+ Ci.nsIPermissionManager.DENY_ACTION),
+ allow: Ci.nsIPermissionManager.ALLOW_ACTION,
+ deny: Ci.nsIPermissionManager.DENY_ACTION,
+ };
+
+ let permObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "perm-changed")
+ return;
+
+ if (testRunner.tests[testRunner._currentTest].observances.length == 0) {
+ // Should fail here as we are not expecting a notification, but we don't.
+ // See bug 1063410.
+ return;
+ }
+
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ let expected = testRunner.tests[testRunner._currentTest].observances.shift();
+
+ is(aData, expected.data, "type of message should be the same");
+ for (let prop of ["type", "capability"]) {
+ if (expected[prop])
+ is(permission[prop], expected[prop],
+ "property: \"" + prop + "\" should be equal");
+ }
+
+ if (expected.origin) {
+ is(permission.principal.origin, expected.origin,
+ "property: \"origin\" should be equal");
+ }
+
+ os.removeObserver(permObserver, "perm-changed");
+
+ let test = testRunner.tests[testRunner._currentTest];
+ if (!test.expectPermObservancesDuringTestFunction) {
+ if (test.cleanUp) {
+ test.cleanUp(params);
+ }
+
+ gBrowser.removeCurrentTab();
+ resolve();
+ }
+ },
+ };
+
+ let os = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+ os.addObserver(permObserver, "perm-changed", false);
+
+ if (testRunner._currentTest == 0) {
+ is(params.tree.view.rowCount, 0, "no cookie exceptions");
+ }
+
+ try {
+ let test = testRunner.tests[testRunner._currentTest];
+ test.test(params);
+ if (test.expectPermObservancesDuringTestFunction) {
+ if (test.cleanUp) {
+ test.cleanUp(params);
+ }
+
+ gBrowser.removeCurrentTab();
+ resolve();
+ }
+ } catch (ex) {
+ ok(false, "exception while running test #" +
+ testNumber + ": " + ex);
+ }
+ },
+ };
+
+ openPreferencesViaOpenPreferencesAPI("panePrivacy", null, {leaveOpen: true}).then(function() {
+ let doc = gBrowser.contentDocument;
+ let historyMode = doc.getElementById("historyMode");
+ historyMode.value = "custom";
+ historyMode.doCommand();
+ doc.getElementById("cookieExceptions").doCommand();
+
+ let subDialogURL = "chrome://browser/content/preferences/permissions.xul";
+ promiseLoadSubDialog(subDialogURL).then(function(win) {
+ helperFunctions.windowLoad(win);
+ });
+ });
+ });
+ },
+};
diff --git a/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js b/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js
new file mode 100644
index 000000000..b30b6d9e2
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js
@@ -0,0 +1,103 @@
+"use strict";
+
+const CHECK_DEFAULT_INITIAL = Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser");
+
+add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+ yield test_with_mock_shellservice({isDefault: false}, function*() {
+ let setDefaultPane = content.document.getElementById("setDefaultPane");
+ Assert.equal(setDefaultPane.selectedIndex, "0",
+ "The 'make default' pane should be visible when not default");
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(!alwaysCheck.checked, "Always Check is unchecked by default");
+ Assert.ok(!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "alwaysCheck pref should be false by default in test runs");
+
+ let setDefaultButton = content.document.getElementById("setDefaultButton");
+ setDefaultButton.click();
+ content.window.gMainPane.updateSetDefaultBrowser();
+
+ yield ContentTaskUtils.waitForCondition(() => alwaysCheck.checked,
+ "'Always Check' checkbox should get checked after clicking the 'Set Default' button");
+
+ Assert.ok(alwaysCheck.checked,
+ "Clicking 'Make Default' checks the 'Always Check' checkbox");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "Checking the checkbox should set the pref to true");
+ Assert.ok(alwaysCheck.disabled,
+ "'Always Check' checkbox is locked with default browser and alwaysCheck=true");
+ Assert.equal(setDefaultPane.selectedIndex, "1",
+ "The 'make default' pane should not be visible when default");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "checkDefaultBrowser pref is now enabled");
+ });
+
+ gBrowser.removeCurrentTab();
+ Services.prefs.clearUserPref("browser.shell.checkDefaultBrowser");
+});
+
+add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
+ Services.prefs.lockPref("browser.shell.checkDefaultBrowser");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+ yield test_with_mock_shellservice({isDefault: false}, function*() {
+ let setDefaultPane = content.document.getElementById("setDefaultPane");
+ Assert.equal(setDefaultPane.selectedIndex, "0",
+ "The 'make default' pane should be visible when not default");
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(alwaysCheck.disabled, "Always Check is disabled when locked");
+ Assert.ok(alwaysCheck.checked,
+ "Always Check is checked because defaultPref is true and pref is locked");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "alwaysCheck pref should ship with 'true' by default");
+
+ let setDefaultButton = content.document.getElementById("setDefaultButton");
+ setDefaultButton.click();
+ content.window.gMainPane.updateSetDefaultBrowser();
+
+ yield ContentTaskUtils.waitForCondition(() => setDefaultPane.selectedIndex == "1",
+ "Browser is now default");
+
+ Assert.ok(alwaysCheck.checked,
+ "'Always Check' is still checked because it's locked");
+ Assert.ok(alwaysCheck.disabled,
+ "'Always Check is disabled because it's locked");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "The pref is locked and so doesn't get changed");
+ });
+
+ Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+ gBrowser.removeCurrentTab();
+});
+
+registerCleanupFunction(function() {
+ Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
+});
+
+function* test_with_mock_shellservice(options, testFn) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, options, function*(options) {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.oldShellService = win.getShellService();
+ let mockShellService = {
+ _isDefault: false,
+ isDefaultBrowser() {
+ return this._isDefault;
+ },
+ setDefaultBrowser() {
+ this._isDefault = true;
+ },
+ };
+ win.getShellService = function() {
+ return mockShellService;
+ }
+ mockShellService._isDefault = options.isDefault;
+ win.gMainPane.updateSetDefaultBrowser();
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, testFn);
+
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
+}
diff --git a/browser/components/preferences/in-content/tests/browser_healthreport.js b/browser/components/preferences/in-content/tests/browser_healthreport.js
new file mode 100644
index 000000000..bbfae9707
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_healthreport.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
+
+function runPaneTest(fn) {
+ open_preferences((win) => {
+ let doc = win.document;
+ win.gotoPref("paneAdvanced");
+ let advancedPrefs = doc.getElementById("advancedPrefs");
+ let tab = doc.getElementById("dataChoicesTab");
+ advancedPrefs.selectedTab = tab;
+ fn(win, doc);
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ resetPreferences();
+ registerCleanupFunction(resetPreferences);
+ runPaneTest(testBasic);
+}
+
+function testBasic(win, doc) {
+ is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), true,
+ "Health Report upload enabled on app first run.");
+
+ let checkbox = doc.getElementById("submitHealthReportBox");
+ ok(checkbox);
+ is(checkbox.checked, true, "Health Report checkbox is checked on app first run.");
+
+ checkbox.checked = false;
+ checkbox.doCommand();
+ is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), false,
+ "Unchecking checkbox opts out of FHR upload.");
+
+ checkbox.checked = true;
+ checkbox.doCommand();
+ is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), true,
+ "Checking checkbox allows FHR upload.");
+
+ win.close();
+ Services.prefs.lockPref(FHR_UPLOAD_ENABLED);
+ runPaneTest(testUploadDisabled);
+}
+
+function testUploadDisabled(win, doc) {
+ ok(Services.prefs.prefIsLocked(FHR_UPLOAD_ENABLED), "Upload enabled flag is locked.");
+ let checkbox = doc.getElementById("submitHealthReportBox");
+ is(checkbox.getAttribute("disabled"), "true", "Checkbox is disabled if upload flag is locked.");
+ Services.prefs.unlockPref(FHR_UPLOAD_ENABLED);
+
+ win.close();
+ finish();
+}
+
+function resetPreferences() {
+ Services.prefs.clearUserPref(FHR_UPLOAD_ENABLED);
+}
+
diff --git a/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
new file mode 100644
index 000000000..366454fcc
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
@@ -0,0 +1,20 @@
+add_task(function*() {
+ is(gBrowser.currentURI.spec, "about:blank", "Test starts with about:blank open");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
+ yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
+ let doc = gBrowser.contentDocument;
+ is(gBrowser.currentURI.spec, "about:preferences#general",
+ "#general should be in the URI for about:preferences");
+ let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
+
+ let useCurrent = doc.getElementById("useCurrent");
+ useCurrent.click();
+
+ is(gBrowser.tabs.length, 3, "Three tabs should be open");
+ is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
+ "about:blank and about:home should be the only homepages set");
+
+ Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js b/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
new file mode 100644
index 000000000..68f9653f6
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+registerCleanupFunction(function() {
+ while (gBrowser.tabs[1])
+ gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function*() {
+ let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent", undefined, {leaveOpen: true});
+ is(prefs.selectedPane, "paneContent", "Content pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let notificationsDoNotDisturbRow = doc.getElementById("notificationsDoNotDisturbRow");
+ if (notificationsDoNotDisturbRow.hidden) {
+ todo(false, "Do not disturb is not available on this platform");
+ return;
+ }
+
+ let alertService;
+ try {
+ alertService = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+ } catch (ex) {
+ ok(true, "Do not disturb is not available on this platform: " + ex.message);
+ return;
+ }
+
+ let checkbox = doc.getElementById("notificationsDoNotDisturb");
+ ok(!checkbox.checked, "Checkbox should not be checked by default");
+ ok(!alertService.manualDoNotDisturb, "Do not disturb should be off by default");
+
+ let checkboxChanged = waitForEvent(checkbox, "command")
+ checkbox.click();
+ yield checkboxChanged;
+ ok(alertService.manualDoNotDisturb, "Do not disturb should be enabled when checked");
+
+ checkboxChanged = waitForEvent(checkbox, "command")
+ checkbox.click();
+ yield checkboxChanged;
+ ok(!alertService.manualDoNotDisturb, "Do not disturb should be disabled when unchecked");
+});
diff --git a/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js b/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js
new file mode 100644
index 000000000..d9253735a
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
+
+add_task(function* urlFieldVisibleForPopupPermissions(finish) {
+ yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let popupPolicyCheckbox = doc.getElementById("popupPolicy");
+ ok(!popupPolicyCheckbox.checked, "popupPolicyCheckbox should be unchecked by default");
+ popupPolicyCheckbox.click();
+ let popupPolicyButton = doc.getElementById("popupPolicyButton");
+ ok(popupPolicyButton, "popupPolicyButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ popupPolicyButton.click();
+ let dialog = yield dialogPromise;
+ ok(dialog, "dialog loaded");
+
+ let urlLabel = dialog.document.getElementById("urlLabel");
+ ok(!urlLabel.hidden, "urlLabel should be visible when one of block/session/allow visible");
+ let url = dialog.document.getElementById("url");
+ ok(!url.hidden, "url should be visible when one of block/session/allow visible");
+
+ popupPolicyCheckbox.click();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* urlFieldHiddenForNotificationPermissions() {
+ yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let notificationsPolicyButton = doc.getElementById("notificationsPolicyButton");
+ ok(notificationsPolicyButton, "notificationsPolicyButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ notificationsPolicyButton.click();
+ let dialog = yield dialogPromise;
+ ok(dialog, "dialog loaded");
+
+ let urlLabel = dialog.document.getElementById("urlLabel");
+ ok(urlLabel.hidden, "urlLabel should be hidden as requested");
+ let url = dialog.document.getElementById("url");
+ ok(url.hidden, "url should be hidden as requested");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_1.js b/browser/components/preferences/in-content/tests/browser_privacypane_1.js
new file mode 100644
index 000000000..0df60c6ac
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_1.js
@@ -0,0 +1,18 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ test_pane_visibility,
+ test_dependent_elements,
+ test_dependent_cookie_elements,
+ test_dependent_clearonclose_elements,
+ test_dependent_prefs,
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_3.js b/browser/components/preferences/in-content/tests/browser_privacypane_3.js
new file mode 100644
index 000000000..8fe6f0825
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_3.js
@@ -0,0 +1,17 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ test_custom_retention("rememberHistory", "remember"),
+ test_custom_retention("rememberHistory", "custom"),
+ test_custom_retention("rememberForms", "remember"),
+ test_custom_retention("rememberForms", "custom"),
+ test_historymode_retention("remember", "remember"),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_4.js b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
new file mode 100644
index 000000000..b7ef3deda
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
@@ -0,0 +1,25 @@
+requestLongerTimeout(2);
+
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+run_test_subset([
+ test_custom_retention("acceptCookies", "remember"),
+ test_custom_retention("acceptCookies", "custom"),
+ test_custom_retention("acceptThirdPartyMenu", "remember", "visited"),
+ test_custom_retention("acceptThirdPartyMenu", "custom", "always"),
+ test_custom_retention("keepCookiesUntil", "remember", 1),
+ test_custom_retention("keepCookiesUntil", "custom", 2),
+ test_custom_retention("keepCookiesUntil", "custom", 0),
+ test_custom_retention("alwaysClear", "remember"),
+ test_custom_retention("alwaysClear", "custom"),
+ test_historymode_retention("remember", "remember"),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_5.js b/browser/components/preferences/in-content/tests/browser_privacypane_5.js
new file mode 100644
index 000000000..a07530010
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_5.js
@@ -0,0 +1,17 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ test_locbar_suggestion_retention("history", true),
+ test_locbar_suggestion_retention("bookmark", true),
+ test_locbar_suggestion_retention("openpage", false),
+ test_locbar_suggestion_retention("history", true),
+ test_locbar_suggestion_retention("history", false),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_8.js b/browser/components/preferences/in-content/tests/browser_privacypane_8.js
new file mode 100644
index 000000000..756b19a2f
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_8.js
@@ -0,0 +1,26 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ // history mode should be initialized to remember
+ test_historymode_retention("remember", undefined),
+
+ // history mode should remain remember; toggle acceptCookies checkbox
+ test_custom_retention("acceptCookies", "remember"),
+
+ // history mode should now be custom; set history mode to dontremember
+ test_historymode_retention("dontremember", "custom"),
+
+ // history mode should remain custom; set history mode to remember
+ test_historymode_retention("remember", "custom"),
+
+ // history mode should now be remember
+ test_historymode_retention("remember", "remember"),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_proxy_backup.js b/browser/components/preferences/in-content/tests/browser_proxy_backup.js
new file mode 100644
index 000000000..3ad24c7ec
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_proxy_backup.js
@@ -0,0 +1,65 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // network.proxy.type needs to be backed up and restored because mochitest
+ // changes this setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function() {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ for (let proxyType of ["http", "ssl", "ftp", "socks"]) {
+ Services.prefs.clearUserPref("network.proxy." + proxyType);
+ Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
+ if (proxyType == "http") {
+ continue;
+ }
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType + "_port");
+ }
+ });
+
+ let connectionURL = "chrome://browser/content/preferences/connection.xul";
+
+ // Set a shared proxy and a SOCKS backup
+ Services.prefs.setIntPref("network.proxy.type", 1);
+ Services.prefs.setBoolPref("network.proxy.share_proxy_settings", true);
+ Services.prefs.setCharPref("network.proxy.http", "example.com");
+ Services.prefs.setIntPref("network.proxy.http_port", 1200);
+ Services.prefs.setCharPref("network.proxy.socks", "example.com");
+ Services.prefs.setIntPref("network.proxy.socks_port", 1200);
+ Services.prefs.setCharPref("network.proxy.backup.socks", "127.0.0.1");
+ Services.prefs.setIntPref("network.proxy.backup.socks_port", 9050);
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ so it has to be opened as a sub dialog of the main pref tab.
+ Open the main tab here.
+ */
+ open_preferences(Task.async(function* tabOpened(aContentWindow) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+ let dialog = yield openAndLoadSubDialog(connectionURL);
+ let dialogClosingPromise = waitForEvent(dialog.document.documentElement, "dialogclosing");
+
+ ok(dialog, "connection window opened");
+ dialog.document.documentElement.acceptDialog();
+
+ let dialogClosingEvent = yield dialogClosingPromise;
+ ok(dialogClosingEvent, "connection window closed");
+
+ // The SOCKS backup should not be replaced by the shared value
+ is(Services.prefs.getCharPref("network.proxy.backup.socks"), "127.0.0.1", "Shared proxy backup shouldn't be replaced");
+ is(Services.prefs.getIntPref("network.proxy.backup.socks_port"), 9050, "Shared proxy port backup shouldn't be replaced");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }));
+}
diff --git a/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js b/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js
new file mode 100644
index 000000000..6b587e036
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js
@@ -0,0 +1,37 @@
+"use strict";
+
+function switchToCustomHistoryMode(doc) {
+ // Select the last item in the menulist.
+ let menulist = doc.getElementById("historyMode");
+ menulist.focus();
+ EventUtils.sendKey("UP");
+}
+
+function testPrefStateMatchesLockedState() {
+ let win = gBrowser.contentWindow;
+ let doc = win.document;
+ switchToCustomHistoryMode(doc);
+
+ let checkbox = doc.getElementById("alwaysClear");
+ let preference = doc.getElementById("privacy.sanitize.sanitizeOnShutdown");
+ is(checkbox.disabled, preference.locked, "Always Clear checkbox should be enabled when preference is not locked.");
+
+ gBrowser.removeCurrentTab();
+}
+
+add_task(function setup() {
+ registerCleanupFunction(function resetPreferences() {
+ Services.prefs.unlockPref("privacy.sanitize.sanitizeOnShutdown");
+ });
+});
+
+add_task(function* test_preference_enabled_when_unlocked() {
+ yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
+ testPrefStateMatchesLockedState();
+});
+
+add_task(function* test_preference_disabled_when_locked() {
+ Services.prefs.lockPref("privacy.sanitize.sanitizeOnShutdown");
+ yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
+ testPrefStateMatchesLockedState();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_searchsuggestions.js b/browser/components/preferences/in-content/tests/browser_searchsuggestions.js
new file mode 100644
index 000000000..0185a23b9
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_searchsuggestions.js
@@ -0,0 +1,43 @@
+var original = Services.prefs.getBoolPref("browser.search.suggest.enabled");
+
+registerCleanupFunction(() => {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", original);
+});
+
+// Open with suggestions enabled
+add_task(function*() {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
+
+ yield openPreferencesViaOpenPreferencesAPI("search", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let urlbarBox = doc.getElementById("urlBarSuggestion");
+ ok(!urlbarBox.disabled, "Checkbox should be enabled");
+
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+
+ ok(urlbarBox.disabled, "Checkbox should be disabled");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Open with suggestions disabled
+add_task(function*() {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+
+ yield openPreferencesViaOpenPreferencesAPI("search", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let urlbarBox = doc.getElementById("urlBarSuggestion");
+ ok(urlbarBox.disabled, "Checkbox should be disabled");
+
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
+
+ ok(!urlbarBox.disabled, "Checkbox should be enabled");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", original);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_security.js b/browser/components/preferences/in-content/tests/browser_security.js
new file mode 100644
index 000000000..e6eb2a91d
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_security.js
@@ -0,0 +1,130 @@
+const PREFS = [
+ "browser.safebrowsing.phishing.enabled",
+ "browser.safebrowsing.malware.enabled",
+
+ "browser.safebrowsing.downloads.enabled",
+
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
+ "browser.safebrowsing.downloads.remote.block_uncommon"
+];
+
+let originals = PREFS.map(pref => [pref, Services.prefs.getBoolPref(pref)])
+let originalMalwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable");
+registerCleanupFunction(function() {
+ originals.forEach(([pref, val]) => Services.prefs.setBoolPref(pref, val))
+ Services.prefs.setCharPref("urlclassifier.malwareTable", originalMalwareTable);
+});
+
+// test the safebrowsing preference
+add_task(function*() {
+ function* checkPrefSwitch(val1, val2) {
+ Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", val1);
+ Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", val2);
+
+ yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("enableSafeBrowsing");
+ let blockDownloads = doc.getElementById("blockDownloads");
+ let blockUncommon = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val1 && val2, "safebrowsing preference is initialized correctly");
+ // should be disabled when checked is false (= pref is turned off)
+ is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
+ is(blockUncommon.hasAttribute("disabled"), !checked, "block uncommon checkbox is set correctly");
+
+ // click the checkbox
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
+
+ // check that both settings are now turned on or off
+ is(Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled"), !checked,
+ "safebrowsing.enabled is set correctly");
+ is(Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"), !checked,
+ "safebrowsing.malware.enabled is set correctly");
+
+ // check if the other checkboxes have updated
+ checked = checkbox.checked;
+ is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
+ is(blockUncommon.hasAttribute("disabled"), !checked || !blockDownloads.checked, "block uncommon checkbox is set correctly");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ yield checkPrefSwitch(true, true);
+ yield checkPrefSwitch(false, true);
+ yield checkPrefSwitch(true, false);
+ yield checkPrefSwitch(false, false);
+});
+
+// test the download protection preference
+add_task(function*() {
+ function* checkPrefSwitch(val) {
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", val);
+
+ yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("blockDownloads");
+ let blockUncommon = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val, "downloads preference is initialized correctly");
+ // should be disabled when val is false (= pref is turned off)
+ is(blockUncommon.hasAttribute("disabled"), !val, "block uncommon checkbox is set correctly");
+
+ // click the checkbox
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
+
+ // check that setting is now turned on or off
+ is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled"), !checked,
+ "safebrowsing.downloads preference is set correctly");
+
+ // check if the uncommon warning checkbox has updated
+ is(blockUncommon.hasAttribute("disabled"), val, "block uncommon checkbox is set correctly");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ yield checkPrefSwitch(true);
+ yield checkPrefSwitch(false);
+});
+
+// test the unwanted/uncommon software warning preference
+add_task(function*() {
+ function* checkPrefSwitch(val1, val2) {
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", val1);
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_uncommon", val2);
+
+ yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val1 && val2, "unwanted/uncommon preference is initialized correctly");
+
+ // click the checkbox
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
+
+ // check that both settings are now turned on or off
+ is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.remote.block_potentially_unwanted"), !checked,
+ "block_potentially_unwanted is set correctly");
+ is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.remote.block_uncommon"), !checked,
+ "block_uncommon is set correctly");
+
+ // when the preference is on, the malware table should include these ids
+ let malwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable").split(",");
+ is(malwareTable.includes("goog-unwanted-shavar"), !checked,
+ "malware table doesn't include goog-unwanted-shavar");
+ is(malwareTable.includes("test-unwanted-simple"), !checked,
+ "malware table doesn't include test-unwanted-simple");
+ let sortedMalware = malwareTable.slice(0);
+ sortedMalware.sort();
+ Assert.deepEqual(malwareTable, sortedMalware, "malware table has been sorted");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ yield* checkPrefSwitch(true, true);
+ yield* checkPrefSwitch(false, true);
+ yield* checkPrefSwitch(true, false);
+ yield* checkPrefSwitch(false, false);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_subdialogs.js b/browser/components/preferences/in-content/tests/browser_subdialogs.js
new file mode 100644
index 000000000..ff0c1f8ae
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -0,0 +1,293 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for the sub-dialog infrastructure, not for actual sub-dialog functionality.
+ */
+
+const gDialogURL = getRootDirectory(gTestPath) + "subdialog.xul";
+const gDialogURL2 = getRootDirectory(gTestPath) + "subdialog2.xul";
+
+function* open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn, url = gDialogURL) {
+ let domcontentloadedFnStr = domcontentloadedFn ?
+ "(" + domcontentloadedFn.toString() + ")()" :
+ "";
+ return ContentTask.spawn(browser, {url, domcontentloadedFnStr}, function*(args) {
+ let {url, domcontentloadedFnStr} = args;
+ let rv = { acceptCount: 0 };
+ let win = content.window;
+ let subdialog = win.gSubDialog;
+ subdialog.open(url, null, rv);
+
+ info("waiting for subdialog DOMFrameContentLoaded");
+ yield ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
+ let result;
+ if (domcontentloadedFnStr) {
+ result = eval(domcontentloadedFnStr);
+ }
+
+ info("waiting for subdialog load");
+ yield ContentTaskUtils.waitForEvent(subdialog._frame, "load");
+ info("subdialog window is loaded");
+
+ let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of subdialog._frame.contentDocument.styleSheets) {
+ let index = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (index >= 0) {
+ expectedStyleSheetURLs.splice(index, 1);
+ }
+ }
+
+ Assert.ok(!!subdialog._frame.contentWindow, "The dialog should be non-null");
+ Assert.notEqual(subdialog._frame.contentWindow.location.toString(), "about:blank",
+ "Subdialog URL should not be about:blank");
+ Assert.equal(win.getComputedStyle(subdialog._overlay, "").visibility, "visible",
+ "Overlay should be visible");
+ Assert.equal(expectedStyleSheetURLs.length, 0,
+ "No stylesheets that were expected are missing");
+ return result;
+ });
+}
+
+function* close_subdialog_and_test_generic_end_state(browser, closingFn, closingButton, acceptCount, options) {
+ let dialogclosingPromise = ContentTask.spawn(browser, {closingButton, acceptCount}, function*(expectations) {
+ let win = content.window;
+ let subdialog = win.gSubDialog;
+ let frame = subdialog._frame;
+ info("waiting for dialogclosing");
+ let closingEvent =
+ yield ContentTaskUtils.waitForEvent(frame.contentWindow, "dialogclosing");
+ let closingButton = closingEvent.detail.button;
+ let actualAcceptCount = frame.contentWindow.arguments &&
+ frame.contentWindow.arguments[0].acceptCount;
+
+ info("waiting for about:blank load");
+ yield ContentTaskUtils.waitForEvent(frame, "load");
+
+ Assert.notEqual(win.getComputedStyle(subdialog._overlay, "").visibility, "visible",
+ "overlay is not visible");
+ Assert.equal(frame.getAttribute("style"), "", "inline styles should be cleared");
+ Assert.equal(frame.contentWindow.location.href.toString(), "about:blank",
+ "sub-dialog should be unloaded");
+ Assert.equal(closingButton, expectations.closingButton,
+ "closing event should indicate button was '" + expectations.closingButton + "'");
+ Assert.equal(actualAcceptCount, expectations.acceptCount,
+ "should be 1 if accepted, 0 if canceled, undefined if closed w/out button");
+ });
+
+ if (options && options.runClosingFnOutsideOfContentTask) {
+ yield closingFn();
+ } else {
+ ContentTask.spawn(browser, null, closingFn);
+ }
+
+ yield dialogclosingPromise;
+}
+
+let tab;
+
+add_task(function* test_initialize() {
+ tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+});
+
+add_task(function* check_titlebar_focus_returnval_titlechanges_accepting() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ let domtitlechangedPromise = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "DOMTitleChanged");
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let dialog = content.window.gSubDialog._frame.contentWindow;
+ let dialogTitleElement = content.document.getElementById("dialogTitle");
+ Assert.equal(dialogTitleElement.textContent, "Sample sub-dialog",
+ "Title should be correct initially");
+ Assert.equal(dialog.document.activeElement.value, "Default text",
+ "Textbox with correct text is focused");
+ dialog.document.title = "Updated title";
+ });
+
+ info("waiting for DOMTitleChanged event");
+ yield domtitlechangedPromise;
+
+ ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let dialogTitleElement = content.document.getElementById("dialogTitle");
+ Assert.equal(dialogTitleElement.textContent, "Updated title",
+ "subdialog should have updated title");
+ });
+
+ // Accept the dialog
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+ "accept", 1);
+});
+
+add_task(function* check_canceling_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.cancelDialog(); },
+ "cancel", 0);
+});
+
+add_task(function* check_reopening_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ info("opening another dialog which will close the first");
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, "", gDialogURL2);
+ info("closing as normal");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+ "accept", 1);
+});
+
+add_task(function* check_opening_while_closing() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ info("closing");
+ content.window.gSubDialog.close();
+ info("reopening immediately after calling .close()");
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+ "accept", 1);
+
+});
+
+add_task(function* window_close_on_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* click_close_button_on_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { return BrowserTestUtils.synthesizeMouseAtCenter("#dialogClose", {}, tab.linkedBrowser); },
+ null, 0, {runClosingFnOutsideOfContentTask: true});
+});
+
+add_task(function* back_navigation_on_subdialog_should_close_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.goBack(); },
+ null, undefined);
+});
+
+add_task(function* back_navigation_on_browser_tab_should_close_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { tab.linkedBrowser.goBack(); },
+ null, undefined, {runClosingFnOutsideOfContentTask: true});
+});
+
+add_task(function* escape_should_close_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { return BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, tab.linkedBrowser); },
+ "cancel", 0, {runClosingFnOutsideOfContentTask: true});
+});
+
+add_task(function* correct_width_and_height_should_be_used_for_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let frameStyle = content.window.gSubDialog._frame.style;
+ Assert.equal(frameStyle.width, "32em",
+ "Width should be set on the frame from the dialog");
+ Assert.equal(frameStyle.height, "5em",
+ "Height should be set on the frame from the dialog");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* wrapped_text_in_dialog_should_have_expected_scrollHeight() {
+ let oldHeight = yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._frame;
+ let doc = frame.contentDocument;
+ let oldHeight = doc.documentElement.scrollHeight;
+ doc.documentElement.style.removeProperty("height");
+ doc.getElementById("desc").textContent = `
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ voluptatem sequi nesciunt.`
+ return oldHeight;
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, oldHeight, function*(oldHeight) {
+ let frame = content.window.gSubDialog._frame;
+ let docEl = frame.contentDocument.documentElement;
+ Assert.equal(frame.style.width, "32em",
+ "Width should be set on the frame from the dialog");
+ Assert.ok(docEl.scrollHeight > oldHeight,
+ "Content height increased (from " + oldHeight + " to " + docEl.scrollHeight + ").");
+ Assert.equal(frame.style.height, docEl.scrollHeight + "px",
+ "Height on the frame should be higher now");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* dialog_too_tall_should_get_reduced_in_height() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._frame;
+ frame.contentDocument.documentElement.style.height = '100000px';
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let frame = content.window.gSubDialog._frame;
+ Assert.equal(frame.style.width, "32em", "Width should be set on the frame from the dialog");
+ Assert.ok(parseInt(frame.style.height, 10) < content.window.innerHeight,
+ "Height on the frame should be smaller than window's innerHeight");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._frame;
+ frame.contentDocument.documentElement.style.removeProperty("height");
+ frame.contentDocument.documentElement.style.removeProperty("width");
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let frame = content.window.gSubDialog._frame;
+ Assert.ok(frame.style.width.endsWith("px"),
+ "Width (" + frame.style.width + ") should be set to a px value of the scrollWidth from the dialog");
+ Assert.ok(frame.style.height.endsWith("px"),
+ "Height (" + frame.style.height + ") should be set to a px value of the scrollHeight from the dialog");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* test_shutdown() {
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_telemetry.js b/browser/components/preferences/in-content/tests/browser_telemetry.js
new file mode 100644
index 000000000..d8139d87a
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_telemetry.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+
+function runPaneTest(fn) {
+ open_preferences((win) => {
+ let doc = win.document;
+ win.gotoPref("paneAdvanced");
+ let advancedPrefs = doc.getElementById("advancedPrefs");
+ let tab = doc.getElementById("dataChoicesTab");
+ advancedPrefs.selectedTab = tab;
+ fn(win, doc);
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ resetPreferences();
+ registerCleanupFunction(resetPreferences);
+ runPaneTest(testTelemetryState);
+}
+
+function testTelemetryState(win, doc) {
+ let fhrCheckbox = doc.getElementById("submitHealthReportBox");
+ Assert.ok(fhrCheckbox.checked, "Health Report checkbox is checked on app first run.");
+
+ let telmetryCheckbox = doc.getElementById("submitTelemetryBox");
+ Assert.ok(!telmetryCheckbox.disabled,
+ "Telemetry checkbox must be enabled if FHR is checked.");
+ Assert.ok(Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED),
+ "Telemetry must be enabled if the checkbox is ticked.");
+
+ // Uncheck the FHR checkbox and make sure that Telemetry checkbox gets disabled.
+ fhrCheckbox.click();
+
+ Assert.ok(telmetryCheckbox.disabled,
+ "Telemetry checkbox must be disabled if FHR is unchecked.");
+ Assert.ok(!Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED),
+ "Telemetry must be disabled if the checkbox is unticked.");
+
+ win.close();
+ finish();
+}
+
+function resetPreferences() {
+ Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
+ Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
+}
+
diff --git a/browser/components/preferences/in-content/tests/head.js b/browser/components/preferences/in-content/tests/head.js
new file mode 100644
index 000000000..0ed811e94
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/head.js
@@ -0,0 +1,165 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Promise.jsm");
+
+const kDefaultWait = 2000;
+
+function is_hidden(aElement) {
+ var style = aElement.ownerGlobal.getComputedStyle(aElement);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+
+ // Hiding a parent element will hide all its children
+ if (aElement.parentNode != aElement.ownerDocument)
+ return is_hidden(aElement.parentNode);
+
+ return false;
+}
+
+function is_element_visible(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(!is_hidden(aElement), aMsg);
+}
+
+function is_element_hidden(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(aElement), aMsg);
+}
+
+function open_preferences(aCallback) {
+ gBrowser.selectedTab = gBrowser.addTab("about:preferences");
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function () {
+ newTabBrowser.removeEventListener("Initialized", arguments.callee, true);
+ aCallback(gBrowser.contentWindow);
+ }, true);
+}
+
+function openAndLoadSubDialog(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+ let promise = promiseLoadSubDialog(aURL);
+ content.gSubDialog.open(aURL, aFeatures, aParams, aClosingCallback);
+ return promise;
+}
+
+function promiseLoadSubDialog(aURL) {
+ return new Promise((resolve, reject) => {
+ content.gSubDialog._frame.addEventListener("load", function load(aEvent) {
+ if (aEvent.target.contentWindow.location == "about:blank")
+ return;
+ content.gSubDialog._frame.removeEventListener("load", load);
+
+ is(content.gSubDialog._frame.contentWindow.location.toString(), aURL,
+ "Check the proper URL is loaded");
+
+ // Check visibility
+ is_element_visible(content.gSubDialog._overlay, "Overlay is visible");
+
+ // Check that stylesheets were injected
+ let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) {
+ let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (i >= 0) {
+ info("found " + styleSheet.href);
+ expectedStyleSheetURLs.splice(i, 1);
+ }
+ }
+ is(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found");
+
+ resolve(content.gSubDialog._frame.contentWindow);
+ });
+ });
+}
+
+/**
+ * Waits a specified number of miliseconds for a specified event to be
+ * fired on a specified element.
+ *
+ * Usage:
+ * let receivedEvent = waitForEvent(element, "eventName");
+ * // Do some processing here that will cause the event to be fired
+ * // ...
+ * // Now yield until the Promise is fulfilled
+ * yield receivedEvent;
+ * if (receivedEvent && !(receivedEvent instanceof Error)) {
+ * receivedEvent.msg == "eventName";
+ * // ...
+ * }
+ *
+ * @param aSubject the element that should receive the event
+ * @param aEventName the event to wait for
+ * @param aTimeoutMs the number of miliseconds to wait before giving up
+ * @returns a Promise that resolves to the received event, or to an Error
+ */
+function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
+ let eventDeferred = Promise.defer();
+ let timeoutMs = aTimeoutMs || kDefaultWait;
+ let stack = new Error().stack;
+ let timerID = setTimeout(function wfe_canceller() {
+ aSubject.removeEventListener(aEventName, listener);
+ eventDeferred.reject(new Error(aEventName + " event timeout at " + stack));
+ }, timeoutMs);
+
+ var listener = function (aEvent) {
+ if (aTarget && aTarget !== aEvent.target)
+ return;
+
+ // stop the timeout clock and resume
+ clearTimeout(timerID);
+ eventDeferred.resolve(aEvent);
+ };
+
+ function cleanup(aEventOrError) {
+ // unhook listener in case of success or failure
+ aSubject.removeEventListener(aEventName, listener);
+ return aEventOrError;
+ }
+ aSubject.addEventListener(aEventName, listener, false);
+ return eventDeferred.promise.then(cleanup, cleanup);
+}
+
+function openPreferencesViaOpenPreferencesAPI(aPane, aAdvancedTab, aOptions) {
+ let deferred = Promise.defer();
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ openPreferences(aPane, aAdvancedTab ? {advancedTab: aAdvancedTab} : undefined);
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ newTabBrowser.addEventListener("Initialized", function PrefInit() {
+ newTabBrowser.removeEventListener("Initialized", PrefInit, true);
+ newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
+ newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ let doc = win.document;
+ let selectedAdvancedTab = aAdvancedTab && doc.getElementById("advancedPrefs").selectedTab.id;
+ if (!aOptions || !aOptions.leaveOpen)
+ gBrowser.removeCurrentTab();
+ deferred.resolve({selectedPane: selectedPane, selectedAdvancedTab: selectedAdvancedTab});
+ });
+ }, true);
+
+ return deferred.promise;
+}
+
+function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
+ return new Promise((resolve, reject) => {
+ function tryNow() {
+ tries++;
+ let rv = aConditionFn();
+ if (rv) {
+ resolve(rv);
+ } else if (tries < aMaxTries) {
+ tryAgain();
+ } else {
+ reject("Condition timed out: " + aConditionFn.toSource());
+ }
+ }
+ function tryAgain() {
+ setTimeout(tryNow, aCheckInterval);
+ }
+ let tries = 0;
+ tryAgain();
+ });
+}
diff --git a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
new file mode 100644
index 000000000..53c6d7d8a
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -0,0 +1,330 @@
+function* runTestOnPrivacyPrefPane(testFunc) {
+ info("runTestOnPrivacyPrefPane entered");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences", true, true);
+ let browser = tab.linkedBrowser;
+ info("loaded about:preferences");
+ browser.contentWindow.gotoPref("panePrivacy");
+ info("viewing privacy pane, executing testFunc");
+ testFunc(browser.contentWindow);
+ yield BrowserTestUtils.removeTab(tab);
+}
+
+function controlChanged(element) {
+ element.doCommand();
+}
+
+// We can only test the panes that don't trigger a preference update
+function test_pane_visibility(win) {
+ let modes = {
+ "remember": "historyRememberPane",
+ "custom": "historyCustomPane"
+ };
+
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let historypane = win.document.getElementById("historyPane");
+ ok(historypane, "history mode pane should exist");
+
+ for (let mode in modes) {
+ historymode.value = mode;
+ controlChanged(historymode);
+ is(historypane.selectedPanel, win.document.getElementById(modes[mode]),
+ "The correct pane should be selected for the " + mode + " mode");
+ is_element_visible(historypane.selectedPanel,
+ "Correct pane should be visible for the " + mode + " mode");
+ }
+}
+
+function test_dependent_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let controls = [
+ win.document.getElementById("rememberHistory"),
+ win.document.getElementById("rememberForms"),
+ win.document.getElementById("keepUntil"),
+ win.document.getElementById("keepCookiesUntil"),
+ win.document.getElementById("alwaysClear"),
+ ];
+ controls.forEach(function(control) {
+ ok(control, "the dependent controls should exist");
+ });
+ let independents = [
+ win.document.getElementById("acceptCookies"),
+ win.document.getElementById("acceptThirdPartyLabel"),
+ win.document.getElementById("acceptThirdPartyMenu")
+ ];
+ independents.forEach(function(control) {
+ ok(control, "the independent controls should exist");
+ });
+ let cookieexceptions = win.document.getElementById("cookieExceptions");
+ ok(cookieexceptions, "the cookie exceptions button should exist");
+ let keepuntil = win.document.getElementById("keepCookiesUntil");
+ ok(keepuntil, "the keep cookies until menulist should exist");
+ let alwaysclear = win.document.getElementById("alwaysClear");
+ ok(alwaysclear, "the clear data on close checkbox should exist");
+ let rememberhistory = win.document.getElementById("rememberHistory");
+ ok(rememberhistory, "the remember history checkbox should exist");
+ let rememberforms = win.document.getElementById("rememberForms");
+ ok(rememberforms, "the remember forms checkbox should exist");
+ let alwaysclearsettings = win.document.getElementById("clearDataSettings");
+ ok(alwaysclearsettings, "the clear data settings button should exist");
+
+ function expect_disabled(disabled) {
+ controls.forEach(function(control) {
+ is(control.disabled, disabled,
+ control.getAttribute("id") + " should " + (disabled ? "" : "not ") + "be disabled");
+ });
+ is(keepuntil.value, disabled ? 2 : 0,
+ "the keep cookies until menulist value should be as expected");
+ if (disabled) {
+ ok(!alwaysclear.checked,
+ "the clear data on close checkbox value should be as expected");
+ ok(!rememberhistory.checked,
+ "the remember history checkbox value should be as expected");
+ ok(!rememberforms.checked,
+ "the remember forms checkbox value should be as expected");
+ }
+ }
+ function check_independents(expected) {
+ independents.forEach(function(control) {
+ is(control.disabled, expected,
+ control.getAttribute("id") + " should " + (expected ? "" : "not ") + "be disabled");
+ });
+
+ ok(!cookieexceptions.disabled,
+ "the cookie exceptions button should never be disabled");
+ ok(alwaysclearsettings.disabled,
+ "the clear data settings button should always be disabled");
+ }
+
+ // controls should only change in custom mode
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_disabled(false);
+ check_independents(false);
+
+ // setting the mode to custom shouldn't change anything
+ historymode.value = "custom";
+ controlChanged(historymode);
+ expect_disabled(false);
+ check_independents(false);
+}
+
+function test_dependent_cookie_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let controls = [
+ win.document.getElementById("acceptThirdPartyLabel"),
+ win.document.getElementById("acceptThirdPartyMenu"),
+ win.document.getElementById("keepUntil"),
+ win.document.getElementById("keepCookiesUntil"),
+ ];
+ controls.forEach(function(control) {
+ ok(control, "the dependent cookie controls should exist");
+ });
+ let acceptcookies = win.document.getElementById("acceptCookies");
+ ok(acceptcookies, "the accept cookies checkbox should exist");
+
+ function expect_disabled(disabled) {
+ controls.forEach(function(control) {
+ is(control.disabled, disabled,
+ control.getAttribute("id") + " should " + (disabled ? "" : "not ") + "be disabled");
+ });
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ expect_disabled(false);
+
+ acceptcookies.checked = false;
+ controlChanged(acceptcookies);
+ expect_disabled(true);
+
+ acceptcookies.checked = true;
+ controlChanged(acceptcookies);
+ expect_disabled(false);
+
+ let accessthirdparty = controls.shift();
+ acceptcookies.checked = false;
+ controlChanged(acceptcookies);
+ expect_disabled(true);
+ ok(accessthirdparty.disabled, "access third party button should be disabled");
+
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ expect_disabled(true);
+ ok(accessthirdparty.disabled, "access third party button should be disabled");
+
+ acceptcookies.checked = true;
+ controlChanged(acceptcookies);
+ expect_disabled(false);
+ ok(!accessthirdparty.disabled, "access third party button should be enabled");
+}
+
+function test_dependent_clearonclose_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let alwaysclear = win.document.getElementById("alwaysClear");
+ ok(alwaysclear, "the clear data on close checkbox should exist");
+ let alwaysclearsettings = win.document.getElementById("clearDataSettings");
+ ok(alwaysclearsettings, "the clear data settings button should exist");
+
+ function expect_disabled(disabled) {
+ is(alwaysclearsettings.disabled, disabled,
+ "the clear data settings should " + (disabled ? "" : "not ") + "be disabled");
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ alwaysclear.checked = false;
+ controlChanged(alwaysclear);
+ expect_disabled(true);
+
+ alwaysclear.checked = true;
+ controlChanged(alwaysclear);
+ expect_disabled(false);
+
+ alwaysclear.checked = false;
+ controlChanged(alwaysclear);
+ expect_disabled(true);
+}
+
+function test_dependent_prefs(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let controls = [
+ win.document.getElementById("rememberHistory"),
+ win.document.getElementById("rememberForms"),
+ win.document.getElementById("acceptCookies")
+ ];
+ controls.forEach(function(control) {
+ ok(control, "the micro-management controls should exist");
+ });
+
+ let thirdPartyCookieMenu = win.document.getElementById("acceptThirdPartyMenu");
+ ok(thirdPartyCookieMenu, "the third-party cookie control should exist");
+
+ function expect_checked(checked) {
+ controls.forEach(function(control) {
+ is(control.checked, checked,
+ control.getAttribute("id") + " should " + (checked ? "not " : "") + "be checked");
+ });
+
+ is(thirdPartyCookieMenu.value == "always" || thirdPartyCookieMenu.value == "visited", checked, "third-party cookies should " + (checked ? "not " : "") + "be limited");
+ }
+
+ // controls should be checked in remember mode
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_checked(true);
+
+ // even if they're unchecked in custom mode
+ historymode.value = "custom";
+ controlChanged(historymode);
+ thirdPartyCookieMenu.value = "never";
+ controlChanged(thirdPartyCookieMenu);
+ controls.forEach(function(control) {
+ control.checked = false;
+ controlChanged(control);
+ });
+ expect_checked(false);
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_checked(true);
+}
+
+function test_historymode_retention(mode, expect) {
+ return function test_historymode_retention_fn(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+
+ if ((historymode.value == "remember" && mode == "dontremember") ||
+ (historymode.value == "dontremember" && mode == "remember") ||
+ (historymode.value == "custom" && mode == "dontremember")) {
+ return;
+ }
+
+ if (expect !== undefined) {
+ is(historymode.value, expect,
+ "history mode is expected to remain " + expect);
+ }
+
+ historymode.value = mode;
+ controlChanged(historymode);
+ };
+}
+
+function test_custom_retention(controlToChange, expect, valueIncrement) {
+ return function test_custom_retention_fn(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+
+ if (expect !== undefined) {
+ is(historymode.value, expect,
+ "history mode is expected to remain " + expect);
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+
+ controlToChange = win.document.getElementById(controlToChange);
+ ok(controlToChange, "the control to change should exist");
+ switch (controlToChange.localName) {
+ case "checkbox":
+ controlToChange.checked = !controlToChange.checked;
+ break;
+ case "textbox":
+ controlToChange.value = parseInt(controlToChange.value) + valueIncrement;
+ break;
+ case "menulist":
+ controlToChange.value = valueIncrement;
+ break;
+ }
+ controlChanged(controlToChange);
+ };
+}
+
+function test_locbar_suggestion_retention(suggestion, autocomplete) {
+ return function(win) {
+ let elem = win.document.getElementById(suggestion + "Suggestion");
+ ok(elem, "Suggest " + suggestion + " checkbox should exist.");
+ elem.click();
+
+ is(Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled"), autocomplete,
+ "browser.urlbar.autocomplete.enabled pref should be " + autocomplete);
+ };
+}
+
+const gPrefCache = new Map();
+
+function cache_preferences(win) {
+ let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+ for (let pref of prefs)
+ gPrefCache.set(pref.name, pref.value);
+}
+
+function reset_preferences(win) {
+ let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+ for (let pref of prefs)
+ pref.value = gPrefCache.get(pref.name);
+}
+
+function run_test_subset(subset) {
+ info("subset: " + Array.from(subset, x => x.name).join(",") + "\n");
+ SpecialPowers.pushPrefEnv({"set": [["browser.preferences.instantApply", true]]});
+
+ let tests = [cache_preferences, ...subset, reset_preferences];
+ for (let test of tests) {
+ add_task(runTestOnPrivacyPrefPane.bind(undefined, test));
+ }
+}
diff --git a/browser/components/preferences/in-content/tests/subdialog.xul b/browser/components/preferences/in-content/tests/subdialog.xul
new file mode 100644
index 000000000..48d297b73
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/subdialog.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<dialog id="subDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Sample sub-dialog" style="width: 32em; height: 5em;"
+ onload="document.getElementById('textbox').focus();"
+ ondialogaccept="acceptSubdialog();">
+ <script>
+ function acceptSubdialog() {
+ window.arguments[0].acceptCount++;
+ }
+ </script>
+
+ <description id="desc">A sample sub-dialog for testing</description>
+
+ <textbox id="textbox" value="Default text" />
+
+ <separator class="thin"/>
+
+ <button oncommand="close();" icon="close" label="Close" />
+
+</dialog>
diff --git a/browser/components/preferences/in-content/tests/subdialog2.xul b/browser/components/preferences/in-content/tests/subdialog2.xul
new file mode 100644
index 000000000..89803c250
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/subdialog2.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<dialog id="subDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Sample sub-dialog #2" style="width: 32em; height: 5em;"
+ onload="document.getElementById('textbox').focus();"
+ ondialogaccept="acceptSubdialog();">
+ <script>
+ function acceptSubdialog() {
+ window.arguments[0].acceptCount++;
+ }
+ </script>
+
+ <description id="desc">A sample sub-dialog for testing</description>
+
+ <textbox id="textbox" value="Default text" />
+
+ <separator class="thin"/>
+
+ <button oncommand="close();" icon="close" label="Close" />
+
+</dialog>