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 }