private async Task TearDownReactContextAsync(ReactContext reactContext, CancellationToken token) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Tearing down React context - entry"); token.ThrowIfCancellationRequested(); DispatcherHelpers.AssertOnDispatcher(); _lifecycleStateMachine.SetContext(null); // Existing root views should be silenced before tearing down the context. // Most of the work is done by the tearing down of the context itself, yet the native root views continue to exist, // so things like "size changes" or touch handling should be stopped immediately. foreach (var rootView in _attachedRootViews) { // Inlining allowed DispatcherHelpers.RunOnDispatcher(rootView.Dispatcher, () => { rootView.RemoveSizeChanged(); rootView.StopTouchHandling(); }, true); } await reactContext.DisposeAsync(); _devSupportManager.OnReactContextDestroyed(reactContext); // TODO: add memory pressure hooks RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Tearing down React context - done"); }
/// <summary> /// Called at the end of each batch. /// Has to be run on the dispatcher thread corresponding to the <see cref="UIManager.NativeViewHierarchyManager"/> processing the batch. /// </summary> internal static void OnBatchComplete() { // Exit fast if no dirty node exists at all if (!s_treeContext.Value.Dirty) { return; } #if PERF_LOG s_treeContext.Value.ProcessedNodesCount = 0; var savedDirtyNodesCount = s_treeContext.Value.DirtyNodesCount; #endif // Recurse through all known roots foreach (var root in s_treeContext.Value.RootViews) { UpdateAccessibilityPropertiesForTree(root); } // Not dirty anymore s_treeContext.Value.Dirty = false; #if PERF_LOG RnLog.Info(ReactConstants.RNW, $"Stats: ElementCount: {s_treeContext.Value.ElementCount}, " + $"MarkedDirtyNodesCount: {s_treeContext.Value.MarkedDirtyNodesCount}, " + $"DirtyNodesCount(before): {savedDirtyNodesCount}, " + $"DirtyNodesCount(after): {s_treeContext.Value.DirtyNodesCount}, " + $"ProcessedNodesCount: {s_treeContext.Value.ProcessedNodesCount}"); s_treeContext.Value.MarkedDirtyNodesCount = 0; #endif }
private async Task StartReactApplicationAsync(ReactInstanceManager reactInstanceManager, string moduleName, JObject initialProps) { RnLog.Info(ReactConstants.RNW, $"ReactRootView: StartReactApplicationAsync ({moduleName}) - entry"); // This is called under the dispatcher associated with the view. DispatcherHelpers.AssertOnDispatcher(this); if (_reactInstanceManager != null) { throw new InvalidOperationException("This root view has already been attached to an instance manager."); } _reactInstanceManager = reactInstanceManager; _jsModuleName = moduleName; _initialProps = initialProps; var getReactContextTaskTask = DispatcherHelpers.CallOnDispatcher(async() => await _reactInstanceManager.GetOrCreateReactContextAsync(CancellationToken.None), true); await getReactContextTaskTask.Unwrap(); // We need to wait for the initial `Measure` call, if this view has // not yet been measured, we set the `_attachScheduled` flag, which // will enable deferred attachment of the root node. if (_wasMeasured) { await _reactInstanceManager.AttachMeasuredRootViewAsync(this); } else { _attachScheduled = true; } RnLog.Info(ReactConstants.RNW, $"ReactRootView: StartReactApplicationAsync ({moduleName}) - done ({(_attachScheduled ? "with scheduled work" : "completely")})"); }
private async Task <ReactContext> InitializeReactContextAsync( Func <IJavaScriptExecutor> jsExecutorFactory, JavaScriptBundleLoader jsBundleLoader, CancellationToken token) { var currentReactContext = _currentReactContext; if (currentReactContext != null) { await TearDownReactContextAsync(currentReactContext, token); _currentReactContext = null; } try { var reactContext = await CreateReactContextCoreAsync(jsExecutorFactory, jsBundleLoader, token); await SetupReactContextAsync(reactContext); return(reactContext); } catch (OperationCanceledException) when(token.IsCancellationRequested) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Creating React context has been canceled"); throw; } catch (Exception ex) { RnLog.Error(ReactConstants.RNW, ex, $"ReactInstanceManager: Exception when creating React context: {ex.Message}"); _devSupportManager.HandleException(ex); } return(null); }
public void getCurrentAppState(ICallback success, ICallback error) { var currentAppState = _appState; RnLog.Info(ReactConstants.RNW, $"AppStateModule: getCurrentAppState returned {currentAppState}"); success.Invoke(CreateAppStateEventMap(currentAppState)); }
private void SendAppStateChangeEvent() { var currentAppState = _appState; RnLog.Info(ReactConstants.RNW, $"AppStateModule: appStateDidChange to {currentAppState}"); Context.GetJavaScriptModule <RCTDeviceEventEmitter>() .emit("appStateDidChange", CreateAppStateEventMap(currentAppState)); }
/// <summary> /// Awaits the currently initializing React context, or returns null if context fails to initialize. /// </summary> /// <param name="token">A token to cancel the request.</param> /// <returns> /// A task to await the React context. /// </returns> public async Task <ReactContext> TryGetReactContextAsync(CancellationToken token) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: TryGetReactContextAsync - entry"); DispatcherHelpers.AssertOnDispatcher(); using (await _lock.LockAsync()) { // By this point context has already been created due to the serialized aspect of context initialization. RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: TryGetReactContextAsync - execute/returning existing {(_currentReactContext == null ? "null" : "valid")} context"); return(_currentReactContext); } }
public async void removeRootView(int rootViewTag) { RnLog.Info(ReactConstants.RNW, $"UIManagerModule: removeRootView ({rootViewTag}) - entry"); // A cleanup task should be waiting here if (!_rootViewCleanupTasks.TryRemove(rootViewTag, out var cleanupTask)) { throw new InvalidOperationException("Unexpected removeRootView"); } await _uiImplementation.RemoveRootViewAsync(rootViewTag); cleanupTask.SetResult(true); RnLog.Info(ReactConstants.RNW, $"UIManagerModule: removeRootView ({rootViewTag}) - done"); }
/// <summary> /// Frees resources associated with this root view. /// </summary> /// <remarks> /// Has to be called under the dispatcher associated with the view. /// </remarks> /// <returns>Awaitable task.</returns> public async Task StopReactApplicationAsync() { RnLog.Info(ReactConstants.RNW, $"ReactRootView: StopReactApplicationAsync ({JavaScriptModuleName}) - entry"); DispatcherHelpers.AssertOnDispatcher(this); var reactInstanceManager = _reactInstanceManager; var attachScheduled = _attachScheduled; _attachScheduled = false; if (!attachScheduled && reactInstanceManager != null) { await reactInstanceManager.DetachRootViewAsync(this); } RnLog.Info(ReactConstants.RNW, $"ReactRootView: StopReactApplicationAsync ({JavaScriptModuleName}) - done"); }
/// <summary> /// Awaits the currently initializing React context. /// </summary> /// <param name="token">A token to cancel the request.</param> /// <returns> /// A task to await the React context. /// </returns> public async Task <ReactContext> GetReactContextAsync(CancellationToken token) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: GetReactContextAsync - entry"); DispatcherHelpers.AssertOnDispatcher(); using (await _lock.LockAsync()) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: GetReactContextAsync - execute"); if (_currentReactContext == null) { throw new InvalidOperationException( "Use the create method to start initializing the React context."); } // By this point context has already been created due to the serialized aspect of context initialization. RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: GetReactContextAsync - returning existing {(_currentReactContext == null ? "null" : "valid")} context"); return(_currentReactContext); } }
internal ReactInstanceManager( string jsBundleFile, string jsMainModuleName, IReadOnlyList <IReactPackage> packages, bool useDeveloperSupport, LifecycleState initialLifecycleState, UIImplementationProvider uiImplementationProvider, Func <IJavaScriptExecutor> javaScriptExecutorFactory, Action <Exception> nativeModuleCallExceptionHandler, bool lazyViewManagersEnabled) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: constructor"); if (packages == null) { throw new ArgumentNullException(nameof(packages)); } if (uiImplementationProvider == null) { throw new ArgumentNullException(nameof(uiImplementationProvider)); } if (javaScriptExecutorFactory == null) { throw new ArgumentNullException(nameof(javaScriptExecutorFactory)); } _jsBundleFile = jsBundleFile; _jsMainModuleName = jsMainModuleName; _packages = packages; _useDeveloperSupport = useDeveloperSupport; _devSupportManager = _useDeveloperSupport ? (IDevSupportManager) new DevSupportManager( new ReactInstanceDevCommandsHandler(this), _jsBundleFile == null, _jsMainModuleName) : new DisabledDevSupportManager(); _lifecycleStateMachine = new LifecycleStateMachine(initialLifecycleState); _uiImplementationProvider = uiImplementationProvider; _javaScriptExecutorFactory = javaScriptExecutorFactory; _nativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; _lazyViewManagersEnabled = lazyViewManagersEnabled; }
private JavaScriptValue NativeLoggingHook( JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData) { try { var message = arguments[1].ToString(); var logLevel = (LogLevel)(int)arguments[2].ToDouble(); RnLog.Info("JS", $"{logLevel} {message}"); } catch { RnLog.Error("JS", $"Unable to process JavaScript console statement"); } return(JavaScriptValue.Undefined); }
/// <summary> /// Recreate the React application and context. This should be called /// if configuration has changed or the developer has requested the /// application to be reloaded. /// </summary> /// <param name="token">A token to cancel the request.</param> /// <returns>A task to await the result.</returns> public async Task <ReactContext> RecreateReactContextAsync(CancellationToken token) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: RecreateReactContextAsync - entry"); DispatcherHelpers.AssertOnDispatcher(); using (await _lock.LockAsync()) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: RecreateReactContextAsync - execute"); if (_currentReactContext == null) { throw new InvalidOperationException( "React context re-creation should only be called after the initial " + "create context background call."); } await CreateReactContextCoreAsync(token); RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: RecreateReactContextAsync - returning {(_currentReactContext == null ? "null" : "valid")} context"); return(_currentReactContext); } }
/// <summary> /// Trigger the React context initialization asynchronously in a /// background task. This enables applications to pre-load the /// application JavaScript, and execute global core code before the /// <see cref="ReactRootView"/> is available and measure. This should /// only be called the first time the application is set up, which is /// enforced to keep developers from accidentally creating their /// applications multiple times. /// </summary> /// <param name="token">A token to cancel the request.</param> /// <returns>A task to await the result.</returns> public async Task <ReactContext> CreateReactContextAsync(CancellationToken token) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: CreateReactContextAsync - entry"); DispatcherHelpers.AssertOnDispatcher(); using (await _lock.LockAsync()) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: CreateReactContextAsync - execute"); if (_currentReactContext != null) { throw new InvalidOperationException( "React context creation should only be called when creating the React " + "application for the first time. When reloading JavaScript, e.g., from " + "a new file, explicitly, use the re-create method."); } await CreateReactContextCoreAsync(token); RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: CreateReactContextAsync - returning {(_currentReactContext == null ? "null" : "valid")} context"); return(_currentReactContext); } }
private async Task DetachViewFromInstanceAsync( ReactRootView rootView, IReactInstance reactInstance) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: DetachViewFromInstanceAsync ({rootView.JavaScriptModuleName}) - entry"); DispatcherHelpers.AssertOnDispatcher(); // Detaches ReactRootView from instance manager root view list and size change monitoring. // This has to complete before unmounting the application. // Returns a task to await the completion of the `removeRootView` UIManager call (an effect of // unmounting the application) and the release of all dispatcher affined UI objects. var rootViewRemovedTask = await reactInstance.GetNativeModule <UIManagerModule>() .DetachRootViewAsync(rootView); reactInstance.GetJavaScriptModule <AppRegistry>().unmountApplicationComponentAtRootTag(rootView.GetTag()); RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: DetachViewFromInstanceAsync ({rootView.JavaScriptModuleName}) - waiting for removeRootView({rootView.GetTag()})"); await rootViewRemovedTask; RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: DetachViewFromInstanceAsync ({rootView.JavaScriptModuleName}) - done"); }
private async Task AttachMeasuredRootViewToInstanceAsync( ReactRootView rootView, IReactInstance reactInstance) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: AttachMeasuredRootViewToInstanceAsync ({rootView.JavaScriptModuleName}) - entry"); DispatcherHelpers.AssertOnDispatcher(); var rootTag = await reactInstance.GetNativeModule <UIManagerModule>() .AddMeasuredRootViewAsync(rootView); RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: AttachMeasuredRootViewToInstanceAsync ({rootView.JavaScriptModuleName}) - added to UIManager (tag: {rootTag}), starting React app"); var jsAppModuleName = rootView.JavaScriptModuleName; var appParameters = new Dictionary <string, object> { { "rootTag", rootTag }, { "initialProps", rootView.InitialProps } }; reactInstance.GetJavaScriptModule <AppRegistry>().runApplication(jsAppModuleName, appParameters); RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: AttachMeasuredRootViewToInstanceAsync ({rootView.JavaScriptModuleName}) - done"); }
private async Task SetupReactContextAsync(ReactContext reactContext) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Setting up React context - entry"); DispatcherHelpers.AssertOnDispatcher(); if (_currentReactContext != null) { throw new InvalidOperationException( "React context has already been setup and has not been destroyed."); } _currentReactContext = reactContext; var reactInstance = reactContext.ReactInstance; _devSupportManager.OnNewReactContextCreated(reactContext); // TODO: set up memory pressure hooks _lifecycleStateMachine.SetContext(reactContext); foreach (var rootView in _attachedRootViews) { await AttachMeasuredRootViewToInstanceAsync(rootView, reactInstance); } RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Setting up React context - done"); }
private static void JsExecutorOnNewLogLine(ChakraBridge.LogLevel logLevel, string logline) { // ChakraBridge.LogLevel value is lost when it gets marshaled to .Net native optimized code, // we need to do a proper marshaling to get actual value // Also, JavaScript already has a log level in the message, hence just RnLog.Info string tag = "JS"; FormattableString message = $"{logline}"; switch (logLevel) { case ChakraBridge.LogLevel.Error: RnLog.Error(tag, message); break; case ChakraBridge.LogLevel.Warning: RnLog.Warn(tag, message); break; case ChakraBridge.LogLevel.Info: case ChakraBridge.LogLevel.Trace: RnLog.Info(tag, message); break; } }
/// <summary> /// Runs the JavaScript at the given path. /// </summary> /// <param name="sourcePath">The source path.</param> /// <param name="sourceUrl">The source URL.</param> public void RunScript(string sourcePath, string sourceUrl) { if (sourcePath == null) { throw new ArgumentNullException(nameof(sourcePath)); } if (sourceUrl == null) { throw new ArgumentNullException(nameof(sourceUrl)); } try { var binPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, BytecodeFileName); if (_useSerialization) { var srcFileInfo = new FileInfo(sourcePath); var binFileInfo = new FileInfo(binPath); bool ranSuccessfully = false; // The idea is to run the JS bundle and generate bytecode for it on a background thread. // This eliminates the need to delay the first start when the app doesn't have bytecode. // Next time the app starts, it checks if bytecode is still good and runs it directly. if (binFileInfo.Exists && binFileInfo.LastWriteTime > srcFileInfo.LastWriteTime) { try { Native.ThrowIfError((JavaScriptErrorCode)_executor.RunSerializedScript(sourcePath, binPath, sourceUrl)); ranSuccessfully = true; } catch (JavaScriptUsageException exc) { if (exc.ErrorCode == JavaScriptErrorCode.BadSerializedScript) { // Bytecode format is dependent on Chakra engine version, so an OS upgrade may require a recompilation RnLog.Warn(ReactConstants.RNW, $"Serialized bytecode script is corrupted or wrong format, will generate new one"); } else { // Some more severe error. We still have a chance (recompiling), so we keep continuing. RnLog.Error(ReactConstants.RNW, exc, $"Failed to run serialized bytecode script, will generate new one"); } File.Delete(binPath); } } else { RnLog.Info(ReactConstants.RNW, $"Serialized bytecode script doesn't exist or is obsolete, will generate one"); } if (!ranSuccessfully) { Task.Run(() => { try { // In Chakra JS engine, only one runtime can be active on a particular thread at a time, // and a runtime can only be active on one thread at a time. However it's possible to // create two runtimes and let them run on different threads. var rt = new ChakraBridge.NativeJavaScriptExecutor(); Native.ThrowIfError((JavaScriptErrorCode)rt.InitializeHost()); Native.ThrowIfError((JavaScriptErrorCode)rt.SerializeScript(sourcePath, binPath)); Native.ThrowIfError((JavaScriptErrorCode)rt.DisposeHost()); } catch (Exception ex) { RnLog.Error(ReactConstants.RNW, ex, $"Failed to generate serialized bytecode script."); // It's fine if the bytecode couldn't be generated: RN can still use the JS bundle. } }); Native.ThrowIfError((JavaScriptErrorCode)_executor.RunScript(sourcePath, sourceUrl)); } } else { Native.ThrowIfError((JavaScriptErrorCode)_executor.RunScript(sourcePath, sourceUrl)); } } catch (JavaScriptScriptException ex) { var jsonError = JavaScriptValueToJTokenConverter.Convert(ex.Error); var message = jsonError.Value <string>("message"); var stackTrace = jsonError.Value <string>("stack"); throw new Modules.Core.JavaScriptException(message ?? ex.Message, stackTrace, ex); } }
private async Task <ReactContext> CreateReactContextCoreAsync( Func <IJavaScriptExecutor> jsExecutorFactory, JavaScriptBundleLoader jsBundleLoader, CancellationToken token) { RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Creating React context - entry"); _sourceUrl = jsBundleLoader.SourceUrl; var reactContext = new ReactContext(); if (_useDeveloperSupport) { reactContext.NativeModuleCallExceptionHandler = _nativeModuleCallExceptionHandler ?? _devSupportManager.HandleException; } var nativeRegistryBuilder = new NativeModuleRegistry.Builder(reactContext); using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "createAndProcessCoreModulesPackage").Start()) { var coreModulesPackage = new CoreModulesPackage( this, InvokeDefaultOnBackPressed, _uiImplementationProvider, _lazyViewManagersEnabled); var coreModulesPackageWrapper = new CoreModulesPackageWrapper(coreModulesPackage); ProcessPackage(coreModulesPackageWrapper, reactContext, nativeRegistryBuilder); } foreach (var reactPackage in _packages) { using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "createAndProcessCustomReactPackage").Start()) { ProcessPackage(reactPackage, reactContext, nativeRegistryBuilder); } } var nativeModuleRegistry = default(NativeModuleRegistry); using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "buildNativeModuleRegistry").Start()) { nativeModuleRegistry = nativeRegistryBuilder.Build(); } var queueConfiguration = ReactQueueConfigurationFactory.Default.Create(reactContext.HandleException); var reactInstanceBuilder = new ReactInstance.Builder { QueueConfiguration = queueConfiguration, JavaScriptExecutorFactory = jsExecutorFactory, Registry = nativeModuleRegistry, BundleLoader = jsBundleLoader, }; var reactInstance = default(ReactInstance); using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "createReactInstance").Start()) { reactInstance = reactInstanceBuilder.Build(); } // TODO: add bridge idle debug listener reactContext.InitializeWithInstance(reactInstance); using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "RunJavaScriptBundle").Start()) { await reactInstance.InitializeBridgeAsync(token); } await reactInstance.InitializeAsync().ConfigureAwait(false); RnLog.Info(ReactConstants.RNW, $"ReactInstanceManager: Creating React context - done"); return(reactContext); }