Bug 952799 - GeckoAppShell.getProxyForURI can be expensive during a pageload. r=mfinkle
authorRichard Newman <rnewman@mozilla.com>
Mon, 23 Dec 2013 11:09:25 -0800
changeset 161788 9bfd0ab40e7e6948b9f95ee0c3e5c50a61b88cc4
parent 161787 275be69a7ca14e7f066d5292c047584f93af6327
child 161789 cd3e9359fd64de26b86c256c5f35f8dee61bda19
push id37981
push userkwierso@gmail.com
push dateTue, 24 Dec 2013 23:25:36 +0000
treeherdermozilla-inbound@06125c2e28fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs952799
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 952799 - GeckoAppShell.getProxyForURI can be expensive during a pageload. r=mfinkle
mobile/android/base/GeckoAppShell.java
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -63,16 +63,17 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Settings;
+import android.support.v4.util.LruCache;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.HapticFeedbackConstants;
 import android.view.Surface;
@@ -2657,49 +2658,94 @@ public class GeckoAppShell
         if (getGeckoInterface() != null) {
             GeckoProfile profile = getGeckoInterface().getProfile();
             File lock = profile.getFile(".parentlock");
             return lock.exists() && lock.delete();
         }
         return false;
     }
 
-    @WrapElementForJNI(stubName = "GetProxyForURIWrapper")
-    public static String getProxyForURI(String spec, String scheme, String host, int port) {
-        URI uri = null;
+    // Holds mappings from hostnames to URIs.
+    // We only use this for HTTP and HTTPS on default ports (-1), because it's
+    // simpler to do so while addressing most uses.
+    private static LruCache<String, URI> httpURIs = new LruCache<String, URI>(10);
+    private static LruCache<String, URI> httpsURIs = new LruCache<String, URI>(10);
+
+    private static URI getAndCacheURIByParts(String scheme, String host, LruCache<String, URI> cache) {
+        URI uri = cache.get(host);
+        if (uri != null) {
+            return uri;
+        }
+
         try {
-            uri = new URI(spec);
-        } catch(java.net.URISyntaxException uriEx) {
-            try {
-                uri = new URI(scheme, null, host, port, null, null, null);
-            } catch(java.net.URISyntaxException uriEx2) {
-                Log.d("GeckoProxy", "Failed to create uri from spec", uriEx);
-                Log.d("GeckoProxy", "Failed to create uri from parts", uriEx2);
+            uri = new URI(scheme, null, host, -1, null, null, null);
+        } catch (java.net.URISyntaxException uriEx) {
+            Log.d("GeckoProxy", "Failed to create URI from parts.", uriEx);
+            return null;
+        }
+
+        cache.put(host, uri);
+        return uri;
+    }
+
+    private static URI getURIByParts(String scheme, String host, int port) {
+        if (port == -1) {
+            if (scheme.equals("http")) {
+                return getAndCacheURIByParts("http", host, httpURIs);
+            } else if (scheme.equals("https")) {
+                return getAndCacheURIByParts("https", host, httpsURIs);
             }
         }
-        if (uri != null) {
-            ProxySelector ps = ProxySelector.getDefault();
-            if (ps != null) {
-                List<Proxy> proxies = ps.select(uri);
-                if (proxies != null && !proxies.isEmpty()) {
-                    Proxy proxy = proxies.get(0);
-                    if (!Proxy.NO_PROXY.equals(proxy)) {
-                        final String proxyStr;
-                        switch (proxy.type()) {
-                        case HTTP:
-                            proxyStr = "PROXY " + proxy.address().toString();
-                            break;
-                        case SOCKS:
-                            proxyStr = "SOCKS " + proxy.address().toString();
-                            break;
-                        case DIRECT:
-                        default:
-                            proxyStr = "DIRECT";
-                            break;
-                        }
-                        return proxyStr;
-                    }
-                }
-            }
+
+        try {
+            return new URI(scheme, null, host, port, null, null, null);
+        } catch (java.net.URISyntaxException uriEx) {
+            Log.d("GeckoProxy", "Failed to create URI from parts.", uriEx);
+        }
+
+        return null;
+    }
+
+    @WrapElementForJNI(stubName = "GetProxyForURIWrapper")
+    public static String getProxyForURI(String spec, String scheme, String host, int port) {
+        final ProxySelector ps;
+        try {
+            // This is cheap -- just a SecurityManager check and a static
+            // access inside ProxySelector.
+            ps = ProxySelector.getDefault();
+        } catch (SecurityException ex) {
+            Log.w(LOGTAG, "Not allowed to use default ProxySelector.");
+            return "DIRECT";
+        }
+
+        if (ps == null) {
+            return "DIRECT";
         }
+
+        // We don't use the whole spec because parsing a URI from a string
+        // is expensive, and the stock Android proxy behavior looks only at
+        // the scheme and host.
+        URI uri = getURIByParts(scheme, host, port);
+
+        if (uri == null) {
+            return "DIRECT";
+        }
+
+        List<Proxy> proxies = ps.select(uri);
+        if (proxies == null || proxies.isEmpty()) {
+            return "DIRECT";
+        }
+
+        Proxy proxy = proxies.get(0);
+        if (Proxy.NO_PROXY.equals(proxy)) {
+            return "DIRECT";
+        }
+        
+        switch (proxy.type()) {
+            case HTTP:
+                return "PROXY " + proxy.address().toString();
+            case SOCKS:
+                return "SOCKS " + proxy.address().toString();
+        }
+
         return "DIRECT";
     }
 }