<commit_before>using ReactNative.Bridge;
using ReactNative.Bridge.Queue;
using ReactNative.Common;
using ReactNative.DevSupport;
using ReactNative.Modules.Core;
using ReactNative.Tracing;
using ReactNative.UIManager;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;

namespace ReactNative
{
    /// <summary>
    /// This interface manages instances of <see cref="IReactInstance" />.
    /// It exposes a way to configure React instances using
    /// <see cref="IReactPackage"/> and keeps track of the lifecycle of that
    /// instance. It also sets up a connection between the instance and the
    /// developer support functionality of the framework.
    ///
    /// An instance of this manager is required to start the JavaScript
    /// application in <see cref="ReactRootView"/>
    /// (<see cref="ReactRootView.StartReactApplication(ReactInstanceManager, string)"/>).
    ///
    /// The lifecycle of the instance of <see cref="ReactInstanceManager"/>
    /// should be bound to the application that owns the
    /// <see cref="ReactRootView"/> that is used to render the React
    /// application using this instance manager. It is required to pass
    /// lifecycle events to the instance manager (i.e., <see cref="OnSuspend"/>,
    /// <see cref="IAsyncDisposable.DisposeAsync"/>, and <see cref="OnResume(Action)"/>).
    /// </summary>
    public class ReactInstanceManager
    {
        private readonly List<ReactRootView> _attachedRootViews = new List<ReactRootView>();

        private readonly object _lifecycleStateLock = new object();
        private readonly SerialDisposable _currentInitializationToken = new SerialDisposable();

        private readonly string _jsBundleFile;
        private readonly string _jsMainModuleName;
        private readonly IReadOnlyList<IReactPackage> _packages;
        private readonly IDevSupportManager _devSupportManager;
        private readonly bool _useDeveloperSupport;
        private readonly UIImplementationProvider _uiImplementationProvider;
        private readonly Func<IJavaScriptExecutor> _javaScriptExecutorFactory;
        private readonly Action<Exception> _nativeModuleCallExceptionHandler;

        private LifecycleState _lifecycleState;
        private bool _hasStartedCreatingInitialContext;
        private Task<ReactContext> _contextInitializationTask;
        private int _pendingInitializationTasks;
        private string _sourceUrl;
        private ReactContext _currentReactContext;
        private Action _defaultBackButtonHandler;

        internal ReactInstanceManager(
            string jsBundleFile,
            string jsMainModuleName,
            IReadOnlyList<IReactPackage> packages,
            bool useDeveloperSupport,
            LifecycleState initialLifecycleState,
            UIImplementationProvider uiImplementationProvider,
            Func<IJavaScriptExecutor> javaScriptExecutorFactory,
            Action<Exception> nativeModuleCallExceptionHandler)
        {
            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();

            _lifecycleState = initialLifecycleState;
            _uiImplementationProvider = uiImplementationProvider;
            _javaScriptExecutorFactory = javaScriptExecutorFactory;
            _nativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
        }

        /// <summary>
        /// The developer support manager for the instance.
        /// </summary>
        public IDevSupportManager DevSupportManager
        {
            get
            {
                return _devSupportManager;
            }
        }

        /// <summary>
        /// Signals whether <see cref="CreateReactContextAsync(CancellationToken)"/> has
        /// been called. Will return <code>false</code> after 
        /// <see cref="IAsyncDisposable.DisposeAsync"/>  until a new initial
        /// context has been created.
        /// </summary>
        public bool HasStartedCreatingInitialContext
        {
            get
            {
                return _hasStartedCreatingInitialContext;
            }
        }

        /// <summary>
        /// The URL where the last bundle was loaded from.
        /// </summary>
        public string SourceUrl
        {
            get
            {
                return _sourceUrl;
            }
        }

