Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
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
7 changes: 7 additions & 0 deletions lib/web_ui/lib/src/engine/browser_detection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ enum BrowserEngine {
/// The engine that powers Safari.
webkit,

/// The engine that powers Firefox.
firefox,

/// We were unable to detect the current browser engine.
unknown,
}
Expand All @@ -31,6 +34,10 @@ BrowserEngine _detectBrowserEngine() {
return BrowserEngine.blink;
} else if (vendor == 'Apple Computer, Inc.') {
return BrowserEngine.webkit;
} else if (vendor == '') {
// An empty string means firefox:
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vendor
return BrowserEngine.firefox;
}

// Assume blink otherwise, but issue a warning.
Expand Down
10 changes: 9 additions & 1 deletion lib/web_ui/lib/src/engine/dom_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,20 @@ flt-semantics input[type=range] {
' -webkit-appearance: none;'
'}',
sheet.cssRules.length);
}

if (browserEngine == BrowserEngine.firefox) {
sheet.insertRule(
'input::-moz-selection {'
' background-color: transparent;'
'}',
sheet.cssRules.length);
} else {
// On iOS, the invisible semantic text field has a visible cursor and
// selection highlight. The following 2 CSS rules force everything to be
// transparent.
sheet.insertRule(
'flt-semantics ::selection {'
'input::selection {'
' background-color: transparent;'
'}',
sheet.cssRules.length);
Expand Down
11 changes: 8 additions & 3 deletions lib/web_ui/lib/src/engine/platform_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,20 @@ void _createPlatformView(
final Map<dynamic, dynamic> args = methodCall.arguments;
final int id = args['id'];
final String viewType = args['viewType'];
const MethodCodec codec = StandardMethodCodec();

// TODO(het): Use 'direction', 'width', and 'height'.
if (!platformViewRegistry._registeredFactories.containsKey(viewType)) {
// TODO(het): Do we have a way of nicely reporting errors during platform
// channel calls?
callback(null);
callback(codec.encodeErrorEnvelope(
code: 'Unregistered factory',
message: "No factory registered for viewtype '$viewType'",
));
return;
}
// TODO(het): Use creation parameters.
final html.Element element =
platformViewRegistry._registeredFactories[viewType](id);

platformViewRegistry._createdViews[id] = element;
callback(codec.encodeSuccessEnvelope(null));
}
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/semantics/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class TextField extends RoleManager {

switch (browserEngine) {
case BrowserEngine.blink:
case BrowserEngine.firefox:
case BrowserEngine.unknown:
_initializeForBlink();
break;
Expand Down
32 changes: 25 additions & 7 deletions lib/web_ui/lib/src/engine/text/paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ class EngineParagraph implements ui.Paragraph {

@override
List<ui.LineMetrics> computeLineMetrics() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Return empty List here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Should be good now. Once and for all!

Copy link
Contributor

Choose a reason for hiding this comment

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

cc @GaryQian does anything in flutter call this yet? Will returning null be safe here?

Copy link
Contributor

@GaryQian GaryQian Aug 30, 2019

Choose a reason for hiding this comment

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

Nothing calls this yet. API just landed today/yesterday. Should be safe to return null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you look at the overall diff for this PR you'll notice that there's no change here. IOW, returning null is exactly what we're already doing. However, I filed an issue for us to implement this properly: flutter/flutter#39537

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated the TODO with the issue link

// TODO(flutter_web): Implement this.
// TODO(flutter_web): https://github.com/flutter/flutter/issues/39537
return null;
}
}
Expand Down Expand Up @@ -1059,7 +1059,7 @@ void _applyParagraphStyleToElement({
cssStyle.lineHeight = '${style._lineHeight}';
}
if (style._textDirection != null) {
cssStyle.direction = _textDirectionToCssValue(style._textDirection);
cssStyle.direction = _textDirectionToCss(style._textDirection);
}
if (style._fontSize != null) {
cssStyle.fontSize = '${style._fontSize.floor()}px';
Expand All @@ -1083,7 +1083,7 @@ void _applyParagraphStyleToElement({
cssStyle.lineHeight = '${style._lineHeight}';
}
if (style._textDirection != previousStyle._textDirection) {
cssStyle.direction = _textDirectionToCssValue(style._textDirection);
cssStyle.direction = _textDirectionToCss(style._textDirection);
}
if (style._fontSize != previousStyle._fontSize) {
cssStyle.fontSize =
Expand Down Expand Up @@ -1272,10 +1272,28 @@ String _decorationStyleToCssString(ui.TextDecorationStyle decorationStyle) {
/// ```css
/// direction: rtl;
/// ```
String _textDirectionToCssValue(ui.TextDirection textDirection) {
return textDirection == ui.TextDirection.ltr
? null // it's the default
: 'rtl';
String _textDirectionToCss(ui.TextDirection textDirection) {
if (textDirection == null) {
return null;
}
return textDirectionIndexToCss(textDirection.index);
}

String textDirectionIndexToCss(int textDirectionIndex) {
switch (textDirectionIndex) {
case 0:
return 'rtl';
case 1:
return null; // ltr is the default
}

assert(() {
throw AssertionError(
'Failed to convert text direction $textDirectionIndex to CSS',
);
}());

return null;
}

/// Converts [align] to its corresponding CSS value.
Expand Down
121 changes: 59 additions & 62 deletions lib/web_ui/lib/src/engine/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,31 @@ void _emptyCallback(dynamic _) {}
///
/// They are assigned once during the creation of the dom element.
void _setStaticStyleAttributes(html.HtmlElement domElement) {
domElement.style
final html.CssStyleDeclaration elementStyle = domElement.style;
elementStyle
..whiteSpace = 'pre'
..alignContent = 'center'
..position = 'absolute'
..top = '0'
..left = '0'
..padding = '0'
..opacity = '1';
..opacity = '1'
..color = 'transparent'
..backgroundColor = 'transparent'
..background = 'transparent'
..outline = 'none'
..border = 'none'
..resize = 'none'
..textShadow = 'transparent'
..transformOrigin = '0 0 0';

/// This property makes the input's blinking cursor transparent.
elementStyle.setProperty('caret-color', 'transparent');

if (_debugVisibleTextEditing) {
domElement.style
elementStyle
..color = 'purple'
..backgroundColor = 'pink';
} else {
domElement.style
..color = 'transparent'
..backgroundColor = 'transparent'
..background = 'transparent'
..border = 'none'
..resize = 'none'
..cursor = 'none'
..textShadow = 'transparent'
..outline = 'none';

/// This property makes the cursor transparent in mobile browsers where
/// cursor = 'none' does not work.
domElement.style.setProperty('caret-color', 'transparent');
..outline = '1px solid purple';
}
}

Expand Down Expand Up @@ -479,6 +480,7 @@ class PersistentTextEditingElement extends TextEditingElement {
}) : _onDomElementSwap = onDomElementSwap,
super(owner) {
// Make sure the dom element is of a type that we support for text editing.
// TODO(yjbanov): move into initializer list when https://github.com/dart-lang/sdk/issues/37881 is fixed.
assert(_getTypeFromElement(domElement) != null);
this.domElement = domElement;
}
Expand Down Expand Up @@ -611,7 +613,7 @@ class HybridTextEditing {
}
break;

case 'TextInput.setEditingLocationSize':
case 'TextInput.setEditableSizeAndTransform':
_setLocation(call.arguments);
break;

Expand Down Expand Up @@ -656,41 +658,41 @@ class HybridTextEditing {
assert(style.containsKey('fontSize'));
assert(style.containsKey('fontFamily'));
assert(style.containsKey('textAlignIndex'));
assert(style.containsKey('textDirectionIndex'));

final int textAlignIndex = style.remove('textAlignIndex');
final int textAlignIndex = style['textAlignIndex'];
final int textDirectionIndex = style['textDirectionIndex'];

/// Converts integer value coming as fontWeightValue from TextInput.setStyle
/// Converts integer value coming as fontWeightIndex from TextInput.setStyle
/// to its CSS equivalent value.
/// Converts index of TextAlign to enum value.
_editingStyle = _EditingStyle(
textDirection: style.containsKey('textDirection')
? style.remove('textDirection')
: ui.TextDirection.ltr,
fontSize: style.remove('fontSize'),
textDirection: ui.TextDirection.values[textDirectionIndex],
fontSize: style['fontSize'],
textAlign: ui.TextAlign.values[textAlignIndex],
fontFamily: style.remove('fontFamily'),
fontWeight: fontWeightIndexToCss(
fontWeightIndex: style.remove('fontWeightValue')),
fontFamily: style['fontFamily'],
fontWeight:
fontWeightIndexToCss(fontWeightIndex: style['fontWeightIndex']),
);
}

/// Location of the editable text on the page as a rectangle.
// TODO(flutter_web): investigate if transform matrix can be used instead of
// a rectangle.
_EditingLocationAndSize _editingLocationAndSize;
_EditingLocationAndSize get editingLocationAndSize => _editingLocationAndSize;
/// Size and transform of the editable text on the page.
_EditableSizeAndTransform _editingLocationAndSize;
_EditableSizeAndTransform get editingLocationAndSize =>
_editingLocationAndSize;

void _setLocation(Map<String, dynamic> editingLocationAndSize) {
assert(editingLocationAndSize.containsKey('top'));
assert(editingLocationAndSize.containsKey('left'));
assert(editingLocationAndSize.containsKey('width'));
assert(editingLocationAndSize.containsKey('height'));

_editingLocationAndSize = _EditingLocationAndSize(
top: editingLocationAndSize.remove('top'),
left: editingLocationAndSize.remove('left'),
width: editingLocationAndSize.remove('width'),
height: editingLocationAndSize.remove('height'));
assert(editingLocationAndSize.containsKey('transform'));

final List<double> transformList =
List<double>.from(editingLocationAndSize['transform']);
_editingLocationAndSize = _EditableSizeAndTransform(
width: editingLocationAndSize['width'],
height: editingLocationAndSize['height'],
transform: Float64List.fromList(transformList),
);

if (editingElement.domElement != null) {
_setDynamicStyleAttributes(editingElement.domElement);
Expand All @@ -714,7 +716,7 @@ class HybridTextEditing {
/// element.
///
/// They are changed depending on the messages coming from method calls:
/// "TextInput.setStyle", "TextInput.setEditingLocationSize".
/// "TextInput.setStyle", "TextInput.setEditableSizeAndTransform".
void _setDynamicStyleAttributes(html.HtmlElement domElement) {
if (_editingLocationAndSize != null &&
!(browserEngine == BrowserEngine.webkit &&
Expand All @@ -731,18 +733,14 @@ class HybridTextEditing {
/// Users can interact with the element and use the functionalities of the
/// right-click menu. Such as copy,paste, cut, select, translate...
void setStyle(html.HtmlElement domElement) {
final String transformCss =
float64ListToCssTransform(_editingLocationAndSize.transform);
domElement.style
..top = '${_editingLocationAndSize.top}px'
..left = '${_editingLocationAndSize.left}px'
..width = '${_editingLocationAndSize.width}px'
..height = '${_editingLocationAndSize.height}px';
if (_debugVisibleTextEditing) {
domElement.style.font = '24px sans-serif';
} else {
domElement.style
..textAlign = _editingStyle.align
..font = font();
}
..height = '${_editingLocationAndSize.height}px'
..textAlign = _editingStyle.align
..font = font()
..transform = transformCss;
}

html.InputElement createInputElement() {
Expand Down Expand Up @@ -785,19 +783,18 @@ class _EditingStyle {

/// Information on the location and size of the editing element.
///
/// This information is received via "TextInput.setEditingLocationSize"
/// This information is received via "TextInput.setEditableSizeAndTransform"
/// message. Framework currently sends this information on paint.
// TODO(flutter_web): send the location during the scroll for more frequent
// updates from the framework.
class _EditingLocationAndSize {
_EditingLocationAndSize(
{@required this.top,
@required this.left,
@required this.width,
@required this.height});

final double top;
final double left;
class _EditableSizeAndTransform {
_EditableSizeAndTransform({
@required this.width,
@required this.height,
@required this.transform,
});

final double width;
final double height;
final Float64List transform;
}
26 changes: 15 additions & 11 deletions lib/web_ui/test/text_editing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -471,21 +471,22 @@ void main() {
'TextInput.setClient', <dynamic>[123, flutterSinglelineConfig]);
textEditing.handleTextInput(codec.encodeMethodCall(setClient));

const MethodCall setLocationSize =
MethodCall('TextInput.setEditingLocationSize', <String, dynamic>{
'top': 0,
'left': 0,
final MethodCall setSizeAndTransform =
MethodCall('TextInput.setEditableSizeAndTransform', <String, dynamic>{
'width': 150,
'height': 50,
'transform':
Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList()
});
textEditing.handleTextInput(codec.encodeMethodCall(setLocationSize));
textEditing.handleTextInput(codec.encodeMethodCall(setSizeAndTransform));

const MethodCall setStyle =
MethodCall('TextInput.setStyle', <String, dynamic>{
'fontSize': 12,
'fontFamily': 'sans-serif',
'textAlignIndex': 4,
'fontWeightValue': 4,
'fontWeightIndex': 4,
'textDirectionIndex': 1,
});
textEditing.handleTextInput(codec.encodeMethodCall(setStyle));

Expand All @@ -500,14 +501,17 @@ void main() {
const MethodCall show = MethodCall('TextInput.show');
textEditing.handleTextInput(codec.encodeMethodCall(show));

checkInputEditingState(
textEditing.editingElement.domElement, 'abcd', 2, 3);
final HtmlElement domElement = textEditing.editingElement.domElement;

checkInputEditingState(domElement, 'abcd', 2, 3);

// Check if the location and styling is correct.
expect(
textEditing.editingElement.domElement.getBoundingClientRect(),
Rectangle<double>.fromPoints(
const Point<double>(0.0, 0.0), const Point<double>(150.0, 50.0)));
domElement.getBoundingClientRect(),
Rectangle<double>.fromPoints(const Point<double>(10.0, 20.0),
const Point<double>(160.0, 70.0)));
expect(domElement.style.transform,
'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 20, 30, 1)');
expect(textEditing.editingElement.domElement.style.font,
'500 12px sans-serif');

Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/test/title_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ void main() {
// Run the unit test without emulating Flutter tester environment.
ui.debugEmulateFlutterTesterEnvironment = false;

// TODO(yjbanov): https://github.com/flutter/flutter/issues/39159
document.title = '';
expect(document.title, '');

Expand Down