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
        }
Beispiel #3
0
        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);
        }
Beispiel #5
0
        public void getCurrentAppState(ICallback success, ICallback error)
        {
            var currentAppState = _appState;

            RnLog.Info(ReactConstants.RNW, $"AppStateModule: getCurrentAppState returned {currentAppState}");
            success.Invoke(CreateAppStateEventMap(currentAppState));
        }
Beispiel #6
0
        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");
        }
Beispiel #9
0
        /// <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);
        }