serveDevTools method

Future<HttpServer?> serveDevTools({
  1. bool enableStdinCommands = true,
  2. bool machineMode = false,
  3. bool debugMode = false,
  4. bool launchBrowser = false,
  5. bool enableNotifications = false,
  6. bool allowEmbedding = true,
  7. bool headlessMode = false,
  8. bool verboseMode = false,
  9. bool printDtdUri = false,
  10. String? hostname,
  11. String? customDevToolsPath,
  12. int port = 0,
  13. int numPortsToTry = defaultTryPorts,
  14. Handler? handler,
  15. String? serviceProtocolUri,
  16. String? profileFilename,
  17. String? appSizeBase,
  18. String? appSizeTest,
  19. String? dtdUri,
})

Serves DevTools.

handler is the shelf.Handler that the server will use for all requests. If null, defaultHandler will be used. Defaults to null.

customDevToolsPath is a path to a directory containing a pre-built DevTools application.

Implementation

// Note: this method is used by the Dart CLI and by package:dwds.
Future<HttpServer?> serveDevTools({
  bool enableStdinCommands = true,
  bool machineMode = false,
  bool debugMode = false,
  bool launchBrowser = false,
  bool enableNotifications = false,
  bool allowEmbedding = true,
  bool headlessMode = false,
  bool verboseMode = false,
  bool printDtdUri = false,
  String? hostname,
  String? customDevToolsPath,
  int port = 0,
  int numPortsToTry = defaultTryPorts,
  shelf.Handler? handler,
  String? serviceProtocolUri,
  String? profileFilename,
  String? appSizeBase,
  String? appSizeTest,
  String? dtdUri,
}) async {
  hostname ??= 'localhost';

  // Collect profiling information.
  if (profileFilename != null && serviceProtocolUri != null) {
    final Uri? vmServiceUri = Uri.tryParse(serviceProtocolUri);
    if (vmServiceUri != null) {
      await _hookupMemoryProfiling(
        vmServiceUri,
        profileFilename,
        verboseMode,
      );
    }
    return null;
  }

  if (machineMode) {
    assert(
      enableStdinCommands,
      'machineMode only works with enableStdinCommands.',
    );
  }

  clientManager = ClientManager(
    requestNotificationPermissions: enableNotifications,
  );

  String? dtdSecret;
  if (dtdUri == null) {
    final (:uri, :secret) = await startDtd(
      machineMode: machineMode,
      printDtdUri: printDtdUri,
    );
    dtdUri = uri;
    dtdSecret = secret;
  }

  handler ??= await defaultHandler(
    buildDir: customDevToolsPath!,
    clientManager: clientManager,
    dtd: (uri: dtdUri, secret: dtdSecret),
    devtoolsExtensionsManager: ExtensionsManager(),
  );

  HttpServer? server;
  SocketException? ex;
  while (server == null && numPortsToTry >= 0) {
    // If we have tried [numPortsToTry] ports and still have not been able to
    // connect, try port 0 to find a random available port.
    if (numPortsToTry == 0) port = 0;

    try {
      server = await HttpMultiServer.bind(hostname, port);
    } on SocketException catch (e) {
      ex = e;
      numPortsToTry--;
      port++;
    }
  }

  // Re-throw the last exception if we failed to bind.
  if (server == null && ex != null) {
    throw ex;
  }

  // Type promote server.
  server!;

  if (allowEmbedding) {
    server.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
    // The origin-agent-cluster header is required to support the embedding of
    // Dart DevTools in Chrome DevTools.
    server.defaultResponseHeaders.add('origin-agent-cluster', '?1');
  }

  // Ensure browsers don't cache older versions of the app.
  server.defaultResponseHeaders.add(
    HttpHeaders.cacheControlHeader,
    'no-store',
  );

  // Add the headers required to serve with wasm.
  server.defaultResponseHeaders
    ..add('Cross-Origin-Embedder-Policy', 'credentialless')
    ..add('Cross-Origin-Opener-Policy', 'same-origin')
    ..add('Cross-Origin-Resource-Policy', 'cross-origin');

  // Serve requests in an error zone to prevent failures
  // when running from another error zone.
  runZonedGuarded(
    () => shelf.serveRequests(server!, handler!),
    (e, _) => print('Error serving requests: $e'),
  );

  final devToolsUrl = 'http://${server.address.host}:${server.port}';

  if (launchBrowser) {
    if (serviceProtocolUri != null) {
      serviceProtocolUri =
          normalizeVmServiceUri(serviceProtocolUri).toString();
    }

    final queryParameters = {
      if (serviceProtocolUri != null) 'uri': serviceProtocolUri,
      if (appSizeBase != null) 'appSizeBase': appSizeBase,
      if (appSizeTest != null) 'appSizeTest': appSizeTest,
    };
    String url = Uri.parse(devToolsUrl)
        .replace(queryParameters: queryParameters)
        .toString();

    // If app size parameters are present, open to the standalone `app-size`
    // page, regardless if there is a vm service uri specified. We only check
    // for the presence of [appSizeBase] here because [appSizeTest] may or may
    // not be specified (it should only be present for diffs). If [appSizeTest]
    // is present without [appSizeBase], we will ignore the parameter.
    if (appSizeBase != null) {
      final startQueryParamIndex = url.indexOf('?');
      if (startQueryParamIndex != -1) {
        url = '${url.substring(0, startQueryParamIndex)}'
            '/#/app-size'
            '${url.substring(startQueryParamIndex)}';
      }
    }

    try {
      await Chrome.start([url]);
    } catch (e) {
      print('Unable to launch Chrome: $e\n');
    }
  }

  if (enableStdinCommands) {
    String message = '''Serving DevTools at $devToolsUrl.

        Hit ctrl-c to terminate the server.''';
    if (!machineMode && debugMode) {
      // Add bold to help find the correct url to open.
      message = ConsoleUtils.bold('$message\n');
    }

    DevToolsUtils.printOutput(
      message,
      {
        'event': 'server.started',
        // TODO(dantup): Remove this `method` field when we're sure VS Code
        // users are all on a newer version that uses `event`. We incorrectly
        // used `method` for the original releases.
        'method': 'server.started',
        'params': {
          'host': server.address.host,
          'port': server.port,
          'pid': pid,
          'protocolVersion': protocolVersion,
        }
      },
      machineMode: machineMode,
    );

    if (machineMode) {
      _machineModeCommandHandler = MachineModeCommandHandler(server: this);
      await _machineModeCommandHandler!.initialize(
        devToolsUrl: devToolsUrl,
        headlessMode: headlessMode,
      );
    }
  }

  return server;
}