Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / browser / components / search / content / engineManager.js
blob0b7e75e125669a8067ec5ce0c36ddce38f4206ac
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);
65   },
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;
82       }
83       gEngineView.invalidate();
84     }
85   },
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();
101   },
102   
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");
108   },
110   onRestoreDefaults: function engineManager_onRestoreDefaults() {
111     var num = gEngineView._engineStore.restoreDefaultEngines();
112     gEngineView.rowCountChanged(0, num);
113     gEngineView.invalidate();
114   },
116   showRestoreDefaults: function engineManager_showRestoreDefaults(val) {
117     document.documentElement.getButton("extra2").disabled = !val;
118   },
120   loadAddEngines: function engineManager_loadAddEngines() {
121     this.onOK();
122     window.opener.BrowserSearch.loadAddEngines();
123     window.close();
124   },
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();
134   },
136   /**
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.
140    */
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();
152   },
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         try {
172           let bmserv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
173                        getService(Ci.nsINavBookmarksService);
174           if (bmserv.getURIForKeyword(alias.value))
175             bduplicate = true;
176         } catch(ex) {}
178         // Check for duplicates in changes we haven't committed yet
179         let engines = gEngineView._engineStore.engines;
180         for each (let engine in engines) {
181           if (engine.alias == alias.value && 
182               engine.name != selectedEngine.name) {
183             eduplicate = true;
184             break;
185           }
186         }
187       }
189       // Notify the user if they have chosen an existing engine/bookmark keyword
190       if (eduplicate || bduplicate) {
191         var dtitle = strings.getString("duplicateTitle");
192         var bmsg = strings.getString("duplicateBookmarkMsg");
193         var emsg = strings.getFormattedString("duplicateEngineMsg",
194                                               [engine.name]);
196         prompt.alert(window, dtitle, (eduplicate) ? emsg : bmsg);
197       } else {
198         gEngineView._engineStore.changeEngine(selectedEngine, "alias",
199                                               alias.value);
200         gEngineView.invalidate();
201         break;
202       }
203     }
204   },
206   onSelect: function engineManager_onSelect() {
207     // buttons only work if an engine is selected and it's not the last engine
208     var disableButtons = (gEngineView.selectedIndex == -1) ||
209                          (gEngineView.lastIndex == 0);
210     var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex);
211     var firstSelected = (gEngineView.selectedIndex == 0);
212     var noSelection = (gEngineView.selectedIndex == -1);
214     document.getElementById("cmd_remove").setAttribute("disabled",
215                                                        disableButtons);
217     document.getElementById("cmd_moveup").setAttribute("disabled",
218                                             disableButtons || firstSelected);
220     document.getElementById("cmd_movedown").setAttribute("disabled",
221                                              disableButtons || lastSelected);
222     document.getElementById("cmd_editkeyword").setAttribute("disabled",
223                                                             noSelection);
224   }
227 var gDragObserver = {
228   onDragStart: function (aEvent, aXferData, aDragAction) {
229     var selectedIndex = gEngineView.selectedIndex;
230     if (selectedIndex == -1)
231       return;
233     aXferData.data = new TransferData();
234     aXferData.data.addDataForFlavour(ENGINE_FLAVOR, selectedIndex.toString());
236     aDragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
237   },
238   onDrop: function (aEvent, aXferData, aDragSession) { },
239   onDragExit: function (aEvent, aDragSession) { },
240   onDragOver: function (aEvent, aFlavour, aDragSession) { },
241   getSupportedFlavours: function() { return null; }
244 // "Operation" objects
245 function EngineMoveOp(aEngineClone, aNewIndex) {
246   if (!aEngineClone)
247     throw new Error("bad args to new EngineMoveOp!");
248   this._engine = aEngineClone.originalEngine;
249   this._newIndex = aNewIndex;
251 EngineMoveOp.prototype = {
252   _engine: null,
253   _newIndex: null,
254   commit: function EMO_commit() {
255     var searchService = Cc["@mozilla.org/browser/search-service;1"].
256                         getService(Ci.nsIBrowserSearchService);
257     searchService.moveEngine(this._engine, this._newIndex);
258   }
261 function EngineRemoveOp(aEngineClone) {
262   if (!aEngineClone)
263     throw new Error("bad args to new EngineRemoveOp!");
264   this._engine = aEngineClone.originalEngine;
266 EngineRemoveOp.prototype = {
267   _engine: null,
268   commit: function ERO_commit() {
269     var searchService = Cc["@mozilla.org/browser/search-service;1"].
270                         getService(Ci.nsIBrowserSearchService);
271     searchService.removeEngine(this._engine);
272   }
275 function EngineUnhideOp(aEngineClone, aNewIndex) {
276   if (!aEngineClone)
277     throw new Error("bad args to new EngineUnhideOp!");
278   this._engine = aEngineClone.originalEngine;
279   this._newIndex = aNewIndex;
281 EngineUnhideOp.prototype = {
282   _engine: null,
283   _newIndex: null,
284   commit: function EUO_commit() {
285     this._engine.hidden = false;
286     var searchService = Cc["@mozilla.org/browser/search-service;1"].
287                         getService(Ci.nsIBrowserSearchService);
288     searchService.moveEngine(this._engine, this._newIndex);
289   }
292 function EngineChangeOp(aEngineClone, aProp, aValue) {
293   if (!aEngineClone)
294     throw new Error("bad args to new EngineChangeOp!");
296   this._engine = aEngineClone.originalEngine;
297   this._prop = aProp;
298   this._newValue = aValue;
300 EngineChangeOp.prototype = {
301   _engine: null,
302   _prop: null,
303   _newValue: null,
304   commit: function ECO_commit() {
305     this._engine[this._prop] = this._newValue;
306   }
309 function EngineStore() {
310   var searchService = Cc["@mozilla.org/browser/search-service;1"].
311                       getService(Ci.nsIBrowserSearchService);
312   this._engines = searchService.getVisibleEngines({}).map(this._cloneEngine);
313   this._defaultEngines = searchService.getDefaultEngines({}).map(this._cloneEngine);
315   this._ops = [];
317   // check if we need to disable the restore defaults button
318   var someHidden = this._defaultEngines.some(function (e) {return e.hidden;});
319   gEngineManagerDialog.showRestoreDefaults(someHidden);
321 EngineStore.prototype = {
322   _engines: null,
323   _defaultEngines: null,
324   _ops: null,
326   get engines() {
327     return this._engines;
328   },
329   set engines(val) {
330     this._engines = val;
331     return val;
332   },
334   _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
335     return this._engines.indexOf(aEngine);
336   },
338   _getEngineByName: function ES_getEngineByName(aName) {
339     for each (var engine in this._engines)
340       if (engine.name == aName)
341         return engine;
343     return null;
344   },
346   _cloneEngine: function ES_cloneObj(aEngine) {
347     var newO=[];
348     for (var i in aEngine)
349       newO[i] = aEngine[i];
350     newO.originalEngine = aEngine;
351     return newO;
352   },
354   // Callback for Array's some(). A thisObj must be passed to some()
355   _isSameEngine: function ES_isSameEngine(aEngineClone) {
356     return aEngineClone.originalEngine == this.originalEngine;
357   },
359   commit: function ES_commit() {
360     var searchService = Cc["@mozilla.org/browser/search-service;1"].
361                         getService(Ci.nsIBrowserSearchService);
362     var currentEngine = this._cloneEngine(searchService.currentEngine);
363     for (var i = 0; i < this._ops.length; i++)
364       this._ops[i].commit();
366     // Restore currentEngine if it is a default engine that is still visible.
367     // Needed if the user deletes currentEngine and then restores it.
368     if (this._defaultEngines.some(this._isSameEngine, currentEngine) &&
369         !currentEngine.originalEngine.hidden)
370       searchService.currentEngine = currentEngine.originalEngine;
371   },
373   addEngine: function ES_addEngine(aEngine) {
374     this._engines.push(this._cloneEngine(aEngine));
375   },
377   moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
378     if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
379       throw new Error("ES_moveEngine: invalid aNewIndex!");
380     var index = this._getIndexForEngine(aEngine);
381     if (index == -1)
382       throw new Error("ES_moveEngine: invalid engine?");
384     if (index == aNewIndex)
385       return; // nothing to do
387     // Move the engine in our internal store
388     var removedEngine = this._engines.splice(index, 1)[0];
389     this._engines.splice(aNewIndex, 0, removedEngine);
391     this._ops.push(new EngineMoveOp(aEngine, aNewIndex));
392   },
394   removeEngine: function ES_removeEngine(aEngine) {
395     var index = this._getIndexForEngine(aEngine);
396     if (index == -1)
397       throw new Error("invalid engine?");
399     this._engines.splice(index, 1);
400     this._ops.push(new EngineRemoveOp(aEngine));
401     if (this._defaultEngines.some(this._isSameEngine, aEngine))
402       gEngineManagerDialog.showRestoreDefaults(true);
403   },
405   restoreDefaultEngines: function ES_restoreDefaultEngines() {
406     var added = 0;
408     for (var i = 0; i < this._defaultEngines.length; ++i) {
409       var e = this._defaultEngines[i];
411       // If the engine is already in the list, just move it.
412       if (this._engines.some(this._isSameEngine, e)) {
413         this.moveEngine(this._getEngineByName(e.name), i);
414       } else {
415         // Otherwise, add it back to our internal store
416         this._engines.splice(i, 0, e);
417         this._ops.push(new EngineUnhideOp(e, i));
418         added++;
419       }
420     }
421     gEngineManagerDialog.showRestoreDefaults(false);
422     return added;
423   },
425   changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
426     var index = this._getIndexForEngine(aEngine);
427     if (index == -1)
428       throw new Error("invalid engine?");
430     this._engines[index][aProp] = aNewValue;
431     this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue));
432   },
434   reloadIcons: function ES_reloadIcons() {
435     this._engines.forEach(function (e) {
436       e.uri = e.originalEngine.uri;
437     });
438   }
441 function EngineView(aEngineStore) {
442   this._engineStore = aEngineStore;
444 EngineView.prototype = {
445   _engineStore: null,
446   tree: null,
448   get lastIndex() {
449     return this.rowCount - 1;
450   },
451   get selectedIndex() {
452     var seln = this.selection;
453     if (seln.getRangeCount() > 0) {
454       var min = { };
455       seln.getRangeAt(0, min, { });
456       return min.value;
457     }
458     return -1;
459   },
460   get selectedEngine() {
461     return this._engineStore.engines[this.selectedIndex];
462   },
464   // Helpers
465   rowCountChanged: function (index, count) {
466     this.tree.rowCountChanged(index, count);
467   },
469   invalidate: function () {
470     this.tree.invalidate();
471   },
473   ensureRowIsVisible: function (index) {
474     this.tree.ensureRowIsVisible(index);
475   },
477   getSourceIndexFromDrag: function () {
478     var dragService = Cc["@mozilla.org/widget/dragservice;1"].
479                       getService().QueryInterface(Ci.nsIDragService);
480     var dragSession = dragService.getCurrentSession();
481     var transfer = Cc["@mozilla.org/widget/transferable;1"].
482                    createInstance(Ci.nsITransferable);
484     transfer.addDataFlavor(ENGINE_FLAVOR);
485     dragSession.getData(transfer, 0);
487     var dataObj = {};
488     var len = {};
489     var sourceIndex = -1;
490     try {
491       transfer.getAnyTransferData({}, dataObj, len);
492     } catch (ex) {}
494     if (dataObj.value) {
495       sourceIndex = dataObj.value.QueryInterface(Ci.nsISupportsString).data;
496       sourceIndex = parseInt(sourceIndex.substring(0, len.value));
497     }
499     return sourceIndex;
500   },
502   // nsITreeView
503   get rowCount() {
504     return this._engineStore.engines.length;
505   },
507   getImageSrc: function(index, column) {
508     if (column.id == "engineName" && this._engineStore.engines[index].iconURI)
509       return this._engineStore.engines[index].iconURI.spec;
510     return "";
511   },
513   getCellText: function(index, column) {
514     if (column.id == "engineName")
515       return this._engineStore.engines[index].name;
516     else if (column.id == "engineKeyword")
517       return this._engineStore.engines[index].alias;
518     return "";
519   },
521   setTree: function(tree) {
522     this.tree = tree;
523   },
525   canDrop: function(targetIndex, orientation) {
526     var sourceIndex = this.getSourceIndexFromDrag();
527     return (sourceIndex != -1 &&
528             sourceIndex != targetIndex &&
529             sourceIndex != (targetIndex + orientation));
530   },
532   drop: function(dropIndex, orientation) {
533     var sourceIndex = this.getSourceIndexFromDrag();
534     var sourceEngine = this._engineStore.engines[sourceIndex];
536     if (dropIndex > sourceIndex) {
537       if (orientation == Ci.nsITreeView.DROP_BEFORE)
538         dropIndex--;
539     } else {
540       if (orientation == Ci.nsITreeView.DROP_AFTER)
541         dropIndex++;    
542     }
544     this._engineStore.moveEngine(sourceEngine, dropIndex);
545     gEngineManagerDialog.showRestoreDefaults(true);
547     // Redraw, and adjust selection
548     this.invalidate();
549     this.selection.clearSelection();
550     this.selection.select(dropIndex);
551   },
553   selection: null,
554   getRowProperties: function(index, properties) { },
555   getCellProperties: function(index, column, properties) { },
556   getColumnProperties: function(column, properties) { },
557   isContainer: function(index) { return false; },
558   isContainerOpen: function(index) { return false; },
559   isContainerEmpty: function(index) { return false; },
560   isSeparator: function(index) { return false; },
561   isSorted: function(index) { return false; },
562   getParentIndex: function(index) { return -1; },
563   hasNextSibling: function(parentIndex, index) { return false; },
564   getLevel: function(index) { return 0; },
565   getProgressMode: function(index, column) { },
566   getCellValue: function(index, column) { },
567   toggleOpenState: function(index) { },
568   cycleHeader: function(column) { },
569   selectionChanged: function() { },
570   cycleCell: function(row, column) { },
571   isEditable: function(index, column) { return false; },
572   isSelectable: function(index, column) { return false; },
573   setCellValue: function(index, column, value) { },
574   setCellText: function(index, column, value) { },
575   performAction: function(action) { },
576   performActionOnRow: function(action, index) { },
577   performActionOnCell: function(action, index, column) { }