5
5
library test.runner.browser.content_shell;
6
6
7
7
import 'dart:async' ;
8
+ import 'dart:convert' ;
8
9
import 'dart:io' ;
9
10
11
+ import '../../util/io.dart' ;
10
12
import '../../utils.dart' ;
11
13
import '../application_exception.dart' ;
12
14
import 'browser.dart' ;
@@ -25,43 +27,97 @@ class ContentShell extends Browser {
25
27
26
28
final Future <Uri > observatoryUrl;
27
29
30
+ final Future <Uri > remoteDebuggerUrl;
31
+
28
32
factory ContentShell (url, {String executable, bool debug: false }) {
29
- var completer = new Completer .sync ();
30
- return new ContentShell ._(() async {
33
+ var observatoryCompleter = new Completer .sync ();
34
+ var remoteDebuggerCompleter = new Completer .sync ();
35
+ return new ContentShell ._(() {
31
36
if (executable == null ) executable = _defaultExecutable ();
32
37
33
- var process = await Process .start (
34
- executable, ["--dump-render-tree" , url.toString ()],
35
- environment: {"DART_FLAGS" : "--checked" });
36
-
37
- if (debug) {
38
- completer.complete (lineSplitter.bind (process.stdout).map ((line) {
39
- var match = _observatoryRegExp.firstMatch (line);
40
- if (match == null ) return null ;
41
- return Uri .parse (match[1 ]);
42
- }).where ((uri) => uri != null ).first);
43
- } else {
44
- completer.complete (null );
38
+ tryPort ([port]) async {
39
+ var args = ["--dump-render-tree" , url.toString ()];
40
+ if (port != null ) args.add ("--remote-debugging-port=$port " );
41
+
42
+ var process = await Process .start (executable, args,
43
+ environment: {"DART_FLAGS" : "--checked" });
44
+
45
+ if (debug) {
46
+ observatoryCompleter.complete (lineSplitter.bind (process.stdout)
47
+ .map ((line) {
48
+ var match = _observatoryRegExp.firstMatch (line);
49
+ if (match == null ) return null ;
50
+ return Uri .parse (match[1 ]);
51
+ }).where ((uri) => uri != null ).first);
52
+ } else {
53
+ observatoryCompleter.complete (null );
54
+ }
55
+
56
+ var stderr = new StreamIterator (lineSplitter.bind (process.stderr));
57
+
58
+ // Before we can consider content_shell started successfully, we have to
59
+ // make sure it's not expired and that the remote debugging port worked.
60
+ // Any errors from this will always come before the "Running without
61
+ // renderer sanxbox" message.
62
+ while (await stderr.moveNext () &&
63
+ ! stderr.current.endsWith ("Running without renderer sandbox" )) {
64
+ if (stderr.current == "[dartToStderr]: Dartium build has expired" ) {
65
+ stderr.cancel ();
66
+ process.kill ();
67
+ // TODO(nweiz): link to dartlang.org once it has download links for
68
+ // content shell
69
+ // (https://github.com/dart-lang/www.dartlang.org/issues/1164).
70
+ throw new ApplicationException (
71
+ "You're using an expired content_shell. Upgrade to the latest "
72
+ "version:\n "
73
+ "http://gsdview.appspot.com/dart-archive/channels/stable/"
74
+ "release/latest/dartium/" );
75
+ } else if (stderr.current.contains ("bind() returned an error" )) {
76
+ // If we failed to bind to the port, return null to tell
77
+ // getUnusedPort to try another one.
78
+ stderr.cancel ();
79
+ process.kill ();
80
+ return null ;
81
+ }
82
+ }
83
+
84
+ if (port != null ) {
85
+ remoteDebuggerCompleter.complete (
86
+ _getRemoteDebuggerUrl (Uri .parse ("http://localhost:$port " )));
87
+ } else {
88
+ remoteDebuggerCompleter.complete (null );
89
+ }
90
+
91
+ stderr.cancel ();
92
+ return process;
45
93
}
46
94
47
- lineSplitter.bind (process.stderr).listen ((line) {
48
- if (line != "[dartToStderr]: Dartium build has expired" ) return ;
49
-
50
- // TODO(nweiz): link to dartlang.org once it has download links for
51
- // content shell
52
- // (https://github.com/dart-lang/www.dartlang.org/issues/1164).
53
- throw new ApplicationException (
54
- "You're using an expired content_shell. Upgrade to the latest "
55
- "version:\n "
56
- "http://gsdview.appspot.com/dart-archive/channels/stable/release/"
57
- "latest/dartium/" );
58
- });
59
-
60
- return process;
61
- }, completer.future);
95
+ if (! debug) return tryPort ();
96
+ return getUnusedPort (tryPort);
97
+ }, observatoryCompleter.future, remoteDebuggerCompleter.future);
98
+ }
99
+
100
+ /// Returns the full URL of the remote debugger for the host page.
101
+ ///
102
+ /// This takes the base remote debugger URL (which points to a browser-wide
103
+ /// page) and uses its JSON API to find the resolved URL for debugging the
104
+ /// host page.
105
+ static Future <Uri > _getRemoteDebuggerUrl (Uri base ) async {
106
+ try {
107
+ var client = new HttpClient ();
108
+ var request = await client.getUrl (base .resolve ("/json/list" ));
109
+ var response = await request.close ();
110
+ var json = await JSON .fuse (UTF8 ).decoder.bind (response).single;
111
+ return base .resolve (json.first["devtoolsFrontendUrl" ]);
112
+ } catch (_) {
113
+ // If we fail to talk to the remote debugger protocol, give up and return
114
+ // the raw URL rather than crashing.
115
+ return base ;
116
+ }
62
117
}
63
118
64
- ContentShell ._(Future <Process > startBrowser (), this .observatoryUrl)
119
+ ContentShell ._(Future <Process > startBrowser (), this .observatoryUrl,
120
+ this .remoteDebuggerUrl)
65
121
: super (startBrowser);
66
122
67
123
/// Return the default executable for the current operating system.
0 commit comments