• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:dwds/dwds.dart';
8import 'package:meta/meta.dart';
9import 'package:vm_service/vm_service.dart' as vmservice;
10
11import '../application_package.dart';
12import '../base/common.dart';
13import '../base/file_system.dart';
14import '../base/logger.dart';
15import '../base/terminal.dart';
16import '../base/utils.dart';
17import '../build_info.dart';
18import '../convert.dart';
19import '../device.dart';
20import '../globals.dart';
21import '../project.dart';
22import '../resident_runner.dart';
23import '../web/web_runner.dart';
24import 'web_fs.dart';
25
26/// Injectable factory to create a [ResidentWebRunner].
27class DwdsWebRunnerFactory extends WebRunnerFactory {
28  @override
29  ResidentRunner createWebRunner(
30    Device device, {
31    String target,
32    @required FlutterProject flutterProject,
33    @required bool ipv6,
34    @required DebuggingOptions debuggingOptions
35  }) {
36    return ResidentWebRunner(
37      device,
38      target: target,
39      flutterProject: flutterProject,
40      debuggingOptions: debuggingOptions,
41      ipv6: ipv6,
42    );
43  }
44}
45
46// TODO(jonahwilliams): remove this constant when the error message is removed.
47// The web engine is currently spamming this message on certain pages. Filter it out
48// until we remove it entirely. See flutter/flutter##37625.
49const String _kBadError = 'WARNING: 3D transformation matrix was passed to BitmapCanvas.';
50
51/// A hot-runner which handles browser specific delegation.
52class ResidentWebRunner extends ResidentRunner {
53  ResidentWebRunner(this.device, {
54    String target,
55    @required this.flutterProject,
56    @required bool ipv6,
57    @required DebuggingOptions debuggingOptions,
58  }) : super(
59          <FlutterDevice>[],
60          target: target,
61          debuggingOptions: debuggingOptions,
62          ipv6: ipv6,
63          usesTerminalUi: true,
64          stayResident: true,
65        );
66
67  final Device device;
68  final FlutterProject flutterProject;
69
70  WebFs _webFs;
71  DebugConnection _debugConnection;
72  StreamSubscription<vmservice.Event> _stdOutSub;
73
74  vmservice.VmService get _vmService => _debugConnection.vmService;
75
76  @override
77  bool get canHotRestart {
78    return true;
79  }
80
81  @override
82  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRawOnFirstIsolate(
83    String method, {
84    Map<String, dynamic> params,
85  }) async {
86    final vmservice.Response response = await _vmService.callServiceExtension(method, args: params);
87    return response.toJson();
88  }
89
90  @override
91  Future<void> cleanupAfterSignal() async {
92    await _cleanup();
93  }
94
95  @override
96  Future<void> cleanupAtFinish() async {
97    await _cleanup();
98  }
99
100  Future<void> _cleanup() async {
101    await _debugConnection?.close();
102    await _stdOutSub?.cancel();
103    await _webFs?.stop();
104  }
105
106  @override
107  void printHelp({bool details = true}) {
108    if (details) {
109      return printHelpDetails();
110    }
111    const String fire = '��';
112    const String rawMessage =
113        '  To hot restart (and rebuild state), press "R".';
114    final String message = terminal.color(
115      fire + terminal.bolden(rawMessage),
116      TerminalColor.red,
117    );
118    const String warning = '�� ';
119    printStatus(warning * 20);
120    printStatus('Warning: Flutter\'s support for building web applications is highly experimental.');
121    printStatus('For more information see https://github.com/flutter/flutter/issues/34082.');
122    printStatus(warning * 20);
123    printStatus('');
124    printStatus(message);
125    const String quitMessage = 'To quit, press "q".';
126    printStatus('For a more detailed help message, press "h". $quitMessage');
127  }
128
129  @override
130  Future<int> run({
131    Completer<DebugConnectionInfo> connectionInfoCompleter,
132    Completer<void> appStartedCompleter,
133    String route,
134  }) async {
135    final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform(
136      TargetPlatform.web_javascript,
137      applicationBinary: null,
138    );
139    if (package == null) {
140      printError('No application found for TargetPlatform.web_javascript.');
141      printError('To add web support to a project, run `flutter create --web .`.');
142      return 1;
143    }
144    if (!fs.isFileSync(mainPath)) {
145      String message = 'Tried to run $mainPath, but that file does not exist.';
146      if (target == null) {
147        message +=
148            '\nConsider using the -t option to specify the Dart file to start.';
149      }
150      printError(message);
151      return 1;
152    }
153    Status buildStatus;
154    try {
155      buildStatus = logger.startProgress('Building application for the web...', timeout: null);
156      _webFs = await webFsFactory(
157        target: target,
158        flutterProject: flutterProject,
159        buildInfo: debuggingOptions.buildInfo,
160      );
161      if (supportsServiceProtocol) {
162        _debugConnection = await _webFs.runAndDebug();
163      }
164    } catch (err, stackTrace) {
165      printError(err.toString());
166      printError(stackTrace.toString());
167      throwToolExit('Failed to build application for the web.');
168    } finally {
169      buildStatus.stop();
170    }
171    appStartedCompleter?.complete();
172    return attach(
173      connectionInfoCompleter: connectionInfoCompleter,
174      appStartedCompleter: appStartedCompleter,
175    );
176  }
177
178  @override
179  Future<int> attach({
180    Completer<DebugConnectionInfo> connectionInfoCompleter,
181    Completer<void> appStartedCompleter,
182  }) async {
183    // Cleanup old subscriptions. These will throw if there isn't anything
184    // listening, which is fine because that is what we want to ensure.
185    try {
186      await _debugConnection?.vmService?.streamCancel('Stdout');
187    } on vmservice.RPCError {
188      // Ignore this specific error.
189    }
190    try {
191      await _debugConnection?.vmService?.streamListen('Stdout');
192    } on vmservice.RPCError  {
193      // Ignore this specific error.
194    }
195    Uri websocketUri;
196    if (supportsServiceProtocol) {
197      _stdOutSub = _debugConnection.vmService.onStdoutEvent.listen((vmservice.Event log) {
198        final String message = utf8.decode(base64.decode(log.bytes)).trim();
199        // TODO(jonahwilliams): remove this error once it is gone from the engine #37625.
200        if (!message.contains(_kBadError)) {
201          printStatus(message);
202        }
203      });
204      websocketUri = Uri.parse(_debugConnection.uri);
205    }
206    connectionInfoCompleter?.complete(
207      DebugConnectionInfo(wsUri: websocketUri)
208    );
209    final int result = await waitForAppToFinish();
210    await cleanupAtFinish();
211    return result;
212  }
213
214  @override
215  Future<OperationResult> restart({
216    bool fullRestart = false,
217    bool pauseAfterRestart = false,
218    String reason,
219    bool benchmarkMode = false,
220  }) async {
221    if (!fullRestart) {
222      return OperationResult(1, 'hot reload not supported on the web.');
223    }
224    final Stopwatch timer = Stopwatch()..start();
225    final Status status = logger.startProgress(
226      'Performing hot restart...',
227      timeout: supportsServiceProtocol
228          ? timeoutConfiguration.fastOperation
229          : timeoutConfiguration.slowOperation,
230      progressId: 'hot.restart',
231    );
232    final bool success = await _webFs.recompile();
233    if (!success) {
234      status.stop();
235      return OperationResult(1, 'Failed to recompile application.');
236    }
237    if (supportsServiceProtocol) {
238      final vmservice.Response reloadResponse = await _vmService.callServiceExtension('hotRestart');
239      status.stop();
240      printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
241      return reloadResponse.type == 'Success'
242          ? OperationResult.ok
243          : OperationResult(1, reloadResponse.toString());
244    }
245    // If we're not in hot mode, the only way to restart is to reload the tab.
246    await _webFs.hardRefresh();
247    status.stop();
248    return OperationResult.ok;
249  }
250
251  @override
252  Future<void> debugDumpApp() async {
253    try {
254      await _vmService.callServiceExtension(
255        'ext.flutter.debugDumpApp',
256      );
257    } on vmservice.RPCError {
258      return;
259    }
260  }
261
262  @override
263  Future<void> debugDumpRenderTree() async {
264    try {
265      await _vmService.callServiceExtension(
266        'ext.flutter.debugDumpRenderTree',
267      );
268    } on vmservice.RPCError {
269      return;
270    }
271  }
272
273  @override
274  Future<void> debugDumpLayerTree() async {
275    try {
276      await _vmService.callServiceExtension(
277        'ext.flutter.debugDumpLayerTree',
278      );
279    } on vmservice.RPCError {
280      return;
281    }
282  }
283
284  @override
285  Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
286    try {
287      await _vmService.callServiceExtension(
288          'ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
289    } on vmservice.RPCError {
290      return;
291    }
292  }
293
294  @override
295  Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
296    try {
297      await _vmService.callServiceExtension(
298          'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
299    } on vmservice.RPCError {
300      return;
301    }
302  }
303
304
305  @override
306  Future<void> debugToggleDebugPaintSizeEnabled() async {
307    try {
308      final vmservice.Response response = await _vmService.callServiceExtension(
309        'ext.flutter.debugPaint',
310      );
311      await _vmService.callServiceExtension(
312        'ext.flutter.debugPaint',
313        args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
314      );
315    } on vmservice.RPCError {
316      return;
317    }
318  }
319
320  @override
321  Future<void> debugToggleDebugCheckElevationsEnabled() async {
322    try {
323      final vmservice.Response response = await _vmService.callServiceExtension(
324        'ext.flutter.debugCheckElevationsEnabled',
325      );
326      await _vmService.callServiceExtension(
327        'ext.flutter.debugCheckElevationsEnabled',
328        args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
329      );
330    } on vmservice.RPCError {
331      return;
332    }
333  }
334
335  @override
336  Future<void> debugTogglePerformanceOverlayOverride() async {
337    try {
338      final vmservice.Response response = await _vmService.callServiceExtension(
339        'ext.flutter.showPerformanceOverlay'
340      );
341      await _vmService.callServiceExtension(
342        'ext.flutter.showPerformanceOverlay',
343        args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
344      );
345    } on vmservice.RPCError {
346      return;
347    }
348  }
349
350  @override
351  Future<void> debugToggleWidgetInspector() async {
352    try {
353      final vmservice.Response response = await _vmService.callServiceExtension(
354        'ext.flutter.debugToggleWidgetInspector'
355      );
356      await _vmService.callServiceExtension(
357        'ext.flutter.debugToggleWidgetInspector',
358        args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
359      );
360    } on vmservice.RPCError {
361      return;
362    }
363  }
364
365  @override
366  Future<void> debugToggleProfileWidgetBuilds() async {
367    try {
368      final vmservice.Response response = await _vmService.callServiceExtension(
369        'ext.flutter.profileWidgetBuilds'
370      );
371      await _vmService.callServiceExtension(
372        'ext.flutter.profileWidgetBuilds',
373        args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
374      );
375    } on vmservice.RPCError {
376      return;
377    }
378  }
379}
380