web_device.dart 8.04 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:meta/meta.dart';

7 8 9 10 11
import '../application_package.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../build_info.dart';
import '../device.dart';
12
import '../features.dart';
13
import '../globals.dart' as globals;
14
import '../project.dart';
15
import 'chrome.dart';
16 17

class WebApplicationPackage extends ApplicationPackage {
18
  WebApplicationPackage(this.flutterProject) : super(id: flutterProject.manifest.appName);
19

20
  final FlutterProject flutterProject;
21 22

  @override
23
  String get name => flutterProject.manifest.appName;
24 25

  /// The location of the web source assets.
26
  Directory get webSourcePath => flutterProject.directory.childDirectory('web');
27 28
}

29 30 31
class ChromeDevice extends Device {
  ChromeDevice() : super(
      'chrome',
32 33 34 35
      category: Category.web,
      platformType: PlatformType.web,
      ephemeral: false,
  );
36

37 38 39
  /// The active chrome instance.
  Chrome _chrome;

40 41
  // TODO(jonahwilliams): this is technically false, but requires some refactoring
  // to allow hot mode restart only devices.
42
  @override
43
  bool get supportsHotReload => true;
44 45

  @override
46
  bool get supportsHotRestart => true;
47 48 49 50 51

  @override
  bool get supportsStartPaused => true;

  @override
52
  bool get supportsFlutterExit => true;
53 54 55 56 57

  @override
  bool get supportsScreenshot => false;

  @override
58
  void clearLogs() { }
59

60 61
  DeviceLogReader _logReader;

62
  @override
63 64 65 66
  DeviceLogReader getLogReader({
    ApplicationPackage app,
    bool includePastLogs = false,
  }) {
67
    return _logReader ??= NoOpDeviceLogReader(app?.name);
68 69 70 71 72 73 74 75 76 77 78 79 80 81
  }

  @override
  Future<bool> installApp(ApplicationPackage app) async => true;

  @override
  Future<bool> isAppInstalled(ApplicationPackage app) async => true;

  @override
  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;

  @override
  Future<bool> get isLocalEmulator async => false;

82 83 84
  @override
  Future<String> get emulatorId async => null;

85
  @override
86
  bool isSupported() =>  featureFlags.isWebEnabled && globals.chromeLauncher.canFindChrome();
87 88

  @override
89
  String get name => 'Chrome';
90 91 92 93 94

  @override
  DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();

  @override
95 96 97 98
  Future<String> get sdkNameAndVersion async => _sdkNameAndVersion ??= await _computeSdkNameAndVersion();

  String _sdkNameAndVersion;
  Future<String> _computeSdkNameAndVersion() async {
99 100 101
    if (!isSupported()) {
      return 'unknown';
    }
102 103
    // See https://bugs.chromium.org/p/chromium/issues/detail?id=158372
    String version = 'unknown';
104 105
    if (globals.platform.isWindows) {
      final ProcessResult result = await globals.processManager.run(<String>[
106
        r'reg', 'query', r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', '/v', 'version',
107 108
      ]);
      if (result.exitCode == 0) {
109
        final List<String> parts = (result.stdout as String).split(RegExp(r'\s+'));
110 111 112 113 114
        if (parts.length > 2) {
          version = 'Google Chrome ' + parts[parts.length - 2];
        }
      }
    } else {
115
      final String chrome = findChromeExecutable(globals.platform, globals.fs);
116
      final ProcessResult result = await globals.processManager.run(<String>[
117 118 119 120
        chrome,
        '--version',
      ]);
      if (result.exitCode == 0) {
121
        version = result.stdout as String;
122
      }
123
    }
124
    return version.trim();
125
  }
126 127 128 129 130 131 132 133 134 135 136

  @override
  Future<LaunchResult> startApp(
    covariant WebApplicationPackage package, {
    String mainPath,
    String route,
    DebuggingOptions debuggingOptions,
    Map<String, Object> platformArgs,
    bool prebuiltApplication = false,
    bool ipv6 = false,
  }) async {
137 138
    // See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
    // for the web initialization and server logic.
139
    final String url = platformArgs['uri'] as String;
140 141
    final bool launchChrome = platformArgs['no-launch-chrome'] != true;
    if (launchChrome) {
142
      _chrome = await globals.chromeLauncher.launch(
143
        url,
144
        cacheDir: globals.fs.currentDirectory
145 146 147 148 149 150 151 152 153
            .childDirectory('.dart_tool')
            .childDirectory('chrome-device'),
        headless: debuggingOptions.webRunHeadless,
        debugPort: debuggingOptions.webBrowserDebugPort,
      );
    }

    globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': launchChrome});
    return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
154 155 156 157
  }

