summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XPCOMEventTarget.java
blob: 31eac71a66c1a37f4ef73f7c02050e7491cd5dbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util;

import androidx.annotation.NonNull;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.JNIObject;
import org.mozilla.geckoview.BuildConfig;

/**
 * Wrapper for nsIEventTarget, enabling seamless dispatch of java runnables to Gecko event queues.
 */
@WrapForJNI
public final class XPCOMEventTarget extends JNIObject implements IXPCOMEventTarget {
  @Override
  public void execute(final Runnable runnable) {
    dispatchNative(new JNIRunnable(runnable));
  }

  public static synchronized IXPCOMEventTarget mainThread() {
    if (mMainThread == null) {
      mMainThread = new AsyncProxy("main");
    }
    return mMainThread;
  }

  private static IXPCOMEventTarget mMainThread = null;

  public static synchronized IXPCOMEventTarget launcherThread() {
    if (mLauncherThread == null) {
      mLauncherThread = new AsyncProxy("launcher");
    }
    return mLauncherThread;
  }

  private static IXPCOMEventTarget mLauncherThread = null;

  /**
   * Runs the provided runnable on the launcher thread. If this method is called from the launcher
   * thread itself, the runnable will be executed immediately and synchronously.
   */
  public static void runOnLauncherThread(@NonNull final Runnable runnable) {
    final IXPCOMEventTarget launcherThread = launcherThread();
    if (launcherThread.isOnCurrentThread()) {
      // We're already on the launcher thread, just execute the runnable
      runnable.run();
      return;
    }

    launcherThread.execute(runnable);
  }

  public static void assertOnLauncherThread() {
    if (BuildConfig.DEBUG_BUILD && !launcherThread().isOnCurrentThread()) {
      throw new AssertionError("Expected to be running on XPCOM launcher thread");
    }
  }

  public static void assertNotOnLauncherThread() {
    if (BuildConfig.DEBUG_BUILD && launcherThread().isOnCurrentThread()) {
      throw new AssertionError("Expected to not be running on XPCOM launcher thread");
    }
  }

  private static synchronized IXPCOMEventTarget getTarget(final String name) {
    if (name.equals("launcher")) {
      return mLauncherThread;
    } else if (name.equals("main")) {
      return mMainThread;
    } else {
      throw new RuntimeException("Attempt to assign to unknown thread named " + name);
    }
  }

  @WrapForJNI
  private static synchronized void setTarget(final String name, final XPCOMEventTarget target) {
    if (name.equals("main")) {
      mMainThread = target;
    } else if (name.equals("launcher")) {
      mLauncherThread = target;
    } else {
      throw new RuntimeException("Attempt to assign to unknown thread named " + name);
    }

    // Ensure that we see the right name in the Java debugger. We don't do this for mMainThread
    // because its name was already set (in this context, "main" is the GeckoThread).
    if (mMainThread != target) {
      target.execute(
          () -> {
            Thread.currentThread().setName(name);
          });
    }
  }

  @Override
  public native boolean isOnCurrentThread();

  private native void dispatchNative(final JNIRunnable runnable);

  @WrapForJNI
  private static synchronized void resolveAndDispatch(final String name, final Runnable runnable) {
    getTarget(name).execute(runnable);
  }

  private static native void resolveAndDispatchNative(final String name, final Runnable runnable);

  @Override
  protected native void disposeNative();

  @WrapForJNI
  private static final class JNIRunnable {
    JNIRunnable(final Runnable inner) {
      mInner = inner;
    }

    @WrapForJNI
    void run() {
      mInner.run();
    }

    private Runnable mInner;
  }

  private static final class AsyncProxy implements IXPCOMEventTarget {
    private String mTargetName;

    public AsyncProxy(final String targetName) {
      mTargetName = targetName;
    }

    @Override
    public void execute(final Runnable runnable) {
      final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName);

      if (target != null && target instanceof XPCOMEventTarget) {
        target.execute(runnable);
        return;
      }

      GeckoThread.queueNativeCallUntil(
          GeckoThread.State.JNI_READY,
          XPCOMEventTarget.class,
          "resolveAndDispatchNative",
          String.class,
          mTargetName,
          Runnable.class,
          runnable);
    }

    @Override
    public boolean isOnCurrentThread() {
      final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName);

      // If target is not yet a XPCOMEventTarget then JNI is not
      // initialized yet. If JNI is not initialized yet, then we cannot
      // possibly be running on a target with an XPCOMEventTarget.
      if (target == null || !(target instanceof XPCOMEventTarget)) {
        return false;
      }

      // Otherwise we have a real XPCOMEventTarget, so we can delegate
      // this call to it.
      return target.isOnCurrentThread();
    }
  }
}