blob: 38956acce395564bff0f575e0138a21a1a5544e5 [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 html;
class _ConsoleVariables {
Map<String, Object> _data = new Map<String, Object>();
/**
* Forward member accesses to the backing JavaScript object.
*/
noSuchMethod(Invocation invocation) {
String member = MirrorSystem.getName(invocation.memberName);
if (invocation.isGetter) {
return _data[member];
} else if (invocation.isSetter) {
assert(member.endsWith('='));
member = member.substring(0, member.length - 1);
_data[member] = invocation.positionalArguments[0];
} else {
return Function.apply(_data[member], invocation.positionalArguments, invocation.namedArguments);
}
}
void clear() => _data.clear();
/**
* List all variables currently defined.
*/
List variables() => _data.keys.toList(growable: false);
}
class _Utils {
static double dateTimeToDouble(DateTime dateTime) =>
dateTime.millisecondsSinceEpoch.toDouble();
static DateTime doubleToDateTime(double dateTime) {
try {
return new DateTime.fromMillisecondsSinceEpoch(dateTime.toInt());
} catch(_) {
// TODO(antonnm): treat exceptions properly in bindings and
// find out how to treat NaNs.
return null;
}
}
static List convertToList(List list) {
// FIXME: [possible optimization]: do not copy the array if Dart_IsArray is fine w/ it.
final length = list.length;
List result = new List(length);
result.setRange(0, length, list);
return result;
}
static List convertMapToList(Map map) {
List result = [];
map.forEach((k, v) => result.addAll([k, v]));
return result;
}
static int convertCanvasElementGetContextMap(Map map) {
int result = 0;
if (map['alpha'] == true) result |= 0x01;
if (map['depth'] == true) result |= 0x02;
if (map['stencil'] == true) result |= 0x4;
if (map['antialias'] == true) result |= 0x08;
if (map['premultipliedAlpha'] == true) result |= 0x10;
if (map['preserveDrawingBuffer'] == true) result |= 0x20;
return result;
}
static List parseStackTrace(StackTrace stackTrace) {
final regExp = new RegExp(r'#\d\s+(.*) \((.*):(\d+):(\d+)\)');
List result = [];
for (var match in regExp.allMatches(stackTrace.toString())) {
result.add([match.group(1), match.group(2), int.parse(match.group(3)), int.parse(match.group(4))]);
}
return result;
}
static void populateMap(Map result, List list) {
for (int i = 0; i < list.length; i += 2) {
result[list[i]] = list[i + 1];
}
}
static bool isMap(obj) => obj is Map;
static Map createMap() => {};
static makeUnimplementedError(String fileName, int lineNo) {
return new UnsupportedError('[info: $fileName:$lineNo]');
}
static bool isTypeSubclassOf(Type type, Type other) {
if (type == other) {
return true;
}
var superclass = reflectClass(type).superclass;
if (superclass != null) {
return isTypeSubclassOf(superclass.reflectedType, other);
}
return false;
}
static window() native "Utils_window";
static forwardingPrint(String message) native "Utils_forwardingPrint";
static int _getNewIsolateId() native "Utils_getNewIsolateId";
// The following methods were added for debugger integration to make working
// with the Dart C mirrors API simpler.
// TODO(jacobr): consider moving them to a separate library.
// If Dart supported dynamic code injection, we would only inject this code
// when the debugger is invoked.
/**
* Strips the private secret prefix from member names of the form
* someName@hash.
*/
static String stripMemberName(String name) {
int endIndex = name.indexOf('@');
return endIndex > 0 ? name.substring(0, endIndex) : name;
}
/**
* Takes a list containing variable names and corresponding values and
* returns a map from normalized names to values. Variable names are assumed
* to have list offsets 2*n values at offset 2*n+1. This method is required
* because Dart_GetLocalVariables returns a list instead of an object that
* can be queried to lookup names and values.
*/
static Map<String, dynamic> createLocalVariablesMap(List localVariables) {
var map = {};
for (int i = 0; i < localVariables.length; i+=2) {
map[stripMemberName(localVariables[i])] = localVariables[i+1];
}
return map;
}
static _ConsoleVariables _consoleTempVariables = new _ConsoleVariables();
/**
* Header passed in from the Dartium Developer Tools when an expression is
* evaluated in the console as opposed to the watch window or another context
* that does not expect REPL support.
*/
static const _CONSOLE_API_SUPPORT_HEADER =
'with ((this && this.console && this.console._commandLineAPI) || {}) {\n';
/**
* Takes an [expression] and a list of [local] variable and returns an
* expression for a closure with a body matching the original expression
* where locals are passed in as arguments. Returns a list containing the
* String expression for the closure and the list of arguments that should
* be passed to it. The expression should then be evaluated using
* Dart_EvaluateExpr which will generate a closure that should be invoked
* with the list of arguments passed to this method.
*
* For example:
* <code>
* _consoleTempVariables = {'a' : someValue, 'b': someOtherValue}
* wrapExpressionAsClosure("${_CONSOLE_API_SUPPORT_HEADER}foo + bar + a",
* ["bar", 40, "foo", 2])
* </code>
* will return:
* <code>
* ["""(final $consoleVariables, final bar, final foo, final a, final b) =>
* (foo + bar + a
* )""",
* [_consoleTempVariables, 40, 2, someValue, someOtherValue]]
* </code>
*/
static List wrapExpressionAsClosure(String expression, List locals) {
// FIXME: dartbug.com/10434 find a less fragile way to determine whether
// we need to strip off console API support added by InjectedScript.
var args = {};
var sb = new StringBuffer("(");
addArg(arg, value) {
arg = stripMemberName(arg);
if (args.containsKey(arg)) return;
// We ignore arguments with the name 'this' rather than throwing an
// exception because Dart_GetLocalVariables includes 'this' and it
// is more convenient to filter it out here than from C++ code.
// 'this' needs to be handled by calling Dart_EvaluateExpr with
// 'this' as the target rather than by passing it as an argument.
if (arg == 'this') return;
if (args.isNotEmpty) {
sb.write(", ");
}
sb.write("final $arg");
args[arg] = value;
}
if (expression.indexOf(_CONSOLE_API_SUPPORT_HEADER) == 0) {
expression = expression.substring(expression.indexOf('\n') + 1);
expression = expression.substring(0, expression.lastIndexOf('\n'));
addArg("\$consoleVariables", _consoleTempVariables);
// FIXME: use a real Dart tokenizer. The following regular expressions
// only allow setting variables at the immediate start of the expression
// to limit the number of edge cases we have to handle.
// Match expressions that start with "var x"
final _VARIABLE_DECLARATION = new RegExp("^(\\s*)var\\s+(\\w+)");
// Match expressions that start with "someExistingConsoleVar ="
final _SET_VARIABLE = new RegExp("^(\\s*)(\\w+)(\\s*=)");
// Match trailing semicolons.
final _ENDING_SEMICOLONS = new RegExp("(;\\s*)*\$");
expression = expression.replaceAllMapped(_VARIABLE_DECLARATION,
(match) {
var variableName = match[2];
// Set the console variable if it isn't already set.
if (!_consoleTempVariables._data.containsKey(variableName)) {
_consoleTempVariables._data[variableName] = null;
}
return "${match[1]}\$consoleVariables.${variableName}";
});
expression = expression.replaceAllMapped(_SET_VARIABLE,
(match) {
var variableName = match[2];
// Only rewrite if the name matches an existing console variable.
if (_consoleTempVariables._data.containsKey(variableName)) {
return "${match[1]}\$consoleVariables.${variableName}${match[3]}";
} else {
return match[0];
}
});
// We only allow dart expressions not Dart statements. Silently remove
// trailing semicolons the user might have added by accident to reduce the
// number of spurious compile errors.
expression = expression.replaceFirst(_ENDING_SEMICOLONS, "");
}
if (locals != null) {
for (int i = 0; i < locals.length; i+= 2) {
addArg(locals[i], locals[i+1]);
}
}
// Inject all the already defined console variables.
_consoleTempVariables._data.forEach(addArg);
// TODO(jacobr): remove the parentheses around the expresson once
// dartbug.com/13723 is fixed. Currently we wrap expression in parentheses
// to ensure only valid Dart expressions are allowed. Otherwise the DartVM
// quietly ignores trailing Dart statements resulting in user confusion
// when part of an invalid expression they entered is ignored.
sb..write(') => (\n$expression\n)');
return [sb.toString(), args.values.toList(growable: false)];
}
/**
* TODO(jacobr): this is a big hack to get around the fact that we are still
* passing some JS expression to the evaluate method even when in a Dart
* context.
*/
static bool isJsExpression(String expression) =>
expression.startsWith("(function getCompletions");
/**
* Returns a list of completions to use if the receiver is o.
*/
static List<String> getCompletions(o) {
MirrorSystem system = currentMirrorSystem();
var completions = new Set<String>();
addAll(Map<Symbol, dynamic> map, bool isStatic) {
map.forEach((symbol, mirror) {
if (mirror.isStatic == isStatic && !mirror.isPrivate) {
var name = MirrorSystem.getName(symbol);
if (mirror is MethodMirror && mirror.isSetter)
name = name.substring(0, name.length - 1);
completions.add(name);
}
});
}
addForClass(ClassMirror mirror, bool isStatic) {
if (mirror == null)
return;
addAll(mirror.members, isStatic);
if (mirror.superclass != null)
addForClass(mirror.superclass, isStatic);
for (var interface in mirror.superinterfaces) {
addForClass(interface, isStatic);
}
}
if (o is Type) {
addForClass(reflectClass(o), true);
} else {
addForClass(reflect(o).type, false);
}
return completions.toList(growable: false);
}
/**
* Convenience helper to get the keys of a [Map] as a [List].
*/
static List getMapKeyList(Map map) => map.keys.toList();
/**
* Returns the keys of an arbitrary Dart Map encoded as unique Strings.
* Keys that are strings are left unchanged except that the prefix ":" is
* added to disambiguate keys from other Dart members.
* Keys that are not strings have # followed by the index of the key in the map
* prepended to disambuguate. This scheme is simplistic but easy to encode and
* decode. The use case for this method is displaying all map keys in a human
* readable way in debugging tools.
*/
static List<String> getEncodedMapKeyList(dynamic obj) {
if (obj is! Map) return null;
var ret = new List<String>();
int i = 0;
return obj.keys.map((key) {
var encodedKey;
if (key is String) {
encodedKey = ':$key';
} else {
// If the key isn't a string, return a guaranteed unique for this map
// string representation of the key that is still somewhat human
// readable.
encodedKey = '#${i}:$key';
}
i++;
return encodedKey;
}).toList(growable: false);
}
static final RegExp _NON_STRING_KEY_REGEXP = new RegExp("^#(\\d+):(.+)\$");
static _decodeKey(Map map, String key) {
// The key is a regular old String.
if (key.startsWith(':')) return key.substring(1);
var match = _NON_STRING_KEY_REGEXP.firstMatch(key);
if (match != null) {
int index = int.parse(match.group(1));
var iter = map.keys.skip(index);
if (iter.isNotEmpty) {
var ret = iter.first;
// Validate that the toString representation of the key matches what we
// expect. FIXME: throw an error if it does not.
assert(match.group(2) == '$ret');
return ret;
}
}
return null;
}
/**
* Converts keys encoded with [getEncodedMapKeyList] to their actual keys.
*/
static lookupValueForEncodedMapKey(Map obj, String key) => obj[_decodeKey(obj, key)];
/**
* Builds a constructor name with the form expected by the C Dart mirrors API.
*/
static String buildConstructorName(String className, String constructorName) => '$className.$constructorName';
/**
* Strips the class name from an expression of the form "className.someName".
*/
static String stripClassName(String str, String className) {
if (str.length > className.length + 1 &&
str.startsWith(className) && str[className.length] == '.') {
return str.substring(className.length + 1);
} else {
return str;
}
}
/**
* Removes the trailing dot from an expression ending in a dot.
* This method is used as Library prefixes include a trailing dot when using
* the C Dart debugger API.
*/
static String stripTrailingDot(String str) =>
(str != null && str[str.length - 1] == '.') ? str.substring(0, str.length - 1) : str;
static String addTrailingDot(String str) => '${str}.';
static bool isNoSuchMethodError(obj) => obj is NoSuchMethodError;
// TODO(jacobr): we need a failsafe way to determine that a Node is really a
// DOM node rather than just a class that extends Node.
static bool isNode(obj) => obj is Node;
static bool _isBuiltinType(ClassMirror cls) {
// TODO(vsm): Find a less hackish way to do this.
LibraryMirror lib = cls.owner;
String libName = lib.uri.toString();
return libName.startsWith('dart:');
}
static void register(Document document, String tag, Type type,
String extendsTagName) {
// TODO(vsm): Move these checks into native code.
ClassMirror cls = reflectClass(type);
if (_isBuiltinType(cls)) {
throw new UnsupportedError("Invalid custom element from $libName.");
}
var className = MirrorSystem.getName(cls.simpleName);
if (!cls.constructors.containsKey(new Symbol('$className.created'))) {
throw new UnsupportedError('Class is missing constructor $className.created');
}
_register(document, tag, type, extendsTagName);
}
static void _register(Document document, String tag, Type customType,
String extendsTagName) native "Utils_register";
static Element createElement(Document document, String tagName) native "Utils_createElement";
static void initializeCustomElement(HtmlElement element) native "Utils_initializeCustomElement";
}
class _DOMWindowCrossFrame extends NativeFieldWrapperClass1 implements
WindowBase {
_DOMWindowCrossFrame.internal();
// Fields.
HistoryBase get history native "Window_history_cross_frame_Getter";
LocationBase get location native "Window_location_cross_frame_Getter";
bool get closed native "Window_closed_Getter";
int get length native "Window_length_Getter";
WindowBase get opener native "Window_opener_Getter";
WindowBase get parent native "Window_parent_Getter";
WindowBase get top native "Window_top_Getter";
// Methods.
void close() native "Window_close_Callback";
void postMessage(/*SerializedScriptValue*/ message, String targetOrigin, [List messagePorts]) native "Window_postMessage_Callback";
// Implementation support.
String get typeName => "Window";
}
class _HistoryCrossFrame extends NativeFieldWrapperClass1 implements HistoryBase {
_HistoryCrossFrame.internal();
// Methods.
void back() native "History_back_Callback";
void forward() native "History_forward_Callback";
void go(int distance) native "History_go_Callback";
// Implementation support.
String get typeName => "History";
}
class _LocationCrossFrame extends NativeFieldWrapperClass1 implements LocationBase {
_LocationCrossFrame.internal();
// Fields.
void set href(String) native "Location_href_Setter";
// Implementation support.
String get typeName => "Location";
}
class _DOMStringMap extends NativeFieldWrapperClass1 implements Map<String, String> {
_DOMStringMap.internal();
bool containsValue(String value) => Maps.containsValue(this, value);
bool containsKey(String key) native "DOMStringMap_containsKey_Callback";
String operator [](String key) native "DOMStringMap_item_Callback";
void operator []=(String key, String value) native "DOMStringMap_setItem_Callback";
String putIfAbsent(String key, String ifAbsent()) => Maps.putIfAbsent(this, key, ifAbsent);
String remove(String key) native "DOMStringMap_remove_Callback";
void clear() => Maps.clear(this);
void forEach(void f(String key, String value)) => Maps.forEach(this, f);
Iterable<String> get keys native "DOMStringMap_getKeys_Callback";
Iterable<String> get values => Maps.getValues(this);
int get length => Maps.length(this);
bool get isEmpty => Maps.isEmpty(this);
bool get isNotEmpty => Maps.isNotEmpty(this);
}
final _printClosure = window.console.log;
final _pureIsolatePrintClosure = (s) {
throw new UnimplementedError("Printing from a background isolate "
"is not supported in the browser");
};
final _forwardingPrintClosure = _Utils.forwardingPrint;
class _Timer implements Timer {
var _canceler;
_Timer(int milliSeconds, void callback(Timer timer), bool repeating) {
if (repeating) {
int id = window._setInterval(() {
callback(this);
}, milliSeconds);
_canceler = () => window._clearInterval(id);
} else {
int id = window._setTimeout(() {
_canceler = null;
callback(this);
}, milliSeconds);
_canceler = () => window._clearTimeout(id);
}
}
void cancel() {
if (_canceler != null) {
_canceler();
}
_canceler = null;
}
bool get isActive => _canceler != null;
}
get _timerFactoryClosure =>
(int milliSeconds, void callback(Timer timer), bool repeating) {
return new _Timer(milliSeconds, callback, repeating);
};
get _pureIsolateTimerFactoryClosure =>
((int milliSeconds, void callback(Timer time), bool repeating) =>
throw new UnimplementedError("Timers on background isolates "
"are not supported in the browser"));
void _initializeCustomElement(Element e) {
_Utils.initializeCustomElement(e);
}