bug 1016747 - add android protocol handler to proxy input streams to Gecko r=snorp
authorBrad Lassey <blassey@mozilla.com>
Wed, 04 Jun 2014 15:28:04 -0400
changeset 193942 3c54b7fe8941da0dc6859029cba1dbeb251be5e3
parent 193941 8fac243930da445352fd91c5d254b7253ba06d54
child 193943 8828261990767364e2b6c54ecd214924fe12f44b
child 194142 ceb42698005edbc985eb6361ec8790766abe1e91
push idunknown
push userunknown
push dateunknown
reviewerssnorp
bugs1016747
milestone32.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 1016747 - add android protocol handler to proxy input streams to Gecko r=snorp
mobile/android/base/GeckoAppShell.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/moz.build
widget/android/nsAndroidProtocolHandler.cpp
widget/android/nsAndroidProtocolHandler.h
widget/android/nsWidgetFactory.cpp
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -11,16 +11,18 @@ import java.io.File;
 import java.io.FileReader;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.net.URLConnection;
+import java.net.MalformedURLException;
 import java.net.Proxy;
 import java.net.URL;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -2715,9 +2717,36 @@ public class GeckoAppShell
     // Don't fail silently, tell the user that we weren't able to share the image
     private static final void showImageShareFailureToast() {
         Toast toast = Toast.makeText(getContext(),
                                      getContext().getResources().getString(R.string.share_image_failed),
                                      Toast.LENGTH_SHORT);
         toast.show();
     }
 
+    @WrapElementForJNI(allowMultithread = true)
+    static InputStream createInputStream(URLConnection connection) throws IOException {
+        return connection.getInputStream();
+    }
+
+    @WrapElementForJNI(allowMultithread = true, narrowChars = true)
+    static URLConnection getConnection(String url) throws MalformedURLException, IOException {
+        String spec;
+        if (url.startsWith("android://")) {
+            spec = url.substring(10);
+        } else {
+            spec = url.substring(8);
+        }
+
+        // if the colon got stripped, put it back
+        int colon = spec.indexOf(':');
+        if (colon == -1 || colon > spec.indexOf('/')) {
+            spec = spec.replaceFirst("/", ":/");
+        }
+        return new URL(spec).openConnection();
+    }
+
+    @WrapElementForJNI(allowMultithread = true, narrowChars = true)
+    static String connectionGetMimeType(URLConnection connection) {
+        return connection.getContentType();
+    }
+
 }
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -201,16 +201,26 @@ AndroidBridge::Init(JNIEnv *jEnv)
 
     jclass eglClass = getClassGlobalRef("com/google/android/gles_jni/EGLSurfaceImpl");
     if (eglClass) {
         jEGLSurfacePointerField = getField("mEGLSurface", "I");
     } else {
         jEGLSurfacePointerField = 0;
     }
 
+    jChannels = getClassGlobalRef("java/nio/channels/Channels");
+    jChannelCreate = jEnv->GetStaticMethodID(jChannels, "newChannel", "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
+
+    jReadableByteChannel = getClassGlobalRef("java/nio/channels/ReadableByteChannel");
+    jByteBufferRead = jEnv->GetMethodID(jReadableByteChannel, "read", "(Ljava/nio/ByteBuffer;)I");
+
+    jInputStream = getClassGlobalRef("java/io/InputStream");
+    jClose = jEnv->GetMethodID(jInputStream, "close", "()V");
+    jAvailable = jEnv->GetMethodID(jInputStream, "available", "()I");
+
     InitAndroidJavaWrappers(jEnv);
 
     // jEnv should NOT be cached here by anything -- the jEnv here
     // is not valid for the real gecko main thread, which is set
     // at SetMainThread time.
 
     return true;
 }
@@ -2088,8 +2098,46 @@ AndroidBridge::RunDelayedTasks()
         mDelayedTaskQueue.RemoveElementAt(0);
         Task* task = nextTask->GetTask();
         delete nextTask;
 
         task->Run();
     }
     return -1;
 }
