fune/browser/components/preferences/in-content/main.js

1277 lines
43 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from preferences.js */
/* import-globals-from ../../../../toolkit/mozapps/preferences/fontbuilder.js */
Components.utils.import("resource://gre/modules/Downloads.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");
Components.utils.import("resource:///modules/ShellService.jsm");
Components.utils.import("resource:///modules/TransientPrefs.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
if (AppConstants.E10S_TESTING_ONLY) {
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
}
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
if (AppConstants.MOZ_DEV_EDITION) {
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
}
const ENGINE_FLAVOR = "text/x-moz-search-engine";
var gEngineView = null;
var gMainPane = {
/**
* Initialize autocomplete to ensure prefs are in sync.
*/
_initAutocomplete() {
Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
},
/**
* Initialization of this.
*/
init() {
function setEventListener(aId, aEventType, aCallback) {
document.getElementById(aId)
.addEventListener(aEventType, aCallback.bind(gMainPane));
}
if (AppConstants.HAVE_SHELL_SERVICE) {
this.updateSetDefaultBrowser();
if (AppConstants.platform == "win") {
// In Windows 8 we launch the control panel since it's the only
// way to get all file type association prefs. So we don't know
// when the user will select the default. We refresh here periodically
// in case the default changes. On other Windows OS's defaults can also
// be set while the prefs are open.
window.setInterval(this.updateSetDefaultBrowser.bind(this), 1000);
}
}
gEngineView = new EngineView(new EngineStore());
document.getElementById("engineList").view = gEngineView;
this.buildDefaultEngineDropDown();
let addEnginesLink = document.getElementById("addEngines");
let searchEnginesURL = Services.wm.getMostRecentWindow("navigator:browser")
.BrowserSearch.searchEnginesURL;
addEnginesLink.setAttribute("href", searchEnginesURL);
window.addEventListener("click", this);
window.addEventListener("command", this);
window.addEventListener("dragstart", this);
window.addEventListener("keypress", this);
window.addEventListener("select", this);
window.addEventListener("blur", this, true);
Services.obs.addObserver(this, "browser-search-engine-modified");
window.addEventListener("unload", () => {
Services.obs.removeObserver(this, "browser-search-engine-modified");
});
this._initAutocomplete();
let suggestsPref =
document.getElementById("browser.search.suggest.enabled");
suggestsPref.addEventListener("change", () => {
this.updateSuggestsCheckbox();
});
this.updateSuggestsCheckbox();
// set up the "use current page" label-changing listener
this._updateUseCurrentButton();
window.addEventListener("focus", this._updateUseCurrentButton.bind(this));
this.updateBrowserStartupLastSession();
if (AppConstants.platform == "win") {
// Functionality for "Show tabs in taskbar" on Windows 7 and up.
try {
let sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
let ver = parseFloat(sysInfo.getProperty("version"));
let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
showTabsInTaskbar.hidden = ver < 6.1;
} catch (ex) {}
}
// The "closing multiple tabs" and "opening multiple tabs might slow down
// &brandShortName;" warnings provide options for not showing these
// warnings again. When the user disabled them, we provide checkboxes to
// re-enable the warnings.
if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnClose"))
document.getElementById("warnCloseMultiple").hidden = true;
if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnOpen"))
document.getElementById("warnOpenMany").hidden = true;
setEventListener("browser.privatebrowsing.autostart", "change",
gMainPane.updateBrowserStartupLastSession);
if (AppConstants.HAVE_SHELL_SERVICE) {
setEventListener("setDefaultButton", "command",
gMainPane.setDefaultBrowser);
}
setEventListener("useCurrent", "command",
gMainPane.setHomePageToCurrent);
setEventListener("useBookmark", "command",
gMainPane.setHomePageToBookmark);
setEventListener("restoreDefaultHomePage", "command",
gMainPane.restoreDefaultHomePage);
setEventListener("chooseLanguage", "command",
gMainPane.showLanguages);
setEventListener("translationAttributionImage", "click",
gMainPane.openTranslationProviderAttribution);
setEventListener("translateButton", "command",
gMainPane.showTranslationExceptions);
setEventListener("font.language.group", "change",
gMainPane._rebuildFonts);
setEventListener("advancedFonts", "command",
gMainPane.configureFonts);
setEventListener("colors", "command",
gMainPane.configureColors);
// Initializes the fonts dropdowns displayed in this pane.
this._rebuildFonts();
// Show translation preferences if we may:
const prefName = "browser.translation.ui.show";
if (Services.prefs.getBoolPref(prefName)) {
let row = document.getElementById("translationBox");
row.removeAttribute("hidden");
// Showing attribution only for Bing Translator.
Components.utils.import("resource:///modules/translation/Translation.jsm");
if (Translation.translationEngine == "bing") {
document.getElementById("bingAttribution").removeAttribute("hidden");
}
}
if (AppConstants.E10S_TESTING_ONLY) {
setEventListener("e10sAutoStart", "command",
gMainPane.enableE10SChange);
let e10sCheckbox = document.getElementById("e10sAutoStart");
let e10sPref = document.getElementById("browser.tabs.remote.autostart");
let e10sTempPref = document.getElementById("e10sTempPref");
let e10sForceEnable = document.getElementById("e10sForceEnable");
let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
if (preffedOn) {
// The checkbox is checked if e10s is preffed on and enabled.
e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
// but if it's force disabled, then the checkbox is disabled.
e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
}
}
if (AppConstants.MOZ_DEV_EDITION) {
let uAppData = OS.Constants.Path.userApplicationDataDir;
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
setEventListener("separateProfileMode", "command", gMainPane.separateProfileModeChange);
let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
setEventListener("getStarted", "click", gMainPane.onGetStarted);
OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
() => separateProfileModeCheckbox.checked = true);
fxAccounts.getSignedInUser().then(data => {
document.getElementById("getStarted").selectedIndex = data ? 1 : 0;
})
.catch(Cu.reportError);
}
// Notify observers that the UI is now ready
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.notifyObservers(window, "main-pane-loaded", null);
},
enableE10SChange() {
if (AppConstants.E10S_TESTING_ONLY) {
let e10sCheckbox = document.getElementById("e10sAutoStart");
let e10sPref = document.getElementById("browser.tabs.remote.autostart");
let e10sTempPref = document.getElementById("e10sTempPref");
let prefsToChange;
if (e10sCheckbox.checked) {
// Enabling e10s autostart
prefsToChange = [e10sPref];
} else {
// Disabling e10s autostart
prefsToChange = [e10sPref];
if (e10sTempPref.value) {
prefsToChange.push(e10sTempPref);
}
}
let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
true, false);
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
for (let prefToChange of prefsToChange) {
prefToChange.value = e10sCheckbox.checked;
}
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
}
// Revert the checkbox in case we didn't quit
e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
}
},
separateProfileModeChange() {
if (AppConstants.MOZ_DEV_EDITION) {
function quitApp() {
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
}
function revertCheckbox(error) {
separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
if (error) {
Cu.reportError("Failed to toggle separate profile mode: " + error);
}
}
function createOrRemoveSpecialDevEditionFile(onSuccess) {
let uAppData = OS.Constants.Path.userApplicationDataDir;
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
if (separateProfileModeCheckbox.checked) {
OS.File.remove(ignoreSeparateProfile).then(onSuccess, revertCheckbox);
} else {
OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(onSuccess, revertCheckbox);
}
}
let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
let button_index = confirmRestartPrompt(separateProfileModeCheckbox.checked,
0, false, true);
switch (button_index) {
case CONFIRM_RESTART_PROMPT_CANCEL:
revertCheckbox();
return;
case CONFIRM_RESTART_PROMPT_RESTART_NOW:
const Cc = Components.classes, Ci = Components.interfaces;
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
.createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
"restart");
if (!cancelQuit.data) {
createOrRemoveSpecialDevEditionFile(quitApp);
return;
}
// Revert the checkbox in case we didn't quit
revertCheckbox();
return;
case CONFIRM_RESTART_PROMPT_RESTART_LATER:
createOrRemoveSpecialDevEditionFile();
}
}
},
onGetStarted(aEvent) {
if (AppConstants.MOZ_DEV_EDITION) {
const Cc = Components.classes, Ci = Components.interfaces;
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
let win = wm.getMostRecentWindow("navigator:browser");
fxAccounts.getSignedInUser().then(data => {
if (win) {
if (data) {
// We have a user, open Sync preferences in the same tab
win.openUILinkIn("about:preferences#sync", "current");
return;
}
let accountsTab = win.gBrowser.addTab("about:accounts?action=signin&entrypoint=dev-edition-setup");
win.gBrowser.selectedTab = accountsTab;
}
});
}
},
// HOME PAGE
/*
* Preferences:
*
* browser.startup.homepage
* - the user's home page, as a string; if the home page is a set of tabs,
* this will be those URLs separated by the pipe character "|"
* browser.startup.page
* - what page(s) to show when the user starts the application, as an integer:
*
* 0: a blank page
* 1: the home page (as set by the browser.startup.homepage pref)
* 2: the last page the user visited (DEPRECATED)
* 3: windows and tabs from the last session (a.k.a. session restore)
*
* The deprecated option is not exposed in UI; however, if the user has it
* selected and doesn't change the UI for this preference, the deprecated
* option is preserved.
*/
syncFromHomePref() {
let homePref = document.getElementById("browser.startup.homepage");
// If the pref is set to about:home or about:newtab, set the value to ""
// to show the placeholder text (about:home title) rather than
// exposing those URLs to users.
let defaultBranch = Services.prefs.getDefaultBranch("");
let defaultValue = defaultBranch.getComplexValue("browser.startup.homepage",
Ci.nsIPrefLocalizedString).data;
let currentValue = homePref.value.toLowerCase();
if (currentValue == "about:home" ||
(currentValue == defaultValue && currentValue == "about:newtab")) {
return "";
}
// If the pref is actually "", show about:blank. The actual home page
// loading code treats them the same, and we don't want the placeholder text
// to be shown.
if (homePref.value == "")
return "about:blank";
// Otherwise, show the actual pref value.
return undefined;
},
syncToHomePref(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() {
let homePage = document.getElementById("browser.startup.homepage");
let tabs = this._getTabsForHomePage();
function getTabURI(t) {
return t.linkedBrowser.currentURI.spec;
}
// FIXME Bug 244192: using dangerous "|" joiner!
if (tabs.length)
homePage.value = tabs.map(getTabURI).join("|");
},
/**
* Displays a dialog in which the user can select a bookmark to use as home
* page. If the user selects a bookmark, that bookmark's name is displayed in
* UI and the bookmark's address is stored to the home page preference.
*/
setHomePageToBookmark() {
var rv = { urls: null, names: null };
gSubDialog.open("chrome://browser/content/preferences/selectBookmark.xul",
"resizable=yes, modal=yes", rv,
this._setHomePageToBookmarkClosed.bind(this, rv));
},
_setHomePageToBookmarkClosed(rv, aEvent) {
if (aEvent.detail.button != "accept")
return;
if (rv.urls && rv.names) {
var homePage = document.getElementById("browser.startup.homepage");
// XXX still using dangerous "|" joiner!
homePage.value = rv.urls.join("|");
}
},
/**
* Switches the "Use Current Page" button between its singular and plural
* forms.
*/
_updateUseCurrentButton() {
let useCurrent = document.getElementById("useCurrent");
let tabs = this._getTabsForHomePage();
if (tabs.length > 1)
useCurrent.label = useCurrent.getAttribute("label2");
else
useCurrent.label = useCurrent.getAttribute("label1");
// In this case, the button's disabled state is set by preferences.xml.
let prefName = "pref.browser.homepage.disable_button.current_page";
if (document.getElementById(prefName).locked)
return;
useCurrent.disabled = !tabs.length
},
_getTabsForHomePage() {
var win;
var tabs = [];
const Cc = Components.classes, Ci = Components.interfaces;
var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
win = wm.getMostRecentWindow("navigator:browser");
if (win && win.document.documentElement
.getAttribute("windowtype") == "navigator:browser") {
// We should only include visible & non-pinned tabs
tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs);
tabs = tabs.filter(this.isNotAboutPreferences);
}
return tabs;
},
/**
* Check to see if a tab is not about:preferences
*/
isNotAboutPreferences(aElement, aIndex, aArray) {
return !aElement.linkedBrowser.currentURI.spec.startsWith("about:preferences");
},
/**
* Restores the default home page as the user's home page.
*/
restoreDefaultHomePage() {
var homePage = document.getElementById("browser.startup.homepage");
homePage.value = homePage.defaultValue;
},
/**
* Utility function to enable/disable the button specified by aButtonID based
* on the value of the Boolean preference specified by aPreferenceID.
*/
updateButtons(aButtonID, aPreferenceID) {
var button = document.getElementById(aButtonID);
var preference = document.getElementById(aPreferenceID);
button.disabled = preference.value != true;
return undefined;
},
/**
* Hide/show the "Show my windows and tabs from last time" option based
* on the value of the browser.privatebrowsing.autostart pref.
*/
updateBrowserStartupLastSession() {
let pbAutoStartPref = document.getElementById("browser.privatebrowsing.autostart");
let startupPref = document.getElementById("browser.startup.page");
let menu = document.getElementById("browserStartupPage");
let option = document.getElementById("browserStartupLastSession");
if (pbAutoStartPref.value) {
option.setAttribute("disabled", "true");
if (option.selected) {
menu.selectedItem = document.getElementById("browserStartupHomePage");
}
} else {
option.removeAttribute("disabled");
startupPref.updateElements(); // select the correct index in the startup menulist
}
},
// TABS
/*
* Preferences:
*
* browser.link.open_newwindow - int
* Determines where links targeting new windows should open.
* Values:
* 1 - Open in the current window or tab.
* 2 - Open in a new window.
* 3 - Open in a new tab in the most recent window.
* browser.tabs.loadInBackground - bool
* True - Whether browser should switch to a new tab opened from a link.
* browser.tabs.warnOnClose - bool
* True - If when closing a window with multiple tabs the user is warned and
* allowed to cancel the action, false to just close the window.
* browser.tabs.warnOnOpen - bool
* True - Whether the user should be warned when trying to open a lot of
* tabs at once (e.g. a large folder of bookmarks), allowing to
* cancel the action.
* browser.taskbar.previews.enable - bool
* True - Tabs are to be shown in Windows 7 taskbar.
* False - Only the window is to be shown in Windows 7 taskbar.
*/
/**
* Determines where a link which opens a new window will open.
*
* @returns |true| if such links should be opened in new tabs
*/
readLinkTarget() {
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() {
var linkTargeting = document.getElementById("linkTargeting");
return linkTargeting.checked ? 3 : 2;
},
/*
* Preferences:
*
* browser.shell.checkDefault
* - true if a default-browser check (and prompt to make it so if necessary)
* occurs at startup, false otherwise
*/
/**
* Show button for setting browser as default browser or information that
* browser is already the default browser.
*/
updateSetDefaultBrowser() {
if (AppConstants.HAVE_SHELL_SERVICE) {
let shellSvc = getShellService();
let defaultBrowserBox = document.getElementById("defaultBrowserBox");
if (!shellSvc) {
defaultBrowserBox.hidden = true;
return;
}
let setDefaultPane = document.getElementById("setDefaultPane");
let isDefault = shellSvc.isDefaultBrowser(false, true);
setDefaultPane.selectedIndex = isDefault ? 1 : 0;
let alwaysCheck = document.getElementById("alwaysCheckDefault");
alwaysCheck.disabled = alwaysCheck.disabled ||
isDefault && alwaysCheck.checked;
}
},
/**
* Set browser as the operating system default browser.
*/
setDefaultBrowser() {
if (AppConstants.HAVE_SHELL_SERVICE) {
let alwaysCheckPref = document.getElementById("browser.shell.checkDefaultBrowser");
alwaysCheckPref.value = true;
let shellSvc = getShellService();
if (!shellSvc)
return;
try {
shellSvc.setDefaultBrowser(true, false);
} catch (ex) {
Cu.reportError(ex);
return;
}
let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
}
},
/**
* Shows a dialog in which the preferred language for web content may be set.
*/
showLanguages() {
gSubDialog.open("chrome://browser/content/preferences/languages.xul");
},
/**
* Displays the translation exceptions dialog where specific site and language
* translation preferences can be set.
*/
showTranslationExceptions() {
gSubDialog.open("chrome://browser/content/preferences/translation.xul");
},
openTranslationProviderAttribution() {
Components.utils.import("resource:///modules/translation/Translation.jsm");
Translation.openProviderAttribution();
},
/**
* Displays the fonts dialog, where web page font names and sizes can be
* configured.
*/
configureFonts() {
gSubDialog.open("chrome://browser/content/preferences/fonts.xul", "resizable=no");
},
/**
* Displays the colors dialog, where default web page/link/etc. colors can be
* configured.
*/
configureColors() {
gSubDialog.open("chrome://browser/content/preferences/colors.xul", "resizable=no");
},
// FONTS
/**
* Populates the default font list in UI.
*/
_rebuildFonts() {
var preferences = document.getElementById("mainPreferences");
// Ensure preferences are "visible" to ensure bindings work.
preferences.hidden = false;
// Force flush:
preferences.clientHeight;
var langGroupPref = document.getElementById("font.language.group");
this._selectDefaultLanguageGroup(langGroupPref.value,
this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
},
/**
* Returns the type of the current default font for the language denoted by
* aLanguageGroup.
*/
_readDefaultFontTypeForLanguage(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", "gMainPane._rebuildFonts();");
document.getElementById("mainPreferences").appendChild(preference);
}
return preference.value;
},
_selectDefaultLanguageGroup(aLanguageGroup, aIsSerif) {
const kFontNameFmtSerif = "font.name.serif.%LANG%";
const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
const kFontSizeFmtVariable = "font.size.variable.%LANG%";
var preferences = document.getElementById("mainPreferences");
var prefs = [{ format: aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif,
type: "fontname",
element: "defaultFont",
fonttype: aIsSerif ? "serif" : "sans-serif" },
{ format: aIsSerif ? kFontNameListFmtSerif : kFontNameListFmtSansSerif,
type: "unichar",
element: null,
fonttype: aIsSerif ? "serif" : "sans-serif" },
{ format: kFontSizeFmtVariable,
type: "int",
element: "defaultFontSize",
fonttype: null }];
for (var i = 0; i < prefs.length; ++i) {
var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
if (!preference) {
preference = document.createElement("preference");
var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
preference.id = name;
preference.setAttribute("name", name);
preference.setAttribute("type", prefs[i].type);
preferences.appendChild(preference);
}
if (!prefs[i].element)
continue;
var element = document.getElementById(prefs[i].element);
if (element) {
element.setAttribute("preference", preference.id);
if (prefs[i].fonttype)
FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
preference.setElementValue(element);
}
}
},
/**
* Returns 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() {
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() {
var checkbox = document.getElementById("checkSpelling");
if (checkbox.checked) {
if (this._storedSpellCheck == 2) {
return 2;
}
return 1;
}
return 0;
},
updateSuggestsCheckbox() {
let suggestsPref =
document.getElementById("browser.search.suggest.enabled");
let permanentPB =
Services.prefs.getBoolPref("browser.privatebrowsing.autostart");
let urlbarSuggests = document.getElementById("urlBarSuggestion");
urlbarSuggests.disabled = !suggestsPref.value || permanentPB;
let urlbarSuggestsPref =
document.getElementById("browser.urlbar.suggest.searches");
urlbarSuggests.checked = urlbarSuggestsPref.value;
if (urlbarSuggests.disabled) {
urlbarSuggests.checked = false;
}
let permanentPBLabel =
document.getElementById("urlBarSuggestionPermanentPBLabel");
permanentPBLabel.hidden = urlbarSuggests.hidden || !permanentPB;
},
buildDefaultEngineDropDown() {
// This is called each time something affects the list of engines.
let list = document.getElementById("defaultEngine");
// Set selection to the current default engine.
let currentEngine = Services.search.currentEngine.name;
// If the current engine isn't in the list any more, select the first item.
let engines = gEngineView._engineStore._engines;
if (!engines.some(e => e.name == currentEngine))
currentEngine = engines[0].name;
// Now clean-up and rebuild the list.
list.removeAllItems();
gEngineView._engineStore._engines.forEach(e => {
let item = list.appendItem(e.name);
item.setAttribute("class", "menuitem-iconic searchengine-menuitem menuitem-with-favicon");
if (e.iconURI) {
item.setAttribute("image", e.iconURI.spec);
}
item.engine = e;
if (e.name == currentEngine)
list.selectedItem = item;
});
},
handleEvent(aEvent) {
switch (aEvent.type) {
case "click":
if (aEvent.target.id != "engineChildren" &&
!aEvent.target.classList.contains("searchEngineAction")) {
let engineList = document.getElementById("engineList");
// We don't want to toggle off selection while editing keyword
// so proceed only when the input field is hidden.
// We need to check that engineList.view is defined here
// because the "click" event listener is on <window> and the
// view might have been destroyed if the pane has been navigated
// away from.
if (engineList.inputField.hidden && engineList.view) {
let selection = engineList.view.selection;
if (selection.count > 0) {
selection.toggleSelect(selection.currentIndex);
}
engineList.blur();
}
}
break;
case "command":
switch (aEvent.target.id) {
case "":
if (aEvent.target.parentNode &&
aEvent.target.parentNode.parentNode &&
aEvent.target.parentNode.parentNode.id == "defaultEngine") {
gMainPane.setDefaultEngine();
}
break;
case "restoreDefaultSearchEngines":
gMainPane.onRestoreDefaults();
break;
case "removeEngineButton":
Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
break;
}
break;
case "dragstart":
if (aEvent.target.id == "engineChildren") {
onDragEngineStart(aEvent);
}
break;
case "keypress":
if (aEvent.target.id == "engineList") {
gMainPane.onTreeKeyPress(aEvent);
}
break;
case "select":
if (aEvent.target.id == "engineList") {
gMainPane.onTreeSelect();
}
break;
case "blur":
if (aEvent.target.id == "engineList" &&
aEvent.target.inputField == document.getBindingParent(aEvent.originalTarget)) {
gMainPane.onInputBlur();
}
break;
}
},
observe(aEngine, aTopic, aVerb) {
if (aTopic == "browser-search-engine-modified") {
aEngine.QueryInterface(Components.interfaces.nsISearchEngine);
switch (aVerb) {
case "engine-added":
gEngineView._engineStore.addEngine(aEngine);
gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
gMainPane.buildDefaultEngineDropDown();
break;
case "engine-changed":
gEngineView._engineStore.reloadIcons();
gEngineView.invalidate();
break;
case "engine-removed":
gMainPane.remove(aEngine);
break;
case "engine-current":
// If the user is going through the drop down using up/down keys, the
// dropdown may still be open (eg. on Windows) when engine-current is
// fired, so rebuilding the list unconditionally would get in the way.
let selectedEngine =
document.getElementById("defaultEngine").selectedItem.engine;
if (selectedEngine.name != aEngine.name)
gMainPane.buildDefaultEngineDropDown();
break;
case "engine-default":
// Not relevant
break;
}
}
},
onInputBlur(aEvent) {
let tree = document.getElementById("engineList");
if (!tree.hasAttribute("editing"))
return;
// Accept input unless discarded.
let accept = aEvent.charCode != KeyEvent.DOM_VK_ESCAPE;
tree.stopEditing(accept);
},
onTreeSelect() {
document.getElementById("removeEngineButton").disabled =
!gEngineView.isEngineSelectedAndRemovable();
},
onTreeKeyPress(aEvent) {
let index = gEngineView.selectedIndex;
let tree = document.getElementById("engineList");
if (tree.hasAttribute("editing"))
return;
if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) {
// Space toggles the checkbox.
let newValue = !gEngineView._engineStore.engines[index].shown;
gEngineView.setCellValue(index, tree.columns.getFirstColumn(),
newValue.toString());
// Prevent page from scrolling on the space key.
aEvent.preventDefault();
} else {
let isMac = Services.appinfo.OS == "Darwin";
if ((isMac && aEvent.keyCode == KeyEvent.DOM_VK_RETURN) ||
(!isMac && aEvent.keyCode == KeyEvent.DOM_VK_F2)) {
tree.startEditing(index, tree.columns.getLastColumn());
} else if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(isMac && aEvent.shiftKey &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE &&
gEngineView.isEngineSelectedAndRemovable())) {
// Delete and Shift+Backspace (Mac) removes selected engine.
Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
}
}
},
onRestoreDefaults() {
let num = gEngineView._engineStore.restoreDefaultEngines();
gEngineView.rowCountChanged(0, num);
gEngineView.invalidate();
},
showRestoreDefaults(aEnable) {
document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable;
},
remove(aEngine) {
let index = gEngineView._engineStore.removeEngine(aEngine);
gEngineView.rowCountChanged(index, -1);
gEngineView.invalidate();
gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
gEngineView.ensureRowIsVisible(gEngineView.currentIndex);
document.getElementById("engineList").focus();
},
editKeyword: Task.async(function* (aEngine, aNewKeyword) {
let keyword = aNewKeyword.trim();
if (keyword) {
let eduplicate = false;
let dupName = "";
// Check for duplicates in Places keywords.
let bduplicate = !!(yield PlacesUtils.keywords.fetch(keyword));
// Check for duplicates in changes we haven't committed yet
let engines = gEngineView._engineStore.engines;
for (let engine of engines) {
if (engine.alias == keyword &&
engine.name != aEngine.name) {
eduplicate = true;
dupName = engine.name;
break;
}
}
// Notify the user if they have chosen an existing engine/bookmark keyword
if (eduplicate || bduplicate) {
let strings = document.getElementById("engineManagerBundle");
let dtitle = strings.getString("duplicateTitle");
let bmsg = strings.getString("duplicateBookmarkMsg");
let emsg = strings.getFormattedString("duplicateEngineMsg", [dupName]);
Services.prompt.alert(window, dtitle, eduplicate ? emsg : bmsg);
return false;
}
}
gEngineView._engineStore.changeEngine(aEngine, "alias", keyword);
gEngineView.invalidate();
return true;
}),
saveOneClickEnginesList() {
let hiddenList = [];
for (let engine of gEngineView._engineStore.engines) {
if (!engine.shown)
hiddenList.push(engine.name);
}
document.getElementById("browser.search.hiddenOneOffs").value =
hiddenList.join(",");
},
setDefaultEngine() {
Services.search.currentEngine =
document.getElementById("defaultEngine").selectedItem.engine;
}
};
function onDragEngineStart(event) {
var selectedIndex = gEngineView.selectedIndex;
var tree = document.getElementById("engineList");
var row = { }, col = { }, child = { };
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, child);
if (selectedIndex >= 0 && !gEngineView.isCheckBox(row.value, col.value)) {
event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
event.dataTransfer.effectAllowed = "move";
}
}
function EngineStore() {
let pref = document.getElementById("browser.search.hiddenOneOffs").value;
this.hiddenList = pref ? pref.split(",") : [];
this._engines = Services.search.getVisibleEngines().map(this._cloneEngine, this);
this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine, this);
// check if we need to disable the restore defaults button
var someHidden = this._defaultEngines.some(e => e.hidden);
gMainPane.showRestoreDefaults(someHidden);
}
EngineStore.prototype = {
_engines: null,
_defaultEngines: null,
get engines() {
return this._engines;
},
set engines(val) {
this._engines = val;
return val;
},
_getIndexForEngine(aEngine) {
return this._engines.indexOf(aEngine);
},
_getEngineByName(aName) {
return this._engines.find(engine => engine.name == aName);
},
_cloneEngine(aEngine) {
var clonedObj = {};
for (var i in aEngine)
clonedObj[i] = aEngine[i];
clonedObj.originalEngine = aEngine;
clonedObj.shown = this.hiddenList.indexOf(clonedObj.name) == -1;
return clonedObj;
},
// Callback for Array's some(). A thisObj must be passed to some()
_isSameEngine(aEngineClone) {
return aEngineClone.originalEngine == this.originalEngine;
},
addEngine(aEngine) {
this._engines.push(this._cloneEngine(aEngine));
},
moveEngine(aEngine, aNewIndex) {
if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
throw new Error("ES_moveEngine: invalid aNewIndex!");
var index = this._getIndexForEngine(aEngine);
if (index == -1)
throw new Error("ES_moveEngine: invalid engine?");
if (index == aNewIndex)
return; // nothing to do
// Move the engine in our internal store
var removedEngine = this._engines.splice(index, 1)[0];
this._engines.splice(aNewIndex, 0, removedEngine);
Services.search.moveEngine(aEngine.originalEngine, aNewIndex);
},
removeEngine(aEngine) {
if (this._engines.length == 1) {
throw new Error("Cannot remove last engine!");
}
let engineName = aEngine.name;
let index = this._engines.findIndex(element => element.name == engineName);
if (index == -1)
throw new Error("invalid engine?");
let removedEngine = this._engines.splice(index, 1)[0];
if (this._defaultEngines.some(this._isSameEngine, removedEngine))
gMainPane.showRestoreDefaults(true);
gMainPane.buildDefaultEngineDropDown();
return index;
},
restoreDefaultEngines() {
var added = 0;
for (var i = 0; i < this._defaultEngines.length; ++i) {
var e = this._defaultEngines[i];
// If the engine is already in the list, just move it.
if (this._engines.some(this._isSameEngine, e)) {
this.moveEngine(this._getEngineByName(e.name), i);
} else {
// Otherwise, add it back to our internal store
// The search service removes the alias when an engine is hidden,
// so clear any alias we may have cached before unhiding the engine.
e.alias = "";
this._engines.splice(i, 0, e);
let engine = e.originalEngine;
engine.hidden = false;
Services.search.moveEngine(engine, i);
added++;
}
}
Services.search.resetToOriginalDefaultEngine();
gMainPane.showRestoreDefaults(false);
gMainPane.buildDefaultEngineDropDown();
return added;
},
changeEngine(aEngine, aProp, aNewValue) {
var index = this._getIndexForEngine(aEngine);
if (index == -1)
throw new Error("invalid engine?");
this._engines[index][aProp] = aNewValue;
aEngine.originalEngine[aProp] = aNewValue;
},
reloadIcons() {
this._engines.forEach(function(e) {
e.uri = e.originalEngine.uri;
});
}
};
function EngineView(aEngineStore) {
this._engineStore = aEngineStore;
}
EngineView.prototype = {
_engineStore: null,
tree: null,
get lastIndex() {
return this.rowCount - 1;
},
get selectedIndex() {
var seln = this.selection;
if (seln.getRangeCount() > 0) {
var min = {};
seln.getRangeAt(0, min, {});
return min.value;
}
return -1;
},
get selectedEngine() {
return this._engineStore.engines[this.selectedIndex];
},
// Helpers
rowCountChanged(index, count) {
this.tree.rowCountChanged(index, count);
},
invalidate() {
this.tree.invalidate();
},
ensureRowIsVisible(index) {
this.tree.ensureRowIsVisible(index);
},
getSourceIndexFromDrag(dataTransfer) {
return parseInt(dataTransfer.getData(ENGINE_FLAVOR));
},
isCheckBox(index, column) {
return column.id == "engineShown";
},
isEngineSelectedAndRemovable() {
return this.selectedIndex != -1 && this.lastIndex != 0;
},
// nsITreeView
get rowCount() {
return this._engineStore.engines.length;
},
getImageSrc(index, column) {
if (column.id == "engineName") {
if (this._engineStore.engines[index].iconURI)
return this._engineStore.engines[index].iconURI.spec;
if (window.devicePixelRatio > 1)
return "chrome://browser/skin/search-engine-placeholder@2x.png";
return "chrome://browser/skin/search-engine-placeholder.png";
}
return "";
},
getCellText(index, column) {
if (column.id == "engineName")
return this._engineStore.engines[index].name;
else if (column.id == "engineKeyword")
return this._engineStore.engines[index].alias;
return "";
},
setTree(tree) {
this.tree = tree;
},
canDrop(targetIndex, orientation, dataTransfer) {
var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
return (sourceIndex != -1 &&
sourceIndex != targetIndex &&
sourceIndex != targetIndex + orientation);
},
drop(dropIndex, orientation, dataTransfer) {
var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
var sourceEngine = this._engineStore.engines[sourceIndex];
const nsITreeView = Components.interfaces.nsITreeView;
if (dropIndex > sourceIndex) {
if (orientation == nsITreeView.DROP_BEFORE)
dropIndex--;
} else if (orientation == nsITreeView.DROP_AFTER) {
dropIndex++;
}
this._engineStore.moveEngine(sourceEngine, dropIndex);
gMainPane.showRestoreDefaults(true);
gMainPane.buildDefaultEngineDropDown();
// Redraw, and adjust selection
this.invalidate();
this.selection.select(dropIndex);
},
selection: null,
getRowProperties(index) { return ""; },
getCellProperties(index, column) { return ""; },
getColumnProperties(column) { return ""; },
isContainer(index) { return false; },
isContainerOpen(index) { return false; },
isContainerEmpty(index) { return false; },
isSeparator(index) { return false; },
isSorted(index) { return false; },
getParentIndex(index) { return -1; },
hasNextSibling(parentIndex, index) { return false; },
getLevel(index) { return 0; },
getProgressMode(index, column) { },
getCellValue(index, column) {
if (column.id == "engineShown")
return this._engineStore.engines[index].shown;
return undefined;
},
toggleOpenState(index) { },
cycleHeader(column) { },
selectionChanged() { },
cycleCell(row, column) { },
isEditable(index, column) { return column.id != "engineName"; },
isSelectable(index, column) { return false; },
setCellValue(index, column, value) {
if (column.id == "engineShown") {
this._engineStore.engines[index].shown = value == "true";
gEngineView.invalidate();
gMainPane.saveOneClickEnginesList();
}
},
setCellText(index, column, value) {
if (column.id == "engineKeyword") {
gMainPane.editKeyword(this._engineStore.engines[index], value)
.then(valid => {
if (!valid)
document.getElementById("engineList").startEditing(index, column);
});
}
},
performAction(action) { },
performActionOnRow(action, index) { },
performActionOnCell(action, index, column) { }
};