Skip to content

Commit 5e17922

Browse files
authored
Revert PR #9372: Un-delete packages/devtools_shared/lib/src/test/chrome.dart (#9409)
1 parent 7da2acd commit 5e17922

File tree

2 files changed

+276
-0
lines changed

2 files changed

+276
-0
lines changed

packages/devtools_shared/lib/devtools_test_utils.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
44

5+
export 'src/test/chrome.dart';
56
export 'src/test/chrome_driver.dart';
67
export 'src/test/cli_test_driver.dart';
78
export 'src/test/integration_test_runner.dart';
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// Copyright 2018 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
4+
5+
// ignore_for_file: avoid_print
6+
7+
// DO NOT DELETE THIS FILE. It is imported by DDS and used in the
8+
// devtools_server tests.
9+
// TODO(https://github.com/flutter/devtools/issues/9408): Move this into
10+
// package:dds instead.
11+
12+
import 'dart:async';
13+
import 'dart:io';
14+
15+
import 'package:path/path.dart' as path;
16+
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
17+
hide ChromeTab;
18+
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
19+
as wip show ChromeTab;
20+
21+
import 'io_utils.dart';
22+
23+
// Change this if you want to be able to see Chrome opening while tests run
24+
// to aid debugging.
25+
const useChromeHeadless = true;
26+
27+
// Chrome headless currently hangs on Windows (both Travis and locally),
28+
// so we won't use it there.
29+
final headlessModeIsSupported = !Platform.isWindows;
30+
31+
class Chrome {
32+
Chrome._(this.executable);
33+
34+
static Chrome? from(String executable) {
35+
return FileSystemEntity.isFileSync(executable)
36+
? Chrome._(executable)
37+
: null;
38+
}
39+
40+
static Chrome? locate() {
41+
if (Platform.isMacOS) {
42+
const defaultPath = '/Applications/Google Chrome.app';
43+
const bundlePath = 'Contents/MacOS/Google Chrome';
44+
45+
if (FileSystemEntity.isDirectorySync(defaultPath)) {
46+
return Chrome.from(path.join(defaultPath, bundlePath));
47+
}
48+
} else if (Platform.isLinux) {
49+
const defaultPath = '/usr/bin/google-chrome';
50+
51+
if (FileSystemEntity.isFileSync(defaultPath)) {
52+
return Chrome.from(defaultPath);
53+
}
54+
} else if (Platform.isWindows) {
55+
final progFiles = Platform.environment['PROGRAMFILES(X86)']!;
56+
final chromeInstall = '$progFiles\\Google\\Chrome';
57+
final defaultPath = '$chromeInstall\\Application\\chrome.exe';
58+
59+
if (FileSystemEntity.isFileSync(defaultPath)) {
60+
return Chrome.from(defaultPath);
61+
}
62+
}
63+
64+
final pathFromEnv = Platform.environment['CHROME_PATH'];
65+
if (pathFromEnv != null && FileSystemEntity.isFileSync(pathFromEnv)) {
66+
return Chrome.from(pathFromEnv);
67+
}
68+
69+
// TODO(devoncarew): check default install locations for linux
70+
// TODO(devoncarew): try `which` on mac, linux
71+
72+
return null;
73+
}
74+
75+
/// Return the path to a per-user Chrome data dir.
76+
///
77+
/// This method will create the directory if it does not exist.
78+
static String getCreateChromeDataDir() {
79+
final prefsDir = getDartPrefsDirectory();
80+
final chromeDataDir = Directory(path.join(prefsDir.path, 'chrome'));
81+
if (!chromeDataDir.existsSync()) {
82+
chromeDataDir.createSync(recursive: true);
83+
}
84+
return chromeDataDir.path;
85+
}
86+
87+
final String executable;
88+
89+
Future<ChromeProcess> start({String? url, int debugPort = 9222}) {
90+
final args = <String>[
91+
'--no-default-browser-check',
92+
'--no-first-run',
93+
'--user-data-dir=${getCreateChromeDataDir()}',
94+
'--remote-debugging-port=$debugPort',
95+
];
96+
if (useChromeHeadless && headlessModeIsSupported) {
97+
args.addAll(<String>[
98+
'--headless',
99+
'--disable-gpu',
100+
]);
101+
}
102+
if (url != null) {
103+
args.add(url);
104+
}
105+
return Process.start(executable, args).then((Process process) {
106+
return ChromeProcess(process, debugPort);
107+
});
108+
}
109+
}
110+
111+
class ChromeProcess {
112+
ChromeProcess(this.process, this.debugPort);
113+
114+
final Process process;
115+
final int debugPort;
116+
bool _processAlive = true;
117+
118+
Future<ChromeTab?> connectToTab(
119+
String url, {
120+
Duration timeout = const Duration(seconds: 20),
121+
}) async {
122+
return await _connectToTab(
123+
connection: ChromeConnection(Uri.parse(url).host, debugPort),
124+
tabFound: (tab) => tab.url == url,
125+
timeout: timeout,
126+
);
127+
}
128+
129+
Future<ChromeTab?> connectToTabId(
130+
String host,
131+
String id, {
132+
Duration timeout = const Duration(seconds: 20),
133+
}) async {
134+
return await _connectToTab(
135+
connection: ChromeConnection(host, debugPort),
136+
tabFound: (tab) => tab.id == id,
137+
timeout: timeout,
138+
);
139+
}
140+
141+
Future<ChromeTab?> getFirstTab({
142+
Duration timeout = const Duration(seconds: 20),
143+
}) async {
144+
return await _connectToTab(
145+
connection: ChromeConnection('localhost', debugPort),
146+
tabFound: (tab) => !tab.isBackgroundPage && !tab.isChromeExtension,
147+
timeout: timeout,
148+
);
149+
}
150+
151+
Future<ChromeTab?> _connectToTab({
152+
required ChromeConnection connection,
153+
required bool Function(wip.ChromeTab) tabFound,
154+
required Duration timeout,
155+
}) async {
156+
final wipTab = await connection.getTab(
157+
(wip.ChromeTab tab) {
158+
return tabFound(tab);
159+
},
160+
retryFor: timeout,
161+
);
162+
163+
unawaited(
164+
process.exitCode.then((_) {
165+
_processAlive = false;
166+
}),
167+
);
168+
169+
return wipTab == null ? null : ChromeTab(wipTab);
170+
}
171+
172+
bool get isAlive => _processAlive;
173+
174+
/// Returns `true` if the signal is successfully delivered to the process.
175+
/// Otherwise the signal could not be sent, usually meaning that the process
176+
/// is already dead.
177+
bool kill() => process.kill();
178+
179+
Future<int> get onExit => process.exitCode;
180+
}
181+
182+
class ChromeTab {
183+
ChromeTab(this.wipTab);
184+
185+
final wip.ChromeTab wipTab;
186+
WipConnection? _wip;
187+
188+
final _entryAddedController = StreamController<LogEntry>.broadcast();
189+
final _consoleAPICalledController =
190+
StreamController<ConsoleAPIEvent>.broadcast();
191+
final _exceptionThrownController =
192+
StreamController<ExceptionThrownEvent>.broadcast();
193+
194+
num? _lostConnectionTime;
195+
196+
Future<WipConnection?> connect({bool verbose = false}) async {
197+
_wip = await wipTab.connect();
198+
final wipConnection = _wip!;
199+
200+
await wipConnection.log.enable();
201+
wipConnection.log.onEntryAdded.listen((LogEntry entry) {
202+
if (_lostConnectionTime == null ||
203+
entry.timestamp > _lostConnectionTime!) {
204+
_entryAddedController.add(entry);
205+
}
206+
});
207+
208+
await wipConnection.runtime.enable();
209+
wipConnection.runtime.onConsoleAPICalled.listen((ConsoleAPIEvent event) {
210+
if (_lostConnectionTime == null ||
211+
event.timestamp > _lostConnectionTime!) {
212+
_consoleAPICalledController.add(event);
213+
}
214+
});
215+
216+
unawaited(
217+
_exceptionThrownController
218+
.addStream(wipConnection.runtime.onExceptionThrown),
219+
);
220+
221+
unawaited(wipConnection.page.enable());
222+
223+
wipConnection.onClose.listen((_) {
224+
_wip = null;
225+
_disconnectStream.add(null);
226+
_lostConnectionTime = DateTime.now().millisecondsSinceEpoch;
227+
});
228+
229+
if (verbose) {
230+
onLogEntryAdded.listen((entry) {
231+
print('chrome • log:${entry.source} • ${entry.text} ${entry.url}');
232+
});
233+
234+
onConsoleAPICalled.listen((entry) {
235+
print(
236+
'chrome • console:${entry.type} • '
237+
'${entry.args.map((a) => a.value).join(', ')}',
238+
);
239+
});
240+
241+
onExceptionThrown.listen((ex) {
242+
throw 'JavaScript exception occurred: ${ex.method}\n\n'
243+
'${ex.params}\n\n'
244+
'${ex.exceptionDetails.text}\n\n'
245+
'${ex.exceptionDetails.exception}\n\n'
246+
'${ex.exceptionDetails.stackTrace}';
247+
});
248+
}
249+
250+
return _wip;
251+
}
252+
253+
Future<String> createNewTarget() {
254+
return _wip!.target.createTarget('about:blank');
255+
}
256+
257+
bool get isConnected => _wip != null;
258+
259+
Stream<void> get onDisconnect => _disconnectStream.stream;
260+
final _disconnectStream = StreamController<void>.broadcast();
261+
262+
Stream<LogEntry> get onLogEntryAdded => _entryAddedController.stream;
263+
264+
Stream<ConsoleAPIEvent> get onConsoleAPICalled =>
265+
_consoleAPICalledController.stream;
266+
267+
Stream<ExceptionThrownEvent> get onExceptionThrown =>
268+
_exceptionThrownController.stream;
269+
270+
Future<WipResponse> reload() => _wip!.page.reload();
271+
272+
Future<WipResponse> navigate(String url) => _wip!.page.navigate(url);
273+
274+
WipConnection? get wipConnection => _wip;
275+
}

0 commit comments

Comments
 (0)