+
+jobject AndroidBridge::ChannelCreate(jobject stream) {
+    JNIEnv *env = GetJNIForThread();
+    env->PushLocalFrame(1);
+    jobject channel = env->CallStaticObjectMethod(sBridge->jReadableByteChannel, sBridge->jChannelCreate, stream);
+    return env->PopLocalFrame(channel);
+}
+
+void AndroidBridge::InputStreamClose(jobject obj) {
+    JNIEnv *env = GetJNIForThread();
+    AutoLocalJNIFrame jniFrame(env, 1);
+    env->CallVoidMethod(obj, sBridge->jClose);
+}
+
+uint32_t AndroidBridge::InputStreamAvailable(jobject obj) {
+    JNIEnv *env = GetJNIForThread();
+    AutoLocalJNIFrame jniFrame(env, 1);
+    return env->CallIntMethod(obj, sBridge->jAvailable);
+}
+
+nsresult AndroidBridge::InputStreamRead(jobject obj, char *aBuf, uint32_t aCount, uint32_t *aRead) {
+    JNIEnv *env = GetJNIForThread();
+    AutoLocalJNIFrame jniFrame(env, 1);
+    jobject arr =  env->NewDirectByteBuffer(aBuf, aCount);
+    jint read = env->CallIntMethod(obj, sBridge->jByteBufferRead, arr);
+
+    if (env->ExceptionCheck()) {
+        env->ExceptionClear();
+        return NS_ERROR_FAILURE;
+    }
+
+    if (read <= 0) {
+        *aRead = 0;
+        return NS_OK;
+    }
+    *aRead = read;
+    return NS_OK;
+}
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -112,17 +112,16 @@ public:
         return mTask;
     }
 
 private:
     Task* mTask;
     TimeStamp mRunTime;
 };
 
