Bug 592436 - Allow badging items in the awesomebar [r=mfinkle]
authorVivien Nicolas <21@vingtetun.org>
Thu, 09 Sep 2010 16:10:27 +0200
changeset 66551 f16aa642eb1450ed670d0ca98678f06fcbd5ae76
parent 66550 ed22af19046ca473676e22353a9237b7a08f4053
child 66552 4cf211c738a84684f13ef3a6bab1ebbb5d2f1946
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)
reviewersmfinkle
bugs592436
Bug 592436 - Allow badging items in the awesomebar [r=mfinkle]
mobile/chrome/content/bindings.xml
mobile/chrome/content/browser-ui.js
mobile/themes/core/browser.css
--- a/mobile/chrome/content/bindings.xml
+++ b/mobile/chrome/content/bindings.xml
@@ -70,21 +70,25 @@
               linkURL: url
             }
           };
           ContextHelper.showPopup(data);
         ]]>
       </handler>
     </handlers>
     <content orient="vertical">
-      <xul:hbox class="autocomplete-item-label" align="top" xbl:inherits="tags, favorite" mousethrough="always">
+      <xul:hbox class="autocomplete-item-container" align="top" xbl:inherits="favorite" mousethrough="always">
         <xul:image xbl:inherits="src"/>
         <xul:vbox flex="1">
-          <xul:label crop="center" xbl:inherits="value"/>
-          <xul:label class="autocomplete-item-url" xbl:inherits="value=url" crop="center" mousethrough="always"/>
+          <xul:label class="autocomplete-item-label" crop="center" xbl:inherits="value"/>
+          <xul:label class="autocomplete-item-url" xbl:inherits="value=url" crop="center"/>
+        </xul:vbox>
+        <xul:vbox align="end">
+          <xul:label class="autocomplete-item-tags" value="" xbl:inherits="value=tags"/>
+          <xul:label class="autocomplete-item-badge" value="" xbl:inherits="value=badge"/>
         </xul:vbox>
       </xul:hbox>
     </content>
   </binding>
 
   <binding id="popup_autocomplete">
     <content orient="vbox" class="autocomplete-box" flex="1">
       <!-- 24 child items, to match browser.urlbar.maxRichResults -->
@@ -125,16 +129,21 @@
       <property name="boxObject"
                 readonly="true"
                 onget="return this._items.boxObject;"/>
 
       <field name="_scrollBoxObject">
         this.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
       </field>
 
+      <!-- Used by the badges implementation -->
+      <field name="_badges">[]</field>
+      <field name="_badgesTimeout">-1</field>
+      <field name="_eTLDService">Cc["@mozilla.org/network/effective-tld-service;1"].getService(Ci.nsIEffectiveTLDService);</field>
+
       <!-- nsIAutocompleteInput -->
       <property name="overrideValue"
                 readonly="true"
                 onget="return null;"/>
 
       <field name="_input"/>
       <property name="input"
                 readonly="true"
@@ -253,60 +262,68 @@
             if (i > matchCount - 1) {
               // Just clear out the old item's value. CSS takes care of hiding
               // everything else based on value="" (star, tags, etc.)
               item.setAttribute("value", "");
               item._empty = true;
 
               continue;
             }
-            item._empty = false;
 
             // Assign the values
             let type = controller.getStyleAt(i);
             let title = controller.getCommentAt(i);
             let tags = '';
 
             if (type == "tag") {
               try {
                 [, title, tags] = title.match(/^(.+) \u2013 (.+)$/);
               } catch (e) {}
             }
             item.setAttribute("tags", tags);
 
             let url = controller.getValueAt(i);
             item.setAttribute("value", title || url);
-            item.setAttribute("url", url);
+
+            // remove the badge only if the url has changed
+            if (item._empty || item.getAttribute("url") != url) {
+              item.setAttribute("url", url);
+              item.removeAttribute("badge");
+            }
 
             let isBookmark = ((type == "bookmark") || (type == "tag"));
             item.setAttribute("favorite", isBookmark);
             item.setAttribute("src", controller.getImageAt(i));
+
+            item._empty = false;
           }
 
           // Show the "no results" or "all bookmarks" entries as needed
           this._updateNoResultsItem(matchCount);
 
           // Make sure the list is scrolled to the top
           this.scrollToTop();
+          this._invalidateBadges();
         ]]></body>
       </method>
 
       <method name="_updateNoResultsItem">
         <parameter name="isResults" />
         <body><![CDATA[
           let noResultsItem = this._items.childNodes.item(1);
           if (isResults) {
             noResultsItem.className = "";
           } else {
             noResultsItem.className = "noresults";
             noResultsItem.setAttribute("value", "]]>&noResults.label;<![CDATA[");
             noResultsItem.removeAttribute("favorite");
             noResultsItem.removeAttribute("url");
             noResultsItem.removeAttribute("src");
             noResultsItem.removeAttribute("tags");
+            noResultsItem.removeAttribute("badge");
           }
         ]]></body>
       </method>
 
       <method name="selectBy">
         <parameter name="aReverse"/>
         <parameter name="aPage"/>
         <body><![CDATA[
@@ -323,16 +340,74 @@
             newIndex = 0;
           else if (newIndex < 0)
             newIndex = lastIndex;
 
           this.selectedIndex = newIndex;
         ]]></body>
       </method>
 
