Remove WebWorker support from Timers

Reviewed By: AaaChiuuu

Differential Revision: D4929245

fbshipit-source-id: 6eae128756a31f6063bf8fe39f0573c1c07ca8bb
This commit is contained in:
Pieter De Baets
2017-04-25 05:29:44 -07:00
committed by Facebook Github Bot
parent ea93577ff2
commit a20882f62e
3 changed files with 40 additions and 111 deletions

View File

@@ -10,10 +10,8 @@
package com.facebook.react.modules.core; package com.facebook.react.modules.core;
import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.SupportsWebWorkers;
import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableArray;
@SupportsWebWorkers
public interface JSTimersExecution extends JavaScriptModule { public interface JSTimersExecution extends JavaScriptModule {
void callTimers(WritableArray timerIDs); void callTimers(WritableArray timerIDs);
void callIdleCallbacks(double frameTime); void callIdleCallbacks(double frameTime);

View File

@@ -11,22 +11,14 @@ package com.facebook.react.modules.core;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import android.util.SparseArray; import android.util.SparseArray;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ExecutorToken;
import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.OnExecutorUnregisteredListener;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
@@ -41,9 +33,9 @@ import com.facebook.react.module.annotations.ReactModule;
/** /**
* Native module for JS timer execution. Timers fire on frame boundaries. * Native module for JS timer execution. Timers fire on frame boundaries.
*/ */
@ReactModule(name = Timing.NAME, supportsWebWorkers = true) @ReactModule(name = Timing.NAME)
public final class Timing extends ReactContextBaseJavaModule implements LifecycleEventListener, public final class Timing extends ReactContextBaseJavaModule implements LifecycleEventListener,
OnExecutorUnregisteredListener, HeadlessJsTaskEventListener { HeadlessJsTaskEventListener {
protected static final String NAME = "Timing"; protected static final String NAME = "Timing";
@@ -58,20 +50,16 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
private final DevSupportManager mDevSupportManager; private final DevSupportManager mDevSupportManager;
private static class Timer { private static class Timer {
private final ExecutorToken mExecutorToken;
private final int mCallbackID; private final int mCallbackID;
private final boolean mRepeat; private final boolean mRepeat;
private final int mInterval; private final int mInterval;
private long mTargetTime; private long mTargetTime;
private Timer( private Timer(
ExecutorToken executorToken,
int callbackID, int callbackID,
long initialTargetTime, long initialTargetTime,
int duration, int duration,
boolean repeat) { boolean repeat) {
mExecutorToken = executorToken;
mCallbackID = callbackID; mCallbackID = callbackID;
mTargetTime = initialTargetTime; mTargetTime = initialTargetTime;
mInterval = duration; mInterval = duration;
@@ -81,8 +69,8 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
private class TimerFrameCallback extends ChoreographerCompat.FrameCallback { private class TimerFrameCallback extends ChoreographerCompat.FrameCallback {
// Temporary map for constructing the individual arrays of timers per ExecutorToken // Temporary map for constructing the individual arrays of timers to call
private final HashMap<ExecutorToken, WritableArray> mTimersToCall = new HashMap<>(); private @Nullable WritableArray mTimersToCall = null;
/** /**
* Calls all timers that have expired since the last time this frame callback was called. * Calls all timers that have expired since the last time this frame callback was called.
@@ -97,32 +85,23 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
synchronized (mTimerGuard) { synchronized (mTimerGuard) {
while (!mTimers.isEmpty() && mTimers.peek().mTargetTime < frameTimeMillis) { while (!mTimers.isEmpty() && mTimers.peek().mTargetTime < frameTimeMillis) {
Timer timer = mTimers.poll(); Timer timer = mTimers.poll();
WritableArray timersForContext = mTimersToCall.get(timer.mExecutorToken); if (mTimersToCall == null) {
if (timersForContext == null) { mTimersToCall = Arguments.createArray();
timersForContext = Arguments.createArray();
mTimersToCall.put(timer.mExecutorToken, timersForContext);
} }
timersForContext.pushInt(timer.mCallbackID); mTimersToCall.pushInt(timer.mCallbackID);
if (timer.mRepeat) { if (timer.mRepeat) {
timer.mTargetTime = frameTimeMillis + timer.mInterval; timer.mTargetTime = frameTimeMillis + timer.mInterval;
mTimers.add(timer); mTimers.add(timer);
} else { } else {
SparseArray<Timer> timers = mTimerIdsToTimers.get(timer.mExecutorToken); mTimerIdsToTimers.remove(timer.mCallbackID);
if (timers != null) {
timers.remove(timer.mCallbackID);
if (timers.size() == 0) {
mTimerIdsToTimers.remove(timer.mExecutorToken);
}
}
} }
} }
} }
for (Map.Entry<ExecutorToken, WritableArray> entry : mTimersToCall.entrySet()) { if (mTimersToCall != null) {
getReactApplicationContext().getJSModule(entry.getKey(), JSTimersExecution.class) getReactApplicationContext().getJSModule(JSTimersExecution.class).callTimers(mTimersToCall);
.callTimers(entry.getValue()); mTimersToCall = null;
} }
mTimersToCall.clear();
mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this); mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
} }
@@ -172,13 +151,13 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
return; return;
} }
mIdleCallbackContextsToCall.clear(); boolean sendIdleEvents;
synchronized (mIdleCallbackGuard) { synchronized (mIdleCallbackGuard) {
mIdleCallbackContextsToCall.addAll(mSendIdleEventsExecutorTokens); sendIdleEvents = mSendIdleEvents;
} }
for (ExecutorToken context : mIdleCallbackContextsToCall) { if (sendIdleEvents) {
getReactApplicationContext().getJSModule(context, JSTimersExecution.class) getReactApplicationContext().getJSModule(JSTimersExecution.class)
.callIdleCallbacks(absoluteFrameStartTime); .callIdleCallbacks(absoluteFrameStartTime);
} }
@@ -193,7 +172,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
private final Object mTimerGuard = new Object(); private final Object mTimerGuard = new Object();
private final Object mIdleCallbackGuard = new Object(); private final Object mIdleCallbackGuard = new Object();
private final PriorityQueue<Timer> mTimers; private final PriorityQueue<Timer> mTimers;
private final Map<ExecutorToken, SparseArray<Timer>> mTimerIdsToTimers; private final SparseArray<Timer> mTimerIdsToTimers;
private final AtomicBoolean isPaused = new AtomicBoolean(true); private final AtomicBoolean isPaused = new AtomicBoolean(true);
private final AtomicBoolean isRunningTasks = new AtomicBoolean(false); private final AtomicBoolean isRunningTasks = new AtomicBoolean(false);
private final TimerFrameCallback mTimerFrameCallback = new TimerFrameCallback(); private final TimerFrameCallback mTimerFrameCallback = new TimerFrameCallback();
@@ -202,9 +181,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
private @Nullable IdleCallbackRunnable mCurrentIdleCallbackRunnable; private @Nullable IdleCallbackRunnable mCurrentIdleCallbackRunnable;
private boolean mFrameCallbackPosted = false; private boolean mFrameCallbackPosted = false;
private boolean mFrameIdleCallbackPosted = false; private boolean mFrameIdleCallbackPosted = false;
private final Set<ExecutorToken> mSendIdleEventsExecutorTokens; private boolean mSendIdleEvents = false;
// Temporary array used to dipatch idle callbacks on the JS thread.
private final List<ExecutorToken> mIdleCallbackContextsToCall;
public Timing(ReactApplicationContext reactContext, DevSupportManager devSupportManager) { public Timing(ReactApplicationContext reactContext, DevSupportManager devSupportManager) {
super(reactContext); super(reactContext);
@@ -225,9 +202,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
} }
} }
}); });
mTimerIdsToTimers = new HashMap<>(); mTimerIdsToTimers = new SparseArray<>();
mSendIdleEventsExecutorTokens = new HashSet<>();
mIdleCallbackContextsToCall = new ArrayList<>();
mReactChoreographer = ReactChoreographer.getInstance(); mReactChoreographer = ReactChoreographer.getInstance();
} }
@@ -291,7 +266,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
private void maybeSetChoreographerIdleCallback() { private void maybeSetChoreographerIdleCallback() {
synchronized (mIdleCallbackGuard) { synchronized (mIdleCallbackGuard) {
if (mSendIdleEventsExecutorTokens.size() > 0) { if (mSendIdleEvents) {
setChoreographerIdleCallback(); setChoreographerIdleCallback();
} }
} }
@@ -347,32 +322,8 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
return NAME; return NAME;
} }
@Override
public boolean supportsWebWorkers() {
return true;
}
@Override
public void onExecutorDestroyed(ExecutorToken executorToken) {
synchronized (mTimerGuard) {
SparseArray<Timer> timersForContext = mTimerIdsToTimers.remove(executorToken);
if (timersForContext == null) {
return;
}
for (int i = 0; i < timersForContext.size(); i++) {
Timer timer = timersForContext.get(timersForContext.keyAt(i));
mTimers.remove(timer);
}
}
synchronized (mIdleCallbackGuard) {
mSendIdleEventsExecutorTokens.remove(executorToken);
}
}
@ReactMethod @ReactMethod
public void createTimer( public void createTimer(
ExecutorToken executorToken,
final int callbackID, final int callbackID,
final int duration, final int duration,
final double jsSchedulingTime, final double jsSchedulingTime,
@@ -386,7 +337,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
if (mDevSupportManager.getDevSupportEnabled()) { if (mDevSupportManager.getDevSupportEnabled()) {
long driftTime = Math.abs(remoteTime - deviceTime); long driftTime = Math.abs(remoteTime - deviceTime);
if (driftTime > 60000) { if (driftTime > 60000) {
getReactApplicationContext().getJSModule(executorToken, JSTimersExecution.class) getReactApplicationContext().getJSModule(JSTimersExecution.class)
.emitTimeDriftWarning( .emitTimeDriftWarning(
"Debugger and device times have drifted by more than 60s. Please correct this by " + "Debugger and device times have drifted by more than 60s. Please correct this by " +
"running adb shell \"date `date +%m%d%H%M%Y.%S`\" on your debugger machine."); "running adb shell \"date `date +%m%d%H%M%Y.%S`\" on your debugger machine.");
@@ -398,59 +349,42 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
if (duration == 0 && !repeat) { if (duration == 0 && !repeat) {
WritableArray timerToCall = Arguments.createArray(); WritableArray timerToCall = Arguments.createArray();
timerToCall.pushInt(callbackID); timerToCall.pushInt(callbackID);
getReactApplicationContext().getJSModule(executorToken, JSTimersExecution.class) getReactApplicationContext().getJSModule(JSTimersExecution.class)
.callTimers(timerToCall); .callTimers(timerToCall);
return; return;
} }
long initialTargetTime = SystemClock.nanoTime() / 1000000 + adjustedDuration; long initialTargetTime = SystemClock.nanoTime() / 1000000 + adjustedDuration;
Timer timer = new Timer(executorToken, callbackID, initialTargetTime, duration, repeat); Timer timer = new Timer(callbackID, initialTargetTime, duration, repeat);
synchronized (mTimerGuard) { synchronized (mTimerGuard) {
mTimers.add(timer); mTimers.add(timer);
SparseArray<Timer> timersForContext = mTimerIdsToTimers.get(executorToken); mTimerIdsToTimers.put(callbackID, timer);
if (timersForContext == null) {
timersForContext = new SparseArray<>();
mTimerIdsToTimers.put(executorToken, timersForContext);
}
timersForContext.put(callbackID, timer);
} }
} }
@ReactMethod @ReactMethod
public void deleteTimer(ExecutorToken executorToken, int timerId) { public void deleteTimer(int timerId) {
synchronized (mTimerGuard) { synchronized (mTimerGuard) {
SparseArray<Timer> timersForContext = mTimerIdsToTimers.get(executorToken); Timer timer = mTimerIdsToTimers.get(timerId);
if (timersForContext == null) {
return;
}
Timer timer = timersForContext.get(timerId);
if (timer == null) { if (timer == null) {
return; return;
} }
// We may have already called/removed it mTimerIdsToTimers.remove(timerId);
timersForContext.remove(timerId);
if (timersForContext.size() == 0) {
mTimerIdsToTimers.remove(executorToken);
}
mTimers.remove(timer); mTimers.remove(timer);
} }
} }
@ReactMethod @ReactMethod
public void setSendIdleEvents(ExecutorToken executorToken, boolean sendIdleEvents) { public void setSendIdleEvents(final boolean sendIdleEvents) {
synchronized (mIdleCallbackGuard) { synchronized (mIdleCallbackGuard) {
if (sendIdleEvents) { mSendIdleEvents = sendIdleEvents;
mSendIdleEventsExecutorTokens.add(executorToken);
} else {
mSendIdleEventsExecutorTokens.remove(executorToken);
}
} }
UiThreadUtil.runOnUiThread(new Runnable() { UiThreadUtil.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
synchronized (mIdleCallbackGuard) { synchronized (mIdleCallbackGuard) {
if (mSendIdleEventsExecutorTokens.size() > 0) { if (sendIdleEvents) {
setChoreographerIdleCallback(); setChoreographerIdleCallback();
} else { } else {
clearChoreographerIdleCallback(); clearChoreographerIdleCallback();

View File

@@ -10,7 +10,6 @@
package com.facebook.react.modules.timing; package com.facebook.react.modules.timing;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ExecutorToken;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaOnlyArray; import com.facebook.react.bridge.JavaOnlyArray;
@@ -53,7 +52,6 @@ public class TimingModuleTest {
private PostFrameIdleCallbackHandler mIdlePostFrameCallbackHandler; private PostFrameIdleCallbackHandler mIdlePostFrameCallbackHandler;
private long mCurrentTimeNs; private long mCurrentTimeNs;
private JSTimersExecution mJSTimersMock; private JSTimersExecution mJSTimersMock;
private ExecutorToken mExecutorTokenMock;
@Rule @Rule
public PowerMockRule rule = new PowerMockRule(); public PowerMockRule rule = new PowerMockRule();
@@ -100,8 +98,7 @@ public class TimingModuleTest {
mTiming = new Timing(reactContext, mock(DevSupportManager.class)); mTiming = new Timing(reactContext, mock(DevSupportManager.class));
mJSTimersMock = mock(JSTimersExecution.class); mJSTimersMock = mock(JSTimersExecution.class);
mExecutorTokenMock = mock(ExecutorToken.class); when(reactContext.getJSModule(JSTimersExecution.class)).thenReturn(mJSTimersMock);
when(reactContext.getJSModule(mExecutorTokenMock, JSTimersExecution.class)).thenReturn(mJSTimersMock);
doAnswer(new Answer() { doAnswer(new Answer() {
@Override @Override
@@ -132,7 +129,7 @@ public class TimingModuleTest {
@Test @Test
public void testSimpleTimer() { public void testSimpleTimer() {
mTiming.onHostResume(); mTiming.onHostResume();
mTiming.createTimer(mExecutorTokenMock, 1, 1, 0, false); mTiming.createTimer(1, 1, 0, false);
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1));
reset(mJSTimersMock); reset(mJSTimersMock);
@@ -142,7 +139,7 @@ public class TimingModuleTest {
@Test @Test
public void testSimpleRecurringTimer() { public void testSimpleRecurringTimer() {
mTiming.createTimer(mExecutorTokenMock, 100, 1, 0, true); mTiming.createTimer(100, 1, 0, true);
mTiming.onHostResume(); mTiming.onHostResume();
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
@@ -155,13 +152,13 @@ public class TimingModuleTest {
@Test @Test
public void testCancelRecurringTimer() { public void testCancelRecurringTimer() {
mTiming.onHostResume(); mTiming.onHostResume();
mTiming.createTimer(mExecutorTokenMock, 105, 1, 0, true); mTiming.createTimer(105, 1, 0, true);
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105));
reset(mJSTimersMock); reset(mJSTimersMock);
mTiming.deleteTimer(mExecutorTokenMock, 105); mTiming.deleteTimer(105);
stepChoreographerFrame(); stepChoreographerFrame();
verifyNoMoreInteractions(mJSTimersMock); verifyNoMoreInteractions(mJSTimersMock);
} }
@@ -169,7 +166,7 @@ public class TimingModuleTest {
@Test @Test
public void testPausingAndResuming() { public void testPausingAndResuming() {
mTiming.onHostResume(); mTiming.onHostResume();
mTiming.createTimer(mExecutorTokenMock, 41, 1, 0, true); mTiming.createTimer(41, 1, 0, true);
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
@@ -189,7 +186,7 @@ public class TimingModuleTest {
public void testHeadlessJsTaskInBackground() { public void testHeadlessJsTaskInBackground() {
mTiming.onHostPause(); mTiming.onHostPause();
mTiming.onHeadlessJsTaskStart(42); mTiming.onHeadlessJsTaskStart(42);
mTiming.createTimer(mExecutorTokenMock, 41, 1, 0, true); mTiming.createTimer(41, 1, 0, true);
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
@@ -204,7 +201,7 @@ public class TimingModuleTest {
public void testHeadlessJsTaskInForeground() { public void testHeadlessJsTaskInForeground() {
mTiming.onHostResume(); mTiming.onHostResume();
mTiming.onHeadlessJsTaskStart(42); mTiming.onHeadlessJsTaskStart(42);
mTiming.createTimer(mExecutorTokenMock, 41, 1, 0, true); mTiming.createTimer(41, 1, 0, true);
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
@@ -223,7 +220,7 @@ public class TimingModuleTest {
public void testHeadlessJsTaskIntertwine() { public void testHeadlessJsTaskIntertwine() {
mTiming.onHostResume(); mTiming.onHostResume();
mTiming.onHeadlessJsTaskStart(42); mTiming.onHeadlessJsTaskStart(42);
mTiming.createTimer(mExecutorTokenMock, 41, 1, 0, true); mTiming.createTimer(41, 1, 0, true);
mTiming.onHostPause(); mTiming.onHostPause();
stepChoreographerFrame(); stepChoreographerFrame();
@@ -243,14 +240,14 @@ public class TimingModuleTest {
@Test @Test
public void testSetTimeoutZero() { public void testSetTimeoutZero() {
mTiming.createTimer(mExecutorTokenMock, 100, 0, 0, false); mTiming.createTimer(100, 0, 0, false);
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100)); verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
} }
@Test @Test
public void testIdleCallback() { public void testIdleCallback() {
mTiming.onHostResume(); mTiming.onHostResume();
mTiming.setSendIdleEvents(mExecutorTokenMock, true); mTiming.setSendIdleEvents(true);
stepChoreographerFrame(); stepChoreographerFrame();
verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis()); verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis());