Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(897)

Side by Side Diff: lib/src/runner/browser/dartium.dart

Issue 1258363003: Expose the Observatory URL when debugging. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/runner/browser/content_shell.dart ('k') | lib/src/runner/browser/server.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library test.runner.browser.dartium; 5 library test.runner.browser.dartium;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:convert';
8 import 'dart:io'; 9 import 'dart:io';
9 10
11 import 'package:async/async.dart';
10 import 'package:path/path.dart' as p; 12 import 'package:path/path.dart' as p;
11 13
14 import '../../util/cancelable_future.dart';
12 import '../../util/io.dart'; 15 import '../../util/io.dart';
16 import '../../utils.dart';
13 import 'browser.dart'; 17 import 'browser.dart';
14 18
19 final _observatoryRegExp = new RegExp(r"^Observatory listening on ([^ ]+)");
20
15 /// A class for running an instance of Dartium. 21 /// A class for running an instance of Dartium.
16 /// 22 ///
17 /// Most of the communication with the browser is expected to happen via HTTP, 23 /// Most of the communication with the browser is expected to happen via HTTP,
18 /// so this exposes a bare-bones API. The browser starts as soon as the class is 24 /// so this exposes a bare-bones API. The browser starts as soon as the class is
19 /// constructed, and is killed when [close] is called. 25 /// constructed, and is killed when [close] is called.
20 /// 26 ///
21 /// Any errors starting or running the process are reported through [onExit]. 27 /// Any errors starting or running the process are reported through [onExit].
22 class Dartium extends Browser { 28 class Dartium extends Browser {
23 final name = "Dartium"; 29 final name = "Dartium";
24 30
25 Dartium(url, {String executable}) 31 final Future<Uri> observatoryUrl;
26 : super(() => _startBrowser(url, executable)); 32
33 factory Dartium(url, {String executable}) {
34 var completer = new Completer.sync();
35 return new Dartium._(() async {
36 if (executable == null) executable = _defaultExecutable();
37
38 var dir = createTempDir();
39 var process = await Process.start(executable, [
40 "--user-data-dir=$dir",
41 url.toString(),
42 "--disable-extensions",
43 "--disable-popup-blocking",
44 "--bwsi",
45 "--no-first-run",
46 "--no-default-browser-check",
47 "--disable-default-apps",
48 "--disable-translate"
49 ], environment: {"DART_FLAGS": "--checked"});
50
51 // The first observatory URL emitted is for the empty start page; the
52 // second is actually for the host page.
53 completer.complete(_getObservatoryUrl(process.stdout));
54
55 process.exitCode
56 .then((_) => new Directory(dir).deleteSync(recursive: true));
57
58 return process;
59 }, completer.future);
60 }
61
62 Dartium._(Future<Process> startBrowser(), this.observatoryUrl)
63 : super(startBrowser);
27 64
28 /// Starts a new instance of Dartium open to the given [url], which may be a 65 /// Starts a new instance of Dartium open to the given [url], which may be a
29 /// [Uri] or a [String]. 66 /// [Uri] or a [String].
30 /// 67 ///
31 /// If [executable] is passed, it's used as the Dartium executable. Otherwise 68 /// If [executable] is passed, it's used as the Dartium executable. Otherwise
32 /// the default executable name for the current OS will be used. 69 /// the default executable name for the current OS will be used.
33 static Future<Process> _startBrowser(url, [String executable]) async {
34 if (executable == null) executable = _defaultExecutable();
35
36 var dir = createTempDir();
37 var process = await Process.start(executable, [
38 "--user-data-dir=$dir",
39 url.toString(),
40 "--disable-extensions",
41 "--disable-popup-blocking",
42 "--bwsi",
43 "--no-first-run",
44 "--no-default-browser-check",
45 "--disable-default-apps",
46 "--disable-translate"
47 ], environment: {"DART_FLAGS": "--checked"});
48
49 process.exitCode
50 .then((_) => new Directory(dir).deleteSync(recursive: true));
51
52 return process;
53 }
54 70
55 /// Return the default executable for the current operating system. 71 /// Return the default executable for the current operating system.
56 static String _defaultExecutable() { 72 static String _defaultExecutable() {
57 var dartium = _executableInEditor(); 73 var dartium = _executableInEditor();
58 if (dartium != null) return dartium; 74 if (dartium != null) return dartium;
59 return Platform.isWindows ? "dartium.exe" : "dartium"; 75 return Platform.isWindows ? "dartium.exe" : "dartium";
60 } 76 }
61 77
62 static String _executableInEditor() { 78 static String _executableInEditor() {
63 var dir = p.dirname(sdkDir); 79 var dir = p.dirname(sdkDir);
(...skipping 15 matching lines...) Expand all
79 dir, "chromium/Chromium.app/Contents/MacOS/Chromium"); 95 dir, "chromium/Chromium.app/Contents/MacOS/Chromium");
80 return new File(dartium).existsSync() ? dartium : null; 96 return new File(dartium).existsSync() ? dartium : null;
81 } 97 }
82 98
83 assert(Platform.isLinux); 99 assert(Platform.isLinux);
84 if (!new File(p.join(dir, "DartEditor")).existsSync()) return null; 100 if (!new File(p.join(dir, "DartEditor")).existsSync()) return null;
85 101
86 var dartium = p.join(dir, "chromium", "chrome"); 102 var dartium = p.join(dir, "chromium", "chrome");
87 return new File(dartium).existsSync() ? dartium : null; 103 return new File(dartium).existsSync() ? dartium : null;
88 } 104 }
105
106 // TODO(nweiz): simplify this when sdk#23923 is fixed.
107 /// Returns the Observatory URL for the Dartium executable with the given
108 /// [stdout] stream, or `null` if the correct one couldn't be found.
109 ///
110 /// Dartium prints out three different Observatory URLs when it starts. Only
111 /// one of them is connected to the VM instance running the host page, and the
112 /// ordering isn't guaranteed, so we need to figure out which one is correct.
113 /// We do so by connecting to the VM service via WebSockets and looking for
114 /// the Observatory instance that actually contains an isolate, and returning
115 /// the corresponding URI.
116 static Future<Uri> _getObservatoryUrl(Stream<List<int>> stdout) async {
117 var urlQueue = new StreamQueue(lineSplitter.bind(stdout).map((line) {
118 var match = _observatoryRegExp.firstMatch(line);
119 return match == null ? null : Uri.parse(match[1]);
120 }).where((line) => line != null));
121
122 var futures = [
123 urlQueue.next,
124 urlQueue.next,
125 urlQueue.next
126 ].map(_checkObservatoryUrl);
127
128 urlQueue.cancel();
129
130 /// Dartium will print three possible observatory URLs. For each one, we
131 /// check whether it's actually connected to an isolate, indicating that
132 /// it's the observatory for the main page. Once we find the one that is, we
133 /// cancel the other requests and return it.
134 return inCompletionOrder(futures)
135 .firstWhere((url) => url != null, defaultValue: () => null);
136 }
137
138 /// If the URL returned by [future] corresponds to the correct Observatory
139 /// instance, returns it. Otherwise, returns `null`.
140 ///
141 /// If the returned future is canceled before it fires, the WebSocket
142 /// connection with the given Observatory will be closed immediately.
143 static CancelableFuture<Uri> _checkObservatoryUrl(Future<Uri> future) {
144 var webSocket;
145 var canceled = false;
146 var completer = new CancelableCompleter(() {
147 canceled = true;
148 if (webSocket != null) webSocket.close();
149 });
150
151 // We've encountered a format we don't understand. Close the web socket and
152 // complete to null.
153 giveUp() {
154 webSocket.close();
155 if (!completer.isCompleted) completer.complete();
156 }
157
158 future.then((url) async {
159 try {
160 webSocket = await WebSocket.connect(
161 url.replace(scheme: 'ws', path: '/ws').toString());
162 if (canceled) {
163 webSocket.close();
164 return null;
165 }
166
167 webSocket.add(JSON.encode({
168 "jsonrpc": "2.0",
169 "method": "streamListen",
170 "params": {"streamId": "Isolate"},
171 "id": "0"
172 }));
173
174 webSocket.add(JSON.encode({
175 "jsonrpc": "2.0",
176 "method": "getVM",
177 "params": {},
178 "id": "1"
179 }));
180
181 webSocket.listen((response) {
182 try {
183 response = JSON.decode(response);
184 } on FormatException catch (_) {
185 giveUp();
186 return;
187 }
188
189 // If there's a "response" key, we're probably talking to the pre-1.0
190 // VM service protocol, in which case we should just give up.
191 if (response is! Map || response.containsKey("response")) {
192 giveUp();
193 return;
194 }
195
196 if (response["id"] == "0") return;
197
198 if (response["id"] == "1") {
199 var result = response["result"];
200 if (result is! Map) {
201 giveUp();
202 return;
203 }
204
205 var isolates = result["isolates"];
206 if (isolates is! List) {
207 giveUp();
208 return;
209 }
210
211 if (isolates.isNotEmpty) {
212 webSocket.close();
213 if (!completer.isCompleted) completer.complete(url);
214 }
215 return;
216 }
217
218 // The 1.0 protocol used a raw "event" key, while the 2.0 protocol
219 // wraps it in JSON-RPC method params.
220 var event;
221 if (response.containsKey("event")) {
222 event = response["event"];
223 } else {
224 var params = response["params"];
225 if (params is Map) event = params["event"];
226 }
227
228 if (event is! Map) {
229 giveUp();
230 return;
231 }
232
233 if (event["kind"] != "IsolateStart") return;
234 webSocket.close();
235 if (completer.isCompleted) return;
236
237 // TODO(nweiz): include the isolate ID in the URL?
238 completer.complete(url);
239 });
240 } on IOException catch (_) {
241 // IO exceptions are probably caused by connecting to an
242 // incorrect WebSocket that already closed.
243 return null;
244 }
245 }).catchError((error, stackTrace) {
246 if (!completer.isCompleted) completer.completeError(error, stackTrace);
247 });
248
249 return completer.future;
250 }
89 } 251 }
OLDNEW
« no previous file with comments | « lib/src/runner/browser/content_shell.dart ('k') | lib/src/runner/browser/server.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698