| // 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:isolate' show |
| ReceivePort, |
| SendPort; |
| |
| import 'editor.dart' show |
| addDiagnostic, |
| isMalformedInput; |
| |
| import 'run.dart' show |
| makeOutputFrame; |
| |
| import 'ui.dart' show |
| buildButton, |
| interaction, |
| outputDiv, |
| outputFrame; |
| |
| import 'settings.dart' show |
| alwaysRunInWorker, |
| alwaysRunInIframe, |
| communicateViaBlobs, |
| incrementalCompilation, |
| minified, |
| onlyAnalyze, |
| verboseCompiler; |
| |
| import 'iframe_error_handler.dart' show |
| errorStream; |
| |
| /** |
| * 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; |
| |
| // TODO(ahe): Remove this. |
| String get currentSource => window.localStorage['currentSource']; |
| |
| void set currentSource(String text) { |
| window.localStorage['currentSource'] = text; |
| } |
| |
| bool startCompilation() { |
| if (!CompilationProcess.shouldStartCompilation()) return false; |
| new CompilationProcess(currentSource, outputDiv).start(); |
| return true; |
| } |
| |
| class CompilationProcess { |
| final String source; |
| final Element console; |
| final ReceivePort receivePort = new ReceivePort(); |
| final Set<String> seenMessages = new Set<String>(); |
| bool isDone = false; |
| bool usesDartHtml = false; |
| Worker worker; |
| List<String> objectUrls = <String>[]; |
| String firstError; |
| |
| 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 start() { |
| if (!shouldStartCompilation()) { |
| receivePort.close(); |
| return; |
| } |
| if (current != null) current.dispose(); |
| current = this; |
| var options = [ |
| '--analyze-main', |
| '--no-source-maps', |
| ]; |
| if (verboseCompiler) options.add('--verbose'); |
| if (minified) options.add('--minify'); |
| if (onlyAnalyze) options.add('--analyze-only'); |
| if (incrementalCompilation.value) { |
| options.addAll(['--incremental-support', '--disable-type-inference']); |
| } |
| interaction.compilationStarting(); |
| compilerPort.send([['options', options], receivePort.sendPort]); |
| compilerPort.send([['communicateViaBlobs', communicateViaBlobs.value], |
| receivePort.sendPort]); |
| 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(_) { |
| interaction.onCompilationFailed(firstError); |
| } |
| |
| onDone(_) { |
| interaction.onCompilationDone(); |
| 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); |
| 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); |
| |
| run(wrapperUrl, () => makeOutputFrame(url)); |
| } |
| |
| // 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) { |
| IFrameElement makeIframe() { |
| // The obvious thing would be to call [makeOutputFrame], but |
| // Safari doesn't support access to Object URLs in an iframe. |
| |
| IFrameElement frame = new IFrameElement() |
| ..src = 'iframe.html' |
| ..style.width = '100%' |
| ..style.height = '0px'; |
| frame.onLoad.listen((_) { |
| frame.contentWindow.postMessage(['source', code], '*'); |
| }); |
| return frame; |
| } |
| |
| String codeWithPrint = |
| '$code\n' |
| 'function dartPrint(msg) { postMessage(msg); }\n'; |
| var url = |
| Url.createObjectUrl( |
| new Blob([codeWithPrint], 'application/javascript')); |
| objectUrls.add(url); |
| |
| run(url, makeIframe); |
| } |
| |
| void run(String url, IFrameElement makeIframe()) { |
| void retryInIframe() { |
| interaction.aboutToRun(); |
| var frame = makeIframe(); |
| frame.style |
| ..visibility = 'hidden' |
| ..position = 'absolute'; |
| outputFrame.parent.insertBefore(frame, outputFrame); |
| outputFrame = frame; |
| errorStream(frame).listen(interaction.onIframeError); |
| } |
| void onError(String errorMessage) { |
| interaction.consolePrintLine(errorMessage); |
| console |
| ..append(buildButton('Try in iframe', (_) => retryInIframe())) |
| ..appendText('\n'); |
| } |
| interaction.aboutToRun(); |
| if (alwaysRunInIframe.value || |
| usesDartHtml && !alwaysRunInWorker) { |
| retryInIframe(); |
| } else { |
| runInWorker(url, onError); |
| } |
| } |
| |
| void runInWorker(String url, void onError(String errorMessage)) { |
| worker = new Worker(url) |
| ..onMessage.listen((MessageEvent event) { |
| interaction.consolePrintLine(event.data); |
| }) |
| ..onError.listen((ErrorEvent event) { |
| worker.terminate(); |
| worker = null; |
| onError(event.message); |
| }); |
| } |
| |
| onDiagnostic(Map<String, dynamic> diagnostic) { |
| if (currentSource != source) return; |
| String kind = diagnostic['kind']; |
| String message = diagnostic['message']; |
| if (kind == 'verbose info') { |
| interaction.verboseCompilerMessage(message); |
| return; |
| } |
| if (kind == 'error' && firstError == null) { |
| firstError = message; |
| } |
| String uri = diagnostic['uri']; |
| if (uri != '${PRIVATE_SCHEME}:/main.dart') { |
| interaction.consolePrintLine('$uri: [$kind] $message'); |
| return; |
| } |
| int begin = diagnostic['begin']; |
| int end = diagnostic['end']; |
| if (begin == null) return; |
| if (seenMessages.add('$begin:$end: [$kind] $message')) { |
| // Guard against duplicated messages. |
| addDiagnostic(kind, message, begin, end); |
| } |
| } |
| |
| onCrash(data) { |
| interaction.onCompilerCrash(data); |
| } |
| } |