Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(540)

Side by Side Diff: client/html/generated/html/dartium/Element.dart

Issue 9537001: Generate dart:html bindings for Dartium as well as Frog. All unittests now pass (or are disabled fo… (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 // TODO(jacobr): use _Lists.dart to remove some of the duplicated
6 // functionality.
7 class _ChildrenElementList implements ElementList {
8 // Raw Element.
9 final _ElementImpl _element;
10 final _HTMLCollectionImpl _childElements;
11
12 _ChildrenElementList._wrap(_ElementImpl element)
13 : _childElements = element._children,
14 _element = element;
15
16 List<Element> _toList() {
17 final output = new List(_childElements.length);
18 for (int i = 0, len = _childElements.length; i < len; i++) {
19 output[i] = _childElements[i];
20 }
21 return output;
22 }
23
24 _ElementImpl get first() {
25 return _element._firstElementChild;
26 }
27
28 void forEach(void f(Element element)) {
29 for (_ElementImpl element in _childElements) {
30 f(element);
31 }
32 }
33
34 ElementList filter(bool f(Element element)) {
35 final output = <Element>[];
36 forEach((Element element) {
37 if (f(element)) {
38 output.add(element);
39 }
40 });
41 return new _FrozenElementList._wrap(output);
42 }
43
44 bool every(bool f(Element element)) {
45 for(Element element in this) {
46 if (!f(element)) {
47 return false;
48 }
49 };
50 return true;
51 }
52
53 bool some(bool f(Element element)) {
54 for(Element element in this) {
55 if (f(element)) {
56 return true;
57 }
58 };
59 return false;
60 }
61
62 bool isEmpty() {
63 return _element._firstElementChild == null;
64 }
65
66 int get length() {
67 return _childElements.length;
68 }
69
70 _ElementImpl operator [](int index) {
71 return _childElements[index];
72 }
73
74 void operator []=(int index, _ElementImpl value) {
75 _element._replaceChild(value, _childElements[index]);
76 }
77
78 void set length(int newLength) {
79 // TODO(jacobr): remove children when length is reduced.
80 throw const UnsupportedOperationException('');
81 }
82
83 Element add(_ElementImpl value) {
84 _element._appendChild(value);
85 return value;
86 }
87
88 Element addLast(_ElementImpl value) => add(value);
89
90 Iterator<Element> iterator() => _toList().iterator();
91
92 void addAll(Collection<_ElementImpl> collection) {
93 for (_ElementImpl element in collection) {
94 _element._appendChild(element);
95 }
96 }
97
98 void sort(int compare(Element a, Element b)) {
99 throw const UnsupportedOperationException('TODO(jacobr): should we impl?');
100 }
101
102 void copyFrom(List<Object> src, int srcStart, int dstStart, int count) {
103 throw 'Not impl yet. todo(jacobr)';
104 }
105
106 void setRange(int start, int length, List from, [int startFrom = 0]) {
107 throw const NotImplementedException();
108 }
109
110 void removeRange(int start, int length) {
111 throw const NotImplementedException();
112 }
113
114 void insertRange(int start, int length, [initialValue = null]) {
115 throw const NotImplementedException();
116 }
117
118 List getRange(int start, int length) =>
119 new _FrozenElementList._wrap(_Lists.getRange(this, start, length,
120 <Element>[]));
121
122 int indexOf(Element element, [int start = 0]) {
123 return _Lists.indexOf(this, element, start, this.length);
124 }
125
126 int lastIndexOf(Element element, [int start = null]) {
127 if (start === null) start = length - 1;
128 return _Lists.lastIndexOf(this, element, start);
129 }
130
131 void clear() {
132 // It is unclear if we want to keep non element nodes?
133 _element.text = '';
134 }
135
136 Element removeLast() {
137 final last = this.last();
138 if (last != null) {
139 _element._removeChild(last);
140 }
141 return last;
142 }
143
144 Element last() {
145 return _element.lastElementChild;
146 }
147 }
148
149 // TODO(jacobr): this is an inefficient implementation but it is hard to see
150 // a better option given that we cannot quite force NodeList to be an
151 // ElementList as there are valid cases where a NodeList JavaScript object
152 // contains Node objects that are not Elements.
153 class _FrozenElementList implements ElementList {
154 final List<Node> _nodeList;
155
156 _FrozenElementList._wrap(this._nodeList);
157
158 Element get first() {
159 return _nodeList.first;
160 }
161
162 void forEach(void f(Element element)) {
163 for (Element el in this) {
164 f(el);
165 }
166 }
167
168 Collection map(f(Element element)) {
169 final out = [];
170 for (Element el in this) {
171 out.add(f(el));
172 }
173 return out;
174 }
175
176 ElementList filter(bool f(Element element)) {
177 final out = new _ElementList([]);
178 for (Element el in this) {
179 if (f(el)) out.add(el);
180 }
181 return out;
182 }
183
184 bool every(bool f(Element element)) {
185 for(Element element in this) {
186 if (!f(element)) {
187 return false;
188 }
189 };
190 return true;
191 }
192
193 bool some(bool f(Element element)) {
194 for(Element element in this) {
195 if (f(element)) {
196 return true;
197 }
198 };
199 return false;
200 }
201
202 bool isEmpty() => _nodeList.isEmpty();
203
204 int get length() => _nodeList.length;
205
206 Element operator [](int index) => _nodeList[index];
207
208 void operator []=(int index, Element value) {
209 throw const UnsupportedOperationException('');
210 }
211
212 void set length(int newLength) {
213 _nodeList.length = newLength;
214 }
215
216 void add(Element value) {
217 throw const UnsupportedOperationException('');
218 }
219
220 void addLast(Element value) {
221 throw const UnsupportedOperationException('');
222 }
223
224 Iterator<Element> iterator() => new _FrozenElementListIterator(this);
225
226 void addAll(Collection<Element> collection) {
227 throw const UnsupportedOperationException('');
228 }
229
230 void sort(int compare(Element a, Element b)) {
231 throw const UnsupportedOperationException('');
232 }
233
234 void setRange(int start, int length, List from, [int startFrom = 0]) {
235 throw const UnsupportedOperationException('');
236 }
237
238 void removeRange(int start, int length) {
239 throw const UnsupportedOperationException('');
240 }
241
242 void insertRange(int start, int length, [initialValue = null]) {
243 throw const UnsupportedOperationException('');
244 }
245
246 ElementList getRange(int start, int length) =>
247 new _FrozenElementList._wrap(_nodeList.getRange(start, length));
248
249 int indexOf(Element element, [int start = 0]) =>
250 _nodeList.indexOf(element, start);
251
252 int lastIndexOf(Element element, [int start = null]) =>
253 _nodeList.lastIndexOf(element, start);
254
255 void clear() {
256 throw const UnsupportedOperationException('');
257 }
258
259 Element removeLast() {
260 throw const UnsupportedOperationException('');
261 }
262
263 Element last() => _nodeList.last();
264 }
265
266 class _FrozenElementListIterator implements Iterator<Element> {
267 final _FrozenElementList _list;
268 int _index = 0;
269
270 _FrozenElementListIterator(this._list);
271
272 /**
273 * Gets the next element in the iteration. Throws a
274 * [NoMoreElementsException] if no element is left.
275 */
276 Element next() {
277 if (!hasNext()) {
278 throw const NoMoreElementsException();
279 }
280
281 return _list[_index++];
282 }
283
284 /**
285 * Returns whether the [Iterator] has elements left.
286 */
287 bool hasNext() => _index < _list.length;
288 }
289
290 class _ElementList extends _ListWrapper<Element> implements ElementList {
291 _ElementList(List<Element> list) : super(list);
292
293 ElementList filter(bool f(Element element)) =>
294 new _ElementList(super.filter(f));
295
296 ElementList getRange(int start, int length) =>
297 new _ElementList(super.getRange(start, length));
298 }
299
300 class ElementAttributeMap implements Map<String, String> {
301
302 final _ElementImpl _element;
303
304 ElementAttributeMap._wrap(this._element);
305
306 bool containsValue(String value) {
307 final attributes = _element._attributes;
308 for (int i = 0, len = attributes.length; i < len; i++) {
309 if(value == attributes[i].value) {
310 return true;
311 }
312 }
313 return false;
314 }
315
316 bool containsKey(String key) {
317 return _element._hasAttribute(key);
318 }
319
320 String operator [](String key) {
321 return _element._getAttribute(key);
322 }
323
324 void operator []=(String key, String value) {
325 _element._setAttribute(key, value);
326 }
327
328 String putIfAbsent(String key, String ifAbsent()) {
329 if (!containsKey(key)) {
330 this[key] = ifAbsent();
331 }
332 }
333
334 String remove(String key) {
335 _element._removeAttribute(key);
336 }
337
338 void clear() {
339 final attributes = _element._attributes;
340 for (int i = attributes.length - 1; i >= 0; i--) {
341 remove(attributes[i].name);
342 }
343 }
344
345 void forEach(void f(String key, String value)) {
346 final attributes = _element._attributes;
347 for (int i = 0, len = attributes.length; i < len; i++) {
348 final item = attributes[i];
349 f(item.name, item.value);
350 }
351 }
352
353 Collection<String> getKeys() {
354 // TODO(jacobr): generate a lazy collection instead.
355 final attributes = _element._attributes;
356 final keys = new List<String>(attributes.length);
357 for (int i = 0, len = attributes.length; i < len; i++) {
358 keys[i] = attributes[i].name;
359 }
360 return keys;
361 }
362
363 Collection<String> getValues() {
364 // TODO(jacobr): generate a lazy collection instead.
365 final attributes = _element._attributes;
366 final values = new List<String>(attributes.length);
367 for (int i = 0, len = attributes.length; i < len; i++) {
368 values[i] = attributes[i].value;
369 }
370 return values;
371 }
372
373 /**
374 * The number of {key, value} pairs in the map.
375 */
376 int get length() {
377 return _element._attributes.length;
378 }
379
380 /**
381 * Returns true if there is no {key, value} pair in the map.
382 */
383 bool isEmpty() {
384 return length == 0;
385 }
386 }
387
388 class _SimpleClientRect implements ClientRect {
389 final num left;
390 final num top;
391 final num width;
392 final num height;
393 num get right() => left + width;
394 num get bottom() => top + height;
395
396 const _SimpleClientRect(this.left, this.top, this.width, this.height);
397
398 bool operator ==(ClientRect other) {
399 return other !== null && left == other.left && top == other.top
400 && width == other.width && height == other.height;
401 }
402
403 String toString() => "($left, $top, $width, $height)";
404 }
405
406 // TODO(jacobr): we cannot currently be lazy about calculating the client
407 // rects as we must perform all measurement queries at a safe point to avoid
408 // triggering unneeded layouts.
409 /**
410 * All your element measurement needs in one place
411 * @domName none
412 */
413 class _ElementRectImpl implements ElementRect {
414 final ClientRect client;
415 final ClientRect offset;
416 final ClientRect scroll;
417
418 // TODO(jacobr): should we move these outside of ElementRect to avoid the
419 // overhead of computing them every time even though they are rarely used.
420 final _ClientRectImpl _boundingClientRect;
421 final _ClientRectListImpl _clientRects;
422
423 _ElementRectImpl(_ElementImpl element) :
424 client = new _SimpleClientRect(element._clientLeft,
425 element._clientTop,
426 element._clientWidth,
427 element._clientHeight),
428 offset = new _SimpleClientRect(element._offsetLeft,
429 element._offsetTop,
430 element._offsetWidth,
431 element._offsetHeight),
432 scroll = new _SimpleClientRect(element._scrollLeft,
433 element._scrollTop,
434 element._scrollWidth,
435 element._scrollHeight),
436 _boundingClientRect = element._getBoundingClientRect(),
437 _clientRects = element._getClientRects();
438
439 _ClientRectImpl get bounding() => _boundingClientRect;
440
441 // TODO(jacobr): cleanup.
442 List<ClientRect> get clientRects() {
443 final out = new List(_clientRects.length);
444 for (num i = 0; i < _clientRects.length; i++) {
445 out[i] = _clientRects.item(i);
446 }
447 return out;
448 }
449 }
450
451 class _ElementImpl extends _NodeImpl implements Element {
452
453 // TODO(jacobr): caching these may hurt performance.
454 ElementAttributeMap _elementAttributeMap;
455 _CssClassSet _cssClassSet;
456 _DataAttributeMap _dataAttributes;
457
458 /**
459 * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute,
460 * Element.removeAttribute
461 */
462 Map<String, String> get attributes() {
463 if (_elementAttributeMap === null) {
464 _elementAttributeMap = new ElementAttributeMap._wrap(this);
465 }
466 return _elementAttributeMap;
467 }
468
469 void set attributes(Map<String, String> value) {
470 Map<String, String> attributes = this.attributes;
471 attributes.clear();
472 for (String key in value.getKeys()) {
473 attributes[key] = value[key];
474 }
475 }
476
477 void set elements(Collection<Element> value) {
478 final elements = this.elements;
479 elements.clear();
480 elements.addAll(value);
481 }
482
483 ElementList get elements() => new _ChildrenElementList._wrap(this);
484
485 ElementList queryAll(String selectors) =>
486 new _FrozenElementList._wrap(_querySelectorAll(selectors));
487
488 Set<String> get classes() {
489 if (_cssClassSet === null) {
490 _cssClassSet = new _CssClassSet(this);
491 }
492 return _cssClassSet;
493 }
494
495 void set classes(Collection<String> value) {
496 _CssClassSet classSet = classes;
497 classSet.clear();
498 classSet.addAll(value);
499 }
500
501 Map<String, String> get dataAttributes() {
502 if (_dataAttributes === null) {
503 _dataAttributes = new _DataAttributeMap(attributes);
504 }
505 return _dataAttributes;
506 }
507
508 void set dataAttributes(Map<String, String> value) {
509 Map<String, String> dataAttributes = this.dataAttributes;
510 dataAttributes.clear();
511 for (String key in value.getKeys()) {
512 dataAttributes[key] = value[key];
513 }
514 }
515
516 Future<ElementRect> get rect() {
517 return _createMeasurementFuture(
518 () => new _ElementRectImpl(this),
519 new Completer<ElementRect>());
520 }
521
522 Future<CSSStyleDeclaration> get computedStyle() {
523 // TODO(jacobr): last param should be null, see b/5045788
524 return getComputedStyle('');
525 }
526
527 Future<CSSStyleDeclaration> getComputedStyle(String pseudoElement) {
528 return _createMeasurementFuture(() =>
529 window._getComputedStyle(this, pseudoElement),
530 new Completer<CSSStyleDeclaration>());
531 }
532 _ElementImpl._wrap(ptr) : super._wrap(ptr);
533
534 int get _childElementCount() => _wrap(_ptr.childElementCount);
535
536 HTMLCollection get _children() => _wrap(_ptr.children);
537
538 DOMTokenList get classList() => _wrap(_ptr.classList);
539
540 String get _className() => _wrap(_ptr.className);
541
542 void set _className(String value) { _ptr.className = _unwrap(value); }
543
544 int get _clientHeight() => _wrap(_ptr.clientHeight);
545
546 int get _clientLeft() => _wrap(_ptr.clientLeft);
547
548 int get _clientTop() => _wrap(_ptr.clientTop);
549
550 int get _clientWidth() => _wrap(_ptr.clientWidth);
551
552 String get contentEditable() => _wrap(_ptr.contentEditable);
553
554 void set contentEditable(String value) { _ptr.contentEditable = _unwrap(value) ; }
555
556 String get dir() => _wrap(_ptr.dir);
557
558 void set dir(String value) { _ptr.dir = _unwrap(value); }
559
560 bool get draggable() => _wrap(_ptr.draggable);
561
562 void set draggable(bool value) { _ptr.draggable = _unwrap(value); }
563
564 Element get _firstElementChild() => _wrap(_ptr.firstElementChild);
565
566 bool get hidden() => _wrap(_ptr.hidden);
567
568 void set hidden(bool value) { _ptr.hidden = _unwrap(value); }
569
570 String get id() => _wrap(_ptr.id);
571
572 void set id(String value) { _ptr.id = _unwrap(value); }
573
574 String get innerHTML() => _wrap(_ptr.innerHTML);
575
576 void set innerHTML(String value) { _ptr.innerHTML = _unwrap(value); }
577
578 bool get isContentEditable() => _wrap(_ptr.isContentEditable);
579
580 String get lang() => _wrap(_ptr.lang);
581
582 void set lang(String value) { _ptr.lang = _unwrap(value); }
583
584 Element get lastElementChild() => _wrap(_ptr.lastElementChild);
585
586 Element get nextElementSibling() => _wrap(_ptr.nextElementSibling);
587
588 int get _offsetHeight() => _wrap(_ptr.offsetHeight);
589
590 int get _offsetLeft() => _wrap(_ptr.offsetLeft);
591
592 Element get offsetParent() => _wrap(_ptr.offsetParent);
593
594 int get _offsetTop() => _wrap(_ptr.offsetTop);
595
596 int get _offsetWidth() => _wrap(_ptr.offsetWidth);
597
598 String get outerHTML() => _wrap(_ptr.outerHTML);
599
600 Element get previousElementSibling() => _wrap(_ptr.previousElementSibling);
601
602 int get _scrollHeight() => _wrap(_ptr.scrollHeight);
603
604 int get _scrollLeft() => _wrap(_ptr.scrollLeft);
605
606 void set _scrollLeft(int value) { _ptr.scrollLeft = _unwrap(value); }
607
608 int get _scrollTop() => _wrap(_ptr.scrollTop);
609
610 void set _scrollTop(int value) { _ptr.scrollTop = _unwrap(value); }
611
612 int get _scrollWidth() => _wrap(_ptr.scrollWidth);
613
614 bool get spellcheck() => _wrap(_ptr.spellcheck);
615
616 void set spellcheck(bool value) { _ptr.spellcheck = _unwrap(value); }
617
618 CSSStyleDeclaration get style() => _wrap(_ptr.style);
619
620 int get tabIndex() => _wrap(_ptr.tabIndex);
621
622 void set tabIndex(int value) { _ptr.tabIndex = _unwrap(value); }
623
624 String get tagName() => _wrap(_ptr.tagName);
625
626 String get webkitRegionOverflow() => _wrap(_ptr.webkitRegionOverflow);
627
628 String get webkitdropzone() => _wrap(_ptr.webkitdropzone);
629
630 void set webkitdropzone(String value) { _ptr.webkitdropzone = _unwrap(value); }
631
632 _ElementEventsImpl get on() {
633 if (_on == null) _on = new _ElementEventsImpl(this);
634 return _on;
635 }
636
637 void blur() {
638 _ptr.blur();
639 return;
640 }
641
642 void click() {
643 _ptr.click();
644 return;
645 }
646
647 void focus() {
648 _ptr.focus();
649 return;
650 }
651
652 String _getAttribute(String name) {
653 return _wrap(_ptr.getAttribute(_unwrap(name)));
654 }
655
656 ClientRect _getBoundingClientRect() {
657 return _wrap(_ptr.getBoundingClientRect());
658 }
659
660 ClientRectList _getClientRects() {
661 return _wrap(_ptr.getClientRects());
662 }
663
664 bool _hasAttribute(String name) {
665 return _wrap(_ptr.hasAttribute(_unwrap(name)));
666 }
667
668 Element insertAdjacentElement(String where, Element element) {
669 return _wrap(_ptr.insertAdjacentElement(_unwrap(where), _unwrap(element)));
670 }
671
672 void insertAdjacentHTML(String where, String html) {
673 _ptr.insertAdjacentHTML(_unwrap(where), _unwrap(html));
674 return;
675 }
676
677 void insertAdjacentText(String where, String text) {
678 _ptr.insertAdjacentText(_unwrap(where), _unwrap(text));
679 return;
680 }
681
682 Element query(String selectors) {
683 return _wrap(_ptr.querySelector(_unwrap(selectors)));
684 }
685
686 NodeList _querySelectorAll(String selectors) {
687 return _wrap(_ptr.querySelectorAll(_unwrap(selectors)));
688 }
689
690 void _removeAttribute(String name) {
691 _ptr.removeAttribute(_unwrap(name));
692 return;
693 }
694
695 void scrollByLines(int lines) {
696 _ptr.scrollByLines(_unwrap(lines));
697 return;
698 }
699
700 void scrollByPages(int pages) {
701 _ptr.scrollByPages(_unwrap(pages));
702 return;
703 }
704
705 void scrollIntoView([bool centerIfNeeded = null]) {
706 if (centerIfNeeded === null) {
707 _ptr.scrollIntoViewIfNeeded();
708 return;
709 } else {
710 _ptr.scrollIntoViewIfNeeded(_unwrap(centerIfNeeded));
711 return;
712 }
713 }
714
715 void _setAttribute(String name, String value) {
716 _ptr.setAttribute(_unwrap(name), _unwrap(value));
717 return;
718 }
719
720 bool matchesSelector(String selectors) {
721 return _wrap(_ptr.webkitMatchesSelector(_unwrap(selectors)));
722 }
723
724 void webkitRequestFullScreen(int flags) {
725 _ptr.webkitRequestFullScreen(_unwrap(flags));
726 return;
727 }
728
729 }
730
731 class _ElementEventsImpl extends _EventsImpl implements ElementEvents {
732 _ElementEventsImpl(_ptr) : super(_ptr);
733
734 EventListenerList get abort() => _get('abort');
735
736 EventListenerList get beforeCopy() => _get('beforecopy');
737
738 EventListenerList get beforeCut() => _get('beforecut');
739
740 EventListenerList get beforePaste() => _get('beforepaste');
741
742 EventListenerList get blur() => _get('blur');
743
744 EventListenerList get change() => _get('change');
745
746 EventListenerList get click() => _get('click');
747
748 EventListenerList get contextMenu() => _get('contextmenu');
749
750 EventListenerList get copy() => _get('copy');
751
752 EventListenerList get cut() => _get('cut');
753
754 EventListenerList get doubleClick() => _get('dblclick');
755
756 EventListenerList get drag() => _get('drag');
757
758 EventListenerList get dragEnd() => _get('dragend');
759
760 EventListenerList get dragEnter() => _get('dragenter');
761
762 EventListenerList get dragLeave() => _get('dragleave');
763
764 EventListenerList get dragOver() => _get('dragover');
765
766 EventListenerList get dragStart() => _get('dragstart');
767
768 EventListenerList get drop() => _get('drop');
769
770 EventListenerList get error() => _get('error');
771
772 EventListenerList get focus() => _get('focus');
773
774 EventListenerList get fullscreenChange() => _get('webkitfullscreenchange');
775
776 EventListenerList get fullscreenError() => _get('webkitfullscreenerror');
777
778 EventListenerList get input() => _get('input');
779
780 EventListenerList get invalid() => _get('invalid');
781
782 EventListenerList get keyDown() => _get('keydown');
783
784 EventListenerList get keyPress() => _get('keypress');
785
786 EventListenerList get keyUp() => _get('keyup');
787
788 EventListenerList get load() => _get('load');
789
790 EventListenerList get mouseDown() => _get('mousedown');
791
792 EventListenerList get mouseMove() => _get('mousemove');
793
794 EventListenerList get mouseOut() => _get('mouseout');
795
796 EventListenerList get mouseOver() => _get('mouseover');
797
798 EventListenerList get mouseUp() => _get('mouseup');
799
800 EventListenerList get mouseWheel() => _get('mousewheel');
801
802 EventListenerList get paste() => _get('paste');
803
804 EventListenerList get reset() => _get('reset');
805
806 EventListenerList get scroll() => _get('scroll');
807
808 EventListenerList get search() => _get('search');
809
810 EventListenerList get select() => _get('select');
811
812 EventListenerList get selectStart() => _get('selectstart');
813
814 EventListenerList get submit() => _get('submit');
815
816 EventListenerList get touchCancel() => _get('touchcancel');
817
818 EventListenerList get touchEnd() => _get('touchend');
819
820 EventListenerList get touchLeave() => _get('touchleave');
821
822 EventListenerList get touchMove() => _get('touchmove');
823
824 EventListenerList get touchStart() => _get('touchstart');
825
826 EventListenerList get transitionEnd() => _get('webkitTransitionEnd');
827 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698