--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
@@ -132,18 +132,19 @@ public class ActivityStream {
}
// If no usable path segment was found then use the host without public suffix and common subdomains
final String host = uri.getHost();
if (TextUtils.isEmpty(host)) {
return url;
}
- return StringUtils.stripCommonSubdomains(
- PublicSuffix.stripPublicSuffix(context, host));
+ return PublicSuffix.stripPublicSuffix(
+ context,
+ StringUtils.stripCommonSubdomains(context, host));
}
@Override
protected void onPostExecute(String label) {
callback.onLabelExtracted(label);
}
}.execute();
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
@@ -614,17 +614,17 @@ public class BrowserSearch extends HomeF
if (host == null) {
continue;
}
if (host.startsWith(searchTerm)) {
return host + "/";
}
- final String strippedHost = StringUtils.stripCommonSubdomains(host);
+ final String strippedHost = StringUtils.stripCommonSubdomains(getContext(), host);
if (strippedHost.startsWith(searchTerm)) {
return strippedHost + "/";
}
++searchCount;
if (!searchPath) {
continue;
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
@@ -51,17 +51,17 @@ public class HomeContextMenuInfo extends
public boolean canRemove() {
return hasBookmarkId() || hasHistoryId() || hasPartnerBookmarkId();
}
public String getDisplayTitle() {
if (!TextUtils.isEmpty(title)) {
return title;
}
- return StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS));
+ return StringUtils.stripCommonSubdomains(targetView.getContext(), StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS));
}
/**
* Interface for creating ContextMenuInfo instances from cursors.
*/
public interface Factory {
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor);
}
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
@@ -62,26 +62,26 @@ public class IconGenerator implements Ic
@VisibleForTesting static IconResponse generate(Context context, String pageURL) {
final Resources resources = context.getResources();
final int widthAndHeight = resources.getDimensionPixelSize(R.dimen.favicon_bg);
final int roundedCorners = resources.getDimensionPixelOffset(R.dimen.favicon_corner_radius);
final Bitmap favicon = Bitmap.createBitmap(widthAndHeight, widthAndHeight, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(favicon);
- final int color = pickColor(pageURL);
+ final int color = pickColor(context, pageURL);
final Paint paint = new Paint();
paint.setColor(color);
canvas.drawRoundRect(new RectF(0, 0, widthAndHeight, widthAndHeight), roundedCorners, roundedCorners, paint);
paint.setColor(Color.WHITE);
- final String character = getRepresentativeCharacter(pageURL);
+ final String character = getRepresentativeCharacter(context, pageURL);
final float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DP, context.getResources().getDisplayMetrics());
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);
paint.setAntiAlias(true);
canvas.drawText(character,
@@ -92,66 +92,66 @@ public class IconGenerator implements Ic
return IconResponse.createGenerated(favicon, color);
}
/**
* Get a representative character for the given URL.
*
* For example this method will return "f" for "http://m.facebook.com/foobar".
*/
- @VisibleForTesting static String getRepresentativeCharacter(String url) {
+ @VisibleForTesting static String getRepresentativeCharacter(@NonNull final Context context, String url) {
if (TextUtils.isEmpty(url)) {
return "?";
}
- final String snippet = getRepresentativeSnippet(url);
+ final String snippet = getRepresentativeSnippet(context, url);
for (int i = 0; i < snippet.length(); i++) {
char c = snippet.charAt(i);
if (Character.isLetterOrDigit(c)) {
return String.valueOf(Character.toUpperCase(c));
}
}
// Nothing found..
return "?";
}
/**
* Return a color for this URL. Colors will be based on the host. URLs with the same host will
* return the same color.
*/
- @VisibleForTesting static int pickColor(String url) {
+ @VisibleForTesting static int pickColor(@NonNull final Context context, String url) {
if (TextUtils.isEmpty(url)) {
return COLORS[0];
}
- final String snippet = getRepresentativeSnippet(url);
+ final String snippet = getRepresentativeSnippet(context, url);
final int color = Math.abs(snippet.hashCode() % COLORS.length);
return COLORS[color];
}
/**
* Get the representative part of the URL. Usually this is the host (without common prefixes).
*/
- private static String getRepresentativeSnippet(@NonNull String url) {
+ private static String getRepresentativeSnippet(@NonNull final Context context, @NonNull String url) {
Uri uri = Uri.parse(url);
// Use the host if available
String snippet = uri.getHost();
if (TextUtils.isEmpty(snippet)) {
// If the uri does not have a host (e.g. file:// uris) then use the path
snippet = uri.getPath();
}
if (TextUtils.isEmpty(snippet)) {
// If we still have no snippet then just return the question mark
return "?";
}
// Strip common prefixes that we do not want to use to determine the representative characterS
- snippet = StringUtils.stripCommonSubdomains(snippet);
+ snippet = StringUtils.stripCommonSubdomains(context, snippet);
return snippet;
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -274,17 +274,17 @@ public class ToolbarDisplayLayout extend
final String baseDomain = tab.getBaseDomain();
String strippedURL = stripAboutReaderURL(url);
final boolean isHttpOrHttps = StringUtils.isHttpOrHttps(strippedURL);
if (mPrefs.shouldTrimUrls()) {
- strippedURL = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(strippedURL));
+ strippedURL = StringUtils.stripCommonSubdomains(getContext(), StringUtils.stripScheme(strippedURL));
}
// The URL bar does not support RTL currently (See bug 928688 and meta bug 702845).
// Displaying a URL using RTL (or mixed) characters can lead to an undesired reordering
// of elements of the URL. That's why we are forcing the URL to use LTR (bug 1284372).
strippedURL = StringUtils.forceLTR(strippedURL);
// This value is not visible to screen readers but we rely on it when running UI tests. Screen
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
@@ -1,19 +1,22 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.util;
+import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
+import org.mozilla.gecko.util.publicsuffix.PublicSuffix;
+
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class StringUtils {
private static final String LOGTAG = "GeckoStringUtils";
@@ -121,34 +124,68 @@ public class StringUtils {
public static boolean isHttpOrHttps(String url) {
if (TextUtils.isEmpty(url)) {
return false;
}
return url.startsWith("http://") || url.startsWith("https://");
}
- public static String stripCommonSubdomains(String host) {
+ final static String[] prefixes = new String[] {
+ "www.",
+ "mobile.",
+ "m."
+ };
+
+ public static String stripCommonSubdomains(final Context context, String host) {
if (host == null) {
return host;
}
// In contrast to desktop, we also strip mobile subdomains,
// since its unlikely users are intentionally typing them
- int start = 0;
+
+ // A host might have multiple prefixes, in undetermined order - sites such as www.mobile.foo.com
+ // and m.www.foo.com exist. Hence we need to keep iterating over all prefixes (and removing them)
+ // until no more known prefixes can be found.
+ // We do this by resetting our iterator every time a prefix is found and removed.
+
+ int i = 0;
- if (host.startsWith("www.")) {
- start = 4;
- } else if (host.startsWith("mobile.")) {
- start = 7;
- } else if (host.startsWith("m.")) {
- start = 2;
+ // String.substring() can be cheap or expensive depending on java implementation, hence
+ // we keep our own offset, and only call substring() at the end.
+ int offset = 0;
+ int previousOffset = 0;
+
+ while (i < prefixes.length) {
+ final String prefix = prefixes[i];
+
+ if (host.regionMatches(true, offset, prefix, 0, prefix.length())) {
+ previousOffset = offset;
+ offset += prefix.length();
+ // reset the iterator, as described above
+ i = 0;
+ } else {
+ i++;
+ }
}
- return host.substring(start);
+ final String hostWithoutSuffix = PublicSuffix.stripPublicSuffix(context, host);
+ final String suffix = host.substring(hostWithoutSuffix.length() + 1);
+
+ final String strippedHost = host.substring(offset);
+
+ // If the suffix is the same as the stripped host, then we've stripped one segment too many
+ // (which can happen e.g. for mobile.com). In this case we simply restore one segment by
+ // using the previous offset:
+ if (strippedHost.length() == suffix.length()) {
+ return host.substring(previousOffset);
+ }
+
+ return strippedHost;
}
/**
* Searches the url query string for the first value with the given key.
*/
public static String getQueryParameter(String url, String desiredKey) {
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(desiredKey)) {
return null;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconGenerator.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconGenerator.java
@@ -1,29 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.icons.loader;
+import android.content.Context;
import android.graphics.Bitmap;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.icons.IconDescriptor;
import org.mozilla.gecko.icons.IconRequest;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.robolectric.RuntimeEnvironment;
@RunWith(TestRunner.class)
public class TestIconGenerator {
+ private final Context context = RuntimeEnvironment.application;
+
@Test
public void testNoIconIsGeneratorIfThereAreIconUrlsToLoadFrom() {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl("http://www.mozilla.org")
.icon(IconDescriptor.createGenericIcon(
"https://www.mozilla.org/media/img/favicon/apple-touch-icon-180x180.00050c5b754e.png"))
.icon(IconDescriptor.createGenericIcon(
"https://www.mozilla.org/media/img/favicon.52506929be4c.ico"))
@@ -47,74 +50,74 @@ public class TestIconGenerator {
IconResponse response = loader.load(request);
Assert.assertNotNull(response);
Assert.assertNotNull(response.getBitmap());
}
@Test
public void testRepresentativeCharacter() {
- Assert.assertEquals("M", IconGenerator.getRepresentativeCharacter("https://mozilla.org"));
- Assert.assertEquals("W", IconGenerator.getRepresentativeCharacter("http://wikipedia.org"));
- Assert.assertEquals("P", IconGenerator.getRepresentativeCharacter("http://plus.google.com"));
- Assert.assertEquals("E", IconGenerator.getRepresentativeCharacter("https://en.m.wikipedia.org/wiki/Main_Page"));
+ Assert.assertEquals("M", IconGenerator.getRepresentativeCharacter(context, "https://mozilla.org"));
+ Assert.assertEquals("W", IconGenerator.getRepresentativeCharacter(context, "http://wikipedia.org"));
+ Assert.assertEquals("P", IconGenerator.getRepresentativeCharacter(context, "http://plus.google.com"));
+ Assert.assertEquals("E", IconGenerator.getRepresentativeCharacter(context, "https://en.m.wikipedia.org/wiki/Main_Page"));
// Stripping common prefixes
- Assert.assertEquals("T", IconGenerator.getRepresentativeCharacter("http://www.theverge.com"));
- Assert.assertEquals("F", IconGenerator.getRepresentativeCharacter("https://m.facebook.com"));
- Assert.assertEquals("T", IconGenerator.getRepresentativeCharacter("https://mobile.twitter.com"));
+ Assert.assertEquals("T", IconGenerator.getRepresentativeCharacter(context, "http://www.theverge.com"));
+ Assert.assertEquals("F", IconGenerator.getRepresentativeCharacter(context, "https://m.facebook.com"));
+ Assert.assertEquals("T", IconGenerator.getRepresentativeCharacter(context, "https://mobile.twitter.com"));
// Special urls
- Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter("file:///"));
- Assert.assertEquals("S", IconGenerator.getRepresentativeCharacter("file:///system/"));
- Assert.assertEquals("P", IconGenerator.getRepresentativeCharacter("ftp://people.mozilla.org/test"));
+ Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(context, "file:///"));
+ Assert.assertEquals("S", IconGenerator.getRepresentativeCharacter(context, "file:///system/"));
+ Assert.assertEquals("P", IconGenerator.getRepresentativeCharacter(context, "ftp://people.mozilla.org/test"));
// No values
- Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(""));
- Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(null));
+ Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(context, ""));
+ Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(context, null));
// Rubbish
- Assert.assertEquals("Z", IconGenerator.getRepresentativeCharacter("zZz"));
- Assert.assertEquals("Ö", IconGenerator.getRepresentativeCharacter("ölkfdpou3rkjaslfdköasdfo8"));
- Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter("_*+*'##"));
- Assert.assertEquals("ツ", IconGenerator.getRepresentativeCharacter("¯\\_(ツ)_/¯"));
- Assert.assertEquals("ಠ", IconGenerator.getRepresentativeCharacter("ಠ_ಠ Look of Disapproval"));
+ Assert.assertEquals("Z", IconGenerator.getRepresentativeCharacter(context, "zZz"));
+ Assert.assertEquals("Ö", IconGenerator.getRepresentativeCharacter(context, "ölkfdpou3rkjaslfdköasdfo8"));
+ Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(context, "_*+*'##"));
+ Assert.assertEquals("ツ", IconGenerator.getRepresentativeCharacter(context, "¯\\_(ツ)_/¯"));
+ Assert.assertEquals("ಠ", IconGenerator.getRepresentativeCharacter(context, "ಠ_ಠ Look of Disapproval"));
// Non-ASCII
- Assert.assertEquals("Ä", IconGenerator.getRepresentativeCharacter("http://www.ätzend.de"));
- Assert.assertEquals("名", IconGenerator.getRepresentativeCharacter("http://名がドメイン.com"));
- Assert.assertEquals("C", IconGenerator.getRepresentativeCharacter("http://√.com"));
- Assert.assertEquals("ß", IconGenerator.getRepresentativeCharacter("http://ß.de"));
- Assert.assertEquals("Ԛ", IconGenerator.getRepresentativeCharacter("http://ԛәлп.com/")); // cyrillic
+ Assert.assertEquals("Ä", IconGenerator.getRepresentativeCharacter(context, "http://www.ätzend.de"));
+ Assert.assertEquals("名", IconGenerator.getRepresentativeCharacter(context, "http://名がドメイン.com"));
+ Assert.assertEquals("C", IconGenerator.getRepresentativeCharacter(context, "http://√.com"));
+ Assert.assertEquals("ß", IconGenerator.getRepresentativeCharacter(context, "http://ß.de"));
+ Assert.assertEquals("Ԛ", IconGenerator.getRepresentativeCharacter(context, "http://ԛәлп.com/")); // cyrillic
// Punycode
- Assert.assertEquals("X", IconGenerator.getRepresentativeCharacter("http://xn--tzend-fra.de")); // ätzend.de
- Assert.assertEquals("X", IconGenerator.getRepresentativeCharacter("http://xn--V8jxj3d1dzdz08w.com")); // 名がドメイン.com
+ Assert.assertEquals("X", IconGenerator.getRepresentativeCharacter(context, "http://xn--tzend-fra.de")); // ätzend.de
+ Assert.assertEquals("X", IconGenerator.getRepresentativeCharacter(context, "http://xn--V8jxj3d1dzdz08w.com")); // 名がドメイン.com
// Numbers
- Assert.assertEquals("1", IconGenerator.getRepresentativeCharacter("https://www.1and1.com/"));
+ Assert.assertEquals("1", IconGenerator.getRepresentativeCharacter(context, "https://www.1and1.com/"));
// IP
- Assert.assertEquals("1", IconGenerator.getRepresentativeCharacter("https://192.168.0.1"));
+ Assert.assertEquals("1", IconGenerator.getRepresentativeCharacter(context, "https://192.168.0.1"));
}
@Test
public void testPickColor() {
- final int color = IconGenerator.pickColor("http://m.facebook.com");
+ final int color = IconGenerator.pickColor(context, "http://m.facebook.com");
// Color does not change
for (int i = 0; i < 100; i++) {
- Assert.assertEquals(color, IconGenerator.pickColor("http://m.facebook.com"));
+ Assert.assertEquals(color, IconGenerator.pickColor(context, "http://m.facebook.com"));
}
// Color is stable for "similar" hosts.
- Assert.assertEquals(color, IconGenerator.pickColor("https://m.facebook.com"));
- Assert.assertEquals(color, IconGenerator.pickColor("http://facebook.com"));
- Assert.assertEquals(color, IconGenerator.pickColor("http://www.facebook.com"));
- Assert.assertEquals(color, IconGenerator.pickColor("http://www.facebook.com/foo/bar/foobar?mobile=1"));
+ Assert.assertEquals(color, IconGenerator.pickColor(context, "https://m.facebook.com"));
+ Assert.assertEquals(color, IconGenerator.pickColor(context, "http://facebook.com"));
+ Assert.assertEquals(color, IconGenerator.pickColor(context, "http://www.facebook.com"));
+ Assert.assertEquals(color, IconGenerator.pickColor(context, "http://www.facebook.com/foo/bar/foobar?mobile=1"));
}
@Test
public void testGeneratingFavicon() {
final IconResponse response = IconGenerator.generate(RuntimeEnvironment.application, "http://m.facebook.com");
final Bitmap bitmap = response.getBitmap();
Assert.assertNotNull(bitmap);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestStringUtils.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestStringUtils.java
@@ -1,28 +1,33 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.util;
+import android.content.Context;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.robolectric.RuntimeEnvironment;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestStringUtils {
+ final Context context = RuntimeEnvironment.application;
+
@Test
public void testIsHttpOrHttps() {
// No value
assertFalse(StringUtils.isHttpOrHttps(null));
assertFalse(StringUtils.isHttpOrHttps(""));
// Garbage
assertFalse(StringUtils.isHttpOrHttps("lksdjflasuf"));
@@ -137,9 +142,37 @@ public class TestStringUtils {
// test ambiguous
String ambiguous = "~!@#$%^&*()_+`34567890-=qwertyuiop[]\\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:\"ZXCVBNM<>?zxcvbnm,./";
ambiguous = ambiguous.replace(" ","").replace(".","").replace(":","");
assertTrue(StringUtils.isSearchQuery(ambiguous,true));
assertFalse(StringUtils.isSearchQuery(ambiguous,false));
}
+
+ @Test
+ public void testStripCommonSubdomains() {
+ assertEquals(null, StringUtils.stripCommonSubdomains(context, null));
+
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "www.mozilla.org"));
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "mobile.mozilla.org"));
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "mobile.mozilla.org"));
+
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "www.mobile.mozilla.org"));
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "www.m.mozilla.org"));
+
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "mobile.www.mozilla.org"));
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "mobile.m.mozilla.org"));
+
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "m.mobile.mozilla.org"));
+ assertEquals("mozilla.org", StringUtils.stripCommonSubdomains(context, "m.www.mozilla.org"));
+
+ assertEquals("mobile.com", StringUtils.stripCommonSubdomains(context, "mobile.com"));
+ assertEquals("mobile.com", StringUtils.stripCommonSubdomains(context, "www.mobile.com"));
+ assertEquals("mobile.co.uk", StringUtils.stripCommonSubdomains(context, "mobile.co.uk"));
+ assertEquals("mobile.co.uk", StringUtils.stripCommonSubdomains(context, "www.m.mobile.co.uk"));
+
+ assertEquals("www.com", StringUtils.stripCommonSubdomains(context, "www.com"));
+ assertEquals("www.com", StringUtils.stripCommonSubdomains(context, "mobile.www.com"));
+
+ assertEquals("arbitrary.mozilla.org", StringUtils.stripCommonSubdomains(context, "arbitrary.mozilla.org"));
+ }
}