+      <method name="_getEffectiveHost">
+        <parameter name="aURI"/>
+        <body><![CDATA[
+          let host = null;
+          try {
+            host = aURI.host;
+            return this._eTLDService.getBaseDomainFromHost(host);
+          } catch (e) {}
+
+          return host ? host : aURI.spec;
+        ]]></body>
+      </method>
+
+      <method name="registerBadgeHandler">
+        <parameter name="aURL"/>
+        <parameter name="aHandler"/>
+        <body><![CDATA[
+          if (!aHandler)
+            return false;
+
+          let effectiveHost = this._getEffectiveHost(Services.io.newURI(aURL, null, null));
+          this._badges[effectiveHost] = aHandler;
+          return true;
+        ]]></body>
+      </method>
+
+      <method name="unregisterBagdeHandler">
+        <parameter name="aURL"/>
+        <body><![CDATA[
+          let effectiveHost = this._getEffectiveHost(Services.io.newURI(aURL, null, null));
+          if (this._badges[effectiveHost])
+            delete this._badges[effectiveHost];
+        ]]></body>
+      </method>
+
+      <method name="_invalidateBadges">
+        <body><![CDATA[
+          window.clearTimeout(this._badgesTimeout);
+
+          this._badgesTimeout = window.setTimeout(function(self) {
+            for (let i = 0; i < self._items.childNodes.length; i++) {
+              let item = self._items.childNodes[i];
+              if (!item.hasAttribute("url"))
+                continue;
+
+              let itemHost = self._getEffectiveHost(Services.io.newURI(item.getAttribute("url"), null, null));
+              for (let badgeHost in self._badges) {
+                if (itemHost == badgeHost) {
+                  let handler = self._badges[badgeHost];
+                  handler.updateBadge ? handler.updateBadge(item) : handler(item);
+                  break;
+                }
+              }
+            }
+          }, 300, this);
+        ]]></body>
+      </method>
+
       <!-- Helpers -->
       <field name="_items">
         document.getAnonymousElementByAttribute(this,
                         "anonid", "autocomplete-items");
       </field>
 
       <property name="_matchCount"
                 readonly="true">
@@ -615,22 +690,25 @@
           ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
   <binding id="place-item" extends="chrome://browser/content/bindings.xml#place-base">
     <content orient="vertical">
-      <xul:hbox anonid="bookmark-item" class="bookmark-item-label" align="top" flex="1" xbl:inherits="tags" mousethrough="always">
+      <xul:hbox anonid="bookmark-item" class="bookmark-item-container" align="top" flex="1" mousethrough="always">
         <xul:image xbl:inherits="src"/>
         <xul:vbox flex="1">
-          <xul:label crop="center" xbl:inherits="value=title"/>
+          <xul:label class="bookmark-item-label" crop="center" xbl:inherits="value=title"/>
           <xul:label anonid="bookmark-url" class="bookmark-item-url" xbl:inherits="value=uri" crop="center" mousethrough="always"/>
         </xul:vbox>
+        <xul:vbox>
+          <xul:label class="bookmark-item-tags" xbl:inherits="value=tags"/>
+        </xul:vbox>
       </xul:hbox>
 
       <xul:hbox anonid="bookmark-manage" class="bookmark-manage" hidden="true" flex="1">
         <xul:image xbl:inherits="src"/>
         <xul:vbox flex="1">
           <xul:vbox flex="1">
             <xul:textbox anonid="name" xbl:inherits="value=title"/>
             <xul:textbox anonid="uri" xbl:inherits="value=uri"/>
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -2428,17 +2428,16 @@ var ContextCommands = {
   },
 
   removeBookmark: function cc_removeBookmark() {
     let target = ContextHelper.popupState.target;
     target.remove();
   }
 }
 
