bug 713503 - prefetch urls from known url shortening sites before gecko is running r=mfinkle

This commit is contained in:
Brad Lassey
2012-01-09 23:50:56 -08:00
parent b9049f193c
commit f94de4f096
3 changed files with 126 additions and 21 deletions

View File

@@ -48,5 +48,19 @@ public class App extends GeckoApp {
public String getContentProcessName() {
return "@MOZ_CHILD_PROCESS_NAME@";
}
public String getDefaultUAString() {
return "Mozilla/5.0 (Android; Linux armv7l; rv:@MOZ_APP_VERSION@) Gecko/@UA_BUILDID@ Firefox/@MOZ_APP_VERSION@ Fennec/@MOZ_APP_VERSION@";
}
public String getUAStringForHost(String host) {
// With our standard UA String, we get a 200 response code and
// client-side redirect from t.co. This slight tweak gives us a
// 302 response code
if ("t.co".equals(host))
return "Mozilla/5.0 (Android; Linux armv7l; rv:@MOZ_APP_VERSION@) Gecko/@UA_BUILDID@ Firefox Mobile/@MOZ_APP_VERSION@";
return getDefaultUAString();
}
};

View File

@@ -98,6 +98,7 @@ abstract public class GeckoApp
public static final String ACTION_WEBAPP = "org.mozilla.gecko.WEBAPP";
public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
public static final String ACTION_BOOKMARK = "org.mozilla.gecko.BOOKMARK";
public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
public static final String SAVED_STATE_URI = "uri";
public static final String SAVED_STATE_TITLE = "title";
public static final String SAVED_STATE_VIEWPORT = "viewport";
@@ -1446,9 +1447,23 @@ abstract public class GeckoApp
if (sGREDir == null)
sGREDir = new File(this.getApplicationInfo().dataDir);
prefetchDNS(intent.getData());
String passedUri = mLastUri;
sGeckoThread = new GeckoThread(intent, mLastUri, mRestoreSession);
Uri data = intent.getData();
if (data != null && "http".equals(data.getScheme()) &&
isHostOnPrefetchWhitelist(data.getHost())) {
Intent copy = new Intent(intent);
copy.setAction(ACTION_LOAD);
GeckoAppShell.getHandler().post(new RedirectorRunnable(copy));
// We're going to handle this uri with the redirector, so setting
// the action to MAIN and clearing the uri data prevents us from
// loading it twice
intent.setAction(Intent.ACTION_MAIN);
intent.setData(null);
passedUri = null;
}
sGeckoThread = new GeckoThread(intent, passedUri, mRestoreSession);
if (!ACTION_DEBUG.equals(intent.getAction()) &&
checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched))
sGeckoThread.start();
@@ -1646,6 +1661,76 @@ abstract public class GeckoApp
mMainLayout.removeView(cameraView);
}
abstract public String getDefaultUAString();
abstract public String getUAStringForHost(String host);
class RedirectorRunnable implements Runnable {
Intent mIntent;
RedirectorRunnable(Intent intent) {
mIntent = intent;
}
public void run() {
HttpURLConnection connection = null;
try {
// this class should only be initialized with an intent with non-null data
URL url = new URL(mIntent.getData().toString());
// data url should have an http scheme
connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("User-Agent", getUAStringForHost(url.getHost()));
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("GET");
connection.connect();
int code = connection.getResponseCode();
if (code >= 300 && code < 400) {
String location = connection.getHeaderField("Location");
Uri data;
if (location != null &&
(data = Uri.parse(location)) != null &&
!"about".equals(data.getScheme()) &&
!"chrome".equals(data.getScheme())) {
mIntent.setData(data);
mLastUri = mLastTitle = location;
} else {
mIntent.putExtra("prefetched", 1);
}
} else {
mIntent.putExtra("prefetched", 1);
}
} catch (IOException ioe) {
Log.i(LOGTAG, "exception trying to pre-fetch redirected url", ioe);
mIntent.putExtra("prefetched", 1);
} catch (Exception e) {
Log.w(LOGTAG, "unexpected exception, passing url directly to Gecko but we should explicitly catch this", e);
mIntent.putExtra("prefetched", 1);
} finally {
if (connection != null)
connection.disconnect();
}
mMainHandler.postAtFrontOfQueue(new Runnable() {
public void run() {
onNewIntent(mIntent);
}
});
}
}
private final String kPrefetchWhiteListArray[] = new String[] {
"t.co",
"bit.ly",
"moz.la",
"aje.me",
"facebook.com",
"goo.gl",
"tinyurl.com"
};
private final CopyOnWriteArrayList<String> kPrefetchWhiteList =
new CopyOnWriteArrayList<String>(kPrefetchWhiteListArray);
private boolean isHostOnPrefetchWhitelist(String host) {
return kPrefetchWhiteList.contains(host);
}
@Override
protected void onNewIntent(Intent intent) {
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onNewIntent");
@@ -1656,10 +1741,25 @@ abstract public class GeckoApp
System.exit(0);
return;
}
if (checkLaunchState(LaunchState.Launched)) {
Uri data = intent.getData();
Bundle bundle = intent.getExtras();
// if the intent has data (i.e. a URI to be opened) and the scheme
// is either http, we'll prefetch it, which means warming
// up the radio and DNS cache by connecting and parsing the redirect
// if the return code is between 300 and 400
if (data != null &&
"http".equals(data.getScheme()) &&
(bundle == null || bundle.getInt("prefetched", 0) != 1) &&
isHostOnPrefetchWhitelist(data.getHost())) {
GeckoAppShell.getHandler().post(new RedirectorRunnable(intent));
return;
}
}
final String action = intent.getAction();
if (ACTION_DEBUG.equals(action) &&
checkAndSetLaunchState(LaunchState.Launching, LaunchState.WaitForDebugger)) {
mMainHandler.postDelayed(new Runnable() {
public void run() {
Log.i(LOGTAG, "Launching from debug intent after 5s wait");
@@ -1677,8 +1777,12 @@ abstract public class GeckoApp
Log.i(LOGTAG, "Intent : ACTION_MAIN");
GeckoAppShell.sendEventToGecko(new GeckoEvent(""));
}
else if (ACTION_LOAD.equals(action)) {
String uri = intent.getDataString();
loadUrl(uri, AwesomeBar.Type.EDIT);
Log.i(LOGTAG,"onNewIntent: " + uri);
}
else if (Intent.ACTION_VIEW.equals(action)) {
prefetchDNS(intent.getData());
String uri = intent.getDataString();
GeckoAppShell.sendEventToGecko(new GeckoEvent(uri));
Log.i(LOGTAG,"onNewIntent: " + uri);
@@ -2334,22 +2438,6 @@ abstract public class GeckoApp
layerController.setLayerClient(mSoftwareLayerClient);
}
private void prefetchDNS(final Uri u) {
// resolving the host here starts up the radio
// and may prime the dns cache. See
// http://www.stevesouders.com/blog/2011/09/21/making-a-mobile-connection/
// for more information.
new Thread(new Runnable() {
public void run() {
try {
Log.i(LOGTAG,"resolving: " + u.getHost());
InetAddress.getByName(u.getHost());
} catch (Exception e) {
// we really don't care.
}
}
}, "DNSPrefetcher Thread").start();
}
}
class PluginLayoutParams extends AbsoluteLayout.LayoutParams

View File

@@ -151,6 +151,8 @@ ifeq (,$(ANDROID_VERSION_CODE))
ANDROID_VERSION_CODE=$(shell $(PYTHON) $(topsrcdir)/toolkit/xre/make-platformini.py --print-buildid | cut -c1-10)
endif
UA_BUILDID=$(shell $(PYTHON) $(topsrcdir)/toolkit/xre/make-platformini.py --print-buildid | cut -c1-8)
DEFINES += \
-DANDROID_PACKAGE_NAME=$(ANDROID_PACKAGE_NAME) \
-DMOZ_APP_DISPLAYNAME="$(MOZ_APP_DISPLAYNAME)" \
@@ -161,6 +163,7 @@ DEFINES += \
-DMOZ_CRASHREPORTER=$(MOZ_CRASHREPORTER) \
-DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
-DMOZILLA_OFFICIAL=$(MOZILLA_OFFICIAL) \
-DUA_BUILDID=$(UA_BUILDID) \
$(NULL)
GARBAGE += \
@@ -554,7 +557,7 @@ $(PP_RES_XML): $(subst res/,$(srcdir)/resources/, $(PP_RES_XML).in)
$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@
AndroidManifest.xml $(FENNEC_PP_JAVA_FILES) $(SYNC_PP_JAVA_FILES) package-name.txt: % : %.in
AndroidManifest.xml $(FENNEC_PP_JAVA_FILES) $(SYNC_PP_JAVA_FILES) package-name.txt: % : %.in Makefile.in
mkdir -p db sync/repositories/android
$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@