Bug 826400 - Part 2: Extract and store optimal apple-touch-icon during page load r=nalexander
--- 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);