-
 var SharingUI = {
   _dialog: null,
 
   show: function show(aURL, aTitle) {
     this._dialog = importDialog(window, "chrome://browser/content/share.xul", null);
     document.getElementById("share-title").value = aTitle || aURL;
 
     BrowserUI.pushPopup(this, this._dialog);
--- a/mobile/themes/core/browser.css
+++ b/mobile/themes/core/browser.css
@@ -669,17 +669,17 @@ placeitem[type="folder"] {
 placeitem[type="folder"]:-moz-locale-dir(rtl) {
   background: url(images/arrowleft-16.png) no-repeat 2% 50%;
 }
 
 placelist[ui="manage"] placeitem[type="folder"] {
   background-image: none;
 }
 
-placeitem[type="folder"] > .bookmark-item-label > image,
+placeitem[type="folder"] > .bookmark-item-container > image,
 placeitem[type="folder"] > .bookmark-manage > image {
   list-style-image: url(images/folder-32.png);
   margin-top: 0;
 }
 
 placeitem[type="folder"] .bookmark-item-url {
   display: none;
 }
@@ -757,89 +757,120 @@ placeitem {
 autocompleteresult:active,
 placelist placeitem:active:not([selected="true"]),
 historylist placeitem:active:not([selected="true"]):not([class="history-item-title"]),
 historylist placeitem:active:not([selected="true"]):not([class="remotetabs-item-title"]),
 .autocompleteresult-selected {
   background-color: #8db8d8;
 }
 
+.autocomplete-item-container,
+.bookmark-item-container {
+  margin: 0;
+  padding: 0;
+}
+
 .autocomplete-item-label,
 .bookmark-item-label {
-  margin: 0;
-  padding: 0;
   font-size: 24px !important;
   font-weight: normal;
   -moz-margin-end: 8px;
 }
 
-.autocomplete-item-label > image,
-.bookmark-item-label > image,
+.autocomplete-item-container > image,
+.bookmark-item-container > image,
 placeitem > .bookmark-manage > image {
   width: 32px;
   height: 32px;
   max-height: 32px;
   /* margin-top = (1 - title's line-height) * title's font-size */
   margin-top: 5px;
   margin-bottom: 0;
   -moz-margin-end: 16px;
   -moz-margin-start: 8px;
 }
 
-.autocomplete-item-label > image[src=""],
-placeitem[src=""] .bookmark-item-label > image {
+.autocomplete-item-container > image[src=""],
+placeitem[src=""] .bookmark-item-container > image {
   list-style-image: url(chrome://mozapps/skin/places/defaultFavicon.png);
 }
 
-.autocomplete-item-label > vbox > label,
-.bookmark-item-label > vbox > label {
+.autocomplete-item-container > vbox > label,
+.bookmark-item-container > vbox > label {
   -moz-margin-start: 1px;
 }
 
-.autocomplete-item-label[favorite="true"] {
-  -moz-padding-end: 30px;
+.autocomplete-item-container[favorite="true"] {
   background: url(images/star-24.png) no-repeat 100% 2px;
 }
 
-.autocomplete-item-label[favorite="true"]:-moz-locale-dir(rtl) {
+.autocomplete-item-container[favorite="true"]:-moz-locale-dir(rtl) {
   background: url(images/star-24.png) no-repeat left 2px;
 }
 
-.autocomplete-item-label:not([tags=""]):after,
-.bookmark-item-label:not([tags=""]):after {
-  float: right;
-  content: attr(tags);
-  font-size: 18px !important;
-  font-weight: lighter;
-  padding-top: 4px;
-  -moz-margin-start: 8px;
-}
-
 .autocomplete-item-url,
 .bookmark-item-url {
   color: blue;
   font-size: 18px !important;
   -moz-margin-end: 24px;
 }
 
+.autocomplete-item-container[favorite="true"] .autocomplete-item-label {
+  -moz-padding-end: 30px;
+}
+
+.autocomplete-item-tags,
+.bookmark-item-tags {
+  content: attr(tags);
+  font-size: 18px !important;
+  font-weight: lighter;
+  margin: 2px 0 4px 0px;
+  -moz-margin-start: 8px;
+  -moz-padding-end: 32px;
+}
+
+.autocomplete-item-tags[value=""] {
+  visibility: hidden;
+}
+
+.autocomplete-item-badge {
+  opacity: 1;
+  -moz-transition: opacity 1s ease;
+  background-color: #c90707;
+  border: 1px solid #951919;
+  -moz-border-radius: 2px;
+  content: attr(badge);
+  font-size: 12px !important;
+  font-weight: bolder;
+  margin: 4px 0 0 0;
+  padding: 4px 6px;
+  color: white;
+}
+
+autocompleteresult:not([badge]) .autocomplete-item-badge,
+.autocomplete-item-badge[value=""] {
+  opacity: 0;
+  -moz-transition: none;
+}
+
 /* special "no results", "awesome header row" and "title rows" items */
 autocompleteresult[class="history-item-title"],
 autocompleteresult[class="remotetabs-item-title"] {
   -moz-box-pack: center;
   background-color: #E9E9E9;
   min-height: 0px;
 }
 
 autocompleteresult[class="history-item-title"] .bookmark-item-url,
 autocompleteresult[class="remotetabs-item-title"] .bookmark-item-url {
   display: none;
 }
 
-autocompleteresult[class="history-item-title"] .bookmark-item-label,
-autocompleteresult[class="remotetabs-item-title"] .bookmark-item-label {
+autocompleteresult[class="history-item-title"] .bookmark-item-container,
+autocompleteresult[class="remotetabs-item-title"] .bookmark-item-container {
   font-size: 24px !important;
 }
 
 autocompleteresult[class="history-item-title"] image,
 autocompleteresult[class="remotetabs-item-title"] image {
   display: none;
 }
 
@@ -847,17 +878,17 @@ autocompleteresult.noresults {
   font-style: italic;
   border-bottom: none;
 }
 
 autocompleteresult.noresults:active {
   background-color: white;
 }
 
-autocompleteresult.noresults > .autocomplete-item-label {
+autocompleteresult.noresults > .autocomplete-item-container {
   text-align: center;
   color: grey;
 }
 
 #awesome-header {
   min-height: 70px; /* row size */
 }