blob: f4c1fe2a181b591ceb09ef51666cfea27ae62c15 [file] [log] [blame]
// 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 == 'CanvasPixelArray') return 'Uint8ClampedArray';
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));
return name;
}
String typeNameInFirefox(obj) {
String name = JS('String', '#', constructorNameFallback(obj));
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 == 'WorkerMessageEvent') return 'MessageEvent';
if (name == 'XMLDocument') return 'Document';
return name;
}
String typeNameInIE(obj) {
String name = JS('String', '#', constructorNameFallback(obj));
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 == '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 == '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')) {
var name = JS('var', r'#.builtin$cls', constructor);
if (name != null) return name;
// The constructor isn't null or undefined at this point. Try
// to grab hold of its name.
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");
// TODO(antonm): remove a reference to DumpRenderTree.
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());
}
/// 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.
}
lookupInterceptor(var hasOwnPropertyFunction, String tag) {
var map = interceptorsByTag;
if (map == null) return null;
return callHasOwnProperty(hasOwnPropertyFunction, map, tag)
? propertyGet(map, tag)
: null;
}
lookupDispatchRecord(obj) {
var hasOwnPropertyFunction = JS('var', 'Object.prototype.hasOwnProperty');
var interceptor = null;
assert(!isDartObject(obj));
String tag = getTypeNameOf(obj);
interceptor = lookupInterceptor(hasOwnPropertyFunction, tag);
if (interceptor == null) {
String secondTag = alternateTag(obj, tag);
if (secondTag != null) {
interceptor = lookupInterceptor(hasOwnPropertyFunction, secondTag);
}
}
if (interceptor == null) {
// This object is not known to Dart. There could be several
// reasons for that, including (but not limited to):
// * A bug in native code (hopefully this is caught during development).
// * An unknown DOM object encountered.
// * JavaScript code running in an unexpected context. For
// example, on node.js.
return null;
}
var isLeaf =
(leafTags != null) && JS('bool', '(#[#]) === true', leafTags, tag);
if (isLeaf) {
var fieldName = JS_IS_INDEXABLE_FIELD_NAME();
bool indexability = JS('bool', r'!!#[#]', interceptor, fieldName);
return makeDispatchRecord(interceptor, false, null, indexability);
} else {
var proto = JS('', 'Object.getPrototypeOf(#)', obj);
return makeDispatchRecord(interceptor, proto, null, null);
}
}