| // 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. |
| |
| library trydart.compilation; |
| |
| import 'dart:html' show |
| Blob, |
| Element, |
| ErrorEvent, |
| IFrameElement, |
| MessageEvent, |
| Url, |
| Worker, |
| window; |
| |
| import 'dart:async' show |
| Timer; |
| |
| import 'dart:isolate' show |
| ReceivePort, |
| SendPort; |
| |
| import 'editor.dart' show |
| addDiagnostic, |
| isMalformedInput; |
| |
| import 'run.dart' show |
| makeOutputFrame; |
| |
| import 'ui.dart' show |
| buildButton, |
| outputDiv, |
| outputFrame; |
| |
| import 'settings.dart' show |
| alwaysRunInWorker, |
| compilationPaused, |
| minified, |
| onlyAnalyze, |
| verboseCompiler; |
| |
| @lazy import 'caching_compiler.dart' as cacheCompiler; |
| |
| @lazy import 'compiler_isolate.dart'; |
| |
| // const lazy = const DeferredLibrary('compiler_isolate'); |
| const lazy = null; |
| |
| /** |
| * Scheme for recognizing files stored in memory. |
| * |
| * From http://tools.ietf.org/html/bcp35#section-2.8: |
| * |
| * Organizations that desire a private name space for URI scheme names |
| * are encouraged to use a prefix based on their domain name, expressed |
| * in reverse order. For example, a URI scheme name of com-example-info |
| * might be registered by the vendor that owns the example.com domain |
| * name. |
| */ |
| const String PRIVATE_SCHEME = 'org-trydart'; |
| |
| SendPort compilerPort; |
| Timer compilerTimer; |
| |
| // TODO(ahe): Remove this. |
| String get currentSource => window.localStorage['currentSource']; |
| |
| void set currentSource(String text) { |
| window.localStorage['currentSource'] = text; |
| } |
| |
| void scheduleCompilation() { |
| if (compilationPaused) return; |
| if (compilerTimer != null) { |
| compilerTimer.cancel(); |
| compilerTimer = null; |
| } |
| compilerTimer = |
| new Timer(const Duration(milliseconds: 500), startCompilation); |
| } |
| |
| void startCompilation() { |
| if (compilerTimer != null) { |
| compilerTimer.cancel(); |
| compilerTimer = null; |
| } |
| |
| new CompilationProcess(currentSource, outputDiv).start(); |
| } |
| |
| class CompilationProcess { |
| final String source; |
| final Element console; |
| final ReceivePort receivePort = new ReceivePort(); |
| bool isCleared = false; |
| bool isDone = false; |
| bool usesDartHtml = false; |
| Worker worker; |
| List<String> objectUrls = <String>[]; |
| |
| static CompilationProcess current; |
| |
| CompilationProcess(this.source, this.console); |
| |
| static bool shouldStartCompilation() { |
| if (compilerPort == null) return false; |
| if (isMalformedInput) return false; |
| if (current != null) return current.isDone; |
| return true; |
| } |
| |
| void clear() { |
| if (verboseCompiler) return; |
| if (!isCleared) console.nodes.clear(); |
| isCleared = true; |
| } |
| |
| void start() { |
| if (!shouldStartCompilation()) { |
| receivePort.close(); |
| if (!isMalformedInput) scheduleCompilation(); |
| return; |
| } |
| if (current != null) current.dispose(); |
| current = this; |
| console.nodes.clear(); |
| var options = []; |
| if (verboseCompiler) options.add('--verbose'); |
| if (minified) options.add('--minify'); |
| if (onlyAnalyze) options.add('--analyze-only'); |
| compilerPort.send([['options', options], receivePort.sendPort]); |
| console.appendHtml('<i class="icon-spinner icon-spin"></i>'); |
| console.appendText(' Compiling Dart program...\n'); |
| outputFrame.style.display = 'none'; |
| receivePort.listen(onMessage); |
| compilerPort.send([source, receivePort.sendPort]); |
| } |
| |
| void dispose() { |
| if (worker != null) worker.terminate(); |
| objectUrls.forEach(Url.revokeObjectUrl); |
| } |
| |
| onMessage(message) { |
| String kind = message is String ? message : message[0]; |
| var data = (message is List && message.length == 2) ? message[1] : null; |
| switch (kind) { |
| case 'done': return onDone(data); |
| case 'url': return onUrl(data); |
| case 'code': return onCode(data); |
| case 'diagnostic': return onDiagnostic(data); |
| case 'crash': return onCrash(data); |
| case 'failed': return onFail(data); |
| case 'dart:html': return onDartHtml(data); |
| default: |
| throw ['Unknown message kind', message]; |
| } |
| } |
| |
| onDartHtml(_) { |
| usesDartHtml = true; |
| } |
| |
| onFail(_) { |
| clear(); |
| consolePrint('Compilation failed'); |
| } |
| |
| onDone(_) { |
| isDone = true; |
| receivePort.close(); |
| } |
| |
| // This is called in browsers that support creating Object URLs in a |
| // web worker. For example, Chrome and Firefox 21. |
| onUrl(String url) { |
| objectUrls.add(url); |
| clear(); |
| String wrapper = ''' |
| // Fool isolate_helper.dart so it does not think this is an isolate. |
| var window = self; |
| function dartPrint(msg) { |
| self.postMessage(msg); |
| }; |
| self.importScripts("$url"); |
| '''; |
| var wrapperUrl = |
| Url.createObjectUrl(new Blob([wrapper], 'application/javascript')); |
| objectUrls.add(wrapperUrl); |
| void retryInIframe(_) { |
| var frame = makeOutputFrame(url); |
| outputFrame.replaceWith(frame); |
| outputFrame = frame; |
| console.append(buildButton('Try in iframe', retryInIframe)); |
| } |
| void onError(String errorMessage) { |
| console.appendText(errorMessage); |
| console.appendText(' '); |
| console.append(buildButton('Try in iframe', retryInIframe)); |
| console.appendText('\n'); |
| } |
| if (usesDartHtml && !alwaysRunInWorker) { |
| retryInIframe(null); |
| } else { |
| runInWorker(wrapperUrl, onError); |
| } |
| } |
| |
| // This is called in browsers that do not support creating Object |
| // URLs in a web worker. For example, Safari and Firefox < 21. |
| onCode(String code) { |
| clear(); |
| |
| void retryInIframe(_) { |
| // The obvious thing would be to call [makeOutputFrame], but |
| // Safari doesn't support access to Object URLs in an iframe. |
| |
| var frame = new IFrameElement() |
| ..src = 'iframe.html' |
| ..style.width = '100%' |
| ..style.height = '0px'; |
| frame.onLoad.listen((_) { |
| frame.contentWindow.postMessage(['source', code], '*'); |
| }); |
| outputFrame.replaceWith(frame); |
| outputFrame = frame; |
| } |
| |
| void onError(String errorMessage) { |
| console.appendText(errorMessage); |
| console.appendText(' '); |
| console.append(buildButton('Try in iframe', retryInIframe)); |
| console.appendText('\n'); |
| } |
| |
| String codeWithPrint = |
| '$code\n' |
| 'function dartPrint(msg) { postMessage(msg); }\n'; |
| var url = |
| Url.createObjectUrl( |
| new Blob([codeWithPrint], 'application/javascript')); |
| objectUrls.add(url); |
| |
| if (usesDartHtml && !alwaysRunInWorker) { |
| retryInIframe(null); |
| } else { |
| runInWorker(url, onError); |
| } |
| } |
| |
| void runInWorker(String url, void onError(String errorMessage)) { |
| worker = new Worker(url) |
| ..onMessage.listen((MessageEvent event) { |
| consolePrint(event.data); |
| }) |
| ..onError.listen((ErrorEvent event) { |
| worker.terminate(); |
| worker = null; |
| onError(event.message); |
| }); |
| } |
| |
| onDiagnostic(Map<String, dynamic> diagnostic) { |
| String kind = diagnostic['kind']; |
| String message = diagnostic['message']; |
| if (kind == 'verbose info') { |
| if (verboseCompiler) { |
| consolePrint(message); |
| } else { |
| console.appendText('.'); |
| } |
| return; |
| } |
| String uri = diagnostic['uri']; |
| if (uri == null) { |
| clear(); |
| consolePrint(message); |
| return; |
| } |
| if (uri != '${PRIVATE_SCHEME}:/main.dart') return; |
| if (currentSource != source) return; |
| int begin = diagnostic['begin']; |
| int end = diagnostic['end']; |
| if (begin == null) return; |
| addDiagnostic(kind, message, begin, end); |
| } |
| |
| onCrash(data) { |
| consolePrint(data); |
| } |
| |
| void consolePrint(message) { |
| if (window.parent != window) { |
| // Test support. |
| // TODO(ahe): Use '/' instead of '*' when Firefox is upgraded to version |
| // 30 across build bots. Support for '/' was added in version 29, and we |
| // support the two most recent versions. |
| window.parent.postMessage('$message\n', '*'); |
| } |
| console.appendText('$message\n'); |
| } |
| } |
| |
| void compilerIsolate(SendPort port) { |
| // TODO(ahe): Restore when restoring deferred loading. |
| // lazy.load().then((_) => port.listen(compile)); |
| ReceivePort replyTo = new ReceivePort(); |
| port.send(replyTo.sendPort); |
| replyTo.listen((message) { |
| List list = message as List; |
| try { |
| compile(list[0], list[1]); |
| } catch (exception, stack) { |
| port.send('$exception\n$stack'); |
| } |
| }); |
| var notTrue = false; // Confuse the analyzer. |
| if (notTrue) { |
| cacheCompiler.compilerFor(null); |
| } |
| } |