-
 class AndroidBridge MOZ_FINAL : public mozilla::layers::GeckoContentController
 {
 public:
     enum {
         // Values for NotifyIME, in addition to values from the Gecko
         // IMEMessage enum; use negative values here to prevent conflict
         NOTIFY_IME_OPEN_VKB = -2,
         NOTIFY_IME_REPLY_EVENT = -1,
@@ -339,16 +338,23 @@ public:
     static jstring NewJavaString(AutoLocalJNIFrame* frame, const char* string);
     static jstring NewJavaString(AutoLocalJNIFrame* frame, const nsACString& string);
 
     static jclass GetClassGlobalRef(JNIEnv* env, const char* className);
     static jfieldID GetFieldID(JNIEnv* env, jclass jClass, const char* fieldName, const char* fieldType);
     static jfieldID GetStaticFieldID(JNIEnv* env, jclass jClass, const char* fieldName, const char* fieldType);
     static jmethodID GetMethodID(JNIEnv* env, jclass jClass, const char* methodName, const char* methodType);
     static jmethodID GetStaticMethodID(JNIEnv* env, jclass jClass, const char* methodName, const char* methodType);
+
+    static jobject ChannelCreate(jobject);
+
+    static void InputStreamClose(jobject obj);
+    static uint32_t InputStreamAvailable(jobject obj);
+    static nsresult InputStreamRead(jobject obj, char *aBuf, uint32_t aCount, uint32_t *aRead);
+
 protected:
     static StaticRefPtr<AndroidBridge> sBridge;
     nsTArray<nsCOMPtr<nsIMobileMessageCallback> > mSmsRequests;
 
     // the global JavaVM
     JavaVM *mJavaVM;
 
     // the JNIEnv for the main thread
@@ -373,16 +379,26 @@ protected:
     bool mHasNativeBitmapAccess;
     bool mHasNativeWindowAccess;
     bool mHasNativeWindowFallback;
 
     int mAPIVersion;
 
     bool QueueSmsRequest(nsIMobileMessageCallback* aRequest, uint32_t* aRequestIdOut);
 
+    // intput stream
+    jclass jReadableByteChannel;
+    jclass jChannels;
+    jmethodID jChannelCreate;
+    jmethodID jByteBufferRead;
+
+    jclass jInputStream;
+    jmethodID jClose;
+    jmethodID jAvailable;
+
     // other things
     jmethodID jNotifyAppShellReady;
     jmethodID jGetOutstandingDrawEvents;
     jmethodID jPostToJavaThread;
     jmethodID jCreateSurface;
     jmethodID jShowSurface;
     jmethodID jHideSurface;
     jmethodID jDestroySurface;
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -15,30 +15,33 @@ jclass GeckoAppShell::mGeckoAppShellClas
 jmethodID GeckoAppShell::jAcknowledgeEvent = 0;
 jmethodID GeckoAppShell::jAddPluginViewWrapper = 0;
 jmethodID GeckoAppShell::jAlertsProgressListener_OnProgress = 0;
 jmethodID GeckoAppShell::jCancelVibrate = 0;
 jmethodID GeckoAppShell::jCheckURIVisited = 0;
 jmethodID GeckoAppShell::jClearMessageList = 0;
 jmethodID GeckoAppShell::jCloseCamera = 0;
 jmethodID GeckoAppShell::jCloseNotification = 0;
+jmethodID GeckoAppShell::jConnectionGetMimeType = 0;
+jmethodID GeckoAppShell::jCreateInputStream = 0;
 jmethodID GeckoAppShell::jCreateMessageListWrapper = 0;
 jmethodID GeckoAppShell::jCreateShortcut = 0;
 jmethodID GeckoAppShell::jDeleteMessageWrapper = 0;
 jmethodID GeckoAppShell::jDisableBatteryNotifications = 0;
 jmethodID GeckoAppShell::jDisableNetworkNotifications = 0;
 jmethodID GeckoAppShell::jDisableScreenOrientationNotifications = 0;
 jmethodID GeckoAppShell::jDisableSensor = 0;
 jmethodID GeckoAppShell::jEnableBatteryNotifications = 0;
 jmethodID GeckoAppShell::jEnableLocation = 0;
 jmethodID GeckoAppShell::jEnableLocationHighAccuracy = 0;
 jmethodID GeckoAppShell::jEnableNetworkNotifications = 0;
 jmethodID GeckoAppShell::jEnableScreenOrientationNotifications = 0;
 jmethodID GeckoAppShell::jEnableSensor = 0;
 jmethodID GeckoAppShell::jGamepadAdded = 0;
+jmethodID GeckoAppShell::jGetConnection = 0;
 jmethodID GeckoAppShell::jGetContext = 0;
 jmethodID GeckoAppShell::jGetCurrentBatteryInformationWrapper = 0;
 jmethodID GeckoAppShell::jGetCurrentNetworkInformationWrapper = 0;
 jmethodID GeckoAppShell::jGetDensity = 0;
 jmethodID GeckoAppShell::jGetDpiWrapper = 0;
 jmethodID GeckoAppShell::jGetExtensionFromMimeTypeWrapper = 0;
 jmethodID GeckoAppShell::jGetHandlersForMimeTypeWrapper = 0;
 jmethodID GeckoAppShell::jGetHandlersForURLWrapper = 0;
@@ -97,30 +100,33 @@ void GeckoAppShell::InitStubs(JNIEnv *jE
     jAcknowledgeEvent = getStaticMethod("acknowledgeEvent", "()V");
     jAddPluginViewWrapper = getStaticMethod("addPluginView", "(Landroid/view/View;FFFFZ)V");
     jAlertsProgressListener_OnProgress = getStaticMethod("alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jCancelVibrate = getStaticMethod("cancelVibrate", "()V");
     jCheckURIVisited = getStaticMethod("checkUriVisited", "(Ljava/lang/String;)V");
     jClearMessageList = getStaticMethod("clearMessageList", "(I)V");
     jCloseCamera = getStaticMethod("closeCamera", "()V");
     jCloseNotification = getStaticMethod("closeNotification", "(Ljava/lang/String;)V");
+    jConnectionGetMimeType = getStaticMethod("connectionGetMimeType", "(Ljava/net/URLConnection;)Ljava/lang/String;");
+    jCreateInputStream = getStaticMethod("createInputStream", "(Ljava/net/URLConnection;)Ljava/io/InputStream;");
     jCreateMessageListWrapper = getStaticMethod("createMessageList", "(JJ[Ljava/lang/String;IIZI)V");
     jCreateShortcut = getStaticMethod("createShortcut", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     jDeleteMessageWrapper = getStaticMethod("deleteMessage", "(II)V");
     jDisableBatteryNotifications = getStaticMethod("disableBatteryNotifications", "()V");
     jDisableNetworkNotifications = getStaticMethod("disableNetworkNotifications", "()V");
     jDisableScreenOrientationNotifications = getStaticMethod("disableScreenOrientationNotifications", "()V");
     jDisableSensor = getStaticMethod("disableSensor", "(I)V");
     jEnableBatteryNotifications = getStaticMethod("enableBatteryNotifications", "()V");
     jEnableLocation = getStaticMethod("enableLocation", "(Z)V");
     jEnableLocationHighAccuracy = getStaticMethod("enableLocationHighAccuracy", "(Z)V");
     jEnableNetworkNotifications = getStaticMethod("enableNetworkNotifications", "()V");
     jEnableScreenOrientationNotifications = getStaticMethod("enableScreenOrientationNotifications", "()V");
     jEnableSensor = getStaticMethod("enableSensor", "(I)V");
     jGamepadAdded = getStaticMethod("gamepadAdded", "(II)V");
+    jGetConnection = getStaticMethod("getConnection", "(Ljava/lang/String;)Ljava/net/URLConnection;");
     jGetContext = getStaticMethod("getContext", "()Landroid/content/Context;");
     jGetCurrentBatteryInformationWrapper = getStaticMethod("getCurrentBatteryInformation", "()[D");
     jGetCurrentNetworkInformationWrapper = getStaticMethod("getCurrentNetworkInformation", "()[D");
     jGetDensity = getStaticMethod("getDensity", "()F");
     jGetDpiWrapper = getStaticMethod("getDpi", "()I");
     jGetExtensionFromMimeTypeWrapper = getStaticMethod("getExtensionFromMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
     jGetHandlersForMimeTypeWrapper = getStaticMethod("getHandlersForMimeType", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
     jGetHandlersForURLWrapper = getStaticMethod("getHandlersForURL", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
@@ -290,16 +296,42 @@ void GeckoAppShell::CloseNotification(co
 
     jstring j0 = AndroidBridge::NewJavaString(env, a0);
 
     env->CallStaticVoidMethod(mGeckoAppShellClass, jCloseNotification, j0);
     AndroidBridge::HandleUncaughtException(env);
     env->PopLocalFrame(nullptr);
 }
 
+jstring GeckoAppShell::ConnectionGetMimeType(jobject a0) {
+    JNIEnv *env = GetJNIForThread();
+    if (env->PushLocalFrame(2) != 0) {
+        AndroidBridge::HandleUncaughtException(env);
+        MOZ_CRASH("Exception should have caused crash.");
+    }
+
+    jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jConnectionGetMimeType, a0);
+    AndroidBridge::HandleUncaughtException(env);
+    jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
+    return ret;
+}
+
+jobject GeckoAppShell::CreateInputStream(jobject a0) {
+    JNIEnv *env = GetJNIForThread();
+    if (env->PushLocalFrame(2) != 0) {
+        AndroidBridge::HandleUncaughtException(env);
+        MOZ_CRASH("Exception should have caused crash.");
+    }
+
+    jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jCreateInputStream, a0);
+    AndroidBridge::HandleUncaughtException(env);
+    jobject ret = static_cast<jobject>(env->PopLocalFrame(temp));
+    return ret;
+}
+
 void GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6) {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (env->PushLocalFrame(1) != 0) {
         AndroidBridge::HandleUncaughtException(env);
         MOZ_CRASH("Exception should have caused crash.");
     }
 
     jvalue args[7];
@@ -473,16 +505,31 @@ void GeckoAppShell::GamepadAdded(int32_t
         MOZ_CRASH("Exception should have caused crash.");
     }
 
     env->CallStaticVoidMethod(mGeckoAppShellClass, jGamepadAdded, a0, a1);
     AndroidBridge::HandleUncaughtException(env);
     env->PopLocalFrame(nullptr);
 }
 
+jobject GeckoAppShell::GetConnection(const nsACString& a0) {
+    JNIEnv *env = GetJNIForThread();
+    if (env->PushLocalFrame(2) != 0) {
+        AndroidBridge::HandleUncaughtException(env);
+        MOZ_CRASH("Exception should have caused crash.");
+    }
+
+    jstring j0 = AndroidBridge::NewJavaString(env, a0);
+
+    jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetConnection, j0);
+    AndroidBridge::HandleUncaughtException(env);
+    jobject ret = static_cast<jobject>(env->PopLocalFrame(temp));
+    return ret;
+}
+
 jobject GeckoAppShell::GetContext() {
     JNIEnv *env = GetJNIForThread();
     if (env->PushLocalFrame(1) != 0) {
         AndroidBridge::HandleUncaughtException(env);
         MOZ_CRASH("Exception should have caused crash.");
     }
 
     jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetContext);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -22,30 +22,33 @@ public:
     static void AcknowledgeEvent();
     static void AddPluginViewWrapper(jobject a0, jfloat a1, jfloat a2, jfloat a3, jfloat a4, bool a5);
     static void AlertsProgressListener_OnProgress(const nsAString& a0, int64_t a1, int64_t a2, const nsAString& a3);
     static void CancelVibrate();
     static void CheckURIVisited(const nsAString& a0);
     static void ClearMessageList(int32_t a0);
     static void CloseCamera();
     static void CloseNotification(const nsAString& a0);
+    static jstring ConnectionGetMimeType(jobject a0);
+    static jobject CreateInputStream(jobject a0);
     static void CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6);
     static void CreateShortcut(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3);
     static void DeleteMessageWrapper(int32_t a0, int32_t a1);
     static void DisableBatteryNotifications();
     static void DisableNetworkNotifications();
     static void DisableScreenOrientationNotifications();
     static void DisableSensor(int32_t a0);
     static void EnableBatteryNotifications();
     static void EnableLocation(bool a0);
     static void EnableLocationHighAccuracy(bool a0);
     static void EnableNetworkNotifications();
     static void EnableScreenOrientationNotifications();
     static void EnableSensor(int32_t a0);
     static void GamepadAdded(int32_t a0, int32_t a1);
+    static jobject GetConnection(const nsACString& a0);
     static jobject GetContext();
     static jdoubleArray GetCurrentBatteryInformationWrapper();
     static jdoubleArray GetCurrentNetworkInformationWrapper();
     static jfloat GetDensity();
     static int32_t GetDpiWrapper();
     static jstring GetExtensionFromMimeTypeWrapper(const nsAString& a0);
     static jobjectArray GetHandlersForMimeTypeWrapper(const nsAString& a0, const nsAString& a1);
     static jobjectArray GetHandlersForURLWrapper(const nsAString& a0, const nsAString& a1);
@@ -103,30 +106,33 @@ protected:
     static jmethodID jAcknowledgeEvent;
     static jmethodID jAddPluginViewWrapper;
     static jmethodID jAlertsProgressListener_OnProgress;
     static jmethodID jCancelVibrate;
     static jmethodID jCheckURIVisited;
     static jmethodID jClearMessageList;
     static jmethodID jCloseCamera;
     static jmethodID jCloseNotification;
+    static jmethodID jConnectionGetMimeType;
+    static jmethodID jCreateInputStream;
     static jmethodID jCreateMessageListWrapper;
     static jmethodID jCreateShortcut;
     static jmethodID jDeleteMessageWrapper;
     static jmethodID jDisableBatteryNotifications;
     static jmethodID jDisableNetworkNotifications;
     static jmethodID jDisableScreenOrientationNotifications;
     static jmethodID jDisableSensor;
     static jmethodID jEnableBatteryNotifications;
     static jmethodID jEnableLocation;
     static jmethodID jEnableLocationHighAccuracy;
     static jmethodID jEnableNetworkNotifications;
     static jmethodID jEnableScreenOrientationNotifications;
     static jmethodID jEnableSensor;
     static jmethodID jGamepadAdded;
+    static jmethodID jGetConnection;
     static jmethodID jGetContext;
     static jmethodID jGetCurrentBatteryInformationWrapper;
     static jmethodID jGetCurrentNetworkInformationWrapper;
     static jmethodID jGetDensity;
     static jmethodID jGetDpiWrapper;
     static jmethodID jGetExtensionFromMimeTypeWrapper;
     static jmethodID jGetHandlersForMimeTypeWrapper;
     static jmethodID jGetHandlersForURLWrapper;
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -22,16 +22,17 @@ SOURCES += [
     'AndroidDirectTexture.cpp',
     'AndroidGraphicBuffer.cpp',
     'AndroidJavaWrappers.cpp',
     'AndroidJNI.cpp',
     'AndroidJNIWrapper.cpp',
     'GeneratedJNIWrappers.cpp',
     'GfxInfo.cpp',
     'NativeJSContainer.cpp',
+    'nsAndroidProtocolHandler.cpp',
     'nsAppShell.cpp',
     'nsClipboard.cpp',
     'nsDeviceContextAndroid.cpp',
     'nsIdleServiceAndroid.cpp',
     'nsIMEPicker.cpp',
     'nsLookAndFeel.cpp',
     'nsPrintOptionsAndroid.cpp',
     'nsScreenManagerAndroid.cpp',
@@ -46,15 +47,16 @@ LIBRARY_NAME = 'widget_android'
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
     '/dom/system/android',
+    '/netwerk/base/src',
     '/netwerk/cache',
     '/widget/android/android',
     '/widget/shared',
     '/widget/xpwidgets',
 ]
 
 #DEFINES['DEBUG_WIDGETS'] = True
new file mode 100644
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* 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/. */
+
+#include "nsAndroidProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "android/log.h"
+#include "nsBaseChannel.h"
+#include "AndroidBridge.h"
+#include "GeneratedJNIWrappers.h"
+
+using namespace mozilla;
+using namespace mozilla::widget::android;
+
+class AndroidInputStream : public nsIInputStream
+{
+public:
+    AndroidInputStream(jobject connection) {
+        JNIEnv *env = GetJNIForThread();
+        mBridgeInputStream = env->NewGlobalRef(GeckoAppShell::CreateInputStream(connection));
+        mBridgeChannel = env->NewGlobalRef(AndroidBridge::ChannelCreate(mBridgeInputStream));
+    }
+    virtual ~AndroidInputStream() {
+        JNIEnv *env = GetJNIForThread();
+        env->DeleteGlobalRef(mBridgeInputStream);
+        env->DeleteGlobalRef(mBridgeChannel);
+    }
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSIINPUTSTREAM
+
+    private:
+    jobject mBridgeInputStream;
+    jobject mBridgeChannel;
+};
+
+NS_IMPL_ISUPPORTS(AndroidInputStream, nsIInputStream)
+
+NS_IMETHODIMP AndroidInputStream::Close(void) {
+    mozilla::AndroidBridge::InputStreamClose(mBridgeInputStream);
+    return NS_OK;
+}
+
+NS_IMETHODIMP AndroidInputStream::Available(uint64_t *_retval) {
+    *_retval = mozilla::AndroidBridge::InputStreamAvailable(mBridgeInputStream);
+    return NS_OK;
+}
+
+NS_IMETHODIMP AndroidInputStream::Read(char *aBuf, uint32_t aCount, uint32_t *_retval) {
+    return  mozilla::AndroidBridge::InputStreamRead(mBridgeChannel, aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP AndroidInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *_retval) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP AndroidInputStream::IsNonBlocking(bool *_retval) {
+    *_retval = false;
+    return NS_OK;
+}
+
+
+class AndroidChannel : public nsBaseChannel
+{
+private:
+    AndroidChannel(nsIURI *aURI, jobject aConnection) {
+        JNIEnv *env = GetJNIForThread();
+        mConnection = env->NewGlobalRef(aConnection);
+        mURI = aURI;
+        nsCString type;
+        jstring jtype = GeckoAppShell::ConnectionGetMimeType(mConnection);
+        if (jtype)
+            SetContentType(nsJNICString(jtype, env));
+    }
+public:
+    static AndroidChannel* CreateChannel(nsIURI *aURI)  {
+        nsCString spec;
+        aURI->GetSpec(spec);
+        jobject connection = GeckoAppShell::GetConnection(spec);
+        if (!connection)
+            return NULL;
+        return new AndroidChannel(aURI, connection);
+    }
+    ~AndroidChannel() {
+        JNIEnv *env = GetJNIForThread();
+        env->DeleteGlobalRef(mConnection);
+    }
+
+    virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
+                                       nsIChannel** channel) {
+        nsCOMPtr<nsIInputStream> stream = new AndroidInputStream(mConnection);
+        NS_ADDREF(*result = stream);
+        return NS_OK;
+    }
+private:
+    jobject mConnection;
+};
+
+NS_IMPL_ISUPPORTS(nsAndroidProtocolHandler,
+                  nsIProtocolHandler,
+                  nsISupportsWeakReference)
+
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetScheme(nsACString &result)
+{
+    result.AssignLiteral("android");
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetDefaultPort(int32_t *result)
+{
+    *result = -1;        // no port for android: URLs
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+    // don't override anything.
+    *_retval = false;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+    *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE | URI_NORELATIVE | URI_DANGEROUS_TO_LOAD;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewURI(const nsACString &aSpec,
+                                 const char *aCharset,
+                                 nsIURI *aBaseURI,
+                                 nsIURI **result)
+{
+    nsresult rv;
+
+    nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    surl->SetMutable(false);
+
+    NS_ADDREF(*result = url);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewChannel(nsIURI* aURI,
+                                     nsIChannel* *aResult)
+{
+    nsCOMPtr<nsIChannel> channel = AndroidChannel::CreateChannel(aURI);
+    if (!channel)
+        return NS_ERROR_FAILURE;
+    NS_ADDREF(*aResult = channel);
+    return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsAndroidProtocolHandler_h___
+#define nsAndroidProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#define NS_ANDROIDPROTOCOLHANDLER_CID                 \
+{ /* e9cd2b7f-8386-441b-aaf5-0b371846bfd0 */         \
+    0xe9cd2b7f,                                      \
+    0x8386,                                          \
+    0x441b,                                          \
+    {0x0b, 0x37, 0x18, 0x46, 0xbf, 0xd0}             \
+}
+
+class nsAndroidProtocolHandler MOZ_FINAL : public nsIProtocolHandler,
+                                           public nsSupportsWeakReference
+{
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    // nsIProtocolHandler methods:
+    NS_DECL_NSIPROTOCOLHANDLER
+
+    // nsAndroidProtocolHandler methods:
+    nsAndroidProtocolHandler() {}
+    ~nsAndroidProtocolHandler() {}
+};
+
+#endif /* nsAndroidProtocolHandler_h___ */
--- a/widget/android/nsWidgetFactory.cpp
+++ b/widget/android/nsWidgetFactory.cpp
@@ -20,29 +20,31 @@
 #include "nsClipboardHelper.h"
 #include "nsTransferable.h"
 #include "nsPrintOptionsAndroid.h"
 #include "nsPrintSession.h"
 #include "nsDeviceContextAndroid.h"
 #include "nsHTMLFormatConverter.h"
 #include "nsIMEPicker.h"
 #include "nsXULAppAPI.h"
+#include "nsAndroidProtocolHandler.h"
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerAndroid)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceAndroid, nsIdleServiceAndroid::GetInstance)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsAndroid, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecAndroid)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsIMEPicker)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidBridge)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidProtocolHandler)
 
 #include "GfxInfo.h"
 namespace mozilla {
 namespace widget {
 // This constructor should really be shared with all platforms.
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
 }
 }
@@ -57,16 +59,17 @@ NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
 NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
 NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
 NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_IMEPICKER_CID);
 NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
 NS_DEFINE_NAMED_CID(NS_ANDROIDBRIDGE_CID);
