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

Side by Side Diff: sdk/lib/js/dartium/js_dartium.dart

Issue 26092003: Maintain referential integrity of proxy instances in dart:js (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 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
« no previous file with comments | « sdk/lib/js/dart2js/js_dart2js.dart ('k') | tests/html/js_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * The js.dart library provides simple JavaScript invocation from Dart that 6 * The js.dart library provides simple JavaScript invocation from Dart that
7 * works on both Dartium and on other modern browsers via Dart2JS. 7 * works on both Dartium and on other modern browsers via Dart2JS.
8 * 8 *
9 * It provides a model based on scoped [JsObject] objects. Proxies give Dart 9 * It provides a model based on scoped [JsObject] objects. Proxies give Dart
10 * code access to JavaScript objects, fields, and functions as well as the 10 * code access to JavaScript objects, fields, and functions as well as the
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 * var foo = new JsObject(context['Foo'], [42]); 59 * var foo = new JsObject(context['Foo'], [42]);
60 * var foo2 = foo.callMethod('add', [foo]); 60 * var foo2 = foo.callMethod('add', [foo]);
61 * print(foo2['x']); 61 * print(foo2['x']);
62 * 62 *
63 * will construct a JavaScript Foo object with the parameter 42, invoke its 63 * will construct a JavaScript Foo object with the parameter 42, invoke its
64 * add method, and return a [JsObject] to a new Foo object whose x field is 84. 64 * add method, and return a [JsObject] to a new Foo object whose x field is 84.
65 */ 65 */
66 66
67 library dart.js; 67 library dart.js;
68 68
69 import 'dart:collection' show HashMap;
69 import 'dart:html'; 70 import 'dart:html';
70 import 'dart:isolate'; 71 import 'dart:isolate';
71 72
72 // Global ports to manage communication from Dart to JS. 73 // Global ports to manage communication from Dart to JS.
74
73 SendPortSync _jsPortSync = window.lookupPort('dart-js-context'); 75 SendPortSync _jsPortSync = window.lookupPort('dart-js-context');
74 SendPortSync _jsPortCreate = window.lookupPort('dart-js-create'); 76 SendPortSync _jsPortCreate = window.lookupPort('dart-js-create');
75 SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof'); 77 SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof');
76 SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property' ); 78 SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property' );
77 SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert'); 79 SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert');
78 80
81 final String _objectIdPrefix = 'dart-obj-ref';
82 final String _functionIdPrefix = 'dart-fun-ref';
83 final _objectTable = new _ObjectTable();
84 final _functionTable = new _ObjectTable.forFunctions();
85
86 // Port to handle and forward requests to the underlying Dart objects.
87 // A remote proxy is uniquely identified by an ID and SendPortSync.
88 ReceivePortSync _port = new ReceivePortSync()
89 ..receive((msg) {
90 try {
91 var id = msg[0];
92 var method = msg[1];
93 if (method == '#call') {
94 var receiver = _getObjectTable(id).get(id);
95 var result;
96 if (receiver is Function) {
97 // remove the first argument, which is 'this', but never
98 // used for a raw function
99 var args = msg[2].sublist(1).map(_deserialize).toList();
100 result = Function.apply(receiver, args);
101 } else if (receiver is Callback) {
102 var args = msg[2].map(_deserialize).toList();
103 result = receiver._call(args);
104 } else {
105 throw new StateError('bad function type: $receiver');
106 }
107 return ['return', _serialize(result)];
108 } else {
109 // TODO(vsm): Support a mechanism to register a handler here.
110 throw 'Invocation unsupported on non-function Dart proxies';
111 }
112 } catch (e) {
113 // TODO(vsm): callSync should just handle exceptions itself.
114 return ['throws', '$e'];
115 }
116 });
117
118 _ObjectTable _getObjectTable(String id) {
119 if (id.startsWith(_functionIdPrefix)) return _functionTable;
120 if (id.startsWith(_objectIdPrefix)) return _objectTable;
121 throw new ArgumentError('internal error: invalid object id: $id');
122 }
79 123
80 JsObject _context; 124 JsObject _context;
81 125
82 /** 126 /**
83 * Returns a proxy to the global JavaScript context for this page. 127 * Returns a proxy to the global JavaScript context for this page.
84 */ 128 */
85 JsObject get context { 129 JsObject get context {
86 if (_context == null) { 130 if (_context == null) {
87 var port = _jsPortSync; 131 var port = _jsPortSync;
88 if (port == null) { 132 if (port == null) {
89 return null; 133 return null;
90 } 134 }
91 _context = _deserialize(_jsPortSync.callSync([])); 135 _context = _deserialize(_jsPortSync.callSync([]));
92 } 136 }
93 return _context; 137 return _context;
94 } 138 }
95 139
96 /** 140 /**
97 * Converts a json-like [data] to a JavaScript map or array and return a 141 * Converts a json-like [data] to a JavaScript map or array and return a
98 * [JsObject] to it. 142 * [JsObject] to it.
99 */ 143 */
100 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data); 144 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data);
101 145
102 /** 146 /**
103 * Converts a local Dart function to a callback that can be passed to 147 * Converts a local Dart function to a callback that can be passed to
104 * JavaScript. 148 * JavaScript.
105 */ 149 */
106 class Callback implements Serializable<JsFunction> { 150 class Callback implements Serializable<JsFunction> {
107 JsFunction _f; 151 final bool _withThis;
152 final Function _function;
153 JsFunction _jsFunction;
108 154
109 Callback._(Function f, bool withThis) { 155 Callback._(this._function, this._withThis) {
110 final id = _proxiedObjectTable.add((List args) { 156 var id = _functionTable.add(this);
111 final arguments = new List.from(args); 157 _jsFunction = new JsFunction._internal(_port.toSendPort(), id);
112 if (!withThis) arguments.removeAt(0);
113 return Function.apply(f, arguments);
114 });
115 _f = new JsFunction._internal(_proxiedObjectTable.sendPort, id);
116 } 158 }
117 159
118 factory Callback(Function f) => new Callback._(f, false); 160 factory Callback(Function f) => new Callback._(f, false);
119 factory Callback.withThis(Function f) => new Callback._(f, true); 161 factory Callback.withThis(Function f) => new Callback._(f, true);
120 162
121 JsFunction toJs() => _f; 163 dynamic _call(List args) {
164 var arguments = (_withThis) ? args : args.sublist(1);
165 return Function.apply(_function, arguments);
166 }
167
168 JsFunction toJs() => _jsFunction;
122 } 169 }
123 170
124 /** 171 /**
125 * Proxies to JavaScript objects. 172 * Proxies to JavaScript objects.
126 */ 173 */
127 class JsObject implements Serializable<JsObject> { 174 class JsObject implements Serializable<JsObject> {
128 final SendPortSync _port; 175 final SendPortSync _port;
129 final String _id; 176 final String _id;
130 177
131 /** 178 /**
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
235 } 282 }
236 } 283 }
237 284
238 /// Marker class used to indicate it is serializable to js. If a class is a 285 /// Marker class used to indicate it is serializable to js. If a class is a
239 /// [Serializable] the "toJs" method will be called and the result will be used 286 /// [Serializable] the "toJs" method will be called and the result will be used
240 /// as value. 287 /// as value.
241 abstract class Serializable<T> { 288 abstract class Serializable<T> {
242 T toJs(); 289 T toJs();
243 } 290 }
244 291
245 // A table to managed local Dart objects that are proxied in JavaScript. 292 class _ObjectTable {
246 class _ProxiedObjectTable { 293 final String name;
247 // Debugging name. 294 final Map<String, Object> objects;
248 final String _name; 295 final Map<Object, String> ids;
296 int nextId = 0;
249 297
250 // Generator for unique IDs. 298 // Creates a table that uses an identity Map to store IDs
251 int _nextId; 299 _ObjectTable()
300 : name = _objectIdPrefix,
301 objects = new HashMap<String, Object>(),
302 ids = new HashMap<Object, String>.identity();
252 303
253 // Table of IDs to Dart objects. 304 // Creates a table that uses an equality-based Map to store IDs, since
254 final Map<String, Object> _registry; 305 // closurized methods may be equal, but not identical
306 _ObjectTable.forFunctions()
307 : name = _functionIdPrefix,
308 objects = new HashMap<String, Object>(),
309 ids = new HashMap<Object, String>();
255 310
256 // Port to handle and forward requests to the underlying Dart objects. 311 // Adds a new object to the table. If [id] is not given, a new unique ID is
257 // A remote proxy is uniquely identified by an ID and SendPortSync. 312 // generated. Returns the ID.
258 final ReceivePortSync _port; 313 String add(Object o, {String id}) {
259
260 _ProxiedObjectTable() :
261 _name = 'dart-ref',
262 _nextId = 0,
263 _registry = {},
264 _port = new ReceivePortSync() {
265 _port.receive((msg) {
266 try {
267 final receiver = _registry[msg[0]];
268 final method = msg[1];
269 final args = msg[2].map(_deserialize).toList();
270 if (method == '#call') {
271 final func = receiver as Function;
272 var result = _serialize(func(args));
273 return ['return', result];
274 } else {
275 // TODO(vsm): Support a mechanism to register a handler here.
276 throw 'Invocation unsupported on non-function Dart proxies';
277 }
278 } catch (e) {
279 // TODO(vsm): callSync should just handle exceptions itself.
280 return ['throws', '$e'];
281 }
282 });
283 }
284
285 // Adds a new object to the table and return a new ID for it.
286 String add(x) {
287 // TODO(vsm): Cache x and reuse id. 314 // TODO(vsm): Cache x and reuse id.
288 final id = '$_name-${_nextId++}'; 315 if (id == null) id = ids[o];
289 _registry[id] = x; 316 if (id == null) id = '$name-${nextId++}';
317 ids[o] = id;
318 objects[id] = o;
290 return id; 319 return id;
291 } 320 }
292 321
293 // Gets an object by ID. 322 // Gets an object by ID.
294 Object get(String id) { 323 Object get(String id) => objects[id];
295 return _registry[id]; 324
296 } 325 bool contains(String id) => objects.containsKey(id);
326
327 String getId(Object o) => ids[o];
297 328
298 // Gets the current number of objects kept alive by this table. 329 // Gets the current number of objects kept alive by this table.
299 get count => _registry.length; 330 get count => objects.length;
300
301 // Gets a send port for this table.
302 get sendPort => _port.toSendPort();
303 } 331 }
304 332
305 // The singleton to manage proxied Dart objects.
306 _ProxiedObjectTable _proxiedObjectTable = new _ProxiedObjectTable();
307
308 /// End of proxy implementation.
309
310 // Dart serialization support. 333 // Dart serialization support.
311 334
312 _serialize(var message) { 335 _serialize(var message) {
313 if (message == null) { 336 if (message == null) {
314 return null; // Convert undefined to null. 337 return null; // Convert undefined to null.
315 } else if (message is String || 338 } else if (message is String ||
316 message is num || 339 message is num ||
317 message is bool) { 340 message is bool) {
318 // Primitives are passed directly through. 341 // Primitives are passed directly through.
319 return message; 342 return message;
320 } else if (message is SendPortSync) { 343 } else if (message is SendPortSync) {
321 // Non-proxied objects are serialized. 344 // Non-proxied objects are serialized.
322 return message; 345 return message;
323 } else if (message is JsFunction) { 346 } else if (message is JsFunction) {
324 // Remote function proxy. 347 // Remote function proxy.
325 return [ 'funcref', message._id, message._port ]; 348 return ['funcref', message._id, message._port];
326 } else if (message is JsObject) { 349 } else if (message is JsObject) {
327 // Remote object proxy. 350 // Remote object proxy.
328 return [ 'objref', message._id, message._port ]; 351 return ['objref', message._id, message._port];
329 } else if (message is Serializable) { 352 } else if (message is Serializable) {
330 // use of result of toJs() 353 // use of result of toJs()
331 return _serialize(message.toJs()); 354 return _serialize(message.toJs());
332 } else if (message is Function) { 355 } else if (message is Function) {
333 return _serialize(new Callback(message)); 356 var id = _functionTable.getId(message);
357 if (id != null) {
358 return ['funcref', id, _port.toSendPort()];
359 }
360 id = _functionTable.add(message);
361 return ['funcref', id, _port.toSendPort()];
334 } else { 362 } else {
335 // Local object proxy. 363 // Local object proxy.
336 return [ 'objref', 364 return ['objref', _objectTable.add(message), _port.toSendPort()];
337 _proxiedObjectTable.add(message),
338 _proxiedObjectTable.sendPort ];
339 } 365 }
340 } 366 }
341 367
342 _deserialize(var message) { 368 _deserialize(var message) {
343 deserializeFunction(message) { 369 deserializeFunction(message) {
344 var id = message[1]; 370 var id = message[1];
345 var port = message[2]; 371 var port = message[2];
346 if (port == _proxiedObjectTable.sendPort) { 372 if (port == _port.toSendPort()) {
347 // Local function. 373 // Local function.
348 return _proxiedObjectTable.get(id); 374 return _functionTable.get(id);
349 } else { 375 } else {
350 // Remote function. Forward to its port. 376 // Remote function.
351 return new JsFunction._internal(port, id); 377 var jsFunction = _functionTable.get(id);
378 if (jsFunction == null) {
379 jsFunction = new JsFunction._internal(port, id);
380 _functionTable.add(jsFunction, id: id);
381 }
382 return jsFunction;
352 } 383 }
353 } 384 }
354 385
355 deserializeObject(message) { 386 deserializeObject(message) {
356 var id = message[1]; 387 var id = message[1];
357 var port = message[2]; 388 var port = message[2];
358 if (port == _proxiedObjectTable.sendPort) { 389 if (port == _port.toSendPort()) {
359 // Local object. 390 // Local object.
360 return _proxiedObjectTable.get(id); 391 return _objectTable.get(id);
361 } else { 392 } else {
362 // Remote object. 393 // Remote object.
363 return new JsObject._internal(port, id); 394 var jsObject = _objectTable.get(id);
395 if (jsObject == null) {
396 jsObject = new JsObject._internal(port, id);
397 _objectTable.add(jsObject, id: id);
398 }
399 return jsObject;
364 } 400 }
365 } 401 }
366 402
367 if (message == null) { 403 if (message == null) {
368 return null; // Convert undefined to null. 404 return null; // Convert undefined to null.
369 } else if (message is String || 405 } else if (message is String ||
370 message is num || 406 message is num ||
371 message is bool) { 407 message is bool) {
372 // Primitives are passed directly through. 408 // Primitives are passed directly through.
373 return message; 409 return message;
374 } else if (message is SendPortSync) { 410 } else if (message is SendPortSync) {
375 // Serialized type. 411 // Serialized type.
376 return message; 412 return message;
377 } 413 }
378 var tag = message[0]; 414 var tag = message[0];
379 switch (tag) { 415 switch (tag) {
380 case 'funcref': return deserializeFunction(message); 416 case 'funcref': return deserializeFunction(message);
381 case 'objref': return deserializeObject(message); 417 case 'objref': return deserializeObject(message);
382 } 418 }
383 throw 'Unsupported serialized data: $message'; 419 throw 'Unsupported serialized data: $message';
384 } 420 }
OLDNEW
« no previous file with comments | « sdk/lib/js/dart2js/js_dart2js.dart ('k') | tests/html/js_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698