blob: 28bb4d4e18d0535332727161f015298de0a3cbe7 [file]
// Copyright 2018 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
/// @docImport '../app.dart';
library;
import 'dart:async';
import 'package:devtools_app_shared/service.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_app_shared/utils.dart';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:devtools_shared/service.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';
import '../extensions/extension_service.dart';
import '../screens/debugger/breakpoint_manager.dart';
import '../service/service_manager.dart';
import '../service/vm_service_wrapper.dart';
import '../shared/analytics/analytics.dart' as ga;
import '../shared/config_specific/framework_initialize/framework_initialize.dart';
import '../shared/console/eval/eval_service.dart';
import '../shared/framework/app_error_handling.dart' as error_handling;
import '../shared/framework/framework_controller.dart';
import '../shared/framework/screen_controllers.dart';
import '../shared/globals.dart';
import '../shared/managers/banner_messages.dart';
import '../shared/managers/notifications.dart';
import '../shared/managers/script_manager.dart';
import '../shared/managers/survey.dart';
import '../shared/offline/offline_data.dart';
import '../shared/preferences/preferences.dart';
import '../shared/primitives/message_bus.dart';
import '../shared/server/server.dart' as server;
import '../shared/utils/utils.dart';
import 'observer/memory_observer.dart';
import 'theme_manager.dart';
typedef ErrorReporter = void Function(String title, Object error);
final _log = Logger('framework_core');
/// A namespace for core framework objects and methods.
extension FrameworkCore on Never {
static final _memoryObserver = MemoryObserver();
/// Initializes the DevTools framework, which includes setting up global
/// variables, local storage, preferences, and initializing framework level
/// managers like the Dart Tooling Daemon manager and the DevTools extensions
/// service.
static Future<void> init() async {
_initGlobals();
await initializePlatform();
// Initialize analytics metrics before initializing the rest of the
// framework. It is important that this method call is here, after the
// `storage` global variable has been set by the `initializePlatform` call,
// since the analytics dimensions reference the `storage` global. We
// initialize this now so that any analytics events sent from here on our
// include the expected metadata.
// TODO(kenz): consider making the dimensions that need initialization
// `late` so that they can be initialized on first access rather than
// manually.
await ga.setupDimensions();
// Print DevTools info at startup.
_log.info(
'Version: $devToolsVersion, Renderer: ${kIsWasm ? 'skwasm' : 'canvaskit'}',
);
await _initDTDConnection();
final preferences = PreferencesController();
// Wait for preferences to load before rendering the app to avoid a flash of
// content with the incorrect theme.
await preferences.init();
// This must be called after the DTD connection has been initialized and after
// preferences have been initialized.
await extensionService.initialize();
_memoryObserver.init();
}
/// Disposes framework level services and managers.
///
/// Any service or manager that is initialized in [init] should be disposed
/// here. This method is called from the [DevToolsAppState.dispose] lifecycle
/// method.
static void dispose() {
extensionService.dispose();
preferences.dispose();
_themeManager?.dispose();
unawaited(dtdManager.dispose());
_memoryObserver.dispose();
}
static void _initGlobals() {
setGlobal(ScreenControllers, ScreenControllers());
setGlobal(ServiceConnectionManager, ServiceConnectionManager());
setGlobal(MessageBus, MessageBus());
setGlobal(FrameworkController, FrameworkController());
setGlobal(SurveyService, SurveyService());
setGlobal(OfflineDataController, OfflineDataController());
setGlobal(ScriptManager, ScriptManager());
setGlobal(NotificationService, NotificationService());
setGlobal(BannerMessagesController, BannerMessagesController());
setGlobal(BreakpointManager, BreakpointManager());
setGlobal(EvalService, EvalService());
setGlobal(ExtensionService, ExtensionService());
setGlobal(IdeTheme, getIdeTheme());
setGlobal(DTDManager, DTDManager());
}
static bool vmServiceInitializationInProgress = false;
static EditorThemeManager? _themeManager;
/// Attempts to initialize a VM service connection and return whether the
/// connection attempt succeeded.
static Future<bool> initVmService({
required String serviceUriAsString,
ErrorReporter? errorReporter = _defaultErrorReporter,
bool logException = true,
}) async {
if (serviceConnection.serviceManager.connectedState.value.connected) {
// TODO(https://github.com/flutter/devtools/issues/1568): why do we call
// this multiple times?
return true;
}
final uri = normalizeVmServiceUri(serviceUriAsString);
if (uri != null) {
vmServiceInitializationInProgress = true;
final finishedCompleter = Completer<void>();
try {
final service = await connect<VmServiceWrapper>(
uri: uri,
finishedCompleter: finishedCompleter,
serviceFactory:
({
// ignore: avoid-dynamic, mirrors types of [VmServiceFactory].
required Stream<dynamic> /*String|List<int>*/ inStream,
required void Function(String message) writeMessage,
Log? log,
DisposeHandler? disposeHandler,
Future? streamClosed,
String? wsUri,
bool trackFutures = false,
}) => VmServiceWrapper.defaultFactory(
inStream: inStream,
writeMessage: writeMessage,
log: log,
disposeHandler: disposeHandler,
streamClosed: streamClosed,
wsUri: wsUri,
trackFutures: integrationTestMode,
),
);
await serviceConnection.serviceManager.vmServiceOpened(
service,
onClosed: finishedCompleter.future,
);
await breakpointManager.initialize();
return true;
} catch (e, st) {
if (logException) {
_log.shout(e, e, st);
}
errorReporter!('Unable to connect to VM service at $uri: $e', e);
return false;
} finally {
vmServiceInitializationInProgress = false;
}
} else {
// Don't report an error here because we do not have a URI to connect to.
return false;
}
}
static void _defaultErrorReporter(String title, Object error) {
notificationService.pushError('$title, $error', isReportable: false);
}
}
Future<void> _initDTDConnection() async {
try {
// Get the dtdUri from the devtools server
final dtdUri = await server.getDtdUri();
if (dtdUri != null) {
await dtdManager.connect(
dtdUri,
onError: (e, st) {
notificationService.pushError(
'Failed to connect to the Dart Tooling Daemon',
isReportable: false,
);
error_handling.reportError(
e,
errorType: 'Dart Tooling Daemon connection failed.',
stack: st,
);
},
);
if (dtdManager.connection.value != null) {
FrameworkCore._themeManager = EditorThemeManager(
dtdManager.connection.value!,
)..listenForThemeChanges();
}
} else {
_log.info('No DTD uri provided from the server during initialization.');
}
} catch (e, st) {
// Dtd failing to connect does not interfere with devtools starting up so
// catch any errors and report them.
error_handling.reportError(
e,
errorType: 'Failed to initialize the DTD connection.',
stack: st,
);
}
}