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 7c83ceac517c..f795778e5e9b 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 @@ -154,6 +154,8 @@ public class GeckoThread extends Thread { public Map prefs; public String userSerialNumber; + public boolean xpcshell; + public String outFilePath; public int prefsFd; public int prefMapFd; public int ipcFd; @@ -274,17 +276,20 @@ public class GeckoThread extends Thread { // argv[0] is the program name, which for us is the package name. args.add(context.getPackageName()); - args.add("-greomni"); - args.add(context.getPackageResourcePath()); - 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 (!mInitInfo.xpcshell) { + args.add("-greomni"); + args.add(context.getPackageResourcePath()); + + 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 (mInitInfo.args != null) { @@ -435,7 +440,8 @@ public class GeckoThread extends Thread { final boolean isChildProcess = isChildProcess(); GeckoLoader.setupGeckoEnvironment(context, isChildProcess, - context.getFilesDir().getPath(), env, mInitInfo.prefs); + context.getFilesDir().getPath(), env, mInitInfo.prefs, + mInitInfo.xpcshell); initGeckoEnvironment(); @@ -465,7 +471,9 @@ public class GeckoThread extends Thread { mInitInfo.extras.getInt(EXTRA_PREF_MAP_FD, -1), mInitInfo.extras.getInt(EXTRA_IPC_FD, -1), mInitInfo.extras.getInt(EXTRA_CRASH_FD, -1), - mInitInfo.extras.getInt(EXTRA_CRASH_ANNOTATION_FD, -1)); + mInitInfo.extras.getInt(EXTRA_CRASH_ANNOTATION_FD, -1), + isChildProcess ? false : mInitInfo.xpcshell, + isChildProcess ? null : mInitInfo.outFilePath); // And... we're done. final boolean restarting = isState(State.RESTARTING); diff --git 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 index fa0d2e797fe2..bed9c4f9e5ac 100644 --- 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 @@ -118,7 +118,8 @@ public final class GeckoLoader { final boolean isChildProcess, final String profilePath, final Collection env, - final Map prefs) { + final Map prefs, + final boolean xpcshell) { for (final String e : env) { putenv(e); } @@ -156,12 +157,15 @@ public final class GeckoLoader { setupInitialPrefs(prefs); } - // setup the tmp path - final File f = getTmpDir(context); - if (!f.exists()) { - f.mkdirs(); + // Xpcshell tests set up their own temp directory + if (!xpcshell) { + // setup the tmp path + final File f = getTmpDir(context); + if (!f.exists()) { + f.mkdirs(); + } + putenv("TMPDIR=" + f.getPath()); } - putenv("TMPDIR=" + f.getPath()); putenv("LANG=" + Locale.getDefault().toString()); @@ -496,7 +500,7 @@ public final class GeckoLoader { private static native void putenv(String map); // These methods are implemented in mozglue/android/APKOpen.cpp - public static native void nativeRun(String[] args, int prefsFd, int prefMapFd, int ipcFd, int crashFd, int crashAnnotationFd); + public static native void nativeRun(String[] args, int prefsFd, int prefMapFd, int ipcFd, int crashFd, int crashAnnotationFd, boolean xpcshell, String outFilePath); private static native void loadGeckoLibsNative(); private static native void loadSQLiteLibsNative(); private static native void loadNSSLibsNative(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index 3852b79fc30a..9d26f0ec9160 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -49,6 +49,7 @@ import org.mozilla.gecko.util.ThreadUtils; import java.io.File; import java.io.FileNotFoundException; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -289,6 +290,27 @@ public final class GeckoRuntime implements Parcelable { return null; } + private void setArguments(final Context context, final GeckoThread.InitInfo initInfo, + final String[] arguments) { + final List result = new ArrayList<>(arguments.length); + for (final String argument : arguments) { + if ("-xpcshell".equals(argument)) { + // Only debug builds of the test app can run an xpcshell + if (!BuildConfig.DEBUG + || !"org.mozilla.geckoview.test".equals( + context.getApplicationContext().getPackageName())) { + throw new IllegalArgumentException("Only the test app can run -xpcshell."); + } + + initInfo.xpcshell = true; + } else { + result.add(argument); + } + } + + initInfo.args = result.toArray(new String[]{}); + } + /* package */ boolean init(final @NonNull Context context, final @NonNull GeckoRuntimeSettings settings) { if (DEBUG) { Log.d(LOGTAG, "init"); @@ -329,7 +351,10 @@ public final class GeckoRuntime implements Parcelable { GeckoFontScaleListener.getInstance().attachToContext(context, settings); final GeckoThread.InitInfo info = new GeckoThread.InitInfo(); - info.args = settings.getArguments(); + setArguments(context, info, settings.getArguments()); + if (info.xpcshell) { + info.outFilePath = settings.getExtras().getString("out_file"); + } info.extras = settings.getExtras(); info.flags = flags; diff --git a/mozglue/android/APKOpen.cpp b/mozglue/android/APKOpen.cpp index e89d47e571f0..4fd7fe336a41 100644 --- a/mozglue/android/APKOpen.cpp +++ b/mozglue/android/APKOpen.cpp @@ -362,11 +362,10 @@ static void FreeArgv(char** argv, int argc) { } extern "C" APKOPEN_EXPORT void MOZ_JNICALL -Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv* jenv, jclass jc, - jobjectArray jargs, - int prefsFd, int prefMapFd, - int ipcFd, int crashFd, - int crashAnnotationFd) { +Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun( + JNIEnv* jenv, jclass jc, jobjectArray jargs, int prefsFd, int prefMapFd, + int ipcFd, int crashFd, int crashAnnotationFd, bool xpcshell, + jstring outFilePath) { EnsureBaseProfilerInitialized(); int argc = 0; @@ -381,7 +380,15 @@ Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv* jenv, jclass jc, #ifdef MOZ_LINKER ElfLoader::Singleton.ExpectShutdown(false); #endif - gBootstrap->GeckoStart(jenv, argv, argc, sAppData); + const char* outFilePathRaw = nullptr; + if (xpcshell) { + MOZ_ASSERT(outFilePath); + outFilePathRaw = jenv->GetStringUTFChars(outFilePath, nullptr); + } + gBootstrap->GeckoStart(jenv, argv, argc, sAppData, xpcshell, outFilePathRaw); + if (outFilePathRaw) { + jenv->ReleaseStringUTFChars(outFilePath, outFilePathRaw); + } #ifdef MOZ_LINKER ElfLoader::Singleton.ExpectShutdown(true); #endif diff --git a/toolkit/xre/Bootstrap.cpp b/toolkit/xre/Bootstrap.cpp index 9f38cba8230c..11e6200fe3e9 100644 --- a/toolkit/xre/Bootstrap.cpp +++ b/toolkit/xre/Bootstrap.cpp @@ -73,8 +73,9 @@ class BootstrapImpl final : public Bootstrap { #ifdef MOZ_WIDGET_ANDROID virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, - const StaticXREAppData& aAppData) override { - ::GeckoStart(aEnv, argv, argc, aAppData); + const StaticXREAppData& aAppData, + bool xpcshell, const char* outFilePath) override { + ::GeckoStart(aEnv, argv, argc, aAppData, xpcshell, outFilePath); } virtual void XRE_SetAndroidChildFds( diff --git a/toolkit/xre/Bootstrap.h b/toolkit/xre/Bootstrap.h index 6310091da3cd..a17c209e4d01 100644 --- a/toolkit/xre/Bootstrap.h +++ b/toolkit/xre/Bootstrap.h @@ -28,7 +28,8 @@ struct StaticXREAppData; } extern "C" NS_EXPORT void GeckoStart(JNIEnv* aEnv, char** argv, int argc, - const mozilla::StaticXREAppData& aAppData); + const mozilla::StaticXREAppData& aAppData, + bool xpcshell, const char* outFilePath); #endif #if defined(XP_WIN) && defined(MOZ_SANDBOX) @@ -120,7 +121,8 @@ class Bootstrap { #ifdef MOZ_WIDGET_ANDROID virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, - const StaticXREAppData& aAppData) = 0; + const StaticXREAppData& aAppData, bool xpcshell, + const char* outFilePath) = 0; virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, const XRE_AndroidChildFds& fds) = 0; diff --git a/toolkit/xre/nsAndroidStartup.cpp b/toolkit/xre/nsAndroidStartup.cpp index 8df017edc6fb..1512f53249e6 100644 --- a/toolkit/xre/nsAndroidStartup.cpp +++ b/toolkit/xre/nsAndroidStartup.cpp @@ -17,13 +17,15 @@ #include "nsAppRunner.h" #include "nsExceptionHandler.h" #include "mozilla/Bootstrap.h" +#include "XREShellData.h" #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, MOZ_APP_NAME, args) using namespace mozilla; extern "C" NS_EXPORT void GeckoStart(JNIEnv* env, char** argv, int argc, - const StaticXREAppData& aAppData) { + const StaticXREAppData& aAppData, + bool xpcshell, const char* outFilePath) { mozilla::jni::SetGeckoThreadEnv(env); if (!argv) { @@ -31,11 +33,26 @@ extern "C" NS_EXPORT void GeckoStart(JNIEnv* env, char** argv, int argc, return; } - BootstrapConfig config; - config.appData = &aAppData; - config.appDataPath = nullptr; + if (xpcshell) { + XREShellData shellData; + FILE* outFile = fopen(outFilePath, "w"); + if (!outFile) { + LOG("XRE_XPCShellMain cannot open %s", outFilePath); + return; + } + // We redirect both stdout and stderr to the same file, to conform with + // what runxpcshell.py does on Desktop. + shellData.outFile = outFile; + shellData.errFile = outFile; + int result = XRE_XPCShellMain(argc, argv, nullptr, &shellData); + fclose(shellData.outFile); + if (result) LOG("XRE_XPCShellMain returned %d", result); + } else { + BootstrapConfig config; + config.appData = &aAppData; + config.appDataPath = nullptr; - int result = XRE_main(argc, argv, config); - - if (result) LOG("XRE_main returned %d", result); + int result = XRE_main(argc, argv, config); + if (result) LOG("XRE_main returned %d", result); + } }