        /// <summary>
        /// Gets the current React context instance.
        /// </summary>
        public ReactContext CurrentReactContext
        {
            get
            {
                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 Task<ReactContext> CreateReactContextAsync(CancellationToken token)
        {
            if (_hasStartedCreatingInitialContext)
            {
                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.");
            }

            ReactChoreographer.Initialize();
            _hasStartedCreatingInitialContext = true;
            return CreateReactContextCoreAsync(token);
        }

        /// <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)
        {
            if (!_hasStartedCreatingInitialContext)
            {
                throw new InvalidOperationException(
                    "Use the create method to start initializing the React context.");
            }

            var contextInitializationTask = _contextInitializationTask;
            if (contextInitializationTask != null)
            {
                return await contextInitializationTask;
            }
            else
            {
                return _currentReactContext;
            }
        }

        /// <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 Task<ReactContext> RecreateReactContextAsync(CancellationToken token)
        {
            if (!_hasStartedCreatingInitialContext)
            {
                throw new InvalidOperationException(
                    "React context re-creation should only be called after the initial " +
                    "create context background call.");
            }

            return CreateReactContextCoreAsync(token);
        }

        /// <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)
            {
                Tracer.Write(ReactConstants.Tag, "Instance detached from instance manager.");
                InvokeDefaultOnBackPressed();
            }
            else
            {
                reactContext.GetNativeModule<DeviceEventManagerModule>().EmitHardwareBackPressed();
            }
        }

        /// <summary>
        /// Called when the application is suspended.
        /// </summary>
        public void OnSuspend()
        {
            DispatcherHelpers.AssertOnDispatcher();

            _defaultBackButtonHandler = null;

            if (_useDeveloperSupport)
            {
                _devSupportManager.IsEnabled = false;
            }

            MoveToBeforeResumeLifecycleState();

            DispatcherHelpers.Reset();
        }

        /// <summary>
        /// Called when the host entered background mode.
        /// </summary>
        public void OnEnteredBackground()
        {
            DispatcherHelpers.AssertOnDispatcher();
            MoveToBackgroundLifecycleState();
        }

        /// <summary>
        /// Called when the host is leaving background mode.
        /// </summary>
        public void OnLeavingBackground()
        {
            DispatcherHelpers.AssertOnDispatcher();
            MoveToResumedLifecycleState(false);
        }

        /// <summary>
        /// Used when the application resumes to reset the back button handling
        /// in JavaScript.
        /// </summary>
        /// <param name="onBackPressed">
        /// The action to take when back is pressed.
        /// </param>
        public void OnResume(Action onBackPressed)
        {
            DispatcherHelpers.Initialize();
            DispatcherHelpers.AssertOnDispatcher();

            _defaultBackButtonHandler = onBackPressed;

            if (_useDeveloperSupport)
            {
                _devSupportManager.IsEnabled = true;
            }

            MoveToResumedLifecycleState(false);
        }

        /// <summary>
        /// Destroy the <see cref="ReactInstanceManager"/>.
        /// </summary>
        public async Task DisposeAsync()
        {
            DispatcherHelpers.AssertOnDispatcher();

            // TODO: memory pressure hooks
            if (_useDeveloperSupport)
            {
                _devSupportManager.IsEnabled = false;
            }

            MoveToBeforeCreateLifecycleState();

            var currentReactContext = _currentReactContext;
            if (currentReactContext != null)
            {
                await currentReactContext.DisposeAsync();
                _currentReactContext = null;
                _hasStartedCreatingInitialContext = false;
            }

            ReactChoreographer.Dispose();
        }

        /// <summary>
        /// Attach given <paramref name="rootView"/> to a React instance
        /// manager and start the JavaScript application using the JavaScript
        /// module provided by the <see cref="ReactRootView.JavaScriptModuleName"/>. If
        /// the React context is currently being (re-)created, or if the react
        /// context has not been created yet, the JavaScript application
        /// associated with the provided root view will be started
        /// asynchronously. This view will then be tracked by this manager and
        /// in case of React instance restart, it will be re-attached.
        /// </summary>
        /// <param name="rootView">The root view.</param>
        public void AttachMeasuredRootView(ReactRootView rootView)
        {
            if (rootView == null)
                throw new ArgumentNullException(nameof(rootView));

            DispatcherHelpers.AssertOnDispatcher();
            rootView.Children.Clear();
            rootView.ClearData();
            _attachedRootViews.Add(rootView);

            // If the React context is being created in the background, the
            // JavaScript application will be started automatically when
            // creation completes, as root view is part of the attached root
            // view list.
            var currentReactContext = _currentReactContext;
            if (_contextInitializationTask == null && currentReactContext != null)
            {
                AttachMeasuredRootViewToInstance(rootView, currentReactContext.ReactInstance);
            }
        }

        /// <summary>
        /// Detach given <paramref name="rootView"/> from the current react
        /// instance. This method is idempotent and can be called multiple
        /// times on the same <see cref="ReactRootView"/> instance.
        /// </summary>
        /// <param name="rootView">The root view.</param>
        public void DetachRootView(ReactRootView rootView)
        {
            if (rootView == null)
                throw new ArgumentNullException(nameof(rootView));

            DispatcherHelpers.AssertOnDispatcher();

            if (_attachedRootViews.Remove(rootView))
            {
                var currentReactContext = _currentReactContext;
                if (currentReactContext != null && currentReactContext.HasActiveReactInstance)
                {
                    DetachViewFromInstance(rootView, currentReactContext.ReactInstance);
                }
            }
        }

        /// <summary>
        /// Uses the configured <see cref="IReactPackage"/> instances to create
        /// all <see cref="IViewManager"/> instances.
        /// </summary>
        /// <param name="reactContext">
        /// The application context.
        /// </param>
        /// <returns>The list of view managers.</returns>
        public IReadOnlyList<IViewManager> CreateAllViewManagers(ReactContext reactContext)
        {
            if (reactContext == null)
                throw new ArgumentNullException(nameof(reactContext));

            using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "createAllViewManagers").Start())
            {
                var allViewManagers = new List<IViewManager>();
                foreach (var package in _packages)
                {
                    allViewManagers.AddRange(
                        package.CreateViewManagers(reactContext));
                }

                return allViewManagers;
            }
        }

