| Index: mojo/public/dart/third_party/test/lib/src/runner/browser/content_shell.dart
 | 
| diff --git a/mojo/public/dart/third_party/test/lib/src/runner/browser/content_shell.dart b/mojo/public/dart/third_party/test/lib/src/runner/browser/content_shell.dart
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..acab425e7baa71cd494fb1be0ea90a58a57907a4
 | 
| --- /dev/null
 | 
| +++ b/mojo/public/dart/third_party/test/lib/src/runner/browser/content_shell.dart
 | 
| @@ -0,0 +1,126 @@
 | 
| +// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 | 
| +// for details. All rights reserved. Use of this source code is governed by a
 | 
| +// BSD-style license that can be found in the LICENSE file.
 | 
| +
 | 
| +library test.runner.browser.content_shell;
 | 
| +
 | 
| +import 'dart:async';
 | 
| +import 'dart:convert';
 | 
| +import 'dart:io';
 | 
| +
 | 
| +import '../../util/io.dart';
 | 
| +import '../../utils.dart';
 | 
| +import '../application_exception.dart';
 | 
| +import 'browser.dart';
 | 
| +
 | 
| +final _observatoryRegExp = new RegExp(r"^Observatory listening on ([^ ]+)");
 | 
| +
 | 
| +/// A class for running an instance of the Dartium content shell.
 | 
| +///
 | 
| +/// Most of the communication with the browser is expected to happen via HTTP,
 | 
| +/// so this exposes a bare-bones API. The browser starts as soon as the class is
 | 
| +/// constructed, and is killed when [close] is called.
 | 
| +///
 | 
| +/// Any errors starting or running the process are reported through [onExit].
 | 
| +class ContentShell extends Browser {
 | 
| +  final name = "Content Shell";
 | 
| +
 | 
| +  final Future<Uri> observatoryUrl;
 | 
| +
 | 
| +  final Future<Uri> remoteDebuggerUrl;
 | 
| +
 | 
| +  factory ContentShell(url, {String executable, bool debug: false}) {
 | 
| +    var observatoryCompleter = new Completer.sync();
 | 
| +    var remoteDebuggerCompleter = new Completer.sync();
 | 
| +    return new ContentShell._(() {
 | 
| +      if (executable == null) executable = _defaultExecutable();
 | 
| +
 | 
| +      tryPort([port]) async {
 | 
| +        var args = ["--dump-render-tree", url.toString()];
 | 
| +        if (port != null) args.add("--remote-debugging-port=$port");
 | 
| +
 | 
| +        var process = await Process.start(executable, args,
 | 
| +            environment: {"DART_FLAGS": "--checked"});
 | 
| +
 | 
| +        if (debug) {
 | 
| +          observatoryCompleter.complete(lineSplitter.bind(process.stdout)
 | 
| +              .map((line) {
 | 
| +            var match = _observatoryRegExp.firstMatch(line);
 | 
| +            if (match == null) return null;
 | 
| +            return Uri.parse(match[1]);
 | 
| +          }).where((uri) => uri != null).first);
 | 
| +        } else {
 | 
| +          observatoryCompleter.complete(null);
 | 
| +        }
 | 
| +
 | 
| +        var stderr = new StreamIterator(lineSplitter.bind(process.stderr));
 | 
| +
 | 
| +        // Before we can consider content_shell started successfully, we have to
 | 
| +        // make sure it's not expired and that the remote debugging port worked.
 | 
| +        // Any errors from this will always come before the "Running without
 | 
| +        // renderer sanxbox" message.
 | 
| +        while (await stderr.moveNext() &&
 | 
| +            !stderr.current.endsWith("Running without renderer sandbox")) {
 | 
| +          if (stderr.current == "[dartToStderr]: Dartium build has expired") {
 | 
| +            stderr.cancel();
 | 
| +            process.kill();
 | 
| +            // TODO(nweiz): link to dartlang.org once it has download links for
 | 
| +            // content shell
 | 
| +            // (https://github.com/dart-lang/www.dartlang.org/issues/1164).
 | 
| +            throw new ApplicationException(
 | 
| +                "You're using an expired content_shell. Upgrade to the latest "
 | 
| +                    "version:\n"
 | 
| +                "http://gsdview.appspot.com/dart-archive/channels/stable/"
 | 
| +                    "release/latest/dartium/");
 | 
| +          } else if (stderr.current.contains("bind() returned an error")) {
 | 
| +            // If we failed to bind to the port, return null to tell
 | 
| +            // getUnusedPort to try another one.
 | 
| +            stderr.cancel();
 | 
| +            process.kill();
 | 
| +            return null;
 | 
| +          }
 | 
| +        }
 | 
| +
 | 
| +        if (port != null) {
 | 
| +          remoteDebuggerCompleter.complete(
 | 
| +              _getRemoteDebuggerUrl(Uri.parse("http://localhost:$port")));
 | 
| +        } else {
 | 
| +          remoteDebuggerCompleter.complete(null);
 | 
| +        }
 | 
| +
 | 
| +        stderr.cancel();
 | 
| +        return process;
 | 
| +      }
 | 
| +
 | 
| +      if (!debug) return tryPort();
 | 
| +      return getUnusedPort(tryPort);
 | 
| +    }, observatoryCompleter.future, remoteDebuggerCompleter.future);
 | 
| +  }
 | 
| +
 | 
| +  /// Returns the full URL of the remote debugger for the host page.
 | 
| +  ///
 | 
| +  /// This takes the base remote debugger URL (which points to a browser-wide
 | 
| +  /// page) and uses its JSON API to find the resolved URL for debugging the
 | 
| +  /// host page.
 | 
| +  static Future<Uri> _getRemoteDebuggerUrl(Uri base) async {
 | 
| +    try {
 | 
| +      var client = new HttpClient();
 | 
| +      var request = await client.getUrl(base.resolve("/json/list"));
 | 
| +      var response = await request.close();
 | 
| +      var json = await JSON.fuse(UTF8).decoder.bind(response).single;
 | 
| +      return base.resolve(json.first["devtoolsFrontendUrl"]);
 | 
| +    } catch (_) {
 | 
| +      // If we fail to talk to the remote debugger protocol, give up and return
 | 
| +      // the raw URL rather than crashing.
 | 
| +      return base;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  ContentShell._(Future<Process> startBrowser(), this.observatoryUrl,
 | 
| +          this.remoteDebuggerUrl)
 | 
| +      : super(startBrowser);
 | 
| +
 | 
| +  /// Return the default executable for the current operating system.
 | 
| +  static String _defaultExecutable() =>
 | 
| +      Platform.isWindows ? "content_shell.exe" : "content_shell";
 | 
| +}
 | 
| 
 |