| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:meta/meta.dart'; |
| import 'package:package_config/package_config.dart'; |
| |
| /// The JavaScript bootstrap script to support in-browser hot restart. |
| /// |
| /// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl] |
| /// loads the special Dart stack trace mapper. The [entrypoint] is the |
| /// actual main.dart file. |
| /// |
| /// This file is served when the browser requests "main.dart.js" in debug mode, |
| /// and is responsible for bootstrapping the RequireJS modules and attaching |
| /// the hot reload hooks. |
| String generateBootstrapScript({ |
| @required String requireUrl, |
| @required String mapperUrl, |
| }) { |
| return ''' |
| "use strict"; |
| |
| // Attach source mapping. |
| var mapperEl = document.createElement("script"); |
| mapperEl.defer = true; |
| mapperEl.async = false; |
| mapperEl.src = "$mapperUrl"; |
| document.head.appendChild(mapperEl); |
| |
| // Attach require JS. |
| var requireEl = document.createElement("script"); |
| requireEl.defer = true; |
| requireEl.async = false; |
| requireEl.src = "$requireUrl"; |
| // This attribute tells require JS what to load as main (defined below). |
| requireEl.setAttribute("data-main", "main_module.bootstrap"); |
| document.head.appendChild(requireEl); |
| '''; |
| } |
| |
| /// Generate a synthetic main module which captures the application's main |
| /// method. |
| /// |
| /// If a [bootstrapModule] name is not provided, defaults to 'main_module.bootstrap'. |
| /// |
| /// RE: Object.keys usage in app.main: |
| /// This attaches the main entrypoint and hot reload functionality to the window. |
| /// The app module will have a single property which contains the actual application |
| /// code. The property name is based off of the entrypoint that is generated, for example |
| /// the file `foo/bar/baz.dart` will generate a property named approximately |
| /// `foo__bar__baz`. Rather than attempt to guess, we assume the first property of |
| /// this object is the module. |
| String generateMainModule({ |
| @required String entrypoint, |
| @required bool nullAssertions, |
| @required bool nativeNullAssertions, |
| String bootstrapModule = 'main_module.bootstrap', |
| }) { |
| // TODO(jonahwilliams): fix typo in dwds and update. |
| return ''' |
| /* ENTRYPOINT_EXTENTION_MARKER */ |
| // Create the main module loaded below. |
| define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { |
| dart_sdk.dart.setStartAsyncSynchronously(true); |
| dart_sdk._debugger.registerDevtoolsFormatter(); |
| dart_sdk.dart.nonNullAsserts($nullAssertions); |
| dart_sdk.dart.nativeNonNullAsserts($nativeNullAssertions); |
| |
| // See the generateMainModule doc comment. |
| var child = {}; |
| child.main = app[Object.keys(app)[0]].main; |
| |
| /* MAIN_EXTENSION_MARKER */ |
| child.main(); |
| |
| window.\$dartLoader = {}; |
| window.\$dartLoader.rootDirectories = []; |
| if (window.\$requireLoader) { |
| window.\$requireLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries; |
| } |
| if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) { |
| window.\$dartStackTraceUtility.ready = true; |
| let dart = dart_sdk.dart; |
| window.\$dartStackTraceUtility.setSourceMapProvider(function(url) { |
| var baseUrl = window.location.protocol + '//' + window.location.host; |
| url = url.replace(baseUrl + '/', ''); |
| if (url == 'dart_sdk.js') { |
| return dart.getSourceMap('dart_sdk'); |
| } |
| url = url.replace(".lib.js", ""); |
| return dart.getSourceMap(url); |
| }); |
| } |
| }); |
| '''; |
| } |
| |
| /// Generates the bootstrap logic required for a flutter test running in a browser. |
| /// |
| /// This hard-codes the device pixel ratio to 3.0 and a 2400 x 1800 window size. |
| String generateTestEntrypoint({ |
| @required String relativeTestPath, |
| @required String absolutePath, |
| @required LanguageVersion languageVersion, |
| }) { |
| return ''' |
| // @dart = ${languageVersion.major}.${languageVersion.minor} |
| import 'org-dartlang-app:///$relativeTestPath' as test; |
| import 'dart:ui' as ui; |
| import 'dart:html'; |
| import 'dart:js'; |
| import 'package:stream_channel/stream_channel.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports |
| import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports |
| import 'package:test_api/src/suite_channel_manager.dart'; // ignore: implementation_imports |
| |
| Future<void> main() async { |
| ui.debugEmulateFlutterTesterEnvironment = true; |
| await ui.webOnlyInitializePlatform(); |
| webGoldenComparator = DefaultWebGoldenComparator(Uri.parse('$absolutePath')); |
| (ui.window as dynamic).debugOverrideDevicePixelRatio(3.0); |
| (ui.window as dynamic).webOnlyDebugPhysicalSizeOverride = const ui.Size(2400, 1800); |
| internalBootstrapBrowserTest(() => test.main); |
| } |
| |
| void internalBootstrapBrowserTest(Function getMain()) { |
| var channel = serializeSuite(getMain, hidePrints: false); |
| postMessageChannel().pipe(channel); |
| } |
| |
| StreamChannel serializeSuite(Function getMain(), {bool hidePrints = true}) => RemoteListener.start(getMain, hidePrints: hidePrints); |
| |
| StreamChannel suiteChannel(String name) { |
| var manager = SuiteChannelManager.current; |
| if (manager == null) { |
| throw StateError('suiteChannel() may only be called within a test worker.'); |
| } |
| return manager.connectOut(name); |
| } |
| |
| StreamChannel postMessageChannel() { |
| var controller = StreamChannelController(sync: true); |
| window.onMessage.firstWhere((message) { |
| return message.origin == window.location.origin && message.data == "port"; |
| }).then((message) { |
| var port = message.ports.first; |
| var portSubscription = port.onMessage.listen((message) { |
| controller.local.sink.add(message.data); |
| }); |
| controller.local.stream.listen((data) { |
| port.postMessage({"data": data}); |
| }, onDone: () { |
| port.postMessage({"event": "done"}); |
| portSubscription.cancel(); |
| }); |
| }); |
| context['parent'].callMethod('postMessage', [ |
| JsObject.jsify({"href": window.location.href, "ready": true}), |
| window.location.origin, |
| ]); |
| return controller.foreign; |
| } |
| '''; |
| } |
| |
| /// Generate the unit test bootstrap file. |
| String generateTestBootstrapFileContents(String mainUri, String requireUrl, String mapperUrl) { |
| return ''' |
| (function() { |
| if (typeof document != 'undefined') { |
| var el = document.createElement("script"); |
| el.defer = true; |
| el.async = false; |
| el.src = '$mapperUrl'; |
| document.head.appendChild(el); |
| |
| el = document.createElement("script"); |
| el.defer = true; |
| el.async = false; |
| el.src = '$requireUrl'; |
| el.setAttribute("data-main", '$mainUri'); |
| document.head.appendChild(el); |
| } else { |
| importScripts('$mapperUrl', '$requireUrl'); |
| require.config({ |
| baseUrl: baseUrl, |
| }); |
| window = self; |
| require(['$mainUri']); |
| } |
| })(); |
| '''; |
| } |