        private Task<ReactContext> CreateReactContextCoreAsync(CancellationToken token)
        {
            DispatcherHelpers.AssertOnDispatcher();

            if (_useDeveloperSupport && _jsBundleFile == null)
            {
                return CreateReactContextFromDevManagerAsync(token);
            }
            else
            {
                return CreateReactContextFromBundleAsync(token);
            }
        }

        private async Task<ReactContext> CreateReactContextFromDevManagerAsync(CancellationToken token)
        {
            if (await _devSupportManager.HasUpToDateBundleInCacheAsync(token))
            {
                return await CreateReactContextFromCachedPackagerBundleAsync(token);
            }
            else
            {
                return await _devSupportManager.CreateReactContextFromPackagerAsync(token);
            }
        }

        private Task<ReactContext> CreateReactContextFromBundleAsync(CancellationToken token)
        {
            return CreateReactContextAsync(
                _javaScriptExecutorFactory,
                JavaScriptBundleLoader.CreateFileLoader(_jsBundleFile),
                token);
        }

        private Task<ReactContext> CreateReactContextFromCachedPackagerBundleAsync(CancellationToken token)
        {
            var bundleLoader = JavaScriptBundleLoader.CreateCachedBundleFromNetworkLoader(
                _devSupportManager.SourceUrl,
                _devSupportManager.DownloadedJavaScriptBundleFile);
            return CreateReactContextAsync(_javaScriptExecutorFactory, bundleLoader, token);
        }

        private Task<ReactContext> CreateReactContextWithRemoteDebuggerAsync(
            Func<IJavaScriptExecutor> javaScriptExecutorFactory,
            CancellationToken token)
        {
            var bundleLoader = JavaScriptBundleLoader.CreateRemoteDebuggerLoader(
                _devSupportManager.JavaScriptBundleUrlForRemoteDebugging,
                _devSupportManager.SourceUrl);
            return CreateReactContextAsync(javaScriptExecutorFactory, bundleLoader, token);
        }

        private async Task<ReactContext> CreateReactContextAsync(
            Func<IJavaScriptExecutor> jsExecutorFactory,
            JavaScriptBundleLoader jsBundleLoader,
            CancellationToken token)
        {
            var cancellationDisposable = new CancellationDisposable();
            _currentInitializationToken.Disposable = cancellationDisposable;
            using (token.Register(cancellationDisposable.Dispose))
            {
                _pendingInitializationTasks++;
                var contextInitializationTask = _contextInitializationTask ?? Task.CompletedTask;
                _contextInitializationTask = contextInitializationTask.ContinueWith(async task =>
                {
                    try
                    {
                        cancellationDisposable.Token.ThrowIfCancellationRequested();
                        return await InitializeReactContextAsync(
                            jsExecutorFactory,
                            jsBundleLoader,
                            cancellationDisposable.Token);
                    }
                    catch (OperationCanceledException)
                    when (cancellationDisposable.Token.IsCancellationRequested)
                    {
                        token.ThrowIfCancellationRequested();
                        return null;
                    }
                },
                TaskContinuationOptions.ExecuteSynchronously).Unwrap();
                return await _contextInitializationTask;
            }
        }

