Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
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
115 changes: 92 additions & 23 deletions lib/web_ui/lib/src/engine/dom_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class DomRenderer {
/// This getter calls the `hasFocus` method of the `Document` interface.
/// See for more details:
/// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus
bool? get windowHasFocus => js_util.callMethod(html.document, 'hasFocus', <dynamic>[]);
bool? get windowHasFocus =>
js_util.callMethod(html.document, 'hasFocus', <dynamic>[]);

void _setupHotRestart() {
// This persists across hot restarts to clear stale DOM.
Expand Down Expand Up @@ -172,7 +173,8 @@ class DomRenderer {
js_util.setProperty(element, name, value);
}

static void setElementStyle(html.Element element, String name, String? value) {
static void setElementStyle(
html.Element element, String name, String? value) {
if (value == null) {
element.style.removeProperty(name);
} else {
Expand All @@ -181,8 +183,8 @@ class DomRenderer {
}

static void setElementTransform(html.Element element, String transformValue) {
js_util.setProperty(js_util.getProperty(element, 'style'), 'transform',
transformValue);
js_util.setProperty(
js_util.getProperty(element, 'style'), 'transform', transformValue);
}

void setText(html.Element element, String text) {
Expand All @@ -200,7 +202,8 @@ class DomRenderer {
}

void setThemeColor(ui.Color color) {
html.MetaElement? theme = html.document.querySelector('#flutterweb-theme') as html.MetaElement?;
html.MetaElement? theme =
html.document.querySelector('#flutterweb-theme') as html.MetaElement?;
if (theme == null) {
theme = html.MetaElement()
..id = 'flutterweb-theme'
Expand Down Expand Up @@ -315,8 +318,8 @@ flt-glass-pane * {
// This css prevents an autofill overlay brought by the browser during
// text field autofill by delaying the transition effect.
// See: https://github.com/flutter/flutter/issues/61132.
if(browserHasAutofillOverlay()) {
sheet.insertRule('''
if (browserHasAutofillOverlay()) {
sheet.insertRule('''
.transparentTextEditing:-webkit-autofill,
.transparentTextEditing:-webkit-autofill:hover,
.transparentTextEditing:-webkit-autofill:focus,
Expand All @@ -326,7 +329,6 @@ flt-glass-pane * {
''', sheet.cssRules.length);
}


final html.BodyElement bodyElement = html.document.body!;
setElementStyle(bodyElement, 'position', 'fixed');
setElementStyle(bodyElement, 'top', '0');
Expand Down Expand Up @@ -416,8 +418,7 @@ flt-glass-pane * {
// pointer events through to the semantics tree. However, for platform
// views, the pointer events will not pass through, and will be handled
// by the platform view.
glassPaneElement
.insertBefore(_accesibilityPlaceholder, _sceneHostElement);
glassPaneElement.insertBefore(_accesibilityPlaceholder, _sceneHostElement);

PointerBinding.initInstance(glassPaneElement);

Expand Down Expand Up @@ -460,6 +461,66 @@ flt-glass-pane * {
_canvasKitScript?.remove();
_canvasKitScript = html.ScriptElement();
_canvasKitScript!.src = canvasKitBaseUrl + 'canvaskit.js';

// TODO(hterkelsen): Rather than this monkey-patch hack, we should
// build CanvasKit ourselves. See:
// https://github.com/flutter/flutter/issues/52588

// Monkey-patch the top-level `module` and `exports` objects so that
// CanvasKit doesn't attempt to register itself as an anonymous module.
//
// The idea behind making these fake `exports` and `module` objects is
// that `canvaskit.js` contains the following lines of code:
//
// if (typeof exports === 'object' && typeof module === 'object')
// module.exports = CanvasKitInit;
// else if (typeof define === 'function' && define['amd'])
// define([], function() { return CanvasKitInit; });
//
// We need to avoid hitting the case where CanvasKit defines an anonymous
// module, since this breaks RequireJS, which DDC and some plugins use.
// Temporarily removing the `define` function won't work because RequireJS
// could load in between this code running and the CanvasKit code running.
// Also, we cannot monkey-patch the `define` function because it is
// non-configurable (it is a top-level 'var').

// First check if `exports` and `module` are already defined. If so, then
// CommonJS is being used, and we shouldn't have any problems.
js.JsFunction objectConstructor = js.context['Object'];
if (js.context['exports'] == null) {
js.JsObject exportsAccessor = js.JsObject.jsify({
'get': js.allowInterop(() {
if (html.document.currentScript == _canvasKitScript) {
return js.JsObject(objectConstructor);
} else {
return js.context['_flutterWebCachedExports'];
}
}),
'set': js.allowInterop((dynamic value) {
js.context['_flutterWebCachedExports'] = value;
}),
'configurable': true,
});
objectConstructor.callMethod('defineProperty',
<dynamic>[js.context, 'exports', exportsAccessor]);
Comment on lines +504 to +505
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that exports is actually module.exports. Do we want to expose it like that?

Currently if the script writes in module.exports, that won't be available in exports; I'm not sure if that can break stuff?

https://stackoverflow.com/a/16383925/1738205

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't care about making exports == module.exports, we only care about making these fake objects so that CanvasKit goes into the first case "if (typeof exports === 'object' && typeof module === 'object')". When CanvasKit sets module.exports = CanvasKitInit it should be a no-op.

}
if (js.context['module'] == null) {
js.JsObject moduleAccessor = js.JsObject.jsify({
'get': js.allowInterop(() {
if (html.document.currentScript == _canvasKitScript) {
return js.JsObject(objectConstructor);
} else {
return js.context['_flutterWebCachedModule'];
}
}),
'set': js.allowInterop((dynamic value) {
js.context['_flutterWebCachedModule'] = value;
}),
'configurable': true,
});
objectConstructor.callMethod(
'defineProperty', <dynamic>[js.context, 'module', moduleAccessor]);
}
html.document.head!.append(_canvasKitScript!);
}

Expand All @@ -469,8 +530,8 @@ flt-glass-pane * {
} else {
_resizeSubscription = html.window.onResize.listen(_metricsDidChange);
}
_localeSubscription = languageChangeEvent.forTarget(html.window)
.listen(_languageDidChange);
_localeSubscription =
languageChangeEvent.forTarget(html.window).listen(_languageDidChange);
EnginePlatformDispatcher.instance._updateLocales();
}

Expand All @@ -484,7 +545,7 @@ flt-glass-pane * {
/// Note: always check for rotations for a mobile device. Update the physical
/// size if the change is caused by a rotation.
void _metricsDidChange(html.Event? event) {
if(isMobile && !window.isRotation() && textEditing.isEditing) {
if (isMobile && !window.isRotation() && textEditing.isEditing) {
window.computeOnScreenKeyboardInsets();
EnginePlatformDispatcher.instance.invokeOnMetricsChanged();
} else {
Expand Down Expand Up @@ -517,13 +578,20 @@ flt-glass-pane * {
static bool? _ellipseFeatureDetected;

/// Draws CanvasElement ellipse with fallback.
static void ellipse(html.CanvasRenderingContext2D context,
double centerX, double centerY, double radiusX, double radiusY,
double rotation, double startAngle, double endAngle, bool antiClockwise) {
static void ellipse(
html.CanvasRenderingContext2D context,
double centerX,
double centerY,
double radiusX,
double radiusY,
double rotation,
double startAngle,
double endAngle,
bool antiClockwise) {
_ellipseFeatureDetected ??= js_util.getProperty(context, 'ellipse') != null;
if (_ellipseFeatureDetected!) {
context.ellipse(centerX, centerY, radiusX, radiusY,
rotation, startAngle, endAngle, antiClockwise);
context.ellipse(centerX, centerY, radiusX, radiusY, rotation, startAngle,
endAngle, antiClockwise);
} else {
context.save();
context.translate(centerX, centerY);
Expand All @@ -539,9 +607,11 @@ flt-glass-pane * {
static const String orientationLockTypeLandscape = 'landscape';
static const String orientationLockTypePortrait = 'portrait';
static const String orientationLockTypePortraitPrimary = 'portrait-primary';
static const String orientationLockTypePortraitSecondary = 'portrait-secondary';
static const String orientationLockTypePortraitSecondary =
'portrait-secondary';
static const String orientationLockTypeLandscapePrimary = 'landscape-primary';
static const String orientationLockTypeLandscapeSecondary = 'landscape-secondary';
static const String orientationLockTypeLandscapeSecondary =
'landscape-secondary';

/// Sets preferred screen orientation.
///
Expand All @@ -556,8 +626,7 @@ flt-glass-pane * {
Future<bool> setPreferredOrientation(List<dynamic>? orientations) {
final html.Screen screen = html.window.screen!;
if (!_unsafeIsNull(screen)) {
final html.ScreenOrientation? screenOrientation =
screen.orientation;
final html.ScreenOrientation? screenOrientation = screen.orientation;
if (!_unsafeIsNull(screenOrientation)) {
if (orientations!.isEmpty) {
screenOrientation!.unlock();
Expand Down Expand Up @@ -588,7 +657,7 @@ flt-glass-pane * {

// Converts device orientation to w3c OrientationLockType enum.
static String? _deviceOrientationToLockType(String deviceOrientation) {
switch(deviceOrientation) {
switch (deviceOrientation) {
case 'DeviceOrientation.portraitUp':
return orientationLockTypePortraitPrimary;
case 'DeviceOrientation.landscapeLeft':
Expand Down