mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-07 11:48:19 +02:00
When the focus moves elsewhere inside the <tree> blur events aren't dispatched outside the shadow tree (as expected), so checking the originalTarget is always bogus. Furthermore, the event handler does nothing if the input isn't focused, and when the input is blurred the tree takes care of calling stopEditing itself, so this change should be pretty safe in general: https://searchfox.org/mozilla-central/rev/492214c05cde6e6db5feff9465ece4920400acc3/toolkit/content/widgets/tree.js#1083 It's not clear to me whether the blur event handler is doing anything at all after bug 1547382 (before this patch), as the binding parent is not a <xul:textbox> anymore, so <input>.getBindingParent() will return the <tree>... Depends on D53338 Differential Revision: https://phabricator.services.mozilla.com/D53339 --HG-- extra : moz-landing-system : lando
930 lines
27 KiB
JavaScript
930 lines
27 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 extensionControlled.js */
|
|
/* import-globals-from preferences.js */
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"PlacesUtils",
|
|
"resource://gre/modules/PlacesUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"ExtensionSettingsStore",
|
|
"resource://gre/modules/ExtensionSettingsStore.jsm"
|
|
);
|
|
|
|
Preferences.addAll([
|
|
{ id: "browser.search.suggest.enabled", type: "bool" },
|
|
{ id: "browser.urlbar.suggest.searches", type: "bool" },
|
|
{ id: "browser.search.suggest.enabled.private", type: "bool" },
|
|
{ id: "browser.search.hiddenOneOffs", type: "unichar" },
|
|
{ id: "browser.search.widget.inNavBar", type: "bool" },
|
|
{ id: "browser.urlbar.matchBuckets", type: "string" },
|
|
{ id: "browser.search.separatePrivateDefault", type: "bool" },
|
|
{ id: "browser.search.separatePrivateDefault.ui.enabled", type: "bool" },
|
|
]);
|
|
|
|
const ENGINE_FLAVOR = "text/x-moz-search-engine";
|
|
const SEARCH_TYPE = "default_search";
|
|
const SEARCH_KEY = "defaultSearch";
|
|
|
|
var gEngineView = null;
|
|
|
|
var gSearchPane = {
|
|
/**
|
|
* Initialize autocomplete to ensure prefs are in sync.
|
|
*/
|
|
_initAutocomplete() {
|
|
Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"].getService(
|
|
Ci.mozIPlacesAutoComplete
|
|
);
|
|
},
|
|
|
|
init() {
|
|
gEngineView = new EngineView(new EngineStore());
|
|
document.getElementById("engineList").view = gEngineView;
|
|
this.buildDefaultEngineDropDowns().catch(console.error);
|
|
|
|
if (
|
|
Services.policies &&
|
|
!Services.policies.isAllowed("installSearchEngine")
|
|
) {
|
|
document.getElementById("addEnginesBox").hidden = true;
|
|
} else {
|
|
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 = Preferences.get("browser.search.suggest.enabled");
|
|
let urlbarSuggestsPref = Preferences.get("browser.urlbar.suggest.searches");
|
|
let privateSuggestsPref = Preferences.get(
|
|
"browser.search.suggest.enabled.private"
|
|
);
|
|
let updateSuggestionCheckboxes = this._updateSuggestionCheckboxes.bind(
|
|
this
|
|
);
|
|
suggestsPref.on("change", updateSuggestionCheckboxes);
|
|
urlbarSuggestsPref.on("change", updateSuggestionCheckboxes);
|
|
let urlbarSuggests = document.getElementById("urlBarSuggestion");
|
|
urlbarSuggests.addEventListener("command", () => {
|
|
urlbarSuggestsPref.value = urlbarSuggests.checked;
|
|
});
|
|
let privateWindowCheckbox = document.getElementById(
|
|
"showSearchSuggestionsPrivateWindows"
|
|
);
|
|
privateWindowCheckbox.addEventListener("command", () => {
|
|
privateSuggestsPref.value = privateWindowCheckbox.checked;
|
|
});
|
|
|
|
setEventListener(
|
|
"browserSeparateDefaultEngine",
|
|
"command",
|
|
this._onBrowserSeparateDefaultEngineChange.bind(this)
|
|
);
|
|
setEventListener("openLocationBarPrivacyPreferences", "click", function(
|
|
event
|
|
) {
|
|
if (event.button == 0) {
|
|
gotoPref("privacy-locationBar");
|
|
}
|
|
});
|
|
|
|
this._initDefaultEngines();
|
|
this._initShowSearchSuggestionsFirst();
|
|
this._updateSuggestionCheckboxes();
|
|
},
|
|
|
|
/**
|
|
* Initialize the default engine handling. This will hide the private default
|
|
* options if they are not enabled yet.
|
|
*/
|
|
_initDefaultEngines() {
|
|
this._separatePrivateDefaultEnabledPref = Preferences.get(
|
|
"browser.search.separatePrivateDefault.ui.enabled"
|
|
);
|
|
|
|
this._separatePrivateDefaultPref = Preferences.get(
|
|
"browser.search.separatePrivateDefault"
|
|
);
|
|
|
|
const checkbox = document.getElementById("browserSeparateDefaultEngine");
|
|
checkbox.checked = !this._separatePrivateDefaultPref.value;
|
|
|
|
this._updatePrivateEngineDisplayBoxes();
|
|
|
|
const listener = () => {
|
|
this._updatePrivateEngineDisplayBoxes();
|
|
this.buildDefaultEngineDropDowns().catch(console.error);
|
|
};
|
|
|
|
this._separatePrivateDefaultEnabledPref.on("change", listener);
|
|
this._separatePrivateDefaultPref.on("change", listener);
|
|
},
|
|
|
|
_updatePrivateEngineDisplayBoxes() {
|
|
const separateEnabled = this._separatePrivateDefaultEnabledPref.value;
|
|
document.getElementById(
|
|
"browserSeparateDefaultEngine"
|
|
).hidden = !separateEnabled;
|
|
|
|
const separateDefault = this._separatePrivateDefaultPref.value;
|
|
|
|
const vbox = document.getElementById("browserPrivateEngineSelection");
|
|
vbox.hidden = !separateEnabled || !separateDefault;
|
|
},
|
|
|
|
_onBrowserSeparateDefaultEngineChange(event) {
|
|
this._separatePrivateDefaultPref.value = !event.target.checked;
|
|
},
|
|
|
|
_initShowSearchSuggestionsFirst() {
|
|
this._urlbarSuggestionsPosPref = Preferences.get(
|
|
"browser.urlbar.matchBuckets"
|
|
);
|
|
let checkbox = document.getElementById(
|
|
"showSearchSuggestionsFirstCheckbox"
|
|
);
|
|
|
|
this._urlbarSuggestionsPosPref.on("change", () => {
|
|
this._syncFromShowSearchSuggestionsFirstPref(checkbox);
|
|
});
|
|
this._syncFromShowSearchSuggestionsFirstPref(checkbox);
|
|
|
|
checkbox.addEventListener("command", () => {
|
|
this._syncToShowSearchSuggestionsFirstPref(checkbox.checked);
|
|
});
|
|
},
|
|
|
|
_syncFromShowSearchSuggestionsFirstPref(checkbox) {
|
|
if (!this._urlbarSuggestionsPosPref.value) {
|
|
// The pref is cleared, meaning search suggestions are shown first.
|
|
checkbox.checked = true;
|
|
return;
|
|
}
|
|
// The pref has a value. If the first bucket in the pref is search
|
|
// suggestions, then check the checkbox.
|
|
let buckets = PlacesUtils.convertMatchBucketsStringToArray(
|
|
this._urlbarSuggestionsPosPref.value
|
|
);
|
|
checkbox.checked = buckets[0] && buckets[0][0] == "suggestion";
|
|
},
|
|
|
|
_syncToShowSearchSuggestionsFirstPref(checked) {
|
|
if (checked) {
|
|
// Show search suggestions first, so clear the pref since that's the
|
|
// default.
|
|
this._urlbarSuggestionsPosPref.reset();
|
|
return;
|
|
}
|
|
// Show history first.
|
|
this._urlbarSuggestionsPosPref.value = "general:5,suggestion:Infinity";
|
|
},
|
|
|
|
_updateSuggestionCheckboxes() {
|
|
let suggestsPref = Preferences.get("browser.search.suggest.enabled");
|
|
let permanentPB = Services.prefs.getBoolPref(
|
|
"browser.privatebrowsing.autostart"
|
|
);
|
|
let urlbarSuggests = document.getElementById("urlBarSuggestion");
|
|
let positionCheckbox = document.getElementById(
|
|
"showSearchSuggestionsFirstCheckbox"
|
|
);
|
|
let privateWindowCheckbox = document.getElementById(
|
|
"showSearchSuggestionsPrivateWindows"
|
|
);
|
|
|
|
urlbarSuggests.disabled = !suggestsPref.value || permanentPB;
|
|
privateWindowCheckbox.disabled = !suggestsPref.value;
|
|
privateWindowCheckbox.checked = Preferences.get(
|
|
"browser.search.suggest.enabled.private"
|
|
).value;
|
|
if (privateWindowCheckbox.disabled) {
|
|
privateWindowCheckbox.checked = false;
|
|
}
|
|
|
|
let urlbarSuggestsPref = Preferences.get("browser.urlbar.suggest.searches");
|
|
urlbarSuggests.checked = urlbarSuggestsPref.value;
|
|
if (urlbarSuggests.disabled) {
|
|
urlbarSuggests.checked = false;
|
|
}
|
|
|
|
if (urlbarSuggests.checked) {
|
|
positionCheckbox.disabled = false;
|
|
this._syncFromShowSearchSuggestionsFirstPref(positionCheckbox);
|
|
} else {
|
|
positionCheckbox.disabled = true;
|
|
positionCheckbox.checked = false;
|
|
}
|
|
|
|
let permanentPBLabel = document.getElementById(
|
|
"urlBarSuggestionPermanentPBLabel"
|
|
);
|
|
permanentPBLabel.hidden = urlbarSuggests.hidden || !permanentPB;
|
|
},
|
|
|
|
/**
|
|
* Builds the default and private engines drop down lists. This is called
|
|
* each time something affects the list of engines.
|
|
*/
|
|
async buildDefaultEngineDropDowns() {
|
|
await this._buildEngineDropDown(
|
|
document.getElementById("defaultEngine"),
|
|
(await Services.search.getDefault()).name,
|
|
false
|
|
);
|
|
|
|
if (this._separatePrivateDefaultEnabledPref.value) {
|
|
await this._buildEngineDropDown(
|
|
document.getElementById("defaultPrivateEngine"),
|
|
(await Services.search.getDefaultPrivate()).name,
|
|
true
|
|
);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Builds a drop down menu of search engines.
|
|
*
|
|
* @param {DOMMenuList} list
|
|
* The menu list element to attach the list of engines.
|
|
* @param {string} currentEngine
|
|
* The name of the current default engine.
|
|
* @param {boolean} isPrivate
|
|
* True if we are dealing with the default engine for private mode.
|
|
*/
|
|
async _buildEngineDropDown(list, currentEngine, isPrivate) {
|
|
// If the current engine isn't in the list any more, select the first item.
|
|
let engines = gEngineView._engineStore._engines;
|
|
if (!engines.length) {
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
});
|
|
|
|
// We don't currently support overriding the engine for private mode with
|
|
// extensions.
|
|
if (isPrivate) {
|
|
return;
|
|
}
|
|
|
|
handleControllingExtension(SEARCH_TYPE, SEARCH_KEY);
|
|
let searchEngineListener = {
|
|
observe(subject, topic, data) {
|
|
handleControllingExtension(SEARCH_TYPE, SEARCH_KEY);
|
|
},
|
|
};
|
|
Services.obs.addObserver(
|
|
searchEngineListener,
|
|
"browser-search-engine-modified"
|
|
);
|
|
window.addEventListener("unload", () => {
|
|
Services.obs.removeObserver(
|
|
searchEngineListener,
|
|
"browser-search-engine-modified"
|
|
);
|
|
});
|
|
},
|
|
|
|
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
|
|
) {
|
|
if (aEvent.target.parentNode.parentNode.id == "defaultEngine") {
|
|
gSearchPane.setDefaultEngine();
|
|
} else if (
|
|
aEvent.target.parentNode.parentNode.id == "defaultPrivateEngine"
|
|
) {
|
|
gSearchPane.setDefaultPrivateEngine();
|
|
}
|
|
}
|
|
break;
|
|
case "restoreDefaultSearchEngines":
|
|
gSearchPane.onRestoreDefaults();
|
|
break;
|
|
case "removeEngineButton":
|
|
Services.search.removeEngine(
|
|
gEngineView.selectedEngine.originalEngine
|
|
);
|
|
break;
|
|
}
|
|
break;
|
|
case "dragstart":
|
|
if (aEvent.target.id == "engineChildren") {
|
|
onDragEngineStart(aEvent);
|
|
}
|
|
break;
|
|
case "keypress":
|
|
if (aEvent.target.id == "engineList") {
|
|
gSearchPane.onTreeKeyPress(aEvent);
|
|
}
|
|
break;
|
|
case "select":
|
|
if (aEvent.target.id == "engineList") {
|
|
gSearchPane.onTreeSelect();
|
|
}
|
|
break;
|
|
case "blur":
|
|
if (aEvent.target.id == "engineList") {
|
|
gSearchPane.onInputBlur(aEvent);
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
observe(aEngine, aTopic, aVerb) {
|
|
if (aTopic == "browser-search-engine-modified") {
|
|
aEngine.QueryInterface(Ci.nsISearchEngine);
|
|
switch (aVerb) {
|
|
case "engine-added":
|
|
gEngineView._engineStore.addEngine(aEngine);
|
|
gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
break;
|
|
case "engine-changed":
|
|
gEngineView._engineStore.reloadIcons();
|
|
gEngineView.invalidate();
|
|
break;
|
|
case "engine-removed":
|
|
gSearchPane.remove(aEngine);
|
|
break;
|
|
case "engine-default": {
|
|
// If the user is going through the drop down using up/down keys, the
|
|
// dropdown may still be open (eg. on Windows) when engine-default is
|
|
// fired, so rebuilding the list unconditionally would get in the way.
|
|
let selectedEngine = document.getElementById("defaultEngine")
|
|
.selectedItem.engine;
|
|
if (selectedEngine.name != aEngine.name) {
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
}
|
|
break;
|
|
}
|
|
case "engine-default-private": {
|
|
if (
|
|
this._separatePrivateDefaultEnabledPref.value &&
|
|
this._separatePrivateDefaultPref.value
|
|
) {
|
|
// If the user is going through the drop down using up/down keys, the
|
|
// dropdown may still be open (eg. on Windows) when engine-default is
|
|
// fired, so rebuilding the list unconditionally would get in the way.
|
|
const selectedEngine = document.getElementById(
|
|
"defaultPrivateEngine"
|
|
).selectedItem.engine;
|
|
if (selectedEngine.name != aEngine.name) {
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
},
|
|
|
|
async onRestoreDefaults() {
|
|
let num = await 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();
|
|
},
|
|
|
|
async editKeyword(aEngine, aNewKeyword) {
|
|
let keyword = aNewKeyword.trim();
|
|
if (keyword) {
|
|
let eduplicate = false;
|
|
let dupName = "";
|
|
|
|
// Check for duplicates in Places keywords.
|
|
let bduplicate = !!(await PlacesUtils.keywords.fetch(keyword));
|
|
|
|
// Check for duplicates in changes we haven't committed yet
|
|
let engines = gEngineView._engineStore.engines;
|
|
let lc_keyword = keyword.toLocaleLowerCase();
|
|
for (let engine of engines) {
|
|
if (
|
|
engine.alias &&
|
|
engine.alias.toLocaleLowerCase() == lc_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 msgids = [{ id: "search-keyword-warning-title" }];
|
|
if (eduplicate) {
|
|
msgids.push({
|
|
id: "search-keyword-warning-engine",
|
|
args: { name: dupName },
|
|
});
|
|
} else {
|
|
msgids.push({ id: "search-keyword-warning-bookmark" });
|
|
}
|
|
|
|
let [dtitle, msg] = await document.l10n.formatValues(msgids);
|
|
|
|
Services.prompt.alert(window, dtitle, msg);
|
|
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);
|
|
}
|
|
}
|
|
Preferences.get("browser.search.hiddenOneOffs").value = hiddenList.join(
|
|
","
|
|
);
|
|
},
|
|
|
|
async setDefaultEngine() {
|
|
await Services.search.setDefault(
|
|
document.getElementById("defaultEngine").selectedItem.engine
|
|
);
|
|
ExtensionSettingsStore.setByUser(SEARCH_TYPE, SEARCH_KEY);
|
|
},
|
|
|
|
async setDefaultPrivateEngine() {
|
|
await Services.search.setDefaultPrivate(
|
|
document.getElementById("defaultPrivateEngine").selectedItem.engine
|
|
);
|
|
},
|
|
};
|
|
|
|
function onDragEngineStart(event) {
|
|
var selectedIndex = gEngineView.selectedIndex;
|
|
var tree = document.getElementById("engineList");
|
|
let cell = tree.getCellAt(event.clientX, event.clientY);
|
|
if (selectedIndex >= 0 && !gEngineView.isCheckBox(cell.row, cell.col)) {
|
|
event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
|
|
event.dataTransfer.effectAllowed = "move";
|
|
}
|
|
}
|
|
|
|
function EngineStore() {
|
|
let pref = Preferences.get("browser.search.hiddenOneOffs").value;
|
|
this.hiddenList = pref ? pref.split(",") : [];
|
|
|
|
this._engines = [];
|
|
this._defaultEngines = [];
|
|
Promise.all([
|
|
Services.search.getVisibleEngines(),
|
|
Services.search.getDefaultEngines(),
|
|
]).then(([visibleEngines, defaultEngines]) => {
|
|
for (let engine of visibleEngines) {
|
|
this.addEngine(engine);
|
|
gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
|
|
}
|
|
this._defaultEngines = defaultEngines.map(this._cloneEngine, this);
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
|
|
// check if we need to disable the restore defaults button
|
|
var someHidden = this._defaultEngines.some(e => e.hidden);
|
|
gSearchPane.showRestoreDefaults(someHidden);
|
|
});
|
|
}
|
|
EngineStore.prototype = {
|
|
_engines: null,
|
|
_defaultEngines: null,
|
|
|
|
get engines() {
|
|
return this._engines;
|
|
},
|
|
set engines(val) {
|
|
this._engines = val;
|
|
return val;
|
|
},
|
|
|
|
_getIndexForEngine(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.includes(clonedObj.name);
|
|
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 Promise.resolve();
|
|
} // nothing to do
|
|
|
|
// Move the engine in our internal store
|
|
var removedEngine = this._engines.splice(index, 1)[0];
|
|
this._engines.splice(aNewIndex, 0, removedEngine);
|
|
|
|
return 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)) {
|
|
gSearchPane.showRestoreDefaults(true);
|
|
}
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
return index;
|
|
},
|
|
|
|
async 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)) {
|
|
await 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;
|
|
await Services.search.moveEngine(engine, i);
|
|
added++;
|
|
}
|
|
}
|
|
Services.search.resetToOriginalDefaultEngine();
|
|
gSearchPane.showRestoreDefaults(false);
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
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) {
|
|
if (this.tree) {
|
|
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
|
|
);
|
|
},
|
|
|
|
async drop(dropIndex, orientation, dataTransfer) {
|
|
var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
|
|
var sourceEngine = this._engineStore.engines[sourceIndex];
|
|
|
|
const nsITreeView = Ci.nsITreeView;
|
|
if (dropIndex > sourceIndex) {
|
|
if (orientation == nsITreeView.DROP_BEFORE) {
|
|
dropIndex--;
|
|
}
|
|
} else if (orientation == nsITreeView.DROP_AFTER) {
|
|
dropIndex++;
|
|
}
|
|
|
|
await this._engineStore.moveEngine(sourceEngine, dropIndex);
|
|
gSearchPane.showRestoreDefaults(true);
|
|
gSearchPane.buildDefaultEngineDropDowns();
|
|
|
|
// 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;
|
|
},
|
|
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";
|
|
},
|
|
setCellValue(index, column, value) {
|
|
if (column.id == "engineShown") {
|
|
this._engineStore.engines[index].shown = value == "true";
|
|
gEngineView.invalidate();
|
|
gSearchPane.saveOneClickEnginesList();
|
|
}
|
|
},
|
|
setCellText(index, column, value) {
|
|
if (column.id == "engineKeyword") {
|
|
gSearchPane
|
|
.editKeyword(this._engineStore.engines[index], value)
|
|
.then(valid => {
|
|
if (!valid) {
|
|
document.getElementById("engineList").startEditing(index, column);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
};
|