Bug 1314466 - part 5, Add service process manager r=snorp
authorRandall Barker <rbarker@mozilla.com>
Wed, 05 Oct 2016 16:46:53 -0700
changeset 322546 a8198e0db7b505acc7d83be4684d5b7fd06c53e4
parent 322545 524f33fa66c6dc47c084c68aa777ff09ea0c804b
child 322547 4ac9b7e9b8a21d9fdb5d2ec7d1fb223565d549e2
push id30952
push usercbook@mozilla.com
push dateTue, 15 Nov 2016 11:27:04 +0000
treeherdermozilla-central@e16d1a881481 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1314466
milestone52.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 1314466 - part 5, Add service process manager r=snorp
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
mozglue/android/APKOpen.cpp
toolkit/xre/nsAndroidStartup.cpp
toolkit/xre/nsEmbedFunctions.cpp
xpcom/build/nsXULAppAPI.h
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
@@ -0,0 +1,12 @@
+/* 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.process;
+import android.os.ParcelFileDescriptor;
+
+interface IChildProcess {
+    void stop();
+    int getPid();
+    void start(in String[] args, in ParcelFileDescriptor crashReporterPfd, in ParcelFileDescriptor ipcPfd);
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -29,16 +29,18 @@ import android.annotation.SuppressLint;
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.permissions.Permissions;
+import org.mozilla.gecko.process.GeckoProcessManager;
+import org.mozilla.gecko.process.GeckoServiceChildProcess;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoRequest;
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ProxySelector;
@@ -79,16 +81,17 @@ import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Looper;
+import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -2269,9 +2272,14 @@ public class GeckoAppShell
         if (sScreenSize == null) {
             final WindowManager wm = (WindowManager)
                     getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
             final Display disp = wm.getDefaultDisplay();
             sScreenSize = new Rect(0, 0, disp.getWidth(), disp.getHeight());
         }
         return sScreenSize;
     }
+
+    @WrapForJNI
+    private static int startGeckoServiceChildProcess(String type, String[] args, int crashFd, int ipcFd) {
+        return GeckoProcessManager.getInstance().start(type, args, crashFd, ipcFd);
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -16,27 +16,29 @@ import org.json.JSONObject;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.StringTokenizer;
 
 public class GeckoThread extends Thread {
     private static final String LOGTAG = "GeckoThread";
 
     public enum State {
         // After being loaded by class loader.
         @WrapForJNI INITIAL(0),
         // After launching Gecko thread
@@ -129,34 +131,51 @@ public class GeckoThread extends Thread 
     private static MessageQueue msgQueue;
 
     private GeckoProfile mProfile;
 
     private final String mArgs;
     private final String mAction;
     private final boolean mDebugging;
 
+    private String[] mChildProcessArgs;
+    private int mCrashFileDescriptor;
+    private int mIPCFileDescriptor;
+
     GeckoThread(GeckoProfile profile, String args, String action, boolean debugging) {
         mProfile = profile;
         mArgs = args;
         mAction = action;
         mDebugging = debugging;
+        mChildProcessArgs = null;
+        mCrashFileDescriptor = -1;
+        mIPCFileDescriptor = -1;
 
         setName("Gecko");
     }
 
     public static boolean init(GeckoProfile profile, String args, String action, boolean debugging) {
         ThreadUtils.assertOnUiThread();
         if (isState(State.INITIAL) && sGeckoThread == null) {
             sGeckoThread = new GeckoThread(profile, args, action, debugging);
             return true;
         }
         return false;
     }
 
+    public static boolean initChildProcess(GeckoProfile profile, String[] args, int crashFd, int ipcFd, boolean debugging) {
+        if (init(profile, null, null, debugging)) {
+            sGeckoThread.mChildProcessArgs = args;
+            sGeckoThread.mCrashFileDescriptor = crashFd;
+            sGeckoThread.mIPCFileDescriptor = ipcFd;
+            return true;
+        }
+        return false;
+    }
+
     private static boolean canUseProfile(final Context context, final GeckoProfile profile,
                                          final String profileName, final File profileDir) {
         if (profileDir != null && !profileDir.isDirectory()) {
             return false;
         }
 
         if (profile == null) {
             // We haven't initialized; any profile is okay as long as we follow the guest mode setting.
@@ -398,57 +417,67 @@ public class GeckoThread extends Thread 
         GeckoLoader.loadSQLiteLibs(context, resourcePath);
         GeckoLoader.loadNSSLibs(context, resourcePath);
         GeckoLoader.loadGeckoLibs(context, resourcePath);
         setState(State.LIBS_READY);
 
         return resourcePath;
     }
 
-    private String addCustomProfileArg(String args) {
-        String profileArg = "";
-
+    private void addCustomProfileArg(String args, ArrayList<String> list) {
         // Make sure a profile exists.
         final GeckoProfile profile = getProfile();
         profile.getDir(); // call the lazy initializer
 
-        // If args don't include the profile, make sure it's included.
-        if (args == null || !args.matches(".*\\B-(P|profile)\\s+\\S+.*")) {
-            if (profile.isCustomProfile()) {
-                profileArg = " -profile " + profile.getDir().getAbsolutePath();
-            } else {
-                profileArg = " -P " + profile.getName();
+        boolean needsProfile = true;
+
+        if (args != null) {
+            StringTokenizer st = new StringTokenizer(args);
+            while (st.hasMoreTokens()) {
+                String token = st.nextToken();
+                if ("-P".equals(token) || "-profile".equals(token)) {
+                    needsProfile = false;
+                }
+                list.add(token);
             }
         }
 
-        return (args != null ? args : "") + profileArg;
+        // If args don't include the profile, make sure it's included.
+        if (args == null || needsProfile) {
+            if (profile.isCustomProfile()) {
+                list.add("-profile");
+                list.add(profile.getDir().getAbsolutePath());
+            } else {
+                list.add("-P");
+                list.add(profile.getName());
+            }
+        }
     }
 
-    private String getGeckoArgs(final String apkPath) {
+    private String[] getGeckoArgs(final String apkPath) {
         // argv[0] is the program name, which for us is the package name.
         final Context context = GeckoAppShell.getApplicationContext();
-        final StringBuilder args = new StringBuilder(context.getPackageName());
-        args.append(" -greomni ").append(apkPath);
+        final ArrayList<String> args = new ArrayList<String>();
+        args.add(context.getPackageName());
+        args.add("-greomni");
+        args.add(apkPath);
 
-        final String userArgs = addCustomProfileArg(mArgs);
-        if (userArgs != null) {
-            args.append(' ').append(userArgs);
-        }
+        addCustomProfileArg(mArgs, args);
 
         // In un-official builds, we want to load Javascript resources fresh
         // with each build.  In official builds, the startup cache is purged by
         // the buildid mechanism, but most un-official builds don't bump the
         // buildid, so we purge here instead.
         if (!AppConstants.MOZILLA_OFFICIAL) {
             Log.w(LOGTAG, "STARTUP PERFORMANCE WARNING: un-official build: purging the " +
                           "startup (JavaScript) caches.");
-            args.append(" -purgecaches");
+            args.add("-purgecaches");
         }
 
-        return args.toString();
+        return args.toArray(new String[args.size()]);
     }
 
     public static GeckoProfile getActiveProfile() {
         if (sGeckoThread == null) {
             return null;
         }
         final GeckoProfile profile = sGeckoThread.mProfile;
         if (profile != null) {
@@ -488,34 +517,41 @@ public class GeckoThread extends Thread 
 
         if (mDebugging) {
             try {
                 Thread.sleep(5 * 1000 /* 5 seconds */);
             } catch (final InterruptedException e) {
             }
         }
 
