Skip to content

Commit

Permalink
[web] Migrate Flutter Web DOM usage to JS static interop - 31. (flutt…
Browse files Browse the repository at this point in the history
  • Loading branch information
joshualitt authored Jun 15, 2022
1 parent 6576558 commit ae357ca
Show file tree
Hide file tree
Showing 16 changed files with 194 additions and 122 deletions.
77 changes: 74 additions & 3 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ extension DomHTMLDocumentExtension on DomHTMLDocument {
external DomHTMLHeadElement? get head;
external DomHTMLBodyElement? get body;
external set title(String? value);
Iterable<DomElement> getElementsByTagName(String tag) =>
createDomListWrapper<DomElement>(js_util
.callMethod<_DomList>(this, 'getElementsByTagName', <Object>[tag]));
}

@JS('document')
Expand All @@ -124,6 +127,8 @@ extension DomEventTargetExtension on DomEventTarget {
<Object>[type, listener, if (useCapture != null) useCapture]);
}
}

external bool dispatchEvent(DomEvent event);
}

typedef DomEventListener = void Function(DomEvent event);
Expand Down Expand Up @@ -220,6 +225,23 @@ extension DomElementExtension on DomElement {
external void remove();
external void setAttribute(String name, Object value);
void appendText(String text) => append(createDomText(text));
external void removeAttribute(String name);
external set tabIndex(int? value);
external int? get tabIndex;
external void focus();

/// [scrollTop] and [scrollLeft] can both return non-integers when using
/// display scaling.
///
/// The setters have a spurious round just in case the supplied [int] flowed
/// from the non-static interop JS API. When all of Flutter Web has been
/// migrated to static interop we can probably remove the rounds.
int get scrollTop => js_util.getProperty(this, 'scrollTop').round();
set scrollTop(int value) =>
js_util.setProperty<num>(this, 'scrollTop', value.round());
int get scrollLeft => js_util.getProperty(this, 'scrollLeft').round();
set scrollLeft(int value) =>
js_util.setProperty<num>(this, 'scrollLeft', value.round());
}

