| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of _js_helper; |
| |
| String typeNameInChrome(obj) { |
| String name = JS('String', "#.constructor.name", obj); |
| return typeNameInWebKitCommon(name); |
| } |
| |
| String typeNameInSafari(obj) { |
| String name = JS('String', '#', constructorNameFallback(obj)); |
| // Safari is very similar to Chrome. |
| return typeNameInWebKitCommon(name); |
| } |
| |
| String typeNameInWebKitCommon(tag) { |
| String name = JS('String', '#', tag); |
| if (name == 'Window') return 'DOMWindow'; |
| if (name == 'CanvasPixelArray') return 'Uint8ClampedArray'; |
| if (name == 'WebKitMutationObserver') return 'MutationObserver'; |
| if (name == 'AudioChannelMerger') return 'ChannelMergerNode'; |
| if (name == 'AudioChannelSplitter') return 'ChannelSplitterNode'; |
| if (name == 'AudioGainNode') return 'GainNode'; |
| if (name == 'AudioPannerNode') return 'PannerNode'; |
| if (name == 'JavaScriptAudioNode') return 'ScriptProcessorNode'; |
| if (name == 'Oscillator') return 'OscillatorNode'; |
| if (name == 'RealtimeAnalyserNode') return 'AnalyserNode'; |
| if (name == 'IDBVersionChangeRequest') return 'IDBOpenDBRequest'; |
| return name; |
| } |
| |
| String typeNameInOpera(obj) { |
| String name = JS('String', '#', constructorNameFallback(obj)); |
| if (name == 'Window') return 'DOMWindow'; |
| if (name == 'ApplicationCache') return 'DOMApplicationCache'; |
| return name; |
| } |
| |
| String typeNameInFirefox(obj) { |
| String name = JS('String', '#', constructorNameFallback(obj)); |
| if (name == 'Window') return 'DOMWindow'; |
| if (name == 'BeforeUnloadEvent') return 'Event'; |
| if (name == 'CSS2Properties') return 'CSSStyleDeclaration'; |
| if (name == 'DataTransfer') return 'Clipboard'; |
| if (name == 'DragEvent') return 'MouseEvent'; |
| if (name == 'GeoGeolocation') return 'Geolocation'; |
| if (name == 'MouseScrollEvent') return 'WheelEvent'; |
| if (name == 'OfflineResourceList') return 'DOMApplicationCache'; |
| if (name == 'WorkerMessageEvent') return 'MessageEvent'; |
| if (name == 'XMLDocument') return 'Document'; |
| return name; |
| } |
| |
| String typeNameInIE(obj) { |
| String name = JS('String', '#', constructorNameFallback(obj)); |
| if (name == 'Window') return 'DOMWindow'; |
| if (name == 'Document') { |
| // IE calls both HTML and XML documents 'Document', so we check for the |
| // xmlVersion property, which is the empty string on HTML documents. |
| if (JS('bool', '!!#.xmlVersion', obj)) return 'Document'; |
| return 'HTMLDocument'; |
| } |
| if (name == 'ApplicationCache') return 'DOMApplicationCache'; |
| if (name == 'BeforeUnloadEvent') return 'Event'; |
| if (name == 'CanvasPixelArray') return 'Uint8ClampedArray'; |
| if (name == 'DataTransfer') return 'Clipboard'; |
| if (name == 'DragEvent') return 'MouseEvent'; |
| if (name == 'HTMLDDElement') return 'HTMLElement'; |
| if (name == 'HTMLDTElement') return 'HTMLElement'; |
| if (name == 'HTMLTableDataCellElement') return 'HTMLTableCellElement'; |
| if (name == 'HTMLTableHeaderCellElement') return 'HTMLTableCellElement'; |
| if (name == 'HTMLPhraseElement') return 'HTMLElement'; |
| if (name == 'MSStyleCSSProperties') return 'CSSStyleDeclaration'; |
| if (name == 'MouseWheelEvent') return 'WheelEvent'; |
| if (name == 'Position') return 'Geoposition'; |
| |
| // Patches for types which report themselves as Objects. |
| if (name == 'Object') { |
| if (JS('bool', 'window.DataView && (# instanceof window.DataView)', obj)) { |
| return 'DataView'; |
| } |
| } |
| return name; |
| } |
| |
| String constructorNameFallback(object) { |
| if (object == null) return 'Null'; |
| var constructor = JS('var', "#.constructor", object); |
| if (identical(JS('String', "typeof(#)", constructor), 'function')) { |
| // The constructor isn't null or undefined at this point. Try |
| // to grab hold of its name. |
| var name = JS('var', '#.name', constructor); |
| // If the name is a non-empty string, we use that as the type |
| // name of this object. On Firefox, we often get 'Object' as |
| // the constructor name even for more specialized objects so |
| // we have to fall through to the toString() based implementation |
| // below in that case. |
| if (name is String |
| && !identical(name, '') |
| && !identical(name, 'Object') |
| && !identical(name, 'Function.prototype')) { // Can happen in Opera. |
| return name; |
| } |
| } |
| String string = JS('String', 'Object.prototype.toString.call(#)', object); |
| return JS('String', '#.substring(8, # - 1)', string, string.length); |
| } |
| |
| /** |
| * If a lookup on an object [object] that has [tag] fails, this function is |
| * called to provide an alternate tag. This allows us to fail gracefully if we |
| * can make a good guess, for example, when browsers add novel kinds of |
| * HTMLElement that we have never heard of. |
| */ |
| String alternateTag(object, String tag) { |
| // Does it smell like some kind of HTML element? |
| if (JS('bool', r'!!/^HTML[A-Z].*Element$/.test(#)', tag)) { |
| // Check that it is not a simple JavaScript object. |
| String string = JS('String', 'Object.prototype.toString.call(#)', object); |
| if (string == '[object Object]') return null; |
| return 'HTMLElement'; |
| } |
| return null; |
| } |
| |
| // TODO(ngeoffray): stop using this method once our optimizers can |
| // change str1.contains(str2) into str1.indexOf(str2) != -1. |
| bool contains(String userAgent, String name) { |
| return JS('int', '#.indexOf(#)', userAgent, name) != -1; |
| } |
| |
| int arrayLength(List array) { |
| return JS('int', '#.length', array); |
| } |
| |
| arrayGet(List array, int index) { |
| return JS('var', '#[#]', array, index); |
| } |
| |
| void arraySet(List array, int index, var value) { |
| JS('var', '#[#] = #', array, index, value); |
| } |
| |
| propertyGet(var object, String property) { |
| return JS('var', '#[#]', object, property); |
| } |
| |
| bool callHasOwnProperty(var function, var object, String property) { |
| return JS('bool', '#.call(#, #)', function, object, property); |
| } |
| |
| void propertySet(var object, String property, var value) { |
| JS('var', '#[#] = #', object, property, value); |
| } |
| |
| getPropertyFromPrototype(var object, String name) { |
| return JS('var', 'Object.getPrototypeOf(#)[#]', object, name); |
| } |
| |
| newJsObject() { |
| return JS('var', '{}'); |
| } |
| |
| /** |
| * Returns the function to use to get the type name of an object. |
| */ |
| Function getFunctionForTypeNameOf() { |
| // If we're not in the browser, we're almost certainly running on v8. |
| if (!identical(JS('String', 'typeof(navigator)'), 'object')) return typeNameInChrome; |
| |
| String userAgent = JS('String', "navigator.userAgent"); |
| if (contains(userAgent, 'Chrome') || contains(userAgent, 'DumpRenderTree')) { |
| return typeNameInChrome; |
| } else if (contains(userAgent, 'Firefox')) { |
| return typeNameInFirefox; |
| } else if (contains(userAgent, 'MSIE')) { |
| return typeNameInIE; |
| } else if (contains(userAgent, 'Opera')) { |
| return typeNameInOpera; |
| } else if (contains(userAgent, 'AppleWebKit')) { |
| // Chrome matches 'AppleWebKit' too, but we test for Chrome first, so this |
| // is not a problem. |
| // Note: Just testing for "Safari" doesn't work when the page is embedded |
| // in a UIWebView on iOS 6. |
| return typeNameInSafari; |
| } else { |
| return constructorNameFallback; |
| } |
| } |
| |
| |
| /** |
| * Cached value for the function to use to get the type name of an |
| * object. |
| */ |
| Function _getTypeNameOf; |
| |
| /** |
| * Returns the type name of [obj]. |
| */ |
| String getTypeNameOf(var obj) { |
| if (_getTypeNameOf == null) _getTypeNameOf = getFunctionForTypeNameOf(); |
| return _getTypeNameOf(obj); |
| } |
| |
| String toStringForNativeObject(var obj) { |
| String name = JS('String', '#', getTypeNameOf(obj)); |
| return 'Instance of $name'; |
| } |
| |
| int hashCodeForNativeObject(object) => Primitives.objectHashCode(object); |
| |
| /** |
| * Sets a JavaScript property on an object. |
| */ |
| void defineProperty(var obj, String property, var value) { |
| JS('void', |
| 'Object.defineProperty(#, #, ' |
| '{value: #, enumerable: false, writable: true, configurable: true})', |
| obj, |
| property, |
| value); |
| } |
| |
| |
| // Is [obj] an instance of a Dart-defined class? |
| bool isDartObject(obj) { |
| // Some of the extra parens here are necessary. |
| return JS('bool', '((#) instanceof (#))', obj, JS_DART_OBJECT_CONSTRUCTOR()); |
| } |
| |
| // For each method name and class inheritance subtree, we use an ordinary JS |
| // object as a hash map to store the methods for each class. Entries are added |
| // in native_emitter.dart (see dynamicName). In order to avoid class names |
| // clashing with the method names on Object.prototype (needed for native |
| // objects) we must always use hasOwnProperty. |
| lookupDynamicClass(var hasOwnPropertyFunction, var methods, String className) { |
| return callHasOwnProperty(hasOwnPropertyFunction, methods, className) |
| ? propertyGet(methods, className) |
| : null; |
| } |
| |
| /// A JavaScript object mapping tags to interceptors. |
| var interceptorsByTag; |
| |
| /// A JavaScript object mapping tags to `true` or `false`. |
| var leafTags; |
| |
| /** |
| * Associates tags with an interceptor. Called from generated code. The tags |
| * are all 'leaf' tags representing classes that have no subclasses with |
| * different behaviour. |
| * |
| * [tags] is a string of `|`-separated tags. |
| */ |
| void defineNativeMethods(String tags, interceptorClass) { |
| defineNativeMethodsCommon(tags, interceptorClass, true); |
| } |
| |
| /** |
| * Associates tags with an interceptor. Called from generated code. The tags |
| * are all non-'leaf' tags, representing classes that have a subclass with |
| * different behaviour. |
| */ |
| void defineNativeMethodsNonleaf(String tags, interceptorClass) { |
| defineNativeMethodsCommon(tags, interceptorClass, false); |
| } |
| |
| void defineNativeMethodsCommon(String tags, var interceptorClass, bool isLeaf) { |
| var methods = JS('', '#.prototype', interceptorClass); |
| if (interceptorsByTag == null) interceptorsByTag = JS('=Object', '{}'); |
| if (leafTags == null) leafTags = JS('=Object', '{}'); |
| |
| var tagsList = JS('=List', '#.split("|")', tags); |
| for (int i = 0; i < tagsList.length; i++) { |
| var tag = tagsList[i]; |
| JS('void', '#[#] = #', interceptorsByTag, tag, methods); |
| JS('void', '#[#] = #', leafTags, tag, isLeaf); |
| } |
| } |
| |
| void defineNativeMethodsFinish() { |
| // TODO(sra): Investigate placing a dispatch record on Object.prototype that |
| // returns an interceptor for JavaScript objects. This avoids needing a test |
| // in every interceptor, and prioritizes the performance of known native |
| // classes over unknown. |
| } |
| |
| lookupDispatchRecord(obj) { |
| var hasOwnPropertyFunction = JS('var', 'Object.prototype.hasOwnProperty'); |
| var interceptor = null; |
| assert(!isDartObject(obj)); |
| String tag = getTypeNameOf(obj); |
| |
| interceptor = lookupInterceptor( |
| hasOwnPropertyFunction, tag, interceptorsByTag); |
| |
| if (interceptor == null) { |
| String secondTag = alternateTag(obj, tag); |
| if (secondTag != null) { |
| interceptor = lookupInterceptor( |
| hasOwnPropertyFunction, secondTag, interceptorsByTag); |
| } |
| } |
| if (interceptor == null) { |
| // TODO(sra): Think about the error case. |
| interceptor = JS('', '{__what: "interceptor not found", __tag: #}', tag); |
| } |
| var isLeaf = JS('', '#[#]', leafTags, tag); |
| if (true == isLeaf) { |
| return makeDispatchRecord(interceptor, false, null); |
| } else { |
| var proto = JS('', 'Object.getPrototypeOf(#)', obj); |
| return makeDispatchRecord(interceptor, proto, null); |
| } |
| } |
| |
| lookupInterceptor(var hasOwnPropertyFunction, String tag, var methods) { |
| var method = lookupDynamicClass(hasOwnPropertyFunction, methods, tag); |
| // Look at the inheritance data, getting the class tags and using them |
| // to check the methods table for this method name. |
| if (method == null && _dynamicMetadata != null) { |
| for (int i = 0; i < arrayLength(_dynamicMetadata); i++) { |
| MetaInfo entry = arrayGet(_dynamicMetadata, i); |
| if (callHasOwnProperty(hasOwnPropertyFunction, entry._set, tag)) { |
| method = |
| lookupDynamicClass(hasOwnPropertyFunction, methods, entry._tag); |
| // Stop if we found it in the methods array. |
| if (method != null) break; |
| } |
| } |
| } |
| return method; |
| } |
| |
| /** |
| * This class encodes the class hierarchy when we need it for dynamic |
| * dispatch. |
| */ |
| class MetaInfo { |
| /** |
| * The type name this [MetaInfo] relates to. |
| */ |
| String _tag; |
| |
| /** |
| * A string containing the names of subtypes of [tag], separated by |
| * '|'. |
| */ |
| String _tags; |
| |
| /** |
| * A list of names of subtypes of [tag]. |
| */ |
| Object _set; |
| |
| MetaInfo(this._tag, this._tags, this._set); |
| } |
| |
| List<MetaInfo> get _dynamicMetadata { |
| // Because [dynamicMetadata] has to be shared with multiple isolates |
| // that access native classes (eg multiple DOM isolates), |
| // [_dynamicMetadata] cannot be a field, otherwise all non-main |
| // isolates would not have any value for it. |
| if (identical(JS('var', 'typeof(\$dynamicMetadata)'), 'undefined')) { |
| _dynamicMetadata = <MetaInfo>[]; |
| } |
| return JS('var', '\$dynamicMetadata'); |
| } |
| |
| void set _dynamicMetadata(List<MetaInfo> table) { |
| JS('void', '\$dynamicMetadata = #', table); |
| } |
| |
| /** |
| * Builds the metadata used for encoding the class hierarchy of native |
| * classes. The following example: |
| * |
| * class A native "*A" {} |
| * class B extends A native "*B" {} |
| * |
| * Will generate: |
| * ['A', 'A|B'] |
| * |
| * This method returns a list of [MetaInfo] objects. |
| */ |
| List <MetaInfo> buildDynamicMetadata(List<List<String>> inputTable) { |
| List<MetaInfo> result = <MetaInfo>[]; |
| for (int i = 0; i < arrayLength(inputTable); i++) { |
| String tag = JS('String', '#', arrayGet(arrayGet(inputTable, i), 0)); |
| String tags = JS('String', '#', arrayGet(arrayGet(inputTable, i), 1)); |
| var set = newJsObject(); |
| List<String> tagNames = tags.split('|'); |
| for (int j = 0; j < arrayLength(tagNames); j++) { |
| propertySet(set, arrayGet(tagNames, j), true); |
| } |
| result.add(new MetaInfo(tag, tags, set)); |
| } |
| return result; |
| } |
| |
| /** |
| * Called by the compiler to setup [_dynamicMetadata]. |
| */ |
| void dynamicSetMetadata(List<List<String>> inputTable) { |
| _dynamicMetadata = buildDynamicMetadata(inputTable); |
| } |