1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/search/content/engineManager.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,491 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.9 + 1.10 +const Ci = Components.interfaces; 1.11 +const Cc = Components.classes; 1.12 + 1.13 +const ENGINE_FLAVOR = "text/x-moz-search-engine"; 1.14 + 1.15 +const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled"; 1.16 + 1.17 +var gEngineView = null; 1.18 + 1.19 +var gEngineManagerDialog = { 1.20 + init: function engineManager_init() { 1.21 + gEngineView = new EngineView(new EngineStore()); 1.22 + 1.23 + var suggestEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); 1.24 + document.getElementById("enableSuggest").checked = suggestEnabled; 1.25 + 1.26 + var tree = document.getElementById("engineList"); 1.27 + tree.view = gEngineView; 1.28 + 1.29 + Services.obs.addObserver(this, "browser-search-engine-modified", false); 1.30 + }, 1.31 + 1.32 + destroy: function engineManager_destroy() { 1.33 + // Remove the observer 1.34 + Services.obs.removeObserver(this, "browser-search-engine-modified"); 1.35 + }, 1.36 + 1.37 + observe: function engineManager_observe(aEngine, aTopic, aVerb) { 1.38 + if (aTopic == "browser-search-engine-modified") { 1.39 + aEngine.QueryInterface(Ci.nsISearchEngine); 1.40 + switch (aVerb) { 1.41 + case "engine-added": 1.42 + gEngineView._engineStore.addEngine(aEngine); 1.43 + gEngineView.rowCountChanged(gEngineView.lastIndex, 1); 1.44 + break; 1.45 + case "engine-changed": 1.46 + gEngineView._engineStore.reloadIcons(); 1.47 + gEngineView.invalidate(); 1.48 + break; 1.49 + case "engine-removed": 1.50 + case "engine-current": 1.51 + case "engine-default": 1.52 + // Not relevant 1.53 + break; 1.54 + } 1.55 + } 1.56 + }, 1.57 + 1.58 + onOK: function engineManager_onOK() { 1.59 + // Set the preference 1.60 + var newSuggestEnabled = document.getElementById("enableSuggest").checked; 1.61 + Services.prefs.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled); 1.62 + 1.63 + // Commit the changes 1.64 + gEngineView._engineStore.commit(); 1.65 + }, 1.66 + 1.67 + onRestoreDefaults: function engineManager_onRestoreDefaults() { 1.68 + var num = gEngineView._engineStore.restoreDefaultEngines(); 1.69 + gEngineView.rowCountChanged(0, num); 1.70 + gEngineView.invalidate(); 1.71 + }, 1.72 + 1.73 + showRestoreDefaults: function engineManager_showRestoreDefaults(val) { 1.74 + document.documentElement.getButton("extra2").disabled = !val; 1.75 + }, 1.76 + 1.77 + loadAddEngines: function engineManager_loadAddEngines() { 1.78 + this.onOK(); 1.79 + window.opener.BrowserSearch.loadAddEngines(); 1.80 + window.close(); 1.81 + }, 1.82 + 1.83 + remove: function engineManager_remove() { 1.84 + gEngineView._engineStore.removeEngine(gEngineView.selectedEngine); 1.85 + var index = gEngineView.selectedIndex; 1.86 + gEngineView.rowCountChanged(index, -1); 1.87 + gEngineView.invalidate(); 1.88 + gEngineView.selection.select(Math.min(index, gEngineView.lastIndex)); 1.89 + gEngineView.ensureRowIsVisible(gEngineView.currentIndex); 1.90 + document.getElementById("engineList").focus(); 1.91 + }, 1.92 + 1.93 + /** 1.94 + * Moves the selected engine either up or down in the engine list 1.95 + * @param aDir 1.96 + * -1 to move the selected engine down, +1 to move it up. 1.97 + */ 1.98 + bump: function engineManager_move(aDir) { 1.99 + var selectedEngine = gEngineView.selectedEngine; 1.100 + var newIndex = gEngineView.selectedIndex - aDir; 1.101 + 1.102 + gEngineView._engineStore.moveEngine(selectedEngine, newIndex); 1.103 + 1.104 + gEngineView.invalidate(); 1.105 + gEngineView.selection.select(newIndex); 1.106 + gEngineView.ensureRowIsVisible(newIndex); 1.107 + this.showRestoreDefaults(true); 1.108 + document.getElementById("engineList").focus(); 1.109 + }, 1.110 + 1.111 + editKeyword: function engineManager_editKeyword() { 1.112 + var selectedEngine = gEngineView.selectedEngine; 1.113 + if (!selectedEngine) 1.114 + return; 1.115 + 1.116 + var alias = { value: selectedEngine.alias }; 1.117 + var strings = document.getElementById("engineManagerBundle"); 1.118 + var title = strings.getString("editTitle"); 1.119 + var msg = strings.getFormattedString("editMsg", [selectedEngine.name]); 1.120 + 1.121 + while (Services.prompt.prompt(window, title, msg, alias, null, {})) { 1.122 + var bduplicate = false; 1.123 + var eduplicate = false; 1.124 + var dupName = ""; 1.125 + 1.126 + if (alias.value != "") { 1.127 + try { 1.128 + let bmserv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. 1.129 + getService(Ci.nsINavBookmarksService); 1.130 + if (bmserv.getURIForKeyword(alias.value)) 1.131 + bduplicate = true; 1.132 + } catch(ex) {} 1.133 + 1.134 + // Check for duplicates in changes we haven't committed yet 1.135 + let engines = gEngineView._engineStore.engines; 1.136 + for each (let engine in engines) { 1.137 + if (engine.alias == alias.value && 1.138 + engine.name != selectedEngine.name) { 1.139 + eduplicate = true; 1.140 + dupName = engine.name; 1.141 + break; 1.142 + } 1.143 + } 1.144 + } 1.145 + 1.146 + // Notify the user if they have chosen an existing engine/bookmark keyword 1.147 + if (eduplicate || bduplicate) { 1.148 + var dtitle = strings.getString("duplicateTitle"); 1.149 + var bmsg = strings.getString("duplicateBookmarkMsg"); 1.150 + var emsg = strings.getFormattedString("duplicateEngineMsg", [dupName]); 1.151 + 1.152 + Services.prompt.alert(window, dtitle, eduplicate ? emsg : bmsg); 1.153 + } else { 1.154 + gEngineView._engineStore.changeEngine(selectedEngine, "alias", 1.155 + alias.value); 1.156 + gEngineView.invalidate(); 1.157 + break; 1.158 + } 1.159 + } 1.160 + }, 1.161 + 1.162 + onSelect: function engineManager_onSelect() { 1.163 + // Buttons only work if an engine is selected and it's not the last engine, 1.164 + // the latter is true when the selected is first and last at the same time. 1.165 + var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex); 1.166 + var firstSelected = (gEngineView.selectedIndex == 0); 1.167 + var noSelection = (gEngineView.selectedIndex == -1); 1.168 + 1.169 + document.getElementById("cmd_remove") 1.170 + .setAttribute("disabled", noSelection || 1.171 + (firstSelected && lastSelected)); 1.172 + 1.173 + document.getElementById("cmd_moveup") 1.174 + .setAttribute("disabled", noSelection || firstSelected); 1.175 + 1.176 + document.getElementById("cmd_movedown") 1.177 + .setAttribute("disabled", noSelection || lastSelected); 1.178 + 1.179 + document.getElementById("cmd_editkeyword") 1.180 + .setAttribute("disabled", noSelection); 1.181 + } 1.182 +}; 1.183 + 1.184 +function onDragEngineStart(event) { 1.185 + var selectedIndex = gEngineView.selectedIndex; 1.186 + if (selectedIndex >= 0) { 1.187 + event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString()); 1.188 + event.dataTransfer.effectAllowed = "move"; 1.189 + } 1.190 +} 1.191 + 1.192 +// "Operation" objects 1.193 +function EngineMoveOp(aEngineClone, aNewIndex) { 1.194 + if (!aEngineClone) 1.195 + throw new Error("bad args to new EngineMoveOp!"); 1.196 + this._engine = aEngineClone.originalEngine; 1.197 + this._newIndex = aNewIndex; 1.198 +} 1.199 +EngineMoveOp.prototype = { 1.200 + _engine: null, 1.201 + _newIndex: null, 1.202 + commit: function EMO_commit() { 1.203 + Services.search.moveEngine(this._engine, this._newIndex); 1.204 + } 1.205 +} 1.206 + 1.207 +function EngineRemoveOp(aEngineClone) { 1.208 + if (!aEngineClone) 1.209 + throw new Error("bad args to new EngineRemoveOp!"); 1.210 + this._engine = aEngineClone.originalEngine; 1.211 +} 1.212 +EngineRemoveOp.prototype = { 1.213 + _engine: null, 1.214 + commit: function ERO_commit() { 1.215 + Services.search.removeEngine(this._engine); 1.216 + } 1.217 +} 1.218 + 1.219 +function EngineUnhideOp(aEngineClone, aNewIndex) { 1.220 + if (!aEngineClone) 1.221 + throw new Error("bad args to new EngineUnhideOp!"); 1.222 + this._engine = aEngineClone.originalEngine; 1.223 + this._newIndex = aNewIndex; 1.224 +} 1.225 +EngineUnhideOp.prototype = { 1.226 + _engine: null, 1.227 + _newIndex: null, 1.228 + commit: function EUO_commit() { 1.229 + this._engine.hidden = false; 1.230 + Services.search.moveEngine(this._engine, this._newIndex); 1.231 + } 1.232 +} 1.233 + 1.234 +function EngineChangeOp(aEngineClone, aProp, aValue) { 1.235 + if (!aEngineClone) 1.236 + throw new Error("bad args to new EngineChangeOp!"); 1.237 + 1.238 + this._engine = aEngineClone.originalEngine; 1.239 + this._prop = aProp; 1.240 + this._newValue = aValue; 1.241 +} 1.242 +EngineChangeOp.prototype = { 1.243 + _engine: null, 1.244 + _prop: null, 1.245 + _newValue: null, 1.246 + commit: function ECO_commit() { 1.247 + this._engine[this._prop] = this._newValue; 1.248 + } 1.249 +} 1.250 + 1.251 +function EngineStore() { 1.252 + this._engines = Services.search.getVisibleEngines().map(this._cloneEngine); 1.253 + this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine); 1.254 + 1.255 + this._ops = []; 1.256 + 1.257 + // check if we need to disable the restore defaults button 1.258 + var someHidden = this._defaultEngines.some(function (e) e.hidden); 1.259 + gEngineManagerDialog.showRestoreDefaults(someHidden); 1.260 +} 1.261 +EngineStore.prototype = { 1.262 + _engines: null, 1.263 + _defaultEngines: null, 1.264 + _ops: null, 1.265 + 1.266 + get engines() { 1.267 + return this._engines; 1.268 + }, 1.269 + set engines(val) { 1.270 + this._engines = val; 1.271 + return val; 1.272 + }, 1.273 + 1.274 + _getIndexForEngine: function ES_getIndexForEngine(aEngine) { 1.275 + return this._engines.indexOf(aEngine); 1.276 + }, 1.277 + 1.278 + _getEngineByName: function ES_getEngineByName(aName) { 1.279 + for each (var engine in this._engines) 1.280 + if (engine.name == aName) 1.281 + return engine; 1.282 + 1.283 + return null; 1.284 + }, 1.285 + 1.286 + _cloneEngine: function ES_cloneEngine(aEngine) { 1.287 + var clonedObj={}; 1.288 + for (var i in aEngine) 1.289 + clonedObj[i] = aEngine[i]; 1.290 + clonedObj.originalEngine = aEngine; 1.291 + return clonedObj; 1.292 + }, 1.293 + 1.294 + // Callback for Array's some(). A thisObj must be passed to some() 1.295 + _isSameEngine: function ES_isSameEngine(aEngineClone) { 1.296 + return aEngineClone.originalEngine == this.originalEngine; 1.297 + }, 1.298 + 1.299 + commit: function ES_commit() { 1.300 + var currentEngine = this._cloneEngine(Services.search.currentEngine); 1.301 + for (var i = 0; i < this._ops.length; i++) 1.302 + this._ops[i].commit(); 1.303 + 1.304 + // Restore currentEngine if it is a default engine that is still visible. 1.305 + // Needed if the user deletes currentEngine and then restores it. 1.306 + if (this._defaultEngines.some(this._isSameEngine, currentEngine) && 1.307 + !currentEngine.originalEngine.hidden) 1.308 + Services.search.currentEngine = currentEngine.originalEngine; 1.309 + }, 1.310 + 1.311 + addEngine: function ES_addEngine(aEngine) { 1.312 + this._engines.push(this._cloneEngine(aEngine)); 1.313 + }, 1.314 + 1.315 + moveEngine: function ES_moveEngine(aEngine, aNewIndex) { 1.316 + if (aNewIndex < 0 || aNewIndex > this._engines.length - 1) 1.317 + throw new Error("ES_moveEngine: invalid aNewIndex!"); 1.318 + var index = this._getIndexForEngine(aEngine); 1.319 + if (index == -1) 1.320 + throw new Error("ES_moveEngine: invalid engine?"); 1.321 + 1.322 + if (index == aNewIndex) 1.323 + return; // nothing to do 1.324 + 1.325 + // Move the engine in our internal store 1.326 + var removedEngine = this._engines.splice(index, 1)[0]; 1.327 + this._engines.splice(aNewIndex, 0, removedEngine); 1.328 + 1.329 + this._ops.push(new EngineMoveOp(aEngine, aNewIndex)); 1.330 + }, 1.331 + 1.332 + removeEngine: function ES_removeEngine(aEngine) { 1.333 + var index = this._getIndexForEngine(aEngine); 1.334 + if (index == -1) 1.335 + throw new Error("invalid engine?"); 1.336 + 1.337 + this._engines.splice(index, 1); 1.338 + this._ops.push(new EngineRemoveOp(aEngine)); 1.339 + if (this._defaultEngines.some(this._isSameEngine, aEngine)) 1.340 + gEngineManagerDialog.showRestoreDefaults(true); 1.341 + }, 1.342 + 1.343 + restoreDefaultEngines: function ES_restoreDefaultEngines() { 1.344 + var added = 0; 1.345 + 1.346 + for (var i = 0; i < this._defaultEngines.length; ++i) { 1.347 + var e = this._defaultEngines[i]; 1.348 + 1.349 + // If the engine is already in the list, just move it. 1.350 + if (this._engines.some(this._isSameEngine, e)) { 1.351 + this.moveEngine(this._getEngineByName(e.name), i); 1.352 + } else { 1.353 + // Otherwise, add it back to our internal store 1.354 + this._engines.splice(i, 0, e); 1.355 + this._ops.push(new EngineUnhideOp(e, i)); 1.356 + added++; 1.357 + } 1.358 + } 1.359 + gEngineManagerDialog.showRestoreDefaults(false); 1.360 + return added; 1.361 + }, 1.362 + 1.363 + changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) { 1.364 + var index = this._getIndexForEngine(aEngine); 1.365 + if (index == -1) 1.366 + throw new Error("invalid engine?"); 1.367 + 1.368 + this._engines[index][aProp] = aNewValue; 1.369 + this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue)); 1.370 + }, 1.371 + 1.372 + reloadIcons: function ES_reloadIcons() { 1.373 + this._engines.forEach(function (e) { 1.374 + e.uri = e.originalEngine.uri; 1.375 + }); 1.376 + } 1.377 +} 1.378 + 1.379 +function EngineView(aEngineStore) { 1.380 + this._engineStore = aEngineStore; 1.381 +} 1.382 +EngineView.prototype = { 1.383 + _engineStore: null, 1.384 + tree: null, 1.385 + 1.386 + get lastIndex() { 1.387 + return this.rowCount - 1; 1.388 + }, 1.389 + get selectedIndex() { 1.390 + var seln = this.selection; 1.391 + if (seln.getRangeCount() > 0) { 1.392 + var min = {}; 1.393 + seln.getRangeAt(0, min, {}); 1.394 + return min.value; 1.395 + } 1.396 + return -1; 1.397 + }, 1.398 + get selectedEngine() { 1.399 + return this._engineStore.engines[this.selectedIndex]; 1.400 + }, 1.401 + 1.402 + // Helpers 1.403 + rowCountChanged: function (index, count) { 1.404 + this.tree.rowCountChanged(index, count); 1.405 + }, 1.406 + 1.407 + invalidate: function () { 1.408 + this.tree.invalidate(); 1.409 + }, 1.410 + 1.411 + ensureRowIsVisible: function (index) { 1.412 + this.tree.ensureRowIsVisible(index); 1.413 + }, 1.414 + 1.415 + getSourceIndexFromDrag: function (dataTransfer) { 1.416 + return parseInt(dataTransfer.getData(ENGINE_FLAVOR)); 1.417 + }, 1.418 + 1.419 + // nsITreeView 1.420 + get rowCount() { 1.421 + return this._engineStore.engines.length; 1.422 + }, 1.423 + 1.424 + getImageSrc: function(index, column) { 1.425 + if (column.id == "engineName" && this._engineStore.engines[index].iconURI) 1.426 + return this._engineStore.engines[index].iconURI.spec; 1.427 + return ""; 1.428 + }, 1.429 + 1.430 + getCellText: function(index, column) { 1.431 + if (column.id == "engineName") 1.432 + return this._engineStore.engines[index].name; 1.433 + else if (column.id == "engineKeyword") 1.434 + return this._engineStore.engines[index].alias; 1.435 + return ""; 1.436 + }, 1.437 + 1.438 + setTree: function(tree) { 1.439 + this.tree = tree; 1.440 + }, 1.441 + 1.442 + canDrop: function(targetIndex, orientation, dataTransfer) { 1.443 + var sourceIndex = this.getSourceIndexFromDrag(dataTransfer); 1.444 + return (sourceIndex != -1 && 1.445 + sourceIndex != targetIndex && 1.446 + sourceIndex != targetIndex + orientation); 1.447 + }, 1.448 + 1.449 + drop: function(dropIndex, orientation, dataTransfer) { 1.450 + var sourceIndex = this.getSourceIndexFromDrag(dataTransfer); 1.451 + var sourceEngine = this._engineStore.engines[sourceIndex]; 1.452 + 1.453 + if (dropIndex > sourceIndex) { 1.454 + if (orientation == Ci.nsITreeView.DROP_BEFORE) 1.455 + dropIndex--; 1.456 + } else { 1.457 + if (orientation == Ci.nsITreeView.DROP_AFTER) 1.458 + dropIndex++; 1.459 + } 1.460 + 1.461 + this._engineStore.moveEngine(sourceEngine, dropIndex); 1.462 + gEngineManagerDialog.showRestoreDefaults(true); 1.463 + 1.464 + // Redraw, and adjust selection 1.465 + this.invalidate(); 1.466 + this.selection.select(dropIndex); 1.467 + }, 1.468 + 1.469 + selection: null, 1.470 + getRowProperties: function(index) { return ""; }, 1.471 + getCellProperties: function(index, column) { return ""; }, 1.472 + getColumnProperties: function(column) { return ""; }, 1.473 + isContainer: function(index) { return false; }, 1.474 + isContainerOpen: function(index) { return false; }, 1.475 + isContainerEmpty: function(index) { return false; }, 1.476 + isSeparator: function(index) { return false; }, 1.477 + isSorted: function(index) { return false; }, 1.478 + getParentIndex: function(index) { return -1; }, 1.479 + hasNextSibling: function(parentIndex, index) { return false; }, 1.480 + getLevel: function(index) { return 0; }, 1.481 + getProgressMode: function(index, column) { }, 1.482 + getCellValue: function(index, column) { }, 1.483 + toggleOpenState: function(index) { }, 1.484 + cycleHeader: function(column) { }, 1.485 + selectionChanged: function() { }, 1.486 + cycleCell: function(row, column) { }, 1.487 + isEditable: function(index, column) { return false; }, 1.488 + isSelectable: function(index, column) { return false; }, 1.489 + setCellValue: function(index, column, value) { }, 1.490 + setCellText: function(index, column, value) { }, 1.491 + performAction: function(action) { }, 1.492 + performActionOnRow: function(action, index) { }, 1.493 + performActionOnCell: function(action, index, column) { } 1.494 +};