From 94bd2e7d8e5de8785ea92e28b032161fc961d8ea Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Wed, 15 Feb 2017 17:12:56 -0500 Subject: [PATCH] Bug 1339160 - 1. Allow GeckoThread to launch without being initialized; r=snorp When GeckoThread is launched without being initialized, it will load all Gecko libs and then wait until it is initialized, before calling the Gecko entry point. This allows us to preload Gecko libs without actually running Gecko. --- .../base/java/org/mozilla/gecko/GeckoApp.java | 4 +- .../java/org/mozilla/gecko/GeckoService.java | 3 +- .../java/org/mozilla/gecko/GeckoProfile.java | 2 - .../java/org/mozilla/gecko/GeckoThread.java | 199 ++++++++++-------- .../process/GeckoServiceChildProcess.java | 2 +- 5 files changed, 111 insertions(+), 99 deletions(-) diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java index e61fa93217b5..5fa16673481e 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java @@ -1220,8 +1220,8 @@ public abstract class GeckoApp final String args = intent.getStringExtra("args"); sAlreadyLoaded = true; - GeckoThread.init(/* profile */ null, args, action, - /* debugging */ ACTION_DEBUG.equals(action)); + GeckoThread.initMainProcess(/* profile */ null, args, + /* debugging */ ACTION_DEBUG.equals(action)); // Speculatively pre-fetch the profile in the background. ThreadUtils.postToBackgroundThread(new Runnable() { diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoService.java b/mobile/android/base/java/org/mozilla/gecko/GeckoService.java index 44c92c062981..05c1f6992c31 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoService.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoService.java @@ -150,7 +150,8 @@ public class GeckoService extends Service { throw new IllegalArgumentException("Intent must specify profile."); } - if (!GeckoThread.initWithProfile(profileName, profileDir != null ? new File(profileDir) : null)) { + if (!GeckoThread.initMainProcessWithProfile( + profileName, profileDir != null ? new File(profileDir) : null)) { Log.w(LOGTAG, "Ignoring due to profile mismatch: " + profileName + " [" + profileDir + ']'); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java index 70140a250cf7..b54df2d76bb4 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java @@ -83,7 +83,6 @@ public final class GeckoProfile { private final String mName; private final File mMozillaDir; - private final Context mApplicationContext; private Object mData; @@ -320,7 +319,6 @@ public final class GeckoProfile { throw new IllegalArgumentException("Custom profile must have a directory"); } - mApplicationContext = context.getApplicationContext(); mName = profileName; mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java index 5d5e5e9a2abb..90b47815b74b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java @@ -125,52 +125,64 @@ public class GeckoThread extends Thread { } }; - private static GeckoThread sGeckoThread; + private static final GeckoThread INSTANCE = new GeckoThread(); @WrapForJNI private static final ClassLoader clsLoader = GeckoThread.class.getClassLoader(); @WrapForJNI private static MessageQueue msgQueue; + private boolean mInitialized; + private String[] mArgs; + + // Main process parameters private GeckoProfile mProfile; + private String mExtraArgs; + private boolean mDebugging; - private final String mArgs; - private final String mAction; - private final boolean mDebugging; - - private String[] mChildProcessArgs; + // Child process parameters 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; - + GeckoThread() { 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; + private boolean isChildProcess() { + return mIPCFileDescriptor != -1; } - 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; + private synchronized boolean init(final GeckoProfile profile, final String[] args, + final String extraArgs, final boolean debugging, + final int crashFd, final int ipcFd) { + ThreadUtils.assertOnUiThread(); + + if (mInitialized) { + return false; } - return false; + + mProfile = profile; + mArgs = args; + mExtraArgs = extraArgs; + mDebugging = debugging; + mCrashFileDescriptor = crashFd; + mIPCFileDescriptor = ipcFd; + + mInitialized = true; + notifyAll(); + return true; + } + + public static boolean initMainProcess(final GeckoProfile profile, final String extraArgs, + final boolean debugging) { + return INSTANCE.init(profile, /* args */ null, extraArgs, debugging, + /* crashFd */ -1, /* ipcFd */ -1); + } + + public static boolean initChildProcess(final String[] args, final int crashFd, + final int ipcFd) { + return INSTANCE.init(/* profile */ null, args, /* extraArgs */ null, + /* debugging */ false, crashFd, ipcFd); } private static boolean canUseProfile(final Context context, final GeckoProfile profile, @@ -203,7 +215,8 @@ public class GeckoThread extends Thread { profileName, profileDir); } - public static boolean initWithProfile(final String profileName, final File profileDir) { + public static boolean initMainProcessWithProfile(final String profileName, + final File profileDir) { if (profileName == null) { throw new IllegalArgumentException("Null profile name"); } @@ -222,14 +235,15 @@ public class GeckoThread extends Thread { } // We haven't initialized yet; okay to initialize now. - return init(GeckoProfile.get(context, profileName, profileDir), - /* args */ null, /* action */ null, /* debugging */ false); + return initMainProcess(GeckoProfile.get(context, profileName, profileDir), + /* args */ null, /* debugging */ false); } public static boolean launch() { ThreadUtils.assertOnUiThread(); + if (checkAndSetState(State.INITIAL, State.LAUNCHED)) { - sGeckoThread.start(); + INSTANCE.start(); return true; } return false; @@ -397,7 +411,7 @@ public class GeckoThread extends Thread { GeckoLoader.loadGeckoLibs(context, resourcePath); } - private static String initGeckoEnvironment() { + private static void initGeckoEnvironment() { final Context context = GeckoAppShell.getApplicationContext(); GeckoLoader.loadMozGlue(context); setState(State.MOZGLUE_READY); @@ -437,48 +451,41 @@ public class GeckoThread extends Thread { } setState(State.LIBS_READY); - return resourcePath; } - private void addCustomProfileArg(String args, ArrayList list) { - // Make sure a profile exists. - final GeckoProfile profile = getProfile(); - profile.getDir(); // call the lazy initializer - - 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); - } - } - - // 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) { - // argv[0] is the program name, which for us is the package name. + private String[] getMainProcessArgs() { final Context context = GeckoAppShell.getApplicationContext(); final ArrayList args = new ArrayList(); + + // argv[0] is the program name, which for us is the package name. args.add(context.getPackageName()); args.add("-greomni"); - args.add(apkPath); + args.add(context.getPackageResourcePath()); - addCustomProfileArg(mArgs, args); + final GeckoProfile profile = getProfile(); + if (profile.isCustomProfile()) { + args.add("-profile"); + args.add(profile.getDir().getAbsolutePath()); + } else { + profile.getDir(); // Make sure the profile dir exists. + args.add("-P"); + args.add(profile.getName()); + } + + if (mExtraArgs != null) { + final StringTokenizer st = new StringTokenizer(mExtraArgs); + while (st.hasMoreTokens()) { + final String token = st.nextToken(); + if ("-P".equals(token) || "-profile".equals(token)) { + // Skip -P and -profile arguments because we added them above. + if (st.hasMoreTokens()) { + st.nextToken(); + } + continue; + } + args.add(token); + } + } // In un-official builds, we want to load Javascript resources fresh // with each build. In official builds, the startup cache is purged by @@ -495,20 +502,20 @@ public class GeckoThread extends Thread { } public static GeckoProfile getActiveProfile() { - if (sGeckoThread == null) { - return null; - } - final GeckoProfile profile = sGeckoThread.mProfile; - if (profile != null) { - return profile; - } - return sGeckoThread.getProfile(); + return INSTANCE.getProfile(); } public synchronized GeckoProfile getProfile() { + if (!mInitialized) { + return null; + } + if (isChildProcess()) { + throw new UnsupportedOperationException( + "Cannot access profile from child process"); + } if (mProfile == null) { final Context context = GeckoAppShell.getApplicationContext(); - mProfile = GeckoProfile.initFromArgs(context, mArgs); + mProfile = GeckoProfile.initFromArgs(context, mExtraArgs); } return mProfile; } @@ -536,20 +543,7 @@ public class GeckoThread extends Thread { }; Looper.myQueue().addIdleHandler(idleHandler); - if (mDebugging) { - try { - Thread.sleep(5 * 1000 /* 5 seconds */); - } catch (final InterruptedException e) { - } - } - - final String[] args; - if (mChildProcessArgs != null) { - initGeckoEnvironment(); - args = mChildProcessArgs; - } else { - args = getGeckoArgs(initGeckoEnvironment()); - } + initGeckoEnvironment(); // This can only happen after the call to initGeckoEnvironment // above, because otherwise the JNI code hasn't been loaded yet. @@ -559,11 +553,30 @@ public class GeckoThread extends Thread { } }); + // Wait until initialization before calling Gecko entry point. + synchronized (this) { + while (!mInitialized) { + try { + wait(); + } catch (final InterruptedException e) { + } + } + } + + final String[] args = isChildProcess() ? mArgs : getMainProcessArgs(); + + if (mDebugging) { + try { + Thread.sleep(5 * 1000 /* 5 seconds */); + } catch (final InterruptedException e) { + } + } + Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - runGecko"); final GeckoAppShell.GeckoInterface gi = GeckoAppShell.getGeckoInterface(); if (gi == null || !gi.isOfficial()) { - Log.i(LOGTAG, "RunGecko - args = " + args); + Log.i(LOGTAG, "RunGecko - args = " + TextUtils.join(" ", args)); } // And go. diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java index 42971a15ae5f..3b98cf71cf42 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java @@ -74,7 +74,7 @@ public class GeckoServiceChildProcess extends Service { public void run() { GeckoAppShell.ensureCrashHandling(); GeckoAppShell.setApplicationContext(getApplicationContext()); - if (GeckoThread.initChildProcess(null, args, crashReporterFd, ipcFd, false)) { + if (GeckoThread.initChildProcess(args, crashReporterFd, ipcFd)) { GeckoThread.launch(); } }