Bug 514510: search engines should appear in the addons manager, r=gavin
authorMark Finkle <mark.finkle@gmail.com>
Tue, 22 Sep 2009 22:19:24 -0400
changeset 65597 a63248f4e3ea6d288276da56dbffd39e5ea8dd8b
parent 65596 0e4e87a4ebadc4894b2a8c2aa7e5618e1169781d
child 65598 97d6977708b305b33caa6469ae2f50d86a50874d
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs514510
Bug 514510: search engines should appear in the addons manager, r=gavin
mobile/chrome/content/bindings/extensions.xml
mobile/chrome/content/browser.css
mobile/chrome/content/browser.js
mobile/chrome/content/extensions.js
mobile/locales/en-US/chrome/browser.properties
--- a/mobile/chrome/content/bindings/extensions.xml
+++ b/mobile/chrome/content/bindings/extensions.xml
@@ -105,16 +105,56 @@
             for (let i = 0; i < prefs.length; i++)
               box.appendChild(prefs.item(i));
           ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
+  <binding id="extension-searchplugin" extends="chrome://browser/content/bindings.xml#richlistitem">
+    <content orient="vertical">
+      <xul:hbox align="start">
+        <xul:image width="32" height="32" xbl:inherits="src=iconURL"/>
+        <xul:vbox flex="1">
+          <xul:hbox align="center">
+            <xul:label class="title" xbl:inherits="value=name"/>
+            <xul:spacer flex="1"/>
+            <xul:label class="normal" crop="end" xbl:inherits="value=typeLabel"/>            
+          </xul:hbox>
+          <xul:vbox>
+            <xul:label class="normal hide-on-select" xbl:inherits="value=description" crop="end" flex="1"/>
+            <xul:description class="normal show-on-select" xbl:inherits="xbl:text=description" flex="1"/>
+          </xul:vbox>
+        </xul:vbox>
+      </xul:hbox>
+      <xul:hbox class="show-on-select">
+        <xul:spacer flex="1"/>
+        <xul:button anonid="enable-button" class="show-on-disable hide-on-enable hide-on-uninstall addon-enable" label="&addonEnable.label;"
+                    oncommand="ExtensionsView.enable(document.getBindingParent(this));"/>
+        <xul:button class="show-on-enable hide-on-disable hide-on-uninstall addon-disable" label="&addonDisable.label;"
+                    oncommand="ExtensionsView.disable(document.getBindingParent(this));"/>
+        <xul:button anonid="uninstall-button" class="hide-on-uninstall addon-uninstall" label="&addonUninstall.label;"
+                    oncommand="ExtensionsView.uninstall(document.getBindingParent(this));"/>
+        <xul:button class="show-on-uninstall addon-cancel" label="&addonCancel.label;"
+                    oncommand="ExtensionsView.cancelUninstall(document.getBindingParent(this));"/>
+      </xul:hbox>
+    </content>
+    
+    <implementation>
+      <constructor>
+        <![CDATA[
+          let appManaged = this.getAttribute("appManaged");
+          if (appManaged == "true")
+            document.getAnonymousElementByAttribute(this, "anonid", "uninstall-button").setAttribute("disabled", "true");
+        ]]>
+      </constructor>
+    </implementation>
+  </binding>
+
   <binding id="extension-search" extends="chrome://browser/content/bindings.xml#richlistitem">
     <content orient="vertical">
       <xul:hbox align="start">
         <xul:image xbl:inherits="src=iconURL"/>
         <xul:vbox flex="1">
           <xul:hbox align="center">
             <xul:label class="title" xbl:inherits="value=name"/>
             <xul:label class="normal" xbl:inherits="value=version"/>
