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

Side by Side Diff: sdk/lib/js/dart2js/js_dart2js.dart

Issue 41163005: Add JsArray (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: landing jsarray 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
« no previous file with comments | « no previous file | sdk/lib/js/dartium/js_dartium.dart » ('j') | tests/html/js_test.dart » ('J')
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 * Support for interoperating with JavaScript. 6 * Support for interoperating with JavaScript.
7 * 7 *
8 * This library provides access to JavaScript objects from Dart, allowing 8 * This library provides access to JavaScript objects from Dart, allowing
9 * Dart code to get and set properties, and call methods of JavaScript objects 9 * Dart code to get and set properties, and call methods of JavaScript objects
10 * and invoke JavaScript functions. The library takes care of converting 10 * and invoke JavaScript functions. The library takes care of converting
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
77 * 77 *
78 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); 78 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2});
79 * 79 *
80 * This expression creates a JavaScript array: 80 * This expression creates a JavaScript array:
81 * 81 *
82 * var jsArray = new JsObject.jsify([1, 2, 3]); 82 * var jsArray = new JsObject.jsify([1, 2, 3]);
83 */ 83 */
84 library dart.js; 84 library dart.js;
85 85
86 import 'dart:html' show Blob, ImageData, Node; 86 import 'dart:html' show Blob, ImageData, Node;
87 import 'dart:collection' show HashMap; 87 import 'dart:collection' show HashMap, ListMixin;
88 import 'dart:indexed_db' show KeyRange; 88 import 'dart:indexed_db' show KeyRange;
89 import 'dart:typed_data' show TypedData; 89 import 'dart:typed_data' show TypedData;
90 90
91 import 'dart:_foreign_helper' show JS, DART_CLOSURE_TO_JS; 91 import 'dart:_foreign_helper' show JS, DART_CLOSURE_TO_JS;
92 import 'dart:_interceptors' show JavaScriptObject, UnknownJavaScriptObject; 92 import 'dart:_interceptors' show JavaScriptObject, UnknownJavaScriptObject;
93 import 'dart:_js_helper' show Primitives, convertDartClosureToJS; 93 import 'dart:_js_helper' show Primitives, convertDartClosureToJS;
94 94
95 final JsObject context = new JsObject._fromJs(Primitives.computeGlobalThis()); 95 final JsObject context = _wrapToDart(Primitives.computeGlobalThis());
96 96
97 _convertDartFunction(Function f, {bool captureThis: false}) { 97 _convertDartFunction(Function f, {bool captureThis: false}) {
98 return JS('', 98 return JS('',
99 'function(_call, f, captureThis) {' 99 'function(_call, f, captureThis) {'
100 'return function() {' 100 'return function() {'
101 'return _call(f, captureThis, this, ' 101 'return _call(f, captureThis, this, '
102 'Array.prototype.slice.apply(arguments));' 102 'Array.prototype.slice.apply(arguments));'
103 '}' 103 '}'
104 '}(#, #, #)', DART_CLOSURE_TO_JS(_callDartFunction), f, captureThis); 104 '}(#, #, #)', DART_CLOSURE_TO_JS(_callDartFunction), f, captureThis);
105 } 105 }
106 106
107 _callDartFunction(callback, bool captureThis, self, List arguments) { 107 _callDartFunction(callback, bool captureThis, self, List arguments) {
108 if (captureThis) { 108 if (captureThis) {
109 arguments = [self]..addAll(arguments); 109 arguments = [self]..addAll(arguments);
110 } 110 }
111 var dartArgs = arguments.map(_convertToDart).toList(); 111 var dartArgs = new List.from(arguments.map(_convertToDart));
112 return _convertToJS(Function.apply(callback, dartArgs)); 112 return _convertToJS(Function.apply(callback, dartArgs));
113 } 113 }
114 114
115 /** 115 /**
116 * Proxies a JavaScript object to Dart. 116 * Proxies a JavaScript object to Dart.
117 * 117 *
118 * The properties of the JavaScript object are accessible via the `[]` and 118 * The properties of the JavaScript object are accessible via the `[]` and
119 * `[]=` operators. Methods are callable via [callMethod]. 119 * `[]=` operators. Methods are callable via [callMethod].
120 */ 120 */
121 class JsObject { 121 class JsObject {
122 // The wrapped JS object. 122 // The wrapped JS object.
123 final dynamic _jsObject; 123 final dynamic _jsObject;
124 124
125 // This shoud only be called from _wrapToDart
125 JsObject._fromJs(this._jsObject) { 126 JsObject._fromJs(this._jsObject) {
126 assert(_jsObject != null); 127 assert(_jsObject != null);
127 // Remember this proxy for the JS object
128 _getDartProxy(_jsObject, _DART_OBJECT_PROPERTY_NAME, (o) => this);
129 } 128 }
130 129
131 /** 130 /**
132 * Constructs a new JavaScript object from [constructor] and returns a proxy 131 * Constructs a new JavaScript object from [constructor] and returns a proxy
133 * to it. 132 * to it.
134 */ 133 */
135 factory JsObject(JsFunction constructor, [List arguments]) { 134 factory JsObject(JsFunction constructor, [List arguments]) {
136 var constr = _convertToJS(constructor); 135 var constr = _convertToJS(constructor);
137 if (arguments == null) { 136 if (arguments == null) {
138 return new JsObject._fromJs(JS('', 'new #()', constr)); 137 return _wrapToDart(JS('', 'new #()', constr));
139 } 138 }
140 // The following code solves the problem of invoking a JavaScript 139 // The following code solves the problem of invoking a JavaScript
141 // constructor with an unknown number arguments. 140 // constructor with an unknown number arguments.
142 // First bind the constructor to the argument list using bind.apply(). 141 // First bind the constructor to the argument list using bind.apply().
143 // The first argument to bind() is the binding of 'this', so add 'null' to 142 // The first argument to bind() is the binding of 'this', so add 'null' to
144 // the arguments list passed to apply(). 143 // the arguments list passed to apply().
145 // After that, use the JavaScript 'new' operator which overrides any binding 144 // After that, use the JavaScript 'new' operator which overrides any binding
146 // of 'this' with the new instance. 145 // of 'this' with the new instance.
147 var args = [null]..addAll(arguments.map(_convertToJS)); 146 var args = [null]..addAll(arguments.map(_convertToJS));
148 var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args); 147 var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args);
149 // Without this line, calling factoryFunction as a constructor throws 148 // Without this line, calling factoryFunction as a constructor throws
150 JS('String', 'String(#)', factoryFunction); 149 JS('String', 'String(#)', factoryFunction);
151 // This could return an UnknownJavaScriptObject, or a native 150 // This could return an UnknownJavaScriptObject, or a native
152 // object for which there is an interceptor 151 // object for which there is an interceptor
153 var jsObj = JS('JavaScriptObject', 'new #()', factoryFunction); 152 var jsObj = JS('JavaScriptObject', 'new #()', factoryFunction);
154 return new JsObject._fromJs(jsObj); 153
154 return _wrapToDart(jsObj);
155 } 155 }
156 156
157 /** 157 /**
158 * Constructs a [JsObject] that proxies a native Dart object; _for expert use 158 * Constructs a [JsObject] that proxies a native Dart object; _for expert use
159 * only_. 159 * only_.
160 * 160 *
161 * Use this constructor only if you wish to get access to JavaScript 161 * Use this constructor only if you wish to get access to JavaScript
162 * properties attached to a browser host object, such as a Node or Blob, that 162 * properties attached to a browser host object, such as a Node or Blob, that
163 * is normally automatically converted into a native Dart object. 163 * is normally automatically converted into a native Dart object.
164 * 164 *
165 * An exception will be thrown if [object] either is `null` or has the type 165 * An exception will be thrown if [object] either is `null` or has the type
166 * `bool`, `num`, or `String`. 166 * `bool`, `num`, or `String`.
167 */ 167 */
168 factory JsObject.fromBrowserObject(object) { 168 factory JsObject.fromBrowserObject(object) {
169 if (object is num || object is String || object is bool || object == null) { 169 if (object is num || object is String || object is bool || object == null) {
170 throw new ArgumentError( 170 throw new ArgumentError(
171 "object cannot be a num, string, bool, or null"); 171 "object cannot be a num, string, bool, or null");
172 } 172 }
173 return new JsObject._fromJs(_convertToJS(object)); 173 return _wrapToDart(_convertToJS(object));
174 } 174 }
175 175
176 /** 176 /**
177 * Recursively converts a JSON-like collection of Dart objects to a 177 * Recursively converts a JSON-like collection of Dart objects to a
178 * collection of JavaScript objects and returns a [JsObject] proxy to it. 178 * collection of JavaScript objects and returns a [JsObject] proxy to it.
179 * 179 *
180 * [object] must be a [Map] or [Iterable], the contents of which are also 180 * [object] must be a [Map] or [Iterable], the contents of which are also
181 * converted. Maps and Iterables are copied to a new JavaScript object. 181 * converted. Maps and Iterables are copied to a new JavaScript object.
182 * Primitives and other transferrable values are directly converted to their 182 * Primitives and other transferrable values are directly converted to their
183 * JavaScript type, and all other objects are proxied. 183 * JavaScript type, and all other objects are proxied.
184 */ 184 */
185 factory JsObject.jsify(object) { 185 factory JsObject.jsify(object) {
186 if ((object is! Map) && (object is! Iterable)) { 186 if ((object is! Map) && (object is! Iterable)) {
187 throw new ArgumentError("object must be a Map or Iterable"); 187 throw new ArgumentError("object must be a Map or Iterable");
188 } 188 }
189 return new JsObject._fromJs(_convertDataTree(object)); 189 return _wrapToDart(_convertDataTree(object));
190 } 190 }
191 191
192 static _convertDataTree(data) { 192 static _convertDataTree(data) {
193 var _convertedObjects = new HashMap.identity(); 193 var _convertedObjects = new HashMap.identity();
194 194
195 _convert(o) { 195 _convert(o) {
196 if (_convertedObjects.containsKey(o)) { 196 if (_convertedObjects.containsKey(o)) {
197 return _convertedObjects[o]; 197 return _convertedObjects[o];
198 } 198 }
199 if (o is Map) { 199 if (o is Map) {
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
297 * returns the result. 297 * returns the result.
298 * 298 *
299 * The type of [method] must be either [String] or [num]. 299 * The type of [method] must be either [String] or [num].
300 */ 300 */
301 dynamic callMethod(method, [List args]) { 301 dynamic callMethod(method, [List args]) {
302 if (method is! String && method is! num) { 302 if (method is! String && method is! num) {
303 throw new ArgumentError("method is not a String or num"); 303 throw new ArgumentError("method is not a String or num");
304 } 304 }
305 return _convertToDart(JS('', '#[#].apply(#, #)', _jsObject, method, 305 return _convertToDart(JS('', '#[#].apply(#, #)', _jsObject, method,
306 _jsObject, 306 _jsObject,
307 args == null ? null : args.map(_convertToJS).toList())); 307 args == null ? null : new List.from(args.map(_convertToJS))));
308 } 308 }
309 } 309 }
310 310
311 /** 311 /**
312 * Proxies a JavaScript Function object. 312 * Proxies a JavaScript Function object.
313 */ 313 */
314 class JsFunction extends JsObject { 314 class JsFunction extends JsObject {
315 315
316 /** 316 /**
317 * Returns a [JsFunction] that captures its 'this' binding and calls [f] 317 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
318 * with the value of this passed as the first argument. 318 * with the value of this passed as the first argument.
319 */ 319 */
320 factory JsFunction.withThis(Function f) { 320 factory JsFunction.withThis(Function f) {
321 var jsFunc = _convertDartFunction(f, captureThis: true); 321 var jsFunc = _convertDartFunction(f, captureThis: true);
322 return new JsFunction._fromJs(jsFunc); 322 return new JsFunction._fromJs(jsFunc);
323 } 323 }
324 324
325 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); 325 JsFunction._fromJs(jsObject) : super._fromJs(jsObject);
326 326
327 /** 327 /**
328 * Invokes the JavaScript function with arguments [args]. If [thisArg] is 328 * Invokes the JavaScript function with arguments [args]. If [thisArg] is
329 * supplied it is the value of `this` for the invocation. 329 * supplied it is the value of `this` for the invocation.
330 */ 330 */
331 dynamic apply(List args, { thisArg }) => 331 dynamic apply(List args, { thisArg }) =>
332 _convertToDart(JS('', '#.apply(#, #)', _jsObject, 332 _convertToDart(JS('', '#.apply(#, #)', _jsObject,
333 _convertToJS(thisArg), 333 _convertToJS(thisArg),
334 args == null ? null : args.map(_convertToJS).toList())); 334 args == null ? null : new List.from(args.map(_convertToJS))));
335 }
336
337 /**
338 * A [List] that proxies a JavaScript array.
339 */
340 class JsArray<E> extends JsObject with ListMixin<E> {
341
342 /**
343 * Creates a new JavaScript array.
344 */
345 JsArray() : super._fromJs([]);
346
347 /**
348 * Creates a new JavaScript array and initializes it to the contents of
349 * [other].
350 */
351 JsArray.from(Iterable<E> other)
352 : super._fromJs([]..addAll(other.map(_convertToJS)));
353
354 JsArray._fromJs(jsObject) : super._fromJs(jsObject);
355
356 _checkIndex(int index) {
357 if (index is int && (index < 0 || index >= length)) {
358 throw new RangeError.range(index, 0, length);
359 }
360 }
361
362 _checkInsertIndex(int index) {
363 if (index is int && (index < 0 || index >= length + 1)) {
364 throw new RangeError.range(index, 0, length);
365 }
366 }
367
368 _checkRange(int start, int end) {
369 if (start < 0 || start > this.length) {
370 throw new RangeError.range(start, 0, this.length);
371 }
372 if (end < start || end > this.length) {
373 throw new RangeError.range(end, start, this.length);
374 }
375 }
376
377 // Methods required by ListMixin
378
379 E operator [](int index) {
380 _checkIndex(index);
381 return super[index];
382 }
383
384 void operator []=(int index, E value) {
385 _checkIndex(index);
386 super[index] = value;
387 }
388
389 int get length => super['length'];
390
391 void set length(int length) { super['length'] = length; }
392
393
394 // Methods overriden for better performance
395
396 void add(E value) {
397 callMethod('push', [value]);
398 }
399
400 void addAll(Iterable<E> iterable) {
401 var list = (JS('bool', '# instanceof Array', iterable))
402 ? iterable
403 : new List.from(iterable);
404 callMethod('push', list);
405 }
406
407 void insert(int index, E element) {
408 _checkInsertIndex(index);
409 callMethod('splice', [index, 0, element]);
410 }
411
412 E removeAt(int index) {
413 _checkIndex(index);
414 return callMethod('splice', [index, 1])[0];
415 }
416
417 E removeLast() {
418 if (length == 0) throw new RangeError(-1);
419 return callMethod('pop');
420 }
421
422 void removeRange(int start, int end) {
423 _checkRange(start, end);
424 callMethod('splice', [start, end - start]);
425 }
426
427 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
428 _checkRange(start, end);
429 int length = end - start;
430 if (length == 0) return;
431 if (skipCount < 0) throw new ArgumentError(skipCount);
432 var args = [start, length]..addAll(iterable.skip(skipCount).take(length));
433 callMethod('splice', args);
434 }
435
436 void sort([int compare(E a, E b)]) {
437 callMethod('sort', [compare]);
438 }
335 } 439 }
336 440
337 // property added to a Dart object referencing its JS-side DartObject proxy 441 // property added to a Dart object referencing its JS-side DartObject proxy
338 const _DART_OBJECT_PROPERTY_NAME = r'_$dart_dartObject'; 442 const _DART_OBJECT_PROPERTY_NAME = r'_$dart_dartObject';
339 const _DART_CLOSURE_PROPERTY_NAME = r'_$dart_dartClosure'; 443 const _DART_CLOSURE_PROPERTY_NAME = r'_$dart_dartClosure';
340 444
341 // property added to a JS object referencing its Dart-side JsObject proxy 445 // property added to a JS object referencing its Dart-side JsObject proxy
342 const _JS_OBJECT_PROPERTY_NAME = r'_$dart_jsObject'; 446 const _JS_OBJECT_PROPERTY_NAME = r'_$dart_jsObject';
343 const _JS_FUNCTION_PROPERTY_NAME = r'$dart_jsFunction'; 447 const _JS_FUNCTION_PROPERTY_NAME = r'$dart_jsFunction';
344 448
345 bool _defineProperty(o, String name, value) { 449 bool _defineProperty(o, String name, value) {
346 if (JS('bool', 'Object.isExtensible(#)', o)) { 450 if (JS('bool', 'Object.isExtensible(#)', o)) {
347 try { 451 try {
348 JS('void', 'Object.defineProperty(#, #, { value: #})', o, name, value); 452 JS('void', 'Object.defineProperty(#, #, { value: #})', o, name, value);
349 return true; 453 return true;
350 } catch(e) { 454 } catch(e) {
351 // object is native and lies about being extensible 455 // object is native and lies about being extensible
352 // see https://bugzilla.mozilla.org/show_bug.cgi?id=775185 456 // see https://bugzilla.mozilla.org/show_bug.cgi?id=775185
353 } 457 }
354 } 458 }
355 return false; 459 return false;
356 } 460 }
357 461
358 dynamic _convertToJS(dynamic o) { 462 dynamic _convertToJS(dynamic o) {
359 if (o == null) { 463 if (o == null) {
360 return null; 464 return null;
361 } else if (o is String || o is num || o is bool 465 } else if (o is String || o is num || o is bool
362 || o is Blob || o is KeyRange || o is ImageData || o is Node 466 || o is Blob || o is KeyRange || o is ImageData || o is Node
363 || o is TypedData) { 467 || o is TypedData) {
364 return o; 468 return o;
365 } else if (o is DateTime) { 469 } else if (o is DateTime) {
366 return Primitives.lazyAsJsDate(o); 470 return Primitives.lazyAsJsDate(o);
367 } else if (o is JsObject) { 471 } else if (o is JsObject) {
368 return o._jsObject; 472 return o._jsObject;
369 } else if (o is Function) { 473 } else if (o is Function) {
370 return _getJsProxy(o, _JS_FUNCTION_PROPERTY_NAME, (o) { 474 return _getJsProxy(o, _JS_FUNCTION_PROPERTY_NAME, (o) {
371 var jsFunction = _convertDartFunction(o); 475 var jsFunction = _convertDartFunction(o);
372 // set a property on the JS closure referencing the Dart closure 476 // set a property on the JS closure referencing the Dart closure
373 _defineProperty(jsFunction, _DART_CLOSURE_PROPERTY_NAME, o); 477 _defineProperty(jsFunction, _DART_CLOSURE_PROPERTY_NAME, o);
(...skipping 10 matching lines...) Expand all
384 if (jsProxy == null) { 488 if (jsProxy == null) {
385 jsProxy = createProxy(o); 489 jsProxy = createProxy(o);
386 _defineProperty(o, propertyName, jsProxy); 490 _defineProperty(o, propertyName, jsProxy);
387 } 491 }
388 return jsProxy; 492 return jsProxy;
389 } 493 }
390 494
391 // converts a Dart object to a reference to a native JS object 495 // converts a Dart object to a reference to a native JS object
392 // which might be a DartObject JS->Dart proxy 496 // which might be a DartObject JS->Dart proxy
393 Object _convertToDart(o) { 497 Object _convertToDart(o) {
498 var isArray = JS('bool', '# instanceof Array', o);
394 if (JS('bool', '# == null', o) || 499 if (JS('bool', '# == null', o) ||
395 JS('bool', 'typeof # == "string"', o) || 500 JS('bool', 'typeof # == "string"', o) ||
396 JS('bool', 'typeof # == "number"', o) || 501 JS('bool', 'typeof # == "number"', o) ||
397 JS('bool', 'typeof # == "boolean"', o)) { 502 JS('bool', 'typeof # == "boolean"', o)) {
398 return o; 503 return o;
399 } else if (o is Blob || o is KeyRange || o is ImageData || o is Node 504 } else if (o is Blob || o is KeyRange || o is ImageData || o is Node
400 || o is TypedData) { 505 || o is TypedData) {
401 return JS('Blob|KeyRange|ImageData|Node|TypedData', '#', o); 506 return JS('Blob|KeyRange|ImageData|Node|TypedData', '#', o);
402 } else if (JS('bool', '# instanceof Date', o)) { 507 } else if (JS('bool', '# instanceof Date', o)) {
403 var ms = JS('num', '#.getMilliseconds()', o); 508 var ms = JS('num', '#.getMilliseconds()', o);
404 return new DateTime.fromMillisecondsSinceEpoch(ms); 509 return new DateTime.fromMillisecondsSinceEpoch(ms);
405 } else if (JS('bool', 'typeof # == "function"', o)) { 510 } else {
511 return _wrapToDart(o);
512 }
513 }
514
515 JsObject _wrapToDart(o) {
516 if (JS('bool', 'typeof # == "function"', o)) {
406 return _getDartProxy(o, _DART_CLOSURE_PROPERTY_NAME, 517 return _getDartProxy(o, _DART_CLOSURE_PROPERTY_NAME,
407 (o) => new JsFunction._fromJs(o)); 518 (o) => new JsFunction._fromJs(o));
519 } else if (JS('bool', '# instanceof Array', o)) {
520 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME,
521 (o) => new JsArray._fromJs(o));
408 } else if (JS('bool', '#.constructor === DartObject', o)) { 522 } else if (JS('bool', '#.constructor === DartObject', o)) {
409 return JS('', '#.o', o); 523 return JS('', '#.o', o);
410 } else { 524 } else {
411 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME, 525 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME,
412 (o) => new JsObject._fromJs(o)); 526 (o) => new JsObject._fromJs(o));
413 } 527 }
414 } 528 }
415 529
416 Object _getDartProxy(o, String propertyName, createProxy(o)) { 530 Object _getDartProxy(o, String propertyName, createProxy(o)) {
417 var dartProxy = JS('', '#[#]', o, propertyName); 531 var dartProxy = JS('', '#[#]', o, propertyName);
418 if (dartProxy == null) { 532 if (dartProxy == null) {
419 dartProxy = createProxy(o); 533 dartProxy = createProxy(o);
420 _defineProperty(o, propertyName, dartProxy); 534 _defineProperty(o, propertyName, dartProxy);
421 } 535 }
422 return dartProxy; 536 return dartProxy;
423 } 537 }
OLDNEW
« no previous file with comments | « no previous file | sdk/lib/js/dartium/js_dartium.dart » ('j') | tests/html/js_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698