-        final String args = getGeckoArgs(initGeckoEnvironment());
+        final String[] args;
+        if (mChildProcessArgs != null) {
+            initGeckoEnvironment();
+            args = mChildProcessArgs;
+        } else {
+            args = getGeckoArgs(initGeckoEnvironment());
+        }
 
         // This can only happen after the call to initGeckoEnvironment
         // above, because otherwise the JNI code hasn't been loaded yet.
         ThreadUtils.postToUiThread(new Runnable() {
             @Override public void run() {
                 registerUiThread();
             }
         });
 
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - runGecko");
 
         if (!AppConstants.MOZILLA_OFFICIAL) {
-            Log.i(LOGTAG, "RunGecko - args = " + args);
+            String msg = new String("RunGecko - args =" + TextUtils.join(" ", args));
+            Log.i(LOGTAG, msg);
         }
 
         // And go.
-        GeckoLoader.nativeRun(args);
+        GeckoLoader.nativeRun(args, mCrashFileDescriptor, mIPCFileDescriptor);
 
         // And... we're done.
         setState(State.EXITED);
 
         try {
             final JSONObject msg = new JSONObject();
             msg.put("type", "Gecko:Exited");
             EventDispatcher.getInstance().dispatchEvent(msg, null);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -11,18 +11,20 @@ import java.io.InputStream;
 import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.text.NumberFormat;
 import java.util.Locale;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import android.content.Context;
+import android.content.Intent;
 import android.os.Build;
 import android.os.Environment;
+import java.util.ArrayList;
 import android.util.Log;
 
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants;
 
 public final class GeckoLoader {
     private static final String LOGTAG = "GeckoLoader";
@@ -30,16 +32,17 @@ public final class GeckoLoader {
     private static volatile SafeIntent sIntent;
     private static File sCacheFile;
     private static File sGREDir;
 
     /* Synchronized on GeckoLoader.class. */
     private static boolean sSQLiteLibsLoaded;
     private static boolean sNSSLibsLoaded;
     private static boolean sMozGlueLoaded;
+    private static String[] sEnvList;
 
     private GeckoLoader() {
         // prevent instantiation
     }
 
     public static File getCacheDir(Context context) {
         if (sCacheFile == null) {
             sCacheFile = context.getCacheDir();
@@ -118,28 +121,41 @@ public final class GeckoLoader {
         }
         return tmpDir;
     }
 
     public static void setLastIntent(SafeIntent intent) {
         sIntent = intent;
     }
 
+    public static void addEnvironmentToIntent(Intent intent) {
+        if (sEnvList != null) {
+            for (int ix = 0; ix < sEnvList.length; ix++) {
+                intent.putExtra("env" + ix, sEnvList[ix]);
+            }
+        }
+    }
+
     public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) {
         // if we have an intent (we're being launched by an activity)
         // read in any environmental variables from it here
         final SafeIntent intent = sIntent;
         if (intent != null) {
+            final ArrayList<String> envList = new ArrayList<String>();
             String env = intent.getStringExtra("env0");
             Log.d(LOGTAG, "Gecko environment env0: " + env);
             for (int c = 1; env != null; c++) {
+                envList.add(env);
                 putenv(env);
                 env = intent.getStringExtra("env" + c);
                 Log.d(LOGTAG, "env" + c + ": " + env);
             }
+            if (envList.size() > 0) {
+              sEnvList = envList.toArray(new String[envList.size()]);
+            }
         }
 
         putenv("MOZ_ANDROID_PACKAGE_NAME=" + context.getPackageName());
 
         setupPluginEnvironment(context, pluginDirs);
         setupDownloadEnvironment(context);
 
         // profile home path
@@ -543,14 +559,14 @@ public final class GeckoLoader {
             uncaughtHandler.uncaughtException(thread, new AbortException(msg));
         }
     }
 
     // These methods are implemented in mozglue/android/nsGeckoUtils.cpp
     private static native void putenv(String map);
 
     // These methods are implemented in mozglue/android/APKOpen.cpp
-    public static native void nativeRun(String args);
+    public static native void nativeRun(String[] args, int crashFd, int ipcFd);
     private static native void loadGeckoLibsNative(String apkName);
     private static native void loadSQLiteLibsNative(String apkName);
     private static native void loadNSSLibsNative(String apkName);
     private static native void extractGeckoLibsNative(String apkName);
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
@@ -0,0 +1,175 @@
+/* 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.process;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.support.v4.util.SimpleArrayMap;
+import android.view.Surface;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import java.util.Map;
+
+public final class GeckoProcessManager {
+    private static final String LOGTAG = "GeckoProcessManager";
+    private static final GeckoProcessManager INSTANCE = new GeckoProcessManager();
+
+    public static GeckoProcessManager getInstance() {
+        return INSTANCE;
+    }
+
+    private static final class ChildConnection implements ServiceConnection, IBinder.DeathRecipient {
+        public final String mType;
+        private boolean mWait = false;
+        public IChildProcess mChild = null;
+        public int mPid = 0;
+        public ChildConnection(String type) {
+            mType = type;
+        }
+
+        void prepareToWait() {
+            mWait = true;
+        }
+
+        void waitForChild() {
+            ThreadUtils.assertNotOnUiThread();
+            synchronized(this) {
+                if (mWait) {
+                    try {
+                        this.wait(5000); // 5 seconds
+                    } catch (final InterruptedException e) {
+                        Log.e(LOGTAG, "Interrupted waiting for child service to start", e);
+                    }
+                }
+            }
+        }
+
+        void clearWait() {
+            mWait = false;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                service.linkToDeath(this, 0);
+            } catch (final RemoteException e) {
+                Log.e(LOGTAG, "Failed to link ChildConnection to death of service.", e);
+            }
+            mChild = IChildProcess.Stub.asInterface(service);
+            try {
+                mPid = mChild.getPid();
+            } catch (final RemoteException e) {
+                Log.e(LOGTAG, "Failed to get child " + mType + " process PID. Process may have died.", e);
+            }
+            synchronized(this) {
+                if (mWait) {
+                    mWait = false;
+                    this.notifyAll();
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (mChild != null) {
+                synchronized(INSTANCE.mConnections) {
+                    INSTANCE.mConnections.remove(mType);
+                }
+                mChild.asBinder().unlinkToDeath(this, 0);
+                mChild = null;
+            }
+            synchronized(this) {
+                if (mWait) {
+                    mWait = false;
+                    this.notifyAll();
+                }
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            Log.e(LOGTAG,"Binder died. Attempt to unbind service: " + mType + " " + mPid);
+            try {
+                GeckoAppShell.getApplicationContext().unbindService(this);
+            } catch (final java.lang.IllegalArgumentException e) {
+                Log.e(LOGTAG,"Looks like connection was already unbound", e);
+            }
+        }
+    }
+
+    SimpleArrayMap<String, ChildConnection> mConnections;
+
+    private GeckoProcessManager() {
+        mConnections = new SimpleArrayMap<String, ChildConnection>();
+    }
+
+    public int start(String type, String[] args, int crashFd, int ipcFd) {
+        ChildConnection connection = null;
+        synchronized(mConnections) {
+            connection = mConnections.get(type);
+        }
+        if (connection != null) {
+            Log.w(LOGTAG, "Attempting to start a child process service that is already running. Attempting to kill existing process first");
+            connection.prepareToWait();
+            try {
+                connection.mChild.stop();
+                connection.waitForChild();
+            } catch (final RemoteException e) {
+                connection.clearWait();
+            }
+        }
+
+        try {
+            connection = new ChildConnection(type);
+            Intent intent = new Intent();
+            intent.setClassName(GeckoAppShell.getApplicationContext(),
+                                "org.mozilla.gecko.process.GeckoServiceChildProcess$" + type);
+            GeckoLoader.addEnvironmentToIntent(intent);
+            connection.prepareToWait();
+            GeckoAppShell.getApplicationContext().bindService(intent, connection, Context.BIND_AUTO_CREATE);
+            connection.waitForChild();
+            if (connection.mChild == null) {
+                // FAILED TO CONNECT.
+                Log.e(LOGTAG, "Failed to connect to child process of '" + type + "'");
+                GeckoAppShell.getApplicationContext().unbindService(connection);
+                return 0;
+            }
+            ParcelFileDescriptor crashPfd = null;
+            if (crashFd >= 0) {
+                crashPfd = ParcelFileDescriptor.fromFd(crashFd);
+            }
+            ParcelFileDescriptor ipcPfd = ParcelFileDescriptor.fromFd(ipcFd);
+            connection.mChild.start(args, crashPfd, ipcPfd);
+            if (crashPfd != null) {
+                crashPfd.close();
+            }
+            ipcPfd.close();
+            synchronized(mConnections) {
+                mConnections.put(type, connection);
+            }
+            return connection.mPid;
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Unable to create child process for: '" + type + "'. Remote Exception:", e);
+        } catch (final IOException e) {
+            Log.e(LOGTAG, "Unable to create child process for: '" + type + "'. Error creating ParcelFileDescriptor needed to create intent:", e);
+        }
+
+        return 0;
+    }
+
+} // GeckoProcessManager
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
@@ -0,0 +1,101 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.process;
+
+import org.mozilla.gecko.annotation.JNITarget;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.mozglue.SafeIntent;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.util.Log;
+
+public class GeckoServiceChildProcess extends Service {
+
+    static private String LOGTAG = "GeckoServiceChildProcess";
+
+    private boolean serviceStarted;
+
+    static private void stop() {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Process.killProcess(Process.myPid());;
+            }
+        });
+    }
+
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    public int onStartCommand(final Intent intent, final int flags, final int startId) {
+        return Service.START_STICKY;
+    }
+
+    private Binder mBinder = new IChildProcess.Stub() {
+        @Override
+        public void stop() {
+            GeckoServiceChildProcess.stop();
+        }
+
+        @Override
+        public int getPid() {
+            return Process.myPid();
+        }
+
+        @Override
+        public void start(final String[] args,
+                          final ParcelFileDescriptor crashReporterPfd,
+                          final ParcelFileDescriptor ipcPfd) {
+            if (serviceStarted) {
+                Log.e(LOGTAG, "Attempting to start a service that has already been started.");
+                return;
+            }
+            serviceStarted = true;
+            final int crashReporterFd = crashReporterPfd != null ? crashReporterPfd.detachFd() : -1;
+            final int ipcFd = ipcPfd != null ? ipcPfd.detachFd() : -1;
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    GeckoAppShell.ensureCrashHandling();
+                    GeckoAppShell.setApplicationContext(getApplicationContext());
+                    if (GeckoThread.initChildProcess(null, args, crashReporterFd, ipcFd, false)) {
+                        GeckoThread.launch();
+                    }
+                }
+            });
+        }
+    };
+
+    public IBinder onBind(final Intent intent) {
+        GeckoLoader.setLastIntent(new SafeIntent(intent));
+        return mBinder;
+    }
+
+    public boolean onUnbind(Intent intent) {
+        Log.i(LOGTAG, "Service has been unbound. Stopping.");
+        stop();
+        return false;
+    }
+
+    @JNITarget
+    static public final class geckomediaplugin extends GeckoServiceChildProcess {}
+
+    @JNITarget
+    static public final class tab extends GeckoServiceChildProcess {}
+}
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -400,40 +400,96 @@ Java_org_mozilla_gecko_mozglue_GeckoLoad
   mozglueresult rv = loadNSSLibs(str);
   if (rv != SUCCESS) {
     JNI_Throw(jenv, "java/lang/Exception", "Error loading nss libraries");
   }
   __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load nss done\n");
   jenv->ReleaseStringUTFChars(jApkName, str);
 }
 
-typedef void (*GeckoStart_t)(JNIEnv*, char*, const nsXREAppData*);
+static char**
+CreateArgvFromObjectArray(JNIEnv *jenv, jobjectArray jargs, int* length)
+{
+  size_t stringCount = jenv->GetArrayLength(jargs);
+
+  if (length) {
+    *length = stringCount;
+  }
+
+  if (!stringCount) {
+    return nullptr;
+  }
+
+  char** argv = new char*[stringCount + 1];
+
+  argv[stringCount] = nullptr;
+
+  for (size_t ix = 0; ix < stringCount; ix++) {
+    jstring string = (jstring) (jenv->GetObjectArrayElement(jargs, ix));
+    const char* rawString = jenv->GetStringUTFChars(string, nullptr);
+    const int strLength = jenv->GetStringUTFLength(string);
+    argv[ix] = strndup(rawString, strLength);
+    jenv->ReleaseStringUTFChars(string, rawString);
+    jenv->DeleteLocalRef(string);
+  }
+
+  return argv;
+}
+
+static void
+FreeArgv(char** argv, int argc)
+{
+  for (int ix=0; ix < argc; ix++) {
+    // String was allocated with strndup, so need to use free to deallocate.
+    free(argv[ix]);
+  }
+  delete[](argv);
+}
+
+typedef void (*GeckoStart_t)(JNIEnv*, char**, int, const nsXREAppData*);
+typedef int GeckoProcessType;
 
 extern "C" NS_EXPORT void MOZ_JNICALL
-Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jstring jargs)
+Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jobjectArray jargs, int crashFd, int ipcFd)
 {
-  GeckoStart_t GeckoStart;
-  xul_dlsym("GeckoStart", &GeckoStart);
-  if (GeckoStart == nullptr)
-    return;
-  // XXX: java doesn't give us true UTF8, we should figure out something
-  // better to do here
-  int len = jenv->GetStringUTFLength(jargs);
-  // GeckoStart needs to write in the args buffer, so we need a copy.
-  char *args = (char *) malloc(len + 1);
-  jenv->GetStringUTFRegion(jargs, 0, len, args);
-  args[len] = '\0';
-  ElfLoader::Singleton.ExpectShutdown(false);
-  GeckoStart(jenv, args, &sAppData);
-  ElfLoader::Singleton.ExpectShutdown(true);
-  free(args);
+  int argc = 0;
+  char** argv = CreateArgvFromObjectArray(jenv, jargs, &argc);
+
+  if (ipcFd < 0) {
+    GeckoStart_t GeckoStart;
+    xul_dlsym("GeckoStart", &GeckoStart);
+
+    if (GeckoStart == nullptr) {
+      FreeArgv(argv, argc);
+      return;
+    }
+
+    ElfLoader::Singleton.ExpectShutdown(false);
+    GeckoStart(jenv, argv, argc, &sAppData);
+    ElfLoader::Singleton.ExpectShutdown(true);
+  } else {
+    void (*fXRE_SetAndroidChildFds)(int, int);
+    xul_dlsym("XRE_SetAndroidChildFds", &fXRE_SetAndroidChildFds);
+
+    void (*fXRE_SetProcessType)(char*);
+    xul_dlsym("XRE_SetProcessType", &fXRE_SetProcessType);
+
+    mozglueresult (*fXRE_InitChildProcess)(int, char**, void*);
+    xul_dlsym("XRE_InitChildProcess", &fXRE_InitChildProcess);
+
+    fXRE_SetAndroidChildFds(crashFd, ipcFd);
+    fXRE_SetProcessType(argv[argc - 1]);
+
+    XREChildData childData;
+    fXRE_InitChildProcess(argc - 1, argv, &childData);
+  }
+
+  FreeArgv(argv, argc);
 }
 
-typedef int GeckoProcessType;
-
 extern "C" NS_EXPORT mozglueresult
 ChildProcessInit(int argc, char* argv[])
 {
   int i;
   for (i = 0; i < (argc - 1); i++) {
     if (strcmp(argv[i], "-greomni"))
       continue;
 
--- a/toolkit/xre/nsAndroidStartup.cpp
+++ b/toolkit/xre/nsAndroidStartup.cpp
@@ -17,39 +17,31 @@
 #include "nsIFile.h"
 #include "nsAppRunner.h"
 #include "APKOpen.h"
 #include "nsExceptionHandler.h"
 
 #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, MOZ_APP_NAME, args)
 
 extern "C" NS_EXPORT void
-GeckoStart(JNIEnv* env, char* data, const nsXREAppData* appData)
+GeckoStart(JNIEnv* env, char** argv, int argc, const nsXREAppData* appData)
 {
     mozilla::jni::SetGeckoThreadEnv(env);
 
 #ifdef MOZ_CRASHREPORTER
     const struct mapping_info *info = getLibraryMapping();
     while (info->name) {
       CrashReporter::AddLibraryMapping(info->name, info->base,
                                        info->len, info->offset);
       info++;
     }
 #endif
 
-    if (!data) {
+    if (!argv) {
         LOG("Failed to get arguments for GeckoStart\n");
         return;
     }
 
-    nsTArray<char *> targs;
-    char *arg = strtok(data, " ");
-    while (arg) {
-        targs.AppendElement(arg);
-        arg = strtok(nullptr, " ");
-    }
-    targs.AppendElement(static_cast<char *>(nullptr));
-
-    int result = XRE_main(targs.Length() - 1, targs.Elements(), appData, 0);
+    int result = XRE_main(argc, argv, appData, 0);
 
     if (result)
         LOG("XRE_main returned %d", result);
 }
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -45,16 +45,19 @@
 #endif
 #include "nsX11ErrorHandler.h"
 #include "nsGDKErrorHandler.h"
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 #include "base/process_util.h"
 #include "chrome/common/child_process.h"
+#if defined(MOZ_WIDGET_ANDROID)
+#include "chrome/common/ipc_channel.h"
+#endif //  defined(MOZ_WIDGET_ANDROID)
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/IOThreadChild.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "ScopedXREEmbed.h"
 
 #include "mozilla/plugins/PluginProcessChild.h"
@@ -217,16 +220,27 @@ XRE_ChildProcessTypeToString(GeckoProces
 }
 
 namespace mozilla {
 namespace startup {
 GeckoProcessType sChildProcessType = GeckoProcessType_Default;
 } // namespace startup
 } // namespace mozilla
 
+#if defined(MOZ_WIDGET_ANDROID)
+void
+XRE_SetAndroidChildFds (int crashFd, int ipcFd)
+{
+#if defined(MOZ_CRASHREPORTER)
+  CrashReporter::SetNotificationPipeForChild(crashFd);
+#endif // defined(MOZ_CRASHREPORTER)
+  IPC::Channel::SetClientChannelFd(ipcFd);
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
+
 void
 XRE_SetProcessType(const char* aProcessTypeString)
 {
   static bool called = false;
   if (called) {
     MOZ_CRASH();
   }
   called = true;
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -418,16 +418,21 @@ static const char* const kGeckoProcessTy
 
 static_assert(MOZ_ARRAY_LENGTH(kGeckoProcessTypeString) ==
               GeckoProcessType_End,
               "Array length mismatch");
 
 XRE_API(const char*,
         XRE_ChildProcessTypeToString, (GeckoProcessType aProcessType))
 
+#if defined(MOZ_WIDGET_ANDROID)
+XRE_API(void,
+        XRE_SetAndroidChildFds, (int crashFd, int ipcFd))
+#endif // defined(MOZ_WIDGET_ANDROID)
+
 XRE_API(void,
         XRE_SetProcessType, (const char* aProcessTypeString))
 
 #if defined(MOZ_CRASHREPORTER)
 // Used in the "master" parent process hosting the crash server
 XRE_API(bool,
         XRE_TakeMinidumpForChild, (uint32_t aChildPid, nsIFile** aDump,
                                    uint32_t* aSequence))