--- a/mobile/chrome/content/browser.css
+++ b/mobile/chrome/content/browser.css
@@ -101,16 +101,20 @@ menulist {
 richlistitem {
   -moz-binding: url("chrome://browser/content/bindings.xml#richlistitem");
 }
 
 richlistitem[typeName="local"] {
   -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-local");
 }
 
+richlistitem[typeName="searchplugin"] {
+  -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-searchplugin");
+}
+
 richlistitem[typeName="search"] {
   -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search");
 }
 
 richlistitem[typeName="search"] hbox.addon-type-or-rating {
   -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search-recommended");
 }
 
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -466,16 +466,19 @@ var Browser = {
     os.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
 #ifdef WINCE
     os.addObserver(SoftKeyboardObserver, "softkb-change", false);
 #endif
 
     // clear out tabs the user hasn't touched lately on memory crunch
     os.addObserver(MemoryObserver, "memory-pressure", false);
 
+    // search engine changes
+    os.addObserver(BrowserSearch, "browser-search-engine-modified", false);
+
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
 
     let browsers = document.getElementById("browsers");
     browsers.addEventListener("command", this._handleContentCommand, false);
     browsers.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver.onUpdatePageReport, false);
 
     /* Initialize Spatial Navigation */
     function panCallback(aElement) {
@@ -568,16 +571,17 @@ var Browser = {
 
     var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
     os.removeObserver(gXPInstallObserver, "xpinstall-install-blocked");
     os.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
     os.removeObserver(MemoryObserver, "memory-pressure");
 #ifdef WINCE
     os.removeObserver(SoftKeyboardObserver, "softkb-change");
 #endif
+    os.removeObserver(BrowserSearch, "browser-search-engine-modified");
 
     window.controllers.removeController(this);
     window.controllers.removeController(BrowserUI);
   },
 
   setPluginState: function(enabled)
   {
     var phs = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
@@ -1369,21 +1373,59 @@ nsBrowserAccess.prototype = {
     return Browser.browsers.some(function (browser) browser.contentWindow == aWindow);
   }
 };
 
 const BrowserSearch = {
   engines: null,
   _allEngines: [],
 
+  observe: function (aSubject, aTopic, aData) {
+    if (aTopic != "browser-search-engine-modified")
+      return;
+
+    switch (aData) {
+      case "engine-added":
+      case "engine-removed":
+	// force a rebuild of the prefs list, if needed
+	// XXX this is inefficient, shouldn't have to rebuild the entire list
+	if (ExtensionsView._list)
+	  ExtensionsView.getAddonsFromLocal();
+
+	// fall through
+      case "engine-changed":
+	// XXX we should probably also update the ExtensionsView list here once
+	// that's efficient, since the icon can change (happen during an async
+	// installs from the web)
+
+	// blow away our cache
+	this._engines = null;
+	break;
+      case "engine-current":
+	// Not relevant
+	break;
+    }
+  },
+
   get _currentEngines() {
     let doc = getBrowser().contentDocument;
     return this._allEngines.filter(function(element) element.doc === doc, this);
   },
 
+  get searchService() {
+    delete this.searchService;
+    return this.searchService = Cc["@mozilla.org/browser/search-service;1"].getService(Ci.nsIBrowserSearchService);
+  },
+
+  get engines() {
+    if (this._engines)
+      return this._engines;
+    return this._engines = this.searchService.getVisibleEngines({ });    
+  },
+
   addPageSearchEngine: function (aEngine, aDocument) {
     // Clean the engine referenced for document that didn't exist anymore
     let browsers = Browser.browsers;
     this._allEngines = this._allEngines.filter(function(element) {
        return browsers.some(function (browser) browser.contentDocument == element.doc);
     }, this);
 
     // Prevent duplicate
@@ -1422,36 +1464,32 @@ const BrowserSearch = {
 
       container.appendChild(button);
     }
 
     container.hidden = false;
   },
 
   addPermanentSearchEngine: function (aEngine) {
-    var searchService = Cc["@mozilla.org/browser/search-service;1"].getService(Ci.nsIBrowserSearchService);
     let iconURL = BrowserUI._favicon.src;
-    searchService.addEngine(aEngine.href, Ci.nsISearchEngine.DATA_XML, iconURL, false);
-
-    this.engines = null;
+    this.searchService.addEngine(aEngine.href, Ci.nsISearchEngine.DATA_XML, iconURL, false);
+
+    this._engines = null;
   },
 
   updateSearchButtons: function() {
-    if (this.engines)
+    if (this._engines)
       return;
 
-    var searchService = Cc["@mozilla.org/browser/search-service;1"].getService(Ci.nsIBrowserSearchService);
-    var engines = searchService.getVisibleEngines({ });
-    this.engines = engines;
-
     // Clean the previous search engines button
     var container = document.getElementById("search-buttons");
     while (container.hasChildNodes())
       container.removeChild(container.lastChild);
 
+    let engines = this.engines;
     for (var e = 0; e < engines.length; e++) {
       var button = document.createElement("radio");
       var engine = engines[e];
       button.id = engine.name;
       button.setAttribute("label", engine.name);
       button.className = "searchengine";
       if (engine.iconURI)
         button.setAttribute("src", engine.iconURI.spec);
--- a/mobile/chrome/content/extensions.js
+++ b/mobile/chrome/content/extensions.js
@@ -50,16 +50,17 @@ var ExtensionsView = {
   _ios: null,
   _strings: {},
   _repo: null,
   _list: null,
   _localItem: null,
   _repoItem: null,
   _msg: null,
   _dloadmgr: null,
+  _search: null,
   _restartCount: 0,
   _observerIndex: -1,
 
   _isXPInstallEnabled: function ev__isXPInstallEnabled() {
     let enabled = false;
     let locked = false;
     try {
       enabled = this._pref.getBoolPref("xpinstall.enabled");
@@ -241,16 +242,17 @@ var ExtensionsView = {
   },
 
   _delayedInit: function ev__delayedInit() {
     if (this._list)
       return;
 
     this._pref = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
     this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+    this._search = Cc["@mozilla.org/browser/search-service;1"].getService(Ci.nsIBrowserSearchService);
 
     let repository = "@mozilla.org/extensions/addon-repository;1";
     try {
       var repo = pref.getCharPref(PREF_GETADDONS_REPOSITORY);
       if (repo in Components.classes)
         repository = repo;
     } catch (e) { }
     this._repo = Cc[repository].createInstance(Ci.nsIAddonRepository);
@@ -267,16 +269,17 @@ var ExtensionsView = {
       this.showRestart();
       this._restartCount--; // showRestart() always increments
     }
     
     let strings = document.getElementById("bundle_browser");
     this._strings["addonType.2"] = strings.getString("addonType.2");
     this._strings["addonType.4"] = strings.getString("addonType.4");
     this._strings["addonType.8"] = strings.getString("addonType.8");
+    this._strings["addonType.1024"] = strings.getString("addonType.1024");
 
     let self = this;
     setTimeout(function() {
       self.getAddonsFromLocal();
       self.getAddonsFromRepo("");
     }, 0);
   },
 
@@ -286,76 +289,127 @@ var ExtensionsView = {
 
     this._extmgr.removeInstallListenerAt(this._observerIndex);
   },
 
   getAddonsFromLocal: function ev_getAddonsFromLocal() {
     this.clearSection("local");
 
     let items = this._extmgr.getItemList(Ci.nsIUpdateItem.TYPE_ANY, {});
-    if (items.length == 0) {
-      let strings = document.getElementById("bundle_browser");
-      this.displaySectionMessage("local", strings.getString("addonsLocalNone.label"), null, true);
-      document.getElementById("addons-update-all").disabled = true;
-    }
 
     for (let i = 0; i < items.length; i++) {
       let addon = items[i];
 
       // Some information is not directly accessible from the extmgr
       let isDisabled = this._getRDFProperty(addon.id, "isDisabled") == "true";
       let appDisabled = this._getRDFProperty(addon.id, "appDisabled");
+      let appManaged = this._getRDFProperty(addon.id, "appManaged");
       let desc = this._getRDFProperty(addon.id, "description");
       let optionsURL = this._getRDFProperty(addon.id, "optionsURL");
       let opType = this._getRDFProperty(addon.id, "opType");
       let updateable = this._getRDFProperty(addon.id, "updateable");
 
       let listitem = this._createItem(addon, "local");
       listitem.setAttribute("isDisabled", isDisabled);
       listitem.setAttribute("appDisabled", appDisabled);
+      listitem.setAttribute("appManaged", appManaged);
       listitem.setAttribute("description", desc);
       listitem.setAttribute("optionsURL", optionsURL);
       listitem.setAttribute("opType", opType);
       listitem.setAttribute("updateable", updateable);
       this._list.insertBefore(listitem, this._repoItem);
     }
+    
+    // Load the search engines
+    let defaults = this._search.getDefaultEngines({ }).map(function (e) e.name);
+    function isDefault(aEngine)
+      defaults.indexOf(aEngine.name) != -1
+
+    let engines = this._search.getEngines({ });
+    for (let e = 0; e < engines.length; e++) {
+      let engine = engines[e];
+      let addon = {};
+      addon.id = engine.name;
+      addon.type = 1024;
+      addon.name = engine.name;
+      addon.version = "";
+      addon.iconURL = engine.iconURI ? engine.iconURI.spec : "";
+
+      let listitem = this._createItem(addon, "searchplugin");
+      listitem._engine = engine;
+      listitem.setAttribute("isDisabled", engine.hidden ? "true" : "false");
+      listitem.setAttribute("appDisabled", "false");
+      listitem.setAttribute("appManaged", isDefault(engine));
+      listitem.setAttribute("description", engine.description);
+      listitem.setAttribute("optionsURL", "");
+      listitem.setAttribute("opType", engine.hidden ? "needs-disable" : "");
+      listitem.setAttribute("updateable", "false");
+      this._list.insertBefore(listitem, this._repoItem);
+    }
+
+    if (engines.length + items.length == 0) {
+      let strings = document.getElementById("bundle_browser");
+      this.displaySectionMessage("local", strings.getString("addonsLocalNone.label"), null, true);
+      document.getElementById("addons-update-all").disabled = true;
+    }
   },
 
   enable: function ev_enable(aItem) {
-    let id = this._getIDFromURI(aItem.id);
-    this._extmgr.enableItem(id);
+    let opType;
+    if (aItem.getAttribute("type") == "1024") {
+      aItem._engine.hidden = false;
+      opType = "needs-enable";
+    } else {
+      let id = this._getIDFromURI(aItem.id);
+      this._extmgr.enableItem(id);
+      opType = this._getRDFProperty(id, "opType");
 
-    let opType = this._getRDFProperty(id, "opType");
-    if (opType == "needs-enable")
-      this.showRestart();
-    else
-      this.hideRestart();
+      if (opType == "needs-enable")
+        this.showRestart();
+      else
+        this.hideRestart();
+    }
+
     aItem.setAttribute("opType", opType);
   },
 
   disable: function ev_disable(aItem) {
-    let id = this._getIDFromURI(aItem.id);
-    this._extmgr.disableItem(id);
+    let opType;
+    if (aItem.getAttribute("type") == "1024") {
+      aItem._engine.hidden = true;
+      opType = "needs-disable";
+    } else {
+      let id = this._getIDFromURI(aItem.id);
+      this._extmgr.disableItem(id);
+      opType = this._getRDFProperty(id, "opType");
 
-    let opType = this._getRDFProperty(id, "opType");
-    if (opType == "needs-disable")
-      this.showRestart();
-    else
-      this.hideRestart();
+      if (opType == "needs-disable")
+        this.showRestart();
+      else
+        this.hideRestart();
+    }
+    
     aItem.setAttribute("opType", opType);
   },
 
   uninstall: function ev_uninstall(aItem) {
-    let id = this._getIDFromURI(aItem.id);
-    this._extmgr.uninstallItem(id);
+    let opType;
+    if (aItem.getAttribute("type") == "1024") {
+      this._search.removeEngine(aItem._engine);
+      // the search-engine-modified observer in browser.js will take care of
+      // updating the list
+    } else {
+      let id = this._getIDFromURI(aItem.id);
+      this._extmgr.uninstallItem(id);
+      opType = this._getRDFProperty(id, "opType");
 
-    let opType = this._getRDFProperty(id, "opType");
-    if (opType == "needs-uninstall")
-      this.showRestart();
-    aItem.setAttribute("opType", opType);
+      if (opType == "needs-uninstall")
+        this.showRestart();
+      aItem.setAttribute("opType", opType);
+    }
   },
 
   cancelUninstall: function ev_cancelUninstall(aItem) {
     let id = this._getIDFromURI(aItem.id);
     this._extmgr.cancelUninstallItem(id);
 
     this.hideRestart();
 
--- a/mobile/locales/en-US/chrome/browser.properties
+++ b/mobile/locales/en-US/chrome/browser.properties
@@ -8,16 +8,17 @@ addonsSearchFail.button=OK
 addonsSearchSuccess.button=Clear search
 
 addonsConfirmInstall.title=Installing Add-on
 addonsConfirmInstall.install=Install
 
 addonType.2=Extension
 addonType.4=Theme
 addonType.8=Locale
+addonType.1024=Search
 
 addonUpdate.checking=Checking for updates…
 addonUpdate.updating=Updating to %S
 addonUpdate.updated=Updated to %S
 addonUpdate.compatibility=A compatibility update has been applied
 addonUpdate.noupdate=No updates were found
 addonUpdate.notsupported=Updates not supported
 addonUpdate.disabled=Updates are disabled