+NS_DEFINE_NAMED_CID(NS_ANDROIDPROTOCOLHANDLER_CID);
 
 static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
   { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
   { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
   { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
   { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerAndroidConstructor },
   { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceAndroidConstructor },
   { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
@@ -74,16 +77,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
   { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsAndroidConstructor },
   { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
   { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecAndroidConstructor },
   { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
   { &kNS_IMEPICKER_CID, false, nullptr, nsIMEPickerConstructor },
   { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
   { &kNS_ANDROIDBRIDGE_CID, false, nullptr, nsAndroidBridgeConstructor },
+  { &kNS_ANDROIDPROTOCOLHANDLER_CID, false, nullptr, nsAndroidProtocolHandlerConstructor },
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
   { "@mozilla.org/widgets/window/android;1", &kNS_WINDOW_CID },
   { "@mozilla.org/widgets/child_window/android;1", &kNS_CHILD_CID },
   { "@mozilla.org/widget/appshell/android;1", &kNS_APPSHELL_CID },
   { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
@@ -93,16 +97,17 @@ static const mozilla::Module::ContractID
   { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
   { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
   { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
   { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
   { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
   { "@mozilla.org/imepicker;1", &kNS_IMEPICKER_CID },
   { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
   { "@mozilla.org/android/bridge;1", &kNS_ANDROIDBRIDGE_CID },
+  { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "android", &kNS_ANDROIDPROTOCOLHANDLER_CID },
   { nullptr }
 };
 
 static void
 nsWidgetAndroidModuleDtor()
 {
     nsLookAndFeel::Shutdown();
     nsAppShellShutdown();