tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeckoThread.java (27807B)


      1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 package org.mozilla.gecko;
      7 
      8 import android.content.Context;
      9 import android.content.res.Configuration;
     10 import android.content.res.Resources;
     11 import android.os.Bundle;
     12 import android.os.Handler;
     13 import android.os.Looper;
     14 import android.os.Message;
     15 import android.os.MessageQueue;
     16 import android.os.Process;
     17 import android.os.SystemClock;
     18 import android.text.TextUtils;
     19 import android.util.Log;
     20 import androidx.annotation.NonNull;
     21 import androidx.annotation.Nullable;
     22 import androidx.annotation.UiThread;
     23 import java.util.ArrayList;
     24 import java.util.Arrays;
     25 import java.util.LinkedList;
     26 import java.util.List;
     27 import java.util.Locale;
     28 import java.util.Map;
     29 import java.util.StringTokenizer;
     30 import org.mozilla.gecko.annotation.RobocopTarget;
     31 import org.mozilla.gecko.annotation.WrapForJNI;
     32 import org.mozilla.gecko.mozglue.GeckoLoader;
     33 import org.mozilla.gecko.process.GeckoProcessManager;
     34 import org.mozilla.gecko.process.GeckoProcessType;
     35 import org.mozilla.gecko.process.MemoryController;
     36 import org.mozilla.gecko.util.GeckoBundle;
     37 import org.mozilla.gecko.util.ThreadUtils;
     38 import org.mozilla.geckoview.BuildConfig;
     39 import org.mozilla.geckoview.GeckoResult;
     40 
     41 public class GeckoThread extends Thread {
     42  private static final String LOGTAG = "GeckoThread";
     43 
     44  public enum State implements NativeQueue.State {
     45    // After being loaded by class loader.
     46    @WrapForJNI
     47    INITIAL(0),
     48    // After launching Gecko thread
     49    @WrapForJNI
     50    LAUNCHED(1),
     51    // After loading the mozglue library.
     52    @WrapForJNI
     53    MOZGLUE_READY(2),
     54    // After loading the libxul library.
     55    @WrapForJNI
     56    LIBS_READY(3),
     57    // After initializing nsAppShell and JNI calls.
     58    @WrapForJNI
     59    JNI_READY(4),
     60    // After initializing profile and prefs.
     61    @WrapForJNI
     62    PROFILE_READY(5),
     63    // After initializing frontend JS
     64    @WrapForJNI
     65    RUNNING(6),
     66    // After granting request to shutdown
     67    @WrapForJNI
     68    EXITING(3),
     69    // After granting request to restart
     70    @WrapForJNI
     71    RESTARTING(3),
     72    // After failed lib extraction due to corrupted APK
     73    CORRUPT_APK(2),
     74    // After exiting GeckoThread (corresponding to "Gecko:Exited" event)
     75    @WrapForJNI
     76    EXITED(0);
     77 
     78    /* The rank is an arbitrary value reflecting the amount of components or features
     79     * that are available for use. During startup and up to the RUNNING state, the
     80     * rank value increases because more components are initialized and available for
     81     * use. During shutdown and up to the EXITED state, the rank value decreases as
     82     * components are shut down and become unavailable. EXITING has the same rank as
     83     * LIBS_READY because both states have a similar amount of components available.
     84     */
     85    private final int mRank;
     86 
     87    State(final int rank) {
     88      mRank = rank;
     89    }
     90 
     91    @Override
     92    public boolean is(final NativeQueue.State other) {
     93      return this == other;
     94    }
     95 
     96    @Override
     97    public boolean isAtLeast(final NativeQueue.State other) {
     98      if (other instanceof State) {
     99        return mRank >= ((State) other).mRank;
    100      }
    101      return false;
    102    }
    103 
    104    @Override
    105    public String toString() {
    106      return name();
    107    }
    108  }
    109 
    110  // -1 denotes an invalid or missing File Descriptor
    111  private static final int INVALID_FD = -1;
    112 
    113  private static final NativeQueue sNativeQueue = new NativeQueue(State.INITIAL, State.RUNNING);
    114 
    115  /* package */ static NativeQueue getNativeQueue() {
    116    return sNativeQueue;
    117  }
    118 
    119  public static final State MIN_STATE = State.INITIAL;
    120  public static final State MAX_STATE = State.EXITED;
    121 
    122  private static final Runnable UI_THREAD_CALLBACK =
    123      new Runnable() {
    124        @Override
    125        public void run() {
    126          ThreadUtils.assertOnUiThread();
    127          final long nextDelay = runUiThreadCallback();
    128          if (nextDelay >= 0) {
    129            ThreadUtils.getUiHandler().postDelayed(this, nextDelay);
    130          }
    131        }
    132      };
    133 
    134  private static final GeckoThread INSTANCE = new GeckoThread();
    135 
    136  @WrapForJNI private static final ClassLoader clsLoader = GeckoThread.class.getClassLoader();
    137  @WrapForJNI private static MessageQueue msgQueue;
    138  @WrapForJNI private static int uiThreadId;
    139 
    140  private static LinkedList<StateGeckoResult> sStateListeners = new LinkedList<>();
    141 
    142  // Main process parameters
    143  public static final int FLAG_DEBUGGING = 1 << 0; // Debugging mode.
    144  public static final int FLAG_PRELOAD_CHILD = 1 << 1; // Preload child during main thread start.
    145  public static final int FLAG_ENABLE_NATIVE_CRASHREPORTER =
    146      1 << 2; // Enable native crash reporting.
    147  public static final int FLAG_DISABLE_LOW_MEMORY_DETECTION =
    148      1 << 3; // Disable low-memory detection and notifications.
    149  public static final int FLAG_CHILD = 1 << 4; // This is a child process.
    150  public static final int FLAG_CONTENT_ISOLATED = 1 << 5; // Content service is isolated process.
    151  public static final int FLAG_CONTENT_ISOLATED_HAS_ZYGOTE =
    152      1 << 6; // Content service has app Zygote enabled.
    153 
    154  /* package */ static final String EXTRA_ARGS = "args";
    155 
    156  private boolean mInitialized;
    157  private InitInfo mInitInfo;
    158  private MemoryController mMemoryController;
    159 
    160  public static class InitInfo {
    161    public final String[] args;
    162    public final Bundle extras;
    163    public final int flags;
    164    public final Map<String, Object> prefs;
    165    public final String userSerialNumber;
    166 
    167    public final boolean xpcshell;
    168    public final String outFilePath;
    169 
    170    public final int[] fds;
    171 
    172    private InitInfo(final Builder builder) {
    173      final List<String> result = new ArrayList<>(builder.mArgs.length);
    174 
    175      boolean xpcshell = false;
    176      for (final String argument : builder.mArgs) {
    177        if ("-xpcshell".equals(argument)) {
    178          xpcshell = true;
    179        } else {
    180          result.add(argument);
    181        }
    182      }
    183      this.xpcshell = xpcshell;
    184 
    185      args = result.toArray(new String[0]);
    186 
    187      extras = builder.mExtras != null ? new Bundle(builder.mExtras) : new Bundle(3);
    188      flags = builder.mFlags;
    189      prefs = builder.mPrefs;
    190      userSerialNumber = builder.mUserSerialNumber;
    191 
    192      outFilePath = xpcshell ? builder.mOutFilePath : null;
    193 
    194      fds = builder.mFds;
    195    }
    196 
    197    public static Builder builder() {
    198      return new Builder();
    199    }
    200 
    201    public static class Builder {
    202      private String[] mArgs;
    203      private Bundle mExtras;
    204      private int mFlags;
    205      private Map<String, Object> mPrefs;
    206      private String mUserSerialNumber;
    207 
    208      private String mOutFilePath;
    209 
    210      private int[] mFds;
    211 
    212      // Prevent direct instantiation
    213      private Builder() {}
    214 
    215      public InitInfo build() {
    216        return new InitInfo(this);
    217      }
    218 
    219      public Builder args(final String[] args) {
    220        mArgs = args;
    221        return this;
    222      }
    223 
    224      public Builder extras(final Bundle extras) {
    225        mExtras = extras;
    226        return this;
    227      }
    228 
    229      public Builder flags(final int flags) {
    230        mFlags = flags;
    231        return this;
    232      }
    233 
    234      public Builder prefs(final Map<String, Object> prefs) {
    235        mPrefs = prefs;
    236        return this;
    237      }
    238 
    239      public Builder userSerialNumber(final String userSerialNumber) {
    240        mUserSerialNumber = userSerialNumber;
    241        return this;
    242      }
    243 
    244      public Builder outFilePath(final String outFilePath) {
    245        mOutFilePath = outFilePath;
    246        return this;
    247      }
    248 
    249      public Builder fds(final int[] fds) {
    250        mFds = fds;
    251        return this;
    252      }
    253    }
    254  }
    255 
    256  private static class StateGeckoResult extends GeckoResult<Void> {
    257    final State state;
    258 
    259    public StateGeckoResult(final State state) {
    260      this.state = state;
    261    }
    262  }
    263 
    264  GeckoThread() {
    265    // Request more (virtual) stack space to avoid overflows in the CSS frame
    266    // constructor. 8 MB matches desktop.
    267    super(null, null, "Gecko", 8 * 1024 * 1024);
    268  }
    269 
    270  @WrapForJNI
    271  private static boolean isChildProcess() {
    272    final InitInfo info = INSTANCE.mInitInfo;
    273    return info != null && ((info.flags & FLAG_CHILD) != 0);
    274  }
    275 
    276  public static boolean init(final InitInfo info) {
    277    return INSTANCE.initInternal(info);
    278  }
    279 
    280  private synchronized boolean initInternal(final InitInfo info) {
    281    ThreadUtils.assertOnUiThread();
    282    uiThreadId = Process.myTid();
    283 
    284    if (mInitialized) {
    285      return false;
    286    }
    287 
    288    mInitInfo = info;
    289    mInitialized = true;
    290    notifyAll();
    291    return true;
    292  }
    293 
    294  public static boolean launch() {
    295    ThreadUtils.assertOnUiThread();
    296 
    297    if (checkAndSetState(State.INITIAL, State.LAUNCHED)) {
    298      INSTANCE.start();
    299      return true;
    300    }
    301    return false;
    302  }
    303 
    304  public static boolean isLaunched() {
    305    return !isState(State.INITIAL);
    306  }
    307 
    308  @RobocopTarget
    309  public static boolean isRunning() {
    310    return isState(State.RUNNING);
    311  }
    312 
    313  private static void loadGeckoLibs(final Context context) {
    314    GeckoLoader.loadSQLiteLibs(context);
    315    GeckoLoader.loadNSSLibs(context);
    316    GeckoLoader.loadGeckoLibs(context);
    317    setState(State.LIBS_READY);
    318  }
    319 
    320  private static void initGeckoEnvironment() {
    321    final Context context = GeckoAppShell.getApplicationContext();
    322    final Locale locale = Locale.getDefault();
    323    final Resources res = context.getResources();
    324    if (locale.toString().equalsIgnoreCase("zh_hk")) {
    325      final Locale mappedLocale = Locale.TRADITIONAL_CHINESE;
    326      Locale.setDefault(mappedLocale);
    327      final Configuration config = res.getConfiguration();
    328      config.locale = mappedLocale;
    329      res.updateConfiguration(config, null);
    330    }
    331 
    332    if (!isChildProcess()) {
    333      GeckoSystemStateListener.getInstance().initialize(context);
    334    }
    335 
    336    loadGeckoLibs(context);
    337  }
    338 
    339  private String[] getMainProcessArgs() {
    340    final Context context = GeckoAppShell.getApplicationContext();
    341    final ArrayList<String> args = new ArrayList<>();
    342 
    343    // argv[0] is the program name, which for us is the package name.
    344    args.add(context.getPackageName());
    345 
    346    if (!mInitInfo.xpcshell) {
    347      args.add("-greomni");
    348      args.add(context.getPackageResourcePath());
    349    }
    350 
    351    if (mInitInfo.args != null) {
    352      args.addAll(Arrays.asList(mInitInfo.args));
    353    }
    354 
    355    // Legacy "args" parameter
    356    final String extraArgs = mInitInfo.extras.getString(EXTRA_ARGS, null);
    357    if (extraArgs != null) {
    358      final StringTokenizer st = new StringTokenizer(extraArgs);
    359      while (st.hasMoreTokens()) {
    360        args.add(st.nextToken());
    361      }
    362    }
    363 
    364    // "argX" parameters
    365    for (int i = 0; mInitInfo.extras.containsKey("arg" + i); i++) {
    366      final String arg = mInitInfo.extras.getString("arg" + i);
    367      args.add(arg);
    368    }
    369 
    370    return args.toArray(new String[0]);
    371  }
    372 
    373  public static @Nullable Bundle getActiveExtras() {
    374    synchronized (INSTANCE) {
    375      if (!INSTANCE.mInitialized) {
    376        return null;
    377      }
    378      return new Bundle(INSTANCE.mInitInfo.extras);
    379    }
    380  }
    381 
    382  public static int getActiveFlags() {
    383    synchronized (INSTANCE) {
    384      if (!INSTANCE.mInitialized) {
    385        return 0;
    386      }
    387 
    388      return INSTANCE.mInitInfo.flags;
    389    }
    390  }
    391 
    392  private static ArrayList<String> getEnvFromExtras(final Bundle extras) {
    393    if (extras == null) {
    394      return new ArrayList<>();
    395    }
    396 
    397    final ArrayList<String> result = new ArrayList<>();
    398    if (extras != null) {
    399      String env = extras.getString("env0");
    400      for (int c = 1; env != null; c++) {
    401        if (BuildConfig.DEBUG_BUILD) {
    402          Log.d(LOGTAG, "env var: " + env);
    403        }
    404        result.add(env);
    405        env = extras.getString("env" + c);
    406      }
    407    }
    408 
    409    return result;
    410  }
    411 
    412  // See GeckoLoader.java and APKOpen.cpp
    413  private int processType() {
    414    if (mInitInfo.xpcshell) {
    415      return GeckoLoader.PROCESS_TYPE_XPCSHELL;
    416    } else if ((mInitInfo.flags & FLAG_CHILD) != 0) {
    417      return GeckoLoader.PROCESS_TYPE_CHILD;
    418    } else {
    419      return GeckoLoader.PROCESS_TYPE_MAIN;
    420    }
    421  }
    422 
    423  @Override
    424  public void run() {
    425    Log.i(LOGTAG, "preparing to run Gecko");
    426 
    427    Looper.prepare();
    428    GeckoThread.msgQueue = Looper.myQueue();
    429    ThreadUtils.sGeckoThread = this;
    430    ThreadUtils.sGeckoHandler = new Handler();
    431 
    432    // Preparation for pumpMessageLoop()
    433    final MessageQueue.IdleHandler idleHandler =
    434        new MessageQueue.IdleHandler() {
    435          @Override
    436          public boolean queueIdle() {
    437            final Handler geckoHandler = ThreadUtils.sGeckoHandler;
    438            final Message idleMsg = Message.obtain(geckoHandler);
    439            // Use |Message.obj == GeckoHandler| to identify our "queue is empty" message
    440            idleMsg.obj = geckoHandler;
    441            geckoHandler.sendMessageAtFrontOfQueue(idleMsg);
    442            // Keep this IdleHandler
    443            return true;
    444          }
    445        };
    446    Looper.myQueue().addIdleHandler(idleHandler);
    447 
    448    // Wait until initialization before preparing environment.
    449    synchronized (this) {
    450      while (!mInitialized) {
    451        try {
    452          wait();
    453        } catch (final InterruptedException e) {
    454        }
    455      }
    456    }
    457 
    458    final Context context = GeckoAppShell.getApplicationContext();
    459    final List<String> env = getEnvFromExtras(mInitInfo.extras);
    460 
    461    // In Gecko, the native crash reporter is enabled by default in opt builds, and
    462    // disabled by default in debug builds.
    463    if ((mInitInfo.flags & FLAG_ENABLE_NATIVE_CRASHREPORTER) == 0 && !BuildConfig.DEBUG_BUILD) {
    464      env.add(0, "MOZ_CRASHREPORTER_DISABLE=1");
    465    } else if ((mInitInfo.flags & FLAG_ENABLE_NATIVE_CRASHREPORTER) != 0
    466        && BuildConfig.DEBUG_BUILD) {
    467      env.add(0, "MOZ_CRASHREPORTER=1");
    468    }
    469 
    470    if (mInitInfo.userSerialNumber != null) {
    471      env.add(0, "MOZ_ANDROID_USER_SERIAL_NUMBER=" + mInitInfo.userSerialNumber);
    472    }
    473 
    474    maybeRegisterMemoryController(env);
    475 
    476    // Start the profiler before even loading mozglue, so we can capture more
    477    // things that are happening on the JVM side.
    478    maybeStartGeckoProfiler(env);
    479 
    480    GeckoLoader.loadMozGlue(context);
    481    setState(State.MOZGLUE_READY);
    482 
    483    final boolean isChildProcess = isChildProcess();
    484 
    485    GeckoLoader.setupGeckoEnvironment(
    486        context,
    487        isChildProcess,
    488        context.getFilesDir().getPath(),
    489        env,
    490        mInitInfo.prefs,
    491        mInitInfo.xpcshell);
    492 
    493    initGeckoEnvironment();
    494 
    495    if ((mInitInfo.flags & FLAG_PRELOAD_CHILD) != 0) {
    496      // Preload the content ("tab") child process.
    497      GeckoProcessManager.getInstance().preload(GeckoProcessType.CONTENT);
    498    }
    499 
    500    if ((mInitInfo.flags & FLAG_CONTENT_ISOLATED) != 0) {
    501      GeckoProcessManager.getInstance().setIsolatedProcessEnabled(true);
    502    }
    503 
    504    if ((mInitInfo.flags & FLAG_CONTENT_ISOLATED_HAS_ZYGOTE) != 0) {
    505      GeckoProcessManager.getInstance().setAppZygoteEnabled(true);
    506    }
    507 
    508    if ((mInitInfo.flags & FLAG_DEBUGGING) != 0) {
    509      try {
    510        Thread.sleep(5 * 1000 /* 5 seconds */);
    511      } catch (final InterruptedException e) {
    512      }
    513    }
    514 
    515    Log.w(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - runGecko");
    516 
    517    final String[] args = isChildProcess ? mInitInfo.args : getMainProcessArgs();
    518 
    519    if ((mInitInfo.flags & FLAG_DEBUGGING) != 0) {
    520      Log.i(LOGTAG, "RunGecko - args = " + TextUtils.join(" ", args));
    521    }
    522 
    523    // And go.
    524    GeckoLoader.nativeRun(
    525        args, mInitInfo.fds, processType(), isChildProcess ? null : mInitInfo.outFilePath);
    526 
    527    // And... we're done.
    528    final boolean restarting = isState(State.RESTARTING);
    529    setState(State.EXITED);
    530 
    531    final GeckoBundle data = new GeckoBundle(1);
    532    data.putBoolean("restart", restarting);
    533    EventDispatcher.getInstance().dispatch("Gecko:Exited", data);
    534 
    535    // Remove pumpMessageLoop() idle handler
    536    Looper.myQueue().removeIdleHandler(idleHandler);
    537 
    538    if (isChildProcess) {
    539      // The child process is completely controlled by Gecko so we don't really need to keep
    540      // it alive after Gecko exits.
    541      System.exit(0);
    542    }
    543  }
    544 
    545  // Register the memory controller which will listen to low-memory events from
    546  // the Android framework and forward them to Gecko. Note that we only use it
    547  // as long as Gecko is running and we don't enable it in tests where it
    548  // causes unpredictable behavior.
    549  private void maybeRegisterMemoryController(final @NonNull List<String> env) {
    550    if ((mInitInfo.flags & GeckoThread.FLAG_DISABLE_LOW_MEMORY_DETECTION) != 0) {
    551      return;
    552    }
    553 
    554    for (final String envItem : env) {
    555      if (envItem == null) {
    556        continue;
    557      }
    558 
    559      final String mozInAutomationEnv = "MOZ_IN_AUTOMATION=";
    560 
    561      if (envItem.startsWith(mozInAutomationEnv)) {
    562        final String value = envItem.substring(mozInAutomationEnv.length());
    563 
    564        if (value.equals("1")) {
    565          // We're in automation, do not check for low memory as sending low
    566          // memory events creates unpredictable conditions in tests.
    567          return;
    568        }
    569      }
    570    }
    571 
    572    final Context context = GeckoAppShell.getApplicationContext();
    573 
    574    mMemoryController = new MemoryController();
    575    waitForState(State.RUNNING)
    576        .accept(
    577            val -> {
    578              context.registerComponentCallbacks(mMemoryController);
    579            },
    580            e -> Log.e(LOGTAG, "Unable to register the MemoryController", e));
    581    waitForState(State.EXITING)
    582        .accept(
    583            val -> {
    584              context.unregisterComponentCallbacks(mMemoryController);
    585            },
    586            e -> Log.e(LOGTAG, "Unable to unregister the MemoryController", e));
    587  }
    588 
    589  // This may start the gecko profiler early by looking at the environment variables.
    590  // Refer to the platform side for more information about the environment variables:
    591  // https://searchfox.org/mozilla-central/rev/2f9eacd9d3d995c937b4251a5557d95d494c9be1/tools/profiler/core/platform.cpp#2969-3072
    592  private static void maybeStartGeckoProfiler(final @NonNull List<String> env) {
    593    final String startupEnv = "MOZ_PROFILER_STARTUP=";
    594    final String intervalEnv = "MOZ_PROFILER_STARTUP_INTERVAL=";
    595    final String capacityEnv = "MOZ_PROFILER_STARTUP_ENTRIES=";
    596    final String filtersEnv = "MOZ_PROFILER_STARTUP_FILTERS=";
    597    boolean isStartupProfiling = false;
    598    // Putting default values for now, but they can be overwritten.
    599    // Keep these values in sync with profiler defaults.
    600    int interval = 1;
    601 
    602    // The default capacity value is the same with the min capacity, but users
    603    // can still enter a different capacity. We also keep this variable to make
    604    // sure that the entered value is not below the min capacity.
    605    // This value is kept in `scMinimumBufferEntries` variable in the cpp side:
    606    // https://searchfox.org/mozilla-central/rev/fa7f47027917a186fb2052dee104cd06c21dd76f/tools/profiler/core/platform.cpp#749
    607    // This number represents 128MiB in entry size.
    608    // This is calculated as:
    609    // 128 * 1024 * 1024 / 8 = 16777216
    610    final int minCapacity = 16777216;
    611 
    612    // ~16M entries which is 128MiB in entry size.
    613    // Keep this in sync with `PROFILER_DEFAULT_STARTUP_ENTRIES`.
    614    // It's computed as 16 * 1024 * 1024 there, which is the same number.
    615    int capacity = minCapacity;
    616 
    617    // Set the default value of no filters - an empty array - which is safer than using null.
    618    // If we find a user provided value, this will be overwritten.
    619    String[] filters = new String[0];
    620 
    621    // Looping the environment variable list to check known variable names.
    622    for (final String envItem : env) {
    623      if (envItem == null) {
    624        continue;
    625      }
    626 
    627      if (envItem.startsWith(startupEnv)) {
    628        // Check the environment variable value to see if it's positive.
    629        final String value = envItem.substring(startupEnv.length());
    630        if (value.isEmpty() || value.equals("0") || value.equals("n") || value.equals("N")) {
    631          // ''/'0'/'n'/'N' values mean do not start the startup profiler.
    632          // There's no need to inspect other environment variables,
    633          // so let's break out of the loop
    634          break;
    635        }
    636 
    637        isStartupProfiling = true;
    638      } else if (envItem.startsWith(intervalEnv)) {
    639        // Parse the interval environment variable if present
    640        final String value = envItem.substring(intervalEnv.length());
    641 
    642        try {
    643          final int intValue = Integer.parseInt(value);
    644          interval = Math.max(intValue, interval);
    645        } catch (final NumberFormatException err) {
    646          // Failed to parse. Do nothing and just use the default value.
    647        }
    648      } else if (envItem.startsWith(capacityEnv)) {
    649        // Parse the capacity environment variable if present
    650        final String value = envItem.substring(capacityEnv.length());
    651 
    652        try {
    653          final int intValue = Integer.parseInt(value);
    654          // See `scMinimumBufferEntries` variable for this value on the platform side.
    655          capacity = Math.max(intValue, minCapacity);
    656        } catch (final NumberFormatException err) {
    657          // Failed to parse. Do nothing and just use the default value.
    658        }
    659      } else if (envItem.startsWith(filtersEnv)) {
    660        filters = envItem.substring(filtersEnv.length()).split(",");
    661      }
    662    }
    663 
    664    if (isStartupProfiling) {
    665      GeckoJavaSampler.start(filters, interval, capacity);
    666    }
    667  }
    668 
    669  @WrapForJNI(calledFrom = "gecko")
    670  private static boolean pumpMessageLoop(final Message msg) {
    671    final Handler geckoHandler = ThreadUtils.sGeckoHandler;
    672 
    673    if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
    674      // Our "queue is empty" message; see runGecko()
    675      return false;
    676    }
    677 
    678    if (msg.getTarget() == null) {
    679      Looper.myLooper().quit();
    680    } else {
    681      msg.getTarget().dispatchMessage(msg);
    682    }
    683 
    684    return true;
    685  }
    686 
    687  /**
    688   * Check that the current Gecko thread state matches the given state.
    689   *
    690   * @param state State to check
    691   * @return True if the current Gecko thread state matches
    692   */
    693  public static boolean isState(final State state) {
    694    return sNativeQueue.getState().is(state);
    695  }
    696 
    697  /**
    698   * Check that the current Gecko thread state is at the given state or further along, according to
    699   * the order defined in the State enum.
    700   *
    701   * @param state State to check
    702   * @return True if the current Gecko thread state matches
    703   */
    704  public static boolean isStateAtLeast(final State state) {
    705    return sNativeQueue.getState().isAtLeast(state);
    706  }
    707 
    708  /**
    709   * Check that the current Gecko thread state is at the given state or prior, according to the
    710   * order defined in the State enum.
    711   *
    712   * @param state State to check
    713   * @return True if the current Gecko thread state matches
    714   */
    715  public static boolean isStateAtMost(final State state) {
    716    return state.isAtLeast(sNativeQueue.getState());
    717  }
    718 
    719  /**
    720   * Check that the current Gecko thread state falls into an inclusive range of states, according to
    721   * the order defined in the State enum.
    722   *
    723   * @param minState Lower range of allowable states
    724   * @param maxState Upper range of allowable states
    725   * @return True if the current Gecko thread state matches
    726   */
    727  public static boolean isStateBetween(final State minState, final State maxState) {
    728    return isStateAtLeast(minState) && isStateAtMost(maxState);
    729  }
    730 
    731  @WrapForJNI(calledFrom = "gecko")
    732  private static void setState(final State newState) {
    733    checkAndSetState(null, newState);
    734  }
    735 
    736  @WrapForJNI(calledFrom = "gecko")
    737  private static boolean checkAndSetState(final State expectedState, final State newState) {
    738    final boolean result = sNativeQueue.checkAndSetState(expectedState, newState);
    739    if (result) {
    740      Log.d(LOGTAG, "State changed to " + newState);
    741      notifyStateListeners();
    742    }
    743    return result;
    744  }
    745 
    746  @WrapForJNI(stubName = "SpeculativeConnect")
    747  private static native void speculativeConnectNative(String uri);
    748 
    749  public static void speculativeConnect(final String uri) {
    750    // This is almost always called before Gecko loads, so we don't
    751    // bother checking here if Gecko is actually loaded or not.
    752    // Speculative connection depends on proxy settings,
    753    // so the earliest it can happen is after profile is ready.
    754    queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, "speculativeConnectNative", uri);
    755  }
    756 
    757  @UiThread
    758  public static GeckoResult<Void> waitForState(final State state) {
    759    final StateGeckoResult result = new StateGeckoResult(state);
    760    if (isStateAtLeast(state)) {
    761      result.complete(null);
    762      return result;
    763    }
    764 
    765    synchronized (sStateListeners) {
    766      sStateListeners.add(result);
    767    }
    768    return result;
    769  }
    770 
    771  private static void notifyStateListeners() {
    772    synchronized (sStateListeners) {
    773      final LinkedList<StateGeckoResult> newListeners = new LinkedList<>();
    774      for (final StateGeckoResult result : sStateListeners) {
    775        if (!isStateAtLeast(result.state)) {
    776          newListeners.add(result);
    777          continue;
    778        }
    779 
    780        result.complete(null);
    781      }
    782 
    783      sStateListeners = newListeners;
    784    }
    785  }
    786 
    787  @WrapForJNI(stubName = "OnPause", dispatchTo = "gecko")
    788  private static native void nativeOnPause();
    789 
    790  public static void onPause() {
    791    if (isStateAtLeast(State.PROFILE_READY)) {
    792      nativeOnPause();
    793    } else {
    794      queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, "nativeOnPause");
    795    }
    796  }
    797 
    798  @WrapForJNI(stubName = "OnResume", dispatchTo = "gecko")
    799  private static native void nativeOnResume();
    800 
    801  public static void onResume() {
    802    if (isStateAtLeast(State.PROFILE_READY)) {
    803      nativeOnResume();
    804    } else {
    805      queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, "nativeOnResume");
    806    }
    807  }
    808 
    809  @WrapForJNI(stubName = "CreateServices", dispatchTo = "gecko")
    810  private static native void nativeCreateServices(String category, String data);
    811 
    812  public static void createServices(final String category, final String data) {
    813    if (isStateAtLeast(State.PROFILE_READY)) {
    814      nativeCreateServices(category, data);
    815    } else {
    816      queueNativeCallUntil(
    817          State.PROFILE_READY,
    818          GeckoThread.class,
    819          "nativeCreateServices",
    820          String.class,
    821          category,
    822          String.class,
    823          data);
    824    }
    825  }
    826 
    827  @WrapForJNI(calledFrom = "ui")
    828  /* package */ static native long runUiThreadCallback();
    829 
    830  @WrapForJNI(dispatchTo = "gecko")
    831  public static native void forceQuit();
    832 
    833  @WrapForJNI(dispatchTo = "gecko")
    834  public static native void crash();
    835 
    836  @WrapForJNI
    837  private static void requestUiThreadCallback(final long delay) {
    838    ThreadUtils.getUiHandler().postDelayed(UI_THREAD_CALLBACK, delay);
    839  }
    840 
    841  /** Queue a call to the given static method until Gecko is in the RUNNING state. */
    842  public static void queueNativeCall(
    843      final Class<?> cls, final String methodName, final Object... args) {
    844    sNativeQueue.queueUntilReady(cls, methodName, args);
    845  }
    846 
    847  /** Queue a call to the given instance method until Gecko is in the RUNNING state. */
    848  public static void queueNativeCall(
    849      final Object obj, final String methodName, final Object... args) {
    850    sNativeQueue.queueUntilReady(obj, methodName, args);
    851  }
    852 
    853  /** Queue a call to the given instance method until Gecko is in the RUNNING state. */
    854  public static void queueNativeCallUntil(
    855      final State state, final Object obj, final String methodName, final Object... args) {
    856    sNativeQueue.queueUntil(state, obj, methodName, args);
    857  }
    858 
    859  /** Queue a call to the given static method until Gecko is in the RUNNING state. */
    860  public static void queueNativeCallUntil(
    861      final State state, final Class<?> cls, final String methodName, final Object... args) {
    862    sNativeQueue.queueUntil(state, cls, methodName, args);
    863  }
    864 }