        private async Task<ReactContext> InitializeReactContextAsync(
            Func<IJavaScriptExecutor> jsExecutorFactory,
            JavaScriptBundleLoader jsBundleLoader,
            CancellationToken token)
        {
            var currentReactContext = _currentReactContext;
            if (currentReactContext != null)
            {
                await TearDownReactContextAsync(currentReactContext);
                _currentReactContext = null;
            }

            try
            {
                var reactContext = await CreateReactContextCoreAsync(jsExecutorFactory, jsBundleLoader, token);
                SetupReactContext(reactContext);
                return reactContext;
            }
            catch (Exception ex)
            {
                _devSupportManager.HandleException(ex);
            }
            finally
            {
                if (--_pendingInitializationTasks == 0)
                {
                    _contextInitializationTask = null;
                }
            }

            return null;
        }

        private void SetupReactContext(ReactContext reactContext)
        {
            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
            MoveReactContextToCurrentLifecycleState(reactContext);

            foreach (var rootView in _attachedRootViews)
            {
                AttachMeasuredRootViewToInstance(rootView, reactInstance);
            }
        }

        private void InvokeDefaultOnBackPressed()
        {
            DispatcherHelpers.AssertOnDispatcher();
            _defaultBackButtonHandler?.Invoke();
        }

        private void AttachMeasuredRootViewToInstance(
            ReactRootView rootView,
            IReactInstance reactInstance)
        {
            DispatcherHelpers.AssertOnDispatcher();

            var uiManagerModule = reactInstance.GetNativeModule<UIManagerModule>();
            var rootTag = uiManagerModule.AddMeasuredRootView(rootView);

            var jsAppModuleName = rootView.JavaScriptModuleName;
            var appParameters = new Dictionary<string, object>
            {
                { "rootTag", rootTag },
                { "initialProps", rootView.InitialProps }
            };

            reactInstance.GetJavaScriptModule<AppRegistry>().runApplication(jsAppModuleName, appParameters);
        }

        private void DetachViewFromInstance(ReactRootView rootView, IReactInstance reactInstance)
        {
            DispatcherHelpers.AssertOnDispatcher();
            reactInstance.GetJavaScriptModule<AppRegistry>().unmountApplicationComponentAtRootTag(rootView.GetTag());
        }

        private async Task TearDownReactContextAsync(ReactContext reactContext)
        {
            DispatcherHelpers.AssertOnDispatcher();

            if (_lifecycleState == LifecycleState.Resumed)
            {
                reactContext.OnSuspend();
            }

            foreach (var rootView in _attachedRootViews)
            {
                rootView.Children.Clear();
                rootView.ClearData();
            }

            await reactContext.DisposeAsync();
            _devSupportManager.OnReactContextDestroyed(reactContext);
            // TODO: add memory pressure hooks
        }

