private void updateSubviewClipStatus(View subview) { if (!mRemoveClippedSubviews || getParent() == null) { return; } Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); // do fast check whether intersect state changed sHelperRect.set(subview.getLeft(), subview.getTop(), subview.getRight(), subview.getBottom()); boolean intersects = mClippingRect.intersects( sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom); // If it was intersecting before, should be attached to the parent boolean oldIntersects = (subview.getParent() != null); if (intersects != oldIntersects) { int clippedSoFar = 0; for (int i = 0; i < mAllChildrenCount; i++) { if (mAllChildren[i] == subview) { updateSubviewClipStatus(mClippingRect, i, clippedSoFar); break; } if (mAllChildren[i].getParent() == null) { clippedSoFar++; } } } }
@Override public void setRemoveClippedSubviews(boolean removeClippedSubviews) { if (removeClippedSubviews == mRemoveClippedSubviews) { return; } mRemoveClippedSubviews = removeClippedSubviews; if (removeClippedSubviews) { mClippingRect = new Rect(); ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect); mAllChildrenCount = getChildCount(); int initialSize = Math.max(12, mAllChildrenCount); mAllChildren = new View[initialSize]; mChildrenLayoutChangeListener = new ChildrenLayoutChangeListener(this); for (int i = 0; i < mAllChildrenCount; i++) { View child = getChildAt(i); mAllChildren[i] = child; child.addOnLayoutChangeListener(mChildrenLayoutChangeListener); } updateClippingRect(); } else { // Add all clipped views back, deallocate additional arrays, remove layoutChangeListener Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); Assertions.assertNotNull(mChildrenLayoutChangeListener); for (int i = 0; i < mAllChildrenCount; i++) { mAllChildren[i].removeOnLayoutChangeListener(mChildrenLayoutChangeListener); } getDrawingRect(mClippingRect); updateClippingToRect(mClippingRect); mAllChildren = null; mClippingRect = null; mAllChildrenCount = 0; mChildrenLayoutChangeListener = null; } }
/** * Instantiates a new {@link ReactInstanceManagerImpl}. Before calling {@code build}, the * following must be called: * * <ul> * <li>{@link #setApplication} * <li>{@link #setJSBundleFile} or {@link #setJSMainModuleName} * </ul> */ public ReactInstanceManager build() { Assertions.assertCondition( mUseDeveloperSupport || mJSBundleFile != null, "JS Bundle File has to be provided when dev support is disabled"); Assertions.assertCondition( mJSMainModuleName != null || mJSBundleFile != null, "Either MainModuleName or JS Bundle File needs to be provided"); if (mUIImplementationProvider == null) { // create default UIImplementationProvider if the provided one is null. mUIImplementationProvider = new UIImplementationProvider(); } return new ReactInstanceManagerImpl( Assertions.assertNotNull( mApplication, "Application property has not been set with this builder"), mJSBundleFile, mJSMainModuleName, mPackages, mUseDeveloperSupport, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), mUIImplementationProvider, mNativeModuleCallExceptionHandler, mJSCConfig); }
private void setupReactContext(ReactApplicationContext reactContext) { Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "setupReactContext"); UiThreadUtil.assertOnUiThread(); Assertions.assertCondition(mCurrentReactContext == null); mCurrentReactContext = Assertions.assertNotNull(reactContext); CatalystInstance catalystInstance = Assertions.assertNotNull(reactContext.getCatalystInstance()); catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); mMemoryPressureRouter.addMemoryPressureListener(catalystInstance); moveReactContextToCurrentLifecycleState(); for (ReactRootView rootView : mAttachedRootViews) { attachMeasuredRootViewToInstance(rootView, catalystInstance); } ReactInstanceEventListener[] listeners = new ReactInstanceEventListener[mReactInstanceEventListeners.size()]; listeners = mReactInstanceEventListeners.toArray(listeners); for (ReactInstanceEventListener listener : listeners) { listener.onReactContextInitialized(reactContext); } Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); }
@Override public void dispatch(RCTEventEmitter rctEventEmitter) { TouchesHelper.sendTouchEvent( rctEventEmitter, Assertions.assertNotNull(mTouchEventType), getViewTag(), Assertions.assertNotNull(mMotionEvent)); }
/*package*/ void removeAllViewsWithSubviewClippingEnabled() { Assertions.assertCondition(mRemoveClippedSubviews); Assertions.assertNotNull(mAllChildren); for (int i = 0; i < mAllChildrenCount; i++) { mAllChildren[i].removeOnLayoutChangeListener(mChildrenLayoutChangeListener); } removeAllViewsInLayout(); mAllChildrenCount = 0; }
@Override public void updateClippingRect() { if (!mRemoveClippedSubviews) { return; } Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect); updateClippingToRect(mClippingRect); }
/*package*/ void addViewWithSubviewClippingEnabled(View child, int index, LayoutParams params) { Assertions.assertCondition(mRemoveClippedSubviews); Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); addInArray(child, index); // we add view as "clipped" and then run {@link #updateSubviewClipStatus} to conditionally // attach it int clippedSoFar = 0; for (int i = 0; i < index; i++) { if (mAllChildren[i].getParent() == null) { clippedSoFar++; } } updateSubviewClipStatus(mClippingRect, index, clippedSoFar); child.addOnLayoutChangeListener(mChildrenLayoutChangeListener); }
private ReactBridge initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { mReactQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor"); ReactBridge bridge; try { bridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mReactQueueConfiguration.getNativeModulesQueueThread()); mMainExecutorToken = bridge.getMainExecutorToken(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig"); try { bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); bridge.setGlobalVariable( "__RCTProfileIsProfiling", Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false"); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } mJavaRegistry.notifyReactBridgeInitialized(bridge); return bridge; }
@Override public void run() { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable"); try { Systrace.endAsyncFlow( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ScheduleDispatchFrameCallback", mHasDispatchScheduledCount); mHasDispatchScheduled = false; mHasDispatchScheduledCount++; Assertions.assertNotNull(mRCTEventEmitter); synchronized (mEventsToDispatchLock) { // We avoid allocating an array and iterator, and "sorting" if we don't need to. // This occurs when the size of mEventsToDispatch is zero or one. if (mEventsToDispatchSize > 1) { Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR); } for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) { Event event = mEventsToDispatch[eventIdx]; // Event can be null if it has been coalesced into another event. if (event == null) { continue; } Systrace.endAsyncFlow( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); event.dispatch(mRCTEventEmitter); event.dispose(); } clearEventsToDispatch(); mEventCookieToLastEventIdx.clear(); } } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } }
@Override public void onTraceStopped() { getJSModule( Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class) .setEnabled(false); }
/** * Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration * (besides the UI thread) to finish running. Must be called from the UI thread so that we can * fully shut down other threads. */ /* package */ void destroy() { UiThreadUtil.assertOnUiThread(); if (mDestroyed) { return; } // TODO: tell all APIs to shut down mDestroyed = true; mJavaRegistry.notifyCatalystInstanceDestroy(); mCatalystQueueConfiguration.destroy(); boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0); if (!wasIdle && !mBridgeIdleListeners.isEmpty()) { for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { listener.onTransitionToBridgeIdle(); } } if (mTraceListener != null) { Systrace.unregisterListener(mTraceListener); } // We can access the Bridge from any thread now because we know either we are on the JS thread // or the JS thread has finished via CatalystQueueConfiguration#destroy() Assertions.assertNotNull(mBridge).dispose(); }
private void addInArray(View child, int index) { View[] children = Assertions.assertNotNull(mAllChildren); final int count = mAllChildrenCount; final int size = children.length; if (index == count) { if (size == count) { mAllChildren = new View[size + ARRAY_CAPACITY_INCREMENT]; System.arraycopy(children, 0, mAllChildren, 0, size); children = mAllChildren; } children[mAllChildrenCount++] = child; } else if (index < count) { if (size == count) { mAllChildren = new View[size + ARRAY_CAPACITY_INCREMENT]; System.arraycopy(children, 0, mAllChildren, 0, index); System.arraycopy(children, index, mAllChildren, index + 1, count - index); children = mAllChildren; } else { System.arraycopy(children, index, children, index + 1, count - index); } children[index] = child; mAllChildrenCount++; } else { throw new IndexOutOfBoundsException("index=" + index + " count=" + count); } }
private void enqueueOnChangeEndpointLongPolling() { Request request = new Request.Builder().url(createOnChangeEndpointUrl()).tag(this).build(); Assertions.assertNotNull(mOnChangePollingClient) .newCall(request) .enqueue( new Callback() { @Override public void onFailure(Request request, IOException e) { if (mOnChangePollingEnabled) { // this runnable is used by onchange endpoint poller to delay subsequent requests // in case // of a failure, so that we don't flood network queue with frequent requests in // case when // dev server is down FLog.d(ReactConstants.TAG, "Error while requesting /onchange endpoint", e); mRestartOnChangePollingHandler.postDelayed( new Runnable() { @Override public void run() { handleOnChangePollingResponse(false); } }, LONG_POLL_FAILURE_DELAY_MS); } } @Override public void onResponse(Response response) throws IOException { handleOnChangePollingResponse(response.code() == 205); } }); }
public void runJSBundle() { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CatalystInstance_runJSBundle"); try { final CountDownLatch initLatch = new CountDownLatch(1); mCatalystQueueConfiguration .getJSQueueThread() .runOnQueue( new Runnable() { @Override public void run() { Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!"); mJSBundleHasLoaded = true; incrementPendingJSCalls(); mJSBundleLoader.loadScript(mBridge); initLatch.countDown(); } }); Assertions.assertCondition( initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS), "Timed out loading JS!"); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } }
/** * Recreate the react application and context. This should be called if configuration has changed * or the developer has requested the app to be reloaded. It should only be called after an * initial call to createReactContextInBackground. * * <p>Called from UI thread. */ public void recreateReactContextInBackground() { Assertions.assertCondition( mHasStartedCreatingInitialContext, "recreateReactContextInBackground should only be called after the initial " + "createReactContextInBackground call."); recreateReactContextInBackgroundInner(); }
/*package*/ void removeViewWithSubviewClippingEnabled(View view) { Assertions.assertCondition(mRemoveClippedSubviews); Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); view.removeOnLayoutChangeListener(mChildrenLayoutChangeListener); int index = indexOfChildInAllChildren(view); if (mAllChildren[index].getParent() != null) { int clippedSoFar = 0; for (int i = 0; i < index; i++) { if (mAllChildren[i].getParent() == null) { clippedSoFar++; } } super.removeViewsInLayout(index - clippedSoFar, 1); } removeFromArray(index); }
/** Initialize all the native modules */ @VisibleForTesting public void initialize() { UiThreadUtil.assertOnUiThread(); Assertions.assertCondition( !mInitialized, "This catalyst instance has already been initialized"); mInitialized = true; mJavaRegistry.notifyCatalystInstanceInitialized(); }
public T get() throws Exception { if (mException != null) { throw mException; } Assertions.assertNotNull(mResult); return mResult; }
public void loadApp( String appKey, ReactInstanceSpecForTest spec, @Nullable Bundle initialProps, String bundleName, boolean useDevSupport) { final CountDownLatch currentLayoutEvent = mLayoutEvent = new CountDownLatch(1); mBridgeIdleSignaler = new ReactBridgeIdleSignaler(); ReactInstanceManager.Builder builder = ReactTestHelper.getReactTestFactory() .getReactInstanceManagerBuilder() .setApplication(getApplication()) .setBundleAssetName(bundleName) // By not setting a JS module name, we force the bundle to be always loaded from // assets, not the devserver, even if dev mode is enabled (such as when testing // redboxes). // This makes sense because we never run the devserver in tests. // .setJSMainModuleName() .addPackage( spec.getAlternativeReactPackageForTest() != null ? spec.getAlternativeReactPackageForTest() : new MainReactPackage()) .addPackage(new InstanceSpecForTestPackage(spec)) .setUseDeveloperSupport(useDevSupport) .setBridgeIdleDebugListener(mBridgeIdleSignaler) .setInitialLifecycleState(mLifecycleState); mReactInstanceManager = builder.build(); mReactInstanceManager.onResume(this, this); Assertions.assertNotNull(mReactRootView) .getViewTreeObserver() .addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { currentLayoutEvent.countDown(); } }); Assertions.assertNotNull(mReactRootView) .startReactApplication(mReactInstanceManager, appKey, initialProps); }
private int indexOfChildInAllChildren(View child) { final int count = mAllChildrenCount; final View[] children = Assertions.assertNotNull(mAllChildren); for (int i = 0; i < count; i++) { if (children[i] == child) { return i; } } return -1; }
/** * @return Themed React context for view with a given {@param reactTag} - in the case of root view * it returns the context from {@link #mRootViewsContext} and all the other cases it gets the * context directly from the view using {@link View#getContext}. */ private ThemedReactContext getReactContextForView(int reactTag) { if (mRootTags.get(reactTag)) { return Assertions.assertNotNull(mRootViewsContext.get(reactTag)); } View view = mTagsToViews.get(reactTag); if (view == null) { throw new JSApplicationIllegalArgumentException("Could not find view with tag " + reactTag); } return (ThemedReactContext) view.getContext(); }
private void updateClippingToRect(Rect clippingRect) { Assertions.assertNotNull(mAllChildren); int clippedSoFar = 0; for (int i = 0; i < mAllChildrenCount; i++) { updateSubviewClipStatus(clippingRect, i, clippedSoFar); if (mAllChildren[i].getParent() == null) { clippedSoFar++; } } }
@Override protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) { Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); try { JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create(); return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader())); } catch (Exception e) { // Pass exception to onPostExecute() so it can be handled on the main thread return Result.of(e); } }
private void setupReactContext(ReactApplicationContext reactContext) { UiThreadUtil.assertOnUiThread(); Assertions.assertCondition(mCurrentReactContext == null); mCurrentReactContext = Assertions.assertNotNull(reactContext); CatalystInstance catalystInstance = Assertions.assertNotNull(reactContext.getCatalystInstance()); catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); mMemoryPressureRouter.onNewReactContextCreated(reactContext); moveReactContextToCurrentLifecycleState(reactContext); for (ReactRootView rootView : mAttachedRootViews) { attachMeasuredRootViewToInstance(rootView, catalystInstance); } for (ReactInstanceEventListener listener : mReactInstanceEventListeners) { listener.onReactContextInitialized(reactContext); } }
/** * Trigger react context initialization asynchronously in a background async task. This enables * applications to pre-load the application JS, and execute global code before {@link * ReactRootView} is available and measured. This should only be called the first time the * application is set up, which is enforced to keep developers from accidentally creating their * application multiple times without realizing it. * * <p>Called from UI thread. */ @Override public void createReactContextInBackground() { Assertions.assertCondition( !mHasStartedCreatingInitialContext, "createReactContextInBackground should only be called when creating the react " + "application for the first time. When reloading JS, e.g. from a new file, explicitly" + "use recreateReactContextInBackground"); mHasStartedCreatingInitialContext = true; recreateReactContextInBackgroundInner(); }
// This method also sets the child's mParent to null private void removeFromArray(int index) { final View[] children = Assertions.assertNotNull(mAllChildren); final int count = mAllChildrenCount; if (index == count - 1) { children[--mAllChildrenCount] = null; } else if (index >= 0 && index < count) { System.arraycopy(children, index + 1, children, index, count - index - 1); children[--mAllChildrenCount] = null; } else { throw new IndexOutOfBoundsException(); } }
private void decrementPendingJSCalls() { int newPendingCalls = mPendingJSCalls.decrementAndGet(); Assertions.assertCondition(newPendingCalls >= 0); boolean isNowIdle = newPendingCalls == 0; Systrace.traceCounter( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, mJsPendingCallsTitleForTrace, newPendingCalls); if (isNowIdle && !mBridgeIdleListeners.isEmpty()) { for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { listener.onTransitionToBridgeIdle(); } } }
private void initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); mBridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mCatalystQueueConfiguration.getNativeModulesQueueThread()); mBridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); }
/** * This method will give JS the opportunity to consume the back button event. If JS does not * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS. */ @Override public void onBackPressed() { UiThreadUtil.assertOnUiThread(); ReactContext reactContext = mCurrentReactContext; if (mCurrentReactContext == null) { // Invoke without round trip to JS. FLog.w(ReactConstants.TAG, "Instance detached from instance manager"); invokeDefaultOnBackPressed(); } else { DeviceEventManagerModule deviceEventManagerModule = Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class); deviceEventManagerModule.emitHardwareBackPressed(); } }