engineManager.js
Go to the documentation of this file.
1 /*
2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 #
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
9 #
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
14 #
15 # The Original Code is the Browser Search Service.
16 #
17 # The Initial Developer of the Original Code is
18 # Google Inc.
19 # Portions created by the Initial Developer are Copyright (C) 2005
20 # the Initial Developer. All Rights Reserved.
21 #
22 # Contributor(s):
23 # Ben Goodger <beng@google.com> (Original author)
24 # Gavin Sharp <gavin@gavinsharp.com>
25 # Pamela Greene <pamg.bugs@gmail.com>
26 # Ryan Flint <rflint@dslr.net>
27 #
28 # Alternatively, the contents of this file may be used under the terms of
29 # either the GNU General Public License Version 2 or later (the "GPL"), or
30 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 # in which case the provisions of the GPL or the LGPL are applicable instead
32 # of those above. If you wish to allow use of your version of this file only
33 # under the terms of either the GPL or the LGPL, and not to allow others to
34 # use your version of this file under the terms of the MPL, indicate your
35 # decision by deleting the provisions above and replace them with the notice
36 # and other provisions required by the GPL or the LGPL. If you do not delete
37 # the provisions above, a recipient may use your version of this file under
38 # the terms of any one of the MPL, the GPL or the LGPL.
39 #
40 # ***** END LICENSE BLOCK *****
41 */
42 
43 const Ci = Components.interfaces;
44 const Cc = Components.classes;
45 
46 const ENGINE_FLAVOR = "text/x-moz-search-engine";
47 
48 const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
49 
51 
53  init: function engineManager_init() {
54  gEngineView = new EngineView(new EngineStore());
55 
56  var prefService = Cc["@mozilla.org/preferences-service;1"].
57  getService(Ci.nsIPrefBranch);
58  var suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
59  document.getElementById("enableSuggest").checked = suggestEnabled;
60 
61  var tree = document.getElementById("engineList");
62  tree.view = gEngineView;
63 
64  var os = Cc["@mozilla.org/observer-service;1"].
65  getService(Ci.nsIObserverService);
66  os.addObserver(this, "browser-search-engine-modified", false);
67  },
68 
69  observe: function engineManager_observe(aEngine, aTopic, aVerb) {
70  if (aTopic == "browser-search-engine-modified") {
71  aEngine.QueryInterface(Ci.nsISearchEngine)
72  switch (aVerb) {
73  case "engine-added":
74  gEngineView._engineStore.addEngine(aEngine);
75  gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
76  break;
77  case "engine-changed":
78  gEngineView._engineStore.reloadIcons();
79  break;
80  case "engine-removed":
81  case "engine-current":
82  // Not relevant
83  return;
84  }
85  gEngineView.invalidate();
86  }
87  },
88 
89  onOK: function engineManager_onOK() {
90  // Remove the observer
91  var os = Cc["@mozilla.org/observer-service;1"].
92  getService(Ci.nsIObserverService);
93  os.removeObserver(this, "browser-search-engine-modified");
94 
95  // Set the preference
96  var newSuggestEnabled = document.getElementById("enableSuggest").checked;
97  var prefService = Cc["@mozilla.org/preferences-service;1"].
98  getService(Ci.nsIPrefBranch);
99  prefService.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled);
100 
101  // Commit the changes
102  gEngineView._engineStore.commit();
103  },
104 
105  onCancel: function engineManager_onCancel() {
106  // Remove the observer
107  var os = Cc["@mozilla.org/observer-service;1"].
108  getService(Ci.nsIObserverService);
109  os.removeObserver(this, "browser-search-engine-modified");
110  },
111 
112  onRestoreDefaults: function engineManager_onRestoreDefaults() {
113  var num = gEngineView._engineStore.restoreDefaultEngines();
114  gEngineView.rowCountChanged(0, num);
115  gEngineView.invalidate();
116  },
117 
118  showRestoreDefaults: function engineManager_showRestoreDefaults(val) {
119  document.documentElement.getButton("extra2").disabled = !val;
120  },
121 
122  loadAddEngines: function engineManager_loadAddEngines() {
123  this.onOK();
124  window.opener.BrowserSearch.loadAddEngines();
125  window.close();
126  },
127 
128  remove: function engineManager_remove() {
129  gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
130  var index = gEngineView.selectedIndex;
131  gEngineView.rowCountChanged(index, -1);
132  gEngineView.invalidate();
133  gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
134  gEngineView.ensureRowIsVisible(Math.min(index, gEngineView.lastIndex));
135  document.getElementById("engineList").focus();
136  },
137 
143  bump: function engineManager_move(aDir) {
144  var selectedEngine = gEngineView.selectedEngine;
145  var newIndex = gEngineView.selectedIndex - aDir;
146 
147  gEngineView._engineStore.moveEngine(selectedEngine, newIndex);
148 
149  gEngineView.invalidate();
150  gEngineView.selection.select(newIndex);
151  gEngineView.ensureRowIsVisible(newIndex);
152  this.showRestoreDefaults(true);
153  document.getElementById("engineList").focus();
154  },
155 
156  editKeyword: function engineManager_editKeyword() {
157  var selectedEngine = gEngineView.selectedEngine;
158  if (!selectedEngine)
159  return;
160 
161  var prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"].
162  getService(Ci.nsIPromptService);
163  var alias = { value: selectedEngine.alias };
164  var strings = document.getElementById("engineManagerBundle");
165  var title = strings.getString("editTitle");
166  var msg = strings.getFormattedString("editMsg", [selectedEngine.name]);
167 
168  while (prompt.prompt(window, title, msg, alias, null, { })) {
169  var bduplicate = false;
170  var eduplicate = false;
171 
172  if (alias.value != "") {
173  try {
174  let bmserv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
175  getService(Ci.nsINavBookmarksService);
176  if (bmserv.getURIForKeyword(alias.value))
177  bduplicate = true;
178  } catch(ex) {}
179 
180  // Check for duplicates in changes we haven't committed yet
181  let engines = gEngineView._engineStore.engines;
182  for each (let engine in engines) {
183  if (engine.alias == alias.value &&
184  engine.name != selectedEngine.name) {
185  eduplicate = true;
186  break;
187  }
188  }
189  }
190 
191  // Notify the user if they have chosen an existing engine/bookmark keyword
192  if (eduplicate || bduplicate) {
193  var dtitle = strings.getString("duplicateTitle");
194  var bmsg = strings.getString("duplicateBookmarkMsg");
195  var emsg = strings.getFormattedString("duplicateEngineMsg",
196  [engine.name]);
197 
198  prompt.alert(window, dtitle, (eduplicate) ? emsg : bmsg);
199  } else {
200  gEngineView._engineStore.changeEngine(selectedEngine, "alias",
201  alias.value);
202  gEngineView.invalidate();
203  break;
204  }
205  }
206  },
207 
208  onSelect: function engineManager_onSelect() {
209  // buttons only work if an engine is selected and it's not the last engine
210  var disableButtons = (gEngineView.selectedIndex == -1) ||
211  (gEngineView.lastIndex == 0);
212  var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex);
213  var firstSelected = (gEngineView.selectedIndex == 0);
214  var noSelection = (gEngineView.selectedIndex == -1);
215 
216  // Songbird: do not allow removal of the songbird internal search engine
217  var isSongbirdEngine = (gEngineView.selectedEngine.alias == "songbird-internal-search");
218 
219  document.getElementById("cmd_remove").setAttribute("disabled",
220  disableButtons || isSongbirdEngine);
221 
222  document.getElementById("cmd_moveup").setAttribute("disabled",
223  disableButtons || firstSelected);
224 
225  document.getElementById("cmd_movedown").setAttribute("disabled",
226  disableButtons || lastSelected);
227  document.getElementById("cmd_editkeyword").setAttribute("disabled",
228  noSelection);
229  }
230 };
231 
233  var selectedIndex = gEngineView.selectedIndex;
234  if (selectedIndex > 0) {
235  event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
236  event.dataTransfer.effectAllowed = "move";
237  }
238 }
239 
240 // "Operation" objects
241 function EngineMoveOp(aEngineClone, aNewIndex) {
242  if (!aEngineClone)
243  throw new Error("bad args to new EngineMoveOp!");
244  this._engine = aEngineClone.originalEngine;
245  this._newIndex = aNewIndex;
246 }
247 EngineMoveOp.prototype = {
248  _engine: null,
249  _newIndex: null,
250  commit: function EMO_commit() {
251  var searchService = Cc["@mozilla.org/browser/search-service;1"].
252  getService(Ci.nsIBrowserSearchService);
253  searchService.moveEngine(this._engine, this._newIndex);
254  }
255 }
256 
257 function EngineRemoveOp(aEngineClone) {
258  if (!aEngineClone)
259  throw new Error("bad args to new EngineRemoveOp!");
260  this._engine = aEngineClone.originalEngine;
261 }
262 EngineRemoveOp.prototype = {
263  _engine: null,
264  commit: function ERO_commit() {
265  var searchService = Cc["@mozilla.org/browser/search-service;1"].
266  getService(Ci.nsIBrowserSearchService);
267  searchService.removeEngine(this._engine);
268  }
269 }
270 
271 function EngineUnhideOp(aEngineClone, aNewIndex) {
272  if (!aEngineClone)
273  throw new Error("bad args to new EngineUnhideOp!");
274  this._engine = aEngineClone.originalEngine;
275  this._newIndex = aNewIndex;
276 }
277 EngineUnhideOp.prototype = {
278  _engine: null,
279  _newIndex: null,
280  commit: function EUO_commit() {
281  this._engine.hidden = false;
282  var searchService = Cc["@mozilla.org/browser/search-service;1"].
283  getService(Ci.nsIBrowserSearchService);
284  searchService.moveEngine(this._engine, this._newIndex);
285  }
286 }
287 
288 function EngineChangeOp(aEngineClone, aProp, aValue) {
289  if (!aEngineClone)
290  throw new Error("bad args to new EngineChangeOp!");
291 
292  this._engine = aEngineClone.originalEngine;
293  this._prop = aProp;
294  this._newValue = aValue;
295 }
296 EngineChangeOp.prototype = {
297  _engine: null,
298  _prop: null,
299  _newValue: null,
300  commit: function ECO_commit() {
301  this._engine[this._prop] = this._newValue;
302  }
303 }
304 
305 function EngineStore() {
306  var searchService = Cc["@mozilla.org/browser/search-service;1"].
307  getService(Ci.nsIBrowserSearchService);
308  this._engines = searchService.getVisibleEngines({}).map(this._cloneEngine);
309  this._defaultEngines = searchService.getDefaultEngines({}).map(this._cloneEngine);
310 
311  this._ops = [];
312 
313  // check if we need to disable the restore defaults button
314  var someHidden = this._defaultEngines.some(function (e) {return e.hidden;});
315  gEngineManagerDialog.showRestoreDefaults(someHidden);
316 }
317 EngineStore.prototype = {
318  _engines: null,
319  _defaultEngines: null,
320  _ops: null,
321 
322  get engines() {
323  return this._engines;
324  },
325  set engines(val) {
326  this._engines = val;
327  return val;
328  },
329 
330  _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
331  return this._engines.indexOf(aEngine);
332  },
333 
334  _getEngineByName: function ES_getEngineByName(aName) {
335  for each (var engine in this._engines)
336  if (engine.name == aName)
337  return engine;
338 
339  return null;
340  },
341 
342  _cloneEngine: function ES_cloneObj(aEngine) {
343  var newO=[];
344  for (var i in aEngine)
345  newO[i] = aEngine[i];
346  newO.originalEngine = aEngine;
347  return newO;
348  },
349 
350  // Callback for Array's some(). A thisObj must be passed to some()
351  _isSameEngine: function ES_isSameEngine(aEngineClone) {
352  return aEngineClone.originalEngine == this.originalEngine;
353  },
354 
355  commit: function ES_commit() {
356  var searchService = Cc["@mozilla.org/browser/search-service;1"].
357  getService(Ci.nsIBrowserSearchService);
358  var currentEngine = this._cloneEngine(searchService.currentEngine);
359  for (var i = 0; i < this._ops.length; i++)
360  this._ops[i].commit();
361 
362  // Restore currentEngine if it is a default engine that is still visible.
363  // Needed if the user deletes currentEngine and then restores it.
364  if (this._defaultEngines.some(this._isSameEngine, currentEngine) &&
365  !currentEngine.originalEngine.hidden)
366  searchService.currentEngine = currentEngine.originalEngine;
367  },
368 
369  addEngine: function ES_addEngine(aEngine) {
370  this._engines.push(this._cloneEngine(aEngine));
371  },
372 
373  moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
374  if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
375  throw new Error("ES_moveEngine: invalid aNewIndex!");
376  var index = this._getIndexForEngine(aEngine);
377  if (index == -1)
378  throw new Error("ES_moveEngine: invalid engine?");
379 
380  if (index == aNewIndex)
381  return; // nothing to do
382 
383  // Move the engine in our internal store
384  var removedEngine = this._engines.splice(index, 1)[0];
385  this._engines.splice(aNewIndex, 0, removedEngine);
386 
387  this._ops.push(new EngineMoveOp(aEngine, aNewIndex));
388  },
389 
390  removeEngine: function ES_removeEngine(aEngine) {
391  var index = this._getIndexForEngine(aEngine);
392  if (index == -1)
393  throw new Error("invalid engine?");
394 
395  this._engines.splice(index, 1);
396  this._ops.push(new EngineRemoveOp(aEngine));
397  if (this._defaultEngines.some(this._isSameEngine, aEngine))
398  gEngineManagerDialog.showRestoreDefaults(true);
399  },
400 
401  restoreDefaultEngines: function ES_restoreDefaultEngines() {
402  var added = 0;
403 
404  for (var i = 0; i < this._defaultEngines.length; ++i) {
405  var e = this._defaultEngines[i];
406 
407  // If the engine is already in the list, just move it.
408  if (this._engines.some(this._isSameEngine, e)) {
409  this.moveEngine(this._getEngineByName(e.name), i);
410  } else {
411  // Otherwise, add it back to our internal store
412  this._engines.splice(i, 0, e);
413  this._ops.push(new EngineUnhideOp(e, i));
414  added++;
415  }
416  }
417  gEngineManagerDialog.showRestoreDefaults(false);
418  return added;
419  },
420 
421  changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
422  var index = this._getIndexForEngine(aEngine);
423  if (index == -1)
424  throw new Error("invalid engine?");
425 
426  this._engines[index][aProp] = aNewValue;
427  this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue));
428  },
429 
430  reloadIcons: function ES_reloadIcons() {
431  this._engines.forEach(function (e) {
432  e.uri = e.originalEngine.uri;
433  });
434  }
435 }
436 
437 function EngineView(aEngineStore) {
438  this._engineStore = aEngineStore;
439 }
440 EngineView.prototype = {
441  _engineStore: null,
442  tree: null,
443 
444  get lastIndex() {
445  return this.rowCount - 1;
446  },
447  get selectedIndex() {
448  var seln = this.selection;
449  if (seln.getRangeCount() > 0) {
450  var min = { };
451  seln.getRangeAt(0, min, { });
452  return min.value;
453  }
454  return -1;
455  },
456  get selectedEngine() {
457  return this._engineStore.engines[this.selectedIndex];
458  },
459 
460  // Helpers
461  rowCountChanged: function (index, count) {
462  this.tree.rowCountChanged(index, count);
463  },
464 
465  invalidate: function () {
466  this.tree.invalidate();
467  },
468 
469  ensureRowIsVisible: function (index) {
470  this.tree.ensureRowIsVisible(index);
471  },
472 
473  getSourceIndexFromDrag: function () {
474  var dragService = Cc["@mozilla.org/widget/dragservice;1"].
475  getService().QueryInterface(Ci.nsIDragService);
476  var dragSession = dragService.getCurrentSession();
477  var transfer = Cc["@mozilla.org/widget/transferable;1"].
478  createInstance(Ci.nsITransferable);
479 
480  transfer.addDataFlavor(ENGINE_FLAVOR);
481  dragSession.getData(transfer, 0);
482 
483  var dataObj = {};
484  var len = {};
485  var sourceIndex = -1;
486  try {
487  transfer.getAnyTransferData({}, dataObj, len);
488  } catch (ex) {}
489 
490  if (dataObj.value) {
491  sourceIndex = dataObj.value.QueryInterface(Ci.nsISupportsString).data;
492  sourceIndex = parseInt(sourceIndex.substring(0, len.value));
493  }
494 
495  return sourceIndex;
496  },
497 
498  // nsITreeView
499  get rowCount() {
500  return this._engineStore.engines.length;
501  },
502 
503  getImageSrc: function(index, column) {
504  if (column.id == "engineName" && this._engineStore.engines[index].iconURI)
505  return this._engineStore.engines[index].iconURI.spec;
506  return "";
507  },
508 
509  getCellText: function(index, column) {
510  if (column.id == "engineName")
511  return this._engineStore.engines[index].name;
512  else if (column.id == "engineKeyword")
513  return this._engineStore.engines[index].alias;
514  return "";
515  },
516 
517  setTree: function(tree) {
518  this.tree = tree;
519  },
520 
521  canDrop: function(targetIndex, orientation) {
522  var sourceIndex = this.getSourceIndexFromDrag();
523  return (sourceIndex != -1 &&
524  sourceIndex != targetIndex &&
525  sourceIndex != (targetIndex + orientation));
526  },
527 
528  drop: function(dropIndex, orientation) {
529  var sourceIndex = this.getSourceIndexFromDrag();
530  var sourceEngine = this._engineStore.engines[sourceIndex];
531 
532  if (dropIndex > sourceIndex) {
533  if (orientation == Ci.nsITreeView.DROP_BEFORE)
534  dropIndex--;
535  } else {
536  if (orientation == Ci.nsITreeView.DROP_AFTER)
537  dropIndex++;
538  }
539 
540  this._engineStore.moveEngine(sourceEngine, dropIndex);
541  gEngineManagerDialog.showRestoreDefaults(true);
542 
543  // Redraw, and adjust selection
544  this.invalidate();
545  this.selection.clearSelection();
546  this.selection.select(dropIndex);
547  },
548 
549  selection: null,
550  getRowProperties: function(index, properties) { },
551  getCellProperties: function(index, column, properties) { },
552  getColumnProperties: function(column, properties) { },
553  isContainer: function(index) { return false; },
554  isContainerOpen: function(index) { return false; },
555  isContainerEmpty: function(index) { return false; },
556  isSeparator: function(index) { return false; },
557  isSorted: function(index) { return false; },
558  getParentIndex: function(index) { return -1; },
559  hasNextSibling: function(parentIndex, index) { return false; },
560  getLevel: function(index) { return 0; },
561  getProgressMode: function(index, column) { },
562  getCellValue: function(index, column) { },
563  toggleOpenState: function(index) { },
564  cycleHeader: function(column) { },
565  selectionChanged: function() { },
566  cycleCell: function(row, column) { },
567  isEditable: function(index, column) { return false; },
568  isSelectable: function(index, column) { return false; },
569  setCellValue: function(index, column, value) { },
570  setCellText: function(index, column, value) { },
571  performAction: function(action) { },
572  performActionOnRow: function(action, index) { },
573  performActionOnCell: function(action, index, column) { }
574 };
function EngineMoveOp(aEngineClone, aNewIndex)
onPageChanged aValue
Definition: FeedWriter.js:1395
var gEngineView
var gEngineManagerDialog
var event
sidebarFactory createInstance
Definition: nsSidebar.js:351
getService(Ci.sbIFaceplateManager)
function onDragEngineStart(event)
let window
gImageView getCellProperties
Definition: pageInfo.js:171
var strings
Definition: Info.js:46
_window init
Definition: FeedWriter.js:1144
inst settings prompt
Native alias
var count
Definition: test_bug7406.js:32
function num(elem, prop)
this _dialogInput val(dateText)
const BROWSER_SUGGEST_PREF
function onCancel()
Definition: safeMode.js:129
return null
Definition: FeedWriter.js:1143
var os
_updateCookies aName
function onOK()
Definition: safeMode.js:111
countRef value
Definition: FeedWriter.js:1423
const Cc
function msg
_dialogDatepicker onSelect
#define min(a, b)
const Ci
const ENGINE_FLAVOR
_getSelectedPageStyle s i
sbDeviceFirmwareAutoCheckForUpdate prototype observe