        private async Task<ReactContext> CreateReactContextCoreAsync(
            Func<IJavaScriptExecutor> jsExecutorFactory,
            JavaScriptBundleLoader jsBundleLoader,
            CancellationToken token)
        {
            Tracer.Write(ReactConstants.Tag, "Creating React context.");

            _sourceUrl = jsBundleLoader.SourceUrl;

            var reactContext = new ReactContext();
            if (_useDeveloperSupport)
            {
                reactContext.NativeModuleCallExceptionHandler = 
                    _nativeModuleCallExceptionHandler ?? _devSupportManager.HandleException;
            }

            var nativeRegistryBuilder = new NativeModuleRegistry.Builder(reactContext);<commit_msg>fix(ReactInstanceManager): Cancel CreateReactContextAsync OnSuspend (#1480)<commit_after>using ReactNative.Bridge;
using ReactNative.Bridge.Queue;
using ReactNative.Common;
using ReactNative.DevSupport;
using ReactNative.Modules.Core;
using ReactNative.Tracing;
using ReactNative.UIManager;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;

namespace ReactNative
{
    /// <summary>
    /// This interface manages instances of <see cref="IReactInstance" />.
    /// It exposes a way to configure React instances using
    /// <see cref="IReactPackage"/> and keeps track of the lifecycle of that
    /// instance. It also sets up a connection between the instance and the
    /// developer support functionality of the framework.
    ///
    /// An instance of this manager is required to start the JavaScript
    /// application in <see cref="ReactRootView"/>
    /// (<see cref="ReactRootView.StartReactApplication(ReactInstanceManager, string)"/>).
    ///
    /// The lifecycle of the instance of <see cref="ReactInstanceManager"/>
    /// should be bound to the application that owns the
    /// <see cref="ReactRootView"/> that is used to render the React
    /// application using this instance manager. It is required to pass
    /// lifecycle events to the instance manager (i.e., <see cref="OnSuspend"/>,
    /// <see cref="IAsyncDisposable.DisposeAsync"/>, and <see cref="OnResume(Action)"/>).
    /// </summary>
    public class ReactInstanceManager
    {
        private readonly List<ReactRootView> _attachedRootViews = new List<ReactRootView>();

        private readonly object _lifecycleStateLock = new object();
        private readonly SerialDisposable _currentInitializationToken = new SerialDisposable();

        private readonly string _jsBundleFile;
        private readonly string _jsMainModuleName;
        private readonly IReadOnlyList<IReactPackage> _packages;
        private readonly IDevSupportManager _devSupportManager;
        private readonly bool _useDeveloperSupport;
        private readonly UIImplementationProvider _uiImplementationProvider;
        private readonly Func<IJavaScriptExecutor> _javaScriptExecutorFactory;
        private readonly Action<Exception> _nativeModuleCallExceptionHandler;

        private LifecycleState _lifecycleState;
        private CancellationDisposable _suspendCancellation;
        private bool _hasStartedCreatingInitialContext;
        private Task<ReactContext> _contextInitializationTask;
        private int _pendingInitializationTasks;
        private string _sourceUrl;
        private ReactContext _currentReactContext;
        private Action _defaultBackButtonHandler;

        internal ReactInstanceManager(
            string jsBundleFile,
            string jsMainModuleName,
            IReadOnlyList<IReactPackage> packages,
            bool useDeveloperSupport,
            LifecycleState initialLifecycleState,
            UIImplementationProvider uiImplementationProvider,
            Func<IJavaScriptExecutor> javaScriptExecutorFactory,
            Action<Exception> nativeModuleCallExceptionHandler)
        {
            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();

            _lifecycleState = initialLifecycleState;
            _uiImplementationProvider = uiImplementationProvider;
            _javaScriptExecutorFactory = javaScriptExecutorFactory;
            _nativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
        }

        /// <summary>
        /// The developer support manager for the instance.
        /// </summary>
        public IDevSupportManager DevSupportManager
        {
            get
            {
                return _devSupportManager;
            }
        }

        /// <summary>
        /// Signals whether <see cref="CreateReactContextAsync(CancellationToken)"/> has
        /// been called. Will return <code>false</code> after 
        /// <see cref="IAsyncDisposable.DisposeAsync"/>  until a new initial
        /// context has been created.
        /// </summary>
        public bool HasStartedCreatingInitialContext
        {
            get
            {
                return _hasStartedCreatingInitialContext;
            }
        }

        /// <summary>
        /// The URL where the last bundle was loaded from.
        /// </summary>
        public string SourceUrl
        {
            get
            {
                return _sourceUrl;
            }
        }

        /// <summary>
        /// Gets the current React context instance.
        /// </summary>
        public ReactContext CurrentReactContext
        {
            get
            {
                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 Task<ReactContext> CreateReactContextAsync(CancellationToken token)
        {
            if (_hasStartedCreatingInitialContext)
            {
                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.");
            }

            ReactChoreographer.Initialize();
            _hasStartedCreatingInitialContext = true;
            return CreateReactContextCoreAsync(token);
        }

        /// <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)
        {
            if (!_hasStartedCreatingInitialContext)
            {
                throw new InvalidOperationException(
                    "Use the create method to start initializing the React context.");
            }

            var contextInitializationTask = _contextInitializationTask;
            if (contextInitializationTask != null)
            {
                return await contextInitializationTask;
            }
            else
            {
                return _currentReactContext;
            }
        }

        /// <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 Task<ReactContext> RecreateReactContextAsync(CancellationToken token)
        {
            if (!_hasStartedCreatingInitialContext)
            {
                throw new InvalidOperationException(
                    "React context re-creation should only be called after the initial " +
                    "create context background call.");
            }

            return CreateReactContextCoreAsync(token);
        }

        /// <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)
            {
                Tracer.Write(ReactConstants.Tag, "Instance detached from instance manager.");
                InvokeDefaultOnBackPressed();
            }
            else
            {
                reactContext.GetNativeModule<DeviceEventManagerModule>().EmitHardwareBackPressed();
            }
        }

        /// <summary>
        /// Called when the application is suspended.
        /// </summary>
        public void OnSuspend()
        {
            DispatcherHelpers.AssertOnDispatcher();

            _defaultBackButtonHandler = null;
            _suspendCancellation?.Dispose();

            if (_useDeveloperSupport)
            {
                _devSupportManager.IsEnabled = false;
            }

            MoveToBeforeResumeLifecycleState();

            DispatcherHelpers.Reset();
        }

        /// <summary>
        /// Called when the host entered background mode.
        /// </summary>
        public void OnEnteredBackground()
        {
            DispatcherHelpers.AssertOnDispatcher();
            MoveToBackgroundLifecycleState();
        }

        /// <summary>
        /// Called when the host is leaving background mode.
        /// </summary>
        public void OnLeavingBackground()
        {
            DispatcherHelpers.AssertOnDispatcher();
            MoveToResumedLifecycleState(false);
        }

        /// <summary>
        /// Used when the application resumes to reset the back button handling
        /// in JavaScript.
        /// </summary>
        /// <param name="onBackPressed">
        /// The action to take when back is pressed.
        /// </param>
        public void OnResume(Action onBackPressed)
        {
            DispatcherHelpers.Initialize();
            DispatcherHelpers.AssertOnDispatcher();

            _defaultBackButtonHandler = onBackPressed;
            _suspendCancellation = new CancellationDisposable();

            if (_useDeveloperSupport)
            {
                _devSupportManager.IsEnabled = true;
            }

            MoveToResumedLifecycleState(false);
        }

        /// <summary>
        /// Destroy the <see cref="ReactInstanceManager"/>.
        /// </summary>
        public async Task DisposeAsync()
        {
            DispatcherHelpers.AssertOnDispatcher();

            // TODO: memory pressure hooks
            if (_useDeveloperSupport)
            {
                _devSupportManager.IsEnabled = false;
            }

            MoveToBeforeCreateLifecycleState();

            var currentReactContext = _currentReactContext;
            if (currentReactContext != null)
            {
                await currentReactContext.DisposeAsync();
                _currentReactContext = null;
                _hasStartedCreatingInitialContext = false;
            }

            ReactChoreographer.Dispose();
        }

        /// <summary>
        /// Attach given <paramref name="rootView"/> to a React instance
        /// manager and start the JavaScript application using the JavaScript
        /// module provided by the <see cref="ReactRootView.JavaScriptModuleName"/>. If
        /// the React context is currently being (re-)created, or if the react
        /// context has not been created yet, the JavaScript application
        /// associated with the provided root view will be started
        /// asynchronously. This view will then be tracked by this manager and
        /// in case of React instance restart, it will be re-attached.
        /// </summary>
        /// <param name="rootView">The root view.</param>
        public void AttachMeasuredRootView(ReactRootView rootView)
        {
            if (rootView == null)
                throw new ArgumentNullException(nameof(rootView));

            DispatcherHelpers.AssertOnDispatcher();
            rootView.Children.Clear();
            rootView.ClearData();
            _attachedRootViews.Add(rootView);

            // If the React context is being created in the background, the
            // JavaScript application will be started automatically when
            // creation completes, as root view is part of the attached root
            // view list.
            var currentReactContext = _currentReactContext;
            if (_contextInitializationTask == null && currentReactContext != null)
            {
                AttachMeasuredRootViewToInstance(rootView, currentReactContext.ReactInstance);
            }
        }

        /// <summary>
        /// Detach given <paramref name="rootView"/> from the current react
        /// instance. This method is idempotent and can be called multiple
        /// times on the same <see cref="ReactRootView"/> instance.
        /// </summary>
        /// <param name="rootView">The root view.</param>
        public void DetachRootView(ReactRootView rootView)
        {
            if (rootView == null)
                throw new ArgumentNullException(nameof(rootView));

            DispatcherHelpers.AssertOnDispatcher();

            if (_attachedRootViews.Remove(rootView))
            {
                var currentReactContext = _currentReactContext;
                if (currentReactContext != null && currentReactContext.HasActiveReactInstance)
                {
                    DetachViewFromInstance(rootView, currentReactContext.ReactInstance);
                }
            }
        }

        /// <summary>
        /// Uses the configured <see cref="IReactPackage"/> instances to create
        /// all <see cref="IViewManager"/> instances.
        /// </summary>
        /// <param name="reactContext">
        /// The application context.
        /// </param>
        /// <returns>The list of view managers.</returns>
        public IReadOnlyList<IViewManager> CreateAllViewManagers(ReactContext reactContext)
        {
            if (reactContext == null)
                throw new ArgumentNullException(nameof(reactContext));

            using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "createAllViewManagers").Start())
            {
                var allViewManagers = new List<IViewManager>();
                foreach (var package in _packages)
                {
                    allViewManagers.AddRange(
                        package.CreateViewManagers(reactContext));
                }

                return allViewManagers;
            }
        }

        private Task<ReactContext> CreateReactContextCoreAsync(CancellationToken token)
        {
            DispatcherHelpers.AssertOnDispatcher();

            if (_useDeveloperSupport && _jsBundleFile == null)
            {
                return CreateReactContextFromDevManagerAsync(token);
            }
            else
            {
                return CreateReactContextFromBundleAsync(token);
            }
        }

        private async Task<ReactContext> CreateReactContextFromDevManagerAsync(CancellationToken token)
        {
            if (await _devSupportManager.HasUpToDateBundleInCacheAsync(token))
            {
                return await CreateReactContextFromCachedPackagerBundleAsync(token);
            }
            else
            {
                return await _devSupportManager.CreateReactContextFromPackagerAsync(token);
            }
        }

        private Task<ReactContext> CreateReactContextFromBundleAsync(CancellationToken token)
        {
            return CreateReactContextAsync(
                _javaScriptExecutorFactory,
                JavaScriptBundleLoader.CreateFileLoader(_jsBundleFile),
                token);
        }

        private Task<ReactContext> CreateReactContextFromCachedPackagerBundleAsync(CancellationToken token)
        {
            var bundleLoader = JavaScriptBundleLoader.CreateCachedBundleFromNetworkLoader(
                _devSupportManager.SourceUrl,
                _devSupportManager.DownloadedJavaScriptBundleFile);
            return CreateReactContextAsync(_javaScriptExecutorFactory, bundleLoader, token);
        }

        private Task<ReactContext> CreateReactContextWithRemoteDebuggerAsync(
            Func<IJavaScriptExecutor> javaScriptExecutorFactory,
            CancellationToken token)
        {
            var bundleLoader = JavaScriptBundleLoader.CreateRemoteDebuggerLoader(
                _devSupportManager.JavaScriptBundleUrlForRemoteDebugging,
                _devSupportManager.SourceUrl);
            return CreateReactContextAsync(javaScriptExecutorFactory, bundleLoader, token);
        }

        private async Task<ReactContext> CreateReactContextAsync(
            Func<IJavaScriptExecutor> jsExecutorFactory,
            JavaScriptBundleLoader jsBundleLoader,
            CancellationToken token)
        {
            var cancellationDisposable = new CancellationDisposable();
            _currentInitializationToken.Disposable = cancellationDisposable;
            using (token.Register(cancellationDisposable.Dispose))
            using (_suspendCancellation?.Token.Register(cancellationDisposable.Dispose))
            {
                _pendingInitializationTasks++;
                var contextInitializationTask = _contextInitializationTask ?? Task.CompletedTask;
                _contextInitializationTask = contextInitializationTask.ContinueWith(async task =>
                {
                    try
                    {
                        cancellationDisposable.Token.ThrowIfCancellationRequested();
                        return await InitializeReactContextAsync(
                            jsExecutorFactory,
                            jsBundleLoader,
                            cancellationDisposable.Token);
                    }
                    catch (OperationCanceledException)
                    when (cancellationDisposable.Token.IsCancellationRequested)
                    {
                        token.ThrowIfCancellationRequested();
                        return null;
                    }
                },
                TaskContinuationOptions.ExecuteSynchronously).Unwrap();
                return await _contextInitializationTask;
            }
        }

        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);
                SetupReactContext(reactContext);
                return reactContext;
            }
            catch (OperationCanceledException)
            when (token.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex)
            {
                _devSupportManager.HandleException(ex);
            }
            finally
            {
                if (--_pendingInitializationTasks == 0)
                {
                    _contextInitializationTask = null;
                }
            }

            return null;
        }

