mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 12:58:05 +00:00
- Fork implementation for Dartium and dart2js. Dartium is working fine.
- dart2js version uses Element.classList (DomTokenList) for 4x speedup. - Fix bug where document.querySelectorAll(...).classes.add('x') fails for SVG elements. - Add test for above. For later: - Identify a polyfill for IE SVGElements (they don't have classList) - Simplify CssClassSet further to exploit uniform availability of classList. This should give another performance boost by enabling inlining. R=alanknight@google.com Committed: https://code.google.com/p/dart/source/detail?r=44888 Committed: https://code.google.com/p/dart/source/detail?r=44897 Review URL: https://codereview.chromium.org//1054863002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@44898 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
3080315805
commit
2956fd8739
|
@ -55,12 +55,14 @@ import 'dart:_foreign_helper' show JS, JS_INTERCEPTOR_CONSTANT, JS_CONST;
|
|||
// Not actually used, but imported since dart:html can generate these objects.
|
||||
import 'dart:_js_helper' show
|
||||
convertDartClosureToJS, Creates, JavaScriptIndexingBehavior,
|
||||
JSName, Native, Null, Returns,
|
||||
JSName, Native, Null, Returns, Inline, ForceInline,
|
||||
findDispatchTagForInterceptorClass, setNativeSubclassDispatchRecord,
|
||||
makeLeafDispatchRecord;
|
||||
import 'dart:_interceptors' show
|
||||
Interceptor, JSExtendableArray, findInterceptorConstructorForType,
|
||||
findConstructorForNativeSubclassType, getNativeInterceptor,
|
||||
Interceptor, JSExtendableArray, JSUInt31,
|
||||
findInterceptorConstructorForType,
|
||||
findConstructorForNativeSubclassType,
|
||||
getNativeInterceptor,
|
||||
setDispatchProperty;
|
||||
|
||||
export 'dart:math' show Rectangle, Point;
|
||||
|
@ -34891,6 +34893,12 @@ abstract class CssClassSet implements Set<String> {
|
|||
*
|
||||
* If [shouldAdd] is true, then we always add that [value] to the element. If
|
||||
* [shouldAdd] is false then we always remove [value] from the element.
|
||||
*
|
||||
* If this corresponds to one element, returns `true` if [value] is present
|
||||
* after the operation, and returns `false` if [value] is absent after the
|
||||
* operation.
|
||||
*
|
||||
* If this corresponds to many elements, `null` is always returned.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]);
|
||||
|
||||
|
@ -34917,7 +34925,7 @@ abstract class CssClassSet implements Set<String> {
|
|||
* If this corresponds to one element. Returns true if [value] was added to
|
||||
* the set, otherwise false.
|
||||
*
|
||||
* If this corresponds to many elements, null is always returned.
|
||||
* If this corresponds to many elements, `null` is always returned.
|
||||
*/
|
||||
bool add(String value);
|
||||
|
||||
|
@ -34958,90 +34966,6 @@ abstract class CssClassSet implements Set<String> {
|
|||
*/
|
||||
void toggleAll(Iterable<String> iterable, [bool shouldAdd]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A set (union) of the CSS classes that are present in a set of elements.
|
||||
* Implemented separately from _ElementCssClassSet for performance.
|
||||
*/
|
||||
class _MultiElementCssClassSet extends CssClassSetImpl {
|
||||
final Iterable<Element> _elementIterable;
|
||||
Iterable<_ElementCssClassSet> _elementCssClassSetIterable;
|
||||
|
||||
_MultiElementCssClassSet(this._elementIterable) {
|
||||
_elementCssClassSetIterable = new List.from(_elementIterable).map(
|
||||
(e) => new _ElementCssClassSet(e));
|
||||
}
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
_elementCssClassSetIterable.forEach(
|
||||
(_ElementCssClassSet e) => s.addAll(e.readClasses()));
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
var classes = s.join(' ');
|
||||
for (Element e in _elementIterable) {
|
||||
e.className = classes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to modify the set of css classes on this element.
|
||||
*
|
||||
* f - callback with:
|
||||
* s - a Set of all the css class name currently on this element.
|
||||
*
|
||||
* After f returns, the modified set is written to the
|
||||
* className property of this element.
|
||||
*/
|
||||
modify( f(Set<String> s)) {
|
||||
_elementCssClassSetIterable.forEach((_ElementCssClassSet e) => e.modify(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the class [value] to the element if it is not on it, removes it if it
|
||||
* is.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]) =>
|
||||
_elementCssClassSetIterable.fold(false,
|
||||
(bool changed, _ElementCssClassSet e) =>
|
||||
e.toggle(value, shouldAdd) || changed);
|
||||
|
||||
/**
|
||||
* Remove the class [value] from element, and return true on successful
|
||||
* removal.
|
||||
*
|
||||
* This is the Dart equivalent of jQuery's
|
||||
* [removeClass](http://api.jquery.com/removeClass/).
|
||||
*/
|
||||
bool remove(Object value) => _elementCssClassSetIterable.fold(false,
|
||||
(bool changed, _ElementCssClassSet e) => e.remove(value) || changed);
|
||||
}
|
||||
|
||||
class _ElementCssClassSet extends CssClassSetImpl {
|
||||
|
||||
final Element _element;
|
||||
|
||||
_ElementCssClassSet(this._element);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
var classname = _element.className;
|
||||
|
||||
for (String name in classname.split(' ')) {
|
||||
String trimmed = name.trim();
|
||||
if (!trimmed.isEmpty) {
|
||||
s.add(trimmed);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
_element.className = s.join(' ');
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2013, 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.
|
||||
|
@ -35303,6 +35227,254 @@ final _WIDTH = ['right', 'left'];
|
|||
final _CONTENT = 'content';
|
||||
final _PADDING = 'padding';
|
||||
final _MARGIN = 'margin';
|
||||
// Copyright (c) 2015, 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.
|
||||
|
||||
|
||||
/**
|
||||
* A set (union) of the CSS classes that are present in a set of elements.
|
||||
* Implemented separately from _ElementCssClassSet for performance.
|
||||
*/
|
||||
class _MultiElementCssClassSet extends CssClassSetImpl {
|
||||
final Iterable<Element> _elementIterable;
|
||||
|
||||
// TODO(sra): Perhaps we should store the DomTokenList instead.
|
||||
final List<CssClassSetImpl> _sets;
|
||||
|
||||
factory _MultiElementCssClassSet(Iterable<Element> elements) {
|
||||
return new _MultiElementCssClassSet._(elements,
|
||||
elements.map((Element e) => e.classes).toList());
|
||||
}
|
||||
|
||||
_MultiElementCssClassSet._(this._elementIterable, this._sets);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
_sets.forEach((CssClassSetImpl e) => s.addAll(e.readClasses()));
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
var classes = s.join(' ');
|
||||
for (Element e in _elementIterable) {
|
||||
e.className = classes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to modify the set of css classes on this element.
|
||||
*
|
||||
* f - callback with:
|
||||
* s - a Set of all the css class name currently on this element.
|
||||
*
|
||||
* After f returns, the modified set is written to the
|
||||
* className property of this element.
|
||||
*/
|
||||
modify( f(Set<String> s)) {
|
||||
_sets.forEach((CssClassSetImpl e) => e.modify(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the class [value] to the element if it is not on it, removes it if it
|
||||
* is.
|
||||
*
|
||||
* TODO(sra): It seems wrong to collect a 'changed' flag like this when the
|
||||
* underlying toggle returns an 'is set' flag.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]) =>
|
||||
_sets.fold(false,
|
||||
(bool changed, CssClassSetImpl e) =>
|
||||
e.toggle(value, shouldAdd) || changed);
|
||||
|
||||
/**
|
||||
* Remove the class [value] from element, and return true on successful
|
||||
* removal.
|
||||
*
|
||||
* This is the Dart equivalent of jQuery's
|
||||
* [removeClass](http://api.jquery.com/removeClass/).
|
||||
*/
|
||||
bool remove(Object value) => _sets.fold(false,
|
||||
(bool changed, CssClassSetImpl e) => e.remove(value) || changed);
|
||||
}
|
||||
|
||||
class _ElementCssClassSet extends CssClassSetImpl {
|
||||
final Element _element;
|
||||
|
||||
_ElementCssClassSet(this._element);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
var classname = _element.className;
|
||||
|
||||
for (String name in classname.split(' ')) {
|
||||
String trimmed = name.trim();
|
||||
if (!trimmed.isEmpty) {
|
||||
s.add(trimmed);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
_element.className = s.join(' ');
|
||||
}
|
||||
|
||||
int get length => _classListLength(_classListOf(_element));
|
||||
bool get isEmpty => length == 0;
|
||||
bool get isNotEmpty => length != 0;
|
||||
|
||||
void clear() {
|
||||
_element.className = '';
|
||||
}
|
||||
|
||||
bool contains(String value) {
|
||||
return _contains(_element, value);
|
||||
}
|
||||
|
||||
bool add(String value) {
|
||||
return _add(_element, value);
|
||||
}
|
||||
|
||||
bool remove(Object value) {
|
||||
return value is String && _remove(_element, value);
|
||||
}
|
||||
|
||||
bool toggle(String value, [bool shouldAdd]) {
|
||||
return _toggle(_element, value, shouldAdd);
|
||||
}
|
||||
|
||||
void addAll(Iterable<String> iterable) {
|
||||
_addAll(_element, iterable);
|
||||
}
|
||||
|
||||
void removeAll(Iterable<String> iterable) {
|
||||
_removeAll(_element, iterable);
|
||||
}
|
||||
|
||||
void retainAll(Iterable<String> iterable) {
|
||||
_removeWhere(_element, iterable.toSet().contains, false);
|
||||
}
|
||||
|
||||
void removeWhere(bool test(String name)) {
|
||||
_removeWhere(_element, test, true);
|
||||
}
|
||||
|
||||
void retainWhere(bool test(String name)) {
|
||||
_removeWhere(_element, test, false);
|
||||
}
|
||||
|
||||
static bool _contains(Element _element, String value) {
|
||||
return _classListContains(_classListOf(_element), value);
|
||||
}
|
||||
|
||||
static bool _add(Element _element, String value) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
// Compute returned result independently of action upon the set. One day we
|
||||
// will be able to optimize it way if unused.
|
||||
bool added = !_classListContains(list, value);
|
||||
_classListAdd(list, value);
|
||||
return added;
|
||||
}
|
||||
|
||||
static bool _remove(Element _element, String value) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
bool removed = _classListContains(list, value);
|
||||
_classListRemove(list, value);
|
||||
return removed;
|
||||
}
|
||||
|
||||
static bool _toggle(Element _element, String value, bool shouldAdd) {
|
||||
// There is no value that can be passed as the second argument of
|
||||
// DomTokenList.toggle that behaves the same as passing one argument.
|
||||
// `null` is seen as false, meaning 'remove'.
|
||||
return shouldAdd == null
|
||||
? _toggleDefault(_element, value)
|
||||
: _toggleOnOff(_element, value, shouldAdd);
|
||||
}
|
||||
|
||||
static bool _toggleDefault(Element _element, String value) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
return _classListToggle1(list, value);
|
||||
}
|
||||
|
||||
static bool _toggleOnOff(Element _element, String value, bool shouldAdd) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
// IE's toggle does not take a second parameter. We would prefer:
|
||||
//
|
||||
// return _classListToggle2(list, value, shouldAdd);
|
||||
//
|
||||
if (shouldAdd) {
|
||||
_classListAdd(list, value);
|
||||
return true;
|
||||
} else {
|
||||
_classListRemove(list, value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void _addAll(Element _element, Iterable<String> iterable) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
for (String value in iterable) {
|
||||
_classListAdd(list, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void _removeAll(Element _element, Iterable<String> iterable) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
for (var value in iterable) {
|
||||
_classListRemove(list, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void _removeWhere(
|
||||
Element _element, bool test(String name), bool doRemove) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
int i = 0;
|
||||
while (i < _classListLength(list)) {
|
||||
String item = list.item(i);
|
||||
if (doRemove == test(item)) {
|
||||
_classListRemove(list, item);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A collection of static methods for DomTokenList. These methods are a
|
||||
// work-around for the lack of annotations to express the full behaviour of
|
||||
// the DomTokenList methods.
|
||||
|
||||
static DomTokenList _classListOf(Element e) =>
|
||||
JS('returns:DomTokenList;creates:DomTokenList;effects:none;depends:all;',
|
||||
'#.classList', e);
|
||||
|
||||
static int _classListLength(DomTokenList list) =>
|
||||
JS('returns:JSUInt31;effects:none;depends:all;', '#.length', list);
|
||||
|
||||
static bool _classListContains(DomTokenList list, String value) =>
|
||||
JS('returns:bool;effects:none;depends:all;',
|
||||
'#.contains(#)', list, value);
|
||||
|
||||
static void _classListAdd(DomTokenList list, String value) {
|
||||
// list.add(value);
|
||||
JS('', '#.add(#)', list, value);
|
||||
}
|
||||
|
||||
static void _classListRemove(DomTokenList list, String value) {
|
||||
// list.remove(value);
|
||||
JS('', '#.remove(#)', list, value);
|
||||
}
|
||||
|
||||
static bool _classListToggle1(DomTokenList list, String value) {
|
||||
return JS('bool', '#.toggle(#)', list, value);
|
||||
}
|
||||
|
||||
static bool _classListToggle2(
|
||||
DomTokenList list, String value, bool shouldAdd) {
|
||||
return JS('bool', '#.toggle(#, #)', list, value, shouldAdd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a
|
||||
|
|
|
@ -37276,6 +37276,12 @@ abstract class CssClassSet implements Set<String> {
|
|||
*
|
||||
* If [shouldAdd] is true, then we always add that [value] to the element. If
|
||||
* [shouldAdd] is false then we always remove [value] from the element.
|
||||
*
|
||||
* If this corresponds to one element, returns `true` if [value] is present
|
||||
* after the operation, and returns `false` if [value] is absent after the
|
||||
* operation.
|
||||
*
|
||||
* If this corresponds to many elements, `null` is always returned.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]);
|
||||
|
||||
|
@ -37302,7 +37308,7 @@ abstract class CssClassSet implements Set<String> {
|
|||
* If this corresponds to one element. Returns true if [value] was added to
|
||||
* the set, otherwise false.
|
||||
*
|
||||
* If this corresponds to many elements, null is always returned.
|
||||
* If this corresponds to many elements, `null` is always returned.
|
||||
*/
|
||||
bool add(String value);
|
||||
|
||||
|
@ -37343,6 +37349,10 @@ abstract class CssClassSet implements Set<String> {
|
|||
*/
|
||||
void toggleAll(Iterable<String> iterable, [bool shouldAdd]);
|
||||
}
|
||||
// Copyright (c) 2015, 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.
|
||||
|
||||
|
||||
/**
|
||||
* A set (union) of the CSS classes that are present in a set of elements.
|
||||
|
|
|
@ -84,9 +84,7 @@ main() {
|
|||
b.attributes.remove('data-v');
|
||||
expect(B.invocations, ['data-v: x => null']);
|
||||
});
|
||||
});
|
||||
|
||||
group('unsupported_on_polyfill', () {
|
||||
test('add, change ID', () {
|
||||
B.invocations = [];
|
||||
|
||||
|
@ -98,6 +96,12 @@ main() {
|
|||
b.attributes.remove('id');
|
||||
expect(B.invocations, ['id: x => null']);
|
||||
});
|
||||
});
|
||||
|
||||
group('unsupported_on_polyfill', () {
|
||||
|
||||
// If these tests start passing, don't remove the status suppression. Move
|
||||
// the tests to the fullYy_supported group.
|
||||
|
||||
test('add, change classes', () {
|
||||
var b = new B();
|
||||
|
|
200
tests/html/element_classes_svg_test.dart
Normal file
200
tests/html/element_classes_svg_test.dart
Normal file
|
@ -0,0 +1,200 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
library ElementTest;
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:unittest/html_config.dart';
|
||||
import 'dart:collection';
|
||||
import 'dart:html';
|
||||
import 'dart:svg' as svg;
|
||||
|
||||
// Test for `querySelectorAll(xxx).classes.op()` where the query returns mixed
|
||||
// Html and Svg elements.
|
||||
|
||||
Element makeElementsContainer() {
|
||||
var e = new Element.html(
|
||||
'<ul class="yes foo">'
|
||||
'<li class="yes quux qux">'
|
||||
'</ul>');
|
||||
final svgContent = r"""
|
||||
<svg version="1.1">
|
||||
<circle class="yes qux"></circle>
|
||||
<path class="yes classy"></path>
|
||||
</svg>""";
|
||||
final svgElement = new svg.SvgElement.svg(svgContent);
|
||||
e.append(svgElement);
|
||||
return e;
|
||||
}
|
||||
|
||||
Element elementsContainer;
|
||||
|
||||
ElementList<Element> elementsSetup() {
|
||||
elementsContainer = makeElementsContainer();
|
||||
document.documentElement.children.add(elementsContainer);
|
||||
var elements = document.querySelectorAll('.yes');
|
||||
expect(elements.length, 4);
|
||||
return elements;
|
||||
}
|
||||
|
||||
void elementsTearDown() {
|
||||
if (elementsContainer != null) {
|
||||
document.documentElement.children.remove(elementsContainer);
|
||||
elementsContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a canonical string for Set<String> and lists of Element's classes.
|
||||
String view(var e) {
|
||||
if (e is Set) return '${e.toList()..sort()}';
|
||||
if (e is Element) return view(e.classes);
|
||||
if (e is Iterable) return '${e.map(view).toList()}';
|
||||
throw new ArgumentError('Cannot make canonical view string for: $e}');
|
||||
}
|
||||
|
||||
main() {
|
||||
useHtmlConfiguration();
|
||||
|
||||
Set<String> extractClasses(Element el) {
|
||||
final match = new RegExp('class="([^"]+)"').firstMatch(el.outerHtml);
|
||||
return new LinkedHashSet.from(match[1].split(' '));
|
||||
}
|
||||
|
||||
tearDown(elementsTearDown);
|
||||
|
||||
test('list_view', () {
|
||||
// Test that the 'view' helper function is behaving.
|
||||
var elements = elementsSetup();
|
||||
expect(view(elements.classes), '[classy, foo, quux, qux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, yes], [quux, qux, yes], [qux, yes], [classy, yes]]');
|
||||
});
|
||||
|
||||
test('listClasses=', () {
|
||||
var elements = elementsSetup();
|
||||
|
||||
elements.classes = ['foo', 'qux'];
|
||||
expect(view(elements.classes), '[foo, qux]');
|
||||
expect(view(elements), '[[foo, qux], [foo, qux], [foo, qux], [foo, qux]]');
|
||||
|
||||
var elements2 = document.querySelectorAll('.qux');
|
||||
expect(view(elements2.classes), '[foo, qux]');
|
||||
expect(view(elements2), '[[foo, qux], [foo, qux], [foo, qux], [foo, qux]]');
|
||||
|
||||
for (Element e in elements2) {
|
||||
expect(e.classes, orderedEquals(['foo', 'qux']));
|
||||
expect(extractClasses(e), orderedEquals(['foo', 'qux']));
|
||||
}
|
||||
|
||||
elements.classes = [];
|
||||
expect(view(elements2.classes), '[]');
|
||||
expect(view(elements2), '[[], [], [], []]');
|
||||
});
|
||||
|
||||
test('listMap', () {
|
||||
var elements = elementsSetup();
|
||||
expect(elements.classes.map((c) => c.toUpperCase()).toList(),
|
||||
unorderedEquals(['YES', 'FOO', 'QUX', 'QUUX', 'CLASSY']));
|
||||
});
|
||||
|
||||
test('listContains', () {
|
||||
var elements = elementsSetup();
|
||||
expect(elements.classes.contains('classy'), isTrue);
|
||||
expect(elements.classes.contains('troll'), isFalse);
|
||||
});
|
||||
|
||||
|
||||
test('listAdd', () {
|
||||
var elements = elementsSetup();
|
||||
var added = elements.classes.add('lassie');
|
||||
expect(added, isNull);
|
||||
|
||||
expect(view(elements.classes), '[classy, foo, lassie, quux, qux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, lassie, yes], [lassie, quux, qux, yes], '
|
||||
'[lassie, qux, yes], [classy, lassie, yes]]');
|
||||
});
|
||||
|
||||
test('listRemove', () {
|
||||
var elements = elementsSetup();
|
||||
expect(elements.classes.remove('lassi'), isFalse);
|
||||
expect(view(elements.classes), '[classy, foo, quux, qux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, yes], [quux, qux, yes], [qux, yes], [classy, yes]]');
|
||||
|
||||
expect(elements.classes.remove('qux'), isTrue);
|
||||
expect(view(elements.classes), '[classy, foo, quux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, yes], [quux, yes], [yes], [classy, yes]]');
|
||||
});
|
||||
|
||||
test('listToggle', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.toggle('qux');
|
||||
expect(view(elements.classes), '[classy, foo, quux, qux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, qux, yes], [quux, yes], [yes], [classy, qux, yes]]');
|
||||
});
|
||||
|
||||
test('listAddAll', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.addAll(['qux', 'lassi', 'sassy']);
|
||||
expect(view(elements.classes),
|
||||
'[classy, foo, lassi, quux, qux, sassy, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, lassi, qux, sassy, yes], [lassi, quux, qux, sassy, yes], '
|
||||
'[lassi, qux, sassy, yes], [classy, lassi, qux, sassy, yes]]');
|
||||
});
|
||||
|
||||
test('listRemoveAll', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.removeAll(['qux', 'classy', 'mumble']);
|
||||
expect(view(elements.classes), '[foo, quux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, yes], [quux, yes], [yes], [yes]]');
|
||||
|
||||
elements.classes.removeAll(['foo', 'yes']);
|
||||
expect(view(elements.classes), '[quux]');
|
||||
expect(view(elements),
|
||||
'[[], [quux], [], []]');
|
||||
});
|
||||
|
||||
test('listToggleAll', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.toggleAll(['qux', 'mornin']);
|
||||
expect(view(elements.classes), '[classy, foo, mornin, quux, qux, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, mornin, qux, yes], [mornin, quux, yes], '
|
||||
'[mornin, yes], [classy, mornin, qux, yes]]');
|
||||
|
||||
});
|
||||
|
||||
test('listRetainAll', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.retainAll(['bar', 'baz', 'classy', 'qux']);
|
||||
expect(view(elements.classes), '[classy, qux]');
|
||||
expect(view(elements), '[[], [qux], [qux], [classy]]');
|
||||
});
|
||||
|
||||
test('listRemoveWhere', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.removeWhere((s) => s.startsWith('q'));
|
||||
expect(view(elements.classes), '[classy, foo, yes]');
|
||||
expect(view(elements),
|
||||
'[[foo, yes], [yes], [yes], [classy, yes]]');
|
||||
});
|
||||
|
||||
test('listRetainWhere', () {
|
||||
var elements = elementsSetup();
|
||||
elements.classes.retainWhere((s) => s.startsWith('q'));
|
||||
expect(view(elements.classes), '[quux, qux]');
|
||||
expect(view(elements),
|
||||
'[[], [quux, qux], [qux], []]');
|
||||
});
|
||||
|
||||
test('listContainsAll', () {
|
||||
var elements = elementsSetup();
|
||||
expect(elements.classes.containsAll(['qux', 'mornin']), isFalse);
|
||||
expect(elements.classes.containsAll(['qux', 'classy']), isTrue);
|
||||
});
|
||||
}
|
|
@ -36,7 +36,7 @@ storage_quota_test: Fail # Support for promises.
|
|||
|
||||
[ $compiler == dart2js && ($runtime == safari || $runtime == safarimobilesim || $runtime == ff || $ie) ]
|
||||
custom/entered_left_view_test/viewless_document: Fail # Polyfill does not handle this
|
||||
fontface_test: Fail # Fontface not supported on these.
|
||||
custom/attribute_changed_callback_test/unsupported_on_polyfill: Fail # Polyfill does not supportfontface_test: Fail # Fontface not supported on these.
|
||||
element_animate_test: Fail # Element.animate not supported on these browsers.
|
||||
|
||||
[ $compiler == none && $runtime == dartium && $system == macos]
|
||||
|
|
|
@ -13,6 +13,12 @@ abstract class CssClassSet implements Set<String> {
|
|||
*
|
||||
* If [shouldAdd] is true, then we always add that [value] to the element. If
|
||||
* [shouldAdd] is false then we always remove [value] from the element.
|
||||
*
|
||||
* If this corresponds to one element, returns `true` if [value] is present
|
||||
* after the operation, and returns `false` if [value] is absent after the
|
||||
* operation.
|
||||
*
|
||||
* If this corresponds to many elements, `null` is always returned.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]);
|
||||
|
||||
|
@ -39,7 +45,7 @@ abstract class CssClassSet implements Set<String> {
|
|||
* If this corresponds to one element. Returns true if [value] was added to
|
||||
* the set, otherwise false.
|
||||
*
|
||||
* If this corresponds to many elements, null is always returned.
|
||||
* If this corresponds to many elements, `null` is always returned.
|
||||
*/
|
||||
bool add(String value);
|
||||
|
||||
|
@ -80,87 +86,3 @@ abstract class CssClassSet implements Set<String> {
|
|||
*/
|
||||
void toggleAll(Iterable<String> iterable, [bool shouldAdd]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A set (union) of the CSS classes that are present in a set of elements.
|
||||
* Implemented separately from _ElementCssClassSet for performance.
|
||||
*/
|
||||
class _MultiElementCssClassSet extends CssClassSetImpl {
|
||||
final Iterable<Element> _elementIterable;
|
||||
Iterable<_ElementCssClassSet> _elementCssClassSetIterable;
|
||||
|
||||
_MultiElementCssClassSet(this._elementIterable) {
|
||||
_elementCssClassSetIterable = new List.from(_elementIterable).map(
|
||||
(e) => new _ElementCssClassSet(e));
|
||||
}
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
_elementCssClassSetIterable.forEach(
|
||||
(_ElementCssClassSet e) => s.addAll(e.readClasses()));
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
var classes = s.join(' ');
|
||||
for (Element e in _elementIterable) {
|
||||
e.className = classes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to modify the set of css classes on this element.
|
||||
*
|
||||
* f - callback with:
|
||||
* s - a Set of all the css class name currently on this element.
|
||||
*
|
||||
* After f returns, the modified set is written to the
|
||||
* className property of this element.
|
||||
*/
|
||||
modify( f(Set<String> s)) {
|
||||
_elementCssClassSetIterable.forEach((_ElementCssClassSet e) => e.modify(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the class [value] to the element if it is not on it, removes it if it
|
||||
* is.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]) =>
|
||||
_elementCssClassSetIterable.fold(false,
|
||||
(bool changed, _ElementCssClassSet e) =>
|
||||
e.toggle(value, shouldAdd) || changed);
|
||||
|
||||
/**
|
||||
* Remove the class [value] from element, and return true on successful
|
||||
* removal.
|
||||
*
|
||||
* This is the Dart equivalent of jQuery's
|
||||
* [removeClass](http://api.jquery.com/removeClass/).
|
||||
*/
|
||||
bool remove(Object value) => _elementCssClassSetIterable.fold(false,
|
||||
(bool changed, _ElementCssClassSet e) => e.remove(value) || changed);
|
||||
}
|
||||
|
||||
class _ElementCssClassSet extends CssClassSetImpl {
|
||||
|
||||
final Element _element;
|
||||
|
||||
_ElementCssClassSet(this._element);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
var classname = _element.className;
|
||||
|
||||
for (String name in classname.split(' ')) {
|
||||
String trimmed = name.trim();
|
||||
if (!trimmed.isEmpty) {
|
||||
s.add(trimmed);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
_element.className = s.join(' ');
|
||||
}
|
||||
}
|
||||
|
|
249
tools/dom/src/dart2js_CssClassSet.dart
Normal file
249
tools/dom/src/dart2js_CssClassSet.dart
Normal file
|
@ -0,0 +1,249 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
part of html;
|
||||
|
||||
/**
|
||||
* A set (union) of the CSS classes that are present in a set of elements.
|
||||
* Implemented separately from _ElementCssClassSet for performance.
|
||||
*/
|
||||
class _MultiElementCssClassSet extends CssClassSetImpl {
|
||||
final Iterable<Element> _elementIterable;
|
||||
|
||||
// TODO(sra): Perhaps we should store the DomTokenList instead.
|
||||
final List<CssClassSetImpl> _sets;
|
||||
|
||||
factory _MultiElementCssClassSet(Iterable<Element> elements) {
|
||||
return new _MultiElementCssClassSet._(elements,
|
||||
elements.map((Element e) => e.classes).toList());
|
||||
}
|
||||
|
||||
_MultiElementCssClassSet._(this._elementIterable, this._sets);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
_sets.forEach((CssClassSetImpl e) => s.addAll(e.readClasses()));
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
var classes = s.join(' ');
|
||||
for (Element e in _elementIterable) {
|
||||
e.className = classes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to modify the set of css classes on this element.
|
||||
*
|
||||
* f - callback with:
|
||||
* s - a Set of all the css class name currently on this element.
|
||||
*
|
||||
* After f returns, the modified set is written to the
|
||||
* className property of this element.
|
||||
*/
|
||||
modify( f(Set<String> s)) {
|
||||
_sets.forEach((CssClassSetImpl e) => e.modify(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the class [value] to the element if it is not on it, removes it if it
|
||||
* is.
|
||||
*
|
||||
* TODO(sra): It seems wrong to collect a 'changed' flag like this when the
|
||||
* underlying toggle returns an 'is set' flag.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]) =>
|
||||
_sets.fold(false,
|
||||
(bool changed, CssClassSetImpl e) =>
|
||||
e.toggle(value, shouldAdd) || changed);
|
||||
|
||||
/**
|
||||
* Remove the class [value] from element, and return true on successful
|
||||
* removal.
|
||||
*
|
||||
* This is the Dart equivalent of jQuery's
|
||||
* [removeClass](http://api.jquery.com/removeClass/).
|
||||
*/
|
||||
bool remove(Object value) => _sets.fold(false,
|
||||
(bool changed, CssClassSetImpl e) => e.remove(value) || changed);
|
||||
}
|
||||
|
||||
class _ElementCssClassSet extends CssClassSetImpl {
|
||||
final Element _element;
|
||||
|
||||
_ElementCssClassSet(this._element);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
var classname = _element.className;
|
||||
|
||||
for (String name in classname.split(' ')) {
|
||||
String trimmed = name.trim();
|
||||
if (!trimmed.isEmpty) {
|
||||
s.add(trimmed);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
_element.className = s.join(' ');
|
||||
}
|
||||
|
||||
int get length => _classListLength(_classListOf(_element));
|
||||
bool get isEmpty => length == 0;
|
||||
bool get isNotEmpty => length != 0;
|
||||
|
||||
void clear() {
|
||||
_element.className = '';
|
||||
}
|
||||
|
||||
bool contains(String value) {
|
||||
return _contains(_element, value);
|
||||
}
|
||||
|
||||
bool add(String value) {
|
||||
return _add(_element, value);
|
||||
}
|
||||
|
||||
bool remove(Object value) {
|
||||
return value is String && _remove(_element, value);
|
||||
}
|
||||
|
||||
bool toggle(String value, [bool shouldAdd]) {
|
||||
return _toggle(_element, value, shouldAdd);
|
||||
}
|
||||
|
||||
void addAll(Iterable<String> iterable) {
|
||||
_addAll(_element, iterable);
|
||||
}
|
||||
|
||||
void removeAll(Iterable<String> iterable) {
|
||||
_removeAll(_element, iterable);
|
||||
}
|
||||
|
||||
void retainAll(Iterable<String> iterable) {
|
||||
_removeWhere(_element, iterable.toSet().contains, false);
|
||||
}
|
||||
|
||||
void removeWhere(bool test(String name)) {
|
||||
_removeWhere(_element, test, true);
|
||||
}
|
||||
|
||||
void retainWhere(bool test(String name)) {
|
||||
_removeWhere(_element, test, false);
|
||||
}
|
||||
|
||||
static bool _contains(Element _element, String value) {
|
||||
return _classListContains(_classListOf(_element), value);
|
||||
}
|
||||
|
||||
static bool _add(Element _element, String value) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
// Compute returned result independently of action upon the set. One day we
|
||||
// will be able to optimize it way if unused.
|
||||
bool added = !_classListContains(list, value);
|
||||
_classListAdd(list, value);
|
||||
return added;
|
||||
}
|
||||
|
||||
static bool _remove(Element _element, String value) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
bool removed = _classListContains(list, value);
|
||||
_classListRemove(list, value);
|
||||
return removed;
|
||||
}
|
||||
|
||||
static bool _toggle(Element _element, String value, bool shouldAdd) {
|
||||
// There is no value that can be passed as the second argument of
|
||||
// DomTokenList.toggle that behaves the same as passing one argument.
|
||||
// `null` is seen as false, meaning 'remove'.
|
||||
return shouldAdd == null
|
||||
? _toggleDefault(_element, value)
|
||||
: _toggleOnOff(_element, value, shouldAdd);
|
||||
}
|
||||
|
||||
static bool _toggleDefault(Element _element, String value) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
return _classListToggle1(list, value);
|
||||
}
|
||||
|
||||
static bool _toggleOnOff(Element _element, String value, bool shouldAdd) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
// IE's toggle does not take a second parameter. We would prefer:
|
||||
//
|
||||
// return _classListToggle2(list, value, shouldAdd);
|
||||
//
|
||||
if (shouldAdd) {
|
||||
_classListAdd(list, value);
|
||||
return true;
|
||||
} else {
|
||||
_classListRemove(list, value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void _addAll(Element _element, Iterable<String> iterable) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
for (String value in iterable) {
|
||||
_classListAdd(list, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void _removeAll(Element _element, Iterable<String> iterable) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
for (var value in iterable) {
|
||||
_classListRemove(list, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void _removeWhere(
|
||||
Element _element, bool test(String name), bool doRemove) {
|
||||
DomTokenList list = _classListOf(_element);
|
||||
int i = 0;
|
||||
while (i < _classListLength(list)) {
|
||||
String item = list.item(i);
|
||||
if (doRemove == test(item)) {
|
||||
_classListRemove(list, item);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A collection of static methods for DomTokenList. These methods are a
|
||||
// work-around for the lack of annotations to express the full behaviour of
|
||||
// the DomTokenList methods.
|
||||
|
||||
static DomTokenList _classListOf(Element e) =>
|
||||
JS('returns:DomTokenList;creates:DomTokenList;effects:none;depends:all;',
|
||||
'#.classList', e);
|
||||
|
||||
static int _classListLength(DomTokenList list) =>
|
||||
JS('returns:JSUInt31;effects:none;depends:all;', '#.length', list);
|
||||
|
||||
static bool _classListContains(DomTokenList list, String value) =>
|
||||
JS('returns:bool;effects:none;depends:all;',
|
||||
'#.contains(#)', list, value);
|
||||
|
||||
static void _classListAdd(DomTokenList list, String value) {
|
||||
// list.add(value);
|
||||
JS('', '#.add(#)', list, value);
|
||||
}
|
||||
|
||||
static void _classListRemove(DomTokenList list, String value) {
|
||||
// list.remove(value);
|
||||
JS('', '#.remove(#)', list, value);
|
||||
}
|
||||
|
||||
static bool _classListToggle1(DomTokenList list, String value) {
|
||||
return JS('bool', '#.toggle(#)', list, value);
|
||||
}
|
||||
|
||||
static bool _classListToggle2(
|
||||
DomTokenList list, String value, bool shouldAdd) {
|
||||
return JS('bool', '#.toggle(#, #)', list, value, shouldAdd);
|
||||
}
|
||||
}
|
89
tools/dom/src/dartium_CssClassSet.dart
Normal file
89
tools/dom/src/dartium_CssClassSet.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
part of html;
|
||||
|
||||
/**
|
||||
* A set (union) of the CSS classes that are present in a set of elements.
|
||||
* Implemented separately from _ElementCssClassSet for performance.
|
||||
*/
|
||||
class _MultiElementCssClassSet extends CssClassSetImpl {
|
||||
final Iterable<Element> _elementIterable;
|
||||
Iterable<_ElementCssClassSet> _elementCssClassSetIterable;
|
||||
|
||||
_MultiElementCssClassSet(this._elementIterable) {
|
||||
_elementCssClassSetIterable = new List.from(_elementIterable).map(
|
||||
(e) => new _ElementCssClassSet(e));
|
||||
}
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
_elementCssClassSetIterable.forEach(
|
||||
(_ElementCssClassSet e) => s.addAll(e.readClasses()));
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
var classes = s.join(' ');
|
||||
for (Element e in _elementIterable) {
|
||||
e.className = classes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to modify the set of css classes on this element.
|
||||
*
|
||||
* f - callback with:
|
||||
* s - a Set of all the css class name currently on this element.
|
||||
*
|
||||
* After f returns, the modified set is written to the
|
||||
* className property of this element.
|
||||
*/
|
||||
modify( f(Set<String> s)) {
|
||||
_elementCssClassSetIterable.forEach((_ElementCssClassSet e) => e.modify(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the class [value] to the element if it is not on it, removes it if it
|
||||
* is.
|
||||
*/
|
||||
bool toggle(String value, [bool shouldAdd]) =>
|
||||
_elementCssClassSetIterable.fold(false,
|
||||
(bool changed, _ElementCssClassSet e) =>
|
||||
e.toggle(value, shouldAdd) || changed);
|
||||
|
||||
/**
|
||||
* Remove the class [value] from element, and return true on successful
|
||||
* removal.
|
||||
*
|
||||
* This is the Dart equivalent of jQuery's
|
||||
* [removeClass](http://api.jquery.com/removeClass/).
|
||||
*/
|
||||
bool remove(Object value) => _elementCssClassSetIterable.fold(false,
|
||||
(bool changed, _ElementCssClassSet e) => e.remove(value) || changed);
|
||||
}
|
||||
|
||||
class _ElementCssClassSet extends CssClassSetImpl {
|
||||
|
||||
final Element _element;
|
||||
|
||||
_ElementCssClassSet(this._element);
|
||||
|
||||
Set<String> readClasses() {
|
||||
var s = new LinkedHashSet<String>();
|
||||
var classname = _element.className;
|
||||
|
||||
for (String name in classname.split(' ')) {
|
||||
String trimmed = name.trim();
|
||||
if (!trimmed.isEmpty) {
|
||||
s.add(trimmed);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void writeClasses(Set<String> s) {
|
||||
_element.className = s.join(' ');
|
||||
}
|
||||
}
|
|
@ -52,12 +52,14 @@ import 'dart:web_gl' show RenderingContext;
|
|||
import 'dart:web_sql';
|
||||
import 'dart:_js_helper' show
|
||||
convertDartClosureToJS, Creates, JavaScriptIndexingBehavior,
|
||||
JSName, Native, Null, Returns,
|
||||
JSName, Native, Null, Returns, Inline, ForceInline,
|
||||
findDispatchTagForInterceptorClass, setNativeSubclassDispatchRecord,
|
||||
makeLeafDispatchRecord;
|
||||
import 'dart:_interceptors' show
|
||||
Interceptor, JSExtendableArray, findInterceptorConstructorForType,
|
||||
findConstructorForNativeSubclassType, getNativeInterceptor,
|
||||
Interceptor, JSExtendableArray, JSUInt31,
|
||||
findInterceptorConstructorForType,
|
||||
findConstructorForNativeSubclassType,
|
||||
getNativeInterceptor,
|
||||
setDispatchProperty;
|
||||
import 'dart:_isolate_helper' show IsolateNatives;
|
||||
import 'dart:_foreign_helper' show JS, JS_INTERCEPTOR_CONSTANT, JS_CONST;
|
||||
|
@ -71,6 +73,7 @@ part '$AUXILIARY_DIR/CanvasImageSource.dart';
|
|||
part '$AUXILIARY_DIR/CrossFrameTypes.dart';
|
||||
part '$AUXILIARY_DIR/CssClassSet.dart';
|
||||
part '$AUXILIARY_DIR/CssRectangle.dart';
|
||||
part '$AUXILIARY_DIR/dart2js_CssClassSet.dart';
|
||||
part '$AUXILIARY_DIR/Dimension.dart';
|
||||
part '$AUXILIARY_DIR/EventListener.dart';
|
||||
part '$AUXILIARY_DIR/EventStreamProvider.dart';
|
||||
|
|
|
@ -67,6 +67,7 @@ part '$AUXILIARY_DIR/AttributeMap.dart';
|
|||
part '$AUXILIARY_DIR/CanvasImageSource.dart';
|
||||
part '$AUXILIARY_DIR/CrossFrameTypes.dart';
|
||||
part '$AUXILIARY_DIR/CssClassSet.dart';
|
||||
part '$AUXILIARY_DIR/dartium_CssClassSet.dart';
|
||||
part '$AUXILIARY_DIR/CssRectangle.dart';
|
||||
part '$AUXILIARY_DIR/Dimension.dart';
|
||||
part '$AUXILIARY_DIR/EventListener.dart';
|
||||
|
|
Loading…
Reference in a new issue