@JS()
Expand Down Expand Up @@ -287,6 +309,10 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration {
set alignItems(String value) => setProperty('align-items', value);
set margin(String value) => setProperty('margin', value);
set background(String value) => setProperty('background', value);
set touchAction(String value) => setProperty('touch-action', value);
set overflowY(String value) => setProperty('overflow-y', value);
set overflowX(String value) => setProperty('overflow-x', value);
set outline(String value) => setProperty('outline', value);
String get width => getPropertyValue('width');
String get height => getPropertyValue('height');
String get position => getPropertyValue('position');
Expand Down Expand Up @@ -342,6 +368,10 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration {
String get alignItems => getPropertyValue('align-items');
String get margin => getPropertyValue('margin');
String get background => getPropertyValue('background');
String get touchAction => getPropertyValue('touch-action');
String get overflowY => getPropertyValue('overflow-y');
String get overflowX => getPropertyValue('overflow-x');
String get outline => getPropertyValue('outline');

external String getPropertyValue(String property);
void setProperty(String propertyName, String value, [String? priority]) {
Expand All @@ -359,7 +389,6 @@ class DomHTMLElement extends DomElement {}

extension DomHTMLElementExtension on DomHTMLElement {
int get offsetWidth => js_util.getProperty<num>(this, 'offsetWidth') as int;
external void focus();
}

@JS()
Expand Down Expand Up @@ -927,11 +956,19 @@ class DomMouseEvent extends DomUIEvent {}
extension DomMouseEventExtension on DomMouseEvent {
external num get clientX;
external num get clientY;
external num get offsetX;
external num get offsetY;
DomPoint get client => DomPoint(clientX, clientY);
DomPoint get offset => DomPoint(offsetX, offsetY);
external int get button;
external int? get buttons;
external bool getModifierState(String keyArg);
}

DomMouseEvent createDomMouseEvent(String type, [Map<dynamic, dynamic>? init]) =>
js_util.callConstructor(domGetConstructor('MouseEvent')!,
<Object>[type, if (init != null) js_util.jsify(init)]);

@JS()
@staticInterop
class DomPointerEvent extends DomMouseEvent {}
Expand All @@ -947,6 +984,11 @@ extension DomPointerEventExtension on DomPointerEvent {
this, 'getCoalescedEvents', <Object>[]).cast<DomPointerEvent>();
}

DomPointerEvent createDomPointerEvent(String type,
[Map<dynamic, dynamic>? init]) =>
js_util.callConstructor(domGetConstructor('PointerEvent')!,
<Object>[type, if (init != null) js_util.jsify(init)]);

@JS()
@staticInterop
class DomWheelEvent extends DomMouseEvent {}
Expand All @@ -973,8 +1015,37 @@ class DomTouch {}

extension DomTouchExtension on DomTouch {
external int? get identifier;
external num? get clientX;
external num? get clientY;
external num get clientX;
external num get clientY;
DomPoint get client => DomPoint(clientX, clientY);
}

DomTouchEvent createDomTouchEvent(String type, [Map<dynamic, dynamic>? init]) =>
js_util.callConstructor(domGetConstructor('TouchEvent')!,
<Object>[type, if (init != null) js_util.jsify(init)]);

@JS()
@staticInterop
class DomHTMLInputElement extends DomHTMLElement {}

extension DomHTMLInputElementExtension on DomHTMLInputElement {
external set type(String? value);
external set max(String? value);
external set min(String value);
external set value(String? value);
external String? get value;
external bool? get disabled;
external set disabled(bool? value);
}

DomHTMLInputElement createDomHTMLInputElement() =>
domDocument.createElement('input') as DomHTMLInputElement;

class DomPoint {
final num x;
final num y;

DomPoint(this.x, this.y);
}

Object? domGetConstructor(String constructorName) =>
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/embedder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ class FlutterViewEmbedder {

final html.Element _accessibilityPlaceholder = EngineSemanticsOwner
.instance.semanticsHelper
.prepareAccessibilityPlaceholder();
.prepareAccessibilityPlaceholder() as html.Element;

glassPaneElementHostNode.nodes.addAll(<html.Node>[
_accessibilityPlaceholder,
Expand Down
4 changes: 1 addition & 3 deletions lib/web_ui/lib/src/engine/keyboard_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:html' as html;

import 'package:ui/ui.dart' as ui;

import '../engine.dart' show registerHotRestartListener;
Expand Down Expand Up @@ -117,7 +115,7 @@ class KeyboardBinding {
if (_debugLogKeyEvents) {
print(event.type);
}
if (EngineSemanticsOwner.instance.receiveGlobalEvent(event as html.Event)) {
if (EngineSemanticsOwner.instance.receiveGlobalEvent(event)) {
return handler(event);
}
return null;
Expand Down
7 changes: 3 additions & 4 deletions lib/web_ui/lib/src/engine/pointer_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:html' as html;
import 'dart:math' as math;

import 'package:meta/meta.dart';
Expand Down Expand Up @@ -293,7 +292,7 @@ abstract class _BaseAdapter {
// Report the event to semantics. This information is used to debounce
// browser gestures. Semantics tells us whether it is safe to forward
// the event to the framework.
if (EngineSemanticsOwner.instance.receiveGlobalEvent(event as html.Event)) {
if (EngineSemanticsOwner.instance.receiveGlobalEvent(event)) {
handler(event);
}
}
Expand Down Expand Up @@ -868,8 +867,8 @@ class _TouchAdapter extends _BaseAdapter {
kind: ui.PointerDeviceKind.touch,
signalKind: ui.PointerSignalKind.none,
device: touch.identifier!,
physicalX: touch.clientX!.toDouble() * ui.window.devicePixelRatio,
physicalY: touch.clientY!.toDouble() * ui.window.devicePixelRatio,
physicalX: touch.clientX.toDouble() * ui.window.devicePixelRatio,
physicalY: touch.clientY.toDouble() * ui.window.devicePixelRatio,
buttons: pressed ? _kPrimaryMouseButton : 0,
pressure: 1.0,
pressureMin: 0.0,
Expand Down
7 changes: 3 additions & 4 deletions lib/web_ui/lib/src/engine/semantics/checkable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
// framework. Currently the framework does not report the
// grouping of radio buttons.

import 'dart:html' as html;

import 'package:ui/ui.dart' as ui;

import '../dom.dart';
import 'semantics.dart';

/// The specific type of checkable control.
Expand Down Expand Up @@ -104,7 +103,7 @@ class Checkable extends RoleManager {

void _updateDisabledAttribute() {
if (semanticsObject.enabledState() == EnabledState.disabled) {
final html.Element element = semanticsObject.element;
final DomElement element = semanticsObject.element;
element
..setAttribute('aria-disabled', 'true')
..setAttribute('disabled', 'true');
Expand All @@ -114,7 +113,7 @@ class Checkable extends RoleManager {
}

void _removeDisabledAttribute() {
final html.Element element = semanticsObject.element;
final DomElement element = semanticsObject.element;
element..removeAttribute('aria-disabled')..removeAttribute('disabled');
}
}
11 changes: 5 additions & 6 deletions lib/web_ui/lib/src/engine/semantics/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:html' as html;

import '../dom.dart';
import 'semantics.dart';

/// Represents semantic objects that deliver information in a visual manner.
Expand All @@ -18,13 +17,13 @@ class ImageRoleManager extends RoleManager {
/// The element with role="img" and aria-label could block access to all
/// children elements, therefore create an auxiliary element and describe the
/// image in that if the semantic object have child nodes.
html.Element? _auxiliaryImageElement;
DomElement? _auxiliaryImageElement;

@override
void update() {
if (semanticsObject.isVisualOnly && semanticsObject.hasChildren) {
if (_auxiliaryImageElement == null) {
_auxiliaryImageElement = html.Element.tag('flt-semantics-img');
_auxiliaryImageElement = domDocument.createElement('flt-semantics-img');
// Absolute positioning and sizing of leaf text elements confuses
// VoiceOver. So we let the browser size the value node. The node will
// still have a bigger tap area. However, if the node is a parent to
Expand Down Expand Up @@ -54,7 +53,7 @@ class ImageRoleManager extends RoleManager {
}
}

void _setLabel(html.Element? element) {
void _setLabel(DomElement? element) {
if (semanticsObject.hasLabel) {
element!.setAttribute('aria-label', semanticsObject.label!);
}
Expand All @@ -69,7 +68,7 @@ class ImageRoleManager extends RoleManager {

void _cleanupElement() {
semanticsObject.setAriaRole('img', false);
semanticsObject.element.attributes.remove('aria-label');
semanticsObject.element.removeAttribute('aria-label');
}

@override
Expand Down
10 changes: 5 additions & 5 deletions lib/web_ui/lib/src/engine/semantics/incrementable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:html' as html;

import 'package:ui/ui.dart' as ui;

import '../dom.dart';
import '../platform_dispatcher.dart';
import '../safe_browser_api.dart';
import 'semantics.dart';

/// Adds increment/decrement event handling to a semantics object.
Expand All @@ -20,7 +20,7 @@ import 'semantics.dart';
/// gestures must be interpreted by the Flutter framework.
class Incrementable extends RoleManager {
/// The HTML element used to render semantics to the browser.
final html.InputElement _element = html.InputElement();
final DomHTMLInputElement _element = createDomHTMLInputElement();

/// The value used by the input element.
///
Expand Down Expand Up @@ -49,7 +49,7 @@ class Incrementable extends RoleManager {
_element.type = 'range';
_element.setAttribute('role', 'slider');

_element.addEventListener('change', (_) {
_element.addEventListener('change', allowInterop((_) {
if (_element.disabled!) {
return;
}
Expand All @@ -64,7 +64,7 @@ class Incrementable extends RoleManager {
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
semanticsObject.id, ui.SemanticsAction.decrease, null);
}
});
}));

// Store the callback as a closure because Dart does not guarantee that
// tear-offs produce the same function object.
Expand Down
9 changes: 4 additions & 5 deletions lib/web_ui/lib/src/engine/semantics/label_and_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:html' as html;

import 'package:ui/ui.dart' as ui;

import '../configuration.dart';
import '../dom.dart';
import 'semantics.dart';

/// Renders [_label] and [_value] to the semantics DOM.
Expand Down Expand Up @@ -47,7 +46,7 @@ class LabelAndValue extends RoleManager {
/// its label is not reachable via accessibility focus. This happens, for
/// example in popup dialogs, such as the alert dialog. The text of the
/// alert is supplied as a label on the parent node.
html.Element? _auxiliaryValueElement;
DomElement? _auxiliaryValueElement;

@override
void update() {
Expand Down Expand Up @@ -90,7 +89,7 @@ class LabelAndValue extends RoleManager {
}

if (_auxiliaryValueElement == null) {
_auxiliaryValueElement = html.Element.tag('flt-semantics-value');
_auxiliaryValueElement = domDocument.createElement('flt-semantics-value');
// Absolute positioning and sizing of leaf text elements confuses
// VoiceOver. So we let the browser size the value node. The node will
// still have a bigger tap area. However, if the node is a parent to other
Expand Down Expand Up @@ -119,7 +118,7 @@ class LabelAndValue extends RoleManager {
_auxiliaryValueElement!.remove();
_auxiliaryValueElement = null;
}
semanticsObject.element.attributes.remove('aria-label');
semanticsObject.element.removeAttribute('aria-label');
semanticsObject.setAriaRole('heading', false);
}

Expand Down
3 changes: 2 additions & 1 deletion lib/web_ui/lib/src/engine/semantics/live_region.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../dom.dart';
import 'semantics.dart';

/// Manages semantics configurations that represent live regions.
Expand Down Expand Up @@ -31,7 +32,7 @@ class LiveRegion extends RoleManager {
}

void _cleanupDom() {
semanticsObject.element.attributes.remove('aria-live');
semanticsObject.element.removeAttribute('aria-live');
}

@override
Expand Down
Loading

0 comments on commit ae357ca

Please sign in to comment.