        private void SetupReactContext(ReactContext reactContext)
        {
            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
            MoveReactContextToCurrentLifecycleState(reactContext);

            foreach (var rootView in _attachedRootViews)
            {
                AttachMeasuredRootViewToInstance(rootView, reactInstance);
            }
        }

        private void InvokeDefaultOnBackPressed()
        {
            DispatcherHelpers.AssertOnDispatcher();
            _defaultBackButtonHandler?.Invoke();
        }

        private void AttachMeasuredRootViewToInstance(
            ReactRootView rootView,
            IReactInstance reactInstance)
        {
            DispatcherHelpers.AssertOnDispatcher();

            var uiManagerModule = reactInstance.GetNativeModule<UIManagerModule>();
            var rootTag = uiManagerModule.AddMeasuredRootView(rootView);

            var jsAppModuleName = rootView.JavaScriptModuleName;
            var appParameters = new Dictionary<string, object>
            {
                { "rootTag", rootTag },
                { "initialProps", rootView.InitialProps }
            };

            reactInstance.GetJavaScriptModule<AppRegistry>().runApplication(jsAppModuleName, appParameters);
        }

        private void DetachViewFromInstance(ReactRootView rootView, IReactInstance reactInstance)
        {
            DispatcherHelpers.AssertOnDispatcher();
            reactInstance.GetJavaScriptModule<AppRegistry>().unmountApplicationComponentAtRootTag(rootView.GetTag());
        }

