summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/search/content
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/components/search/content
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/components/search/content')
-rw-r--r--comm/suite/components/search/content/engineManager.js508
-rw-r--r--comm/suite/components/search/content/engineManager.xul98
-rw-r--r--comm/suite/components/search/content/search-panel.js86
-rw-r--r--comm/suite/components/search/content/search-panel.xul48
-rw-r--r--comm/suite/components/search/content/search.xml739
-rw-r--r--comm/suite/components/search/content/searchbarBindings.css21
6 files changed, 1500 insertions, 0 deletions
diff --git a/comm/suite/components/search/content/engineManager.js b/comm/suite/components/search/content/engineManager.js
new file mode 100644
index 0000000000..c505bff7fc
--- /dev/null
+++ b/comm/suite/components/search/content/engineManager.js
@@ -0,0 +1,508 @@
+/* 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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+ChromeUtils.defineModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+const ENGINE_FLAVOR = "text/x-moz-search-engine";
+
+const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
+
+var gEngineView = null;
+
+var gEngineManagerDialog = {
+ init: function engineManager_init() {
+ gEngineView = new EngineView(new EngineStore());
+
+ var suggestEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF);
+ document.getElementById("enableSuggest").checked = suggestEnabled;
+
+ var tree = document.getElementById("engineList");
+ tree.view = gEngineView;
+
+ Services.obs.addObserver(this, "browser-search-engine-modified");
+ },
+
+ destroy: function engineManager_destroy() {
+ // Remove the observer
+ Services.obs.removeObserver(this, "browser-search-engine-modified");
+ },
+
+ observe: function engineManager_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);
+ break;
+ case "engine-changed":
+ gEngineView._engineStore.reloadIcons();
+ gEngineView.invalidate();
+ break;
+ case "engine-removed":
+ case "engine-current":
+ case "engine-default":
+ // Not relevant
+ break;
+ }
+ }
+ },
+
+ onOK: function engineManager_onOK() {
+ // Set the preference
+ var newSuggestEnabled = document.getElementById("enableSuggest").checked;
+ Services.prefs.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled);
+
+ // Commit the changes
+ gEngineView._engineStore.commit();
+ },
+
+ onRestoreDefaults: function engineManager_onRestoreDefaults() {
+ var num = gEngineView._engineStore.restoreDefaultEngines();
+ gEngineView.rowCountChanged(0, num);
+ gEngineView.invalidate();
+ },
+
+ showRestoreDefaults: function engineManager_showRestoreDefaults(val) {
+ document.documentElement.getButton("extra2").disabled = !val;
+ },
+
+ loadAddEngines: function engineManager_loadAddEngines() {
+ this.onOK();
+ window.arguments[0].value = true; // see OpenSearchEngineManager()
+ window.close();
+ },
+
+ remove: function engineManager_remove() {
+ gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
+ var index = gEngineView.selectedIndex;
+ gEngineView.rowCountChanged(index, -1);
+ gEngineView.invalidate();
+ gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
+ gEngineView.ensureRowIsVisible(gEngineView.currentIndex);
+ document.getElementById("engineList").focus();
+ },
+
+ /**
+ * Moves the selected engine either up or down in the engine list
+ * @param aDir
+ * -1 to move the selected engine down, +1 to move it up.
+ */
+ bump: function engineManager_move(aDir) {
+ var selectedEngine = gEngineView.selectedEngine;
+ var newIndex = gEngineView.selectedIndex - aDir;
+
+ gEngineView._engineStore.moveEngine(selectedEngine, newIndex);
+
+ gEngineView.invalidate();
+ gEngineView.selection.select(newIndex);
+ gEngineView.ensureRowIsVisible(newIndex);
+ this.showRestoreDefaults(true);
+ document.getElementById("engineList").focus();
+ },
+
+ selectEditKeyword: function engineManager_selectEditKeyword() {
+ let index = gEngineView.selectedIndex;
+ // No engine selected.
+ if (index == -1)
+ return;
+
+ let tree = document.getElementById("engineList");
+ let column = tree.columns.getColumnFor(document.getElementById("engineKeyword"));
+ tree.startEditing(index, column);
+ },
+
+ 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;
+ 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;
+ },
+
+ onSelect: function engineManager_onSelect() {
+ // Buttons only work if an engine is selected and it's not the last engine,
+ // the latter is true when the selected is first and last at the same time.
+ var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex);
+ var firstSelected = (gEngineView.selectedIndex == 0);
+ var noSelection = (gEngineView.selectedIndex == -1);
+
+ document.getElementById("cmd_remove")
+ .setAttribute("disabled", noSelection ||
+ (firstSelected && lastSelected));
+
+ document.getElementById("cmd_moveup")
+ .setAttribute("disabled", noSelection || firstSelected);
+
+ document.getElementById("cmd_movedown")
+ .setAttribute("disabled", noSelection || lastSelected);
+
+ document.getElementById("cmd_editkeyword")
+ .setAttribute("disabled", noSelection);
+ },
+
+ onKeydown: function(aEvent) {
+ var tree = document.getElementById("engineList");
+ if (tree.editingColumn)
+ return;
+
+ if (aEvent.keyCode == (AppConstants.platform == "macosx" ?
+ KeyEvent.DOM_VK_RETURN : KeyEvent.DOM_VK_F2) &&
+ tree.startEditing(gEngineView.selectedIndex,
+ tree.columns.engineKeyword)) {
+ aEvent.preventDefault();
+ }
+ }
+};
+
+function onDragEngineStart(event) {
+ var selectedIndex = gEngineView.selectedIndex;
+ if (selectedIndex >= 0) {
+ event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
+ event.dataTransfer.effectAllowed = "move";
+ }
+}
+
+// "Operation" objects
+function EngineMoveOp(aEngineClone, aNewIndex) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineMoveOp!");
+ this._engine = aEngineClone.originalEngine;
+ this._newIndex = aNewIndex;
+}
+EngineMoveOp.prototype = {
+ _engine: null,
+ _newIndex: null,
+ commit: function EMO_commit() {
+ Services.search.moveEngine(this._engine, this._newIndex);
+ }
+}
+
+function EngineRemoveOp(aEngineClone) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineRemoveOp!");
+ this._engine = aEngineClone.originalEngine;
+}
+EngineRemoveOp.prototype = {
+ _engine: null,
+ commit: function ERO_commit() {
+ Services.search.removeEngine(this._engine);
+ }
+}
+
+function EngineUnhideOp(aEngineClone, aNewIndex) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineUnhideOp!");
+ this._engine = aEngineClone.originalEngine;
+ this._newIndex = aNewIndex;
+}
+EngineUnhideOp.prototype = {
+ _engine: null,
+ _newIndex: null,
+ commit: function EUO_commit() {
+ this._engine.hidden = false;
+ Services.search.moveEngine(this._engine, this._newIndex);
+ }
+}
+
+function EngineChangeOp(aEngineClone, aProp, aValue) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineChangeOp!");
+
+ this._engine = aEngineClone.originalEngine;
+ this._prop = aProp;
+ this._newValue = aValue;
+}
+EngineChangeOp.prototype = {
+ _engine: null,
+ _prop: null,
+ _newValue: null,
+ commit: function ECO_commit() {
+ this._engine[this._prop] = this._newValue;
+ }
+}
+
+function EngineStore() {
+ this._engines = Services.search.getVisibleEngines().map(this._cloneEngine);
+ this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine);
+
+ this._ops = [];
+
+ // check if we need to disable the restore defaults button
+ var someHidden = this._defaultEngines.some(e => e.hidden);
+ gEngineManagerDialog.showRestoreDefaults(someHidden);
+}
+EngineStore.prototype = {
+ _engines: null,
+ _defaultEngines: null,
+ _ops: null,
+
+ get engines() {
+ return this._engines;
+ },
+ set engines(val) {
+ this._engines = val;
+ return val;
+ },
+
+ _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
+ return this._engines.indexOf(aEngine);
+ },
+
+ _getEngineByName: function ES_getEngineByName(aName) {
+ for (var engine of this._engines)
+ if (engine.name == aName)
+ return engine;
+
+ return null;
+ },
+
+ _cloneEngine: function ES_cloneEngine(aEngine) {
+ var clonedObj={};
+ for (var i in aEngine)
+ clonedObj[i] = aEngine[i];
+ clonedObj.originalEngine = aEngine;
+ return clonedObj;
+ },
+
+ // Callback for Array's some(). A thisObj must be passed to some()
+ _isSameEngine: function ES_isSameEngine(aEngineClone) {
+ return aEngineClone.originalEngine == this.originalEngine;
+ },
+
+ commit: function ES_commit() {
+ var currentEngine = this._cloneEngine(Services.search.currentEngine);
+ for (var i = 0; i < this._ops.length; i++)
+ this._ops[i].commit();
+
+ // Restore currentEngine if it is a default engine that is still visible.
+ // Needed if the user deletes currentEngine and then restores it.
+ if (this._defaultEngines.some(this._isSameEngine, currentEngine) &&
+ !currentEngine.originalEngine.hidden)
+ Services.search.currentEngine = currentEngine.originalEngine;
+ },
+
+ addEngine: function ES_addEngine(aEngine) {
+ this._engines.push(this._cloneEngine(aEngine));
+ },
+
+ moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
+ if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
+ throw new Error("ES_moveEngine: invalid aNewIndex!");
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("ES_moveEngine: invalid engine?");
+
+ if (index == aNewIndex)
+ return; // nothing to do
+
+ // Move the engine in our internal store
+ var removedEngine = this._engines.splice(index, 1)[0];
+ this._engines.splice(aNewIndex, 0, removedEngine);
+
+ this._ops.push(new EngineMoveOp(aEngine, aNewIndex));
+ },
+
+ removeEngine: function ES_removeEngine(aEngine) {
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ this._engines.splice(index, 1);
+ this._ops.push(new EngineRemoveOp(aEngine));
+ if (this._defaultEngines.some(this._isSameEngine, aEngine))
+ gEngineManagerDialog.showRestoreDefaults(true);
+ },
+
+ restoreDefaultEngines: function ES_restoreDefaultEngines() {
+ var added = 0;
+
+ for (var i = 0; i < this._defaultEngines.length; ++i) {
+ var e = this._defaultEngines[i];
+
+ // If the engine is already in the list, just move it.
+ if (this._engines.some(this._isSameEngine, e)) {
+ this.moveEngine(this._getEngineByName(e.name), i);
+ } else {
+ // Otherwise, add it back to our internal store
+ this._engines.splice(i, 0, e);
+ this._ops.push(new EngineUnhideOp(e, i));
+ added++;
+ }
+ }
+ gEngineManagerDialog.showRestoreDefaults(false);
+ return added;
+ },
+
+ changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ this._engines[index][aProp] = aNewValue;
+ this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue));
+ },
+
+ reloadIcons: function ES_reloadIcons() {
+ this._engines.forEach(function (e) {
+ e.uri = e.originalEngine.uri;
+ });
+ }
+}
+
+function EngineView(aEngineStore) {
+ this._engineStore = aEngineStore;
+}
+EngineView.prototype = {
+ _engineStore: null,
+ tree: null,
+
+ get lastIndex() {
+ return this.rowCount - 1;
+ },
+ get selectedIndex() {
+ var seln = this.selection;
+ if (seln.getRangeCount() > 0) {
+ var min = {};
+ seln.getRangeAt(0, min, {});
+ return min.value;
+ }
+ return -1;
+ },
+ get selectedEngine() {
+ return this._engineStore.engines[this.selectedIndex];
+ },
+
+ // Helpers
+ rowCountChanged: function (index, count) {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function () {
+ this.tree.invalidate();
+ },
+
+ ensureRowIsVisible: function (index) {
+ this.tree.ensureRowIsVisible(index);
+ },
+
+ getSourceIndexFromDrag: function (dataTransfer) {
+ return parseInt(dataTransfer.getData(ENGINE_FLAVOR));
+ },
+
+ // nsITreeView
+ get rowCount() {
+ return this._engineStore.engines.length;
+ },
+
+ getImageSrc: function(index, column) {
+ if (column.id == "engineName" && this._engineStore.engines[index].iconURI)
+ return this._engineStore.engines[index].iconURI.spec;
+ return "";
+ },
+
+ getCellText: function(index, column) {
+ if (column.id == "engineName")
+ return this._engineStore.engines[index].name;
+ else if (column.id == "engineKeyword")
+ return this._engineStore.engines[index].alias;
+ return "";
+ },
+
+ setCellText: function(index, column, value) {
+ if (column.id == "engineKeyword") {
+ gEngineManagerDialog.editKeyword(this._engineStore.engines[index], value)
+ .then(valid => {
+ if (!valid)
+ document.getElementById("engineList").startEditing(index, column);
+ });
+ }
+ },
+
+ setTree: function(tree) {
+ this.tree = tree;
+ },
+
+ canDrop: function(targetIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ return (sourceIndex != -1 &&
+ sourceIndex != targetIndex &&
+ sourceIndex != targetIndex + orientation);
+ },
+
+ drop: function(dropIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ var sourceEngine = this._engineStore.engines[sourceIndex];
+
+ if (dropIndex > sourceIndex) {
+ if (orientation == Ci.nsITreeView.DROP_BEFORE)
+ dropIndex--;
+ } else {
+ if (orientation == Ci.nsITreeView.DROP_AFTER)
+ dropIndex++;
+ }
+
+ this._engineStore.moveEngine(sourceEngine, dropIndex);
+ gEngineManagerDialog.showRestoreDefaults(true);
+
+ // Redraw, and adjust selection
+ this.invalidate();
+ this.selection.select(dropIndex);
+ },
+
+ selection: null,
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(index, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function(index) { return false; },
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(parentIndex, index) { return false; },
+ getLevel: function(index) { return 0; },
+ getProgressMode: function(index, column) { },
+ getCellValue: function(index, column) { },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(column) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(index, column) { return column.id == "engineKeyword"; },
+ isSelectable: function(index, column) { return false; },
+ setCellValue: function(index, column, value) { },
+};
diff --git a/comm/suite/components/search/content/engineManager.xul b/comm/suite/components/search/content/engineManager.xul
new file mode 100644
index 0000000000..b160afc4eb
--- /dev/null
+++ b/comm/suite/components/search/content/engineManager.xul
@@ -0,0 +1,98 @@
+<?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/"?>
+<?xml-stylesheet href="chrome://communicator/skin/search/engineManager.css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://communicator/locale/search/engineManager.dtd">
+
+<dialog id="engineManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel,extra2"
+ buttonlabelextra2="&restoreDefaults.label;"
+ buttonaccesskeyextra2="&restoreDefaults.accesskey;"
+ onload="gEngineManagerDialog.init();"
+ onunload="gEngineManagerDialog.destroy();"
+ ondialogaccept="gEngineManagerDialog.onOK();"
+ ondialogextra2="gEngineManagerDialog.onRestoreDefaults();"
+ title="&engineManager.title;"
+ style="&engineManager.style;"
+ persist="screenX screenY width height"
+ windowtype="Browser:SearchManager">
+
+ <script src="chrome://communicator/content/search/engineManager.js"/>
+
+ <commandset id="engineManagerCommandSet">
+ <command id="cmd_remove"
+ oncommand="gEngineManagerDialog.remove();"
+ disabled="true"/>
+ <command id="cmd_moveup"
+ oncommand="gEngineManagerDialog.bump(1);"
+ disabled="true"/>
+ <command id="cmd_movedown"
+ oncommand="gEngineManagerDialog.bump(-1);"
+ disabled="true"/>
+ <command id="cmd_editkeyword"
+ oncommand="gEngineManagerDialog.selectEditKeyword();"
+ disabled="true"/>
+ </commandset>
+
+ <keyset id="engineManagerKeyset">
+ <key id="delete" keycode="VK_DELETE" command="cmd_remove"/>
+ </keyset>
+
+ <stringbundleset id="engineManagerBundleset">
+ <stringbundle id="engineManagerBundle" src="chrome://communicator/locale/search/engineManager.properties"/>
+ </stringbundleset>
+
+ <description>&engineManager.intro;</description>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <tree id="engineList"
+ flex="1"
+ rows="10"
+ hidecolumnpicker="true"
+ editable="true"
+ seltype="single"
+ onselect="gEngineManagerDialog.onSelect();"
+ onkeydown="gEngineManagerDialog.onKeydown(event);">
+ <treechildren id="engineChildren" flex="1"
+ ondragstart="onDragEngineStart(event);"/>
+ <treecols>
+ <treecol id="engineName" flex="4" label="&columnLabel.name;"/>
+ <treecol id="engineKeyword" flex="1" label="&columnLabel.keyword;"/>
+ </treecols>
+ </tree>
+ <vbox>
+ <spacer flex="1"/>
+ <button id="edit"
+ label="&edit.label;"
+ accesskey="&edit.accesskey;"
+ command="cmd_editkeyword"/>
+ <button id="up"
+ label="&up.label;"
+ accesskey="&up.accesskey;"
+ command="cmd_moveup"/>
+ <button id="down"
+ label="&dn.label;"
+ accesskey="&dn.accesskey;"
+ command="cmd_movedown"/>
+ <spacer flex="1"/>
+ <button id="remove"
+ label="&remove.label;"
+ accesskey="&remove.accesskey;"
+ command="cmd_remove"/>
+ </vbox>
+ </hbox>
+ <hbox>
+ <checkbox id="enableSuggest"
+ label="&enableSuggest.label;"
+ accesskey="&enableSuggest.accesskey;"/>
+ </hbox>
+ <hbox>
+ <label id="addEngines" class="text-link" value="&addEngine.label;"
+ onclick="if (event.button == 0) { gEngineManagerDialog.loadAddEngines(); }"/>
+ </hbox>
+</dialog>
diff --git a/comm/suite/components/search/content/search-panel.js b/comm/suite/components/search/content/search-panel.js
new file mode 100644
index 0000000000..c6ab43c082
--- /dev/null
+++ b/comm/suite/components/search/content/search-panel.js
@@ -0,0 +1,86 @@
+/* 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 {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {FormHistory} = ChromeUtils.import("resource://gre/modules/FormHistory.jsm");
+
+const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
+var isPB, menulist, textbox;
+
+function Startup() {
+ menulist = document.getElementById("sidebar-search-engines");
+ textbox = document.getElementById("sidebar-search-text");
+ isPB = top.gPrivate;
+ if (isPB)
+ textbox.searchParam += "|private";
+
+ LoadEngineList();
+ Services.obs.addObserver(engineObserver, SEARCH_ENGINE_TOPIC, true);
+}
+
+function LoadEngineList() {
+ var currentEngineName = Services.search.currentEngine.name;
+ // Make sure the popup is empty.
+ menulist.removeAllItems();
+
+ var engines = Services.search.getVisibleEngines();
+ for (let engine of engines) {
+ let name = engine.name;
+ let menuitem = menulist.appendItem(name, name);
+ menuitem.setAttribute("class", "menuitem-iconic");
+ if (engine.iconURI)
+ menuitem.setAttribute("image", engine.iconURI.spec);
+ menuitem.engine = engine;
+ if (engine.name == currentEngineName) {
+ // Set selection to the current default engine.
+ menulist.selectedItem = menuitem;
+ }
+ }
+ // If the current engine isn't in the list any more, select the first item.
+ if (menulist.selectedIndex < 0)
+ menulist.selectedIndex = 0;
+}
+
+function SelectEngine() {
+ if (menulist.selectedItem)
+ Services.search.currentEngine = menulist.selectedItem.engine;
+ Services.obs.notifyObservers(null, SEARCH_ENGINE_TOPIC, "engine-current");
+}
+
+function doSearch() {
+ var textValue = textbox.value;
+
+ // Save the current value in the form history (shared with the search bar)
+ // except when in Private Browsing mode.
+
+ if (textValue && !isPB) {
+ FormHistory.update({
+ op: "bump",
+ fieldname: "searchbar-history",
+ value: textValue
+ }, {
+ handleError: function(aError) {
+ Cu.reportError("Saving search to form history failed: " + aError.message);
+ }
+ });
+ }
+
+ var where = Services.prefs.getBoolPref("browser.search.openintab") ? "tab" : "current";
+ var submission = Services.search.currentEngine.getSubmission(textValue);
+ openUILinkIn(submission.uri.spec, where, null, submission.postData);
+}
+
+var engineObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ observe: function(aEngine, aTopic, aVerb) {
+ if (aTopic == SEARCH_ENGINE_TOPIC) {
+ // Right now, always just rebuild the list after any modification.
+ LoadEngineList();
+ }
+ }
+}
diff --git a/comm/suite/components/search/content/search-panel.xul b/comm/suite/components/search/content/search-panel.xul
new file mode 100644
index 0000000000..8f7c86285e
--- /dev/null
+++ b/comm/suite/components/search/content/search-panel.xul
@@ -0,0 +1,48 @@
+<?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://communicator/content/search/searchbarBindings.css"?>
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/search/search.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://communicator/locale/search/search-panel.dtd" >
+<page id="searchPanel"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="Startup();"
+ elementtofocus="sidebar-search-text">
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://communicator/content/search/search-panel.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+
+ <popupset id="sidebarPopupset">
+ <panel id="PopupAutoComplete"
+ type="autocomplete"
+ noautofocus="true"/>
+ </popupset>
+
+ <menulist id="sidebar-search-engines"
+ oncommand="SelectEngine(this);"/>
+
+ <hbox align="center">
+ <textbox id="sidebar-search-text" flex="1"
+ class="search-textbox padded"
+ ontextentered="doSearch();"
+ placeholder="&search.placeholder;"
+ type="autocomplete"
+ inputtype="search"
+ autocompletepopup="PopupAutoComplete"
+ autocompletesearch="search-autocomplete"
+ autocompletesearchparam="searchbar-history"
+ maxrows="10"
+ completeselectedindex="true"
+ tabscrolling="true"/>
+ <button id="searchButton" label="&search.button.label;"
+ oncommand="doSearch();"/>
+ </hbox>
+ <button id="managerButton"
+ label="&search.engineManager.label;"
+ oncommand="window.top.OpenSearchEngineManager();"/>
+</page>
diff --git a/comm/suite/components/search/content/search.xml b/comm/suite/components/search/content/search.xml
new file mode 100644
index 0000000000..8e89824314
--- /dev/null
+++ b/comm/suite/components/search/content/search.xml
@@ -0,0 +1,739 @@
+<?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 bindings [
+ <!ENTITY % searchBarDTD SYSTEM "chrome://communicator/locale/search/searchbar.dtd">
+ %searchBarDTD;
+ <!ENTITY % textcontextDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd">
+ %textcontextDTD;
+ <!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd">
+ %navigatorDTD;
+]>
+
+<bindings id="SearchBindings"
+ 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="searchbar">
+ <resources>
+ <stylesheet src="chrome://communicator/content/search/searchbarBindings.css"/>
+ <stylesheet src="chrome://communicator/skin/search/searchbar.css"/>
+ </resources>
+ <content>
+ <xul:stringbundle src="chrome://communicator/locale/search/search.properties"
+ anonid="searchbar-stringbundle"/>
+ <!--
+ There is a dependency between "maxrows" attribute and
+ "SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing
+ one of them requires changing the other one.
+ -->
+ <xul:textbox class="searchbar-textbox"
+ anonid="searchbar-textbox"
+ type="autocomplete"
+ inputtype="search"
+ flex="1"
+ autocompletepopup="_child"
+ autocompletesearch="search-autocomplete"
+ autocompletesearchparam="searchbar-history"
+ maxrows="10"
+ completeselectedindex="true"
+ showcommentcolumn="true"
+ tabscrolling="true"
+ xbl:inherits="disabled">
+ <xul:box>
+ <xul:toolbarbutton class="plain searchbar-engine-button"
+ type="menu"
+ anonid="searchbar-engine-button"
+ xbl:inherits="image">
+ <xul:menupopup class="searchbar-popup"
+ anonid="searchbar-popup">
+ <xul:menuseparator/>
+ <xul:menuitem class="open-engine-manager"
+ anonid="open-engine-manager"
+ label="&cmd_engineManager.label;"
+ oncommand="OpenSearchEngineManager();"/>
+ </xul:menupopup>
+ </xul:toolbarbutton>
+ </xul:box>
+ <xul:hbox class="search-go-container">
+ <xul:image class="search-go-button"
+ anonid="search-go-button"
+ onclick="handleSearchCommand(event);"
+ tooltiptext="&searchEndCap.label;"/>
+ </xul:hbox>
+ <xul:panel anonid="searchPopupAutoComplete"
+ type="autocomplete"
+ noautofocus="true"/>
+ </xul:textbox>
+ </content>
+
+ <implementation implements="nsIObserver, nsIBrowserSearchInitObserver, nsISearchInstallCallback">
+ <constructor><![CDATA[
+ if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
+ return;
+
+ if (this.usePrivateBrowsing)
+ this._textbox.searchParam += "|private";
+
+ Services.obs.addObserver(this, "browser-search-engine-modified");
+ Services.obs.addObserver(this, "browser-search-service");
+ this._initialized = true;
+
+ Services.search.init(this);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this._initialized) {
+ this._initialized = false;
+ Services.obs.removeObserver(this, "browser-search-engine-modified");
+ Services.obs.removeObserver(this, "browser-search-service");
+ }
+
+ // Make sure to break the cycle from _textbox to us. Otherwise we leak
+ // the world. But make sure it's actually pointing to us.
+ // Also make sure the textbox has ever been constructed, otherwise the
+ // _textbox getter will cause the textbox constructor to run, add an
+ // observer, and leak the world too.
+ if (this._textboxInitialized && this._textbox.mController.input == this)
+ this._textbox.mController.input = null;
+ ]]></destructor>
+
+ <field name="_stringBundle">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-stringbundle");</field>
+ <field name="_textboxInitialized">false</field>
+ <field name="_textbox">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-textbox");</field>
+ <field name="_popup">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-popup");</field>
+ <field name="searchButton">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-engine-button");</field>
+ <field name="usePrivateBrowsing" readonly="true">
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .usePrivateBrowsing
+ </field>
+ <field name="_initialized">false</field>
+ <field name="_engines">null</field>
+ <field name="_needToBuildPopup">true</field>
+ <field name="FormHistory" readonly="true"><![CDATA[
+ (ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {}))
+ .FormHistory;
+ ]]>
+ </field>
+ <property name="engines" readonly="true">
+ <getter><![CDATA[
+ if (!this._engines)
+ this._engines = Services.search.getVisibleEngines();
+ return this._engines;
+ ]]></getter>
+ </property>
+
+ <property name="currentEngine">
+ <setter><![CDATA[
+ Services.search.currentEngine = val;
+ Services.obs.notifyObservers(null, "browser-search-engine-modified",
+ "engine-current");
+ return val;
+ ]]></setter>
+ <getter><![CDATA[
+ var currentEngine = Services.search.currentEngine;
+ // Return a dummy engine if there is no currentEngine
+ return currentEngine || {name: "", uri: null};
+ ]]></getter>
+ </property>
+
+ <!-- textbox is used by sanitize.js to clear the undo history when
+ clearing form information. -->
+ <property name="textbox" readonly="true"
+ onget="return this._textbox;"/>
+
+ <property name="value" onget="return this._textbox.value;"
+ onset="return this._textbox.value = val;"/>
+
+ <method name="focus">
+ <body><![CDATA[
+ this._textbox.focus();
+ ]]></body>
+ </method>
+
+ <method name="select">
+ <body><![CDATA[
+ this._textbox.select();
+ ]]></body>
+ </method>
+
+ <method name="onInitComplete">
+ <parameter name="aStatus"/>
+ <body><![CDATA[
+ if (!this._initialized)
+ return;
+ if (Components.isSuccessCode(aStatus)) {
+ // Refresh the display (updating icon, etc)
+ this.updateDisplay();
+ } else {
+ Cu.reportError("Cannot initialize search service, bailing out: " + aStatus);
+ }
+ ]]></body>
+ </method>
+
+ <method name="onSuccess">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ this.currentEngine = aEngine;
+ ]]></body>
+ </method>
+
+ <method name="onError">
+ <parameter name="aErrorCode"/>
+ <body><![CDATA[
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aEngine"/>
+ <parameter name="aTopic"/>
+ <parameter name="aVerb"/>
+ <body><![CDATA[
+ if (aTopic == "browser-search-engine-modified" ||
+ (aTopic == "browser-search-service" && aVerb == "init-complete")) {
+ switch (aVerb) {
+ case "engine-removed":
+ this.offerNewEngine(aEngine);
+ break;
+ case "engine-added":
+ this.hideNewEngine(aEngine);
+ break;
+ case "engine-current":
+ // The current engine was changed. Rebuilding the menu appears to
+ // confuse its idea of whether it should be open when it's just
+ // been clicked, so we force it to close now.
+ this._popup.hidePopup();
+ break;
+ case "engine-changed":
+ // An engine was removed (or hidden) or added, or an icon was
+ // changed. Do nothing special.
+ }
+
+ // Make sure the engine list is refetched next time it's needed
+ this._engines = null;
+
+ // Rebuild the popup and update the display after any modification.
+ this.rebuildPopup();
+ this.updateDisplay();
+ }
+ ]]></body>
+ </method>
+
+ <!-- There are two seaprate lists of search engines, whose uses intersect
+ in this file. The search service (nsIBrowserSearchService and
+ nsSearchService.js) maintains a list of Engine objects which is used to
+ populate the searchbox list of available engines and to perform queries.
+ That list is accessed here via this.SearchService, and it's that sort of
+ Engine that is passed to this binding's observer as aEngine.
+
+ In addition, navigator.js fills two lists of autodetected search engines
+ (browser.engines and browser.hiddenEngines) as properties of
+ mCurrentBrowser. Those lists contain unnamed JS objects of the form
+ { uri:, title:, icon: }, and that's what the searchbar uses to determine
+ whether to show any "Add <EngineName>" menu items in the drop-down.
+
+ The two types of engines are currently related by their identifying
+ titles (the Engine object's 'name'), although that may change; see bug
+ 335102. -->
+
+ <!-- If the engine that was just removed from the searchbox list was
+ autodetected on this page, move it to each browser's active list so it
+ will be offered to be added again. -->
+ <method name="offerNewEngine">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ for (var browser of getBrowser().browsers) {
+ if (browser.hiddenEngines) {
+ // XXX This will need to be changed when engines are identified by
+ // URL rather than title; see bug 335102.
+ var removeTitle = aEngine.wrappedJSObject.name;
+ for (var i = 0; i < browser.hiddenEngines.length; i++) {
+ if (browser.hiddenEngines[i].title == removeTitle) {
+ if (!browser.engines)
+ browser.engines = [];
+ browser.engines.push(browser.hiddenEngines[i]);
+ browser.hiddenEngines.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ this.updateSearchButton();
+ ]]></body>
+ </method>
+
+ <!-- If the engine that was just added to the searchbox list was
+ autodetected on this page, move it to each browser's hidden list so it is
+ no longer offered to be added. -->
+ <method name="hideNewEngine">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ for (var browser of getBrowser().browsers) {
+ if (browser.engines) {
+ // XXX This will need to be changed when engines are identified by
+ // URL rather than title; see bug 335102.
+ var removeTitle = aEngine.wrappedJSObject.name;
+ for (var i = 0; i < browser.engines.length; i++) {
+ if (browser.engines[i].title == removeTitle) {
+ if (!browser.hiddenEngines)
+ browser.hiddenEngines = [];
+ browser.hiddenEngines.push(browser.engines[i]);
+ browser.engines.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ this.updateSearchButton();
+ ]]></body>
+ </method>
+
+ <method name="updateSearchButton">
+ <body><![CDATA[
+ var engines = getBrowser().mCurrentBrowser.engines;
+ if (engines && engines.length)
+ this.searchButton.setAttribute("addengines", "true");
+ else
+ this.searchButton.removeAttribute("addengines");
+ ]]></body>
+ </method>
+
+ <method name="updateDisplay">
+ <body><![CDATA[
+ var uri = this.currentEngine.iconURI;
+ this.setAttribute("image", uri ? uri.spec : "");
+
+ var name = this.currentEngine.name;
+ var text = this._stringBundle.getFormattedString("searchtip", [name]);
+ this._textbox.placeholder = name;
+ this._textbox.tooltipText = text;
+ ]]></body>
+ </method>
+
+ <!-- Rebuilds the dynamic portion of the popup menu (i.e., the menu items
+ for new search engines that can be added to the available list). This
+ is called each time the popup is shown.
+ -->
+ <method name="rebuildPopupDynamic">
+ <body><![CDATA[
+ // We might not have added the main popup items yet, do that first
+ // if needed.
+ if (this._needToBuildPopup)
+ this.rebuildPopup();
+
+ var popup = this._popup;
+ // Clear any addengine menuitems, including addengine-item entries and
+ // the addengine-separator. Work backward to avoid invalidating the
+ // indexes as items are removed.
+ var items = popup.childNodes;
+ for (var i = items.length - 1; i >= 0; i--) {
+ if (items[i].classList.contains("addengine-item") ||
+ items[i].classList.contains("addengine-separator"))
+ items[i].remove();
+ }
+
+ var addengines = getBrowser().mCurrentBrowser.engines;
+ if (addengines && addengines.length > 0) {
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ // Find the (first) separator in the remaining menu, or the first item
+ // if no separators are present.
+ var insertLocation = popup.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.localName != "menuseparator") {
+ insertLocation = insertLocation.nextSibling;
+ }
+ if (insertLocation.localName != "menuseparator")
+ insertLocation = popup.firstChild;
+
+ var separator = document.createElementNS(kXULNS, "menuseparator");
+ separator.setAttribute("class", "addengine-separator");
+ popup.insertBefore(separator, insertLocation);
+
+ // Insert the "add this engine" items.
+ for (var i = 0; i < addengines.length; i++) {
+ var engineInfo = addengines[i];
+ var labelStr =
+ this._stringBundle.getFormattedString("cmd_addFoundEngine",
+ [engineInfo.title]);
+ var menuitem = document.createElementNS(kXULNS, "menuitem");
+ menuitem.setAttribute("class", "menuitem-iconic addengine-item");
+ menuitem.setAttribute("label", labelStr);
+ menuitem.setAttribute("tooltiptext", engineInfo.uri);
+ menuitem.setAttribute("uri", engineInfo.uri);
+ if (engineInfo.icon)
+ menuitem.setAttribute("image", engineInfo.icon);
+ menuitem.setAttribute("title", engineInfo.title);
+ popup.insertBefore(menuitem, insertLocation);
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- Rebuilds the list of visible search engines in the menu. Does not remove
+ or update any dynamic entries (i.e., "Add this engine" items) nor the
+ Manage Engines item. This is called by the observer when the list of
+ visible engines, or the currently selected engine, has changed.
+ -->
+ <method name="rebuildPopup">
+ <body><![CDATA[
+ var popup = this._popup;
+
+ // Clear the popup, down to the first separator
+ while (popup.firstChild && popup.firstChild.localName != "menuseparator")
+ popup.firstChild.remove();
+
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var engines = this.engines;
+ for (var i = engines.length - 1; i >= 0; --i) {
+ var menuitem = document.createElementNS(kXULNS, "menuitem");
+ var name = engines[i].name;
+ menuitem.setAttribute("label", name);
+ menuitem.setAttribute("class", "menuitem-iconic searchbar-engine-menuitem menuitem-with-favicon");
+ // Since this menu is rebuilt by the observer method whenever a new
+ // engine is selected, the "selected" attribute does not need to be
+ // explicitly cleared anywhere.
+ if (engines[i] == this.currentEngine)
+ menuitem.setAttribute("selected", "true");
+ var tooltip = this._stringBundle.getFormattedString("searchtip", [name]);
+ menuitem.setAttribute("tooltiptext", tooltip);
+ if (engines[i].iconURI)
+ menuitem.setAttribute("image", engines[i].iconURI.spec);
+ popup.insertBefore(menuitem, popup.firstChild);
+ menuitem.engine = engines[i];
+ }
+
+ this._needToBuildPopup = false;
+ ]]></body>
+ </method>
+
+ <method name="selectEngine">
+ <parameter name="aEvent"/>
+ <parameter name="isNextEngine"/>
+ <body><![CDATA[
+ // Find the new index
+ var newIndex = this.engines.indexOf(this.currentEngine);
+ newIndex += isNextEngine ? 1 : -1;
+
+ if (newIndex >= 0 && newIndex < this.engines.length)
+ this.currentEngine = this.engines[newIndex];
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ ]]></body>
+ </method>
+
+ <method name="handleSearchCommand">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var textBox = this._textbox;
+ var textValue = textBox.value;
+
+ var where = "current";
+ if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
+ if (aEvent.button == 2)
+ return;
+ where = whereToOpenLink(aEvent, false, true);
+ }
+ else {
+ var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
+ if ((aEvent && aEvent.altKey) ^ newTabPref)
+ where = "tabfocused";
+ }
+
+ this.doSearch(textValue, where);
+ ]]></body>
+ </method>
+
+ <method name="doSearch">
+ <parameter name="aData"/>
+ <parameter name="aWhere"/>
+ <body><![CDATA[
+ var textBox = this._textbox;
+
+ // Save the current value in the form history.
+ if (aData && !this.usePrivateBrowsing && this.FormHistory.enabled) {
+ this.FormHistory.update(
+ { op: "bump",
+ fieldname: textBox.getAttribute("autocompletesearchparam"),
+ value: aData },
+ { handleError(aError) {
+ Cu.reportError("Saving search to form history failed: " + aError.message);
+ }});
+ }
+
+ // null parameter below specifies HTML response for search
+ var submission = this.currentEngine.getSubmission(aData);
+ openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command"><![CDATA[
+ const target = event.originalTarget;
+ if (target.engine) {
+ this.currentEngine = target.engine;
+ } else if (target.classList.contains("addengine-item")) {
+ // We only detect OpenSearch files
+ var type = Ci.nsISearchEngine.DATA_XML;
+ // Select the installed engine if the installation succeeds
+ Services.search.addEngine(target.getAttribute("uri"), type,
+ target.getAttribute("image"), false,
+ this);
+ }
+ else
+ return;
+
+ this.focus();
+ this.select();
+ ]]></handler>
+
+ <handler event="popupshowing" action="this.rebuildPopupDynamic();"/>
+
+ <handler event="DOMMouseScroll"
+ phase="capturing"
+ modifiers="accel"
+ action="this.selectEngine(event, (event.detail > 0));"/>
+
+ <handler event="focus"><![CDATA[
+ // Speculatively connect to the current engine's search URI (and
+ // suggest URI, if different) to reduce request latency.
+ this.currentEngine.speculativeConnect({window: window});
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="search-textbox"
+ extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+ <implementation implements="nsIObserver">
+ <constructor><![CDATA[
+ var bindingParent = document.getBindingParent(this);
+ if (bindingParent && bindingParent.parentNode.parentNode.localName ==
+ "toolbarpaletteitem")
+ return;
+
+ if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll"))
+ this.setAttribute("clickSelectsAll", true);
+
+ // Add items to context menu and attach controller to handle them
+ this.controllers.appendController(this.searchbarController);
+ // bindingParent is not set in the sidebar because there is no
+ // searchbar created in it.
+ if (bindingParent) {
+ bindingParent._textboxInitialized = true;
+ }
+
+ // Add observer for suggest preference
+ Services.prefs.addObserver("browser.search.suggest.enabled", this);
+
+ this._inputBox.setAttribute("suggestchecked", this._suggestEnabled);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ Services.prefs.removeObserver("browser.search.suggest.enabled", this);
+
+ // Because XBL and the customize toolbar code interacts poorly,
+ // there may not be anything to remove here
+ try {
+ this.controllers.removeController(this.searchbarController);
+ } catch (ex) { }
+ ]]></destructor>
+ <field name="_inputBox">
+ document.getAnonymousElementByAttribute(this, "anonid", "textbox-input-box");
+ </field>
+ <field name="_suggestEnabled">
+ Services.prefs.getBoolPref("browser.search.suggest.enabled");
+ </field>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed") {
+ this._suggestEnabled =
+ Services.prefs.getBoolPref("browser.search.suggest.enabled");
+ this._inputBox.setAttribute("suggestchecked", this._suggestEnabled);
+ }
+ ]]></body>
+ </method>
+
+ <field name="FormHistory" readonly="true"><![CDATA[
+ (ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {}))
+ .FormHistory;
+ ]]>
+ </field>
+
+ <!-- nsIController -->
+ <field name="searchbarController" readonly="true"><![CDATA[({
+ supportsCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_pasteAndSearch":
+ case "cmd_clearhistory":
+ case "cmd_togglesuggest":
+ return true;
+ }
+ return false;
+ },
+
+ isCommandEnabled: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_pasteAndSearch":
+ return document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .isCommandEnabled("cmd_paste");
+ case "cmd_clearhistory":
+ case "cmd_togglesuggest":
+ return true;
+ }
+ return false;
+ },
+
+ doCommand: function (aCommand) {
+ switch (aCommand) {
+ case "cmd_pasteAndSearch":
+ this.select();
+ goDoCommand("cmd_paste");
+ this.onTextEntered();
+ break;
+ case "cmd_clearhistory":
+ this.FormHistory.update(
+ { op : "remove", fieldname : "searchbar-history" },
+ null);
+ this.value = "";
+ break;
+ case "cmd_togglesuggest":
+ // The pref observer will update _suggestEnabled and the menu
+ // checkmark.
+ Services.prefs.setBoolPref("browser.search.suggest.enabled",
+ !this._suggestEnabled);
+ break;
+ default:
+ // do nothing with unrecognized command
+ }
+ }.bind(this)
+ })]]></field>
+ </implementation>
+
+ <handlers>
+ <handler event="dragover">
+ <![CDATA[
+ var types = event.dataTransfer.types;
+ if (types.includes("text/plain") || types.includes("text/x-moz-text-internal"))
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="drop">
+ <![CDATA[
+ var dataTransfer = event.dataTransfer;
+ var data = dataTransfer.getData("text/plain");
+ if (!data)
+ data = dataTransfer.getData("text/x-moz-text-internal");
+ if (data) {
+ event.preventDefault();
+ this.value = data;
+ this.onTextEntered();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="searchbar-textbox"
+ extends="chrome://communicator/content/search/search.xml#search-textbox">
+ <implementation>
+ <method name="openSearch">
+ <body>
+ <![CDATA[
+ // Don't open search popup if history popup is open
+ if (!this.popupOpen) {
+ document.getBindingParent(this).searchButton.open = true;
+ return false;
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <!-- override |onTextEntered| in autocomplete.xml -->
+ <method name="onTextEntered">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var evt = aEvent || this.mEnterEvent;
+ document.getBindingParent(this).handleSearchCommand(evt);
+ this.mEnterEvent = null;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_UP" modifiers="accel"
+ phase="capturing"
+ action="document.getBindingParent(this).selectEngine(event, false);"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="accel"
+ phase="capturing"
+ action="document.getBindingParent(this).selectEngine(event, true);"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt"
+ phase="capturing"
+ action="return this.openSearch();"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="alt"
+ phase="capturing"
+ action="return this.openSearch();"/>
+
+ <handler event="keypress" keycode="VK_F4" phase="capturing">
+ <![CDATA[
+ return (AppConstants.platform == "macosx") || this.openSearch()
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="input-box-search" extends="chrome://global/content/bindings/textbox.xml#input-box">
+ <content context="_child">
+ <children/>
+ <xul:menupopup anonid="input-box-contextmenu"
+ class="textbox-contextmenu"
+ onpopupshowing="var input =
+ this.parentNode.getElementsByAttribute('anonid', 'input')[0];
+ if (document.commandDispatcher.focusedElement != input)
+ input.focus();
+ this.parentNode._doPopupItemEnabling(this);"
+ oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if (cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
+ <xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
+ <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
+ <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
+ <xul:menuitem label="&pasteSearchCmd.label;" accesskey="&pasteSearchCmd.accesskey;" cmd="cmd_pasteAndSearch"/>
+ <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&clearHistoryCmd.label;" accesskey="&clearHistoryCmd.accesskey;" cmd="cmd_clearhistory"/>
+ <xul:menuitem label="&showSuggestionsCmd.label;"
+ accesskey="&showSuggestionsCmd.accesskey;"
+ anonid="toggle-suggest-item"
+ type="checkbox"
+ autocheck="false"
+ xbl:inherits="checked=suggestchecked"
+ cmd="cmd_togglesuggest"/>
+ </xul:menupopup>
+ </content>
+ </binding>
+</bindings>
diff --git a/comm/suite/components/search/content/searchbarBindings.css b/comm/suite/components/search/content/searchbarBindings.css
new file mode 100644
index 0000000000..3fefc9365a
--- /dev/null
+++ b/comm/suite/components/search/content/searchbarBindings.css
@@ -0,0 +1,21 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.search-textbox {
+ -moz-binding: url("chrome://communicator/content/search/search.xml#search-textbox");
+}
+
+.searchbar-textbox {
+ -moz-binding: url("chrome://communicator/content/search/search.xml#searchbar-textbox");
+}
+
+.textbox-input-box {
+ -moz-binding: url("chrome://communicator/content/search/search.xml#input-box-search");
+}
+
+.autocomplete-history-dropmarker {
+ display: none;
+}