Skip to content

Commit

Permalink
Support connecting debug mode DevTools app to DevTools server (#8621)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Dec 12, 2024
1 parent b48b31b commit 2da31a2
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ class EmbeddedExtensionControllerImpl extends EmbeddedExtensionController
}

final basePath = devtoolsAssetsBasePath(
origin: window.location.origin,
// This needs to use the DevTools server URI as the origin because the
// extension assets are served through a DevTools server handler. The
// DevTools server handler loads them directly from their location in the
// user's pub-cache.
origin: devToolsServerUriAsString,
path: window.location.pathname,
);
final baseUri = path.join(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,8 @@ void cancelTimingOperation(String screenName, String timedOperation) {
final operation = _timedOperationsInProgress.remove(operationKey);
assert(
operation != null,
'The operation cannot be cancelled because it does not exist.',
'The operation $screenName.$timedOperation cannot be cancelled because it '
'does not exist.',
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ Future<String> initializePlatform() async {
// Here, we try and initialize the connection between the DevTools web app and
// its local server. DevTools can be launched without the server however, so
// establishing this connection is a best-effort.
// TODO(kenz): investigate it we can remove the DevToolsServerConnection
// code in general. We do not appear to be using the SSE connection.
final connection = await DevToolsServerConnection.connect();
if (connection != null) {
setGlobal(Storage, ServerConnectionStorage());
setGlobal(Storage, server.ServerConnectionStorage());
} else {
setGlobal(Storage, BrowserStorage());
}
Expand Down Expand Up @@ -93,19 +95,6 @@ void _sendKeyPressToParent(KeyboardEvent event) {
);
}

class ServerConnectionStorage implements Storage {
@override
Future<String?> getValue(String key) async {
final value = await server.getPreferenceValue(key);
return value == null ? null : '$value';
}

@override
Future<void> setValue(String key, String value) async {
await server.setPreferenceValue(key, value);
}
}

class BrowserStorage implements Storage {
@override
Future<String?> getValue(String key) async {
Expand Down
13 changes: 11 additions & 2 deletions packages/devtools_app/lib/src/shared/preferences/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import '../utils/utils.dart';
part '_cpu_profiler_preferences.dart';
part '_extension_preferences.dart';
part '_inspector_preferences.dart';
part '_memory_preferences.dart';
part '_logging_preferences.dart';
part '_memory_preferences.dart';
part '_network_preferences.dart';
part '_performance_preferences.dart';

Expand Down Expand Up @@ -211,8 +211,17 @@ class PreferencesController extends DisposableController
return;
}

// Whether DevTools was run using the `dt serve --run-app` command, which
// runs DevTools in debug mode using `flutter run` and connects it to an
// instance of the DevTools server.
final usingDebugDevToolsServer =
(const String.fromEnvironment('debug_devtools_server')).isNotEmpty &&
!kReleaseMode;
final shouldEnableWasm =
(enabledFromStorage || enabledFromQueryParams) && kIsWeb;
(enabledFromStorage || enabledFromQueryParams) &&
kIsWeb &&
// Wasm cannot be enabled if DevTools was built using `flutter run`.
!usingDebugDevToolsServer;
assert(kIsWasm == shouldEnableWasm);
// This should be a no-op if the flutter_bootstrap.js logic set the
// renderer properly, but we call this to be safe in case something went
Expand Down
53 changes: 46 additions & 7 deletions packages/devtools_app/lib/src/shared/server/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,49 @@ import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;

import '../development_helpers.dart';
import '../globals.dart';
import '../primitives/storage.dart';
import '../primitives/utils.dart';

part '_analytics_api.dart';
part '_app_size_api.dart';
part '_deep_links_api.dart';
part '_dtd_api.dart';
part '_extensions_api.dart';
part '_preferences_api.dart';
part '_release_notes_api.dart';
part '_survey_api.dart';
part '_dtd_api.dart';

final _log = Logger('devtools_server_client');

// The DevTools server is only available in release mode right now.
// TODO(kenz): design a way to run the DevTools server and DevTools app together
// in debug mode.
bool get isDevToolsServerAvailable => kReleaseMode;
/// Whether the DevTools server is available.
///
/// Since the DevTools server is a web server, it is only available for the
/// web platform.
///
/// In `framework_initialize_web.dart`, we test the DevTools server connection
/// by pinging the server and checking the response. If this is successful, we
/// set the [storage] global to an instance of [ServerConnectionStorage].
bool get isDevToolsServerAvailable =>
kIsWeb && storage is ServerConnectionStorage;

const _debugDevToolsServerFlag = 'debug_devtools_server';

String get devToolsServerUriAsString {
const debugDevToolsServerUriAsString = String.fromEnvironment(
_debugDevToolsServerFlag,
);
// Ensure we only use the debug DevTools server URI in non-release
// builds. By running `dt serve --run-app`, an instance of DevTools ran
// with `flutter run` can be connected to the DevTools server on a
// different port.
return debugDevToolsServerUriAsString.isNotEmpty && !kReleaseMode
? debugDevToolsServerUriAsString
: Uri.base.toString();
}

/// Helper to catch any server request which could fail.
///
Expand All @@ -38,8 +62,10 @@ Future<Response?> request(String url) async {
Response? response;

try {
_log.fine('requesting $url');
response = await post(Uri.parse(url));
// This will be the empty string if this environment declaration was not
// set using `--dart-define`.
const baseUri = String.fromEnvironment(_debugDevToolsServerFlag);
response = await post(Uri.parse(path.join(baseUri, url)));
} catch (_) {}

return response;
Expand Down Expand Up @@ -107,3 +133,16 @@ extension ResponseExtension on Response {
bool get statusForbidden => statusCode == 403;
bool get statusError => statusCode == 500;
}

class ServerConnectionStorage implements Storage {
@override
Future<String?> getValue(String key) async {
final value = await getPreferenceValue(key);
return value == null ? null : '$value';
}

@override
Future<void> setValue(String key, String value) async {
await setPreferenceValue(key, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:logging/logging.dart';
import '../config_specific/notifications/notifications.dart';
import '../framework/framework_controller.dart';
import '../globals.dart';
import 'server.dart';

final _log = Logger('lib/src/shared/server_api_client');

Expand Down Expand Up @@ -46,7 +47,8 @@ class DevToolsServerConnection {
: baseUri.resolve('api/');

static Future<DevToolsServerConnection?> connect() async {
final apiUri = apiUriFor(Uri.base);
final serverUri = Uri.parse(devToolsServerUriAsString);
final apiUri = apiUriFor(serverUri);
final pingUri = apiUri.resolve('ping');

try {
Expand Down
4 changes: 4 additions & 0 deletions packages/devtools_app/web/flutter_bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const wasmQueryParameterKey = 'wasm';

// Calls the DevTools server API to read the user's wasm preference.
async function getDevToolsWasmPreference() {
// Note: when the DevTools server is running on a different port than the
// DevTools web app, this request path will be incorrect and the request
// will fail. This is okay because DevTools cannot be built with WASM when
// running from `flutter run` anyway.
const request = 'api/getPreferenceValue?key=experiment.wasm';
try {
const response = await fetch(request);
Expand Down
Loading

0 comments on commit 2da31a2

Please sign in to comment.