Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate injected client code to package:web (DO NOT SUBMIT - bug repro) #2305

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,458 changes: 975 additions & 2,483 deletions dwds/lib/src/injected/client.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dwds/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies:
vm_service: ^13.0.0
vm_service_interface: 1.0.0
web_socket_channel: ^2.2.0
web: ">=0.3.0 <0.5.0"
webkit_inspection_protocol: ^1.0.1

dev_dependencies:
Expand Down
53 changes: 34 additions & 19 deletions dwds/web/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ library hot_reload_client;

import 'dart:async';
import 'dart:convert';
import 'dart:html';
import 'dart:js';
import 'dart:js_interop';

import 'package:built_collection/built_collection.dart';
import 'package:dwds/data/build_result.dart';
Expand All @@ -26,6 +26,7 @@ import 'package:dwds/src/sockets.dart';
import 'package:js/js.dart';
import 'package:sse/client/sse_client.dart';
import 'package:uuid/uuid.dart';
import 'package:web/helpers.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import 'promise.dart';
Expand Down Expand Up @@ -156,9 +157,12 @@ Future<void>? main() {
} else if (event is RunRequest) {
runMain();
} else if (event is ErrorResponse) {
window.console
.error('Error from backend:\n\nError: ${event.error}\n\n'
'Stack Trace:\n${event.stackTrace}');
window.reportError(
'Error from backend:\n\n'
'Error: ${event.error}\n\n'
'Stack Trace:\n${event.stackTrace}'
.toJS,
);
}
},
onError: (error) {
Expand Down Expand Up @@ -276,35 +280,46 @@ void _launchCommunicationWithDebugExtension() {
),
),
);
dispatchEvent(CustomEvent('dart-app-ready', detail: debugInfoJson));
_dispatchEvent('dart-app-ready', debugInfoJson);
}

void _dispatchEvent(String message, String detail) {
window.dispatchEvent(
CustomEvent(
'dart-auth-response',
CustomEventInit(detail: detail.toJS),
),
);
}

void _listenForDebugExtensionAuthRequest() {
window.addEventListener(
'message',
allowInterop((event) async {
final messageEvent = event as MessageEvent;
if (messageEvent.data is! String) return;
if (messageEvent.data as String != 'dart-auth-request') return;

// Notify the Dart Debug Extension of authentication status:
if (_authUrl != null) {
final isAuthenticated = await _authenticateUser(_authUrl!);
dispatchEvent(
CustomEvent('dart-auth-response', detail: '$isAuthenticated'),
);
}
}),
_handleAuthRequest.toJS,
);
}

void _handleAuthRequest(Event event) {
final messageEvent = event as MessageEvent;
if (messageEvent.data is! String) return;
if (messageEvent.data as String != 'dart-auth-request') return;

// Notify the Dart Debug Extension of authentication status:
if (_authUrl != null) {
_authenticateUser(_authUrl!).then(
(isAuthenticated) =>
_dispatchEvent('dart-auth-response', '$isAuthenticated'),
);
}
}

Future<bool> _authenticateUser(String authUrl) async {
final response = await HttpRequest.request(
authUrl,
method: 'GET',
withCredentials: true,
);
final responseText = response.responseText ?? '';
final responseText = response.responseText;
return responseText.contains('Dart Debug Authentication Success!');
}

Expand Down
6 changes: 4 additions & 2 deletions dwds/web/reloader/legacy_restarter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:html';
import 'dart:js';
import 'dart:js_interop';

import 'package:web/helpers.dart';

import 'restarter.dart';