        private async Task TearDownReactContextAsync(ReactContext reactContext, CancellationToken token)
        {
            token.ThrowIfCancellationRequested();

            DispatcherHelpers.AssertOnDispatcher();

            if (_lifecycleState == LifecycleState.Resumed)
            {
                reactContext.OnSuspend();
            }

            foreach (var rootView in _attachedRootViews)
            {
                rootView.Children.Clear();
                rootView.ClearData();
            }

            await reactContext.DisposeAsync();
            _devSupportManager.OnReactContextDestroyed(reactContext);
            // TODO: add memory pressure hooks
        }

        private async Task<ReactContext> CreateReactContextCoreAsync(
            Func<IJavaScriptExecutor> jsExecutorFactory,
            JavaScriptBundleLoader jsBundleLoader,
            CancellationToken token)
        {
            Tracer.Write(ReactConstants.Tag, "Creating React context.");

            _sourceUrl = jsBundleLoader.SourceUrl;

            var reactContext = new ReactContext();
            if (_useDeveloperSupport)
            {
                reactContext.NativeModuleCallExceptionHandler =
                    _nativeModuleCallExceptionHandler ?? _devSupportManager.HandleException;
            }

            var nativeRegistryBuilder = new NativeModuleRegistry.Builder(reactContext);
{'fraction_non_alphanumeric': 0.07205216984939566, 'fraction_numerical': 0.0001274507721392612, 'mean_word_length': 1.8800929891104858, 'pattern_counts': {'":': 0, '<': 219, '<?xml version=': 0, '>': 221, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2365036', 'n_tokens_mistral': 10660, 'n_tokens_neox': 10411, 'n_words': 2609}