Bug 449371 Firefox/Thunderbird crashes at exit [@ gdk_display_x11_finalize], p=Brian...
[wine-gecko.git] / browser / components / search / content / engineManager.js
blob3958b2daaa9c6b9fd3711fff0a93389302349663
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
14 # The Original Code is the Browser Search Service.
16 # The Initial Developer of the Original Code is
17 # Google Inc.
18 # Portions created by the Initial Developer are Copyright (C) 2005
19 # the Initial Developer. All Rights Reserved.
21 # Contributor(s):
22 # Ben Goodger <beng@google.com> (Original author)
23 # Gavin Sharp <gavin@gavinsharp.com>
24 # Pamela Greene <pamg.bugs@gmail.com>
25 # Ryan Flint <rflint@dslr.net>
27 # Alternatively, the contents of this file may be used under the terms of
28 # either the GNU General Public License Version 2 or later (the "GPL"), or
29 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 # in which case the provisions of the GPL or the LGPL are applicable instead
31 # of those above. If you wish to allow use of your version of this file only
32 # under the terms of either the GPL or the LGPL, and not to allow others to
33 # use your version of this file under the terms of the MPL, indicate your
34 # decision by deleting the provisions above and replace them with the notice
35 # and other provisions required by the GPL or the LGPL. If you do not delete
36 # the provisions above, a recipient may use your version of this file under
37 # the terms of any one of the MPL, the GPL or the LGPL.
39 # ***** END LICENSE BLOCK *****
41 const Ci = Components.interfaces;
42 const Cc = Components.classes;
44 const ENGINE_FLAVOR = "text/x-moz-search-engine";
46 const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
48 var gEngineView = null;
50 var gEngineManagerDialog = {
51 init: function engineManager_init() {
52 gEngineView = new EngineView(new EngineStore());
54 var prefService = Cc["@mozilla.org/preferences-service;1"].
55 getService(Ci.nsIPrefBranch);
56 var suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
57 document.getElementById("enableSuggest").checked = suggestEnabled;
59 var tree = document.getElementById("engineList");
60 tree.view = gEngineView;
62 var os = Cc["@mozilla.org/observer-service;1"].
63 getService(Ci.nsIObserverService);
64 os.addObserver(this, "browser-search-engine-modified", false);
67 observe: function engineManager_observe(aEngine, aTopic, aVerb) {
68 if (aTopic == "browser-search-engine-modified") {
69 aEngine.QueryInterface(Ci.nsISearchEngine)
70 switch (aVerb) {
71 case "engine-added":
72 gEngineView._engineStore.addEngine(aEngine);
73 gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
74 break;
75 case "engine-changed":
76 gEngineView._engineStore.reloadIcons();
77 break;
78 case "engine-removed":
79 case "engine-current":
80 // Not relevant
81 return;
83 gEngineView.invalidate();
87 onOK: function engineManager_onOK() {
88 // Remove the observer
89 var os = Cc["@mozilla.org/observer-service;1"].
90 getService(Ci.nsIObserverService);
91 os.removeObserver(this, "browser-search-engine-modified");
93 // Set the preference
94 var newSuggestEnabled = document.getElementById("enableSuggest").checked;
95 var prefService = Cc["@mozilla.org/preferences-service;1"].
96 getService(Ci.nsIPrefBranch);
97 prefService.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled);
99 // Commit the changes
100 gEngineView._engineStore.commit();
103 onCancel: function engineManager_onCancel() {
104 // Remove the observer
105 var os = Cc["@mozilla.org/observer-service;1"].
106 getService(Ci.nsIObserverService);
107 os.removeObserver(this, "browser-search-engine-modified");
110 onRestoreDefaults: function engineManager_onRestoreDefaults() {
111 var num = gEngineView._engineStore.restoreDefaultEngines();
112 gEngineView.rowCountChanged(0, num);
113 gEngineView.invalidate();
116 showRestoreDefaults: function engineManager_showRestoreDefaults(val) {
117 document.documentElement.getButton("extra2").disabled = !val;
120 loadAddEngines: function engineManager_loadAddEngines() {
121 this.onOK();
122 window.opener.BrowserSearch.loadAddEngines();
123 window.close();
126 remove: function engineManager_remove() {
127 gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
128 var index = gEngineView.selectedIndex;
129 gEngineView.rowCountChanged(index, -1);
130 gEngineView.invalidate();
131 gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
132 gEngineView.ensureRowIsVisible(Math.min(index, gEngineView.lastIndex));
133 document.getElementById("engineList").focus();
137 * Moves the selected engine either up or down in the engine list
138 * @param aDir
139 * -1 to move the selected engine down, +1 to move it up.
141 bump: function engineManager_move(aDir) {
142 var selectedEngine = gEngineView.selectedEngine;
143 var newIndex = gEngineView.selectedIndex - aDir;
145 gEngineView._engineStore.moveEngine(selectedEngine, newIndex);
147 gEngineView.invalidate();
148 gEngineView.selection.select(newIndex);
149 gEngineView.ensureRowIsVisible(newIndex);
150 this.showRestoreDefaults(true);
151 document.getElementById("engineList").focus();
154 editKeyword: function engineManager_editKeyword() {
155 var selectedEngine = gEngineView.selectedEngine;
156 if (!selectedEngine)
157 return;
159 var prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"].
160 getService(Ci.nsIPromptService);
161 var alias = { value: selectedEngine.alias };
162 var strings = document.getElementById("engineManagerBundle");
163 var title = strings.getString("editTitle");
164 var msg = strings.getFormattedString("editMsg", [selectedEngine.name]);
166 while (prompt.prompt(window, title, msg, alias, null, { })) {
167 var bduplicate = false;
168 var eduplicate = false;
170 if (alias.value != "") {
171 var searchService = Cc["@mozilla.org/browser/search-service;1"].
172 getService(Ci.nsIBrowserSearchService);
173 var engine = searchService.getEngineByAlias(alias.value);
175 if (engine) {
176 if (engine.name != selectedEngine.name)
177 eduplicate = true;
178 } else {
179 try {
180 var bmserv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
181 getService(Ci.nsINavBookmarksService);
182 if (bmserv.getURIForKeyword(alias.value))
183 bduplicate = true;
184 } catch(ex) {}
186 // Check for duplicates in changes we haven't committed yet
187 var engines = gEngineView._engineStore.engines;
188 for each (var engine in engines) {
189 if (engine.alias == alias.value &&
190 engine.name != selectedEngine.name) {
191 eduplicate = true;
192 break;
198 // Notify the user if they have chosen an existing engine/bookmark keyword
199 if (eduplicate || bduplicate) {
200 var dtitle = strings.getString("duplicateTitle");
201 var bmsg = strings.getString("duplicateBookmarkMsg");
202 var emsg = strings.getFormattedString("duplicateEngineMsg",
203 [engine.name]);
205 prompt.alert(window, dtitle, (eduplicate) ? emsg : bmsg);
206 } else {
207 gEngineView._engineStore.changeEngine(selectedEngine, "alias",
208 alias.value);
209 gEngineView.invalidate();
210 break;
215 onSelect: function engineManager_onSelect() {
216 // buttons only work if an engine is selected and it's not the last engine
217 var disableButtons = (gEngineView.selectedIndex == -1) ||
218 (gEngineView.lastIndex == 0);
219 var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex);
220 var firstSelected = (gEngineView.selectedIndex == 0);
221 var noSelection = (gEngineView.selectedIndex == -1);
223 document.getElementById("cmd_remove").setAttribute("disabled",
224 disableButtons);
226 document.getElementById("cmd_moveup").setAttribute("disabled",
227 disableButtons || firstSelected);
229 document.getElementById("cmd_movedown").setAttribute("disabled",
230 disableButtons || lastSelected);
231 document.getElementById("cmd_editkeyword").setAttribute("disabled",
232 noSelection);
236 var gDragObserver = {
237 onDragStart: function (aEvent, aXferData, aDragAction) {
238 var selectedIndex = gEngineView.selectedIndex;
239 if (selectedIndex == -1)
240 return;
242 aXferData.data = new TransferData();
243 aXferData.data.addDataForFlavour(ENGINE_FLAVOR, selectedIndex.toString());
245 aDragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
247 onDrop: function (aEvent, aXferData, aDragSession) { },
248 onDragExit: function (aEvent, aDragSession) { },
249 onDragOver: function (aEvent, aFlavour, aDragSession) { },
250 getSupportedFlavours: function() { return null; }
253 // "Operation" objects
254 function EngineMoveOp(aEngineClone, aNewIndex) {
255 if (!aEngineClone)
256 throw new Error("bad args to new EngineMoveOp!");
257 this._engine = aEngineClone.originalEngine;
258 this._newIndex = aNewIndex;
260 EngineMoveOp.prototype = {
261 _engine: null,
262 _newIndex: null,
263 commit: function EMO_commit() {
264 var searchService = Cc["@mozilla.org/browser/search-service;1"].
265 getService(Ci.nsIBrowserSearchService);
266 searchService.moveEngine(this._engine, this._newIndex);
270 function EngineRemoveOp(aEngineClone) {
271 if (!aEngineClone)
272 throw new Error("bad args to new EngineRemoveOp!");
273 this._engine = aEngineClone.originalEngine;
275 EngineRemoveOp.prototype = {
276 _engine: null,
277 commit: function ERO_commit() {
278 var searchService = Cc["@mozilla.org/browser/search-service;1"].
279 getService(Ci.nsIBrowserSearchService);
280 searchService.removeEngine(this._engine);
284 function EngineUnhideOp(aEngineClone, aNewIndex) {
285 if (!aEngineClone)
286 throw new Error("bad args to new EngineUnhideOp!");
287 this._engine = aEngineClone.originalEngine;
288 this._newIndex = aNewIndex;
290 EngineUnhideOp.prototype = {
291 _engine: null,
292 _newIndex: null,
293 commit: function EUO_commit() {
294 this._engine.hidden = false;
295 var searchService = Cc["@mozilla.org/browser/search-service;1"].
296 getService(Ci.nsIBrowserSearchService);
297 searchService.moveEngine(this._engine, this._newIndex);
301 function EngineChangeOp(aEngineClone, aProp, aValue) {
302 if (!aEngineClone)
303 throw new Error("bad args to new EngineChangeOp!");
305 this._engine = aEngineClone.originalEngine;
306 this._prop = aProp;
307 this._newValue = aValue;
309 EngineChangeOp.prototype = {
310 _engine: null,
311 _prop: null,
312 _newValue: null,
313 commit: function ECO_commit() {
314 this._engine[this._prop] = this._newValue;
318 function EngineStore() {
319 var searchService = Cc["@mozilla.org/browser/search-service;1"].
320 getService(Ci.nsIBrowserSearchService);
321 this._engines = searchService.getVisibleEngines({}).map(this._cloneEngine);
322 this._defaultEngines = searchService.getDefaultEngines({}).map(this._cloneEngine);
324 this._ops = [];
326 // check if we need to disable the restore defaults button
327 var someHidden = this._defaultEngines.some(function (e) {return e.hidden;});
328 gEngineManagerDialog.showRestoreDefaults(someHidden);
330 EngineStore.prototype = {
331 _engines: null,
332 _defaultEngines: null,
333 _ops: null,
335 get engines() {
336 return this._engines;
338 set engines(val) {
339 this._engines = val;
340 return val;
343 _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
344 return this._engines.indexOf(aEngine);
347 _getEngineByName: function ES_getEngineByName(aName) {
348 for each (var engine in this._engines)
349 if (engine.name == aName)
350 return engine;
352 return null;
355 _cloneEngine: function ES_cloneObj(aEngine) {
356 var newO=[];
357 for (var i in aEngine)
358 newO[i] = aEngine[i];
359 newO.originalEngine = aEngine;
360 return newO;
363 // Callback for Array's some(). A thisObj must be passed to some()
364 _isSameEngine: function ES_isSameEngine(aEngineClone) {
365 return aEngineClone.originalEngine == this.originalEngine;
368 commit: function ES_commit() {
369 var searchService = Cc["@mozilla.org/browser/search-service;1"].
370 getService(Ci.nsIBrowserSearchService);
371 var currentEngine = this._cloneEngine(searchService.currentEngine);
372 for (var i = 0; i < this._ops.length; i++)
373 this._ops[i].commit();
375 // Restore currentEngine if it is a default engine that is still visible.
376 // Needed if the user deletes currentEngine and then restores it.
377 if (this._defaultEngines.some(this._isSameEngine, currentEngine) &&
378 !currentEngine.originalEngine.hidden)
379 searchService.currentEngine = currentEngine.originalEngine;
382 addEngine: function ES_addEngine(aEngine) {
383 this._engines.push(this._cloneEngine(aEngine));
386 moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
387 if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
388 throw new Error("ES_moveEngine: invalid aNewIndex!");
389 var index = this._getIndexForEngine(aEngine);
390 if (index == -1)
391 throw new Error("ES_moveEngine: invalid engine?");
393 if (index == aNewIndex)
394 return; // nothing to do
396 // Move the engine in our internal store
397 var removedEngine = this._engines.splice(index, 1)[0];
398 this._engines.splice(aNewIndex, 0, removedEngine);
400 this._ops.push(new EngineMoveOp(aEngine, aNewIndex));
403 removeEngine: function ES_removeEngine(aEngine) {
404 var index = this._getIndexForEngine(aEngine);
405 if (index == -1)
406 throw new Error("invalid engine?");
408 this._engines.splice(index, 1);
409 this._ops.push(new EngineRemoveOp(aEngine));
410 if (this._defaultEngines.some(this._isSameEngine, aEngine))
411 gEngineManagerDialog.showRestoreDefaults(true);
414 restoreDefaultEngines: function ES_restoreDefaultEngines() {
415 var added = 0;
417 for (var i = 0; i < this._defaultEngines.length; ++i) {
418 var e = this._defaultEngines[i];
420 // If the engine is already in the list, just move it.
421 if (this._engines.some(this._isSameEngine, e)) {
422 this.moveEngine(this._getEngineByName(e.name), i);
423 } else {
424 // Otherwise, add it back to our internal store
425 this._engines.splice(i, 0, e);
426 this._ops.push(new EngineUnhideOp(e, i));
427 added++;
430 gEngineManagerDialog.showRestoreDefaults(false);
431 return added;
434 changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
435 var index = this._getIndexForEngine(aEngine);
436 if (index == -1)
437 throw new Error("invalid engine?");
439 this._engines[index][aProp] = aNewValue;
440 this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue));
443 reloadIcons: function ES_reloadIcons() {
444 this._engines.forEach(function (e) {
445 e.uri = e.originalEngine.uri;
450 function EngineView(aEngineStore) {
451 this._engineStore = aEngineStore;
453 EngineView.prototype = {
454 _engineStore: null,
455 tree: null,
457 get lastIndex() {
458 return this.rowCount - 1;
460 get selectedIndex() {
461 var seln = this.selection;
462 if (seln.getRangeCount() > 0) {
463 var min = { };
464 seln.getRangeAt(0, min, { });
465 return min.value;
467 return -1;
469 get selectedEngine() {
470 return this._engineStore.engines[this.selectedIndex];
473 // Helpers
474 rowCountChanged: function (index, count) {
475 this.tree.rowCountChanged(index, count);
478 invalidate: function () {
479 this.tree.invalidate();
482 ensureRowIsVisible: function (index) {
483 this.tree.ensureRowIsVisible(index);
486 getSourceIndexFromDrag: function () {
487 var dragService = Cc["@mozilla.org/widget/dragservice;1"].
488 getService().QueryInterface(Ci.nsIDragService);
489 var dragSession = dragService.getCurrentSession();
490 var transfer = Cc["@mozilla.org/widget/transferable;1"].
491 createInstance(Ci.nsITransferable);
493 transfer.addDataFlavor(ENGINE_FLAVOR);
494 dragSession.getData(transfer, 0);
496 var dataObj = {};
497 var len = {};
498 var sourceIndex = -1;
499 try {
500 transfer.getAnyTransferData({}, dataObj, len);
501 } catch (ex) {}
503 if (dataObj.value) {
504 sourceIndex = dataObj.value.QueryInterface(Ci.nsISupportsString).data;
505 sourceIndex = parseInt(sourceIndex.substring(0, len.value));
508 return sourceIndex;
511 // nsITreeView
512 get rowCount() {
513 return this._engineStore.engines.length;
516 getImageSrc: function(index, column) {
517 if (column.id == "engineName" && this._engineStore.engines[index].iconURI)
518 return this._engineStore.engines[index].iconURI.spec;
519 return "";
522 getCellText: function(index, column) {
523 if (column.id == "engineName")
524 return this._engineStore.engines[index].name;
525 else if (column.id == "engineKeyword")
526 return this._engineStore.engines[index].alias;
527 return "";
530 setTree: function(tree) {
531 this.tree = tree;
534 canDrop: function(targetIndex, orientation) {
535 var sourceIndex = this.getSourceIndexFromDrag();
536 return (sourceIndex != -1 &&
537 sourceIndex != targetIndex &&
538 sourceIndex != (targetIndex + orientation));
541 drop: function(dropIndex, orientation) {
542 var sourceIndex = this.getSourceIndexFromDrag();
543 var sourceEngine = this._engineStore.engines[sourceIndex];
545 if (dropIndex > sourceIndex) {
546 if (orientation == Ci.nsITreeView.DROP_BEFORE)
547 dropIndex--;
548 } else {
549 if (orientation == Ci.nsITreeView.DROP_AFTER)
550 dropIndex++;
553 this._engineStore.moveEngine(sourceEngine, dropIndex);
554 gEngineManagerDialog.showRestoreDefaults(true);
556 // Redraw, and adjust selection
557 this.invalidate();
558 this.selection.clearSelection();
559 this.selection.select(dropIndex);
562 selection: null,
563 getRowProperties: function(index, properties) { },
564 getCellProperties: function(index, column, properties) { },
565 getColumnProperties: function(column, properties) { },
566 isContainer: function(index) { return false; },
567 isContainerOpen: function(index) { return false; },
568 isContainerEmpty: function(index) { return false; },
569 isSeparator: function(index) { return false; },
570 isSorted: function(index) { return false; },
571 getParentIndex: function(index) { return -1; },
572 hasNextSibling: function(parentIndex, index) { return false; },
573 getLevel: function(index) { return 0; },
574 getProgressMode: function(index, column) { },
575 getCellValue: function(index, column) { },
576 toggleOpenState: function(index) { },
577 cycleHeader: function(column) { },
578 selectionChanged: function() { },
579 cycleCell: function(row, column) { },
580 isEditable: function(index, column) { return false; },
581 isSelectable: function(index, column) { return false; },
582 setCellValue: function(index, column, value) { },
583 setCellText: function(index, column, value) { },
584 performAction: function(action) { },
585 performActionOnRow: function(action, index) { },
586 performActionOnCell: function(action, index, column) { }