Bug 826400 - Part 2: Extract and store optimal apple-touch-icon during page load r=nalexander draft
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 05 Jan 2016 14:49:43 -0800
changeset 319139 074a36a6acc9e3aa8f63c71403a7cc4afdb31032
parent 319138 854144400164d80114a84dcd77e05cf27647280f
child 319140 dbdfaf915ffd0dc1ae3d8c266c484c4a4c635826
push id8984
push userahunt@mozilla.com
push dateTue, 05 Jan 2016 23:06:12 +0000
reviewersnalexander
bugs826400
milestone46.0a1
Bug 826400 - Part 2: Extract and store optimal apple-touch-icon during page load r=nalexander
mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
@@ -4,21 +4,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 package org.mozilla.gecko.db;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.Assert;
+import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.support.v4.util.LruCache;
@@ -47,28 +51,72 @@ public class LocalURLMetadata implements
     // Store a cache of recent results. This number is chosen to match the max number of tiles on about:home
     private static final int CACHE_SIZE = 9;
     // Note: Members of this cache are unmodifiable.
     private final LruCache<String, Map<String, Object>> cache = new LruCache<String, Map<String, Object>>(CACHE_SIZE);
 
     /**
      * Converts a JSON object into a unmodifiable Map of known metadata properties.
      * Will throw away any properties that aren't stored in the database.
+     *
+     * Incoming data can include a list like: {touchIconList:{56:"http://x.com/56.png", 76:"http://x.com/76.png"}}.
+     * This will then be filtered to find the most appropriate touchIcon, i.e. the closest icon size that is larger
+     * than (or equal to) the preferred homescreen launcher icon size, which is then stored in the "touchIcon" property.
+     *
+     * @see #getModel() Returns the list of properties that will be stored in the database.
      */
     @Override
     public Map<String, Object> fromJSON(JSONObject obj) {
         Map<String, Object> data = new HashMap<String, Object>();
 
         Set<String> model = getModel();
         for (String key : model) {
             if (obj.has(key)) {
                 data.put(key, obj.optString(key));
             }
         }
 
+
+        try {
+            JSONObject icons;
+            if (obj.has("touchIconList") &&
+                    (icons = obj.getJSONObject("touchIconList")).length() > 0) {
+                int preferredSize = GeckoAppShell.getPreferredIconSize();
+                int bestSizeFound = -1;
+
+                Iterator<String> keys = icons.keys();
+
+                ArrayList<Integer> sizes = new ArrayList<Integer>(icons.length());
+                while (keys.hasNext()) {
+                    sizes.add(new Integer(keys.next()));
+                }
+
+                Collections.sort(sizes);
+                for (int size : sizes) {
+                    if (size >= preferredSize) {
+                        bestSizeFound = size;
+                        break;
+                    }
+                }
+
+                // If all icons are smaller than the preferred size then we don't have an icon
+                // selected yet (bestSizeFound == -1), therefore just take the largest (last) icon.
+                if (bestSizeFound == -1) {
+                    bestSizeFound = sizes.get(sizes.size() - 1);
+                }
+
+                String iconURL = icons.getString(Integer.toString(bestSizeFound));
+
+                data.put(URLMetadataTable.TOUCH_ICON_COLUMN, iconURL);
+            }
+        } catch (JSONException e) {
+            Log.w(LOGTAG, "Exception processing touchIconList for LocalURLMetadata; ignoring.", e);
+            Assert.isTrue(false);
+        }
+
         return Collections.unmodifiableMap(data);
     }
 
     /**
      * Converts a Cursor into a unmodifiable Map of known metadata properties.
      * Will throw away any properties that aren't stored in the database.
      * Will also not iterate through multiple rows in the cursor.
      */
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -3985,17 +3985,22 @@ Tab.prototype = {
 
   addMetadata: function(type, value, quality = 1) {
     if (!this.metatags) {
       this.metatags = {
         url: this.browser.currentURI.spec
       };
     }
 
-    if (!this.metatags[type] || this.metatags[type + "_quality"] < quality) {
+    if (type == "touchIconList") {
+      if (!this.metatags['touchIconList']) {
+        this.metatags['touchIconList'] = {};
+      }
+      this.metatags.touchIconList[quality] = value;
+    } else if (!this.metatags[type] || this.metatags[type + "_quality"] < quality) {
       this.metatags[type] = value;
       this.metatags[type + "_quality"] = quality;
     }
   },
 
   sanitizeRelString: function(linkRel) {
     // Sanitize the rel string
     let list = [];
@@ -4222,16 +4227,19 @@ Tab.prototype = {
         // Ignore on frames and other documents
         if (target.ownerDocument != this.browser.contentDocument)
           return;
 
         // Sanitize rel link
         let list = this.sanitizeRelString(target.rel);
         if (list.indexOf("[icon]") != -1) {
           jsonMessage = this.makeFaviconMessage(target);
+        } else if (list.indexOf("[apple-touch-icon]") != -1) {
+          let message = this.makeFaviconMessage(target);
+          this.addMetadata("touchIconList", message.href, message.size);
         } else if (list.indexOf("[alternate]") != -1 && aEvent.type == "DOMLinkAdded") {
           let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
           let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
 
           if (!isFeed)
             return;
 
           jsonMessage = this.makeFeedMessage(target, type);