/// <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 }
public void getCurrentAppState(ICallback success, ICallback error) { var currentAppState = _appState; RnLog.Info(ReactConstants.RNW, $"AppStateModule: getCurrentAppState returned {currentAppState}"); success.Invoke(CreateAppStateEventMap(currentAppState)); }
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")})"); }
public async void clear(ICallback callback) { var error = default(JObject); await _mutex.WaitAsync().ConfigureAwait(false); try { var storageFolder = await GetAsyncStorageFolder(false).ConfigureAwait(false); if (storageFolder != null) { await storageFolder.DeleteAsync().ConfigureAwait(false); } } catch (Exception ex) { error = AsyncStorageHelpers.GetError(ex); } finally { _mutex.Release(); } if (error != null) { RnLog.Warn(ReactConstants.RNW, $"Error in AsyncStorageModule.clear: {error}"); callback.Invoke(error); } else { callback.Invoke(); } }
public void UpdateView() { if (_connectedViewTag == -1) { return; } foreach (var entry in _propNodeMapping) { var node = _manager.GetNodeById(entry.Value); if (node is StyleAnimatedNode styleNode) { styleNode.CollectViewUpdates(_propMap); } else if (node is ValueAnimatedNode valueNode) { _propMap[entry.Key] = valueNode.Value; } else { throw new InvalidOperationException($"Unsupported type of node used in property node '{node.GetType()}'."); } } var updated = _uiImplementation.SynchronouslyUpdateViewOnDispatcherThread( _connectedViewTag, _propMap); if (!updated) { RnLog.Warn(ReactConstants.RNW, $"Native animation workaround, frame lost as result of race condition."); } }
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"); }
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); }
/// <summary> /// Parse a <see cref="DebugServerException"/> from the server response. /// </summary> /// <param name="content"> /// JSON response returned by the debug server. /// </param> /// <returns>The exception instance.</returns> public static DebugServerException Parse(string content) { if (!string.IsNullOrEmpty(content)) { try { var jsonObject = JObject.Parse(content); var fileName = jsonObject.Value <string>("filename"); var description = jsonObject.Value <string>("description"); if (description != null) { return(new DebugServerException( jsonObject.Value <string>("description"), ShortenFileName(fileName), jsonObject.Value <int>("lineNumber"), jsonObject.Value <int>("column"))); } } catch (JsonException ex) { RnLog.Error(ReactConstants.RNW, ex, $"Failure deserializing debug server exception message."); } } return(null); }
private void SendAppStateChangeEvent() { var currentAppState = _appState; RnLog.Info(ReactConstants.RNW, $"AppStateModule: appStateDidChange to {currentAppState}"); Context.GetJavaScriptModule <RCTDeviceEventEmitter>() .emit("appStateDidChange", CreateAppStateEventMap(currentAppState)); }
private static void Forget(Task task) { task.ContinueWith( t => { RnLog.Fatal(ReactConstants.RNW, t.Exception, $"Exception in fire and forget asynchronous function"); }, TaskContinuationOptions.OnlyOnFaulted); }
/// <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 void HandleException(Exception exception) { if (IsEnabled) { ShowNewNativeError(exception.Message, exception); } else { RnLog.Fatal(ReactConstants.RNW, exception, $"Exception caught in top handler"); } }
public async void multiRemove(string[] keys, ICallback callback) { if (keys == null || keys.Length == 0) { callback.Invoke(AsyncStorageHelpers.GetInvalidKeyError(null)); return; } var error = default(JObject); await _mutex.WaitAsync().ConfigureAwait(false); try { foreach (var key in keys) { if (key == null) { error = AsyncStorageHelpers.GetInvalidKeyError(null); break; } error = await RemoveAsync(key).ConfigureAwait(false); if (error != null) { break; } } } catch (Exception ex) { error = AsyncStorageHelpers.GetError(ex); } finally { _mutex.Release(); } if (error != null) { RnLog.Warn(ReactConstants.RNW, $"Error in AsyncStorageModule.multiRemove: {error}"); callback.Invoke(error); } else { callback.Invoke(); } }
/// <summary> /// Runs the script 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)); } var startupCode = default(string); if (IsUnbundle(sourcePath)) { _unbundle = new FileBasedJavaScriptUnbundle(sourcePath); InstallNativeRequire(); startupCode = _unbundle.GetStartupCode(); } else if (IsIndexedUnbundle(sourcePath)) { _unbundle = new IndexedJavaScriptUnbundle(sourcePath); InstallNativeRequire(); startupCode = _unbundle.GetStartupCode(); } else { startupCode = LoadScript(sourcePath); try { if (this._modifyBundle != null) { //Modify bundle function returns modified bundle. var updatedBundle = this._modifyBundle(startupCode); startupCode = updatedBundle; } } catch (Exception ex) { RnLog.Error("Native", ex, @"Exception during bundle modification."); #if DEBUG throw; #endif } } EvaluateScript(startupCode, sourceUrl); }
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> /// Passes the exception to the current /// <see cref="NativeModuleCallExceptionHandler"/>, if set, otherwise /// rethrows. /// </summary> /// <param name="exception"></param> public void HandleException(Exception exception) { var nativeModuleCallExceptionHandler = NativeModuleCallExceptionHandler; if (_reactInstance != null && !_reactInstance.IsDisposed && nativeModuleCallExceptionHandler != null) { nativeModuleCallExceptionHandler(exception); } else { RnLog.Fatal(ReactConstants.RNW, exception, $"Unhandled Exception in React Context"); } }
/// <summary> /// Method that gives JavaScript the opportunity to consume the back /// button event. If JavaScript does not consume the event, the /// default back press action will be invoked at the end of the /// roundtrip to JavaScript. /// </summary> public void OnBackPressed() { DispatcherHelpers.AssertOnDispatcher(); var reactContext = _currentReactContext; if (reactContext == null) { RnLog.Warn(ReactConstants.RNW, $"ReactInstanceManager: OnBackPressed: Instance detached from instance manager."); InvokeDefaultOnBackPressed(); } else { reactContext.GetNativeModule <DeviceEventManagerModule>().EmitHardwareBackPressed(); } }
/// <summary> /// Invokes an action on a specified dispatcher and priority /// </summary> /// <param name="dispatcher">The dispatcher.</param> /// <param name="priority">The priority.</param> /// <param name="action">The action to invoke.</param> /// <param name="allowInlining">True if inlining is allowed when calling thread is on the same dispatcher as the one in the parameter.</param> public static void RunOnDispatcher(CoreDispatcher dispatcher, CoreDispatcherPriority priority, DispatchedHandler action, bool allowInlining = false) { if (allowInlining && IsOnDispatcher(dispatcher)) { action(); } else { dispatcher.RunAsync(priority, action).AsTask().ContinueWith( t => { RnLog.Fatal(ReactConstants.RNW, t.Exception, $"Exception in fire and forget asynchronous function"); }, TaskContinuationOptions.OnlyOnFaulted); } }
/// <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"); }
public async void multiGet(string[] keys, ICallback callback) { if (keys == null) { callback.Invoke(AsyncStorageHelpers.GetInvalidKeyError(null), null); return; } var error = default(JObject); var data = new JArray(); await _mutex.WaitAsync().ConfigureAwait(false); try { foreach (var key in keys) { if (key == null) { error = AsyncStorageHelpers.GetInvalidKeyError(null); break; } var value = await GetAsync(key).ConfigureAwait(false); data.Add(new JArray(key, value)); } } catch (Exception ex) { error = AsyncStorageHelpers.GetError(ex); } finally { _mutex.Release(); } if (error != null) { RnLog.Warn(ReactConstants.RNW, $"Error in AsyncStorageModule.multiGet: {error}"); callback.Invoke(error); } else { callback.Invoke(null, data); } }
/// <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); } }
public async void getAllKeys(ICallback callback) { var error = default(JObject); var keys = new JArray(); await _mutex.WaitAsync().ConfigureAwait(false); try { var storageFolder = await GetAsyncStorageFolder(false).ConfigureAwait(false); if (storageFolder != null) { var items = await storageFolder.GetItemsAsync().AsTask().ConfigureAwait(false); foreach (var item in items) { var itemName = item.Name; if (itemName.EndsWith(AsyncStorageHelpers.FileExtension)) { keys.Add(AsyncStorageHelpers.GetKeyName(itemName)); } } } } catch (Exception ex) { error = AsyncStorageHelpers.GetError(ex); } finally { _mutex.Release(); } if (error != null) { RnLog.Warn(ReactConstants.RNW, $"Error in AsyncStorageModule.getAllKeys: {error}"); callback.Invoke(error); } else { callback.Invoke(null, keys); } }
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; }
/// <summary> /// Used by the native animated module to bypass the process of /// updating the values through the shadow view hierarchy. This method /// will directly update the native views, which means that updates for /// layout-related props won't be handled properly. /// </summary> /// <param name="tag">The view tag.</param> /// <param name="props">The props.</param> /// <remarks> /// Make sure you know what you're doing before calling this method :) /// </remarks> public bool SynchronouslyUpdateViewOnDispatcherThread(int tag, JObject props) { // The native animations module is a single threaded implementation affinitized to the "main" dispatcher thread. // As a result all calls of this method are on main dispatcher thread. // Yet, for secondary views we have to dispatch to correct dispatcher as fast as possible DispatcherHelpers.AssertOnDispatcher(); UIViewOperationQueueInstance queue = GetQueueByTag(tag, true); if (queue == null) { // Returns false as per the caller's expectation return(false); } if (queue == MainUIViewOperationQueue) { // Main queue case. Just forward. if (!MainUIViewOperationQueue.NativeViewHierarchyManager.ViewExists(tag)) { return(false); } MainUIViewOperationQueue.NativeViewHierarchyManager.UpdateProps(tag, props); } else { // Dispatch to the correct thread. DispatcherHelpers.RunOnDispatcher(queue.Dispatcher, CoreDispatcherPriority.High, () => { if (queue.NativeViewHierarchyManager.ViewExists(tag)) { queue.NativeViewHierarchyManager.UpdateProps(tag, props); } else { RnLog.Warn(nameof(UIViewOperationQueue), $"View with tag '{tag}' not found due to race condition"); } }); } return(true); }
/// <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); } }
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> /// 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 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"); }
public void InvokeCallback(int callbackId, JArray arguments) { if (IsDisposed) { RnLog.Warn(ReactConstants.RNW, $"Invoking JS callback after bridge has been destroyed."); return; } QueueConfiguration.JavaScriptQueue.Dispatch(() => { QueueConfiguration.JavaScriptQueue.AssertOnThread(); if (IsDisposed) { return; } using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "<callback>").Start()) { _bridge.InvokeCallback(callbackId, arguments); } }); }
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"); }