  @override
  Future<bool> stopApp(ApplicationPackage app) async {
158
    await _chrome?.close();
159 160 161 162
    return true;
  }

  @override
163
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
164 165 166 167

  @override
  Future<bool> uninstallApp(ApplicationPackage app) async => true;

168 169 170 171
  @override
  bool isSupportedForProject(FlutterProject flutterProject) {
    return flutterProject.web.existsSync();
  }
172 173 174 175 176 177

  @override
  Future<void> dispose() async {
    _logReader?.dispose();
    await portForwarder?.dispose();
  }
178 179 180
}

class WebDevices extends PollingDeviceDiscovery {
181
  WebDevices() : super('chrome');
182

183
  final bool _chromeIsAvailable = globals.chromeLauncher.canFindChrome();
184
  final ChromeDevice _webDevice = ChromeDevice();
185
  final WebServerDevice _webServerDevice = WebServerDevice();
186 187

  @override
188
  bool get canListAnything => featureFlags.isWebEnabled;
189 190

  @override
191
  Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
192
    return <Device>[
193 194
      if (_chromeIsAvailable)
        _webDevice,
195
      _webServerDevice,
196 197 198 199
    ];
  }

  @override
200
  bool get supportsPlatform =>  featureFlags.isWebEnabled;
201
}
202 203 204

@visibleForTesting
String parseVersionForWindows(String input) {
205
  return input.split(RegExp(r'\w')).last;
206
}
207 208 209 210 211


/// A special device type to allow serving for arbitrary browsers.
class WebServerDevice extends Device {
  WebServerDevice() : super(
212
    'web-server',
213 214 215 216 217 218 219 220 221 222 223
    platformType: PlatformType.web,
    category: Category.web,
    ephemeral: false,
  );

  @override
  void clearLogs() { }

  @override
  Future<String> get emulatorId => null;

224 225
  DeviceLogReader _logReader;

226
  @override
227 228 229 230
  DeviceLogReader getLogReader({
    ApplicationPackage app,
    bool includePastLogs = false,
  }) {
231
    return _logReader ??= NoOpDeviceLogReader(app?.name);
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
  }

  @override
  Future<bool> installApp(ApplicationPackage app) async => true;

  @override
  Future<bool> isAppInstalled(ApplicationPackage app) async => true;

  @override
  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;

  @override
  Future<bool> get isLocalEmulator async => false;

  @override
  bool isSupported() => featureFlags.isWebEnabled;

  @override
  bool isSupportedForProject(FlutterProject flutterProject) {
    return flutterProject.web.existsSync();
  }

  @override
255
  String get name => 'Web Server';
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

  @override
  DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();

  @override
  Future<String> get sdkNameAndVersion async => 'Flutter Tools';

  @override
  Future<LaunchResult> startApp(ApplicationPackage package, {
    String mainPath,
    String route,
    DebuggingOptions debuggingOptions,
    Map<String, Object> platformArgs,
    bool prebuiltApplication = false,
    bool ipv6 = false,
  }) async {
272
    final String url = platformArgs['uri'] as String;
273
    if (debuggingOptions.startPaused) {
274
      globals.printStatus('Waiting for connection from Dart debug extension at $url', emphasis: true);
275
    } else {
276
      globals.printStatus('$mainPath is being served at $url', emphasis: true);
277
    }
278
    globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false});
279
    return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
280 281 282 283 284 285 286 287 288 289 290 291 292 293
  }

  @override
  Future<bool> stopApp(ApplicationPackage app) async {
    return true;
  }

  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;

  @override
  Future<bool> uninstallApp(ApplicationPackage app) async {
    return true;
  }
294 295 296 297 298 299

  @override
  Future<void> dispose() async {
    _logReader?.dispose();
    await portForwarder?.dispose();
  }
300
}