blob: 682f6ea8e2eaf4baa2ad4c1de7b7eb04b7ce2f29 [file] [log] [blame]
// 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.
import 'dart:async';
import 'dart:io';
import '../../backend/test_platform.dart';
import '../../util/io.dart';
import '../../utils.dart';
import '../application_exception.dart';
import '../executable_settings.dart';
import 'browser.dart';
import 'default_settings.dart';
final _observatoryRegExp = new RegExp(r"^Observatory listening on ([^ ]+)");
final _errorTimeout = const Duration(seconds: 10);
/// 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, {ExecutableSettings settings, bool debug: false}) {
settings ??= defaultSettings[TestPlatform.contentShell];
var observatoryCompleter = new Completer<Uri>.sync();
var remoteDebuggerCompleter = new Completer<Uri>.sync();
return new ContentShell._(() {
var tryPort = ([int port]) async {
var args = ["--dump-render-tree", url.toString()]
..addAll(settings.arguments);
if (port != null) args.add("--remote-debugging-port=$port");
var process = await Process.start(settings.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 sandbox" message.
while (await stderr.moveNext().timeout(_errorTimeout).catchError((_) =>
throw new ApplicationException(
"Error starting up content_shell.\n"
"Ensure you are using the latest version:\n"
"http://gsdview.appspot.com/dart-archive/channels/stable/"
"release/latest/dartium/")) &&
!stderr.current.endsWith("Running without renderer sandbox") &&
!stderr.current.contains("Running without the SUID sandbox") &&
// Error messages on Mac can get gobbled, we must assume that it
// it started up successfully.
!stderr.current
.contains("kq_init: detected broken kqueue; not using")) {
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<Process>(tryPort);
}, observatoryCompleter.future, remoteDebuggerCompleter.future);
}
ContentShell._(Future<Process> startBrowser(), this.observatoryUrl,
this.remoteDebuggerUrl)
: super(startBrowser);
}