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

Side by Side Diff: dart/third_party/pkg/js/lib/js.dart

Issue 57393002: Version 0.8.10.2 (Closed) Base URL: http://dart.googlecode.com/svn/trunk/
Patch Set: Created 7 years, 1 month 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) 2013, 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 /**
6 * The js.dart library provides simple JavaScript invocation from Dart that
7 * works on both Dartium and on other modern browsers via Dart2JS.
8 *
9 * It provides a model based on scoped [Proxy] objects. Proxies give Dart
10 * code access to JavaScript objects, fields, and functions as well as the
11 * ability to pass Dart objects and functions to JavaScript functions. Scopes
12 * enable developers to use proxies without memory leaks - a common challenge
13 * with cross-runtime interoperation.
14 *
15 * The top-level [context] getter provides a [Proxy] to the global JavaScript
16 * context for the page your Dart code is running on. In the following example:
17 *
18 * import 'package:js/js.dart' as js;
19 *
20 * void main() {
21 * js.context.alert('Hello from Dart via JavaScript');
22 * }
23 *
24 * js.context.alert creates a proxy to the top-level alert function in
25 * JavaScript. It is invoked from Dart as a regular function that forwards to
26 * the underlying JavaScript one. By default, proxies are released when
27 * the currently executing event completes, e.g., when main is completes
28 * in this example.
29 *
30 * The library also enables JavaScript proxies to Dart objects and functions.
31 * For example, the following Dart code:
32 *
33 * js.context.dartCallback = new Callback.once((x) => print(x*2));
34 *
35 * defines a top-level JavaScript function 'dartCallback' that is a proxy to
36 * the corresponding Dart function. The [Callback.once] constructor allows the
37 * proxy to the Dart function to be retained across multiple events;
38 * instead it is released after the first invocation. (This is a common
39 * pattern for asychronous callbacks.)
40 *
41 * Note, parameters and return values are intuitively passed by value for
42 * primitives and by reference for non-primitives. In the latter case, the
43 * references are automatically wrapped and unwrapped as proxies by the library.
44 *
45 * This library also allows construction of JavaScripts objects given a [Proxy]
46 * to a corresponding JavaScript constructor. For example, if the following
47 * JavaScript is loaded on the page:
48 *
49 * function Foo(x) {
50 * this.x = x;
51 * }
52 *
53 * Foo.prototype.add = function(other) {
54 * return new Foo(this.x + other.x);
55 * }
56 *
57 * then, the following Dart:
58 *
59 * var foo = new js.Proxy(js.context.Foo, 42);
60 * var foo2 = foo.add(foo);
61 * print(foo2.x);
62 *
63 * will construct a JavaScript Foo object with the parameter 42, invoke its
64 * add method, and return a [Proxy] to a new Foo object whose x field is 84.
65 *
66 * See [samples](http://dart-lang.github.com/js-interop/example) for more
67 * examples of usage.
68 *
69 * See this [article](http://www.dartlang.org/articles/js-dart-interop) for
70 * more detailed discussion.
71 */
72
73 library js;
74
75 import 'dart:async';
76 import 'dart:html';
77 import 'dart:isolate';
78 import 'dart:mirrors';
79
80 // JavaScript bootstrapping code.
81 // TODO(vsm): Migrate this to use a builtin resource mechanism once we have
82 // one.
83
84 // NOTE: Please re-run tools/create_bootstrap.dart on any modification of
85 // this bootstrap string.
86 final _JS_BOOTSTRAP = r"""
87 (function() {
88 // Proxy support for js.dart.
89
90 var globalContext = window;
91
92 // Support for binding the receiver (this) in proxied functions.
93 function bindIfFunction(f, _this) {
94 if (typeof(f) != "function") {
95 return f;
96 } else {
97 return new BoundFunction(_this, f);
98 }
99 }
100
101 function unbind(obj) {
102 if (obj instanceof BoundFunction) {
103 return obj.object;
104 } else {
105 return obj;
106 }
107 }
108
109 function getBoundThis(obj) {
110 if (obj instanceof BoundFunction) {
111 return obj._this;
112 } else {
113 return globalContext;
114 }
115 }
116
117 function BoundFunction(_this, object) {
118 this._this = _this;
119 this.object = object;
120 }
121
122 // Table for local objects and functions that are proxied.
123 function ProxiedObjectTable() {
124 // Name for debugging.
125 this.name = 'js-ref';
126
127 // Table from IDs to JS objects.
128 this.map = {};
129
130 // Generator for new IDs.
131 this._nextId = 0;
132
133 // Counter for deleted proxies.
134 this._deletedCount = 0;
135
136 // Flag for one-time initialization.
137 this._initialized = false;
138
139 // Ports for managing communication to proxies.
140 this.port = new ReceivePortSync();
141 this.sendPort = this.port.toSendPort();
142
143 // Set of IDs that are global.
144 // These will not be freed on an exitScope().
145 this.globalIds = {};
146
147 // Stack of scoped handles.
148 this.handleStack = [];
149
150 // Stack of active scopes where each value is represented by the size of
151 // the handleStack at the beginning of the scope. When an active scope
152 // is popped, the handleStack is restored to where it was when the
153 // scope was entered.
154 this.scopeIndices = [];
155 }
156
157 // Number of valid IDs. This is the number of objects (global and local)
158 // kept alive by this table.
159 ProxiedObjectTable.prototype.count = function () {
160 return Object.keys(this.map).length;
161 }
162
163 // Number of total IDs ever allocated.
164 ProxiedObjectTable.prototype.total = function () {
165 return this.count() + this._deletedCount;
166 }
167
168 // Adds an object to the table and return an ID for serialization.
169 ProxiedObjectTable.prototype.add = function (obj) {
170 if (this.scopeIndices.length == 0) {
171 throw "Cannot allocate a proxy outside of a scope.";
172 }
173 // TODO(vsm): Cache refs for each obj?
174 var ref = this.name + '-' + this._nextId++;
175 this.handleStack.push(ref);
176 this.map[ref] = obj;
177 return ref;
178 }
179
180 ProxiedObjectTable.prototype._initializeOnce = function () {
181 if (!this._initialized) {
182 this._initialize();
183 this._initialized = true;
184 }
185 }
186
187 // Enters a new scope for this table.
188 ProxiedObjectTable.prototype.enterScope = function() {
189 this._initializeOnce();
190 this.scopeIndices.push(this.handleStack.length);
191 }
192
193 // Invalidates all non-global IDs in the current scope and
194 // exit the current scope.
195 ProxiedObjectTable.prototype.exitScope = function() {
196 var start = this.scopeIndices.pop();
197 for (var i = start; i < this.handleStack.length; ++i) {
198 var key = this.handleStack[i];
199 if (!this.globalIds.hasOwnProperty(key)) {
200 delete this.map[this.handleStack[i]];
201 this._deletedCount++;
202 }
203 }
204 this.handleStack = this.handleStack.splice(0, start);
205 }
206
207 // Makes this ID globally scope. It must be explicitly invalidated.
208 ProxiedObjectTable.prototype.globalize = function(id) {
209 this.globalIds[id] = true;
210 }
211
212 // Invalidates this ID, potentially freeing its corresponding object.
213 ProxiedObjectTable.prototype.invalidate = function(id) {
214 var old = this.get(id);
215 delete this.globalIds[id];
216 delete this.map[id];
217 this._deletedCount++;
218 }
219
220 // Gets the object or function corresponding to this ID.
221 ProxiedObjectTable.prototype.get = function (id) {
222 if (!this.map.hasOwnProperty(id)) {
223 throw 'Proxy ' + id + ' has been invalidated.'
224 }
225 return this.map[id];
226 }
227
228 ProxiedObjectTable.prototype._initialize = function () {
229 // Configure this table's port to forward methods, getters, and setters
230 // from the remote proxy to the local object.
231 var table = this;
232
233 this.port.receive(function (message) {
234 // TODO(vsm): Support a mechanism to register a handler here.
235 try {
236 var object = table.get(message[0]);
237 var receiver = unbind(object);
238 var member = message[1];
239 var kind = message[2];
240 var args = message[3].map(deserialize);
241 if (kind == 'get') {
242 // Getter.
243 var field = member;
244 if (field in receiver && args.length == 0) {
245 var result = bindIfFunction(receiver[field], receiver);
246 return [ 'return', serialize(result) ];
247 }
248 } else if (kind == 'set') {
249 // Setter.
250 var field = member;
251 if (args.length == 1) {
252 return [ 'return', serialize(receiver[field] = args[0]) ];
253 }
254 } else if (kind == 'apply') {
255 // Direct function invocation.
256 var _this = getBoundThis(object);
257 return [ 'return', serialize(receiver.apply(_this, args)) ];
258 } else if (member == '[]' && args.length == 1) {
259 // Index getter.
260 var result = bindIfFunction(receiver[args[0]], receiver);
261 return [ 'return', serialize(result) ];
262 } else if (member == '[]=' && args.length == 2) {
263 // Index setter.
264 return [ 'return', serialize(receiver[args[0]] = args[1]) ];
265 } else {
266 // Member function invocation.
267 var f = receiver[member];
268 if (f) {
269 var result = f.apply(receiver, args);
270 return [ 'return', serialize(result) ];
271 }
272 }
273 return [ 'none' ];
274 } catch (e) {
275 return [ 'throws', e.toString() ];
276 }
277 });
278 }
279
280 // Singleton for local proxied objects.
281 var proxiedObjectTable = new ProxiedObjectTable();
282
283 // DOM element serialization code.
284 var _localNextElementId = 0;
285 var _DART_ID = 'data-dart_id';
286 var _DART_TEMPORARY_ATTACHED = 'data-dart_temporary_attached';
287
288 function serializeElement(e) {
289 // TODO(vsm): Use an isolate-specific id.
290 var id;
291 if (e.hasAttribute(_DART_ID)) {
292 id = e.getAttribute(_DART_ID);
293 } else {
294 id = (_localNextElementId++).toString();
295 e.setAttribute(_DART_ID, id);
296 }
297 if (e !== document.documentElement) {
298 // Element must be attached to DOM to be retrieve in js part.
299 // Attach top unattached parent to avoid detaching parent of "e" when
300 // appending "e" directly to document. We keep count of elements
301 // temporarily attached to prevent detaching top unattached parent to
302 // early. This count is equals to the length of _DART_TEMPORARY_ATTACHED
303 // attribute. There could be other elements to serialize having the same
304 // top unattached parent.
305 var top = e;
306 while (true) {
307 if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) {
308 var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED);
309 var newValue = oldValue + "a";
310 top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue);
311 break;
312 }
313 if (top.parentNode == null) {
314 top.setAttribute(_DART_TEMPORARY_ATTACHED, "a");
315 document.documentElement.appendChild(top);
316 break;
317 }
318 if (top.parentNode === document.documentElement) {
319 // e was already attached to dom
320 break;
321 }
322 top = top.parentNode;
323 }
324 }
325 return id;
326 }
327
328 function deserializeElement(id) {
329 // TODO(vsm): Clear the attribute.
330 var list = document.querySelectorAll('[' + _DART_ID + '="' + id + '"]');
331
332 if (list.length > 1) throw 'Non unique ID: ' + id;
333 if (list.length == 0) {
334 throw 'Element must be attached to the document: ' + id;
335 }
336 var e = list[0];
337 if (e !== document.documentElement) {
338 // detach temporary attached element
339 var top = e;
340 while (true) {
341 if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) {
342 var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED);
343 var newValue = oldValue.substring(1);
344 top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue);
345 // detach top only if no more elements have to be unserialized
346 if (top.getAttribute(_DART_TEMPORARY_ATTACHED).length === 0) {
347 top.removeAttribute(_DART_TEMPORARY_ATTACHED);
348 document.documentElement.removeChild(top);
349 }
350 break;
351 }
352 if (top.parentNode === document.documentElement) {
353 // e was already attached to dom
354 break;
355 }
356 top = top.parentNode;
357 }
358 }
359 return e;
360 }
361
362
363 // Type for remote proxies to Dart objects.
364 function DartProxy(id, sendPort) {
365 this.id = id;
366 this.port = sendPort;
367 }
368
369 // Serializes JS types to SendPortSync format:
370 // - primitives -> primitives
371 // - sendport -> sendport
372 // - DOM element -> [ 'domref', element-id ]
373 // - Function -> [ 'funcref', function-id, sendport ]
374 // - Object -> [ 'objref', object-id, sendport ]
375 function serialize(message) {
376 if (message == null) {
377 return null; // Convert undefined to null.
378 } else if (typeof(message) == 'string' ||
379 typeof(message) == 'number' ||
380 typeof(message) == 'boolean') {
381 // Primitives are passed directly through.
382 return message;
383 } else if (message instanceof SendPortSync) {
384 // Non-proxied objects are serialized.
385 return message;
386 } else if (message instanceof Element &&
387 (message.ownerDocument == null || message.ownerDocument == document)) {
388 return [ 'domref', serializeElement(message) ];
389 } else if (message instanceof BoundFunction &&
390 typeof(message.object) == 'function') {
391 // Local function proxy.
392 return [ 'funcref',
393 proxiedObjectTable.add(message),
394 proxiedObjectTable.sendPort ];
395 } else if (typeof(message) == 'function') {
396 if ('_dart_id' in message) {
397 // Remote function proxy.
398 var remoteId = message._dart_id;
399 var remoteSendPort = message._dart_port;
400 return [ 'funcref', remoteId, remoteSendPort ];
401 } else {
402 // Local function proxy.
403 return [ 'funcref',
404 proxiedObjectTable.add(message),
405 proxiedObjectTable.sendPort ];
406 }
407 } else if (message instanceof DartProxy) {
408 // Remote object proxy.
409 return [ 'objref', message.id, message.port ];
410 } else {
411 // Local object proxy.
412 return [ 'objref',
413 proxiedObjectTable.add(message),
414 proxiedObjectTable.sendPort ];
415 }
416 }
417
418 function deserialize(message) {
419 if (message == null) {
420 return null; // Convert undefined to null.
421 } else if (typeof(message) == 'string' ||
422 typeof(message) == 'number' ||
423 typeof(message) == 'boolean') {
424 // Primitives are passed directly through.
425 return message;
426 } else if (message instanceof SendPortSync) {
427 // Serialized type.
428 return message;
429 }
430 var tag = message[0];
431 switch (tag) {
432 case 'funcref': return deserializeFunction(message);
433 case 'objref': return deserializeObject(message);
434 case 'domref': return deserializeElement(message[1]);
435 }
436 throw 'Unsupported serialized data: ' + message;
437 }
438
439 // Create a local function that forwards to the remote function.
440 function deserializeFunction(message) {
441 var id = message[1];
442 var port = message[2];
443 // TODO(vsm): Add a more robust check for a local SendPortSync.
444 if ("receivePort" in port) {
445 // Local function.
446 return unbind(proxiedObjectTable.get(id));
447 } else {
448 // Remote function. Forward to its port.
449 var f = function () {
450 var depth = enterScope();
451 try {
452 var args = Array.prototype.slice.apply(arguments);
453 args.splice(0, 0, this);
454 args = args.map(serialize);
455 var result = port.callSync([id, '#call', args]);
456 if (result[0] == 'throws') throw deserialize(result[1]);
457 return deserialize(result[1]);
458 } finally {
459 exitScope(depth);
460 }
461 };
462 // Cache the remote id and port.
463 f._dart_id = id;
464 f._dart_port = port;
465 return f;
466 }
467 }
468
469 // Creates a DartProxy to forwards to the remote object.
470 function deserializeObject(message) {
471 var id = message[1];
472 var port = message[2];
473 // TODO(vsm): Add a more robust check for a local SendPortSync.
474 if ("receivePort" in port) {
475 // Local object.
476 return proxiedObjectTable.get(id);
477 } else {
478 // Remote object.
479 return new DartProxy(id, port);
480 }
481 }
482
483 // Remote handler to construct a new JavaScript object given its
484 // serialized constructor and arguments.
485 function construct(args) {
486 args = args.map(deserialize);
487 var constructor = unbind(args[0]);
488 args = Array.prototype.slice.call(args, 1);
489
490 // Until 10 args, the 'new' operator is used. With more arguments we use a
491 // generic way that may not work, particulary when the constructor does not
492 // have an "apply" method.
493 var ret = null;
494 if (args.length === 0) {
495 ret = new constructor();
496 } else if (args.length === 1) {
497 ret = new constructor(args[0]);
498 } else if (args.length === 2) {
499 ret = new constructor(args[0], args[1]);
500 } else if (args.length === 3) {
501 ret = new constructor(args[0], args[1], args[2]);
502 } else if (args.length === 4) {
503 ret = new constructor(args[0], args[1], args[2], args[3]);
504 } else if (args.length === 5) {
505 ret = new constructor(args[0], args[1], args[2], args[3], args[4]);
506 } else if (args.length === 6) {
507 ret = new constructor(args[0], args[1], args[2], args[3], args[4],
508 args[5]);
509 } else if (args.length === 7) {
510 ret = new constructor(args[0], args[1], args[2], args[3], args[4],
511 args[5], args[6]);
512 } else if (args.length === 8) {
513 ret = new constructor(args[0], args[1], args[2], args[3], args[4],
514 args[5], args[6], args[7]);
515 } else if (args.length === 9) {
516 ret = new constructor(args[0], args[1], args[2], args[3], args[4],
517 args[5], args[6], args[7], args[8]);
518 } else if (args.length === 10) {
519 ret = new constructor(args[0], args[1], args[2], args[3], args[4],
520 args[5], args[6], args[7], args[8], args[9]);
521 } else {
522 // Dummy Type with correct constructor.
523 var Type = function(){};
524 Type.prototype = constructor.prototype;
525
526 // Create a new instance
527 var instance = new Type();
528
529 // Call the original constructor.
530 ret = constructor.apply(instance, args);
531 ret = Object(ret) === ret ? ret : instance;
532 }
533 return serialize(ret);
534 }
535
536 // Remote handler to return the top-level JavaScript context.
537 function context(data) {
538 return serialize(globalContext);
539 }
540
541 // Remote handler to track number of live / allocated proxies.
542 function proxyCount() {
543 var live = proxiedObjectTable.count();
544 var total = proxiedObjectTable.total();
545 return [live, total];
546 }
547
548 // Return true if two JavaScript proxies are equal (==).
549 function proxyEquals(args) {
550 return deserialize(args[0]) == deserialize(args[1]);
551 }
552
553 // Return true if a JavaScript proxy is instance of a given type (instanceof).
554 function proxyInstanceof(args) {
555 var obj = unbind(deserialize(args[0]));
556 var type = unbind(deserialize(args[1]));
557 return obj instanceof type;
558 }
559
560 // Return true if a JavaScript proxy has a given property.
561 function proxyHasProperty(args) {
562 var obj = unbind(deserialize(args[0]));
563 var member = unbind(deserialize(args[1]));
564 return member in obj;
565 }
566
567 // Delete a given property of object.
568 function proxyDeleteProperty(args) {
569 var obj = unbind(deserialize(args[0]));
570 var member = unbind(deserialize(args[1]));
571 delete obj[member];
572 }
573
574 function proxyConvert(args) {
575 return serialize(deserializeDataTree(args));
576 }
577
578 function deserializeDataTree(data) {
579 var type = data[0];
580 var value = data[1];
581 if (type === 'map') {
582 var obj = {};
583 for (var i = 0; i < value.length; i++) {
584 obj[value[i][0]] = deserializeDataTree(value[i][1]);
585 }
586 return obj;
587 } else if (type === 'list') {
588 var list = [];
589 for (var i = 0; i < value.length; i++) {
590 list.push(deserializeDataTree(value[i]));
591 }
592 return list;
593 } else /* 'simple' */ {
594 return deserialize(value);
595 }
596 }
597
598 function makeGlobalPort(name, f) {
599 var port = new ReceivePortSync();
600 port.receive(f);
601 window.registerPort(name, port.toSendPort());
602 }
603
604 // Enters a new scope in the JavaScript context.
605 function enterJavaScriptScope() {
606 proxiedObjectTable.enterScope();
607 }
608
609 // Enters a new scope in both the JavaScript and Dart context.
610 var _dartEnterScopePort = null;
611 function enterScope() {
612 enterJavaScriptScope();
613 if (!_dartEnterScopePort) {
614 _dartEnterScopePort = window.lookupPort('js-dart-interop-enter-scope');
615 }
616 return _dartEnterScopePort.callSync([]);
617 }
618
619 // Exits the current scope (and invalidate local IDs) in the JavaScript
620 // context.
621 function exitJavaScriptScope() {
622 proxiedObjectTable.exitScope();
623 }
624
625 // Exits the current scope in both the JavaScript and Dart context.
626 var _dartExitScopePort = null;
627 function exitScope(depth) {
628 exitJavaScriptScope();
629 if (!_dartExitScopePort) {
630 _dartExitScopePort = window.lookupPort('js-dart-interop-exit-scope');
631 }
632 return _dartExitScopePort.callSync([ depth ]);
633 }
634
635 makeGlobalPort('dart-js-interop-context', context);
636 makeGlobalPort('dart-js-interop-create', construct);
637 makeGlobalPort('dart-js-interop-proxy-count', proxyCount);
638 makeGlobalPort('dart-js-interop-equals', proxyEquals);
639 makeGlobalPort('dart-js-interop-instanceof', proxyInstanceof);
640 makeGlobalPort('dart-js-interop-has-property', proxyHasProperty);
641 makeGlobalPort('dart-js-interop-delete-property', proxyDeleteProperty);
642 makeGlobalPort('dart-js-interop-convert', proxyConvert);
643 makeGlobalPort('dart-js-interop-enter-scope', enterJavaScriptScope);
644 makeGlobalPort('dart-js-interop-exit-scope', exitJavaScriptScope);
645 makeGlobalPort('dart-js-interop-globalize', function(data) {
646 if (data[0] == "objref" || data[0] == "funcref") return proxiedObjectTable.g lobalize(data[1]);
647 throw 'Illegal type: ' + data[0];
648 });
649 makeGlobalPort('dart-js-interop-invalidate', function(data) {
650 if (data[0] == "objref" || data[0] == "funcref") return proxiedObjectTable.i nvalidate(data[1]);
651 throw 'Illegal type: ' + data[0];
652 });
653 })();
654 """;
655
656 // Injects JavaScript source code onto the page.
657 // This is only used to load the bootstrapping code above.
658 void _inject(code) {
659 final script = new ScriptElement();
660 script.type = 'text/javascript';
661 script.innerHtml = code;
662 document.body.nodes.add(script);
663 }
664
665 // Global ports to manage communication from Dart to JS.
666 SendPortSync _jsPortSync = null;
667 SendPortSync _jsPortCreate = null;
668 SendPortSync _jsPortProxyCount = null;
669 SendPortSync _jsPortEquals = null;
670 SendPortSync _jsPortInstanceof = null;
671 SendPortSync _jsPortHasProperty = null;
672 SendPortSync _jsPortDeleteProperty = null;
673 SendPortSync _jsPortConvert = null;
674 SendPortSync _jsEnterJavaScriptScope = null;
675 SendPortSync _jsExitJavaScriptScope = null;
676 SendPortSync _jsGlobalize = null;
677 SendPortSync _jsInvalidate = null;
678
679 // Global ports to manage communication from JS to Dart.
680 ReceivePortSync _dartEnterDartScope = null;
681 ReceivePortSync _dartExitDartScope = null;
682
683 // Initializes bootstrap code and ports.
684 void _initialize() {
685 if (_jsPortSync != null) return;
686
687 // Test if the port is already defined.
688 try {
689 _jsPortSync = window.lookupPort('dart-js-interop-context');
690 } catch (e) {
691 // TODO(vsm): Suppress the exception until dartbug.com/5854 is fixed.
692 }
693
694 // If not, try injecting the script.
695 if (_jsPortSync == null) {
696 _inject(_JS_BOOTSTRAP);
697 _jsPortSync = window.lookupPort('dart-js-interop-context');
698 }
699
700 _jsPortCreate = window.lookupPort('dart-js-interop-create');
701 _jsPortProxyCount = window.lookupPort('dart-js-interop-proxy-count');
702 _jsPortEquals = window.lookupPort('dart-js-interop-equals');
703 _jsPortInstanceof = window.lookupPort('dart-js-interop-instanceof');
704 _jsPortHasProperty = window.lookupPort('dart-js-interop-has-property');
705 _jsPortDeleteProperty = window.lookupPort('dart-js-interop-delete-property');
706 _jsPortConvert = window.lookupPort('dart-js-interop-convert');
707 _jsEnterJavaScriptScope = window.lookupPort('dart-js-interop-enter-scope');
708 _jsExitJavaScriptScope = window.lookupPort('dart-js-interop-exit-scope');
709 _jsGlobalize = window.lookupPort('dart-js-interop-globalize');
710 _jsInvalidate = window.lookupPort('dart-js-interop-invalidate');
711
712 _dartEnterDartScope = new ReceivePortSync()
713 ..receive((_) => _enterScope());
714 _dartExitDartScope = new ReceivePortSync()
715 ..receive((args) => _exitScope(args[0]));
716 window.registerPort('js-dart-interop-enter-scope', _dartEnterDartScope.toSendP ort());
717 window.registerPort('js-dart-interop-exit-scope', _dartExitDartScope.toSendPor t());
718 }
719
720 /**
721 * Returns a proxy to the global JavaScript context for this page.
722 */
723 Proxy get context {
724 _enterScopeIfNeeded();
725 return _deserialize(_jsPortSync.callSync([]));
726 }
727
728 // Depth of current scope. Return 0 if no scope.
729 get _depth => _proxiedObjectTable._scopeIndices.length;
730
731 // If we are not already in a scope, enter one and register a
732 // corresponding exit once we return to the event loop.
733 void _enterScopeIfNeeded() {
734 if (_depth == 0) {
735 var depth = _enterScope();
736 scheduleMicrotask(() => _exitScope(depth));
737 }
738 }
739
740 /**
741 * Executes the closure [f] within a scope. Any proxies created within this
742 * scope are invalidated afterward unless they are converted to a global proxy.
743 */
744 scoped(f) {
745 var depth = _enterScope();
746 try {
747 return f();
748 } finally {
749 _exitScope(depth);
750 }
751 }
752
753 int _enterScope() {
754 _initialize();
755 _proxiedObjectTable.enterScope();
756 _jsEnterJavaScriptScope.callSync([]);
757 return _proxiedObjectTable._scopeIndices.length;
758 }
759
760 void _exitScope(int depth) {
761 assert(_proxiedObjectTable._scopeIndices.length == depth);
762 _jsExitJavaScriptScope.callSync([]);
763 _proxiedObjectTable.exitScope();
764 }
765
766 /*
767 * Enters a scope and returns the depth of the scope stack.
768 */
769 /// WARNING: This API is experimental and may be removed.
770 int $experimentalEnterScope() {
771 return _enterScope();
772 }
773
774 /*
775 * Exits a scope. The [depth] must match that returned by the corresponding
776 * enter scope call.
777 */
778 /// WARNING: This API is experimental and may be removed.
779 void $experimentalExitScope(int depth) {
780 _exitScope(depth);
781 }
782
783 /**
784 * Retains the given [object] beyond the current scope.
785 * Instead, it will need to be explicitly released.
786 * The given [object] is returned for convenience.
787 */
788 // TODO(aa) : change dynamic to Serializable<Proxy> if http://dartbug.com/9023
789 // is fixed.
790 // TODO(aa) : change to "<T extends Serializable<Proxy>> T retain(T object)"
791 // once generic methods have landed.
792 dynamic retain(Serializable<Proxy> object) {
793 _jsGlobalize.callSync(_serialize(object.toJs()));
794 return object;
795 }
796
797 /**
798 * Releases a retained [object].
799 */
800 void release(Serializable<Proxy> object) {
801 _jsInvalidate.callSync(_serialize(object.toJs()));
802 }
803
804 /**
805 * Check if [proxy] is instance of [type].
806 */
807 bool instanceof(Serializable<Proxy> proxy, Serializable<FunctionProxy> type) =>
808 _jsPortInstanceof.callSync([proxy.toJs(), type.toJs()].map(_serialize)
809 .toList());
810
811 /**
812 * Check if [proxy] has a [name] property.
813 */
814 bool hasProperty(Serializable<Proxy> proxy, String name) =>
815 _jsPortHasProperty.callSync([proxy.toJs(), name].map(_serialize).toList());
816
817 /**
818 * Delete the [name] property of [proxy].
819 */
820 void deleteProperty(Serializable<Proxy> proxy, String name) {
821 _jsPortDeleteProperty.callSync([proxy.toJs(), name].map(_serialize).toList());
822 }
823
824 /**
825 * Converts a Dart map [data] to a JavaScript map and return a [Proxy] to it.
826 */
827 Proxy map(Map data) => new Proxy._json(data);
828
829 /**
830 * Converts a Dart [Iterable] to a JavaScript array and return a [Proxy] to it.
831 */
832 Proxy array(Iterable data) => new Proxy._json(data);
833
834 /**
835 * Converts a local Dart function to a callback that can be passed to
836 * JavaScript.
837 *
838 * A callback can either be:
839 *
840 * - single-fire, in which case it is automatically invalidated after the first
841 * invocation, or
842 * - multi-fire, in which case it must be explicitly disposed.
843 */
844 class Callback implements Serializable<FunctionProxy> {
845 var _manualDispose;
846 var _id;
847 var _callback;
848
849 _initialize(manualDispose) {
850 _manualDispose = manualDispose;
851 _id = _proxiedObjectTable.add(_callback);
852 _proxiedObjectTable.globalize(_id);
853 }
854
855 _dispose() {
856 var c = _proxiedObjectTable.invalidate(_id);
857 }
858
859 FunctionProxy toJs() =>
860 new FunctionProxy._internal(_proxiedObjectTable.sendPort, _id);
861
862 /**
863 * Disposes this [Callback] so that it may be collected.
864 * Once a [Callback] is disposed, it is an error to invoke it from JavaScript.
865 */
866 dispose() {
867 assert(_manualDispose);
868 _dispose();
869 }
870
871 /**
872 * Creates a single-fire [Callback] that invokes [f]. The callback is
873 * automatically disposed after the first invocation.
874 */
875 Callback.once(Function f, {bool withThis: false}) {
876 _callback = (List args) {
877 try {
878 return Function.apply(f, withThis ? args : args.skip(1).toList());
879 } finally {
880 _dispose();
881 }
882 };
883 _initialize(false);
884 }
885
886 /**
887 * Creates a multi-fire [Callback] that invokes [f]. The callback must be
888 * explicitly disposed to avoid memory leaks.
889 */
890 Callback.many(Function f, {bool withThis: false}) {
891 _callback = (List args) =>
892 Function.apply(f, withThis ? args : args.skip(1).toList());
893 _initialize(true);
894 }
895 }
896
897 // Detect unspecified arguments.
898 class _Undefined {
899 const _Undefined();
900 }
901 const _undefined = const _Undefined();
902 List _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6) {
903 // This assumes no argument
904 final args = [arg1, arg2, arg3, arg4, arg5, arg6];
905 final index = args.indexOf(_undefined);
906 if (index < 0) return args;
907 return args.sublist(0, index);
908 }
909
910 /**
911 * Proxies to JavaScript objects.
912 */
913 class Proxy implements Serializable<Proxy> {
914 SendPortSync _port;
915 final _id;
916
917 /**
918 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a)
919 * JavaScript [constructor]. The arguments should be either
920 * primitive values, DOM elements, or Proxies.
921 */
922 factory Proxy(Serializable<FunctionProxy> constructor,
923 [arg1 = _undefined,
924 arg2 = _undefined,
925 arg3 = _undefined,
926 arg4 = _undefined,
927 arg5 = _undefined,
928 arg6 = _undefined]) {
929 var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6);
930 return new Proxy.withArgList(constructor, arguments);
931 }
932
933 /**
934 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a)
935 * JavaScript [constructor]. The [arguments] list should contain either
936 * primitive values, DOM elements, or Proxies.
937 */
938 factory Proxy.withArgList(Serializable<FunctionProxy> constructor,
939 List arguments) {
940 _enterScopeIfNeeded();
941 final serialized = ([constructor]..addAll(arguments)).map(_serialize).
942 toList();
943 final result = _jsPortCreate.callSync(serialized);
944 return _deserialize(result);
945 }
946
947 /**
948 * Constructs a [Proxy] to a new JavaScript map or list created defined via
949 * Dart map or list.
950 */
951 factory Proxy._json(data) {
952 _enterScopeIfNeeded();
953 return _convert(data);
954 }
955
956 static _convert(data) {
957 return _deserialize(_jsPortConvert.callSync(_serializeDataTree(data)));
958 }
959
960 static _serializeDataTree(data) {
961 if (data is Map) {
962 final entries = new List();
963 for (var key in data.keys) {
964 entries.add([key, _serializeDataTree(data[key])]);
965 }
966 return ['map', entries];
967 } else if (data is Iterable) {
968 return ['list', data.map(_serializeDataTree).toList()];
969 } else {
970 return ['simple', _serialize(data)];
971 }
972 }
973
974 Proxy._internal(this._port, this._id);
975
976 Proxy toJs() => this;
977
978 // Resolve whether this is needed.
979 operator[](arg) => _forward(this, '[]', 'method', [ arg ]);
980
981 // Resolve whether this is needed.
982 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
983
984 // Test if this is equivalent to another Proxy. This essentially
985 // maps to JavaScript's == operator.
986 // TODO(vsm): Can we avoid forwarding to JS?
987 operator==(other) => identical(this, other)
988 ? true
989 : (other is Proxy &&
990 _jsPortEquals.callSync([_serialize(this), _serialize(other)]));
991
992 String toString() {
993 try {
994 return _forward(this, 'toString', 'method', []);
995 } catch(e) {
996 return super.toString();
997 }
998 }
999
1000 // Forward member accesses to the backing JavaScript object.
1001 noSuchMethod(Invocation invocation) {
1002 String member = MirrorSystem.getName(invocation.memberName);
1003 // If trying to access a JavaScript field/variable that starts with
1004 // _ (underscore), Dart treats it a library private and member name
1005 // it suffixed with '@internalLibraryIdentifier' which we have to
1006 // strip before sending over to the JS side.
1007 if (member.indexOf('@') != -1) {
1008 member = member.substring(0, member.indexOf('@'));
1009 }
1010 String kind;
1011 List args = invocation.positionalArguments;
1012 if (args == null) args = [];
1013 if (invocation.isGetter) {
1014 kind = 'get';
1015 } else if (invocation.isSetter) {
1016 kind = 'set';
1017 if (member.endsWith('=')) {
1018 member = member.substring(0, member.length - 1);
1019 }
1020 } else if (member == 'call') {
1021 // A 'call' (probably) means that this proxy was invoked directly
1022 // as if it was a function. Map this to JS function application.
1023 kind = 'apply';
1024 } else {
1025 kind = 'method';
1026 }
1027 return _forward(this, member, kind, args);
1028 }
1029
1030 // Forward member accesses to the backing JavaScript object.
1031 static _forward(Proxy receiver, String member, String kind, List args) {
1032 _enterScopeIfNeeded();
1033 var result = receiver._port.callSync([receiver._id, member, kind,
1034 args.map(_serialize).toList()]);
1035 switch (result[0]) {
1036 case 'return': return _deserialize(result[1]);
1037 case 'throws': throw _deserialize(result[1]);
1038 case 'none': throw new NoSuchMethodError(receiver, member, args, {});
1039 default: throw 'Invalid return value';
1040 }
1041 }
1042 }
1043
1044 // TODO(aa) make FunctionProxy implements Function once it is allowed
1045 /// A [Proxy] subtype to JavaScript functions.
1046 class FunctionProxy extends Proxy
1047 implements Serializable<FunctionProxy> /*,Function*/ {
1048 FunctionProxy._internal(SendPortSync port, id) : super._internal(port, id);
1049
1050 // TODO(vsm): This allows calls with a limited number of arguments
1051 // in the context of dartbug.com/9283. Eliminate pending the resolution
1052 // of this bug. Note, if this Proxy is called with more arguments then
1053 // allowed below, it will trigger the 'call' path in Proxy.noSuchMethod
1054 // - and still work correctly in unminified mode.
1055 call([arg1 = _undefined, arg2 = _undefined,
1056 arg3 = _undefined, arg4 = _undefined,
1057 arg5 = _undefined, arg6 = _undefined]) {
1058 var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6);
1059 return Proxy._forward(this, '', 'apply', arguments);
1060 }
1061 }
1062
1063 /// Marker class used to indicate it is serializable to js. If a class is a
1064 /// [Serializable] the "toJs" method will be called and the result will be used
1065 /// as value.
1066 abstract class Serializable<T> {
1067 T toJs();
1068 }
1069
1070 // A table to managed local Dart objects that are proxied in JavaScript.
1071 class _ProxiedObjectTable {
1072 // Debugging name.
1073 final String _name;
1074
1075 // Generator for unique IDs.
1076 int _nextId;
1077
1078 // Counter for invalidated IDs for debugging.
1079 int _deletedCount;
1080
1081 // Table of IDs to Dart objects.
1082 final Map<String, Object> _registry;
1083
1084 // Port to handle and forward requests to the underlying Dart objects.
1085 // A remote proxy is uniquely identified by an ID and SendPortSync.
1086 final ReceivePortSync _port;
1087
1088 // The set of IDs that are global. These must be explicitly invalidated.
1089 final Set<String> _globalIds;
1090
1091 // The stack of valid IDs.
1092 final List<String> _handleStack;
1093
1094 // The stack of scopes, where each scope is represented by an index into the
1095 // handleStack.
1096 final List<int> _scopeIndices;
1097
1098 // Enters a new scope.
1099 enterScope() {
1100 _scopeIndices.add(_handleStack.length);
1101 }
1102
1103 // Invalidates non-global IDs created in the current scope and
1104 // restore to the previous scope.
1105 exitScope() {
1106 int start = _scopeIndices.removeLast();
1107 for (int i = start; i < _handleStack.length; ++i) {
1108 String key = _handleStack[i];
1109 if (!_globalIds.contains(key)) {
1110 _registry.remove(_handleStack[i]);
1111 _deletedCount++;
1112 }
1113 }
1114 if (start != _handleStack.length) {
1115 _handleStack.removeRange(start, _handleStack.length - start);
1116 }
1117 }
1118
1119 // Converts an ID to a global.
1120 globalize(id) => _globalIds.add(id);
1121
1122 // Invalidates an ID.
1123 invalidate(id) {
1124 var old = _registry[id];
1125 _globalIds.remove(id);
1126 _registry.remove(id);
1127 _deletedCount++;
1128 return old;
1129 }
1130
1131 // Replaces the object referenced by an ID.
1132 _replace(id, x) {
1133 _registry[id] = x;
1134 }
1135
1136 _ProxiedObjectTable() :
1137 _name = 'dart-ref',
1138 _nextId = 0,
1139 _deletedCount = 0,
1140 _registry = {},
1141 _port = new ReceivePortSync(),
1142 _handleStack = new List<String>(),
1143 _scopeIndices = new List<int>(),
1144 _globalIds = new Set<String>() {
1145 _port.receive((msg) {
1146 try {
1147 final receiver = _registry[msg[0]];
1148 final method = msg[1];
1149 final args = msg[2].map(_deserialize).toList();
1150 if (method == '#call') {
1151 final func = receiver as Function;
1152 var result = _serialize(func(args));
1153 return ['return', result];
1154 } else {
1155 // TODO(vsm): Support a mechanism to register a handler here.
1156 throw 'Invocation unsupported on non-function Dart proxies';
1157 }
1158 } catch (e) {
1159 // TODO(vsm): callSync should just handle exceptions itself.
1160 return ['throws', '$e'];
1161 }
1162 });
1163 }
1164
1165 // Adds a new object to the table and return a new ID for it.
1166 String add(x) {
1167 _enterScopeIfNeeded();
1168 // TODO(vsm): Cache x and reuse id.
1169 final id = '$_name-${_nextId++}';
1170 _registry[id] = x;
1171 _handleStack.add(id);
1172 return id;
1173 }
1174
1175 // Gets an object by ID.
1176 Object get(String id) {
1177 return _registry[id];
1178 }
1179
1180 // Gets the current number of objects kept alive by this table.
1181 get count => _registry.length;
1182
1183 // Gets the total number of IDs ever allocated.
1184 get total => count + _deletedCount;
1185
1186 // Gets a send port for this table.
1187 get sendPort => _port.toSendPort();
1188 }
1189
1190 // The singleton to manage proxied Dart objects.
1191 _ProxiedObjectTable _proxiedObjectTable = new _ProxiedObjectTable();
1192
1193 /// End of proxy implementation.
1194
1195 // Dart serialization support.
1196
1197 _serialize(var message) {
1198 if (message == null) {
1199 return null; // Convert undefined to null.
1200 } else if (message is String ||
1201 message is num ||
1202 message is bool) {
1203 // Primitives are passed directly through.
1204 return message;
1205 } else if (message is SendPortSync) {
1206 // Non-proxied objects are serialized.
1207 return message;
1208 } else if (message is Element &&
1209 (message.document == null || message.document == document)) {
1210 return [ 'domref', _serializeElement(message) ];
1211 } else if (message is FunctionProxy) {
1212 // Remote function proxy.
1213 return [ 'funcref', message._id, message._port ];
1214 } else if (message is Proxy) {
1215 // Remote object proxy.
1216 return [ 'objref', message._id, message._port ];
1217 } else if (message is Serializable) {
1218 // use of result of toJs()
1219 return _serialize(message.toJs());
1220 } else {
1221 // Local object proxy.
1222 return [ 'objref',
1223 _proxiedObjectTable.add(message),
1224 _proxiedObjectTable.sendPort ];
1225 }
1226 }
1227
1228 _deserialize(var message) {
1229 deserializeFunction(message) {
1230 var id = message[1];
1231 var port = message[2];
1232 if (port == _proxiedObjectTable.sendPort) {
1233 // Local function.
1234 return _proxiedObjectTable.get(id);
1235 } else {
1236 // Remote function. Forward to its port.
1237 return new FunctionProxy._internal(port, id);
1238 }
1239 }
1240
1241 deserializeObject(message) {
1242 var id = message[1];
1243 var port = message[2];
1244 if (port == _proxiedObjectTable.sendPort) {
1245 // Local object.
1246 return _proxiedObjectTable.get(id);
1247 } else {
1248 // Remote object.
1249 return new Proxy._internal(port, id);
1250 }
1251 }
1252
1253
1254 if (message == null) {
1255 return null; // Convert undefined to null.
1256 } else if (message is String ||
1257 message is num ||
1258 message is bool) {
1259 // Primitives are passed directly through.
1260 return message;
1261 } else if (message is SendPortSync) {
1262 // Serialized type.
1263 return message;
1264 }
1265 var tag = message[0];
1266 switch (tag) {
1267 case 'funcref': return deserializeFunction(message);
1268 case 'objref': return deserializeObject(message);
1269 case 'domref': return _deserializeElement(message[1]);
1270 }
1271 throw 'Unsupported serialized data: $message';
1272 }
1273
1274 // DOM element serialization.
1275
1276 int _localNextElementId = 0;
1277
1278 const _DART_ID = 'data-dart_id';
1279 const _DART_TEMPORARY_ATTACHED = 'data-dart_temporary_attached';
1280
1281 _serializeElement(Element e) {
1282 // TODO(vsm): Use an isolate-specific id.
1283 var id;
1284 if (e.attributes.containsKey(_DART_ID)) {
1285 id = e.attributes[_DART_ID];
1286 } else {
1287 id = 'dart-${_localNextElementId++}';
1288 e.attributes[_DART_ID] = id;
1289 }
1290 if (!identical(e, document.documentElement)) {
1291 // Element must be attached to DOM to be retrieve in js part.
1292 // Attach top unattached parent to avoid detaching parent of "e" when
1293 // appending "e" directly to document. We keep count of elements
1294 // temporarily attached to prevent detaching top unattached parent to
1295 // early. This count is equals to the length of _DART_TEMPORARY_ATTACHED
1296 // attribute. There could be other elements to serialize having the same
1297 // top unattached parent.
1298 var top = e;
1299 while (true) {
1300 if (top.attributes.containsKey(_DART_TEMPORARY_ATTACHED)) {
1301 final oldValue = top.attributes[_DART_TEMPORARY_ATTACHED];
1302 final newValue = oldValue + 'a';
1303 top.attributes[_DART_TEMPORARY_ATTACHED] = newValue;
1304 break;
1305 }
1306 if (top.parent == null) {
1307 top.attributes[_DART_TEMPORARY_ATTACHED] = 'a';
1308 document.documentElement.children.add(top);
1309 break;
1310 }
1311 if (identical(top.parent, document.documentElement)) {
1312 // e was already attached to dom
1313 break;
1314 }
1315 top = top.parent;
1316 }
1317 }
1318 return id;
1319 }
1320
1321 Element _deserializeElement(var id) {
1322 var list = queryAll('[$_DART_ID="$id"]');
1323 if (list.length > 1) throw 'Non unique ID: $id';
1324 if (list.length == 0) {
1325 throw 'Only elements attached to document can be serialized: $id';
1326 }
1327 final e = list[0];
1328 if (!identical(e, document.documentElement)) {
1329 // detach temporary attached element
1330 var top = e;
1331 while (true) {
1332 if (top.attributes.containsKey(_DART_TEMPORARY_ATTACHED)) {
1333 final oldValue = top.attributes[_DART_TEMPORARY_ATTACHED];
1334 final newValue = oldValue.substring(1);
1335 top.attributes[_DART_TEMPORARY_ATTACHED] = newValue;
1336 // detach top only if no more elements have to be unserialized
1337 if (top.attributes[_DART_TEMPORARY_ATTACHED].length == 0) {
1338 top.attributes.remove(_DART_TEMPORARY_ATTACHED);
1339 top.remove();
1340 }
1341 break;
1342 }
1343 if (identical(top.parent, document.documentElement)) {
1344 // e was already attached to dom
1345 break;
1346 }
1347 top = top.parent;
1348 }
1349 }
1350 return e;
1351 }
1352
1353 // Fetch the number of proxies to JavaScript objects.
1354 // This returns a 2 element list. The first is the number of currently
1355 // live proxies. The second is the total number of proxies ever
1356 // allocated.
1357 List _proxyCountJavaScript() => _jsPortProxyCount.callSync([]);
1358
1359 /**
1360 * Returns the number of allocated proxy objects matching the given
1361 * conditions. By default, the total number of live proxy objects are
1362 * return. In a well behaved program, this should stay below a small
1363 * bound.
1364 *
1365 * Set [all] to true to return the total number of proxies ever allocated.
1366 * Set [dartOnly] to only count proxies to Dart objects (live or all).
1367 * Set [jsOnly] to only count proxies to JavaScript objects (live or all).
1368 */
1369 int proxyCount({all: false, dartOnly: false, jsOnly: false}) {
1370 final js = !dartOnly;
1371 final dart = !jsOnly;
1372 final jsCounts = js ? _proxyCountJavaScript() : null;
1373 var sum = 0;
1374 if (!all) {
1375 if (js)
1376 sum += jsCounts[0];
1377 if (dart)
1378 sum += _proxiedObjectTable.count;
1379 } else {
1380 if (js)
1381 sum += jsCounts[1];
1382 if (dart)
1383 sum += _proxiedObjectTable.total;
1384 }
1385 return sum;
1386 }
1387
1388 // Prints the number of live handles in Dart and JavaScript. This is for
1389 // debugging / profiling purposes.
1390 void _proxyDebug([String message = '']) {
1391 print('Proxy status $message:');
1392 var dartLive = proxyCount(dartOnly: true);
1393 var dartTotal = proxyCount(dartOnly: true, all: true);
1394 var jsLive = proxyCount(jsOnly: true);
1395 var jsTotal = proxyCount(jsOnly: true, all: true);
1396 print(' Dart objects Live : $dartLive (out of $dartTotal ever allocated).');
1397 print(' JS objects Live : $jsLive (out of $jsTotal ever allocated).');
1398 }
OLDNEW
« no previous file with comments | « dart/third_party/pkg/js/lib/dart_interop.js ('k') | dart/third_party/pkg/js/lib/js_wrapping.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698