| // Copyright (c) 2013, 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 polymer; |
| |
| /** Annotation used to automatically register polymer elements. */ |
| class CustomTag { |
| final String tagName; |
| const CustomTag(this.tagName); |
| } |
| |
| /** |
| * Metadata used to label static or top-level methods that are called |
| * automatically when loading the library of a custom element. |
| */ |
| const initMethod = const _InitMethodAnnotation(); |
| |
| /** |
| * Initializes a polymer application as follows: |
| * * set up up polling for observable changes |
| * * initialize Model-Driven Views |
| * * Include some style to prevent flash of unstyled content (FOUC) |
| * * for each library in [libraries], register custom elements labeled with |
| * [CustomTag] and invoke the initialization method on it. If [libraries] |
| * is null, first find all libraries that need to be loaded by scanning for |
| * HTML imports in the main document. |
| * |
| * The initialization on each library is a top-level function and annotated with |
| * [initMethod]. |
| * |
| * The urls in [libraries] can be absolute or relative to |
| * `currentMirrorSystem().isolate.rootLibrary.uri`. |
| */ |
| void initPolymer([List<String> libraries]) { |
| runMicrotask(() { |
| // DOM events don't yet go through microtasks, so we catch those here. |
| new Timer.periodic(new Duration(milliseconds: 125), |
| (_) => performMicrotaskCheckpoint()); |
| |
| preventFlashOfUnstyledContent(); |
| |
| // TODO(jmesserly): mdv should use initMdv instead of mdv.initialize. |
| mdv.initialize(); |
| document.register(PolymerDeclaration._TAG, PolymerDeclaration); |
| |
| // Note: we synchronously load all libraries because the script invoking |
| // this is run after all HTML imports are resolved. |
| if (libraries == null) { |
| libraries = _discoverScripts(document, window.location.href); |
| } |
| _loadLibraries(libraries); |
| }); |
| } |
| |
| void _loadLibraries(libraries) { |
| for (var lib in libraries) { |
| _loadLibrary(lib); |
| } |
| Polymer._ready.complete(); |
| } |
| |
| /** |
| * Walks the HTML import structure to discover all script tags that are |
| * implicitly loaded. |
| */ |
| List<String> _discoverScripts(Document doc, String baseUri, |
| [Set<Document> seen, List<String> scripts]) { |
| if (seen == null) seen = new Set<Document>(); |
| if (scripts == null) scripts = <String>[]; |
| if (seen.contains(doc)) return scripts; |
| seen.add(doc); |
| |
| var inlinedScriptCount = 0; |
| for (var node in doc.queryAll('script,link[rel="import"]')) { |
| if (node is LinkElement) { |
| _discoverScripts(node.import, node.href, seen, scripts); |
| } else if (node is ScriptElement && node.type == 'application/dart') { |
| var url = node.src; |
| if (url != '') { |
| // TODO(sigmund): consider either normalizing package: urls or add a |
| // warning to let users know about cannonicalization issues. |
| scripts.add(url); |
| } else { |
| // We generate a unique identifier for inlined scripts which we later |
| // translate to the unique identifiers used by Dartium. Dartium uses |
| // line/column number information which we can't compute here. |
| scripts.add('$baseUri:$inlinedScriptCount'); |
| inlinedScriptCount++; |
| } |
| } |
| } |
| return scripts; |
| } |
| |
| /** All libraries in the current isolate. */ |
| final _libs = currentMirrorSystem().libraries; |
| |
| // TODO(sigmund): explore other (cheaper) ways to resolve URIs relative to the |
| // root library (see dartbug.com/12612) |
| final _rootUri = currentMirrorSystem().isolate.rootLibrary.uri; |
| |
| /** Regex that matches urls used to represent inlined scripts. */ |
| final RegExp _inlineScriptRegExp = new RegExp('\(.*\.html.*\):\([0-9]\+\)'); |
| |
| /** |
| * Map URLs fabricated by polymer to URLs fabricated by Dartium to represent |
| * inlined scripts. Polymer uses baseUri:script#, Dartium uses baseUri:line# |
| */ |
| // TODO(sigmund): figure out if we can generate the same URL and expose it. |
| final Map<Uri, List<Uri>> _inlinedScriptMapping = () { |
| var map = {}; |
| for (var uri in _libs.keys) { |
| var uriString = uri.toString(); |
| var match = _inlineScriptRegExp.firstMatch(uriString); |
| if (match == null) continue; |
| var baseUri = Uri.parse(match.group(1)); |
| if (map[baseUri] == null) map[baseUri] = []; |
| map[baseUri].add(uri); |
| } |
| return map; |
| }(); |
| |
| /** Returns a new Uri that replaces [path] in [uri]. */ |
| Uri _replacePath(Uri uri, String path) { |
| return new Uri(scheme: uri.scheme, host: uri.host, port: uri.port, |
| path: path, query: uri.query, fragment: uri.fragment); |
| } |
| |
| /** Returns the Uri in [href] without query parameters or fragments. */ |
| String _baseUri(String href) { |
| var uri = Uri.parse(window.location.href); |
| var trimUri = new Uri(scheme: uri.scheme, host: uri.host, |
| port: uri.port, path: uri.path); |
| return trimUri.toString(); |
| } |
| |
| /** |
| * Reads the library at [uriString] (which can be an absolute URI or a relative |
| * URI from the root library), and: |
| * |
| * * If present, invokes any top-level and static functions marked |
| * with the [initMethod] annotation (in the order they appear). |
| * |
| * * Registers any [PolymerElement] that is marked with the [CustomTag] |
| * annotation. |
| */ |
| void _loadLibrary(String uriString) { |
| var uri = _rootUri.resolve(uriString); |
| var lib; |
| var match = _inlineScriptRegExp.firstMatch(uriString); |
| if (match != null) { |
| var baseUri = Uri.parse(match.group(1)); |
| var list = _inlinedScriptMapping[baseUri]; |
| var pos = int.parse(match.group(2), onError: (_) => -1); |
| if (list != null && pos >= 0 && pos < list.length && list[pos] != null) { |
| lib = _libs[list[pos]]; |
| } |
| } else { |
| lib = _libs[uri]; |
| } |
| if (lib == null) { |
| print('warning: $uri library not found'); |
| return; |
| } |
| |
| // Search top-level functions marked with @initMethod |
| for (var f in lib.functions.values) { |
| _maybeInvoke(lib, f); |
| } |
| |
| for (var c in lib.classes.values) { |
| // Search for @CustomTag on classes |
| for (var m in c.metadata) { |
| var meta = m.reflectee; |
| if (meta is CustomTag) { |
| Polymer.register(meta.tagName, getReflectedTypeWorkaround(c)); |
| } |
| } |
| |
| // TODO(sigmund): check also static methods marked with @initMethod. |
| // This is blocked on two bugs: |
| // - dartbug.com/12133 (static methods are incorrectly listed as top-level |
| // in dart2js, so they end up being called twice) |
| // - dartbug.com/12134 (sometimes "method.metadata" throws an exception, |
| // we could wrap and hide those exceptions, but it's not ideal). |
| } |
| } |
| |
| void _maybeInvoke(ObjectMirror obj, MethodMirror method) { |
| var annotationFound = false; |
| for (var meta in method.metadata) { |
| if (identical(meta.reflectee, initMethod)) { |
| annotationFound = true; |
| break; |
| } |
| } |
| if (!annotationFound) return; |
| if (!method.isStatic) { |
| print("warning: methods marked with @initMethod should be static," |
| " ${method.simpleName} is not."); |
| return; |
| } |
| if (!method.parameters.where((p) => !p.isOptional).isEmpty) { |
| print("warning: methods marked with @initMethod should take no " |
| "arguments, ${method.simpleName} expects some."); |
| return; |
| } |
| obj.invoke(method.simpleName, const []); |
| } |
| |
| class _InitMethodAnnotation { |
| const _InitMethodAnnotation(); |
| } |