blob: ee81750e6e9938acbbbfa87d0eaa5e7048df4686 [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 _Property {
_Property(this.name)
: _hasValue = false,
writable = false,
isMethod = false,
isOwn = true,
wasThrown = false;
bool get hasValue => _hasValue;
get value => _value;
set value(v) {
_value = v;
_hasValue = true;
}
final String name;
Function setter;
Function getter;
var _value;
bool _hasValue;
bool writable;
bool isMethod;
bool isOwn;
bool wasThrown;
}
/**
* Manager for navigating between libraries from the devtools console.
*/
class _LibraryManager {
/**
* Current active library
*/
static var _currentLibrary;
static var _validCache = false;
static List<Uri> _libraryUris;
// List of all maps to check to determine if there is an exact match.
static Map<String, List<Uri>> _fastPaths;
static void _addFastPath(String key, Uri uri) {
_fastPaths.putIfAbsent(key, () => <Uri>[]).add(uri);
}
static cache() {
if (_validCache) return;
_validCache = true;
_libraryUris = <Uri>[];
_fastPaths = new Map<String, List<Uri>>();
var system = currentMirrorSystem();
system.libraries.forEach((uri, library) {
_libraryUris.add(uri);
_addFastPath(uri.toString(), uri);
_addFastPath(MirrorSystem.getName(library.simpleName), uri);
});
}
static String get currentLibrary {
if (_currentLibrary == null) {
_currentLibrary =
currentMirrorSystem().isolate.rootLibrary.uri.toString();
}
return _currentLibrary;
}
/**
* Find libraries matching a given name.
*
* Uses heuristics to only return a single match when the user intent is
* generally unambiguous.
*/
static List<Uri> findMatches(String name) {
cache();
var nameAsFile = name.endsWith('.dart') ? name : '${name}.dart';
// Perfect match first.
var fastPatchMatches = _fastPaths[name];
if (fastPatchMatches != null) {
return fastPatchMatches.toList();
}
// Exact match for file path.
var matches = new LinkedHashSet<Uri>();
for (var uri in _libraryUris) {
if (uri.path == name || uri.path == nameAsFile) matches.add(uri);
}
if (matches.isNotEmpty) return matches.toList();
// Exact match for file name.
if (name != nameAsFile) {
for (var uri in _libraryUris) {
if (uri.pathSegments.isNotEmpty &&
(uri.pathSegments.last == nameAsFile)) {
matches.add(uri);
}
}
if (matches.isNotEmpty) return matches.toList();
}
for (var uri in _libraryUris) {
if (uri.pathSegments.isNotEmpty && (uri.pathSegments.last == name)) {
matches.add(uri);
}
}
if (matches.isNotEmpty) return matches.toList();
// Partial match on path.
for (var uri in _libraryUris) {
if (uri.path.contains(name)) {
matches.add(uri);
}
}
if (matches.isNotEmpty) return matches.toList();
// Partial match on entire uri.
for (var uri in _libraryUris) {
if (uri.toString().contains(name)) {
matches.add(uri);
}
}
if (matches.isNotEmpty) return matches.toList();
// Partial match on entire uri ignoring case.
name = name.toLowerCase();
for (var uri in _libraryUris) {
if (uri.toString().toLowerCase().contains(name)) {
matches.add(uri);
}
}
return matches.toList();
}
static setLibrary([String name]) {
// Bust cache in case library list has changed. Ideally we would listen for
// when libraries are loaded and invalidate based on that.
_validCache = false;
cache();
if (name == null) {
window.console
..group("Current library: $_currentLibrary")
..groupCollapsed("All libraries:");
_listLibraries();
window.console..groupEnd()..groupEnd();
return;
}
var matches = findMatches(name);
if (matches.length != 1) {
if (matches.length > 1) {
window.console.warn("Ambiguous library name: $name");
}
showMatches(name, matches);
return;
}
_currentLibrary = matches.first.toString();
window.console.log("Set library to $_currentLibrary");
}
static getLibrary() {
return currentLibrary;
}
static List<Uri> _sortUris(Iterable<Uri> uris) {
return (uris.toList())
..sort((Uri a, Uri b) {
if (a.scheme != b.scheme) {
if (a.scheme == 'dart') return -1;
if (b.scheme == 'dart') return 1;
return a.scheme.compareTo(b.scheme);
}
return a.toString().compareTo(b.toString());
});
}
static void listLibraries() {
_validCache = false;
cache();
_listLibraries();
}
static void _listLibraries() {
window.console.log(_sortUris(_libraryUris).join("\n"));
}
// Workaround to allow calling console.log with an arbitrary number of
// arguments.
static void _log(List<String> args) {
js.JsNative.callMethod(window.console, 'log', args);
}
static showMatches(String key, Iterable<Uri> uris) {
var boldPairs = [];
var sb = new StringBuffer();
if (uris.isEmpty) {
window.console.group("All libraries:");
_listLibraries();
window.console
..groupEnd()
..error("No library names or URIs match '$key'");
return;
}
sb.write("${uris.length} matches\n");
var lowerCaseKey = key.toLowerCase();
for (var uri in uris) {
var txt = uri.toString();
int index = txt.toLowerCase().indexOf(lowerCaseKey);
if (index != -1) {
// %c enables styling console log messages with css
// specified at the end of the console.
sb..write(txt.substring(0, index))..write('%c');
var matchEnd = index + key.length;
sb
..write(txt.substring(index, matchEnd))
..write('%c')
..write(txt.substring(matchEnd))
..write('\n');
boldPairs..add('font-weight: bold')..add('font-weight: normal');
}
}
_log([sb.toString()]..addAll(boldPairs));
}
}
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();
void setVariable(String name, value) {
_data[name] = value;
}
}
/**
* Base class for invocation trampolines used to closurize methods, getters
* and setters.
*/
abstract class _Trampoline implements Function {
final ObjectMirror _receiver;
final MethodMirror _methodMirror;
final Symbol _selector;
_Trampoline(this._receiver, this._methodMirror, this._selector);
}
class _MethodTrampoline extends _Trampoline {
_MethodTrampoline(
ObjectMirror receiver, MethodMirror methodMirror, Symbol selector)
: super(receiver, methodMirror, selector);
noSuchMethod(Invocation msg) {
if (msg.memberName != #call) return super.noSuchMethod(msg);
return _receiver
.invoke(_selector, msg.positionalArguments, msg.namedArguments)
.reflectee;
}
}
/**
* Invocation trampoline class used to closurize getters.
*/
class _GetterTrampoline extends _Trampoline {
_GetterTrampoline(
ObjectMirror receiver, MethodMirror methodMirror, Symbol selector)
: super(receiver, methodMirror, selector);
call() => _receiver.getField(_selector).reflectee;
}
/**
* Invocation trampoline class used to closurize setters.
*/
class _SetterTrampoline extends _Trampoline {
_SetterTrampoline(
ObjectMirror receiver, MethodMirror methodMirror, Symbol selector)
: super(receiver, methodMirror, selector);
call(value) {
_receiver.setField(_selector, value);
}
}
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 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 List toListIfIterable(obj) => obj is Iterable ? obj.toList() : null;
static Map createMap() => {};
static parseJson(String jsonSource) =>
const JsonDecoder().convert(jsonSource);
static String getLibraryUrl() => _LibraryManager.currentLibrary;
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 Element getAndValidateNativeType(Type type, String tagName) {
var element = new Element.tag(tagName);
if (!isTypeSubclassOf(type, element.runtimeType)) {
return null;
}
return element;
}
static forwardingPrint(String message) =>
_blink.Blink_Utils.forwardingPrint(message);
static void spawnDomHelper(Function f, int replyTo) =>
_blink.Blink_Utils.spawnDomHelper(f, replyTo);
// TODO(vsm): Make this API compatible with spawnUri. It should also
// return a Future<Isolate>.
// TODO(jacobr): IS THIS RIGHT? I worry we have broken conversion from Promise to Future.
static spawnDomUri(String uri) => _blink.Blink_Utils.spawnDomUri(uri);
// 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();
/**
* 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("foo + bar + a", ["bar", 40, "foo", 2], true)
* </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, bool includeCommandLineAPI) {
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;
// Avoid being broken by bogus ':async_op' local passed in when within
// an async method.
if (arg.startsWith(':')) return;
if (args.isNotEmpty) {
sb.write(", ");
}
sb.write("final $arg");
args[arg] = value;
}
if (includeCommandLineAPI) {
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 expression 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)];
}
static String _getShortSymbolName(
Symbol symbol, DeclarationMirror declaration) {
var name = MirrorSystem.getName(symbol);
if (declaration is MethodMirror) {
if (declaration.isSetter && name[name.length - 1] == "=") {
return name.substring(0, name.length - 1);
}
if (declaration.isConstructor) {
return name.substring(name.indexOf('.') + 1);
}
}
return name;
}
/**
* Handle special console commands such as $lib and $libs that should not be
* evaluated as Dart expressions and instead should be interpreted directly.
* Commands supported:
* library <-- shows the current library and lists all libraries.
* library "library_uri" <-- select a specific library
* library "library_uri_fragment"
*/
static bool maybeHandleSpecialConsoleCommand(String expression) {
expression = expression.trim();
var setLibraryCommand = r'library ';
if (expression == r'library') {
_LibraryManager.setLibrary();
return true;
}
if (expression.startsWith(setLibraryCommand)) {
expression = expression.substring(setLibraryCommand.length);
if (expression.length >= 2) {
String start = expression[0];
String end = expression[expression.length - 1];
// TODO(jacobr): maybe we should require quotes.
if ((start == "'" && end == "'") || (start == '"' && end == '"')) {
expression = expression.substring(1, expression.length - 1);
}
}
_LibraryManager.setLibrary(expression);
return true;
}
return false;
}
/**
* 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.declarations, 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);
}
/**
* Adds all candidate String completions from [declarations] to [output]
* filtering based on [staticContext] and [includePrivate].
*/
static void _getCompletionsHelper(ClassMirror classMirror, bool staticContext,
LibraryMirror libraryMirror, Set<String> output) {
bool includePrivate = libraryMirror == classMirror.owner;
classMirror.declarations.forEach((symbol, declaration) {
if (!includePrivate && declaration.isPrivate) return;
if (declaration is VariableMirror) {
if (staticContext != declaration.isStatic) return;
} else if (declaration is MethodMirror) {
if (declaration.isOperator) return;
if (declaration.isConstructor) {
if (!staticContext) return;
var name = MirrorSystem.getName(declaration.constructorName);
if (name.isNotEmpty) output.add(name);
return;
}
if (staticContext != declaration.isStatic) return;
} else if (declaration is TypeMirror) {
return;
}
output.add(_getShortSymbolName(symbol, declaration));
});
if (!staticContext) {
for (var interface in classMirror.superinterfaces) {
_getCompletionsHelper(interface, staticContext, libraryMirror, output);
}
if (classMirror.superclass != null) {
_getCompletionsHelper(
classMirror.superclass, staticContext, libraryMirror, output);
}
}
}
static void _getLibraryCompletionsHelper(
LibraryMirror library, bool includePrivate, Set<String> output) {
library.declarations.forEach((symbol, declaration) {
if (!includePrivate && declaration.isPrivate) return;
output.add(_getShortSymbolName(symbol, declaration));
});
}
static LibraryMirror getLibraryMirror(String url) =>
currentMirrorSystem().libraries[Uri.parse(url)];
/**
* Get code completions for [o] only showing privates from [libraryUrl].
*/
static List<String> getObjectCompletions(o, String libraryUrl) {
var classMirror;
bool staticContext;
if (o is Type) {
classMirror = reflectClass(o);
staticContext = true;
} else {
classMirror = reflect(o).type;
staticContext = false;
}
var names = new Set<String>();
getClassCompletions(classMirror, names, staticContext, libraryUrl);
return names.toList()..sort();
}
static void getClassCompletions(ClassMirror classMirror, Set<String> names,
bool staticContext, String libraryUrl) {
LibraryMirror libraryMirror = getLibraryMirror(libraryUrl);
_getCompletionsHelper(classMirror, staticContext, libraryMirror, names);
}
static List<String> getLibraryCompletions(String url) {
var names = new Set<String>();
_getLibraryCompletionsHelper(getLibraryMirror(url), true, names);
return names.toList();
}
/**
* Get valid code completions from within a library and all libraries
* imported by that library.
*/
static List<String> getLibraryCompletionsIncludingImports(String url) {
var names = new Set<String>();
var libraryMirror = getLibraryMirror(url);
_getLibraryCompletionsHelper(libraryMirror, true, names);
for (var dependency in libraryMirror.libraryDependencies) {
if (dependency.isImport) {
if (dependency.prefix == null) {
_getLibraryCompletionsHelper(dependency.targetLibrary, false, names);
} else {
names.add(MirrorSystem.getName(dependency.prefix));
}
}
}
return names.toList();
}
static final SIDE_EFFECT_FREE_LIBRARIES = new Set<String>()
..add('dart:html')
..add('dart:indexed_db')
..add('dart:svg')
..add('dart:typed_data')
..add('dart:web_audio')
..add('dart:web_gl')
..add('dart:web_sql');
static LibraryMirror _getLibrary(MethodMirror methodMirror) {
var owner = methodMirror.owner;
if (owner is ClassMirror) {
return owner;
} else if (owner is LibraryMirror) {
return owner;
}
return null;
}
/**
* For parity with the JavaScript debugger, we treat some getters as if
* they are fields so that users can see their values immediately.
* This matches JavaScript's behavior for getters on DOM objects.
* In the future we should consider adding an annotation to tag getters
* in user libraries as side effect free.
*/
static bool _isSideEffectFreeGetter(
MethodMirror methodMirror, LibraryMirror libraryMirror) {
// This matches JavaScript behavior. We should consider displaying
// getters for all dart platform libraries rather than just the DOM
// libraries.
return libraryMirror.uri.scheme == 'dart' &&
SIDE_EFFECT_FREE_LIBRARIES.contains(libraryMirror.uri.toString());
}
/**
* Whether we should treat a property as a field for the purposes of the
* debugger.
*/
static bool treatPropertyAsField(
MethodMirror methodMirror, LibraryMirror libraryMirror) {
return (methodMirror.isGetter || methodMirror.isSetter) &&
(methodMirror.isSynthetic ||
_isSideEffectFreeGetter(methodMirror, libraryMirror));
}
// TODO(jacobr): generate more concise function descriptions instead of
// dumping the entire function source.
static String describeFunction(function) {
if (function is _Trampoline) return function._methodMirror.source;
try {
var mirror = reflect(function);
return mirror.function.source;
} catch (e) {
return function.toString();
}
}
static List getInvocationTrampolineDetails(_Trampoline method) {
var loc = method._methodMirror.location;
return [
loc.line,
loc.column,
loc.sourceUri.toString(),
MirrorSystem.getName(method._selector)
];
}
static List getLibraryProperties(
String libraryUrl, bool ownProperties, bool accessorPropertiesOnly) {
var properties = new Map<String, _Property>();
var libraryMirror = getLibraryMirror(libraryUrl);
_addInstanceMirrors(
libraryMirror,
libraryMirror,
libraryMirror.declarations,
ownProperties,
accessorPropertiesOnly,
false,
false,
properties);
if (!accessorPropertiesOnly) {
// We need to add class properties for all classes in the library.
libraryMirror.declarations.forEach((symbol, declarationMirror) {
if (declarationMirror is ClassMirror) {
var name = MirrorSystem.getName(symbol);
if (declarationMirror.hasReflectedType &&
!properties.containsKey(name)) {
properties[name] = new _Property(name)
..value = declarationMirror.reflectedType;
}
}
});
}
return packageProperties(properties);
}
static List getObjectProperties(
o, bool ownProperties, bool accessorPropertiesOnly) {
var properties = new Map<String, _Property>();
var names = new Set<String>();
var objectMirror = reflect(o);
var classMirror = objectMirror.type;
_addInstanceMirrors(
objectMirror,
classMirror.owner,
classMirror.instanceMembers,
ownProperties,
accessorPropertiesOnly,
false,
true,
properties);
return packageProperties(properties);
}
static List getObjectClassProperties(
o, bool ownProperties, bool accessorPropertiesOnly) {
var properties = new Map<String, _Property>();
var objectMirror = reflect(o);
var classMirror = objectMirror.type;
_addInstanceMirrors(
objectMirror,
classMirror.owner,
classMirror.instanceMembers,
ownProperties,
accessorPropertiesOnly,
true,
false,
properties);
_addStatics(classMirror, properties, accessorPropertiesOnly);
return packageProperties(properties);
}
static List getClassProperties(
Type t, bool ownProperties, bool accessorPropertiesOnly) {
var properties = new Map<String, _Property>();
var classMirror = reflectClass(t);
_addStatics(classMirror, properties, accessorPropertiesOnly);
return packageProperties(properties);
}
static void _addStatics(ClassMirror classMirror,
Map<String, _Property> properties, bool accessorPropertiesOnly) {
var libraryMirror = classMirror.owner;
classMirror.declarations.forEach((symbol, declaration) {
var name = _getShortSymbolName(symbol, declaration);
if (name.isEmpty) return;
if (declaration is VariableMirror) {
if (accessorPropertiesOnly) return;
if (!declaration.isStatic) return;
properties.putIfAbsent(name, () => new _Property(name))
..value = classMirror.getField(symbol).reflectee
..writable = !declaration.isFinal && !declaration.isConst;
} else if (declaration is MethodMirror) {
MethodMirror methodMirror = declaration;
// FIXMEDART: should we display constructors?
if (methodMirror.isConstructor) return;
if (!methodMirror.isStatic) return;
if (accessorPropertiesOnly) {
if (methodMirror.isRegularMethod ||
treatPropertyAsField(methodMirror, libraryMirror)) {
return;
}
} else if (!methodMirror.isRegularMethod &&
!treatPropertyAsField(methodMirror, libraryMirror)) {
return;
}
var property = properties.putIfAbsent(name, () => new _Property(name));
_fillMethodMirrorProperty(libraryMirror, classMirror, methodMirror,
symbol, accessorPropertiesOnly, property);
}
});
}
static void _fillMethodMirrorProperty(
LibraryMirror libraryMirror,
methodOwner,
MethodMirror methodMirror,
Symbol symbol,
bool accessorPropertiesOnly,
_Property property) {
if (methodMirror.isRegularMethod) {
property
..value = new _MethodTrampoline(methodOwner, methodMirror, symbol)
..isMethod = true;
} else if (methodMirror.isGetter) {
if (treatPropertyAsField(methodMirror, libraryMirror)) {
try {
property.value = methodOwner.getField(symbol).reflectee;
} catch (e) {
property
..wasThrown = true
..value = e;
}
} else if (accessorPropertiesOnly) {
property.getter =
new _GetterTrampoline(methodOwner, methodMirror, symbol);
}
} else if (methodMirror.isSetter) {
if (accessorPropertiesOnly &&
!treatPropertyAsField(methodMirror, libraryMirror)) {
property.setter = new _SetterTrampoline(methodOwner, methodMirror,
MirrorSystem.getSymbol(property.name, libraryMirror));
}
property.writable = true;
}
}
/**
* Helper method that handles collecting up properties from classes
* or libraries using the filters [ownProperties], [accessorPropertiesOnly],
* [hideFields], and [hideMethods] to determine which properties are
* collected. [accessorPropertiesOnly] specifies whether all properties
* should be returned or just accessors. [hideFields] specifies whether
* fields should be hidden. hideMethods specifies whether methods should be
* shown or hidden. [ownProperties] is not currently used but is part of the
* Blink devtools API for enumerating properties.
*/
static void _addInstanceMirrors(
ObjectMirror objectMirror,
LibraryMirror libraryMirror,
Map<Symbol, Mirror> declarations,
bool ownProperties,
bool accessorPropertiesOnly,
bool hideFields,
bool hideMethods,
Map<String, _Property> properties) {
declarations.forEach((symbol, declaration) {
if (declaration is TypedefMirror || declaration is ClassMirror) return;
var name = _getShortSymbolName(symbol, declaration);
if (name.isEmpty) return;
bool isField = declaration is VariableMirror ||
(declaration is MethodMirror &&
treatPropertyAsField(declaration, libraryMirror));
if ((isField && hideFields) || (hideMethods && !isField)) return;
if (accessorPropertiesOnly) {
if (declaration is VariableMirror ||
declaration.isRegularMethod ||
isField) {
return;
}
} else if (declaration is MethodMirror &&
(declaration.isGetter || declaration.isSetter) &&
!treatPropertyAsField(declaration, libraryMirror)) {
return;
}
var property = properties.putIfAbsent(name, () => new _Property(name));
if (declaration is VariableMirror) {
property
..value = objectMirror.getField(symbol).reflectee
..writable = !declaration.isFinal && !declaration.isConst;
return;
}
_fillMethodMirrorProperty(libraryMirror, objectMirror, declaration,
symbol, accessorPropertiesOnly, property);
});
}
/**
* Flatten down the properties data structure into a List that is easy to
* access from native code.
*/
static List packageProperties(Map<String, _Property> properties) {
var ret = [];
for (var property in properties.values) {
ret.addAll([
property.name,
property.setter,
property.getter,
property.value,
property.hasValue,
property.writable,
property.isMethod,
property.isOwn,
property.wasThrown
]);
}
return ret;
}
/**
* Get a property, returning null if the property does not exist.
* For private property names, we attempt to resolve the property in the
* context of each library that the property name could be associated with.
*/
static getObjectPropertySafe(o, String propertyName) {
var objectMirror = reflect(o);
var classMirror = objectMirror.type;
if (propertyName.startsWith("_")) {
var attemptedLibraries = new Set<LibraryMirror>();
while (classMirror != null) {
LibraryMirror library = classMirror.owner;
if (!attemptedLibraries.contains(library)) {
try {
return objectMirror
.getField(MirrorSystem.getSymbol(propertyName, library))
.reflectee;
} catch (e) {}
attemptedLibraries.add(library);
}
classMirror = classMirror.superclass;
}
return null;
}
try {
return objectMirror
.getField(MirrorSystem.getSymbol(propertyName))
.reflectee;
} catch (e) {
return null;
}
}
/**
* Helper to wrap the inspect method on InjectedScriptHost to provide the
* inspect method required for the
*/
static List consoleApi(host) {
return [
"inspect",
(o) {
js.JsNative.callMethod(host, "_inspect", [o]);
return o;
},
"dir",
window.console.dir,
"dirxml",
window.console.dirxml
// FIXME: add copy method.
];
}
static List getMapKeyList(Map map) => map.keys.toList();
static bool isNoSuchMethodError(obj) => obj is NoSuchMethodError;
static void register(
Document document, String tag, Type type, String extendsTagName) {
var nativeClass = _validateCustomType(type);
if (extendsTagName == null) {
if (nativeClass.reflectedType != HtmlElement) {
throw new UnsupportedError('Class must provide extendsTag if base '
'native class is not HTMLElement');
}
}
_register(document, tag, type, extendsTagName);
}
static void _register(Document document, String tag, Type customType,
String extendsTagName) =>
_blink.Blink_Utils.register(document, tag, customType, extendsTagName);
static Element createElement(Document document, String tagName) =>
_blink.Blink_Utils.createElement(document, tagName);
}
class _DOMWindowCrossFrame extends DartHtmlDomObject implements WindowBase {
_DOMWindowCrossFrame.internal();
static _createSafe(win) {
if (identical(win, window)) {
// The current Window object is the only window object that should not
// use _DOMWindowCrossFrame.
return window;
}
return win is _DOMWindowCrossFrame
? win
: _blink.Blink_Utils.setInstanceInterceptor(win, _DOMWindowCrossFrame);
}
// Fields.
HistoryBase get history {
var history = _blink.BlinkWindow.instance.history_Getter_(this);
return history is _HistoryCrossFrame
? history
: _blink.Blink_Utils
.setInstanceInterceptor(history, _HistoryCrossFrame);
}
LocationBase get location {
var location = _blink.BlinkWindow.instance.location_Getter_(this);
return location is _LocationCrossFrame
? location
: _blink.Blink_Utils
.setInstanceInterceptor(location, _LocationCrossFrame);
}
bool get closed => _blink.BlinkWindow.instance.closed_Getter_(this);
WindowBase get opener => _convertNativeToDart_Window(
_blink.BlinkWindow.instance.opener_Getter_(this));
WindowBase get parent => _convertNativeToDart_Window(
_blink.BlinkWindow.instance.parent_Getter_(this));
WindowBase get top => _convertNativeToDart_Window(
_blink.BlinkWindow.instance.top_Getter_(this));
// Methods.
void close() => _blink.BlinkWindow.instance.close_Callback_0_(this);
void postMessage(Object message, String targetOrigin,
[List<MessagePort> transfer]) =>
_blink.BlinkWindow.instance.postMessage_Callback_3_(
this,
convertDartToNative_SerializedScriptValue(message),
targetOrigin,
transfer);
// Implementation support.
String get typeName => "Window";
// TODO(efortuna): Remove this method. dartbug.com/16814
Events get on => throw new UnsupportedError(
'You can only attach EventListeners to your own window.');
// TODO(efortuna): Remove this method. dartbug.com/16814
void _addEventListener(
[String type, EventListener listener, bool useCapture]) =>
throw new UnsupportedError(
'You can only attach EventListeners to your own window.');
// TODO(efortuna): Remove this method. dartbug.com/16814
void addEventListener(String type, EventListener listener,
[bool useCapture]) =>
throw new UnsupportedError(
'You can only attach EventListeners to your own window.');
// TODO(efortuna): Remove this method. dartbug.com/16814
bool dispatchEvent(Event event) => throw new UnsupportedError(
'You can only attach EventListeners to your own window.');
// TODO(efortuna): Remove this method. dartbug.com/16814
void _removeEventListener(
[String type, EventListener listener, bool useCapture]) =>
throw new UnsupportedError(
'You can only attach EventListeners to your own window.');
// TODO(efortuna): Remove this method. dartbug.com/16814
void removeEventListener(String type, EventListener listener,
[bool useCapture]) =>
throw new UnsupportedError(
'You can only attach EventListeners to your own window.');
}
class _HistoryCrossFrame extends DartHtmlDomObject implements HistoryBase {
_HistoryCrossFrame.internal();
// Methods.
void back() => _blink.BlinkHistory.instance.back_Callback_0_(this);
void forward() => _blink.BlinkHistory.instance.forward_Callback_0_(this);
void go([int delta]) {
if (delta != null) {
_blink.BlinkHistory.instance.go_Callback_1_(this, delta);
return;
}
_blink.BlinkHistory.instance.go_Callback_0_(this);
return;
}
// Implementation support.
String get typeName => "History";
}
class _LocationCrossFrame extends DartHtmlDomObject implements LocationBase {
_LocationCrossFrame.internal();
// Fields.
set href(String value) =>
_blink.BlinkLocation.instance.href_Setter_(this, value);
// Implementation support.
String get typeName => "Location";
}
// TODO(vsm): Remove DOM isolate code once we have Dartium isolates
// as workers. This is only used to support
// printing and timers in background isolates. As workers they should
// be able to just do those things natively.
_makeSendPortFuture(spawnRequest) {
final completer = new Completer<SendPort>.sync();
final port = new ReceivePort();
port.listen((result) {
completer.complete(result);
port.close();
});
// TODO: SendPort.hashCode is ugly way to access port id.
spawnRequest(port.sendPort.hashCode);
return completer.future;
}
Future<SendPort> _spawnDomHelper(Function f) => _makeSendPortFuture((portId) {
_Utils.spawnDomHelper(f, portId);
});
final Future<SendPort> __HELPER_ISOLATE_PORT =
_spawnDomHelper(_helperIsolateMain);
// Tricky part.
// Once __HELPER_ISOLATE_PORT gets resolved, it will still delay in .then
// and to delay Timer.run is used. However, Timer.run will try to register
// another Timer and here we got stuck: event cannot be posted as then
// callback is not executed because it's delayed with timer.
// Therefore once future is resolved, it's unsafe to call .then on it
// in Timer code.
SendPort __SEND_PORT;
_sendToHelperIsolate(msg, SendPort replyTo) {
if (__SEND_PORT != null) {
__SEND_PORT.send([msg, replyTo]);
} else {
__HELPER_ISOLATE_PORT.then((port) {
__SEND_PORT = port;
__SEND_PORT.send([msg, replyTo]);
});
}
}
final _TIMER_REGISTRY = new Map<SendPort, Timer>();
const _NEW_TIMER = 'NEW_TIMER';
const _CANCEL_TIMER = 'CANCEL_TIMER';
const _TIMER_PING = 'TIMER_PING';
const _PRINT = 'PRINT';
_helperIsolateMain(originalSendPort) {
var port = new ReceivePort();
originalSendPort.send(port.sendPort);
port.listen((args) {
var msg = args.first;
var replyTo = args.last;
final cmd = msg[0];
if (cmd == _NEW_TIMER) {
final duration = new Duration(milliseconds: msg[1]);
bool periodic = msg[2];
ping() {
replyTo.send(_TIMER_PING);
}
;
_TIMER_REGISTRY[replyTo] = periodic
? new Timer.periodic(duration, (_) {
ping();
})
: new Timer(duration, ping);
} else if (cmd == _CANCEL_TIMER) {
_TIMER_REGISTRY.remove(replyTo).cancel();
} else if (cmd == _PRINT) {
final message = msg[1];
// TODO(antonm): we need somehow identify those isolates.
print('[From isolate] $message');
}
});
}
final _printClosure = (s) => window.console.log(s);
final _pureIsolatePrintClosure = (s) {
_sendToHelperIsolate([_PRINT, s], null);
};
final _forwardingPrintClosure = _Utils.forwardingPrint;
final _uriBaseClosure = () => Uri.parse(window.location.href);
final _pureIsolateUriBaseClosure = () {
throw new UnimplementedError("Uri.base on a background isolate "
"is not supported in the browser");
};
class _Timer implements Timer {
static const int _STATE_TIMEOUT = 0;
static const int _STATE_INTERVAL = 1;
int _state;
_Timer(int milliSeconds, void callback(Timer timer), bool repeating) {
if (repeating) {
_state = (window._setInterval(() {
callback(this);
}, milliSeconds) <<
1) |
_STATE_INTERVAL;
} else {
_state = (window._setTimeout(() {
_state = null;
callback(this);
}, milliSeconds) <<
1) |
_STATE_TIMEOUT;
}
}
void cancel() {
if (_state == null) return;
int id = _state >> 1;
if ((_state & 1) == _STATE_TIMEOUT) {
window._clearTimeout(id);
} else {
window._clearInterval(id);
}
_state = null;
}
bool get isActive => _state != null;
}
get _timerFactoryClosure =>
(int milliSeconds, void callback(Timer timer), bool repeating) {
return new _Timer(milliSeconds, callback, repeating);
};
class _PureIsolateTimer implements Timer {
bool _isActive = true;
final ReceivePort _port = new ReceivePort();
SendPort _sendPort; // Effectively final.
// static SendPort _SEND_PORT;
_PureIsolateTimer(int milliSeconds, callback, repeating) {
_sendPort = _port.sendPort;
_port.listen((msg) {
assert(msg == _TIMER_PING);
_isActive = repeating;
callback(this);
if (!repeating) _cancel();
});
_send([_NEW_TIMER, milliSeconds, repeating]);
}
void cancel() {
_cancel();
_send([_CANCEL_TIMER]);
}
void _cancel() {
_isActive = false;
_port.close();
}
_send(msg) {
_sendToHelperIsolate(msg, _sendPort);
}
bool get isActive => _isActive;
}
get _pureIsolateTimerFactoryClosure =>
((int milliSeconds, void callback(Timer time), bool repeating) =>
new _PureIsolateTimer(milliSeconds, callback, repeating));
class _ScheduleImmediateHelper {
MutationObserver _observer;
final DivElement _div = new DivElement();
Function _callback;
_ScheduleImmediateHelper() {
// Run in the root-zone as the DOM callback would otherwise execute in the
// current zone.
Zone.root.run(() {
// Mutation events get fired as soon as the current event stack is unwound
// so we just make a dummy event and listen for that.
_observer = new MutationObserver(_handleMutation);
_observer.observe(_div, attributes: true);
});
}
void _schedule(callback) {
if (_callback != null) {
throw new StateError(
'Only one immediate callback can be scheduled at once');
}
_callback = callback;
// Toggle it to trigger the mutation event.
_div.hidden = !_div.hidden;
}
_handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
var tmp = _callback;
_callback = null;
tmp();
}
}
final _ScheduleImmediateHelper _scheduleImmediateHelper =
new _ScheduleImmediateHelper();
get _scheduleImmediateClosure => (void callback()) {
_scheduleImmediateHelper._schedule(callback);
};
get _pureIsolateScheduleImmediateClosure => ((void callback()) =>
throw new UnimplementedError("scheduleMicrotask in background isolates "
"are not supported in the browser"));
// Class for unsupported native browser 'DOM' objects.
class _UnsupportedBrowserObject extends DartHtmlDomObject {}