Expand All @@ -21,7 +23,7 @@ class LegacyRestarter implements Restarter {
}
final reloadCompleter = Completer<bool>();
final sub = window.onMessage.listen((event) {
final message = event.data;
final message = event.data?.dartify();
if (message is Map &&
message['type'] == 'DDC_STATE_CHANGE' &&
message['state'] == 'restart_end') {
Expand Down
2 changes: 1 addition & 1 deletion dwds/web/reloader/manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:html';

import 'package:dwds/data/isolate_events.dart';
import 'package:dwds/data/serializers.dart';
import 'package:dwds/src/sockets.dart';
import 'package:web/helpers.dart';

import 'restarter.dart';

Expand Down
49 changes: 15 additions & 34 deletions dwds/web/reloader/require_restarter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ library require_reloading_manager;

import 'dart:async';
import 'dart:collection';
import 'dart:html';
import 'dart:js_interop';
import 'dart:js_util';

import 'package:graphs/graphs.dart' as graphs;
import 'package:js/js.dart';
import 'package:js/js_util.dart';
import 'package:web/helpers.dart';

import '../promise.dart';
import '../run_main.dart';
import '../web_utils.dart';
import 'restarter.dart';

/// The last known digests of all the modules in the application.
Expand All @@ -36,16 +38,6 @@ external set dartRunMain(Function() func);
@JS(r'$dartRunMain')
external Function() get dartRunMain;

List<K> keys<K, V>(JsMap<K, V> map) {
return List.from(_jsArrayFrom(map.keys()));
}

@JS('Array.from')
external List _jsArrayFrom(Object any);

@JS('Object.values')
external List _jsObjectValues(Object any);

@anonymous
@JS()
class RequireLoader {
Expand All @@ -71,24 +63,6 @@ class HotReloadFailedException implements Exception {
String toString() => "HotReloadFailedException: '$_s'";
}

@JS('Error')
abstract class JsError {
@JS()
external String get message;

@JS()
external String get stack;
}

@JS('Map')
abstract class JsMap<K, V> {
@JS()
external V? get(K key);

@JS()
external Object keys();
}

/// Handles hot restart reloading for use with the require module system.
class RequireRestarter implements Restarter {
final _moduleOrdering = HashMap<String, int>();
Expand Down Expand Up @@ -141,23 +115,25 @@ class RequireRestarter implements Restarter {
return result;
}

List<String> _allModules() => keys(requireLoader.moduleParentsGraph);
Iterable<String> _allModules() => requireLoader.moduleParentsGraph.dartKeys;

Future<Map<String, String>> _getDigests() async {
final request = await HttpRequest.request(
requireLoader.digestsPath,
responseType: 'json',
method: 'GET',
);
return (request.response as Map).cast<String, String>();

final response = request.response.dartify();
return (response as Map).cast<String, String>();
}

Future<void> _initialize() async {
_lastKnownDigests = await _getDigests();
}

List<String> _moduleParents(String module) =>
requireLoader.moduleParentsGraph.get(module)?.cast() ?? [];
requireLoader.moduleParentsGraph.get(module) ?? [];

int _moduleTopologicalCompare(String module1, String module2) {
var topological = 0;
Expand Down Expand Up @@ -207,13 +183,17 @@ class RequireRestarter implements Restarter {
if (parentIds.isEmpty) {
// The bootstrap module is not reloaded but we need to update the
// $dartRunMain reference to the newly loaded child module.
final childModule = callMethod(
final childModule = callMethod<JSObject>(
getProperty(require('dart_sdk'), 'dart'),
'getModuleLibraries',
[previousModuleId],
);
dartRunMain = allowInterop(() {
callMethod(_jsObjectValues(childModule).first, 'main', []);
callMethod(
childModule.values.first!,
'main',
[],
);
});
} else {
++reloadedModules;
Expand Down Expand Up @@ -255,6 +235,7 @@ class RequireRestarter implements Restarter {

void _updateGraph() {
final allModules = _allModules();
print('Modules: $allModules');
final stronglyConnectedComponents =
graphs.stronglyConnectedComponents(allModules, _moduleParents);
_moduleOrdering.clear();
Expand Down
46 changes: 26 additions & 20 deletions dwds/web/run_main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,46 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:html';
import 'dart:js_interop';
import 'package:web/helpers.dart';

/// Creates a script that will run properly when strict CSP is enforced.
///
/// More specifically, the script has the correct `nonce` value set.
final ScriptElement Function() _createScript = (() {
final nonce = _findNonce();
if (nonce == null) return ScriptElement.new;

return () => ScriptElement()..setAttribute('nonce', nonce);
})();
import 'web_utils.dart';

// According to the CSP3 spec a nonce must be a valid base64 string.
final _noncePattern = RegExp('^[\\w+/_-]+[=]{0,2}\$');

/// Returns CSP nonce, if set for any script tag.
String? _findNonce() {
final elements = window.document.querySelectorAll('script');
for (final element in elements) {
final nonceValue =
(element as HtmlElement).nonce ?? element.attributes['nonce'];
if (nonceValue != null && _noncePattern.hasMatch(nonceValue)) {
return nonceValue;
}
}
elements.forEach(
(Node element) {
final nonceValue = (element as HtmlElement).nonce;
if (_noncePattern.hasMatch(nonceValue)) {
return nonceValue;
}
}.toJS,
);
return null;
}

/// Creates a script that will run properly when strict CSP is enforced.
///
/// More specifically, the script has the correct `nonce` value set.
HTMLScriptElement _createScript() {
final nonce = _findNonce();

return nonce == null ? HTMLScriptElement() : HTMLScriptElement()
..setAttribute('nonce', nonce!);
}

/// Runs `window.$dartRunMain()` by injecting a script tag.
///
/// We do this so that we don't see user exceptions bubble up in our own error
/// handling zone.
void runMain() {
final scriptElement = _createScript()..innerHtml = r'window.$dartRunMain();';
document.body!.append(scriptElement);
Future.microtask(scriptElement.remove);
final scriptElement = _createScript()..htmlFor = r'window.$dartRunMain();';
(document.body as HTMLBodyElement).append(scriptElement.toJSBox);
// External tear-offs are not allowed.
// ignore: unnecessary_lambdas
Future.microtask(() => scriptElement.remove());
}
54 changes: 54 additions & 0 deletions dwds/web/web_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@JS()
library require_reloading_manager;

import 'dart:js_interop';

import 'package:js/js.dart';
import 'package:web/helpers.dart';

@JS('Array.from')
external JSArray _jsArrayFrom(Object any);

@JS('Object.values')
external JSArray _jsObjectValues(Object any);

@JS('Error')
abstract class JsError {
@JS()
external String get message;

@JS()
external String get stack;
}

@JS('Map')
abstract class JsMap<K, V> {
@JS()
external V? get(K key);

@JS()
external Object keys();

@JS()
external Object values();
}

extension ObjectValues on JSObject {
Iterable<Object?> get values => _jsObjectValues(this).toDartIterable();
}

extension JSArrayToIterable on JSArray {
Iterable<T> toDartIterable<T>() => toDart.map((e) => e.dartify() as T);
}

extension JSMapToMap<K, V> on JsMap<K, V> {
Iterable<K> get dartKeys => _jsArrayFrom(keys()).toDartIterable<K>();
}

extension NodeListExtension on NodeList {
external void forEach(JSFunction callback);
}
2 changes: 1 addition & 1 deletion fixtures/_webdevSoundSmoke/web/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ void main() {
print('Initial Print');

registerExtension('ext.print', (_, __) async {
print('Hello World');
print('Hello World1');
return ServiceExtensionResponse.result(json.encode({'success': true}));
});
document.body?.append(SpanElement()..text = 'Hello World!!');
Expand Down
Loading