diff options
Diffstat (limited to 'browser/components/preferences')
39 files changed, 9609 insertions, 0 deletions
diff --git a/browser/components/preferences/advanced.js b/browser/components/preferences/advanced.js new file mode 100644 index 000000000..9fd7e9943 --- /dev/null +++ b/browser/components/preferences/advanced.js @@ -0,0 +1,726 @@ +// 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/ctypes.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/LoadContextInfo.jsm"); +Components.utils.import("resource://gre/modules/BrowserUtils.jsm"); + +var gAdvancedPane = { + _inited: false, + + /** + * Brings the appropriate tab to the front and initializes various bits of UI. + */ + init: function() + { + this._inited = true; + var advancedPrefs = document.getElementById("advancedPrefs"); + + var extraArgs = window.arguments[1]; + if (extraArgs && extraArgs["advancedTab"]){ + advancedPrefs.selectedTab = document.getElementById(extraArgs["advancedTab"]); + } else { + var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex"); + if (preference.value !== null) + advancedPrefs.selectedIndex = preference.value; + } + +#ifdef MOZ_UPDATER + this.updateReadPrefs(); +#endif + this.updateOfflineAppsPermissions(); + this.updateOfflineApps(); + + this.updateActualCacheSize(); + this.updateActualAppCacheSize(); + + this.updateHWADisplay(); + + this.updateUAODisplay(); + + // Notify observers that the UI is now ready + Services.obs.notifyObservers(window, "advanced-pane-loaded", null); + }, + + /** + * 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"); + 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 + * 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"); + return checkbox.checked ? (this._storedSpellCheck == 2 ? 2 : 1) : 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() + { +#ifdef XP_WIN + var fromPref = document.getElementById("layers.acceleration.enabled"); + var toPref = document.getElementById("gfx.direct2d.enabled"); + toPref.value = fromPref.value; +#endif + this.updateHWADisplay(); + }, + + updateHWADisplay: function() + { +#ifdef XP_LINUX + let HWA = document.getElementById("layers.acceleration.enabled"); + document.getElementById("forceHWAccel").disabled = !HWA.value; +#endif + }, + + updateUAODisplay: function() + { + let GUAO = Services.prefs.getCharPref("network.http.useragent.global_override", ""); + let overridden = (GUAO != ""); + document.getElementById("UACompatGroup").hidden = overridden; + document.getElementById("GUAOwarning").hidden = !overridden; + }, + + GUAOReset: function() + { + Services.prefs.clearUserPref("network.http.useragent.global_override"); + this.updateUAODisplay(); + }, + + // DATA CHOICES TAB + + /** + * opening links behind a modal dialog is poor form. Work around flawed text-link handling here. + */ + openTextLink: function(evt) { + let where = Services.prefs.getBoolPref("browser.preferences.instantApply") ? "tab" : "window"; + openUILinkIn(evt.target.getAttribute("href"), where); + evt.preventDefault(); + }, + + /** + * 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"); + } + }, + + // 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() + { + document.documentElement.openSubDialog("chrome://browser/content/preferences/connection.xul", + "", null); + }, + + // Retrieves the amount of space currently used by disk cache + updateActualCacheSize: function() + { + var sum = 0; + function updateUI(consumption) { + var actualSizeLabel = document.getElementById("actualDiskCacheSize"); + var sizeStrings = DownloadUtils.convertByteUnits(consumption); + var prefStrBundle = document.getElementById("bundlePreferences"); + var sizeStr = prefStrBundle.getFormattedString("actualDiskCacheSize", sizeStrings); + actualSizeLabel.value = sizeStr; + } + + Visitor.prototype = { + expected: 0, + sum: 0, + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsICacheStorageVisitor)) { + return this; + } + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + onCacheStorageInfo: function(num, consumption) + { + this.sum += consumption; + if (!--this.expected) + updateUI(this.sum); + } + }; + function Visitor(callbacksExpected) { + this.expected = callbacksExpected; + } + + var cacheService = + Components.classes["@mozilla.org/netwerk/cache-storage-service;1"] + .getService(Components.interfaces.nsICacheStorageService); + // non-anonymous + var storage1 = cacheService.diskCacheStorage(LoadContextInfo.default, false); + // anonymous + var storage2 = cacheService.diskCacheStorage(LoadContextInfo.anonymous, false); + + // expect 2 callbacks + var visitor = new Visitor(2); + storage1.asyncVisitStorage(visitor, false /* Do not walk entries */); + storage2.asyncVisitStorage(visitor, false /* Do not walk entries */); + }, + + // 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"); + var sizeStr = prefStrBundle.getFormattedString("actualAppCacheSize", sizeStrings); + actualSizeLabel.value = sizeStr; + } + }; + + var cacheService = + Components.classes["@mozilla.org/netwerk/cache-storage-service;1"] + .getService(Components.interfaces.nsICacheStorageService); + var storage = cacheService.appCacheStorage(LoadContextInfo.default, null); + try { + storage.asyncVisitStorage(visitor, false); + } catch(ex) { + // Service unavailable: user most likely crippled the cache. + } + }, + + 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 returns that + * value. + */ + readCacheSize: function() + { + var preference = document.getElementById("browser.cache.disk.capacity"); + return preference.value / 1024; + }, + + /** + * Converts the cache size as specified in UI (in MB) to KB and returns that + * value. + */ + writeCacheSize: function() + { + var cacheSize = document.getElementById("cacheSize"); + var intValue = parseInt(cacheSize.value, 10); + return isNaN(intValue) ? 0 : intValue * 1024; + }, + + /** + * Clears the cache. + */ + clearCache: function() + { + var cache = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"] + .getService(Components.interfaces.nsICacheStorageService); + try { + 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(); + }, + + updateOfflineAppsPermissions: function() + { + var permPref = document.getElementById("offline-apps.permissions"); + var allowPref = document.getElementById("offline-apps.allow_by_default"); + var notifyPref = document.getElementById("browser.offline-apps.notify"); + switch (permPref.value) { + case 0: allowPref.value = false; + notifyPref.value = false; + break; + case 1: allowPref.value = false; + notifyPref.value = true; + break; + case 2: allowPref.value = true; + notifyPref.value = true; + break; + default: console.error("Preference error: Invalid value ",permPref.value," for offline app permissions - resetting to default."); + permPref.value = 2; + allowPref.value = true; + notifyPref.value = true; + } + // Set state of "Exceptions" button accordingly. + var button = document.getElementById("offlineNotifyExceptions"); + button.disabled = !allowPref.value && !notifyPref.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") }; + document.documentElement.openWindow("Browser:Permissions", + "chrome://browser/content/preferences/permissions.xul", + "", params); + }, + + // XXX: duplicated in browser.js + _getOfflineAppUsage: function(perm, groups) + { + var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"]. + getService(Components.interfaces.nsIApplicationCacheService); + if (!groups) { + try { + groups = cacheService.getGroups(); + } catch(ex) { + // Cache disabled. + return 0; + } + } + + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + + var usage = 0; + for (var i = 0; i < groups.length; i++) { + var uri = ios.newURI(groups[i], null, null); + if (perm.matchesURI(uri, true)) { + var cache = cacheService.getActiveCache(groups[i]); + 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 cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"]. + getService(Components.interfaces.nsIApplicationCacheService); + + try { + var groups = cacheService.getGroups(); + + 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); + } + } + } catch(ex) { + // Cache service unavailable/errored, off-line app cache is disabled or 0 + // Do nothing, just leave the box blank. + } + }, + + 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 + * 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 + * app.update.auto + * - true if updates should be automatically downloaded and installed, + * possibly with a warning if incompatible extensions are installed (see + * app.update.mode); false if the user should be asked what he wants to do + * when an update is available + * app.update.mode + * - an integer: + * 0 do not warn if an update will disable extensions or themes + * 1 warn if an update will disable extensions or themes + * 2 warn if an update will disable extensions or themes *or* if the + * update is a major update + */ + +#ifdef MOZ_UPDATER + /** + * Selects the item of the radiogroup, and sets the warnIncompatible checkbox + * based on the pref values and locked states. + * + * UI state matrix for update preference conditions + * + * UI Components: Preferences + * Radiogroup i = app.update.enabled + * Warn before disabling extensions checkbox ii = app.update.auto + * iii = app.update.mode + * + * 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* + * iii 0/1/2 t/f false + * warnIncompatible i t f false + * i t *t* *true* + * i *f* t/f *true* + * ii t f false + * ii t *t* *true* + * ii *f* t/f *true* + * iii 0/1/2 f false + * iii 0/1/2 *t* *true* + */ + updateReadPrefs: function() + { + 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 for Desktop only + 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; + + var modePref = document.getElementById("app.update.mode"); + var warnIncompatible = document.getElementById("warnIncompatible"); + // the warnIncompatible checkbox value is set by readAddonWarn + warnIncompatible.disabled = radiogroup.disabled || modePref.locked || + !enabledPref.value || !autoPref.value; + }, + + /** + * Sets the pref values based on the selected item of the radiogroup, + * and sets the disabled state of the warnIncompatible checkbox accordingly. + */ + updateWritePrefs: function() + { + 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; + } + + var warnIncompatible = document.getElementById("warnIncompatible"); + var modePref = document.getElementById("app.update.mode"); + warnIncompatible.disabled = enabledPref.locked || !enabledPref.value || + autoPref.locked || !autoPref.value || + modePref.locked; + + }, + + /** + * Stores the value of the app.update.mode preference, which is a tristate + * integer preference. We store the value here so that we can properly + * restore the preference value if the UI reflecting the preference value + * is in a state which can represent either of two integer values (as + * opposed to only one possible value in the other UI state). + */ + _modePreference: -1, + + /** + * Reads the app.update.mode preference and converts its value into a + * true/false value for use in determining whether the "Warn me if this will + * disable extensions or themes" checkbox is checked. We also save the value + * of the preference so that the preference value can be properly restored if + * the user's preferences cannot adequately be expressed by a single checkbox. + * + * app.update.mode Checkbox State Meaning + * 0 Unchecked Do not warn + * 1 Checked Warn if there are incompatibilities + * 2 Checked Warn if there are incompatibilities, + * or the update is major. + */ + readAddonWarn: function() + { + var preference = document.getElementById("app.update.mode"); + var warn = preference.value != 0; + gAdvancedPane._modePreference = warn ? preference.value : 1; + return warn; + }, + + /** + * Converts the state of the "Warn me if this will disable extensions or + * themes" checkbox into the integer preference which represents it, + * returning that value. + */ + writeAddonWarn: function() + { + var warnIncompatible = document.getElementById("warnIncompatible"); + return !warnIncompatible.checked ? 0 : gAdvancedPane._modePreference; + }, + + /** + * Displays the history of installed updates. + */ + showUpdates: function() + { + var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"] + .createInstance(Components.interfaces.nsIUpdatePrompt); + prompter.showUpdateHistory(window); + }, +#endif + + // CERTIFICATES 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() + { + document.documentElement.openWindow("mozilla:certmanager", + "chrome://pippki/content/certManager.xul", + "", null); + }, + + /** + * Displays a dialog from which the user can manage his security devices. + */ + showSecurityDevices: function() + { + document.documentElement.openWindow("mozilla:devicemanager", + "chrome://pippki/content/device_manager.xul", + "", null); + } +}; diff --git a/browser/components/preferences/advanced.xul b/browser/components/preferences/advanced.xul new file mode 100644 index 000000000..cfc857aed --- /dev/null +++ b/browser/components/preferences/advanced.xul @@ -0,0 +1,448 @@ +<?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/. --> + +<!DOCTYPE overlay [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % advancedDTD SYSTEM "chrome://browser/locale/preferences/advanced.dtd"> +%advancedDTD; +<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd"> +%privacyDTD; +]> + +<overlay id="AdvancedPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <prefpane id="paneAdvanced" onpaneload="gAdvancedPane.init();"> + + <preferences id="advancedPreferences"> + <preference id="browser.preferences.advanced.selectedTabIndex" + name="browser.preferences.advanced.selectedTabIndex" + type="int"/> + + <!--XXX button prefs --> + + <!-- General tab --> + <preference id="accessibility.typeaheadfind" name="accessibility.typeaheadfind" type="bool"/> + + <preference id="general.autoScroll" name="general.autoScroll" type="bool"/> + <preference id="general.smoothScroll" name="general.smoothScroll" type="bool"/> + <preference id="layers.acceleration.enabled" name="layers.acceleration.enabled" type="bool" + onchange="gAdvancedPane.updateHardwareAcceleration()"/> + <preference id="layers.acceleration.force" name="layers.acceleration.force" type="bool"/> +#ifdef XP_WIN + <preference id="gfx.direct2d.enabled" name="gfx.direct2d.disabled" type="bool" inverted="true"/> +#endif + <preference id="layout.spellcheckDefault" name="layout.spellcheckDefault" type="int"/> + + <preference id="pref.general.compatmode" name="general.useragent.compatMode" type="int"/> + + <preference id="pref.general.captiveportal" name="network.captive-portal-service.enabled" type="bool"/> + + <!-- Network tab --> + <preference id="browser.cache.disk.capacity" name="browser.cache.disk.capacity" type="int"/> + + <preference id="browser.cache.disk.smart_size.enabled" + name="browser.cache.disk.smart_size.enabled" + inverted="true" + type="bool"/> + + <preference id="offline-apps.permissions" name="offline-apps.permissions" type="int" + onchange="gAdvancedPane.updateOfflineAppsPermissions()"/> + <preference id="browser.offline-apps.notify" name="browser.offline-apps.notify" type="bool"/> + <preference id="offline-apps.allow_by_default" name="offline-apps.allow_by_default" 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.mode" name="app.update.mode" type="int"/> + + <preference id="app.update.disable_button.showUpdateHistory" + name="app.update.disable_button.showUpdateHistory" + type="bool"/> +#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"/> + <preference id="security.OCSP.require" + name="security.OCSP.require" + type="bool"/> + + <!-- Pale Moon: smooth scrolling tab --> + <preference id="general.smoothScroll.lines" name="general.smoothScroll.lines" type="bool"/> + <preference id="general.smoothScroll.lines.durationMinMS" name="general.smoothScroll.lines.durationMinMS" type="int"/> + <preference id="general.smoothScroll.lines.durationMaxMS" name="general.smoothScroll.lines.durationMaxMS" type="int"/> + <preference id="general.smoothScroll.pages" name="general.smoothScroll.pages" type="bool"/> + <preference id="general.smoothScroll.pages.durationMinMS" name="general.smoothScroll.pages.durationMinMS" type="int"/> + <preference id="general.smoothScroll.pages.durationMaxMS" name="general.smoothScroll.pages.durationMaxMS" type="int"/> + <preference id="general.smoothScroll.mouseWheel" name="general.smoothScroll.mouseWheel" type="bool"/> + <preference id="general.smoothScroll.mouseWheel.durationMinMS" name="general.smoothScroll.mouseWheel.durationMinMS" type="int"/> + <preference id="general.smoothScroll.mouseWheel.durationMaxMS" name="general.smoothScroll.mouseWheel.durationMaxMS" type="int"/> + <preference id="general.smoothScroll.scrollbars" name="general.smoothScroll.scrollbars" type="bool"/> + <preference id="general.smoothScroll.scrollbars.durationMinMS" name="general.smoothScroll.scrollbars.durationMinMS" type="int"/> + <preference id="general.smoothScroll.scrollbars.durationMaxMS" name="general.smoothScroll.scrollbars.durationMaxMS" type="int"/> + + <preference id="mousewheel.default.delta_multiplier_y" name="mousewheel.default.delta_multiplier_y" type="int"/> + </preferences> + + <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> + + <script type="application/javascript" src="chrome://browser/content/preferences/advanced.js"/> + + <tabbox id="advancedPrefs" flex="1" + onselect="gAdvancedPane.tabSelectionChanged();"> + + <tabs id="tabsElement"> + <tab id="generalTab" label="&generalTab.label;" helpTopic="prefs-advanced-general"/> + <tab id="networkTab" label="&networkTab.label;" helpTopic="prefs-advanced-network"/> + <tab id="updateTab" label="&updateTab.label;" helpTopic="prefs-advanced-update"/> + <tab id="encryptionTab" label="&certificateTab.label;" helpTopic="prefs-advanced-encryption"/> + <tab id="scrollparamTab" label="&scrollparamTab.label;" helpTopic="prefs-advanced-scrollparams"/> + </tabs> + + <tabpanels flex="1"> + + <!-- General --> + <tabpanel id="generalPanel" orient="vertical"> + + <!-- Accessibility --> + <groupbox id="accessibilityGroup" align="start"> + <caption label="&accessibility.label;"/> + + <checkbox id="searchStartTyping" + label="&searchStartTyping.label;" + accesskey="&searchStartTyping.accesskey;" + preference="accessibility.typeaheadfind"/> + </groupbox> + + <!-- Browsing --> + <groupbox id="browsingGroup" align="start"> + <caption label="&browsing.label;"/> + + <checkbox id="useAutoScroll" + label="&useAutoScroll.label;" + accesskey="&useAutoScroll.accesskey;" + preference="general.autoScroll"/> + <checkbox id="checkSpelling" + label="&checkSpelling.label;" + accesskey="&checkSpelling.accesskey;" + onsyncfrompreference="return gAdvancedPane.readCheckSpelling();" + onsynctopreference="return gAdvancedPane.writeCheckSpelling();" + preference="layout.spellcheckDefault"/> + </groupbox> + + <!-- Hardware Acceleration --> + <groupbox id="browsingGroup" align="start"> + <caption label="&HWAccel.label;"/> + <label>&restartRequired.label;</label> + <checkbox id="allowHWAccel" + label="&allowHWAccel.label;" + accesskey="&allowHWAccel.accesskey;" + preference="layers.acceleration.enabled"/> +#ifdef XP_LINUX + <checkbox id="forceHWAccel" class="indent" + label="&forceHWAccel.label;" + preference="layers.acceleration.force"/> +#endif + </groupbox> + + <!-- User Agent compatibility --> + <hbox id="GUAOwarning" align="center" hidden="true"> + <label style="color:red;" id="UAWarning">&UAWarning.label;</label> + <button label="&UAWarning.reset;" oncommand="gAdvancedPane.GUAOReset();" /> + </hbox> + <groupbox id="UACompatGroup" orient="vertical"> + <caption label="&UACompatGroup.label;"/> + <hbox align="center"> + <label id="UACompat" control="UACompat-menu">&UACompat.label;</label> + <menulist id="UACompat-menu" preference="pref.general.compatmode" sizetopopup="always"> + <menupopup> + <menuitem label="&UACompat.Native;" value="0" /> + <menuitem label="&UACompat.Gecko;" value="1" /> + <menuitem label="&UACompat.Firefox;" value="2" /> + </menupopup> + </menulist> + </hbox> + </groupbox> + + <!-- Captive portal detection --> + <groupbox id="captivePortalGroup" orient="vertical"> + <caption label="&captivePortalGroup.label;"/> + <checkbox id="captivePortalDetect" + label="&captivePortalDetect.label;" + preference="pref.general.captiveportal"/> + </groupbox> + + </tabpanel> + + <!-- Network --> + <tabpanel id="networkPanel" orient="vertical"> + + <!-- Connection --> + <groupbox id="connectionGroup"> + <caption label="&connection.label;"/> + + <hbox align="center"> + <description flex="1" control="connectionSettings">&connectionDesc.label;</description> + <button id="connectionSettings" icon="network" label="&connectionSettings.label;" + accesskey="&connectionSettings.accesskey;" + oncommand="gAdvancedPane.showConnections();"/> + </hbox> + </groupbox> + + <!-- Cache --> + <groupbox id="cacheGroup"> + <caption label="&httpCache.label;"/> + + <hbox align="center"> + <label id="actualDiskCacheSize" flex="1"/> + <button id="clearCacheButton" icon="clear" + label="&clearCacheNow.label;" accesskey="&clearCacheNow.accesskey;" + oncommand="gAdvancedPane.clearCache();"/> + </hbox> + <checkbox preference="browser.cache.disk.smart_size.enabled" + id="allowSmartSize" flex="1" + onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();" + label="&overrideSmartCacheSize.label;" + accesskey="&overrideSmartCacheSize.accesskey;"/> + <hbox align="center" class="indent"> + <label id="useCacheBefore" control="cacheSize" + accesskey="&limitCacheSizeBefore.accesskey;" + value="&limitCacheSizeBefore.label;"/> + <textbox id="cacheSize" type="number" size="4" max="1024" + preference="browser.cache.disk.capacity" + onsyncfrompreference="return gAdvancedPane.readCacheSize();" + onsynctopreference="return gAdvancedPane.writeCacheSize();" + aria-labelledby="useCacheBefore cacheSize useCacheAfter"/> + <label id="useCacheAfter" flex="1">&limitCacheSizeAfter.label;</label> + </hbox> + </groupbox> + + <!-- Offline apps --> + <groupbox id="offlineGroup"> + <caption label="&offlineStorage2.label;"/> + + <hbox align="center"> + <label id="actualAppCacheSize" flex="1"/> + <button id="clearOfflineAppCacheButton" icon="clear" + label="&clearOfflineAppCacheNow.label;" accesskey="&clearOfflineAppCacheNow.accesskey;" + oncommand="gAdvancedPane.clearOfflineAppCache();"/> + </hbox> + <label id="offlineAppsPermsLabel">&offlineAppsPermissions.label;</label> + <hbox align="center"> + <menulist id="offlineAppsPerms-menu" preference="offline-apps.permissions" sizetopopup="always"> + <menupopup> + <menuitem label="&offlineAppsPermissions.Allow;" value="2" /> + <menuitem label="&offlineAppsPermissions.Ask;" value="1" /> + <menuitem label="&offlineAppsPermissions.Deny;" value="0" /> + </menupopup> + </menulist> + <spacer flex="1"/> + <button id="offlineNotifyExceptions" + label="&offlineNotifyExceptions.label;" + accesskey="&offlineNotifyExceptions.accesskey;" + oncommand="gAdvancedPane.showOfflineExceptions();"/> + </hbox> + <hbox> + <vbox flex="1"> + <label id="offlineAppsListLabel">&offlineAppsList2.label;</label> + <listbox id="offlineAppsList" + style="height: &offlineAppsList.height;;" + flex="1" + aria-labelledby="offlineAppsListLabel" + onselect="gAdvancedPane.offlineAppSelected(event);"> + </listbox> + </vbox> + <vbox pack="end"> + <button id="offlineAppsListRemove" + disabled="true" + label="&offlineAppsListRemove.label;" + accesskey="&offlineAppsListRemove.accesskey;" + oncommand="gAdvancedPane.removeOfflineApp();"/> + </vbox> + </hbox> + </groupbox> + </tabpanel> + + <!-- Update --> + <tabpanel id="updatePanel" orient="vertical"> +#ifdef MOZ_UPDATER + <groupbox id="updateApp"> + <caption label="&updateApp.label;"/> + <radiogroup id="updateRadioGroup" + oncommand="gAdvancedPane.updateWritePrefs();"> + <radio id="autoDesktop" + value="auto" + label="&updateAuto1.label;" + accesskey="&updateAuto1.accesskey;"/> + <hbox class="indent"> + <checkbox id="warnIncompatible" + label="&updateAutoAddonWarn.label;" + accesskey="&updateAutoAddonWarn.accesskey;" + preference="app.update.mode" + onsyncfrompreference="return gAdvancedPane.readAddonWarn();" + onsynctopreference="return gAdvancedPane.writeAddonWarn();"/> + </hbox> + <radio value="checkOnly" + label="&updateCheck.label;" + accesskey="&updateCheck.accesskey;"/> + <radio value="manual" + label="&updateManual.label;" + accesskey="&updateManual.accesskey;"/> + </radiogroup> + + <hbox> + <button id="showUpdateHistory" + label="&updateHistory.label;" + accesskey="&updateHistory.accesskey;" + preference="app.update.disable_button.showUpdateHistory" + oncommand="gAdvancedPane.showUpdates();"/> + </hbox> + </groupbox> +#endif + <groupbox id="updateOthers"> + <caption label="&updateOthers.label;"/> + <checkbox id="enableSearchUpdate" + label="&enableSearchUpdate.label;" + accesskey="&enableSearchUpdate.accesskey;" + preference="browser.search.update"/> + </groupbox> + </tabpanel> + + <!-- Certificates --> + <tabpanel id="encryptionPanel" orient="vertical"> + + <!-- + 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. + --> + + <groupbox> + <caption label="&certGroup.label;"/> + <description id="CertSelectionDesc" control="certSelection">&certSelection.description;</description> + <radiogroup id="certSelection" orient="horizontal" 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> + <groupbox> + <caption label="&ocspGroup.label;"/> + <checkbox id="enableOCSP" + label="&enableOCSP.label;" + accesskey="&enableOCSP.accesskey;" + onsyncfrompreference="return gAdvancedPane.readEnableOCSP();" + onsynctopreference="return gAdvancedPane.writeEnableOCSP();" + preference="security.OCSP.enabled"/> + <checkbox id="requireOCSP" + label="&requireOCSP.label;" + accesskey="&requireOCSP.accesskey;" + preference="security.OCSP.require"/> + </groupbox> + + <separator/> + + <hbox> + <button id="viewCertificatesButton" + label="&viewCerts.label;" accesskey="&viewCerts.accesskey;" + oncommand="gAdvancedPane.showCertificates();" + preference="security.disable_button.openCertManager"/> + <button id="viewSecurityDevicesButton" + label="&viewSecurityDevices.label;" accesskey="&viewSecurityDevices.accesskey;" + oncommand="gAdvancedPane.showSecurityDevices();" + preference="security.disable_button.openDeviceManager"/> + </hbox> + </tabpanel> + + <!-- Pale Moon: Scrolling tab --> + <tabpanel id="scrollparamTab" orient="vertical"> + + <checkbox id="useSmoothScrolling" + label="&useSmoothScrolling.label;" + accesskey="&useSmoothScrolling.accesskey;" + preference="general.smoothScroll"/> + + <label>&smoothscroll.explain.label;</label> + + <groupbox> + <caption label="&smoothscroll.params.label;"/> + + <checkbox label="&smoothscroll.mousewheel.label;" preference="general.smoothScroll.mouseWheel"/> + <hbox align="center" class="indent"> + <label value="&smoothscroll.mousewheel.duration;"/> + <textbox type="number" size="3" max="500" + preference="general.smoothScroll.mouseWheel.durationMinMS"/> + <label>&smoothscroll.to;</label> + <textbox type="number" size="4" max="2000" + preference="general.smoothScroll.mouseWheel.durationMaxMS"/> + <label flex="1">ms.</label> + </hbox> + + <checkbox label="&smoothscroll.arrowkeys.label;" preference="general.smoothScroll.lines"/> + <hbox align="center" class="indent"> + <label value="&smoothscroll.arrowkeys.duration;"/> + <textbox type="number" size="3" max="500" + preference="general.smoothScroll.lines.durationMinMS"/> + <label>&smoothscroll.to;</label> + <textbox type="number" size="4" max="2000" + preference="general.smoothScroll.lines.durationMaxMS"/> + <label flex="1">ms.</label> + </hbox> + + <checkbox label="&smoothscroll.pagekeys.label;" preference="general.smoothScroll.pages"/> + <hbox align="center" class="indent"> + <label value="&smoothscroll.pagekeys.duration;"/> + <textbox type="number" size="3" max="500" + preference="general.smoothScroll.pages.durationMinMS"/> + <label>&smoothscroll.to;</label> + <textbox type="number" size="4" max="2000" + preference="general.smoothScroll.pages.durationMaxMS"/> + <label flex="1">ms.</label> + </hbox> + + <checkbox label="&smoothscroll.scrollbar.label;" preference="general.smoothScroll.scrollbars"/> + <hbox align="center" class="indent"> + <label value="&smoothscroll.scrollbar.duration;"/> + <textbox type="number" size="3" max="500" + preference="general.smoothScroll.scrollbars.durationMinMS"/> + <label>&smoothscroll.to;</label> + <textbox type="number" size="4" max="2000" + preference="general.smoothScroll.scrollbars.durationMaxMS"/> + <label flex="1">ms.</label> + </hbox> + + <hbox align="center"> + <label value="&smoothscroll.overall.yspeed.label;"/> + <textbox type="number" size="3" min="1" max="999" + preference="mousewheel.default.delta_multiplier_y"/> + <label flex="1">%.</label> + </hbox> + </groupbox> + </tabpanel> + <!-- end Smooth scrolling tab --> + + </tabpanels> + </tabbox> + </prefpane> + +</overlay> diff --git a/browser/components/preferences/applicationManager.js b/browser/components/preferences/applicationManager.js new file mode 100644 index 000000000..43558c156 --- /dev/null +++ b/browser/components/preferences/applicationManager.js @@ -0,0 +1,97 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +var gAppManagerDialog = { + _removed: [], + + init: function() { + this.handlerInfo = window.arguments[0]; + + var bundle = document.getElementById("appManagerBundle"); + var contentText; + if (this.handlerInfo.type == TYPE_MAYBE_FEED) + contentText = bundle.getString("handleWebFeeds"); + else { + var description = gApplicationsPane._describeType(this.handlerInfo); + var key = + (this.handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) ? "handleFile" + : "handleProtocol"; + contentText = bundle.getFormattedString(key, [description]); + } + contentText = bundle.getFormattedString("descriptionApplications", [contentText]); + document.getElementById("appDescription").textContent = contentText; + + var list = document.getElementById("appList"); + var apps = this.handlerInfo.possibleApplicationHandlers.enumerate(); + while (apps.hasMoreElements()) { + let app = apps.getNext(); + if (!gApplicationsPane.isValidHandlerApp(app)) + continue; + + app.QueryInterface(Ci.nsIHandlerApp); + var item = list.appendItem(app.name); + item.setAttribute("image", gApplicationsPane._getIconURLForHandlerApp(app)); + item.className = "listitem-iconic"; + item.app = app; + } + + list.selectedIndex = 0; + }, + + onOK: function() { + if (!this._removed.length) { + // return early to avoid calling the |store| method. + return; + } + + for (var i = 0; i < this._removed.length; ++i) + this.handlerInfo.removePossibleApplicationHandler(this._removed[i]); + + this.handlerInfo.store(); + }, + + onCancel: function() { + // do nothing + }, + + remove: function() { + var list = document.getElementById("appList"); + this._removed.push(list.selectedItem.app); + var index = list.selectedIndex; + list.removeItemAt(index); + if (list.getRowCount() == 0) { + // The list is now empty, make the bottom part disappear + document.getElementById("appDetails").hidden = true; + } + else { + // Select the item at the same index, if we removed the last + // item of the list, select the previous item + if (index == list.getRowCount()) + --index; + list.selectedIndex = index; + } + }, + + onSelect: function() { + var list = document.getElementById("appList"); + if (!list.selectedItem) { + document.getElementById("remove").disabled = true; + return; + } + document.getElementById("remove").disabled = false; + var app = list.selectedItem.app; + var address = ""; + if (app instanceof Ci.nsILocalHandlerApp) + address = app.executable.path; + else if (app instanceof Ci.nsIWebHandlerApp) + address = app.uriTemplate; + else if (app instanceof Ci.nsIWebContentHandlerInfo) + address = app.uri; + document.getElementById("appLocation").value = address; + var bundle = document.getElementById("appManagerBundle"); + var appType = app instanceof Ci.nsILocalHandlerApp ? "descriptionLocalApp" + : "descriptionWebApp"; + document.getElementById("appType").value = bundle.getString(appType); + } +}; diff --git a/browser/components/preferences/applicationManager.xul b/browser/components/preferences/applicationManager.xul new file mode 100644 index 000000000..b5605c290 --- /dev/null +++ b/browser/components/preferences/applicationManager.xul @@ -0,0 +1,59 @@ +<?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/"?> + +<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/applicationManager.dtd"> + +<dialog id="appManager" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + onload="gAppManagerDialog.init();" + ondialogaccept="gAppManagerDialog.onOK();" + ondialogcancel="gAppManagerDialog.onCancel();" + title="&appManager.title;" + style="&appManager.style;" + persist="screenX screenY"> + + <script type="application/javascript" + src="chrome://browser/content/utilityOverlay.js"/> + <script type="application/javascript" + src="chrome://browser/content/preferences/applicationManager.js"/> + <script type="application/javascript" + src="chrome://browser/content/preferences/applications.js"/> + + <commandset id="appManagerCommandSet"> + <command id="cmd_remove" + oncommand="gAppManagerDialog.remove();" + disabled="true"/> + </commandset> + + <keyset id="appManagerKeyset"> + <key id="delete" keycode="VK_DELETE" command="cmd_remove"/> + </keyset> + + <stringbundleset id="appManagerBundleset"> + <stringbundle id="appManagerBundle" + src="chrome://browser/locale/preferences/applicationManager.properties"/> + </stringbundleset> + + <description id="appDescription"/> + <separator class="thin"/> + <hbox flex="1"> + <listbox id="appList" onselect="gAppManagerDialog.onSelect();" flex="1"/> + <vbox> + <button id="remove" + label="&remove.label;" + accesskey="&remove.accesskey;" + command="cmd_remove"/> + <spacer flex="1"/> + </vbox> + </hbox> + <vbox id="appDetails"> + <separator class="thin"/> + <label id="appType"/> + <textbox id="appLocation" readonly="true" class="plain"/> + </vbox> +</dialog> diff --git a/browser/components/preferences/applications.js b/browser/components/preferences/applications.js new file mode 100644 index 000000000..3751ee732 --- /dev/null +++ b/browser/components/preferences/applications.js @@ -0,0 +1,1876 @@ +// 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/. + +//****************************************************************************// +// Constants & Enumeration Values + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +Components.utils.import('resource://gre/modules/Services.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 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; + +/* +#ifdef MOZ_WIDGET_GTK +*/ +const ICON_URL_APP = "moz-icon://dummy.exe?size=16"; +/* +#else +*/ +const ICON_URL_APP = "chrome://browser/skin/preferences/application.png"; +/* +#endif +*/ + +// 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) { +#ifdef XP_WIN + if (file instanceof Ci.nsILocalFileWin) { + try { + return file.getVersionInfoField("FileDescription"); + } catch (e) {} + } +#endif + 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; + else + 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("Goanna-Content-Viewers", + this.type, + false); + }, + + enablePluginType: function() { + var disabledPluginTypes = this._getDisabledPluginTypes(); + + var type = this.type; + disabledPluginTypes = disabledPluginTypes.filter(function(v) v != type); + + this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES, + disabledPluginTypes.join(",")); + + // Update the category manager so existing browser windows update. + this._categoryMgr. + addCategoryEntry("Goanna-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: +#ifdef HAVE_SHELL_SERVICE + getShellService(), +#else + null, +#endif + + + //**************************************************************************// + // 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 each (let webHandler in webHandlers) + this._possibleApplicationHandlers.appendElement(webHandler, false); + + return this._possibleApplicationHandlers; + }, + + __defaultApplicationHandler: undefined, + get _defaultApplicationHandler() { + if (typeof this.__defaultApplicationHandler != "undefined") + return this.__defaultApplicationHandler; + + var defaultFeedReader = null; +#ifdef HAVE_SHELL_SERVICE + try { + defaultFeedReader = this._shellSvc.defaultFeedReader; + } + catch(ex) { + // no default reader or _shellSvc is null + } +#endif + + 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() { +#ifdef HAVE_SHELL_SERVICE + try { + if (this._shellSvc.defaultFeedReader) + return true; + } + catch(ex) { + // no default reader or _shellSvc is null + } +#endif + + 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 each (let app in 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); + } +}; + +//****************************************************************************// +// 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() { + // 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); + + + // 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._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 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 each (let visibleType in 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]); + else + 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]); + } + }, + + _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) { + 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 +#ifdef XP_WIN +#expand aExecutable.leafName != "__MOZ_APP_NAME__.exe"; +#else +#expand aExecutable.leafName != "__MOZ_APP_NAME__-bin"; +#endif + }, + + /** + * 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) { + var 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)) { + var 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. +#ifdef XP_WIN + // On Windows, selecting an application to open another application + // would be meaningless so we special case executables. + var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService) + .getTypeFromExtension("exe"); + if (handlerInfo.type != executableType) +#endif + { + let menuItem = document.createElement("menuitem"); + menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)"); + 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.setAttribute("oncommand", "gApplicationsPane.manageApp(event)"); + 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(function(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]; + + document.documentElement.openSubDialog("chrome://browser/content/preferences/applicationManager.xul", + "", handlerInfo); + + // 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)); + } + }, + + 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); + +#ifdef XP_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; + + window.openDialog("chrome://global/content/appPicker.xul", null, + "chrome,modal,centerscreen,titlebar,dialog=yes", + params); + + if (this.isValidHandlerApp(params.handlerApp)) { + handlerApp = params.handlerApp; + + // Add the app to the type's list of possible handlers. + handlerInfo.addPossibleApplicationHandler(handlerApp); + } + + chooseAppCallback(handlerApp); +#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); +#endif + }, + + // 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); + break; + + // 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 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 handler'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/applications.xul b/browser/components/preferences/applications.xul new file mode 100644 index 000000000..2e6fa549e --- /dev/null +++ b/browser/components/preferences/applications.xul @@ -0,0 +1,99 @@ +<?xml version="1.0"?> + +<!-- -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- --> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE overlay [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + <!ENTITY % applicationsDTD SYSTEM "chrome://browser/locale/preferences/applications.dtd"> + %brandDTD; + %applicationsDTD; +]> + +<overlay id="ApplicationsPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <prefpane id="paneApplications" + onpaneload="gApplicationsPane.init();" + flex="1" + helpTopic="prefs-applications"> + + <preferences id="feedsPreferences"> + <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> + + <script type="application/javascript" src="chrome://browser/content/preferences/applications.js"/> + + <keyset> + <key key="&focusSearch1.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/> + <key key="&focusSearch2.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/> + </keyset> + + <hbox> + <textbox id="filter" flex="1" + type="search" + placeholder="&filter.emptytext;" + aria-controls="handlersView" + oncommand="gApplicationsPane.filter();"/> + </hbox> + + <separator class="thin"/> + + <richlistbox id="handlersView" orient="vertical" persist="lastSelectedType" + preference="pref.downloads.disable_button.edit_actions" + onselect="gApplicationsPane.onSelectionChanged();"> + <listheader equalsize="always" style="border: 0; padding: 0; -moz-appearance: none;"> + <treecol id="typeColumn" label="&typeColumn.label;" value="type" + accesskey="&typeColumn.accesskey;" persist="sortDirection" + flex="1" onclick="gApplicationsPane.sort(event);" + sortDirection="ascending"/> + <treecol id="actionColumn" label="&actionColumn2.label;" value="action" + accesskey="&actionColumn2.accesskey;" persist="sortDirection" + flex="1" onclick="gApplicationsPane.sort(event);"/> + </listheader> + </richlistbox> + </prefpane> +</overlay> diff --git a/browser/components/preferences/colors.xul b/browser/components/preferences/colors.xul new file mode 100644 index 000000000..caf8c8c0e --- /dev/null +++ b/browser/components/preferences/colors.xul @@ -0,0 +1,114 @@ +<?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/" type="text/css"?> + +<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/colors.dtd" > + +<prefwindow id="ColorsDialog" type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&colorsDialog.title;" + dlgbuttons="accept,cancel,help" + ondialoghelp="openPrefsHelp()" + style="width: &window.width; !important;"> + + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + <prefpane id="ColorsDialogPane" + helpTopic="prefs-fonts-and-colors"> + + <preferences> + <preference id="browser.display.document_color_use" name="browser.display.document_color_use" type="int"/> + <preference id="browser.anchor_color" name="browser.anchor_color" type="string"/> + <preference id="browser.visited_color" name="browser.visited_color" type="string"/> + <preference id="browser.underline_anchors" name="browser.underline_anchors" type="bool"/> + <preference id="browser.display.foreground_color" name="browser.display.foreground_color" type="string"/> + <preference id="browser.display.background_color" name="browser.display.background_color" type="string"/> + <preference id="browser.display.use_system_colors" name="browser.display.use_system_colors" type="bool"/> + <preference id="browser.display.prefers_color_scheme" name="browser.display.prefers_color_scheme" type="int"/> + </preferences> + + <hbox> + <groupbox flex="1"> + <caption label="&color;"/> + <hbox align="center"> + <label value="&textColor.label;" accesskey="&textColor.accesskey;" control="foregroundtextmenu"/> + <spacer flex="1"/> + <colorpicker type="button" id="foregroundtextmenu" palettename="standard" + preference="browser.display.foreground_color"/> + </hbox> + <hbox align="center" style="margin-top: 5px"> + <label value="&backgroundColor.label;" accesskey="&backgroundColor.accesskey;" control="backgroundmenu"/> + <spacer flex="1"/> + <colorpicker type="button" id="backgroundmenu" palettename="standard" + preference="browser.display.background_color"/> + </hbox> + <separator class="thin"/> + <hbox align="center"> + <checkbox id="browserUseSystemColors" label="&useSystemColors.label;" accesskey="&useSystemColors.accesskey;" + preference="browser.display.use_system_colors"/> + </hbox> + </groupbox> + + <groupbox flex="1"> + <caption label="&links;"/> + <hbox align="center"> + <label value="&linkColor.label;" accesskey="&linkColor.accesskey;" control="unvisitedlinkmenu"/> + <spacer flex="1"/> + <colorpicker type="button" id="unvisitedlinkmenu" palettename="standard" + preference="browser.anchor_color"/> + </hbox> + <hbox align="center" style="margin-top: 5px"> + <label value="&visitedLinkColor.label;" accesskey="&visitedLinkColor.accesskey;" control="visitedlinkmenu"/> + <spacer flex="1"/> + <colorpicker type="button" id="visitedlinkmenu" palettename="standard" + preference="browser.visited_color"/> + </hbox> + <separator class="thin"/> + <hbox align="center"> + <checkbox id="browserUnderlineAnchors" label="&underlineLinks.label;" accesskey="&underlineLinks.accesskey;" + preference="browser.underline_anchors"/> + </hbox> + </groupbox> + </hbox> +#ifdef XP_WIN + <vbox align="start"> +#else + <vbox> +#endif + <label accesskey="&overridePageColors.accesskey;" + control="useDocumentColors">&overridePageColors.label;</label> + <menulist id="useDocumentColors" preference="browser.display.document_color_use"> + <menupopup> + <menuitem label="&overridePageColors.always.label;" + value="2" id="documentColorAlways"/> + <menuitem label="&overridePageColors.auto.label;" + value="0" id="documentColorAutomatic"/> + <menuitem label="&overridePageColors.never.label;" + value="1" id="documentColorNever"/> + </menupopup> + </menulist> + </vbox> + + <groupbox> + <caption label="&prefersColorScheme.caption;"/> + <label control="prefersColorSchemeSelection">&prefersColorScheme.label;</label> + <radiogroup id="prefersColorSchemeSelection" + preference="browser.display.prefers_color_scheme"> + <radio value="1" + label="&prefersColorSchemeLight.label;" + accesskey="&prefersColorSchemeLight.accesskey;"/> + <radio value="2" + label="&prefersColorSchemeDark.label;" + accesskey="&prefersColorSchemeDark.accesskey;"/> + <radio value="0" + label="&prefersColorSchemeDisabled.label;" + accesskey="&prefersColorSchemeDisabled.accesskey;"/> + </radiogroup> + <description>&prefersColorSchemeWarning;</description> + </groupbox> + + </prefpane> +</prefwindow> diff --git a/browser/components/preferences/connection.js b/browser/components/preferences/connection.js new file mode 100644 index 000000000..f94819d3f --- /dev/null +++ b/browser/components/preferences/connection.js @@ -0,0 +1,199 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +var gConnectionsDialog = { + beforeAccept: function () + { + var proxyTypePref = document.getElementById("network.proxy.type"); + if (proxyTypePref.value == 2) { + this.doAutoconfigURLFixup(); + return true; + } + + if (proxyTypePref.value != 1) + return true; + + var httpProxyURLPref = document.getElementById("network.proxy.http"); + var httpProxyPortPref = document.getElementById("network.proxy.http_port"); + var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings"); + if (shareProxiesPref.value) { + var proxyPrefs = ["ssl", "ftp", "socks"]; + for (var i = 0; i < proxyPrefs.length; ++i) { + var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]); + var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port"); + var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]); + var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port"); + backupServerURLPref.value = proxyServerURLPref.value; + backupPortPref.value = proxyPortPref.value; + proxyServerURLPref.value = httpProxyURLPref.value; + proxyPortPref.value = httpProxyPortPref.value; + } + } + + this.sanitizeNoProxiesPref(); + + return true; + }, + + checkForSystemProxy: function () + { + if ("@mozilla.org/system-proxy-settings;1" in Components.classes) + document.getElementById("systemPref").removeAttribute("hidden"); + }, + + proxyTypeChanged: function () + { + var proxyTypePref = document.getElementById("network.proxy.type"); + + // Update http + var httpProxyURLPref = document.getElementById("network.proxy.http"); + httpProxyURLPref.disabled = proxyTypePref.value != 1; + var httpProxyPortPref = document.getElementById("network.proxy.http_port"); + httpProxyPortPref.disabled = proxyTypePref.value != 1; + + // Now update the other protocols + this.updateProtocolPrefs(); + + var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings"); + shareProxiesPref.disabled = proxyTypePref.value != 1; + + var autologinProxyPref = document.getElementById("signon.autologin.proxy"); + autologinProxyPref.disabled = proxyTypePref.value == 0; + + var noProxiesPref = document.getElementById("network.proxy.no_proxies_on"); + noProxiesPref.disabled = proxyTypePref.value == 0; + + var autoconfigURLPref = document.getElementById("network.proxy.autoconfig_url"); + autoconfigURLPref.disabled = proxyTypePref.value != 2; + + this.updateReloadButton(); + }, + + updateDNSPref: function () + { + var socksVersionPref = document.getElementById("network.proxy.socks_version"); + var socksDNSPref = document.getElementById("network.proxy.socks_remote_dns"); + var proxyTypePref = document.getElementById("network.proxy.type"); + var isDefinitelySocks4 = !socksVersionPref.disabled && socksVersionPref.value == 4; + socksDNSPref.disabled = (isDefinitelySocks4 || proxyTypePref.value == 0); + return undefined; + }, + + updateReloadButton: function () + { + // Disable the "Reload PAC" button if the selected proxy type is not PAC or + // if the current value of the PAC textbox does not match the value stored + // in prefs. Likewise, disable the reload button if PAC is not configured + // in prefs. + + var typedURL = document.getElementById("networkProxyAutoconfigURL").value; + var proxyTypeCur = document.getElementById("network.proxy.type").value; + + var prefs = + Components.classes["@mozilla.org/preferences-service;1"]. + getService(Components.interfaces.nsIPrefBranch); + var pacURL = prefs.getCharPref("network.proxy.autoconfig_url"); + var proxyType = prefs.getIntPref("network.proxy.type"); + + var disableReloadPref = + document.getElementById("pref.advanced.proxies.disable_button.reload"); + disableReloadPref.disabled = + (proxyTypeCur != 2 || proxyType != 2 || typedURL != pacURL); + }, + + readProxyType: function () + { + this.proxyTypeChanged(); + return undefined; + }, + + updateProtocolPrefs: function () + { + var proxyTypePref = document.getElementById("network.proxy.type"); + var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings"); + var proxyPrefs = ["ssl", "ftp", "socks"]; + for (var i = 0; i < proxyPrefs.length; ++i) { + var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]); + var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port"); + + // Restore previous per-proxy custom settings, if present. + if (!shareProxiesPref.value) { + var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]); + var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port"); + if (backupServerURLPref.hasUserValue) { + proxyServerURLPref.value = backupServerURLPref.value; + backupServerURLPref.reset(); + } + if (backupPortPref.hasUserValue) { + proxyPortPref.value = backupPortPref.value; + backupPortPref.reset(); + } + } + + proxyServerURLPref.updateElements(); + proxyPortPref.updateElements(); + proxyServerURLPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value; + proxyPortPref.disabled = proxyServerURLPref.disabled; + } + var socksVersionPref = document.getElementById("network.proxy.socks_version"); + socksVersionPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value; + this.updateDNSPref(); + return undefined; + }, + + readProxyProtocolPref: function (aProtocol, aIsPort) + { + var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings"); + if (shareProxiesPref.value) { + var pref = document.getElementById("network.proxy.http" + (aIsPort ? "_port" : "")); + return pref.value; + } + + var backupPref = document.getElementById("network.proxy.backup." + aProtocol + (aIsPort ? "_port" : "")); + return backupPref.hasUserValue ? backupPref.value : undefined; + }, + + reloadPAC: function () + { + Components.classes["@mozilla.org/network/protocol-proxy-service;1"]. + getService().reloadPAC(); + }, + + doAutoconfigURLFixup: function () + { + var autoURL = document.getElementById("networkProxyAutoconfigURL"); + var autoURLPref = document.getElementById("network.proxy.autoconfig_url"); + var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"] + .getService(Components.interfaces.nsIURIFixup); + try { + autoURLPref.value = autoURL.value = URIFixup.createFixupURI(autoURL.value, 0).spec; + } catch(ex) {} + }, + + sanitizeNoProxiesPref: function() + { + var noProxiesPref = document.getElementById("network.proxy.no_proxies_on"); + // replace substrings of ; and \n with commas if they're neither immediately + // preceded nor followed by a valid separator character + noProxiesPref.value = noProxiesPref.value.replace(/([^, \n;])[;\n]+(?![,\n;])/g, '$1,'); + // replace any remaining ; and \n since some may follow commas, etc. + noProxiesPref.value = noProxiesPref.value.replace(/[;\n]/g, ''); + }, + + readHTTPProxyServer: function () + { + var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings"); + if (shareProxiesPref.value) + this.updateProtocolPrefs(); + return undefined; + }, + + readHTTPProxyPort: function () + { + var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings"); + if (shareProxiesPref.value) + this.updateProtocolPrefs(); + return undefined; + } +}; diff --git a/browser/components/preferences/connection.xul b/browser/components/preferences/connection.xul new file mode 100644 index 000000000..e21168652 --- /dev/null +++ b/browser/components/preferences/connection.xul @@ -0,0 +1,159 @@ +<?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/. --> + +<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/connection.dtd"> + +<?xml-stylesheet href="chrome://global/skin/"?> + +<prefwindow id="ConnectionsDialog" type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&connectionsDialog.title;" + dlgbuttons="accept,cancel,help" + onbeforeaccept="return gConnectionsDialog.beforeAccept();" + onload="gConnectionsDialog.checkForSystemProxy();" + ondialoghelp="openPrefsHelp()" + style="width: &window.width; !important;"> + + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + + <prefpane id="ConnectionsDialogPane" + helpTopic="prefs-connection-settings"> + + <preferences> + <preference id="network.proxy.type" name="network.proxy.type" type="int" + onchange="gConnectionsDialog.proxyTypeChanged();"/> + <preference id="network.proxy.http" name="network.proxy.http" type="string"/> + <preference id="network.proxy.http_port" name="network.proxy.http_port" type="int"/> + <preference id="network.proxy.ftp" name="network.proxy.ftp" type="string"/> + <preference id="network.proxy.ftp_port" name="network.proxy.ftp_port" type="int"/> + <preference id="network.proxy.ssl" name="network.proxy.ssl" type="string"/> + <preference id="network.proxy.ssl_port" name="network.proxy.ssl_port" type="int"/> + <preference id="network.proxy.socks" name="network.proxy.socks" type="string"/> + <preference id="network.proxy.socks_port" name="network.proxy.socks_port" type="int"/> + <preference id="network.proxy.socks_version" name="network.proxy.socks_version" type="int" + onchange="gConnectionsDialog.updateDNSPref();"/> + <preference id="network.proxy.socks_remote_dns" name="network.proxy.socks_remote_dns" type="bool"/> + <preference id="network.proxy.no_proxies_on" name="network.proxy.no_proxies_on" type="string"/> + <preference id="network.proxy.autoconfig_url" name="network.proxy.autoconfig_url" type="string"/> + <preference id="network.proxy.share_proxy_settings" name="network.proxy.share_proxy_settings" type="bool"/> + <preference id="signon.autologin.proxy" name="signon.autologin.proxy" type="bool"/> + <preference id="pref.advanced.proxies.disable_button.reload" + name="pref.advanced.proxies.disable_button.reload" type="bool"/> + <preference id="network.proxy.backup.ftp" name="network.proxy.backup.ftp" type="string"/> + <preference id="network.proxy.backup.ftp_port" name="network.proxy.backup.ftp_port" type="int"/> + <preference id="network.proxy.backup.ssl" name="network.proxy.backup.ssl" type="string"/> + <preference id="network.proxy.backup.ssl_port" name="network.proxy.backup.ssl_port" type="int"/> + <preference id="network.proxy.backup.socks" name="network.proxy.backup.socks" type="string"/> + <preference id="network.proxy.backup.socks_port" name="network.proxy.backup.socks_port" type="int"/> + </preferences> + + <script type="application/javascript" src="chrome://browser/content/preferences/connection.js"/> + + <stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/> + + <groupbox> + <caption label="&proxyTitle.label;"/> + + <radiogroup id="networkProxyType" preference="network.proxy.type" + onsyncfrompreference="return gConnectionsDialog.readProxyType();"> + <radio value="0" label="&noProxyTypeRadio.label;" accesskey="&noProxyTypeRadio.accesskey;"/> + <radio value="4" label="&WPADTypeRadio.label;" accesskey="&WPADTypeRadio.accesskey;"/> + <radio value="5" label="&systemTypeRadio.label;" accesskey="&systemTypeRadio.accesskey;" id="systemPref" hidden="true"/> + <radio value="1" label="&manualTypeRadio.label;" accesskey="&manualTypeRadio.accesskey;"/> + <grid class="indent" flex="1"> + <columns> + <column/> + <column flex="1"/> + </columns> + <rows> + <row align="center"> + <hbox pack="end"> + <label value="&http.label;" accesskey="&http.accesskey;" control="networkProxyHTTP"/> + </hbox> + <hbox align="center"> + <textbox id="networkProxyHTTP" flex="1" + preference="network.proxy.http" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyServer();"/> + <label value="&port.label;" accesskey="&HTTPport.accesskey;" control="networkProxyHTTP_Port"/> + <textbox id="networkProxyHTTP_Port" type="number" max="65535" size="5" + preference="network.proxy.http_port" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyPort();"/> + </hbox> + </row> + <row> + <hbox/> + <hbox> + <checkbox id="shareAllProxies" label="&shareproxy.label;" accesskey="&shareproxy.accesskey;" + preference="network.proxy.share_proxy_settings" + onsyncfrompreference="return gConnectionsDialog.updateProtocolPrefs();"/> + </hbox> + </row> + <row align="center"> + <hbox pack="end"> + <label value="&ssl.label;" accesskey="&ssl.accesskey;" control="networkProxySSL"/> + </hbox> + <hbox align="center"> + <textbox id="networkProxySSL" flex="1" preference="network.proxy.ssl" + onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', false);"/> + <label value="&port.label;" accesskey="&SSLport.accesskey;" control="networkProxySSL_Port"/> + <textbox id="networkProxySSL_Port" type="number" max="65535" size="5" preference="network.proxy.ssl_port" + onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', true);"/> + </hbox> + </row> + <row align="center"> + <hbox pack="end"> + <label value="&ftp.label;" accesskey="&ftp.accesskey;" control="networkProxyFTP"/> + </hbox> + <hbox align="center"> + <textbox id="networkProxyFTP" flex="1" preference="network.proxy.ftp" + onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', false);"/> + <label value="&port.label;" accesskey="&FTPport.accesskey;" control="networkProxyFTP_Port"/> + <textbox id="networkProxyFTP_Port" type="number" max="65535" size="5" preference="network.proxy.ftp_port" + onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', true);"/> + </hbox> + </row> + <row align="center"> + <hbox pack="end"> + <label value="&socks.label;" accesskey="&socks.accesskey;" control="networkProxySOCKS"/> + </hbox> + <hbox align="center"> + <textbox id="networkProxySOCKS" flex="1" preference="network.proxy.socks" + onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', false);"/> + <label value="&port.label;" accesskey="&SOCKSport.accesskey;" control="networkProxySOCKS_Port"/> + <textbox id="networkProxySOCKS_Port" type="number" max="65535" size="5" preference="network.proxy.socks_port" + onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', true);"/> + </hbox> + </row> + <row> + <spacer/> + <radiogroup id="networkProxySOCKSVersion" orient="horizontal" + preference="network.proxy.socks_version"> + <radio id="networkProxySOCKSVersion4" value="4" label="&socks4.label;" accesskey="&socks4.accesskey;"/> + <radio id="networkProxySOCKSVersion5" value="5" label="&socks5.label;" accesskey="&socks5.accesskey;"/> + </radiogroup> + </row> + </rows> + </grid> + <radio value="2" label="&autoTypeRadio.label;" accesskey="&autoTypeRadio.accesskey;"/> + <hbox class="indent" flex="1" align="center"> + <textbox id="networkProxyAutoconfigURL" flex="1" preference="network.proxy.autoconfig_url" + oninput="gConnectionsDialog.updateReloadButton();"/> + <button id="autoReload" icon="refresh" + label="&reload.label;" accesskey="&reload.accesskey;" + oncommand="gConnectionsDialog.reloadPAC();" + preference="pref.advanced.proxies.disable_button.reload"/> + </hbox> + </radiogroup> + <separator class="thin"/> + <label value="&noproxy.label;" accesskey="&noproxy.accesskey;" control="networkProxyNone"/> + <textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/> + <label value="&noproxyExplain.label;" control="networkProxyNone"/> + <checkbox id="autologinProxy" preference="signon.autologin.proxy" + label="&autologinproxy.label;" accesskey="&autologinproxy.accesskey;" + tooltiptext="&autologinproxy.tooltip;"/> + <checkbox id="networkProxySOCKSRemoteDNS" preference="network.proxy.socks_remote_dns" + label="&socksRemoteDNS.label;" accesskey="&socksRemoteDNS.accesskey;"/> + </groupbox> + </prefpane> +</prefwindow> diff --git a/browser/components/preferences/content.js b/browser/components/preferences/content.js new file mode 100644 index 000000000..62a675c92 --- /dev/null +++ b/browser/components/preferences/content.js @@ -0,0 +1,186 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +var gContentPane = { + + /** + * Initializes the fonts dropdowns displayed in this pane. + */ + init: function () + { + this._rebuildFonts(); + var menulist = document.getElementById("defaultFont"); + if (menulist.selectedIndex == -1) { + menulist.insertItemAt(0, "", "", ""); + menulist.selectedIndex = 0; + } + }, + + // 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; + }, + + /** + * Utility function to enable/disable the checkboxes for MSE options depending + * on the value of media.mediasource.enabled. + */ + updateMSE: function () + { + var checkboxMSEMP4 = document.getElementById('videoMSEMP4'); + var checkboxMSEWebM = document.getElementById('videoMSEWebM'); + var preference = document.getElementById('media.mediasource.enabled'); + checkboxMSEMP4.disabled = preference.value != true; + checkboxMSEWebM.disabled = preference.value != true; + }, + + // BEGIN UI CODE + + /* + * Preferences: + * + * dom.disable_open_during_load + * - true if popups are blocked by default, false otherwise + */ + + // 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"); + document.documentElement.openWindow("Browser:Permissions", + "chrome://browser/content/preferences/permissions.xul", + "", params); + }, + + + // FONTS + + /** + * Populates the default font list in UI. + */ + _rebuildFonts: function () + { + 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 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 }]; + var preferences = document.getElementById("contentPreferences"); + 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 () + { + document.documentElement.openSubDialog("chrome://browser/content/preferences/fonts.xul", + "", null); + }, + + /** + * Displays the colors dialog, where default web page/link/etc. colors can be + * configured. + */ + configureColors: function () + { + document.documentElement.openSubDialog("chrome://browser/content/preferences/colors.xul", + "", null); + }, + + // LANGUAGES + + /** + * Shows a dialog in which the preferred language for web content may be set. + */ + showLanguages: function () + { + document.documentElement.openSubDialog("chrome://browser/content/preferences/languages.xul", + "", null); + } +}; diff --git a/browser/components/preferences/content.xul b/browser/components/preferences/content.xul new file mode 100644 index 000000000..21f9e5d81 --- /dev/null +++ b/browser/components/preferences/content.xul @@ -0,0 +1,209 @@ +<?xml version="1.0"?> + +<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- --> +<!-- 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/. --> + +<!DOCTYPE overlay [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + <!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd"> + %brandDTD; + %contentDTD; +]> + +<overlay id="ContentPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <prefpane id="paneContent" + onpaneload="gContentPane.init();" + helpTopic="prefs-content"> + + <preferences id="contentPreferences"> + <!--XXX buttons prefs --> + + <!-- POPUPS, IMAGES --> + <preference id="dom.disable_open_during_load" name="dom.disable_open_during_load" type="bool"/> + <preference id="permissions.default.image" name="permissions.default.image" type="int"/> + + <!-- FONTS --> + <preference id="font.language.group" + name="font.language.group" + type="wstring" + onchange="gContentPane._rebuildFonts();"/> + + <!-- JavaScript --> + <preference id="javascript.options.wasm" name="javascript.options.wasm" type="bool"/> + + + <!-- VIDEO --> + <preference id="media.mediasource.enabled" name="media.mediasource.enabled" type="bool"/> + <preference id="media.mediasource.mp4.enabled" name="media.mediasource.mp4.enabled" type="bool"/> + <preference id="media.mediasource.webm.enabled" name="media.mediasource.webm.enabled" type="bool"/> + + <!-- Media formats --> + <preference id="media.av1.enabled" name="media.av1.enabled" type="bool"/> + <preference id="media.flac.enabled" name="media.flac.enabled" type="bool"/> + <preference id="media.mp4.enabled" name="media.mp4.enabled" type="bool"/> + <preference id="media.ogg.enabled" name="media.ogg.enabled" type="bool"/> + <preference id="media.opus.enabled" name="media.opus.enabled" type="bool"/> + <preference id="media.webm.enabled" name="media.webm.enabled" type="bool"/> + + </preferences> + + <script type="application/javascript" src="chrome://mozapps/content/preferences/fontbuilder.js"/> + <script type="application/javascript" src="chrome://browser/content/preferences/content.js"/> + + <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> + + <!-- various checkboxes, font-fu --> + <groupbox id="miscGroup"> + <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> + <button id="popupPolicyButton" label="&popupExceptions.label;" + oncommand="gContentPane.showPopupExceptions();" + accesskey="&popupExceptions.accesskey;"/> + </row> + <row id="enableImagesRow"> + <hbox align="center"> + <label id="loadImages" control="loadImages-menu">&loadImages.label;</label> + <menulist id="loadImages-menu" preference="permissions.default.image" sizetopopup="always"> + <menupopup> + <menuitem label="&loadImages.always;" value="1" /> + <menuitem label="&loadImages.never;" value="2" /> + <menuitem label="&loadImages.no3rdparty;" value="3" /> + </menupopup> + </menulist> + </hbox> + </row> + </rows> + </grid> + </groupbox> + + <!-- Fonts and Colors --> + <groupbox id="fontsGroup"> + <caption label="&fontsAndColors.label;"/> + + <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" flex="1"/> + <label control="defaultFontSize" accesskey="&defaultSize.accesskey;">&defaultSize.label;</label> + <menulist id="defaultFontSize"> + <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;" + oncommand="gContentPane.configureFonts();"/> + </row> + <row id="colorsRow"> + <hbox/> + <button id="colors" icon="select-color" + label="&colors.label;" + accesskey="&colors.accesskey;" + oncommand="gContentPane.configureColors();"/> + </row> + </rows> + </grid> + </groupbox> + + <!-- Languages --> + <groupbox id="languagesGroup"> + <caption label="&languages.label;"/> + + <hbox id="languagesBox" align="center"> + <description flex="1" control="chooseLanguage">&chooseLanguage.label;</description> + <button id="chooseLanguage" + label="&chooseButton.label;" + accesskey="&chooseButton.accesskey;" + oncommand="gContentPane.showLanguages();"/> + </hbox> + </groupbox> + + <!-- Javascript --> + <groupbox id="jsOptionsGroup"> + <caption label="&jsOptions.label;"/> + + <checkbox id="jsOptionsWasm" preference="javascript.options.wasm" + label="&jsOptionsWasm.label;" accesskey="&jsOptionsWasm.accesskey;"/> + </groupbox> + + <!-- Video --> + <groupbox id="videoGroup"> + <caption label="&video.label;"/> + + <checkbox id="videoMSE" preference="media.mediasource.enabled" + label="&videoMSE.label;" accesskey="&videoMSE.accesskey;" + onsyncfrompreference="gContentPane.updateMSE();"/> + <checkbox class="indent" id="videoMSEMP4" preference="media.mediasource.mp4.enabled" + label="&videoMSEMP4.label;" accesskey="&videoMSEMP4.accesskey;"/> + <checkbox class="indent" id="videoMSEWebM" preference="media.mediasource.webm.enabled" + label="&videoMSEWebM.label;" accesskey="&videoMSEWebM.accesskey;"/> + </groupbox> + + <!-- Media formats --> + <groupbox id="mediaSupport" align="start"> + <caption label="&mediaSupport.label;"/> + <hbox align="center"> + <label id="allowEnable" value="&allowEnable.label;"/> +#ifdef MOZ_FMP4 + <checkbox id="enableMP4" label="&enableMP4.label;" preference="media.mp4.enabled"/> +#endif + <checkbox id="enableWebM" label="&enableWebM.label;" preference="media.webm.enabled"/> +#ifdef MOZ_AV1 + <checkbox id="enableAV1" label="&enableAV1.label;" preference="media.av1.enabled"/> +#endif + <checkbox id="enableOGG" label="&enableOGG.label;" preference="media.ogg.enabled"/> + <checkbox id="enableOPUS" label="&enableOPUS.label;" preference="media.opus.enabled"/> + <checkbox id="enableFLAC" label="&enableFLAC.label;" preference="media.flac.enabled"/> + </hbox> + </groupbox> + + </prefpane> + +</overlay> diff --git a/browser/components/preferences/cookies.js b/browser/components/preferences/cookies.js new file mode 100644 index 000000000..dbc2b3ef6 --- /dev/null +++ b/browser/components/preferences/cookies.js @@ -0,0 +1,943 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +const nsICookie = Components.interfaces.nsICookie; + +Components.utils.import("resource://gre/modules/PluralForm.jsm"); + +var gCookiesWindow = { + _cm : Components.classes["@mozilla.org/cookiemanager;1"] + .getService(Components.interfaces.nsICookieManager), + _ds : Components.classes["@mozilla.org/intl/scriptabledateformat;1"] + .getService(Components.interfaces.nsIScriptableDateFormat), + _hosts : {}, + _hostOrder : [], + _tree : null, + _bundle : null, + + init: function() { + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + os.addObserver(this, "cookie-changed", false); + os.addObserver(this, "perm-changed", false); + + this._bundle = document.getElementById("bundlePreferences"); + this._tree = document.getElementById("cookiesList"); + + let removeAllCookies = document.getElementById("removeAllCookies"); + removeAllCookies.setAttribute("accesskey", this._bundle.getString("removeAllCookies.accesskey")); + let removeSelectedCookies = document.getElementById("removeSelectedCookies"); + removeSelectedCookies.setAttribute("accesskey", this._bundle.getString("removeSelectedCookies.accesskey")); + + this._populateList(true); + + document.getElementById("filter").focus(); + }, + + uninit: function() { + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + os.removeObserver(this, "cookie-changed"); + os.removeObserver(this, "perm-changed"); + }, + + _populateList: function(aInitialLoad) { + this._loadCookies(); + this._tree.view = this._view; + if (aInitialLoad) + this.sort("rawHost"); + if (this._view.rowCount > 0) + this._tree.view.selection.select(0); + + if (aInitialLoad) { + if ("arguments" in window && + window.arguments[0] && + window.arguments[0].filterString) + this.setFilter(window.arguments[0].filterString); + } + else { + if (document.getElementById("filter").value != "") + this.filter(); + } + + this._updateRemoveAllButton(); + + this._saveState(); + }, + + _cookieEquals: function(aCookieA, aCookieB, aStrippedHost) { + return aCookieA.rawHost == aStrippedHost && + aCookieA.name == aCookieB.name && + aCookieA.path == aCookieB.path && + ChromeUtils.isOriginAttributesEqual(aCookieA.originAttributes, + aCookieB.originAttributes); + }, + + observe: function(aCookie, aTopic, aData) { + if (aTopic != "cookie-changed") + return; + + if (aCookie instanceof Components.interfaces.nsICookie) { + var strippedHost = this._makeStrippedHost(aCookie.host); + if (aData == "changed") + this._handleCookieChanged(aCookie, strippedHost); + else if (aData == "added") + this._handleCookieAdded(aCookie, strippedHost); + } + else if (aData == "cleared") { + this._hosts = {}; + this._hostOrder = []; + + var oldRowCount = this._view._rowCount; + this._view._rowCount = 0; + this._tree.treeBoxObject.rowCountChanged(0, -oldRowCount); + this._view.selection.clearSelection(); + this._updateRemoveAllButton(); + } + else if (aData == "reload") { + // first, clear any existing entries + this.observe(aCookie, aTopic, "cleared"); + + // then, reload the list + this._populateList(false); + } + + // We don't yet handle aData == "deleted" - it's a less common case + // and is rather complicated as selection tracking is difficult + }, + + _handleCookieChanged: function(changedCookie, strippedHost) { + var rowIndex = 0; + var cookieItem = null; + if (!this._view._filtered) { + for (var i = 0; i < this._hostOrder.length; ++i) { // (var host in this._hosts) { + ++rowIndex; + var hostItem = this._hosts[this._hostOrder[i]]; // var hostItem = this._hosts[host]; + if (this._hostOrder[i] == strippedHost) { // host == strippedHost) { + // Host matches, look for the cookie within this Host collection + // and update its data + for (var j = 0; j < hostItem.cookies.length; ++j) { + ++rowIndex; + var currCookie = hostItem.cookies[j]; + if (this._cookieEquals(currCookie, changedCookie, strippedHost)) { + currCookie.value = changedCookie.value; + currCookie.isSecure = changedCookie.isSecure; + currCookie.isDomain = changedCookie.isDomain; + currCookie.expires = changedCookie.expires; + cookieItem = currCookie; + break; + } + } + } + else if (hostItem.open) + rowIndex += hostItem.cookies.length; + } + } + else { + // Just walk the filter list to find the item. It doesn't matter that + // we don't update the main Host collection when we do this, because + // when the filter is reset the Host collection is rebuilt anyway. + for (rowIndex = 0; rowIndex < this._view._filterSet.length; ++rowIndex) { + currCookie = this._view._filterSet[rowIndex]; + if (this._cookieEquals(currCookie, changedCookie, strippedHost)) { + currCookie.value = changedCookie.value; + currCookie.isSecure = changedCookie.isSecure; + currCookie.isDomain = changedCookie.isDomain; + currCookie.expires = changedCookie.expires; + cookieItem = currCookie; + break; + } + } + } + + // Make sure the tree display is up to date... + this._tree.treeBoxObject.invalidateRow(rowIndex); + // ... and if the cookie is selected, update the displayed metadata too + if (cookieItem != null && this._view.selection.currentIndex == rowIndex) + this._updateCookieData(cookieItem); + }, + + _handleCookieAdded: function(changedCookie, strippedHost) { + var rowCountImpact = 0; + var addedHost = { value: 0 }; + this._addCookie(strippedHost, changedCookie, addedHost); + if (!this._view._filtered) { + // The Host collection for this cookie already exists, and it's not open, + // so don't increment the rowCountImpact becaues the user is not going to + // see the additional rows as they're hidden. + if (addedHost.value || this._hosts[strippedHost].open) + ++rowCountImpact; + } + else { + // We're in search mode, and the cookie being added matches + // the search condition, so add it to the list. + var c = this._makeCookieObject(strippedHost, changedCookie); + if (this._cookieMatchesFilter(c)) { + this._view._filterSet.push(this._makeCookieObject(strippedHost, changedCookie)); + ++rowCountImpact; + } + } + // Now update the tree display at the end (we could/should re run the sort + // if any to get the position correct.) + var oldRowCount = this._rowCount; + this._view._rowCount += rowCountImpact; + this._tree.treeBoxObject.rowCountChanged(oldRowCount - 1, rowCountImpact); + + this._updateRemoveAllButton(); + }, + + _view: { + _filtered : false, + _filterSet : [], + _filterValue: "", + _rowCount : 0, + _cacheValid : 0, + _cacheItems : [], + get rowCount() { + return this._rowCount; + }, + + _getItemAtIndex: function(aIndex) { + if (this._filtered) + return this._filterSet[aIndex]; + + var start = 0; + var count = 0, hostIndex = 0; + + var cacheIndex = Math.min(this._cacheValid, aIndex); + if (cacheIndex > 0) { + var cacheItem = this._cacheItems[cacheIndex]; + start = cacheItem['start']; + count = hostIndex = cacheItem['count']; + } + + for (var i = start; i < gCookiesWindow._hostOrder.length; ++i) { // var host in gCookiesWindow._hosts) { + var currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host]; + if (!currHost) continue; + if (count == aIndex) + return currHost; + hostIndex = count; + + var cacheEntry = { 'start' : i, 'count' : count }; + var cacheStart = count; + + if (currHost.open) { + if (count < aIndex && aIndex <= (count + currHost.cookies.length)) { + // We are looking for an entry within this host's children, + // enumerate them looking for the index. + ++count; + for (var i = 0; i < currHost.cookies.length; ++i) { + if (count == aIndex) { + var cookie = currHost.cookies[i]; + cookie.parentIndex = hostIndex; + return cookie; + } + ++count; + } + } + else { + // A host entry was open, but we weren't looking for an index + // within that host entry's children, so skip forward over the + // entry's children. We need to add one to increment for the + // host value too. + count += currHost.cookies.length + 1; + } + } + else + ++count; + + for (var j = cacheStart; j < count; j++) + this._cacheItems[j] = cacheEntry; + this._cacheValid = count - 1; + } + return null; + }, + + _removeItemAtIndex: function(aIndex, aCount) { + var removeCount = aCount === undefined ? 1 : aCount; + if (this._filtered) { + // remove the cookies from the unfiltered set so that they + // don't reappear when the filter is changed. See bug 410863. + for (var i = aIndex; i < aIndex + removeCount; ++i) { + var item = this._filterSet[i]; + var parent = gCookiesWindow._hosts[item.rawHost]; + for (var j = 0; j < parent.cookies.length; ++j) { + if (item == parent.cookies[j]) { + parent.cookies.splice(j, 1); + break; + } + } + } + this._filterSet.splice(aIndex, removeCount); + return; + } + + var item = this._getItemAtIndex(aIndex); + if (!item) return; + this._invalidateCache(aIndex - 1); + if (item.container) { + gCookiesWindow._hosts[item.rawHost] = null; + } else { + var parent = this._getItemAtIndex(item.parentIndex); + for (var i = 0; i < parent.cookies.length; ++i) { + var cookie = parent.cookies[i]; + if (item.rawHost == cookie.rawHost && + item.name == cookie.name && + item.path == cookie.path && + ChromeUtils.isOriginAttributesEqual(item.originAttributes, + cookie.originAttributes)) { + parent.cookies.splice(i, removeCount); + } + } + } + }, + + _invalidateCache: function(aIndex) { + this._cacheValid = Math.min(this._cacheValid, aIndex); + }, + + getCellText: function(aIndex, aColumn) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) + return ""; + if (aColumn.id == "domainCol") + return item.rawHost; + else if (aColumn.id == "nameCol") + return item.name; + } + else { + if (aColumn.id == "domainCol") + return this._filterSet[aIndex].rawHost; + else if (aColumn.id == "nameCol") + return this._filterSet[aIndex].name; + } + return ""; + }, + + _selection: null, + get selection () { return this._selection; }, + set selection (val) { this._selection = val; return val; }, + getRowProperties: function(aIndex) { return ""; }, + getCellProperties: function(aIndex, aColumn) { return ""; }, + getColumnProperties: function(aColumn) { return ""; }, + isContainer: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) return false; + return item.container; + } + return false; + }, + isContainerOpen: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) return false; + return item.open; + } + return false; + }, + isContainerEmpty: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) return false; + return item.cookies.length == 0; + } + return false; + }, + isSeparator: function(aIndex) { return false; }, + isSorted: function(aIndex) { return false; }, + canDrop: function(aIndex, aOrientation) { return false; }, + drop: function(aIndex, aOrientation) {}, + getParentIndex: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + // If an item has no parent index (i.e. it is at the top level) this + // function MUST return -1 otherwise we will go into an infinite loop. + // Containers are always top level items in the cookies tree, so make + // sure to return the appropriate value here. + if (!item || item.container) return -1; + return item.parentIndex; + } + return -1; + }, + hasNextSibling: function(aParentIndex, aIndex) { + if (!this._filtered) { + // |aParentIndex| appears to be bogus, but we can get the real + // parent index by getting the entry for |aIndex| and reading the + // parentIndex field. + // The index of the last item in this host collection is the + // index of the parent + the size of the host collection, and + // aIndex has a next sibling if it is less than this value. + var item = this._getItemAtIndex(aIndex); + if (item) { + if (item.container) { + for (var i = aIndex + 1; i < this.rowCount; ++i) { + var subsequent = this._getItemAtIndex(i); + if (subsequent.container) + return true; + } + return false; + } + else { + var parent = this._getItemAtIndex(item.parentIndex); + if (parent && parent.container) + return aIndex < item.parentIndex + parent.cookies.length; + } + } + } + return aIndex < this.rowCount - 1; + }, + hasPreviousSibling: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) return false; + var parent = this._getItemAtIndex(item.parentIndex); + if (parent && parent.container) + return aIndex > item.parentIndex + 1; + } + return aIndex > 0; + }, + getLevel: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) return 0; + return item.level; + } + return 0; + }, + getImageSrc: function(aIndex, aColumn) {}, + getProgressMode: function(aIndex, aColumn) {}, + getCellValue: function(aIndex, aColumn) {}, + setTree: function(aTree) {}, + toggleOpenState: function(aIndex) { + if (!this._filtered) { + var item = this._getItemAtIndex(aIndex); + if (!item) return; + this._invalidateCache(aIndex); + var multiplier = item.open ? -1 : 1; + var delta = multiplier * item.cookies.length; + this._rowCount += delta; + item.open = !item.open; + gCookiesWindow._tree.treeBoxObject.rowCountChanged(aIndex + 1, delta); + gCookiesWindow._tree.treeBoxObject.invalidateRow(aIndex); + } + }, + cycleHeader: function(aColumn) {}, + selectionChanged: function() {}, + cycleCell: function(aIndex, aColumn) {}, + isEditable: function(aIndex, aColumn) { + return false; + }, + isSelectable: function(aIndex, aColumn) { + return false; + }, + setCellValue: function(aIndex, aColumn, aValue) {}, + setCellText: function(aIndex, aColumn, aValue) {}, + performAction: function(aAction) {}, + performActionOnRow: function(aAction, aIndex) {}, + performActionOnCell: function(aAction, aindex, aColumn) {} + }, + + _makeStrippedHost: function(aHost) { + var formattedHost = aHost.charAt(0) == "." ? aHost.substring(1, aHost.length) : aHost; + return formattedHost.substring(0, 4) == "www." ? formattedHost.substring(4, formattedHost.length) : formattedHost; + }, + + _addCookie: function(aStrippedHost, aCookie, aHostCount) { + if (!(aStrippedHost in this._hosts) || !this._hosts[aStrippedHost]) { + this._hosts[aStrippedHost] = { cookies : [], + rawHost : aStrippedHost, + level : 0, + open : false, + container : true }; + this._hostOrder.push(aStrippedHost); + ++aHostCount.value; + } + + var c = this._makeCookieObject(aStrippedHost, aCookie); + this._hosts[aStrippedHost].cookies.push(c); + }, + + _makeCookieObject: function(aStrippedHost, aCookie) { + var host = aCookie.host; + var formattedHost = host.charAt(0) == "." ? host.substring(1, host.length) : host; + var c = { name : aCookie.name, + value : aCookie.value, + isDomain : aCookie.isDomain, + host : aCookie.host, + rawHost : aStrippedHost, + path : aCookie.path, + isSecure : aCookie.isSecure, + expires : aCookie.expires, + level : 1, + container : false, + originAttributes: aCookie.originAttributes }; + return c; + }, + + _loadCookies: function() { + var e = this._cm.enumerator; + var hostCount = { value: 0 }; + this._hosts = {}; + this._hostOrder = []; + while (e.hasMoreElements()) { + var cookie = e.getNext(); + if (cookie && cookie instanceof Components.interfaces.nsICookie) { + var strippedHost = this._makeStrippedHost(cookie.host); + this._addCookie(strippedHost, cookie, hostCount); + } + else + break; + } + this._view._rowCount = hostCount.value; + }, + + formatExpiresString: function(aExpires) { + if (aExpires) { + var date = new Date(1000 * aExpires); + return this._ds.FormatDateTime("", this._ds.dateFormatLong, + this._ds.timeFormatSeconds, + date.getFullYear(), + date.getMonth() + 1, + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds()); + } + return this._bundle.getString("expireAtEndOfSession"); + }, + + _updateCookieData: function(aItem) { + var seln = this._view.selection; + var ids = ["name", "value", "host", "path", "isSecure", "expires"]; + var properties; + + if (aItem && !aItem.container && seln.count > 0) { + properties = { name: aItem.name, value: aItem.value, host: aItem.host, + path: aItem.path, expires: this.formatExpiresString(aItem.expires), + isDomain: aItem.isDomain ? this._bundle.getString("domainColon") + : this._bundle.getString("hostColon"), + isSecure: aItem.isSecure ? this._bundle.getString("forSecureOnly") + : this._bundle.getString("forAnyConnection") }; + for (var i = 0; i < ids.length; ++i) + document.getElementById(ids[i]).disabled = false; + } + else { + var noneSelected = this._bundle.getString("noCookieSelected"); + properties = { name: noneSelected, value: noneSelected, host: noneSelected, + path: noneSelected, expires: noneSelected, + isSecure: noneSelected }; + for (i = 0; i < ids.length; ++i) + document.getElementById(ids[i]).disabled = true; + } + for (var property in properties) + document.getElementById(property).value = properties[property]; + }, + + onCookieSelected: function() { + var properties, item; + var seln = this._tree.view.selection; + var hasRows = this._tree.view.rowCount > 0; + var hasSelection = seln.count > 0; + if (!this._view._filtered) + item = this._view._getItemAtIndex(seln.currentIndex); + else + item = this._view._filterSet[seln.currentIndex]; + + this._updateCookieData(item); + + var rangeCount = seln.getRangeCount(); + var selectedCookieCount = 0; + for (var i = 0; i < rangeCount; ++i) { + var min = {}; var max = {}; + seln.getRangeAt(i, min, max); + for (var j = min.value; j <= max.value; ++j) { + item = this._view._getItemAtIndex(j); + if (!item) continue; + if (item.container && !item.open) + selectedCookieCount += item.cookies.length; + else if (!item.container) + ++selectedCookieCount; + } + } + var item = this._view._getItemAtIndex(seln.currentIndex); + if (item && seln.count == 1 && item.container && item.open) + selectedCookieCount += 2; + + let buttonLabel = this._bundle.getString("removeSelectedCookies.label"); + let removeSelectedCookies = document.getElementById("removeSelectedCookies"); + removeSelectedCookies.label = PluralForm.get(selectedCookieCount, buttonLabel) + .replace("#1", selectedCookieCount); + + removeSelectedCookies.disabled = !hasRows || !hasSelection; + }, + + performDeletion: function(deleteItems) { + var psvc = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + var blockFutureCookies = false; + if (psvc.prefHasUserValue("network.cookie.blockFutureCookies")) + blockFutureCookies = psvc.getBoolPref("network.cookie.blockFutureCookies"); + for (var i = 0; i < deleteItems.length; ++i) { + var item = deleteItems[i]; + this._cm.remove(item.host, item.name, item.path, + blockFutureCookies, item.originAttributes); + } + }, + + deleteCookie: function() { + // Selection Notes + // - Selection always moves to *NEXT* adjacent item unless item + // is last child at a given level in which case it moves to *PREVIOUS* + // item + // + // Selection Cases (Somewhat Complicated) + // + // 1) Single cookie selected, host has single child + // v cnn.com + // //// cnn.com ///////////// goksdjf@ //// + // > atwola.com + // + // Before SelectedIndex: 1 Before RowCount: 3 + // After SelectedIndex: 0 After RowCount: 1 + // + // 2) Host selected, host open + // v goats.com //////////////////////////// + // goats.com sldkkfjl + // goat.scom flksj133 + // > atwola.com + // + // Before SelectedIndex: 0 Before RowCount: 4 + // After SelectedIndex: 0 After RowCount: 1 + // + // 3) Host selected, host closed + // > goats.com //////////////////////////// + // > atwola.com + // + // Before SelectedIndex: 0 Before RowCount: 2 + // After SelectedIndex: 0 After RowCount: 1 + // + // 4) Single cookie selected, host has many children + // v goats.com + // goats.com sldkkfjl + // //// goats.com /////////// flksjl33 //// + // > atwola.com + // + // Before SelectedIndex: 2 Before RowCount: 4 + // After SelectedIndex: 1 After RowCount: 3 + // + // 5) Single cookie selected, host has many children + // v goats.com + // //// goats.com /////////// flksjl33 //// + // goats.com sldkkfjl + // > atwola.com + // + // Before SelectedIndex: 1 Before RowCount: 4 + // After SelectedIndex: 1 After RowCount: 3 + var seln = this._view.selection; + var tbo = this._tree.treeBoxObject; + + if (seln.count < 1) return; + + var nextSelected = 0; + var rowCountImpact = 0; + var deleteItems = []; + if (!this._view._filtered) { + var ci = seln.currentIndex; + nextSelected = ci; + var invalidateRow = -1; + var item = this._view._getItemAtIndex(ci); + if (item.container) { + rowCountImpact -= (item.open ? item.cookies.length : 0) + 1; + deleteItems = deleteItems.concat(item.cookies); + if (!this._view.hasNextSibling(-1, ci)) + --nextSelected; + this._view._removeItemAtIndex(ci); + } + else { + var parent = this._view._getItemAtIndex(item.parentIndex); + --rowCountImpact; + if (parent.cookies.length == 1) { + --rowCountImpact; + deleteItems.push(item); + if (!this._view.hasNextSibling(-1, ci)) + --nextSelected; + if (!this._view.hasNextSibling(-1, item.parentIndex)) + --nextSelected; + this._view._removeItemAtIndex(item.parentIndex); + invalidateRow = item.parentIndex; + } + else { + deleteItems.push(item); + if (!this._view.hasNextSibling(-1, ci)) + --nextSelected; + this._view._removeItemAtIndex(ci); + } + } + this._view._rowCount += rowCountImpact; + tbo.rowCountChanged(ci, rowCountImpact); + if (invalidateRow != -1) + tbo.invalidateRow(invalidateRow); + } + else { + var rangeCount = seln.getRangeCount(); + // Traverse backwards through selections to avoid messing + // up the indices when they are deleted. + // See bug 388079. + for (var i = rangeCount - 1; i >= 0; --i) { + var min = {}; var max = {}; + seln.getRangeAt(i, min, max); + nextSelected = min.value; + for (var j = min.value; j <= max.value; ++j) { + deleteItems.push(this._view._getItemAtIndex(j)); + if (!this._view.hasNextSibling(-1, max.value)) + --nextSelected; + } + var delta = max.value - min.value + 1; + this._view._removeItemAtIndex(min.value, delta); + rowCountImpact = -1 * delta; + this._view._rowCount += rowCountImpact; + tbo.rowCountChanged(min.value, rowCountImpact); + } + } + + this.performDeletion(deleteItems); + + if (nextSelected < 0) + seln.clearSelection(); + else { + seln.select(nextSelected); + this._tree.focus(); + } + }, + + deleteAllCookies: function() { + if (this._view._filtered) { + var rowCount = this._view.rowCount; + var deleteItems = []; + for (var index = 0; index < rowCount; index++) { + deleteItems.push(this._view._getItemAtIndex(index)); + } + this._view._removeItemAtIndex(0, rowCount); + this._view._rowCount = 0; + this._tree.treeBoxObject.rowCountChanged(0, -rowCount); + this.performDeletion(deleteItems); + } + else { + this._cm.removeAll(); + } + this._updateRemoveAllButton(); + this.focusFilterBox(); + }, + + onCookieKeyPress: function(aEvent) { + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) { + this.deleteCookie(); + } + }, + + _lastSortProperty : "", + _lastSortAscending: false, + sort: function(aProperty) { + var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true; + // Sort the Non-Filtered Host Collections + if (aProperty == "rawHost") { + function sortByHost(a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()); + } + this._hostOrder.sort(sortByHost); + if (!ascending) + this._hostOrder.reverse(); + } + + function sortByProperty(a, b) { + return a[aProperty].toLowerCase().localeCompare(b[aProperty].toLowerCase()); + } + for (var host in this._hosts) { + var cookies = this._hosts[host].cookies; + cookies.sort(sortByProperty); + if (!ascending) + cookies.reverse(); + } + // Sort the Filtered List, if in Filtered mode + if (this._view._filtered) { + this._view._filterSet.sort(sortByProperty); + if (!ascending) + this._view._filterSet.reverse(); + } + + // Adjust the Sort Indicator + var domainCol = document.getElementById("domainCol"); + var nameCol = document.getElementById("nameCol"); + var sortOrderString = ascending ? "ascending" : "descending"; + if (aProperty == "rawHost") { + domainCol.setAttribute("sortDirection", sortOrderString); + nameCol.removeAttribute("sortDirection"); + } + else { + nameCol.setAttribute("sortDirection", sortOrderString); + domainCol.removeAttribute("sortDirection"); + } + + this._view._invalidateCache(0); + this._view.selection.clearSelection(); + if (this._view.rowCount > 0) { + this._view.selection.select(0); + } + this._tree.treeBoxObject.invalidate(); + this._tree.treeBoxObject.ensureRowIsVisible(0); + + this._lastSortAscending = ascending; + this._lastSortProperty = aProperty; + }, + + clearFilter: function() { + // Revert to single-select in the tree + this._tree.setAttribute("seltype", "single"); + + // Clear the Tree Display + this._view._filtered = false; + this._view._rowCount = 0; + this._tree.treeBoxObject.rowCountChanged(0, -this._view._filterSet.length); + this._view._filterSet = []; + + // Just reload the list to make sure deletions are respected + this._loadCookies(); + this._tree.view = this._view; + + // Restore sort order + var sortby = this._lastSortProperty; + if (sortby == "") { + this._lastSortAscending = false; + this.sort("rawHost"); + } + else { + this._lastSortAscending = !this._lastSortAscending; + this.sort(sortby); + } + + // Restore open state + for (var i = 0; i < this._openIndices.length; ++i) + this._view.toggleOpenState(this._openIndices[i]); + this._openIndices = []; + + // Restore selection + this._view.selection.clearSelection(); + for (i = 0; i < this._lastSelectedRanges.length; ++i) { + var range = this._lastSelectedRanges[i]; + this._view.selection.rangedSelect(range.min, range.max, true); + } + this._lastSelectedRanges = []; + + document.getElementById("cookiesIntro").value = this._bundle.getString("cookiesAll"); + this._updateRemoveAllButton(); + }, + + _cookieMatchesFilter: function(aCookie) { + return aCookie.rawHost.indexOf(this._view._filterValue) != -1 || + aCookie.name.indexOf(this._view._filterValue) != -1 || + aCookie.value.indexOf(this._view._filterValue) != -1; + }, + + _filterCookies: function(aFilterValue) { + this._view._filterValue = aFilterValue; + var cookies = []; + for (var i = 0; i < gCookiesWindow._hostOrder.length; ++i) { //var host in gCookiesWindow._hosts) { + var currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host]; + if (!currHost) continue; + for (var j = 0; j < currHost.cookies.length; ++j) { + var cookie = currHost.cookies[j]; + if (this._cookieMatchesFilter(cookie)) + cookies.push(cookie); + } + } + return cookies; + }, + + _lastSelectedRanges: [], + _openIndices: [], + _saveState: function() { + // Save selection + var seln = this._view.selection; + this._lastSelectedRanges = []; + var rangeCount = seln.getRangeCount(); + for (var i = 0; i < rangeCount; ++i) { + var min = {}; var max = {}; + seln.getRangeAt(i, min, max); + this._lastSelectedRanges.push({ min: min.value, max: max.value }); + } + + // Save open states + this._openIndices = []; + for (i = 0; i < this._view.rowCount; ++i) { + var item = this._view._getItemAtIndex(i); + if (item && item.container && item.open) + this._openIndices.push(i); + } + }, + + _updateRemoveAllButton: function() { + let removeAllCookies = document.getElementById("removeAllCookies"); + removeAllCookies.disabled = this._view._rowCount == 0; + + let labelStringID = "removeAllCookies.label"; + let accessKeyStringID = "removeAllCookies.accesskey"; + if (this._view._filtered) { + labelStringID = "removeAllShownCookies.label"; + accessKeyStringID = "removeAllShownCookies.accesskey"; + } + removeAllCookies.setAttribute("label", this._bundle.getString(labelStringID)); + removeAllCookies.setAttribute("accesskey", this._bundle.getString(accessKeyStringID)); + }, + + filter: function() { + var filter = document.getElementById("filter").value; + if (filter == "") { + gCookiesWindow.clearFilter(); + return; + } + var view = gCookiesWindow._view; + view._filterSet = gCookiesWindow._filterCookies(filter); + if (!view._filtered) { + // Save Display Info for the Non-Filtered mode when we first + // enter Filtered mode. + gCookiesWindow._saveState(); + view._filtered = true; + } + // Move to multi-select in the tree + gCookiesWindow._tree.setAttribute("seltype", "multiple"); + + // Clear the display + var oldCount = view._rowCount; + view._rowCount = 0; + gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, -oldCount); + // Set up the filtered display + view._rowCount = view._filterSet.length; + gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, view.rowCount); + + // if the view is not empty then select the first item + if (view.rowCount > 0) + view.selection.select(0); + + document.getElementById("cookiesIntro").value = gCookiesWindow._bundle.getString("cookiesFiltered"); + this._updateRemoveAllButton(); + }, + + setFilter: function(aFilterString) { + document.getElementById("filter").value = aFilterString; + this.filter(); + }, + + focusFilterBox: function() { + var filter = document.getElementById("filter"); + filter.focus(); + filter.select(); + }, + + onWindowKeyPress: function(aEvent) { + if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) + window.close(); + } +}; diff --git a/browser/components/preferences/cookies.xul b/browser/components/preferences/cookies.xul new file mode 100644 index 000000000..8dd757fd0 --- /dev/null +++ b/browser/components/preferences/cookies.xul @@ -0,0 +1,103 @@ +<?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/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/cookies.dtd" > + +<window id="CookiesDialog" windowtype="Browser:Cookies" + class="windowDialog" title="&window.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: &window.width;;" + onload="gCookiesWindow.init();" + onunload="gCookiesWindow.uninit();" + persist="screenX screenY width height" + onkeypress="gCookiesWindow.onWindowKeyPress(event);"> + + <script src="chrome://browser/content/preferences/cookies.js"/> + + <stringbundle id="bundlePreferences" + src="chrome://browser/locale/preferences/preferences.properties"/> + + <keyset> + <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/> + <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/> + <key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/> + </keyset> + + <vbox flex="1" class="contentPane"> + <hbox align="center"> + <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label> + <textbox type="search" id="filter" flex="1" + aria-controls="cookiesList" + oncommand="gCookiesWindow.filter();"/> + </hbox> + <separator class="thin"/> + <label control="cookiesList" id="cookiesIntro" value="&cookiesonsystem.label;"/> + <separator class="thin"/> + <tree id="cookiesList" flex="1" style="height: 10em;" + onkeypress="gCookiesWindow.onCookieKeyPress(event)" + onselect="gCookiesWindow.onCookieSelected();" + hidecolumnpicker="true" seltype="single"> + <treecols> + <treecol id="domainCol" label="&cookiedomain.label;" flex="2" primary="true" + persist="width" onclick="gCookiesWindow.sort('rawHost');"/> + <splitter class="tree-splitter"/> + <treecol id="nameCol" label="&cookiename.label;" flex="1" + persist="width" + onclick="gCookiesWindow.sort('name');"/> + </treecols> + <treechildren id="cookiesChildren"/> + </tree> + <hbox id="cookieInfoBox"> + <grid flex="1" id="cookieInfoGrid"> + <columns> + <column/> + <column flex="1"/> + </columns> + <rows> + <row align="center"> + <hbox pack="end"><label id="nameLabel" control="name" value="&props.name.label;"/></hbox> + <textbox id="name" readonly="true" class="plain"/> + </row> + <row align="center"> + <hbox pack="end"><label id="valueLabel" control="value" value="&props.value.label;"/></hbox> + <textbox id="value" readonly="true" class="plain"/> + </row> + <row align="center"> + <hbox pack="end"><label id="isDomain" control="host" value="&props.domain.label;"/></hbox> + <textbox id="host" readonly="true" class="plain"/> + </row> + <row align="center"> + <hbox pack="end"><label id="pathLabel" control="path" value="&props.path.label;"/></hbox> + <textbox id="path" readonly="true" class="plain"/> + </row> + <row align="center"> + <hbox pack="end"><label id="isSecureLabel" control="isSecure" value="&props.secure.label;"/></hbox> + <textbox id="isSecure" readonly="true" class="plain"/> + </row> + <row align="center"> + <hbox pack="end"><label id="expiresLabel" control="expires" value="&props.expires.label;"/></hbox> + <textbox id="expires" readonly="true" class="plain"/> + </row> + </rows> + </grid> + </hbox> + </vbox> + <hbox align="end"> + <hbox class="actionButtons" flex="1"> + <button id="removeSelectedCookies" disabled="true" icon="clear" + oncommand="gCookiesWindow.deleteCookie();"/> + <button id="removeAllCookies" disabled="true" icon="clear" + oncommand="gCookiesWindow.deleteAllCookies();"/> + <spacer flex="1"/> + <button oncommand="close();" icon="close" + label="&button.close.label;" accesskey="&button.close.accesskey;"/> + </hbox> + <resizer type="window" dir="bottomend"/> + </hbox> +</window> diff --git a/browser/components/preferences/fonts.js b/browser/components/preferences/fonts.js new file mode 100644 index 000000000..975671a6e --- /dev/null +++ b/browser/components/preferences/fonts.js @@ -0,0 +1,143 @@ +// 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.display.languageList LOCK ALL when LOCKED + +const kDefaultFontType = "font.default.%LANG%"; +const kFontNameFmtSerif = "font.name.serif.%LANG%"; +const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%"; +const kFontNameFmtMonospace = "font.name.monospace.%LANG%"; +const kFontNameListFmtSerif = "font.name-list.serif.%LANG%"; +const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%"; +const kFontNameListFmtMonospace = "font.name-list.monospace.%LANG%"; +const kFontSizeFmtVariable = "font.size.variable.%LANG%"; +const kFontSizeFmtFixed = "font.size.fixed.%LANG%"; +const kFontMinSizeFmt = "font.minimum-size.%LANG%"; + +var gFontsDialog = { + _selectLanguageGroup: function (aLanguageGroup) + { + var prefs = [{ format: kDefaultFontType, type: "string", element: "defaultFontType", fonttype: null}, + { format: kFontNameFmtSerif, type: "fontname", element: "serif", fonttype: "serif" }, + { format: kFontNameFmtSansSerif, type: "fontname", element: "sans-serif", fonttype: "sans-serif" }, + { format: kFontNameFmtMonospace, type: "fontname", element: "monospace", fonttype: "monospace" }, + { format: kFontNameListFmtSerif, type: "unichar", element: null, fonttype: "serif" }, + { format: kFontNameListFmtSansSerif, type: "unichar", element: null, fonttype: "sans-serif" }, + { format: kFontNameListFmtMonospace, type: "unichar", element: null, fonttype: "monospace" }, + { format: kFontSizeFmtVariable, type: "int", element: "sizeVar", fonttype: null }, + { format: kFontSizeFmtFixed, type: "int", element: "sizeMono", fonttype: null }, + { format: kFontMinSizeFmt, type: "int", element: "minSize", fonttype: null }]; + var preferences = document.getElementById("fontPreferences"); + 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); + } + } + }, + + readFontLanguageGroup: function () + { + var languagePref = document.getElementById("font.language.group"); + this._selectLanguageGroup(languagePref.value); + return undefined; + }, + + readFontSelection: function (aElement) + { + // Determine the appropriate value to select, for the following cases: + // - there is no setting + // - the font selected by the user is no longer present (e.g. deleted from + // fonts folder) + var preference = document.getElementById(aElement.getAttribute("preference")); + if (preference.value) { + var fontItems = aElement.getElementsByAttribute("value", preference.value); + + // There is a setting that actually is in the list. Respect it. + if (fontItems.length > 0) + return undefined; + } + + var defaultValue = aElement.firstChild.firstChild.getAttribute("value"); + var languagePref = document.getElementById("font.language.group"); + preference = document.getElementById("font.name-list." + aElement.id + "." + languagePref.value); + if (!preference || !preference.hasUserValue) + return defaultValue; + + var fontNames = preference.value.split(","); + var stripWhitespace = /^\s*(.*)\s*$/; + + for (var i = 0; i < fontNames.length; ++i) { + var fontName = fontNames[i].replace(stripWhitespace, "$1"); + fontItems = aElement.getElementsByAttribute("value", fontName); + if (fontItems.length) + break; + } + if (fontItems.length) + return fontItems[0].getAttribute("value"); + return defaultValue; + }, + + readUseDocumentFonts: function () + { + var preference = document.getElementById("browser.display.use_document_fonts"); + return preference.value == 1; + }, + + writeUseDocumentFonts: function () + { + var useDocumentFonts = document.getElementById("useDocumentFonts"); + return useDocumentFonts.checked ? 1 : 0; + }, + + onBeforeAccept: function () + { + // Only care in in-content prefs + if (!window.frameElement) { + return true; + } + + let preferences = document.querySelectorAll("preference[id*='font.minimum-size']"); + // It would be good if we could avoid touching languages the pref pages won't use, but + // unfortunately the language group APIs (deducing language groups from language codes) + // are C++ - only. So we just check all the things the user touched: + // Don't care about anything up to 24px, or if this value is the same as set previously: + preferences = Array.filter(preferences, prefEl => { + return prefEl.value > 24 && prefEl.value != prefEl.valueFromPreferences; + }); + if (!preferences.length) { + return; + } + + let strings = document.getElementById("bundlePreferences"); + let title = strings.getString("veryLargeMinimumFontTitle"); + let confirmLabel = strings.getString("acceptVeryLargeMinimumFont"); + let warningMessage = strings.getString("veryLargeMinimumFontWarning"); + let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {}); + let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL | + Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING | + Services.prompt.BUTTON_POS_1_DEFAULT; + let buttonChosen = Services.prompt.confirmEx(window, title, warningMessage, flags, confirmLabel, null, "", "", {}); + return buttonChosen == 0; + }, +}; + diff --git a/browser/components/preferences/fonts.xul b/browser/components/preferences/fonts.xul new file mode 100644 index 000000000..1c14bcf91 --- /dev/null +++ b/browser/components/preferences/fonts.xul @@ -0,0 +1,275 @@ +<?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/" type="text/css"?> + +<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/fonts.dtd" > + +<prefwindow id="FontsDialog" type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&fontsDialog.title;" + dlgbuttons="accept,cancel,help" + ondialoghelp="openPrefsHelp()" + onbeforeaccept="return gFontsDialog.onBeforeAccept();" + style=""> + + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + + <prefpane id="FontsDialogPane" + class="largeDialogContainer" + helpTopic="prefs-fonts-and-colors"> + + <preferences id="fontPreferences"> + <preference id="font.language.group" name="font.language.group" type="wstring"/> + <preference id="browser.display.use_document_fonts" + name="browser.display.use_document_fonts" + type="int"/> + <preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/> + </preferences> + + <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> + <script type="application/javascript" src="chrome://mozapps/content/preferences/fontbuilder.js"/> + <script type="application/javascript" src="chrome://browser/content/preferences/fonts.js"/> + + <!-- Fonts for: [ Language ] --> + <groupbox> + <caption> + <hbox align="center"> + <label accesskey="&language.accesskey;" control="selectLangs">&language.label;</label> + </hbox> + <menulist id="selectLangs" preference="font.language.group" + onsyncfrompreference="return gFontsDialog.readFontLanguageGroup();"> + <menupopup> + <menuitem value="ar" label="&font.langGroup.arabic;"/> + <menuitem value="x-armn" label="&font.langGroup.armenian;"/> + <menuitem value="x-beng" label="&font.langGroup.bengali;"/> + <menuitem value="zh-CN" label="&font.langGroup.simpl-chinese;"/> + <menuitem value="zh-HK" label="&font.langGroup.trad-chinese-hk;"/> + <menuitem value="zh-TW" label="&font.langGroup.trad-chinese;"/> + <menuitem value="x-cyrillic" label="&font.langGroup.cyrillic;"/> + <menuitem value="x-devanagari" label="&font.langGroup.devanagari;"/> + <menuitem value="x-ethi" label="&font.langGroup.ethiopic;"/> + <menuitem value="x-geor" label="&font.langGroup.georgian;"/> + <menuitem value="el" label="&font.langGroup.el;"/> + <menuitem value="x-gujr" label="&font.langGroup.gujarati;"/> + <menuitem value="x-guru" label="&font.langGroup.gurmukhi;"/> + <menuitem value="he" label="&font.langGroup.hebrew;"/> + <menuitem value="ja" label="&font.langGroup.japanese;"/> + <menuitem value="x-knda" label="&font.langGroup.kannada;"/> + <menuitem value="x-khmr" label="&font.langGroup.khmer;"/> + <menuitem value="ko" label="&font.langGroup.korean;"/> + <menuitem value="x-western" label="&font.langGroup.latin;"/> + <menuitem value="x-mlym" label="&font.langGroup.malayalam;"/> + <menuitem value="x-orya" label="&font.langGroup.oriya;"/> + <menuitem value="x-sinh" label="&font.langGroup.sinhala;"/> + <menuitem value="x-tamil" label="&font.langGroup.tamil;"/> + <menuitem value="x-telu" label="&font.langGroup.telugu;"/> + <menuitem value="th" label="&font.langGroup.thai;"/> + <menuitem value="x-tibt" label="&font.langGroup.tibetan;"/> + <menuitem value="x-cans" label="&font.langGroup.canadian;"/> + <menuitem value="x-unicode" label="&font.langGroup.other;"/> + </menupopup> + </menulist> + </caption> + + <grid> + <columns> + <column/> + <column flex="1"/> + <column/> + <column/> + </columns> + + <rows> + <row> + <separator class="thin"/> + </row> + + <row align="center"> + <hbox align="center" pack="end"> + <label accesskey="&proportional.accesskey;" control="defaultFontType">&proportional.label;</label> + </hbox> + <menulist id="defaultFontType" flex="1" style="width: 0px;"> + <menupopup> + <menuitem value="serif" label="&useDefaultFontSerif.label;"/> + <menuitem value="sans-serif" label="&useDefaultFontSansSerif.label;"/> + </menupopup> + </menulist> + <hbox align="center" pack="end"> + <label value="&size.label;" + accesskey="&sizeProportional.accesskey;" + control="sizeVar"/> + </hbox> + <menulist id="sizeVar"> + <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> + </row> + <row align="center"> + <hbox align="center" pack="end"> + <label accesskey="&serif.accesskey;" control="serif">&serif.label;</label> + </hbox> + <menulist id="serif" flex="1" style="width: 0px;" + onsyncfrompreference="return gFontsDialog.readFontSelection(document.getElementById('serif'));"/> + <spacer/> + </row> + <row align="center"> + <hbox align="center" pack="end"> + <label accesskey="&sans-serif.accesskey;" control="sans-serif">&sans-serif.label;</label> + </hbox> + <menulist id="sans-serif" flex="1" style="width: 0px;" + onsyncfrompreference="return gFontsDialog.readFontSelection(document.getElementById('sans-serif'));"/> + <spacer/> + </row> + <row align="center"> + <hbox align="center" pack="end"> + <label accesskey="&monospace.accesskey;" control="monospace">&monospace.label;</label> + </hbox> + <menulist id="monospace" flex="1" style="width: 0px;" crop="right" + onsyncfrompreference="return gFontsDialog.readFontSelection(document.getElementById('monospace'));"/> + <hbox align="center" pack="end"> + <label value="&size.label;" + accesskey="&sizeMonospace.accesskey;" + control="sizeMono"/> + </hbox> + <menulist id="sizeMono"> + <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> + </row> + </rows> + </grid> + <separator class="thin"/> + <hbox flex="1"> + <spacer flex="1"/> + <hbox align="center" pack="end"> + <label accesskey="&minSize.accesskey;" control="minSize">&minSize.label;</label> + <menulist id="minSize"> + <menupopup> + <menuitem value="0" label="&minSize.none;"/> + <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> + </hbox> + <separator/> + <separator class="groove"/> + <hbox> + <checkbox id="useDocumentFonts" + label="&allowPagesToUse.label;" accesskey="&allowPagesToUse.accesskey;" + preference="browser.display.use_document_fonts" + onsyncfrompreference="return gFontsDialog.readUseDocumentFonts();" + onsynctopreference="return gFontsDialog.writeUseDocumentFonts();"/> + </hbox> + </groupbox> + + <!-- Character Encoding --> + <groupbox> + <caption label="&languages.customize.Fallback.grouplabel;"/> + <description>&languages.customize.Fallback.desc;</description> + <hbox align="center"> + <label value="&languages.customize.Fallback.label;" + accesskey="&languages.customize.Fallback.accesskey;" + control="DefaultCharsetList"/> + <menulist id="DefaultCharsetList" preference="intl.charset.fallback.override"> + <menupopup> + <menuitem label="&languages.customize.Fallback.auto;" value="*"/> + <menuitem label="&languages.customize.Fallback.utf8;" value="UTF-8"/> + <menuitem label="&languages.customize.Fallback.arabic;" value="windows-1256"/> + <menuitem label="&languages.customize.Fallback.baltic;" value="windows-1257"/> + <menuitem label="&languages.customize.Fallback.ceiso;" value="ISO-8859-2"/> + <menuitem label="&languages.customize.Fallback.cewindows;" value="windows-1250"/> + <menuitem label="&languages.customize.Fallback.simplified;" value="gbk"/> + <menuitem label="&languages.customize.Fallback.traditional;" value="Big5"/> + <menuitem label="&languages.customize.Fallback.cyrillic;" value="windows-1251"/> + <menuitem label="&languages.customize.Fallback.greek;" value="ISO-8859-7"/> + <menuitem label="&languages.customize.Fallback.hebrew;" value="windows-1255"/> + <menuitem label="&languages.customize.Fallback.japanese;" value="Shift_JIS"/> + <menuitem label="&languages.customize.Fallback.korean;" value="EUC-KR"/> + <menuitem label="&languages.customize.Fallback.thai;" value="windows-874"/> + <menuitem label="&languages.customize.Fallback.turkish;" value="windows-1254"/> + <menuitem label="&languages.customize.Fallback.vietnamese;" value="windows-1258"/> + <menuitem label="&languages.customize.Fallback.other;" value="windows-1252"/> + </menupopup> + </menulist> + </hbox> + </groupbox> + </prefpane> +</prefwindow> diff --git a/browser/components/preferences/handlers.css b/browser/components/preferences/handlers.css new file mode 100644 index 000000000..9a1d47446 --- /dev/null +++ b/browser/components/preferences/handlers.css @@ -0,0 +1,25 @@ +/* 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/. */ + +richlistitem { + -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler"); +} + +richlistitem[selected="true"] { + -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler-selected"); +} + +/** + * Make the icons appear. + * Note: we display the icon box for every item whether or not it has an icon + * so the labels of all the items align vertically. + */ +.actionsMenu > menupopup > menuitem > .menu-iconic-left { + display: -moz-box; + min-width: 16px; +} + +listitem.offlineapp { + -moz-binding: url("chrome://browser/content/preferences/handlers.xml#offlineapp"); +} diff --git a/browser/components/preferences/handlers.xml b/browser/components/preferences/handlers.xml new file mode 100644 index 000000000..5fb915cee --- /dev/null +++ b/browser/components/preferences/handlers.xml @@ -0,0 +1,81 @@ +<?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/. --> + +<!DOCTYPE overlay [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + <!ENTITY % applicationsDTD SYSTEM "chrome://browser/locale/preferences/applications.dtd"> + %brandDTD; + %applicationsDTD; +]> + +<bindings id="handlerBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xbl="http://www.mozilla.org/xbl"> + + <binding id="handler-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> + <implementation> + <property name="type" readonly="true"> + <getter> + return this.getAttribute("type"); + </getter> + </property> + </implementation> + </binding> + + <binding id="handler" extends="chrome://browser/content/preferences/handlers.xml#handler-base"> + <content> + <xul:hbox flex="1" equalsize="always"> + <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription"> + <xul:image src="moz-icon://goat?size=16" class="typeIcon" + xbl:inherits="src=typeIcon" height="16" width="16"/> + <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/> + </xul:hbox> + <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=actionDescription"> + <xul:image xbl:inherits="src=actionIcon" height="16" width="16" class="actionIcon"/> + <xul:label flex="1" crop="end" xbl:inherits="value=actionDescription"/> + </xul:hbox> + </xul:hbox> + </content> + </binding> + + <binding id="handler-selected" extends="chrome://browser/content/preferences/handlers.xml#handler-base"> + <content> + <xul:hbox flex="1" equalsize="always"> + <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription"> + <xul:image src="moz-icon://goat?size=16" class="typeIcon" + xbl:inherits="src=typeIcon" height="16" width="16"/> + <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/> + </xul:hbox> + <xul:hbox flex="1"> + <xul:menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1" + xbl:inherits="tooltiptext=actionDescription" + oncommand="gApplicationsPane.onSelectAction(event.originalTarget)"> + <xul:menupopup/> + </xul:menulist> + </xul:hbox> + </xul:hbox> + </content> + + <implementation> + <constructor> + gApplicationsPane.rebuildActionsMenu(); + </constructor> + </implementation> + + </binding> + + <binding id="offlineapp" + extends="chrome://global/content/bindings/listbox.xml#listitem"> + <content> + <children> + <xul:listcell xbl:inherits="label=origin"/> + <xul:listcell xbl:inherits="label=usage"/> + </children> + </content> + </binding> + +</bindings> diff --git a/browser/components/preferences/jar.mn b/browser/components/preferences/jar.mn new file mode 100644 index 000000000..9256e3927 --- /dev/null +++ b/browser/components/preferences/jar.mn @@ -0,0 +1,44 @@ +# 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/advanced.xul +* content/browser/preferences/advanced.js + content/browser/preferences/applications.xul +* content/browser/preferences/applications.js + content/browser/preferences/applicationManager.xul + content/browser/preferences/applicationManager.js +* content/browser/preferences/colors.xul + content/browser/preferences/cookies.xul + content/browser/preferences/cookies.js +* content/browser/preferences/content.xul + content/browser/preferences/content.js + content/browser/preferences/connection.xul + content/browser/preferences/connection.js + content/browser/preferences/fonts.xul + content/browser/preferences/fonts.js + content/browser/preferences/handlers.xml + content/browser/preferences/handlers.css + content/browser/preferences/languages.xul + content/browser/preferences/languages.js +* content/browser/preferences/main.xul +* content/browser/preferences/main.js + content/browser/preferences/newtaburl.js + content/browser/preferences/permissions.xul + content/browser/preferences/permissions.js +* content/browser/preferences/preferences.xul + content/browser/preferences/privacy.xul + content/browser/preferences/privacy.js + content/browser/preferences/sanitize.xul + content/browser/preferences/sanitize.js + content/browser/preferences/security.xul + content/browser/preferences/security.js + content/browser/preferences/selectBookmark.xul + content/browser/preferences/selectBookmark.js +#ifdef MOZ_SERVICES_SYNC + content/browser/preferences/sync.xul + content/browser/preferences/sync.js +#endif +* content/browser/preferences/tabs.xul +* content/browser/preferences/tabs.js diff --git a/browser/components/preferences/languages.js b/browser/components/preferences/languages.js new file mode 100644 index 000000000..5b8ea38a6 --- /dev/null +++ b/browser/components/preferences/languages.js @@ -0,0 +1,303 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +var gLanguagesDialog = { + + _availableLanguagesList : [], + _acceptLanguages : { }, + + _selectedItemID : null, + + init: function () + { + if (!this._availableLanguagesList.length) + this._loadAvailableLanguages(); + }, + + get _activeLanguages() + { + return document.getElementById("activeLanguages"); + }, + + get _availableLanguages() + { + return document.getElementById("availableLanguages"); + }, + + _loadAvailableLanguages: function () + { + // This is a parser for: resource://gre/res/language.properties + // The file is formatted like so: + // ab[-cd].accept=true|false + // ab = language + // cd = region + var bundleAccepted = document.getElementById("bundleAccepted"); + var bundleRegions = document.getElementById("bundleRegions"); + var bundleLanguages = document.getElementById("bundleLanguages"); + var bundlePreferences = document.getElementById("bundlePreferences"); + + function LanguageInfo(aName, aABCD, aIsVisible) + { + this.name = aName; + this.abcd = aABCD; + this.isVisible = aIsVisible; + } + + // 1) Read the available languages out of language.properties + var strings = bundleAccepted.strings; + while (strings.hasMoreElements()) { + var currString = strings.getNext(); + if (!(currString instanceof Components.interfaces.nsIPropertyElement)) + break; + + var property = currString.key.split("."); // ab[-cd].accept + if (property[1] == "accept") { + var abCD = property[0]; + var abCDPairs = abCD.split("-"); // ab[-cd] + var useABCDFormat = abCDPairs.length > 1; + var ab = useABCDFormat ? abCDPairs[0] : abCD; + var cd = useABCDFormat ? abCDPairs[1] : ""; + if (ab) { + var language = ""; + try { + language = bundleLanguages.getString(ab); + } + catch (e) { continue; }; + + var region = ""; + if (useABCDFormat) { + try { + region = bundleRegions.getString(cd); + } + catch (e) { continue; } + } + + var name = ""; + if (useABCDFormat) + name = bundlePreferences.getFormattedString("languageRegionCodeFormat", + [language, region, abCD]); + else + name = bundlePreferences.getFormattedString("languageCodeFormat", + [language, abCD]); + + if (name && abCD) { + var isVisible = currString.value == "true" && + (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD]); + var li = new LanguageInfo(name, abCD, isVisible); + this._availableLanguagesList.push(li); + } + } + } + } + this._buildAvailableLanguageList(); + }, + + _buildAvailableLanguageList: function () + { + var availableLanguagesPopup = document.getElementById("availableLanguagesPopup"); + while (availableLanguagesPopup.hasChildNodes()) + availableLanguagesPopup.removeChild(availableLanguagesPopup.firstChild); + + // Sort the list of languages by name + this._availableLanguagesList.sort(function (a, b) { + return a.name.localeCompare(b.name); + }); + + // Load the UI with the data + for (var i = 0; i < this._availableLanguagesList.length; ++i) { + var abCD = this._availableLanguagesList[i].abcd; + if (this._availableLanguagesList[i].isVisible && + (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD])) { + var menuitem = document.createElement("menuitem"); + menuitem.id = this._availableLanguagesList[i].abcd; + availableLanguagesPopup.appendChild(menuitem); + menuitem.setAttribute("label", this._availableLanguagesList[i].name); + } + } + }, + + readAcceptLanguages: function () + { + while (this._activeLanguages.hasChildNodes()) + this._activeLanguages.removeChild(this._activeLanguages.firstChild); + + var selectedIndex = 0; + var preference = document.getElementById("intl.accept_languages"); + if (preference.value == "") + return undefined; + var languages = preference.value.toLowerCase().split(/\s*,\s*/); + for (var i = 0; i < languages.length; ++i) { + var name = this._getLanguageName(languages[i]); + if (!name) + name = "[" + languages[i] + "]"; + var listitem = document.createElement("listitem"); + listitem.id = languages[i]; + if (languages[i] == this._selectedItemID) + selectedIndex = i; + this._activeLanguages.appendChild(listitem); + listitem.setAttribute("label", name); + + // Hash this language as an "Active" language so we don't + // show it in the list that can be added. + this._acceptLanguages[languages[i]] = true; + } + + if (this._activeLanguages.childNodes.length > 0) { + this._activeLanguages.ensureIndexIsVisible(selectedIndex); + this._activeLanguages.selectedIndex = selectedIndex; + } + + return undefined; + }, + + writeAcceptLanguages: function () + { + return undefined; + }, + + onAvailableLanguageSelect: function () + { + var addButton = document.getElementById("addButton"); + addButton.disabled = false; + + this._availableLanguages.removeAttribute("accesskey"); + }, + + addLanguage: function () + { + var selectedID = this._availableLanguages.selectedItem.id; + var preference = document.getElementById("intl.accept_languages"); + var arrayOfPrefs = preference.value.toLowerCase().split(/\s*,\s*/); + for (var i = 0; i < arrayOfPrefs.length; ++i ){ + if (arrayOfPrefs[i] == selectedID) + return; + } + + this._selectedItemID = selectedID; + + if (preference.value == "") + preference.value = selectedID; + else { + arrayOfPrefs.unshift(selectedID); + preference.value = arrayOfPrefs.join(","); + } + + this._acceptLanguages[selectedID] = true; + this._availableLanguages.selectedItem = null; + + // Rebuild the available list with the added item removed... + this._buildAvailableLanguageList(); + + this._availableLanguages.setAttribute("label", this._availableLanguages.getAttribute("label2")); + }, + + removeLanguage: function () + { + // Build the new preference value string. + var languagesArray = []; + for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) { + var item = this._activeLanguages.childNodes[i]; + if (!item.selected) + languagesArray.push(item.id); + else + this._acceptLanguages[item.id] = false; + } + var string = languagesArray.join(","); + + // Get the item to select after the remove operation completes. + var selection = this._activeLanguages.selectedItems; + var lastSelected = selection[selection.length-1]; + var selectItem = lastSelected.nextSibling || lastSelected.previousSibling; + selectItem = selectItem ? selectItem.id : null; + + this._selectedItemID = selectItem; + + // Update the preference and force a UI rebuild + var preference = document.getElementById("intl.accept_languages"); + preference.value = string; + + this._buildAvailableLanguageList(); + }, + + _getLanguageName: function (aABCD) + { + if (!this._availableLanguagesList.length) + this._loadAvailableLanguages(); + for (var i = 0; i < this._availableLanguagesList.length; ++i) { + if (aABCD == this._availableLanguagesList[i].abcd) + return this._availableLanguagesList[i].name; + } + return ""; + }, + + moveUp: function () + { + var selectedItem = this._activeLanguages.selectedItems[0]; + var previousItem = selectedItem.previousSibling; + + var string = ""; + for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) { + var item = this._activeLanguages.childNodes[i]; + string += (i == 0 ? "" : ","); + if (item.id == previousItem.id) + string += selectedItem.id; + else if (item.id == selectedItem.id) + string += previousItem.id; + else + string += item.id; + } + + this._selectedItemID = selectedItem.id; + + // Update the preference and force a UI rebuild + var preference = document.getElementById("intl.accept_languages"); + preference.value = string; + }, + + moveDown: function () + { + var selectedItem = this._activeLanguages.selectedItems[0]; + var nextItem = selectedItem.nextSibling; + + var string = ""; + for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) { + var item = this._activeLanguages.childNodes[i]; + string += (i == 0 ? "" : ","); + if (item.id == nextItem.id) + string += selectedItem.id; + else if (item.id == selectedItem.id) + string += nextItem.id; + else + string += item.id; + } + + this._selectedItemID = selectedItem.id; + + // Update the preference and force a UI rebuild + var preference = document.getElementById("intl.accept_languages"); + preference.value = string; + }, + + onLanguageSelect: function () + { + var upButton = document.getElementById("up"); + var downButton = document.getElementById("down"); + var removeButton = document.getElementById("remove"); + switch (this._activeLanguages.selectedCount) { + case 0: + upButton.disabled = downButton.disabled = removeButton.disabled = true; + break; + case 1: + upButton.disabled = this._activeLanguages.selectedIndex == 0; + downButton.disabled = this._activeLanguages.selectedIndex == this._activeLanguages.childNodes.length - 1; + removeButton.disabled = false; + break; + default: + upButton.disabled = true; + downButton.disabled = true; + removeButton.disabled = false; + } + } +}; + diff --git a/browser/components/preferences/languages.xul b/browser/components/preferences/languages.xul new file mode 100644 index 000000000..bd74e11cf --- /dev/null +++ b/browser/components/preferences/languages.xul @@ -0,0 +1,94 @@ +<?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/. --> + +<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/languages.dtd"> + +<?xml-stylesheet href="chrome://global/skin/"?> + +<prefwindow id="LanguagesDialog" type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&languages.customize.Header;" + dlgbuttons="accept,cancel,help" + ondialoghelp="openPrefsHelp()" + style="width: &window.width;;"> + + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + + <prefpane id="LanguagesDialogPane" + onpaneload="gLanguagesDialog.init();" + helpTopic="prefs-languages"> + + <preferences> + <preference id="intl.accept_languages" name="intl.accept_languages" type="wstring"/> + <preference id="pref.browser.language.disable_button.up" + name="pref.browser.language.disable_button.up" + type="bool"/> + <preference id="pref.browser.language.disable_button.down" + name="pref.browser.language.disable_button.down" + type="bool"/> + <preference id="pref.browser.language.disable_button.remove" + name="pref.browser.language.disable_button.remove" + type="bool"/> + </preferences> + + <script type="application/javascript" src="chrome://browser/content/preferences/languages.js"/> + + <stringbundleset id="languageSet"> + <stringbundle id="bundleRegions" src="chrome://global/locale/regionNames.properties"/> + <stringbundle id="bundleLanguages" src="chrome://global/locale/languageNames.properties"/> + <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> + <stringbundle id="bundleAccepted" src="resource://gre/res/language.properties"/> + </stringbundleset> + + <description>&languages.customize.prefLangDescript;</description> + <label>&languages.customize.active.label;</label> + <grid flex="1"> + <columns> + <column flex="1"/> + <column/> + </columns> + <rows> + <row flex="1"> + <listbox id="activeLanguages" flex="1" rows="6" + seltype="multiple" onselect="gLanguagesDialog.onLanguageSelect();" + preference="intl.accept_languages" + onsyncfrompreference="return gLanguagesDialog.readAcceptLanguages();" + onsynctopreference="return gLanguagesDialog.writeAcceptLanguages();"/> + <vbox> + <button id="up" class="up" oncommand="gLanguagesDialog.moveUp();" disabled="true" + label="&languages.customize.moveUp.label;" + accesskey="&languages.customize.moveUp.accesskey;" + preference="pref.browser.language.disable_button.up"/> + <button id="down" class="down" oncommand="gLanguagesDialog.moveDown();" disabled="true" + label="&languages.customize.moveDown.label;" + accesskey="&languages.customize.moveDown.accesskey;" + preference="pref.browser.language.disable_button.down"/> + <button id="remove" oncommand="gLanguagesDialog.removeLanguage();" disabled="true" + label="&languages.customize.deleteButton.label;" + accesskey="&languages.customize.deleteButton.accesskey;" + preference="pref.browser.language.disable_button.remove"/> + </vbox> + </row> + <row> + <separator class="thin"/> + </row> + <row> + <menulist id="availableLanguages" oncommand="gLanguagesDialog.onAvailableLanguageSelect();" + label="&languages.customize.selectLanguage.label;" + label2="&languages.customize.selectLanguage.label;"> + <menupopup id="availableLanguagesPopup"/> + </menulist> + <button id="addButton" oncommand="gLanguagesDialog.addLanguage();" disabled="true" + label="&languages.customize.addButton.label;" + accesskey="&languages.customize.addButton.accesskey;"/> + </row> + </rows> + </grid> + <separator/> + <separator/> + </prefpane> +</prefwindow> + diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js new file mode 100644 index 000000000..d4daeeab3 --- /dev/null +++ b/browser/components/preferences/main.js @@ -0,0 +1,543 @@ +// 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, "DownloadsCommon", + "resource:///modules/DownloadsCommon.jsm"); + +var gMainPane = { + _pane: null, + + /** + * Initialization of this. + */ + init: function () + { + this._pane = document.getElementById("paneMain"); + + // set up the "use current page" label-changing listener + this._updateUseCurrentButton(); + window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false); + + this.updateBrowserStartupLastSession(); + + this.setupDownloadsWindowOptions(); + +#ifdef HAVE_SHELL_SERVICE + this.updateSetDefaultBrowser(); +#ifdef XP_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, 1000); +#endif +#endif + + // 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); + }, + + setupDownloadsWindowOptions: function () + { + let showWhenDownloading = document.getElementById("showWhenDownloading"); + let closeWhenDone = document.getElementById("closeWhenDone"); + + // These radio buttons should be hidden when the Downloads Panel is enabled. + let shouldHide = !DownloadsCommon.useToolkitUI; + showWhenDownloading.hidden = shouldHide; + closeWhenDone.hidden = shouldHide; + }, + + // 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, set the value to "" to show the + // placeholder text (about:home title). + if (homePref.value.toLowerCase() == "about:home") + return ""; + + // If the pref is actually "", show a blank page. 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:logopage"; + + // 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) 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 }; + document.documentElement.openSubDialog("chrome://browser/content/preferences/selectBookmark.xul", + "resizable", rv); + 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. + if (document.getElementById + ("pref.browser.homepage.disable_button.current_page").locked) + return; + + useCurrent.disabled = !tabs.length + }, + + _getTabsForHomePage: function () + { + var win; + var tabs = []; + if (document.documentElement.instantApply) { + const Cc = Components.classes, Ci = Components.interfaces; + // If we're in instant-apply mode, use the most recent browser window + var wm = Cc["@mozilla.org/appshell/window-mediator;1"] + .getService(Ci.nsIWindowMediator); + win = wm.getMostRecentWindow("navigator:browser"); + } + else { + win = window.opener; + } + + 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); + } + + return tabs; + }, + + /** + * 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.showWhenStarting - bool + * True if the Download Manager should be opened when a download is + * started, false if it shouldn't be opened. + * browser.download.closeWhenDone - bool + * True if the Download Manager should be closed when all downloads + * complete, false if it should be left open. + * 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. + */ + + /** + * Updates preferences which depend upon the value of the preference which + * determines whether the Downloads manager is opened at the start of a + * download. + */ + readShowDownloadsWhenStarting: function () + { + this.showDownloadsWhenStartingPrefChanged(); + + // don't override the preference's value in UI + return undefined; + }, + + /** + * Enables or disables the "close Downloads manager when downloads finished" + * preference element, consequently updating the associated UI. + */ + showDownloadsWhenStartingPrefChanged: function () + { + var showWhenStartingPref = document.getElementById("browser.download.manager.showWhenStarting"); + var closeWhenDonePref = document.getElementById("browser.download.manager.closeWhenDone"); + closeWhenDonePref.disabled = !showWhenStartingPref.value; + }, + + /** + * 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; + chooseFolder.disabled = !preference.value; + + // 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: function () + { + const nsIFilePicker = Components.interfaces.nsIFilePicker; + const nsILocalFile = Components.interfaces.nsILocalFile; + + let bundlePreferences = document.getElementById("bundlePreferences"); + let title = bundlePreferences.getString("chooseDownloadFolderTitle"); + let folderListPref = document.getElementById("browser.download.folderList"); + let currentDirPref = this._indexToFolder(folderListPref.value); // file + let defDownloads = this._indexToFolder(1); // file + let fp = Components.classes["@mozilla.org/filepicker;1"]. + createInstance(nsIFilePicker); + let fpCallback = function fpCallback_done(aResult) { + if (aResult == nsIFilePicker.returnOK) { + let file = fp.file.QueryInterface(nsILocalFile); + let downloadDirPref = document.getElementById("browser.download.dir"); + + downloadDirPref.value = file; + folderListPref.value = this._folderToIndex(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. + } + }.bind(this); + + fp.init(window, title, nsIFilePicker.modeGetFolder); + fp.appendFilters(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 = this._indexToFolder(0); + } + fp.open(fpCallback); + }, + + /** + * Initializes the download folder display settings based on the user's + * preferences. + */ + displayDownloadDirPref: 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(this._indexToFolder(1)); + } else { + // 'Desktop' + downloadFolder.label = bundlePreferences.getString("desktopFolderName"); + iconUrlSpec = fph.getURLSpecFromFile(this._getDownloadsFolder("Desktop")); + } + downloadFolder.image = "moz-icon://" + iconUrlSpec + "?size=16"; + + // don't override the preference's value in UI + return undefined; + }, + + /** + * 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: 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); + break; + case "Downloads": + var dnldMgr = Components.classes["@mozilla.org/download-manager;1"] + .getService(Components.interfaces.nsIDownloadManager); + return dnldMgr.defaultDownloadsDirectory; + break; + } + 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: function (aFolder) + { + if (!aFolder || aFolder.equals(this._getDownloadsFolder("Desktop"))) + return 0; + else if (aFolder.equals(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: function (aIndex) + { + switch (aIndex) { + case 0: + return this._getDownloadsFolder("Desktop"); + case 1: + return this._getDownloadsFolder("Downloads"); + } + var currentDirPref = document.getElementById("browser.download.dir"); + return currentDirPref.value; + }, + + /** + * Returns the value for the browser.download.folderList preference. + */ + getFolderListPref: function () + { + var folderListPref = document.getElementById("browser.download.folderList"); + switch (folderListPref.value) { + case 0: // Desktop + case 1: // Downloads + return folderListPref.value; + break; + case 2: // Custom + var currentDirPref = document.getElementById("browser.download.dir"); + if (currentDirPref.value) { + // Resolve to a known location if possible. We are writing out + // to prefs on this call, so now would be a good time to do it. + return this._folderToIndex(currentDirPref.value); + } + return 0; + break; + } + }, + + /** + * 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 + } + } +#ifdef HAVE_SHELL_SERVICE + , + + // SYSTEM DEFAULTS + + /* + * 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() + { + let shellSvc = getShellService(); + let setDefaultPane = document.getElementById("setDefaultPane"); + if (!shellSvc) { + setDefaultPane.hidden = true; + document.getElementById("alwaysCheckDefault").disabled = true; + return; + } + let selectedIndex = + shellSvc.isDefaultBrowser(false, true) ? 1 : 0; + setDefaultPane.selectedIndex = selectedIndex; + }, + + /** + * Set browser as the operating system default browser. + */ + setDefaultBrowser: function() + { + let shellSvc = getShellService(); + if (!shellSvc) + return; + try { + let claimAllTypes = true; +#ifdef XP_WIN + // In Windows 8+, the UI for selecting default protocol is much + // nicer than the UI for setting file type associations. So we + // only show the protocol association screen on Windows 8+. + // Windows 8 is version 6.2. + let version = Services.sysinfo.getProperty("version"); + claimAllTypes = (parseFloat(version) < 6.2); +#endif + shellSvc.setDefaultBrowser(claimAllTypes, false); + } catch (ex) { + Cu.reportError(ex); + return; + } + let selectedIndex = + shellSvc.isDefaultBrowser(false, true) ? 1 : 0; + document.getElementById("setDefaultPane").selectedIndex = selectedIndex; + } +#endif +}; diff --git a/browser/components/preferences/main.xul b/browser/components/preferences/main.xul new file mode 100644 index 000000000..0943e5580 --- /dev/null +++ b/browser/components/preferences/main.xul @@ -0,0 +1,216 @@ +<?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/. --> + +<!DOCTYPE overlay [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + <!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd"> + <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd"> + %brandDTD; + %mainDTD; + %aboutHomeDTD; +]> + +<overlay id="MainPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <prefpane id="paneMain" + onpaneload="gMainPane.init();" + helpTopic="prefs-main"> + + <script type="application/javascript" src="chrome://browser/content/preferences/main.js"/> + + <preferences id="mainPreferences"> + <!-- XXX Button preferences --> + + <!-- Startup --> + <preference id="browser.startup.page" + name="browser.startup.page" + type="int"/> + <preference id="browser.startup.homepage" + name="browser.startup.homepage" + type="wstring"/> + + <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" + onchange="gMainPane.updateBrowserStartupLastSession();"/> + + <!-- Downloads --> + <preference id="browser.download.manager.showWhenStarting" + name="browser.download.manager.showWhenStarting" + type="bool" + onchange="gMainPane.showDownloadsWhenStartingPrefChanged();"/> + <preference id="browser.download.manager.closeWhenDone" + name="browser.download.manager.closeWhenDone" + type="bool"/> + <preference id="browser.download.useDownloadDir" + name="browser.download.useDownloadDir" + type="bool"/> + <preference id="browser.download.dir" + name="browser.download.dir" + type="file" + onchange="gMainPane.displayDownloadDirPref();"/> + <preference id="browser.download.folderList" name="browser.download.folderList" type="int"/> + <preference id="browser.download.useToolkitUI" name="browser.download.useToolkitUI" type="bool" /> +#ifdef XP_WIN + <preference id="browser.download.saveZoneInformation" name="browser.download.saveZoneInformation" type="int" /> +#endif + +#ifdef HAVE_SHELL_SERVICE + <!-- System Defaults --> + <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 + </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"/> + + <!-- Startup --> + <groupbox id="startupGroup"> + <caption label="&startup.label;"/> + + <hbox align="center"> + <label value="&startupPage.label;" accesskey="&startupPage.accesskey;" + control="browserStartupPage"/> + <menulist id="browserStartupPage" 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> + </hbox> + <separator class="thin"/> + <hbox align="center"> + <label value="&homepage.label;" accesskey="&homepage.accesskey;" control="browserHomePage"/> + <textbox id="browserHomePage" class="padded uri-element" flex="1" + type="autocomplete" autocompletesearch="history" + onsyncfrompreference="return gMainPane.syncFromHomePref();" + onsynctopreference="return gMainPane.syncToHomePref(this.value);" + oninput="gNewtabUrl.writeNewtabUrl(null, this.value);" + placeholder="&abouthome.pageTitle;" + preference="browser.startup.homepage"/> + </hbox> + <hbox align="center" pack="end"> + <button label="" accesskey="&useCurrentPage.accesskey;" + label1="&useCurrentPage.label;" + label2="&useMultiple.label;" + oncommand="gMainPane.setHomePageToCurrent(); gNewtabUrl.writeNewtabUrl();" + id="useCurrent" + preference="pref.browser.homepage.disable_button.current_page"/> + <button label="&chooseBookmark.label;" accesskey="&chooseBookmark.accesskey;" + oncommand="gMainPane.setHomePageToBookmark(); gNewtabUrl.writeNewtabUrl();" + id="useBookmark" + preference="pref.browser.homepage.disable_button.bookmark_page"/> + <button label="&restoreDefault.label;" accesskey="&restoreDefault.accesskey;" + oncommand="gMainPane.restoreDefaultHomePage(); gNewtabUrl.writeNewtabUrl();" + id="restoreDefaultHomePage" + preference="pref.browser.homepage.disable_button.restore_default"/> + </hbox> + </groupbox> + + <!-- Downloads --> + <groupbox id="downloadsGroup"> + <caption label="&downloads.label;"/> + + <checkbox id="showWhenDownloading" label="&showWhenDownloading.label;" + accesskey="&showWhenDownloading.accesskey;" + preference="browser.download.manager.showWhenStarting" + onsyncfrompreference="return gMainPane.readShowDownloadsWhenStarting();"/> + <checkbox id="closeWhenDone" label="&closeWhenDone.label;" + accesskey="&closeWhenDone.accesskey;" class="indent" + preference="browser.download.manager.closeWhenDone"/> + + <separator class="thin"/> + + <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();" + onsynctopreference="return gMainPane.getFolderListPref()"/> + <button id="chooseFolder" oncommand="gMainPane.chooseFolder();" + accesskey="&chooseFolderWin.accesskey;" + label="&chooseFolderWin.label;" + preference="browser.download.folderList" + onsynctopreference="return gMainPane.getFolderListPref();"/> + </hbox> + <radio id="alwaysAsk" value="false" + label="&alwaysAsk.label;" + accesskey="&alwaysAsk.accesskey;"/> + </radiogroup> +#if 0 +<!-- Disabled for now -- ToolkitUI DM is nonfunctional. --> + <checkbox id="classicDownloadWindow" + preference="browser.download.useToolkitUI" + label="&toolkit.classic.download.window.label;" /> +#endif +#ifdef XP_WIN + <hbox align="center"> + <label id="zoneInfoLabel" control="zoneInfo-menu">&zoneInfo.label;</label> + <menulist id="zoneInfo-menu" + preference="browser.download.saveZoneInformation" + sizetopopup="always"> + <menupopup> + <menuitem label="&zoneInfo.never;" value="0" /> + <menuitem label="&zoneInfo.always;" value="1" /> + <menuitem label="&zoneInfo.system;" value="2" /> + </menupopup> + </menulist> + </hbox> +#endif + </groupbox> + +#ifdef HAVE_SHELL_SERVICE + <!-- System Defaults --> + <groupbox id="systemDefaultsGroup" orient="vertical"> + <caption label="&systemDefaults.label;"/> + + <checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser" + label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;" + flex="1"/> + <hbox class="indent"> + <deck id="setDefaultPane"> + <button id="setDefaultButton" + label="&setDefault.label;" accesskey="&setDefault.accesskey;" + oncommand="gMainPane.setDefaultBrowser();" + preference="pref.general.disable_button.default_browser"/> + <description>&isDefault.label;</description> + </deck> + </hbox> + </groupbox> +#endif + </prefpane> + +</overlay> diff --git a/browser/components/preferences/moz.build b/browser/components/preferences/moz.build new file mode 100644 index 000000000..c888607f0 --- /dev/null +++ b/browser/components/preferences/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# 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'): + DEFINES['HAVE_SHELL_SERVICE'] = 1 + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/browser/components/preferences/newtaburl.js b/browser/components/preferences/newtaburl.js new file mode 100644 index 000000000..f9103f00d --- /dev/null +++ b/browser/components/preferences/newtaburl.js @@ -0,0 +1,102 @@ +// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+var gNewtabUrl = {
+ /**
+ * Writes browser.newtab.url with the appropriate value.
+ * If the choice is "my home page", get and sanitize
+ * the browser home page URL to make it suitable for newtab use.
+ *
+ * Called from prefwindow ondialogaccept in preferences.xul,
+ * newtabPage oncommand in tabs.xul, browserHomePage oninput,
+ * useCurrent, useBookmark and restoreDefaultHomePage oncommand
+ * in main.xul to consider instantApply.
+ */
+ writeNewtabUrl: function(newtabUrlChoice, browserHomepageUrl) {
+ try {
+ if (newtabUrlChoice) {
+ if (Services.prefs.getBoolPref("browser.preferences.instantApply")) {
+ newtabUrlChoice = parseInt(newtabUrlChoice);
+ } else {
+ return;
+ }
+ } else {
+ if (this.newtabUrlChoiceIsSet) {
+ newtabUrlChoice = Services.prefs.getIntPref("browser.newtab.choice");
+ } else {
+ newtabUrlChoice = this.getNewtabChoice();
+ }
+ }
+ if (browserHomepageUrl || browserHomepageUrl == "") {
+ if (Services.prefs.getBoolPref("browser.preferences.instantApply")) {
+ if (browserHomepageUrl == "") {
+ browserHomepageUrl = "about:home";
+ }
+ } else {
+ return;
+ }
+ } else {
+ browserHomepageUrl = Services.prefs.getComplexValue("browser.startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ }
+ let newtabUrlPref = Services.prefs.getCharPref("browser.newtab.url");
+ switch (newtabUrlChoice) {
+ case 1:
+ newtabUrlPref = "about:logopage";
+ break;
+ case 2:
+ newtabUrlPref = Services.prefs.getDefaultBranch("browser.")
+ .getComplexValue("startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ break;
+ case 3:
+ // If url is a pipe-delimited set of pages, just take the first one.
+ let newtabUrlSanitizedPref=browserHomepageUrl.split("|")[0];
+ // XXX: do we need extra sanitation here, e.g. for invalid URLs?
+ Services.prefs.setCharPref("browser.newtab.myhome", newtabUrlSanitizedPref);
+ newtabUrlPref = newtabUrlSanitizedPref;
+ break;
+ case 4:
+ newtabUrlPref = "about:newtab";
+ break;
+ default:
+ // In case of any other value it's a custom URL, consider instantApply.
+ if (this.newtabPageCustom) {
+ newtabUrlPref = this.newtabPageCustom;
+ }
+ }
+ Services.prefs.setCharPref("browser.newtab.url",newtabUrlPref);
+ } catch(e) { console.error(e); }
+ },
+
+ /**
+ * Determines the value of browser.newtab.choice based
+ * on the value of browser.newtab.url
+ *
+ * @returns the value of browser.newtab.choice
+ */
+ getNewtabChoice: function() {
+ let newtabUrlPref = Services.prefs.getCharPref("browser.newtab.url");
+ let browserHomepageUrl = Services.prefs.getComplexValue("browser.startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ let newtabUrlSanitizedPref = browserHomepageUrl.split("|")[0];
+ let defaultStartupHomepage = Services.prefs.getDefaultBranch("browser.")
+ .getComplexValue("startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ switch (newtabUrlPref) {
+ case "about:logopage":
+ return 1;
+ case defaultStartupHomepage:
+ return 2;
+ case newtabUrlSanitizedPref:
+ return 3;
+ case "about:newtab":
+ return 4;
+ default: // Custom URL entered.
+ // We need this to consider instantApply.
+ this.newtabPageCustom = newtabUrlPref;
+ return 0;
+ }
+ }
+};
diff --git a/browser/components/preferences/permissions.js b/browser/components/preferences/permissions.js new file mode 100644 index 000000000..a3c7c1b48 --- /dev/null +++ b/browser/components/preferences/permissions.js @@ -0,0 +1,459 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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"); + +const nsIPermissionManager = Components.interfaces.nsIPermissionManager; +const nsICookiePermission = Components.interfaces.nsICookiePermission; + +const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions"; + +function Permission(principal, type, capability) +{ + this.principal = principal; + this.origin = principal.origin; + this.type = type; + this.capability = capability; +} + +var gPermissionManager = { + _type : "", + _permissions : [], + _permissionsToAdd : new Map(), + _permissionsToDelete : new Map(), + _bundle : null, + _tree : null, + _observerRemoved : false, + + _view: { + _rowCount: 0, + get rowCount() + { + return this._rowCount; + }, + getCellText: function (aRow, aColumn) + { + if (aColumn.id == "siteCol") + return gPermissionManager._permissions[aRow].origin; + else if (aColumn.id == "statusCol") + return gPermissionManager._permissions[aRow].capability; + return ""; + }, + + isSeparator: function(aIndex) { return false; }, + isSorted: function() { return false; }, + isContainer: function(aIndex) { return false; }, + setTree: function(aTree){}, + getImageSrc: function(aRow, aColumn) {}, + getProgressMode: function(aRow, aColumn) {}, + getCellValue: function(aRow, aColumn) {}, + cycleHeader: function(column) {}, + getRowProperties: function(row){ return ""; }, + getColumnProperties: function(column){ return ""; }, + getCellProperties: function(row,column){ + if (column.element.getAttribute("id") == "siteCol") + return "ltr"; + + return ""; + } + }, + + _getCapabilityString: function (aCapability) + { + var stringKey = null; + switch (aCapability) { + case nsIPermissionManager.ALLOW_ACTION: + stringKey = "can"; + break; + case nsIPermissionManager.DENY_ACTION: + stringKey = "cannot"; + break; + case nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY: + stringKey = "canAccessFirstParty"; + break; + case nsICookiePermission.ACCESS_SESSION: + stringKey = "canSession"; + break; + } + return this._bundle.getString(stringKey); + }, + + addPermission: function (aCapability) + { + var textbox = document.getElementById("url"); + var input_url = textbox.value.replace(/^\s*/, ""); // trim any leading space + let principal; + try { + // The origin accessor on the principal object will throw if the + // principal doesn't have a canonical origin representation. This will + // help catch cases where the URI parser parsed something like + // `localhost:8080` as having the scheme `localhost`, rather than being + // an invalid URI. A canonical origin representation is required by the + // permission manager for storage, so this won't prevent any valid + // permissions from being entered by the user. + let uri; + try { + uri = Services.io.newURI(input_url, null, null); + principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri); + // If we have ended up with an unknown scheme, the following will throw. + principal.origin; + } catch(ex) { + uri = Services.io.newURI("http://" + input_url, null, null); + principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri); + // If we have ended up with an unknown scheme, the following will throw. + principal.origin; + } + } catch(ex) { + var message = this._bundle.getString("invalidURI"); + var title = this._bundle.getString("invalidURITitle"); + Services.prompt.alert(window, title, message); + return; + } + + var capabilityString = this._getCapabilityString(aCapability); + + // check whether the permission already exists, if not, add it + let permissionExists = false; + let capabilityExists = false; + for (var i = 0; i < this._permissions.length; ++i) { + if (this._permissions[i].principal.equals(principal)) { + permissionExists = true; + capabilityExists = this._permissions[i].capability == capabilityString; + if (!capabilityExists) { + this._permissions[i].capability = capabilityString; + } + break; + } + } + + + let permissionParams = {principal: principal, type: this._type, capability: aCapability}; + if (!permissionExists) { + this._permissionsToAdd.set(principal.origin, permissionParams); + this._addPermission(permissionParams); + } + else if (!capabilityExists) { + this._permissionsToAdd.set(principal.origin, permissionParams); + this._handleCapabilityChange(); + } + + textbox.value = ""; + textbox.focus(); + + // covers a case where the site exists already, so the buttons don't disable + this.onHostInput(textbox); + + // enable "remove all" button as needed + document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0; + }, + + _removePermission: function(aPermission) + { + this._removePermissionFromList(aPermission.principal); + + // If this permission was added during this session, let's remove + // it from the pending adds list to prevent calls to the + // permission manager. + let isNewPermission = this._permissionsToAdd.delete(aPermission.principal.origin); + + if (!isNewPermission) { + this._permissionsToDelete.set(aPermission.principal.origin, aPermission); + } + + }, + + _handleCapabilityChange: function () + { + // Re-do the sort, if the status changed from Block to Allow + // or vice versa, since if we're sorted on status, we may no + // longer be in order. + if (this._lastPermissionSortColumn == "statusCol") { + this._resortPermissions(); + } + this._tree.treeBoxObject.invalidate(); + }, + + _addPermission: function(aPermission) + { + this._addPermissionToList(aPermission); + ++this._view._rowCount; + this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1); + // Re-do the sort, since we inserted this new item at the end. + this._resortPermissions(); + }, + + _resortPermissions: function() + { + gTreeUtils.sort(this._tree, this._view, this._permissions, + this._lastPermissionSortColumn, + this._permissionsComparator, + this._lastPermissionSortColumn, + !this._lastPermissionSortAscending); // keep sort direction + }, + + onHostInput: function (aSiteField) + { + document.getElementById("btnSession").disabled = !aSiteField.value; + document.getElementById("btnBlock").disabled = !aSiteField.value; + document.getElementById("btnAllow").disabled = !aSiteField.value; + }, + + onWindowKeyPress: function (aEvent) + { + if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) + window.close(); + }, + + onHostKeyPress: function (aEvent) + { + if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) + document.getElementById("btnAllow").click(); + }, + + onLoad: function () + { + this._bundle = document.getElementById("bundlePreferences"); + var params = window.arguments[0]; + this.init(params); + }, + + init: function (aParams) + { + if (this._type) { + // reusing an open dialog, clear the old observer + this.uninit(); + } + + this._type = aParams.permissionType; + this._manageCapability = aParams.manageCapability; + + var permissionsText = document.getElementById("permissionsText"); + while (permissionsText.hasChildNodes()) + permissionsText.removeChild(permissionsText.firstChild); + permissionsText.appendChild(document.createTextNode(aParams.introText)); + + document.title = aParams.windowTitle; + + document.getElementById("btnBlock").hidden = !aParams.blockVisible; + document.getElementById("btnSession").hidden = !aParams.sessionVisible; + document.getElementById("btnAllow").hidden = !aParams.allowVisible; + + var urlFieldVisible = (aParams.blockVisible || aParams.sessionVisible || aParams.allowVisible); + + var urlField = document.getElementById("url"); + urlField.value = aParams.prefilledHost; + urlField.hidden = !urlFieldVisible; + + this.onHostInput(urlField); + + var urlLabel = document.getElementById("urlLabel"); + urlLabel.hidden = !urlFieldVisible; + + let treecols = document.getElementsByTagName("treecols")[0]; + treecols.addEventListener("click", event => { + if (event.target.nodeName != "treecol" || event.button != 0) { + return; + } + + let sortField = event.target.getAttribute("data-field-name"); + if (!sortField) { + return; + } + + gPermissionManager.onPermissionSort(sortField); + }); + + Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type); + Services.obs.addObserver(this, "perm-changed", false); + + this._loadPermissions(); + + urlField.focus(); + }, + + uninit: function () + { + if (!this._observerRemoved) { + Services.obs.removeObserver(this, "perm-changed"); + + this._observerRemoved = true; + } + }, + + observe: function (aSubject, aTopic, aData) + { + if (aTopic == "perm-changed") { + var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission); + + // Ignore unrelated permission types. + if (permission.type != this._type) + return; + + if (aData == "added") { + this._addPermission(permission); + } + else if (aData == "changed") { + for (var i = 0; i < this._permissions.length; ++i) { + if (permission.matches(this._permissions[i].principal, true)) { + this._permissions[i].capability = this._getCapabilityString(permission.capability); + break; + } + } + this._handleCapabilityChange(); + } + else if (aData == "deleted") { + this._removePermissionFromList(permission.principal); + } + } + }, + + onPermissionSelected: function () + { + var hasSelection = this._tree.view.selection.count > 0; + var hasRows = this._tree.view.rowCount > 0; + document.getElementById("removePermission").disabled = !hasRows || !hasSelection; + document.getElementById("removeAllPermissions").disabled = !hasRows; + }, + + onPermissionDeleted: function () + { + if (!this._view.rowCount) + return; + var removedPermissions = []; + gTreeUtils.deleteSelectedItems(this._tree, this._view, this._permissions, removedPermissions); + for (var i = 0; i < removedPermissions.length; ++i) { + var p = removedPermissions[i]; + this._removePermission(p); + } + document.getElementById("removePermission").disabled = !this._permissions.length; + document.getElementById("removeAllPermissions").disabled = !this._permissions.length; + }, + + onAllPermissionsDeleted: function () + { + if (!this._view.rowCount) + return; + var removedPermissions = []; + gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions); + for (var i = 0; i < removedPermissions.length; ++i) { + var p = removedPermissions[i]; + this._removePermission(p); + } + document.getElementById("removePermission").disabled = true; + document.getElementById("removeAllPermissions").disabled = true; + }, + + onPermissionKeyPress: function (aEvent) + { + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) { + this.onPermissionDeleted(); + } + }, + + _lastPermissionSortColumn: "", + _lastPermissionSortAscending: false, + _permissionsComparator : function (a, b) + { + return a.toLowerCase().localeCompare(b.toLowerCase()); + }, + + + onPermissionSort: function (aColumn) + { + this._lastPermissionSortAscending = gTreeUtils.sort(this._tree, + this._view, + this._permissions, + aColumn, + this._permissionsComparator, + this._lastPermissionSortColumn, + this._lastPermissionSortAscending); + this._lastPermissionSortColumn = aColumn; + }, + + onApplyChanges: function() + { + // Stop observing permission changes since we are about + // to write out the pending adds/deletes and don't need + // to update the UI + this.uninit(); + + for (let permissionParams of this._permissionsToAdd.values()) { + Services.perms.addFromPrincipal(permissionParams.principal, permissionParams.type, permissionParams.capability); + } + + for (let p of this._permissionsToDelete.values()) { + Services.perms.removeFromPrincipal(p.principal, p.type); + } + + window.close(); + }, + + _loadPermissions: function () + { + this._tree = document.getElementById("permissionsTree"); + this._permissions = []; + + // load permissions into a table + var count = 0; + var enumerator = Services.perms.enumerator; + while (enumerator.hasMoreElements()) { + var nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission); + this._addPermissionToList(nextPermission); + } + + this._view._rowCount = this._permissions.length; + + // sort and display the table + this._tree.view = this._view; + this.onPermissionSort("origin"); + + // disable "remove all" button if there are none + document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0; + }, + + _addPermissionToList: function (aPermission) + { + if (aPermission.type == this._type && + (!this._manageCapability || + (aPermission.capability == this._manageCapability))) { + + var principal = aPermission.principal; + var capabilityString = this._getCapabilityString(aPermission.capability); + var p = new Permission(principal, + aPermission.type, + capabilityString); + this._permissions.push(p); + } + }, + + _removePermissionFromList: function (aPrincipal) + { + for (let i = 0; i < this._permissions.length; ++i) { + if (this._permissions[i].principal.equals(aPrincipal)) { + this._permissions.splice(i, 1); + this._view._rowCount--; + this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1); + this._tree.treeBoxObject.invalidate(); + break; + } + } + }, + + setOrigin: function (aOrigin) + { + document.getElementById("url").value = aOrigin; + } +}; + +function setOrigin(aOrigin) +{ + gPermissionManager.setOrigin(aOrigin); +} + +function initWithParams(aParams) +{ + gPermissionManager.init(aParams); +} + diff --git a/browser/components/preferences/permissions.xul b/browser/components/preferences/permissions.xul new file mode 100644 index 000000000..33806cc27 --- /dev/null +++ b/browser/components/preferences/permissions.xul @@ -0,0 +1,85 @@ +<?xml version="1.0"?> + +<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- --> +<!-- 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/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/permissions.dtd" > + +<window id="PermissionsDialog" class="windowDialog" + windowtype="Browser:Permissions" + title="&window.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: &window.width;;" + onload="gPermissionManager.onLoad();" + onunload="gPermissionManager.uninit();" + persist="screenX screenY width height" + onkeypress="gPermissionManager.onWindowKeyPress(event);"> + + <script src="chrome://global/content/treeUtils.js"/> + <script src="chrome://browser/content/preferences/permissions.js"/> + + <stringbundle id="bundlePreferences" + src="chrome://browser/locale/preferences/preferences.properties"/> + + <keyset> + <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/> + </keyset> + + <vbox class="contentPane" flex="1"> + <description id="permissionsText" control="url"/> + <separator class="thin"/> + <label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/> + <hbox align="start"> + <textbox id="url" flex="1" + oninput="gPermissionManager.onHostInput(event.target);" + onkeypress="gPermissionManager.onHostKeyPress(event);"/> + </hbox> + <hbox pack="end"> + <button id="btnBlock" disabled="true" label="&block.label;" accesskey="&block.accesskey;" + oncommand="gPermissionManager.addPermission(nsIPermissionManager.DENY_ACTION);"/> + <button id="btnSession" disabled="true" label="&session.label;" accesskey="&session.accesskey;" + oncommand="gPermissionManager.addPermission(nsICookiePermission.ACCESS_SESSION);"/> + <button id="btnAllow" disabled="true" label="&allow.label;" default="true" accesskey="&allow.accesskey;" + oncommand="gPermissionManager.addPermission(nsIPermissionManager.ALLOW_ACTION);"/> + </hbox> + <separator class="thin"/> + <tree id="permissionsTree" flex="1" style="height: 18em;" + hidecolumnpicker="true" + onkeypress="gPermissionManager.onPermissionKeyPress(event)" + onselect="gPermissionManager.onPermissionSelected();"> + <treecols> + <treecol id="siteCol" label="&treehead.sitename.label;" flex="3" + data-field-name="origin" persist="width"/> + <splitter class="tree-splitter"/> + <treecol id="statusCol" label="&treehead.status.label;" flex="1" + data-field-name="capability" persist="width"/> + </treecols> + <treechildren/> + </tree> + </vbox> + <vbox> + <hbox class="actionButtons" align="left" flex="1"> + <button id="removePermission" disabled="true" + accesskey="&removepermission.accesskey;" + icon="remove" label="&removepermission.label;" + oncommand="gPermissionManager.onPermissionDeleted();"/> + <button id="removeAllPermissions" + icon="clear" label="&removeallpermissions.label;" + accesskey="&removeallpermissions.accesskey;" + oncommand="gPermissionManager.onAllPermissionsDeleted();"/> + </hbox> + <spacer flex="1"/> + <hbox class="actionButtons" align="right" flex="1"> + <button oncommand="close();" icon="close" + label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" /> + <button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save" + label="&button.ok.label;" accesskey="&button.ok.accesskey;"/> + </hbox> + <resizer type="window" dir="bottomend"/> + </vbox> +</window> diff --git a/browser/components/preferences/preferences.xul b/browser/components/preferences/preferences.xul new file mode 100644 index 000000000..b56b16ecc --- /dev/null +++ b/browser/components/preferences/preferences.xul @@ -0,0 +1,77 @@ +<?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://mozapps/content/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> + +<!-- XXX This should be in applications.xul, but bug 393953 means putting it + - there causes the Applications pane not to work the first time you open + - the Preferences dialog in a browsing session, so we work around the problem + - by putting it here instead. + --> +<?xml-stylesheet href="chrome://browser/content/preferences/handlers.css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?> + +<!DOCTYPE prefwindow [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +<!ENTITY % preferencesDTD SYSTEM "chrome://browser/locale/preferences/preferences.dtd"> +%brandDTD; +%preferencesDTD; +]> + +#ifdef XP_WIN +#define USE_WIN_TITLE_STYLE +#endif + +<prefwindow type="prefwindow" + id="BrowserPreferences" + windowtype="Browser:Preferences" + ondialoghelp="openPrefsHelp()" +#ifdef USE_WIN_TITLE_STYLE + title="&prefWindow.titleWin;" +#else +#ifdef XP_UNIX + title="&prefWindow.titleGNOME;" +#endif +#endif + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" +#ifdef USE_WIN_TITLE_STYLE + style="&prefWinMinSize.styleWin2;" +#else + style="&prefWinMinSize.styleGNOME;" +#endif + onunload="if (typeof gSecurityPane != 'undefined') gSecurityPane.syncAddonSecurityLevel();" + ondialogaccept="gNewtabUrl.writeNewtabUrl();"> + + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + <script type="application/javascript" src="chrome://browser/content/preferences/newtaburl.js"/> + + <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/> + <stringbundle id="bundlePreferences" + src="chrome://browser/locale/preferences/preferences.properties"/> + + <prefpane id="paneMain" label="&paneGeneral.title;" + src="chrome://browser/content/preferences/main.xul"/> + <prefpane id="paneTabs" label="&paneTabs.title;" + src="chrome://browser/content/preferences/tabs.xul"/> + <prefpane id="paneContent" label="&paneContent.title;" + src="chrome://browser/content/preferences/content.xul"/> + <prefpane id="paneApplications" label="&paneApplications.title;" + src="chrome://browser/content/preferences/applications.xul"/> + <prefpane id="panePrivacy" label="&panePrivacy.title;" + src="chrome://browser/content/preferences/privacy.xul"/> + <prefpane id="paneSecurity" label="&paneSecurity.title;" + src="chrome://browser/content/preferences/security.xul"/> +#ifdef MOZ_SERVICES_SYNC + <prefpane id="paneSync" label="&paneSync.title;" + src="chrome://browser/content/preferences/sync.xul"/> +#endif + <prefpane id="paneAdvanced" label="&paneAdvanced.title;" + src="chrome://browser/content/preferences/advanced.xul"/> + +</prefwindow> + diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js new file mode 100644 index 000000000..05ed3bcdd --- /dev/null +++ b/browser/components/preferences/privacy.js @@ -0,0 +1,458 @@ +// 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"); + +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, + + /** + * Sets up the UI for the number of days of history to keep, and updates the + * label of the "Clear Now..." button. + */ + init: function() + { + this._updateSanitizeSettingsButton(); + this.initializeHistoryMode(); + this.updateHistoryModePane(); + this.updatePrivacyMicroControls(); + this.initAutoStartPrivateBrowsingReverter(); + }, + + // 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 have their default values, 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 append their IDs to this array if needed. + */ + prefsForDefault: [ + "places.history.enabled", + "browser.formfill.enable", + "network.cookie.cookieBehavior", + "network.cookie.lifetimePolicy", + "privacy.sanitize.sanitizeOnShutdown" + ], + + /** + * 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 all the preferences values are set to their default values + * + * @param aPrefs an array of pref names to check for + * @returns boolean true if all of the prefs are set to their default values, + * false otherwise + */ + _checkDefaultValues: function(aPrefs) { + for (let i = 0; i < aPrefs.length; ++i) { + let pref = document.getElementById(aPrefs[i]); + if (pref.value != pref.defaultValue) + return false; + } + return true; + }, + + /** + * Initialize the history mode menulist based on the privacy preferences + */ + initializeHistoryMode: function() + { + let mode; + let getVal = function(aPref) + document.getElementById(aPref).value; + + if (this._checkDefaultValues(this.prefsForDefault)) { + 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() + { + 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() + { + 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 + document.getElementById("places.history.enabled").value = true; + + // select the remember forms history option + document.getElementById("browser.formfill.enable").value = true; + + // select the accept 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() + { + if (document.getElementById("historyMode").value == "custom") { + let disabled = this._autoStartPrivateBrowsing = + document.getElementById("privateBrowsingAutoStart").checked; + this.dependentControls + .forEach(function(aElement) + document.getElementById(aElement).disabled = disabled); + + const Ci = Components.interfaces; + // 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() + { + let mode = document.getElementById("historyMode"); + let autoStart = document.getElementById("privateBrowsingAutoStart"); + this._lastMode = mode.selectedIndex; + this._lastCheckState = autoStart.hasAttribute('checked'); + }, + + _lastMode: null, + _lasCheckState: null, + updateAutostart: function() { + 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; + } + + const Cc = Components.classes, Ci = Components.interfaces; + let brandName = document.getElementById("bundleBrand").getString("brandShortName"); + let bundle = document.getElementById("bundlePreferences"); + let msg = bundle.getFormattedString(autoStart.checked ? + "featureEnableRequiresRestart" : "featureDisableRequiresRestart", + [brandName]); + let title = bundle.getFormattedString("shouldRestartTitle", [brandName]); + let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService); + let shouldProceed = prompts.confirm(window, title, msg) + if (shouldProceed) { + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", + "restart"); + shouldProceed = !cancelQuit.data; + + if (shouldProceed) { + pref.value = autoStart.hasAttribute('checked'); + document.documentElement.acceptDialog(); + 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'); + } + mode.selectedIndex = this._lastMode; + mode.doCommand(); + + this._shouldPromptForRestart = true; + }, + + // 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") }; + document.documentElement.openWindow("Browser:Permissions", + "chrome://browser/content/preferences/permissions.xul", + "", params); + }, + + /** + * Displays all the user's cookies in a dialog. + */ + showCookies: function(aCategory) + { + document.documentElement.openWindow("Browser:Cookies", + "chrome://browser/content/preferences/cookies.xul", + "", null); + }, + + // 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() + { + document.documentElement.openSubDialog("chrome://browser/content/preferences/sanitize.xul", + "", null); + }, + + + /** + * 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; + + const Cc = Components.classes, Ci = Components.interfaces; + var glue = Cc["@mozilla.org/browser/browserglue;1"] + .getService(Ci.nsIBrowserGlue); + glue.sanitize(window); + + // 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; + } + +}; diff --git a/browser/components/preferences/privacy.xul b/browser/components/preferences/privacy.xul new file mode 100644 index 000000000..e5175422d --- /dev/null +++ b/browser/components/preferences/privacy.xul @@ -0,0 +1,256 @@ +<?xml version="1.0"?> + +<!-- -*- Mode: XML; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- --> +<!-- 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/. --> + +<!DOCTYPE overlay [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd"> +%brandDTD; +%privacyDTD; +]> + +<overlay id="PrivacyPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <prefpane id="panePrivacy" + onpaneload="gPrivacyPane.init();" + helpTopic="prefs-privacy"> + + <preferences id="privacyPreferences"> + + <!-- Global Privacy Control --> + <preference id="privacy.GPCheader.enabled" + name="privacy.GPCheader.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"/> + + <!-- 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" + onchange="gPrivacyPane._updateSanitizeSettingsButton();" + type="bool"/> + <preference id="privacy.sanitize.timeSpan" + name="privacy.sanitize.timeSpan" + type="int"/> + + <!-- Private Browsing --> + <preference id="browser.privatebrowsing.autostart" + name="browser.privatebrowsing.autostart" + onchange="gPrivacyPane.updatePrivacyMicroControls();" + type="bool"/> + + </preferences> + + <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> + + <script type="application/javascript" src="chrome://browser/content/preferences/privacy.js"/> + + <!-- History --> + <groupbox id="historyPanel"> + <caption>&history.label;</caption> + <hbox align="center"> + <label id="historyModeLabel" + control="historyMode" + accesskey="&historyHeader.pre.accesskey;">&historyHeader.pre.label;</label> + <menulist id="historyMode" + oncommand="gPrivacyPane.updateHistoryModePane(); + gPrivacyPane.updateHistoryModePrefs(); + gPrivacyPane.updatePrivacyMicroControls(); + gPrivacyPane.updateAutostart();"> + <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 align="center" id="historyRememberPane"> + <hbox align="center" flex="1"> + <spacer flex="1" class="indent"/> + <vbox flex="2"> + <description>&rememberDescription.label;</description> + <separator/> + <description>&rememberActions.pre.label;<html:a + class="inline-link" href="#" + onclick="gPrivacyPane.clearPrivateDataNow(false); return false;" + >&rememberActions.clearHistory.label;</html:a>&rememberActions.middle.label;<html:a + class="inline-link" href="#" + onclick="gPrivacyPane.showCookies(); return false;" + >&rememberActions.removeCookies.label;</html:a>&rememberActions.post.label;</description> + </vbox> + <spacer flex="1" class="indent"/> + </hbox> + </vbox> + <vbox align="center" id="historyDontRememberPane"> + <hbox align="center" flex="1"> + <spacer flex="1" class="indent"/> + <vbox flex="2"> + <description>&dontrememberDescription.label;</description> + <separator/> + <description>&dontrememberActions.pre.label;<html:a + class="inline-link" href="#" + onclick="gPrivacyPane.clearPrivateDataNow(true); return false;" + >&dontrememberActions.clearHistory.label;</html:a>&dontrememberActions.post.label;</description> + </vbox> + <spacer flex="1" class="indent"/> + </hbox> + </vbox> + <vbox id="historyCustomPane"> + <separator class="thin"/> + <checkbox id="privateBrowsingAutoStart" class="indent" + label="&privateBrowsingPermanent2.label;" + accesskey="&privateBrowsingPermanent2.accesskey;" + preference="browser.privatebrowsing.autostart" + oncommand="gPrivacyPane.updateAutostart()"/> + + <vbox class="indent"> + <vbox class="indent"> + <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"/> + <hbox id="cookiesBox"> + <checkbox id="acceptCookies" label="&acceptCookies.label;" flex="1" + preference="network.cookie.cookieBehavior" + accesskey="&acceptCookies.accesskey;" + onsyncfrompreference="return gPrivacyPane.readAcceptCookies();" + onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/> + <button id="cookieExceptions" oncommand="gPrivacyPane.showCookieExceptions();" + label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;" + preference="pref.privacy.disable_button.cookie_exceptions"/> + </hbox> + <hbox id="acceptThirdPartyRow" class="indent"> + <hbox id="acceptThirdPartyBox" 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> + + <hbox id="keepRow" class="indent"> + <hbox id="keepBox" 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> + </hbox> + <hbox flex="1"/> + <button id="showCookiesButton" + label="&showCookies.label;" accesskey="&showCookies.accesskey;" + oncommand="gPrivacyPane.showCookies();" + preference="pref.privacy.disable_button.view_cookies"/> + </hbox> + + <hbox id="clearDataBox" align="center"> + <checkbox id="alwaysClear" flex="1" + preference="privacy.sanitize.sanitizeOnShutdown" + label="&clearOnClose.label;" + accesskey="&clearOnClose.accesskey;"/> + <button id="clearDataSettings" label="&clearOnCloseSettings.label;" + accesskey="&clearOnCloseSettings.accesskey;" + oncommand="gPrivacyPane.showClearPrivateDataSettings();"/> + </hbox> + </vbox> + </vbox> + </vbox> + </deck> + + </groupbox> + + <!-- Global Privacy Control --> + <groupbox id="dataPrivacyPanel"> + <caption>&dataPrivacy.label;</caption> + <hbox align="center"> + <checkbox id="privacyGPCCheckbox" + label="&sendGPCheader.label;" + accesskey="&sendGPCheader.accesskey;" + preference="privacy.GPCheader.enabled"/> + <separator class="thin"/> + <label class="text-link" id="GPCInfo" + href="https://www.palemoon.org/support/global-privacy-control" + value="&GPCInfo.label;"/> + + </hbox> + </groupbox> + + <!-- Location Bar --> + <groupbox id="locatioBarPanel"> + <caption>&locationBar.label;</caption> + + <label id="locationBarSuggestionLabel">&locbar.suggest.label;</label> + <hbox id="tabPrefsBox" align="center" flex="1"> + <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"/> + </hbox> + + </groupbox> + + </prefpane> + +</overlay> diff --git a/browser/components/preferences/sanitize.js b/browser/components/preferences/sanitize.js new file mode 100644 index 000000000..4383bee4f --- /dev/null +++ b/browser/components/preferences/sanitize.js @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +var gSanitizeDialog = Object.freeze({ + onClearHistoryChanged: function () { + let downloadsPref = document.getElementById("privacy.clearOnShutdown.downloads"); + let historyPref = document.getElementById("privacy.clearOnShutdown.history"); + downloadsPref.value = historyPref.value; + } +}); diff --git a/browser/components/preferences/sanitize.xul b/browser/components/preferences/sanitize.xul new file mode 100644 index 000000000..829b5dfc8 --- /dev/null +++ b/browser/components/preferences/sanitize.xul @@ -0,0 +1,108 @@ +<?xml version="1.0"?> + +<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- --> +<!-- 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/"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> + +<!DOCTYPE dialog [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd"> + %brandDTD; + %sanitizeDTD; +]> + +<prefwindow id="SanitizeDialog" type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + dlgbuttons="accept,cancel,help" + ondialoghelp="openPrefsHelp()" + style="width: &dialog.width2;;" + title="&sanitizePrefs2.title;" + onload="gSanitizeDialog.onClearHistoryChanged();"> + + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + <script type="application/javascript" src="chrome://browser/content/preferences/sanitize.js"/> + + <prefpane id="SanitizeDialogPane" + helpTopic="prefs-clear-private-data"> + + <preferences> + <preference id="privacy.clearOnShutdown.history" name="privacy.clearOnShutdown.history" type="bool" + onchange="return gSanitizeDialog.onClearHistoryChanged();"/> + <preference id="privacy.clearOnShutdown.formdata" name="privacy.clearOnShutdown.formdata" type="bool"/> + <preference id="privacy.clearOnShutdown.passwords" name="privacy.clearOnShutdown.passwords" type="bool"/> + <preference id="privacy.clearOnShutdown.downloads" name="privacy.clearOnShutdown.downloads" type="bool"/> + <preference id="privacy.clearOnShutdown.cookies" name="privacy.clearOnShutdown.cookies" type="bool"/> + <preference id="privacy.clearOnShutdown.cache" name="privacy.clearOnShutdown.cache" type="bool"/> + <preference id="privacy.clearOnShutdown.offlineApps" name="privacy.clearOnShutdown.offlineApps" type="bool"/> + <preference id="privacy.clearOnShutdown.sessions" name="privacy.clearOnShutdown.sessions" type="bool"/> + <preference id="privacy.clearOnShutdown.siteSettings" name="privacy.clearOnShutdown.siteSettings" type="bool"/> + <preference id="privacy.clearOnShutdown.connectivityData" name="privacy.clearOnShutdown.connectivityData" type="bool"/> + </preferences> + + <description>&clearDataSettings2.label;</description> + + <groupbox orient="horizontal"> + <caption label="&historySection.label;"/> + <grid flex="1"> + <columns> + <column style="width: &column.width2;"/> + <column flex="1"/> + </columns> + <rows> + <row> + <checkbox label="&itemHistoryAndDownloads.label;" + accesskey="&itemHistoryAndDownloads.accesskey;" + preference="privacy.clearOnShutdown.history"/> + <checkbox label="&itemCookies.label;" + accesskey="&itemCookies.accesskey;" + preference="privacy.clearOnShutdown.cookies"/> + </row> + <row> + <checkbox label="&itemActiveLogins.label;" + accesskey="&itemActiveLogins.accesskey;" + preference="privacy.clearOnShutdown.sessions"/> + <checkbox label="&itemCache.label;" + accesskey="&itemCache.accesskey;" + preference="privacy.clearOnShutdown.cache"/> + </row> + <row> + <checkbox label="&itemFormSearchHistory.label;" + accesskey="&itemFormSearchHistory.accesskey;" + preference="privacy.clearOnShutdown.formdata"/> + </row> + </rows> + </grid> + </groupbox> + <groupbox orient="horizontal"> + <caption label="&dataSection.label;"/> + <grid flex="1"> + <columns> + <column style="width: &column.width2;"/> + <column flex="1"/> + </columns> + <rows> + <row> + <checkbox label="&itemPasswords.label;" + accesskey="&itemPasswords.accesskey;" + preference="privacy.clearOnShutdown.passwords"/> + <checkbox label="&itemOfflineApps.label;" + accesskey="&itemOfflineApps.accesskey;" + preference="privacy.clearOnShutdown.offlineApps"/> + </row> + <row> + <checkbox label="&itemSitePreferences.label;" + accesskey="&itemSitePreferences.accesskey;" + preference="privacy.clearOnShutdown.siteSettings"/> + <checkbox label="&itemConnectivityData.label;" + accesskey="&itemConnectivityData.accesskey;" + preference="privacy.clearOnShutdown.connectivityData"/> + </row> + </rows> + </grid> + </groupbox> + </prefpane> +</prefwindow> diff --git a/browser/components/preferences/security.js b/browser/components/preferences/security.js new file mode 100644 index 000000000..d8f491b1c --- /dev/null +++ b/browser/components/preferences/security.js @@ -0,0 +1,235 @@ +// 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 UI. + */ + init: function () + { + this._pane = document.getElementById("paneSecurity"); + this._initMasterPasswordUI(); + }, + + // 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"); + } + + document.documentElement.openWindow("Browser:Permissions", + "chrome://browser/content/preferences/permissions.xul", + "", params); + }, + + /** + * Parameters for the add-on install permissions dialog. + */ + _addonParams: + { + blockVisible: false, + sessionVisible: false, + allowVisible: true, + prefilledHost: "", + permissionType: "install" + }, + + /** + * Ensures that the blocklist is enabled/disabled appropriately based on level + */ + addonLevelNeedsSync: function() + { + Services.prefs.setBoolPref("extensions.blocklist.level.updated", true); + }, + // called from preferences window onunload. + syncAddonSecurityLevel: function() + { + if (Services.prefs.getBoolPref("extensions.blocklist.level.updated") == true) { + Services.prefs.setBoolPref("extensions.blocklist.level.updated", false); + var secLevel = Services.prefs.getIntPref("extensions.blocklist.level"); + Services.prefs.setBoolPref("extensions.blocklist.enabled", + !(secLevel == 99)); + } + }, + + // 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; + } else { + 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 () + { + let bundlePrefs = document.getElementById("bundlePreferences"); + let params = { + blockVisible: true, + sessionVisible: false, + allowVisible: false, + hideStatusColumn: true, + prefilledHost: "", + permissionType: "login-saving", + windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"), + introText: bundlePrefs.getString("savedLoginsExceptions_desc") + }; + + document.documentElement.openWindow("Toolkit:PasswordManagerExceptions", + "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; + }, + + /** + * 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 () + { + const Cc = Components.classes, Ci = Components.interfaces; + 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")); + } + else { + document.documentElement.openSubDialog("chrome://mozapps/content/preferences/removemp.xul", + "", null); + } + this._initMasterPasswordUI(); + }, + + /** + * Displays a dialog in which the master password may be changed. + */ + changeMasterPassword: function () + { + document.documentElement.openSubDialog("chrome://mozapps/content/preferences/changemp.xul", + "", null); + this._initMasterPasswordUI(); + }, + + /** + * Shows the sites where the user has saved passwords and the associated login + * information. + */ + showPasswords: function () + { + document.documentElement.openWindow("Toolkit:PasswordManager", + "chrome://passwordmgr/content/passwordManager.xul", + "", null); + } +}; diff --git a/browser/components/preferences/security.xul b/browser/components/preferences/security.xul new file mode 100644 index 000000000..350eb0d79 --- /dev/null +++ b/browser/components/preferences/security.xul @@ -0,0 +1,177 @@ +<?xml version="1.0"?> + +<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- --> +<!-- 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/. --> + +<!DOCTYPE overlay [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + <!ENTITY % securityDTD SYSTEM "chrome://browser/locale/preferences/security.dtd"> + %brandDTD; + %securityDTD; +]> + +<overlay id="SecurityPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <prefpane id="paneSecurity" + onpaneload="gSecurityPane.init();" + helpTopic="prefs-security"> + + <preferences id="securityPreferences"> + <!-- 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="extensions.blocklist.level" + name="extensions.blocklist.level" + onchange="gSecurityPane.addonLevelNeedsSync();" + type="int"/> + + <!-- Passwords --> + <preference id="signon.rememberSignons" name="signon.rememberSignons" type="bool"/> + <preference id="signon.autofillForms" name="signon.autofillForms" type="bool"/> + + <!-- Security Protocols --> + + <preference id="network.stricttransportsecurity.enabled" + name="network.stricttransportsecurity.enabled" + type="bool"/> + + <!-- Opportunistic Encryption --> + + <preference id="network.http.upgrade-insecure-requests" + name="network.http.upgrade-insecure-requests" + type="bool"/> + <preference id="network.http.altsvc.oe" + name="network.http.altsvc.oe" + type="bool"/> + + <!-- XSS Filter --> + <!-- + <preference id="security.xssfilter.enable" name="security.xssfilter.enable" type="bool"/> + --> + + </preferences> + + <script type="application/javascript" src="chrome://browser/content/preferences/security.js"/> + + <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> + + <!-- addons, forgery (phishing) UI --> + <groupbox id="addonsSecurityGroup"> + <caption label="&addons.label;"/> + + <hbox id="addonInstallBox"> + <checkbox id="warnAddonInstall" flex="1" + label="&warnAddonInstall.label;" + accesskey="&warnAddonInstall.accesskey;" + preference="xpinstall.whitelist.required" + onsyncfrompreference="return gSecurityPane.readWarnAddonInstall();"/> + <button id="addonExceptions" + label="&addonExceptions.label;" + accesskey="&addonExceptions.accesskey;" + oncommand="gSecurityPane.showAddonExceptions();"/> + </hbox> + <hbox id="addonSecuritySettingsBox" flex="1"> + <vbox> + <label id="addonSecurity" control="addonsecurity-menu">&addonSecuritylevel;</label> + <menulist id="addonsecurity-menu" preference="extensions.blocklist.level" sizetopopup="always"> + <menupopup> + <menuitem label="&addonSecurityLevel_Off;" value="99" /> + <menuitem label="&addonSecurityLevel_Low;" value="3" /> + <menuitem label="&addonSecurityLevel_High;" value="2" /> + <menuitem label="&addonSecurityLevel_Extreme;" value="1" /> + </menupopup> + </menulist> + </vbox> + </hbox> + </groupbox> + + <!-- Passwords --> + <groupbox id="passwordsGroup" orient="vertical"> + <caption label="&passwords.label;"/> + + <hbox id="savePasswordsBox"> + <checkbox id="savePasswords" flex="1" + label="&rememberPasswords.label;" accesskey="&rememberPasswords.accesskey;" + preference="signon.rememberSignons" + onsyncfrompreference="return gSecurityPane.readSavePasswords();"/> + <button id="passwordExceptions" + label="&passwordExceptions.label;" + accesskey="&passwordExceptions.accesskey;" + oncommand="gSecurityPane.showPasswordExceptions();" + preference="pref.privacy.disable_button.view_passwords_exceptions"/> + </hbox> + <checkbox id="autofillPasswords" flex="1" + label="&autofillPasswords.label;" accesskey="&autofillPasswords.accesskey;" + preference="signon.autofillForms"/> + <hbox id="masterPasswordBox"> + <checkbox id="useMasterPassword" flex="1" + oncommand="gSecurityPane.updateMasterPasswordButton();" + label="&useMasterPassword.label;" + accesskey="&useMasterPassword.accesskey;"/> + <button id="changeMasterPassword" + label="&changeMasterPassword.label;" + accesskey="&changeMasterPassword.accesskey;" + oncommand="gSecurityPane.changeMasterPassword();"/> + </hbox> + + <hbox id="showPasswordsBox"> + <spacer flex="1"/> + <button id="showPasswords" + label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;" + oncommand="gSecurityPane.showPasswords();" + preference="pref.privacy.disable_button.view_passwords"/> + </hbox> + </groupbox> + + <!-- Security protocols --> + <groupbox id="SecProtoGroup"> + <caption label="&SecProto.label;"/> + + <vbox id="SecProtoBox" align="start" flex="1"> + <checkbox id="enableHSTS" + label="&enableHSTS.label;" + accesskey="&enableHSTS.accesskey;" + preference="network.stricttransportsecurity.enabled" /> + </vbox> + </groupbox> + + <groupbox id="OpportunisticEncryption"> + <caption label="&OpEnc.label;"/> + <checkbox id="enableUIROpEnc" + label="&enableUIROpEnc.label;" + preference="network.http.upgrade-insecure-requests" /> + <checkbox id="enableAltSvcOpEnc" + label="&enableAltSvcOpEnc.label;" + preference="network.http.altsvc.oe" /> + </groupbox> + + <!-- XSS Filter --> + <!-- + <groupbox id="XSSFiltGroup"> + <caption label="&XSSFilt.label;"/> + + <hbox id="XSSFiltBox"> + <checkbox id="enableXSSFilt" flex="1" + label="&enableXSSFilt.label;" + accesskey="&enableXSSFilt.accesskey;" + preference="security.xssfilter.enable" /> + </hbox> + + </groupbox> + --> + + </prefpane> + +</overlay> diff --git a/browser/components/preferences/selectBookmark.js b/browser/components/preferences/selectBookmark.js new file mode 100644 index 000000000..ba468646c --- /dev/null +++ b/browser/components/preferences/selectBookmark.js @@ -0,0 +1,82 @@ +// 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/. + +/** + * SelectBookmarkDialog controls the user interface for the "Use Bookmark for + * Home Page" dialog. + * + * The caller (gMainPane.setHomePageToBookmark in main.js) invokes this dialog + * with a single argument - a reference to an object with a .urls property and + * a .names property. This dialog is responsible for updating the contents of + * the .urls property with an array of URLs to use as home pages and for + * updating the .names property with an array of names for those URLs before it + * closes. + */ +var SelectBookmarkDialog = { + init: function() { + document.getElementById("bookmarks").place = + "place:queryType=1&folder=" + PlacesUIUtils.allBookmarksFolderId; + + // Initial update of the OK button. + this.selectionChanged(); + }, + + /** + * Update the disabled state of the OK button as the user changes the + * selection within the view. + */ + selectionChanged: function() { + var accept = document.documentElement.getButton("accept"); + var bookmarks = document.getElementById("bookmarks"); + var disableAcceptButton = true; + if (bookmarks.hasSelection) { + if (!PlacesUtils.nodeIsSeparator(bookmarks.selectedNode)) + disableAcceptButton = false; + } + accept.disabled = disableAcceptButton; + }, + + onItemDblClick: function() { + var bookmarks = document.getElementById("bookmarks"); + var selectedNode = bookmarks.selectedNode; + if (selectedNode && PlacesUtils.nodeIsURI(selectedNode)) { + /** + * The user has double clicked on a tree row that is a link. Take this to + * mean that they want that link to be their homepage, and close the dialog. + */ + document.documentElement.getButton("accept").click(); + } + }, + + /** + * User accepts their selection. Set all the selected URLs or the contents + * of the selected folder as the list of homepages. + */ + accept: function() { + var bookmarks = document.getElementById("bookmarks"); + NS_ASSERT(bookmarks.hasSelection, + "Should not be able to accept dialog if there is no selected URL!"); + var urls = []; + var names = []; + var selectedNode = bookmarks.selectedNode; + if (PlacesUtils.nodeIsFolder(selectedNode)) { + var contents = PlacesUtils.getFolderContents(selectedNode.itemId).root; + var cc = contents.childCount; + for (var i = 0; i < cc; ++i) { + var node = contents.getChild(i); + if (PlacesUtils.nodeIsURI(node)) { + urls.push(node.uri); + names.push(node.title); + } + } + contents.containerOpen = false; + } + else { + urls.push(selectedNode.uri); + names.push(selectedNode.title); + } + window.arguments[0].urls = urls; + window.arguments[0].names = names; + } +}; diff --git a/browser/components/preferences/selectBookmark.xul b/browser/components/preferences/selectBookmark.xul new file mode 100644 index 000000000..5547534b6 --- /dev/null +++ b/browser/components/preferences/selectBookmark.xul @@ -0,0 +1,44 @@ +<?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://browser/content/places/places.css"?> + +<?xml-stylesheet href="chrome://global/skin/"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/selectBookmark.dtd"> + +<dialog id="selectBookmarkDialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&selectBookmark.title;" style="width: 32em;" + persist="screenX screenY width height" screenX="24" screenY="24" + onload="SelectBookmarkDialog.init();" + ondialogaccept="SelectBookmarkDialog.accept();"> + + <script type="application/javascript" + src="chrome://browser/content/preferences/selectBookmark.js"/> + + <description>&selectBookmark.label;</description> + + <separator class="thin"/> + + <tree id="bookmarks" flex="1" type="places" + style="height: 15em;" + hidecolumnpicker="true" + seltype="single" + ondblclick="SelectBookmarkDialog.onItemDblClick();" + onselect="SelectBookmarkDialog.selectionChanged();"> + <treecols> + <treecol id="title" flex="1" primary="true" hideheader="true"/> + </treecols> + <treechildren id="bookmarksChildren" flex="1"/> + </tree> + + <separator class="thin"/> + +</dialog> diff --git a/browser/components/preferences/sync.js b/browser/components/preferences/sync.js new file mode 100644 index 000000000..ecf4fe6ef --- /dev/null +++ b/browser/components/preferences/sync.js @@ -0,0 +1,192 @@ +// 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"); + +const PAGE_NO_ACCOUNT = 0; +const PAGE_HAS_ACCOUNT = 1; +const PAGE_NEEDS_UPDATE = 2; + +var gSyncPane = { + _stringBundle: null, + 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.value = Weave.Utils.getErrorString(Weave.Status.login); + label.className = "error"; + }, + + init: function () { + // 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; + } + + 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(); + }, + + _init: function () { + let topics = ["weave:service:login:error", + "weave:service:login:finish", + "weave:service:start-over", + "weave:service:setup-complete", + "weave:service:logout:finish"]; + + // 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); + + this._stringBundle = + Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties"); + this.updateWeavePrefs(); + }, + + updateWeavePrefs: function () { + if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED || + Weave.Svc.Prefs.get("firstSync", "") == "notReady") { + this.page = PAGE_NO_ACCOUNT; + } 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").value = Weave.Service.identity.account; + document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName; + document.getElementById("tosPP").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(); + } + }, + + /** + * 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 win = Services.wm.getMostRecentWindow("Weave:AccountSetup"); + if (win) { + win.focus(); + } else { + window.openDialog("chrome://weave/content/setup.xul", + "weaveSetup", "centerscreen,chrome,resizable=no", + wizardType); + } + }, + + openQuotaDialog: function () { + let win = Services.wm.getMostRecentWindow("Sync:ViewQuota"); + if (win) { + win.focus(); + } else { + window.openDialog("chrome://weave/content/quota.xul", "", + "centerscreen,chrome,dialog,modal"); + } + }, + + openAddDevice: function () { + if (!Weave.Utils.ensureMPUnlocked()) { + return; + } + + let win = Services.wm.getMostRecentWindow("Sync:AddDevice"); + if (win) { + win.focus(); + } else { + window.openDialog("chrome://weave/content/addDevice.xul", + "syncAddDevice", "centerscreen,chrome,resizable=no"); + } + }, + + resetSync: function () { + this.openSetup("reset"); + }, +}; + diff --git a/browser/components/preferences/sync.xul b/browser/components/preferences/sync.xul new file mode 100644 index 000000000..2c91e0cd5 --- /dev/null +++ b/browser/components/preferences/sync.xul @@ -0,0 +1,178 @@ +<?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/. --> + +<!DOCTYPE overlay [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +<!ENTITY % syncBrandDTD SYSTEM "chrome://branding/locale/syncBrand.dtd"> +<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/preferences/sync.dtd"> +%brandDTD; +%syncBrandDTD; +%syncDTD; +]> + +<overlay id="SyncPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <prefpane id="paneSync" + helpTopic="prefs-weave" + onpaneload="gSyncPane.init()"> + + <preferences> +<!-- <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/sync.js"/> + <script type="application/javascript" + src="chrome://weave/content/utils.js"/> + + + <deck id="weavePrefsDeck"> + <vbox id="noAccount" align="center"> + <spacer flex="1"/> + <description id="syncDesc"> + &weaveDesc.label; + </description> + <separator/> + <label class="text-link" + onclick="event.stopPropagation(); gSyncPane.openSetup(null);" + value="&setupButton.label;"/> + <separator/> + <label class="text-link" + onclick="event.stopPropagation(); gSyncPane.openSetup('pair');" + value="&pairDevice.label;"/> + <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" value=""/> + </caption> + + <hbox> + <button type="menu" + label="&manageAccount.label;" + accesskey="&manageAccount.accesskey;"> + <menupopup> + <menuitem label="&viewQuota.label;" + oncommand="gSyncPane.openQuotaDialog();"/> + <menuseparator/> + <menuitem label="&changePassword2.label;" + oncommand="gSyncUtils.changePassword();"/> + <menuitem label="&myRecoveryKey.label;" + oncommand="gSyncUtils.resetPassphrase();"/> + <menuseparator/> + <menuitem label="&resetSync2.label;" + oncommand="gSyncPane.resetSync();"/> + </menupopup> + </button> + </hbox> + + <hbox> + <label id="syncAddDeviceLabel" + class="text-link" + onclick="gSyncPane.openAddDevice(); return false;" + value="&pairDevice.label;"/> + </hbox> + + <vbox> + <label value="&syncMy.label;" /> + <richlistbox id="syncEnginesList" + orient="vertical" + onselect="if (this.selectedCount) this.clearSelection();"> +<!-- <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 value="&syncDeviceName.label;" + accesskey="&syncDeviceName.accesskey;" + control="syncComputerName"/> + <textbox id="syncComputerName" + onchange="gSyncUtils.changeName(this)"/> + </row> + </rows> + </grid> + <hbox> + <label class="text-link" + onclick="gSyncPane.startOver(true); return false;" + value="&unlinkDevice.label;"/> + </hbox> + </groupbox> + <hbox id="tosPP" pack="center"> + <label class="text-link" + onclick="event.stopPropagation();gSyncUtils.openToS();" + value="&prefs.tosLink.label;"/> + <label class="text-link" + onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();" + value="&prefs.ppLink.label;"/> + </hbox> + </vbox> + + <vbox id="needsUpdate" align="center" pack="center"> + <hbox> + <label id="loginError" value=""/> + <label class="text-link" + onclick="gSyncPane.updatePass(); return false;" + value="&updatePass.label;"/> + <label class="text-link" + onclick="gSyncPane.resetPass(); return false;" + value="&resetPass.label;"/> + </hbox> + <label class="text-link" + onclick="gSyncPane.startOver(true); return false;" + value="&unlinkDevice.label;"/> + </vbox> + </deck> + </prefpane> +</overlay> diff --git a/browser/components/preferences/tabs.js b/browser/components/preferences/tabs.js new file mode 100644 index 000000000..811064291 --- /dev/null +++ b/browser/components/preferences/tabs.js @@ -0,0 +1,89 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +var gTabsPane = { + + /* + * Preferences: + * + * browser.link.open_newwindow + * - determines where pages which would open in a new window are opened: + * 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 + */ + + /** + * Initialize any platform-specific UI. + */ + init: function () { +#ifdef XP_WIN + const Cc = Components.classes; + const Ci = Components.interfaces; + 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) {} +#endif + // Set the proper value in the newtab drop-down. + gTabsPane.readNewtabUrl(); + }, + + /** + * Pale Moon: synchronize warnOnClose and warnOnCloseOtherTabs + */ + syncWarnOnClose: function() { + var warnOnClosePref = document.getElementById("browser.tabs.warnOnClose"); + var warnOnCloseOtherPref = document.getElementById("browser.tabs.warnOnCloseOtherTabs"); + warnOnCloseOtherPref.value = warnOnClosePref.value; + }, + + /** + * 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; + }, + + /** + * Determines the value of the New Tab display drop-down based + * on the value of browser.newtab.url. + */ + readNewtabUrl: function() { + let newtabUrlChoice = document.getElementById("browser.newtab.choice"); + newtabUrlChoice.value = gNewtabUrl.getNewtabChoice(); + if (newtabUrlChoice.value == 0) { + document.getElementById("newtabPageCustom").hidden = false; + } + gNewtabUrl.newtabUrlChoiceIsSet = true; + } +}; diff --git a/browser/components/preferences/tabs.xul b/browser/components/preferences/tabs.xul new file mode 100644 index 000000000..1f7a2a9e3 --- /dev/null +++ b/browser/components/preferences/tabs.xul @@ -0,0 +1,101 @@ +<?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/. --> + +<!DOCTYPE overlay [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % tabsDTD SYSTEM "chrome://browser/locale/preferences/tabs.dtd"> +%tabsDTD; +]> + +<overlay id="TabsPaneOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <prefpane id="paneTabs" + onpaneload="gTabsPane.init();" + helpTopic="prefs-tabs"> + + <preferences id="tabsPreferences"> + <preference id="browser.link.open_newwindow" name="browser.link.open_newwindow" type="int"/> + <preference id="browser.tabs.autoHide" name="browser.tabs.autoHide" type="bool" inverted="true"/> + <preference id="browser.tabs.loadInBackground" name="browser.tabs.loadInBackground" type="bool" inverted="true"/> + <preference id="browser.tabs.warnOnClose" name="browser.tabs.warnOnClose" type="bool" + onchange="gTabsPane.syncWarnOnClose();"/> + <preference id="browser.tabs.warnOnCloseOtherTabs" name="browser.tabs.warnOnCloseOtherTabs" 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.tabs.insertRelatedAfterCurrent" name="browser.tabs.insertRelatedAfterCurrent" type="bool"/> + <preference id="browser.search.context.loadInBackground" name="browser.search.context.loadInBackground" type="bool" inverted="true"/> + <preference id="browser.tabs.closeWindowWithLastTab" name="browser.tabs.closeWindowWithLastTab" type="bool"/> + <preference id="browser.ctrlTab.previews" name="browser.ctrlTab.previews" type="bool"/> + + <preference id="browser.newtab.url" name="browser.newtab.url" type="string"/> + <preference id="browser.newtab.myhome" name="browser.newtab.myhome" type="string"/> + <preference id="browser.newtab.choice" name="browser.newtab.choice" type="int"/> + </preferences> + + <script type="application/javascript" src="chrome://browser/content/preferences/tabs.js"/> + + <!-- XXX flex below is a hack because wrapping checkboxes don't reflow + properly; see bug 349098 --> + <vbox id="tabPrefsBox" align="start" flex="1"> + <checkbox id="linkTargeting" label="&newWindowsAsTabs.label;" + accesskey="&newWindowsAsTabs.accesskey;" + preference="browser.link.open_newwindow" + onsyncfrompreference="return gTabsPane.readLinkTarget();" + onsynctopreference="return gTabsPane.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="showTabBar" label="&showTabBar.label;" + accesskey="&showTabBar.accesskey;" + preference="browser.tabs.autoHide"/> + <checkbox id="restoreOnDemand" label="&restoreTabsOnDemand.label;" + accesskey="&restoreTabsOnDemand.accesskey;" + preference="browser.sessionstore.restore_on_demand"/> + <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 +<!-- Pale Moon additions --> + <checkbox id="insertRelatedAfterCurrent" label="&insertRelatedAfterCurrent.label;" + preference="browser.tabs.insertRelatedAfterCurrent"/> + <checkbox id="contextLoadInBackground" label="&contextLoadInBackground.label;" + preference="browser.search.context.loadInBackground"/> + <checkbox id="closeWindowWithLastTab" label="&closeWindowWithLastTab.label;" + preference="browser.tabs.closeWindowWithLastTab"/> + <checkbox id="showTabPreviews" label="&showTabPreviews.label;" + preference="browser.ctrlTab.previews"/> + <hbox align="center"> + <label value="&newtabPage.label;"/> + <menulist + id="newtabPage" + preference="browser.newtab.choice" + oncommand="gNewtabUrl.writeNewtabUrl(event.target.value);"> + <menupopup> + <menuitem label="&newtabPage.custom.label;" value="0" id="newtabPageCustom" hidden="true" /> + <menuitem label="&newtabPage.blank.label;" value="1" /> + <menuitem label="&newtabPage.home.label;" value="2" /> + <menuitem label="&newtabPage.myhome.label;" value="3" /> + <menuitem label="&newtabPage.quickdial.label;" value="4" /> + </menupopup> + </menulist> + </hbox> + </vbox> + + </prefpane> + +</overlay> |