b87f38077c
R=kasperl@google.com Review URL: https://chromereviews.googleplex.com/3508021 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@32 260f80e4-7a28-3924-810f-c04153c831b5
547 lines
16 KiB
JavaScript
547 lines
16 KiB
JavaScript
// Copyright (c) 2011, 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.
|
|
|
|
var isolate$current = null;
|
|
var isolate$rootIsolate = null; // Will only be set in the main worker.
|
|
var isolate$inits = [];
|
|
var isolate$globalThis = this;
|
|
|
|
// These declarations are needed to avoid errors from the Closure Compiler
|
|
// optimizer. They are defined in client/dom/generated/dart_dom_wrapping.js.
|
|
var __dom_wrap;
|
|
var __dom_unwrap;
|
|
|
|
var isolate$inWorker =
|
|
(typeof isolate$globalThis['importScripts']) != "undefined";
|
|
var isolate$supportsWorkers =
|
|
isolate$inWorker || ((typeof isolate$globalThis['Worker']) != 'undefined');
|
|
|
|
var isolate$MAIN_WORKER_ID = 0;
|
|
// Non-main workers will update the id variable.
|
|
var isolate$thisWorkerId = isolate$MAIN_WORKER_ID;
|
|
|
|
// Whether to use web workers when implementing isolates.
|
|
var isolate$useWorkers = isolate$supportsWorkers;
|
|
// Uncomment this to not use web workers even if they're available.
|
|
// isolate$useWorkers = false;
|
|
|
|
// Whether to use the web-worker JSON-based message serialization protocol,
|
|
// even if not using web workers.
|
|
var isolate$useWorkerSerializationProtocol = false;
|
|
// Uncomment this to always use the web-worker JSON-based message
|
|
// serialization protocol, e.g. for testing purposes.
|
|
// isolate$useWorkerSerializationProtocol = true;
|
|
|
|
|
|
// ------- SendPort -------
|
|
function isolate$sendMessage(workerId, isolateId, receivePortId,
|
|
message, replyTo) {
|
|
// Both, the message and the replyTo are already serialized.
|
|
if (workerId == isolate$thisWorkerId) {
|
|
var isolate = isolate$isolateRegistry.get(isolateId);
|
|
if (!isolate) return; // Isolate has been closed.
|
|
var receivePort = isolate.getReceivePortForId(receivePortId);
|
|
if (!receivePort) return; // ReceivePort has been closed.
|
|
isolate$receiveMessage(receivePort, isolate, message, replyTo);
|
|
} else {
|
|
var worker;
|
|
if (isolate$inWorker) {
|
|
worker = isolate$mainWorker;
|
|
} else {
|
|
worker = isolate$workerRegistry.get(workerId);
|
|
}
|
|
worker.postMessage({ command: 'message',
|
|
workerId: workerId,
|
|
isolateId: isolateId,
|
|
portId: receivePortId,
|
|
msg: message,
|
|
replyTo: replyTo });
|
|
}
|
|
}
|
|
|
|
function isolate$receiveMessage(port, isolate,
|
|
serializedMessage, serializedReplyTo) {
|
|
isolate$IsolateEvent.enqueue(isolate, function() {
|
|
var message = isolate$deserializeMessage(serializedMessage);
|
|
var replyTo = isolate$deserializeMessage(serializedReplyTo);
|
|
native_ReceivePortImpl__invokeCallback(port, message, replyTo);
|
|
native__IsolateJsUtil__promiseQueueProcess();
|
|
});
|
|
}
|
|
|
|
// ------- ReceivePort -------
|
|
|
|
function native_ReceivePortImpl__register(id) {
|
|
isolate$current.registerReceivePort(id, this);
|
|
}
|
|
|
|
function native_ReceivePortImpl__unregister(id) {
|
|
isolate$current.unregisterReceivePort(id);
|
|
}
|
|
|
|
function native_ReceivePortImpl__currentWorkerId() {
|
|
return isolate$thisWorkerId;
|
|
}
|
|
|
|
function native_ReceivePortImpl__currentIsolateId() {
|
|
return isolate$current.id;
|
|
}
|
|
|
|
// -------- Registry ---------
|
|
function isolate$Registry() {
|
|
this.map = {};
|
|
this.count = 0;
|
|
}
|
|
|
|
isolate$Registry.prototype.register = function(id, val) {
|
|
if (this.map[id]) {
|
|
throw Error("Registry: Elements must be registered only once.");
|
|
}
|
|
this.map[id] = val;
|
|
this.count++;
|
|
};
|
|
|
|
isolate$Registry.prototype.unregister = function(id) {
|
|
if (id in this.map) {
|
|
delete this.map[id];
|
|
this.count--;
|
|
}
|
|
};
|
|
|
|
isolate$Registry.prototype.get = function(id) {
|
|
return this.map[id];
|
|
};
|
|
|
|
isolate$Registry.prototype.isEmpty = function() {
|
|
return this.count === 0;
|
|
};
|
|
|
|
|
|
// ------- Worker registry -------
|
|
// Only used in the main worker.
|
|
var isolate$workerRegistry = new isolate$Registry();
|
|
|
|
// ------- Isolate registry -------
|
|
// Isolates must be registered if, and only if, receive ports are alive.
|
|
// Normally no open receive-ports means that the isolate is dead, but
|
|
// DOM callbacks could resurrect it.
|
|
var isolate$isolateRegistry = new isolate$Registry();
|
|
|
|
// ------- Debugging log function -------
|
|
function isolate$log(msg) {
|
|
return;
|
|
if (isolate$inWorker) {
|
|
isolate$mainWorker.postMessage({ command: 'log', msg: msg });
|
|
} else {
|
|
try {
|
|
isolate$globalThis.console.log(msg);
|
|
} catch(e) {
|
|
throw String(e.stack);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isolate$initializeWorker(workerId) {
|
|
isolate$thisWorkerId = workerId;
|
|
}
|
|
|
|
var isolate$workerPrint = false;
|
|
if (isolate$inWorker) {
|
|
isolate$workerPrint = function(msg){
|
|
isolate$mainWorker.postMessage({ command: 'print', msg: msg });
|
|
}
|
|
}
|
|
|
|
// ------- Message handler -------
|
|
function isolate$processWorkerMessage(sender, e) {
|
|
var msg = e.data;
|
|
switch (msg.command) {
|
|
case 'start':
|
|
isolate$log("starting worker: " + msg.id + " " + msg.factoryName);
|
|
isolate$initializeWorker(msg.id);
|
|
var runnerObject = (isolate$globalThis[msg.factoryName])();
|
|
var serializedReplyTo = msg.replyTo;
|
|
isolate$IsolateEvent.enqueue(new isolate$Isolate(), function() {
|
|
var replyTo = isolate$deserializeMessage(serializedReplyTo);
|
|
native__IsolateJsUtil__startIsolate(runnerObject, replyTo);
|
|
});
|
|
isolate$runEventLoop();
|
|
break;
|
|
case 'spawn-worker':
|
|
isolate$spawnWorker(msg.factoryName, msg.replyPort);
|
|
break;
|
|
case 'message':
|
|
isolate$sendMessage(msg.workerId, msg.isolateId, msg.portId,
|
|
msg.msg, msg.replyTo);
|
|
isolate$runEventLoop();
|
|
break;
|
|
case 'close':
|
|
isolate$log("Closing Worker");
|
|
isolate$workerRegistry.unregister(sender.id);
|
|
sender.terminate();
|
|
isolate$runEventLoop();
|
|
break;
|
|
case 'log':
|
|
isolate$log(msg.msg);
|
|
break;
|
|
case 'print':
|
|
native__IsolateJsUtil__print(msg.msg);
|
|
break;
|
|
case 'error':
|
|
throw msg.msg;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isolate$supportsWorkers) {
|
|
isolate$globalThis.onmessage = function(e) {
|
|
isolate$processWorkerMessage(isolate$mainWorker, e);
|
|
};
|
|
}
|
|
|
|
// ------- Default Worker -------
|
|
function isolate$MainWorker() {
|
|
this.id = isolate$MAIN_WORKER_ID;
|
|
}
|
|
|
|
var isolate$mainWorker = new isolate$MainWorker();
|
|
isolate$mainWorker.postMessage = function(msg) {
|
|
isolate$globalThis.postMessage(msg);
|
|
};
|
|
|
|
var isolate$nextFreeIsolateId = 1;
|
|
|
|
// Native methods for isolate functionality.
|
|
/**
|
|
* @constructor
|
|
*/
|
|
function isolate$Isolate() {
|
|
// The isolate ids is only unique within the current worker and frame.
|
|
this.id = isolate$nextFreeIsolateId++;
|
|
// When storing information on DOM nodes the isolate's id is not enough.
|
|
// We instead use a token with a hashcode. The token can be stored in the
|
|
// DOM node (since it is small and will not keep much data alive).
|
|
this.token = new Object();
|
|
this.token.hashCode = (Math.random() * 0xFFFFFFF) >>> 0;
|
|
this.receivePorts = new isolate$Registry();
|
|
this.run(function() {
|
|
// The Dart-to-JavaScript compiler builds a list of functions that
|
|
// need to run for each isolate to setup the state of static
|
|
// variables. Run through the list and execute each function.
|
|
for (var i = 0, len = isolate$inits.length; i < len; i++) {
|
|
isolate$inits[i]();
|
|
}
|
|
});
|
|
}
|
|
|
|
// It is allowed to stack 'run' calls. The stacked isolates can be different.
|
|
// That is Isolate1.run could call the DOM which then calls Isolate2.run.
|
|
isolate$Isolate.prototype.run = function(code) {
|
|
var old = isolate$current;
|
|
isolate$current = this;
|
|
var result = null;
|
|
try {
|
|
result = code();
|
|
} finally {
|
|
isolate$current = old;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
isolate$Isolate.prototype.registerReceivePort = function(id, port) {
|
|
if (this.receivePorts.isEmpty()) {
|
|
isolate$isolateRegistry.register(this.id, this);
|
|
}
|
|
this.receivePorts.register(id, port);
|
|
};
|
|
|
|
isolate$Isolate.prototype.unregisterReceivePort = function(id) {
|
|
this.receivePorts.unregister(id);
|
|
if (this.receivePorts.isEmpty()) {
|
|
isolate$isolateRegistry.unregister(this.id);
|
|
}
|
|
};
|
|
|
|
isolate$Isolate.prototype.getReceivePortForId = function(id) {
|
|
return this.receivePorts.get(id);
|
|
};
|
|
|
|
var isolate$events = [];
|
|
|
|
/**
|
|
* @constructor
|
|
*/
|
|
function isolate$IsolateEvent(isolate, fn) {
|
|
this.isolate = isolate;
|
|
this.fn = fn;
|
|
}
|
|
|
|
isolate$IsolateEvent.prototype.process = function() {
|
|
this.isolate.run(this.fn);
|
|
};
|
|
|
|
isolate$IsolateEvent.enqueue = function(isolate, fn) {
|
|
isolate$events.push(new isolate$IsolateEvent(isolate, fn));
|
|
};
|
|
|
|
isolate$IsolateEvent.dequeue = function() {
|
|
if (isolate$events.length == 0) return $Dart$Null;
|
|
var result = isolate$events[0];
|
|
isolate$events.splice(0, 1);
|
|
return result;
|
|
};
|
|
|
|
var isolate$lightCount = 0;
|
|
var isolate$heavyCount = 0;
|
|
|
|
function native_IsolateNatives__spawn(runnable, light, replyPort) {
|
|
// TODO(kasperl): Allow mixing heavy and light isolates.
|
|
if (light) {
|
|
isolate$lightCount++;
|
|
if (isolate$heavyCount != 0) {
|
|
throw Error("Cannot mix light and heavy isolates.");
|
|
}
|
|
isolate$useWorkers = false;
|
|
} else {
|
|
isolate$heavyCount++;
|
|
if (isolate$lightCount != 0) {
|
|
throw Error("Cannot mix light and heavy isolates.");
|
|
}
|
|
}
|
|
|
|
// TODO(floitsch): throw exception if runnable's class doesn't have a
|
|
// default constructor.
|
|
if (isolate$useWorkers) {
|
|
isolate$startWorker(runnable, replyPort);
|
|
} else {
|
|
isolate$startNonWorker(runnable, replyPort);
|
|
}
|
|
}
|
|
|
|
function native_IsolateNatives_bind(fn) {
|
|
var isolate = isolate$current;
|
|
return function() {
|
|
var self = this;
|
|
var args = arguments;
|
|
isolate.run(function() {
|
|
fn.apply(self, args);
|
|
});
|
|
isolate$runEventLoop();
|
|
};
|
|
}
|
|
|
|
function isolate$startNonWorker(runnable, replyTo) {
|
|
// Spawn a new isolate and create the receive port in it.
|
|
var spawned = new isolate$Isolate();
|
|
|
|
// Instead of just running the provided runnable, we create a
|
|
// new cloned instance of it with a fresh state in the spawned
|
|
// isolate. This way, we do not get cross-isolate references
|
|
// through the runnable.
|
|
var factory = runnable.getIsolateFactory();
|
|
isolate$IsolateEvent.enqueue(spawned, function() {
|
|
native__IsolateJsUtil__startIsolate(factory(), replyTo);
|
|
});
|
|
}
|
|
|
|
// This field is only used by the main worker.
|
|
var isolate$nextFreeWorkerId = isolate$thisWorkerId + 1;
|
|
|
|
var isolate$thisScript = function() {
|
|
if (!isolate$supportsWorkers || isolate$inWorker) return null;
|
|
|
|
// TODO(5334778): Find a cross-platform non-brittle way of getting the
|
|
// currently running script.
|
|
var scripts = document.getElementsByTagName('script');
|
|
// The scripts variable only contains the scripts that have already been
|
|
// executed. The last one is the currently running script.
|
|
var script = scripts[scripts.length - 1];
|
|
var src = script.src;
|
|
if (!src) {
|
|
// TODO()
|
|
src = "FIXME:5407062" + "_" + Math.random().toString();
|
|
script.src = src;
|
|
}
|
|
return src;
|
|
}();
|
|
|
|
function isolate$startWorker(runnable, replyPort) {
|
|
var factory = runnable.getIsolateFactory();
|
|
var factoryName = factory.name;
|
|
var serializedReplyPort = isolate$serializeMessage(replyPort);
|
|
if (isolate$inWorker) {
|
|
isolate$mainWorker.postMessage({ command: 'spawn-worker',
|
|
factoryName: factoryName,
|
|
replyPort: serializedReplyPort } );
|
|
} else {
|
|
isolate$spawnWorker(factoryName, serializedReplyPort);
|
|
}
|
|
}
|
|
|
|
function isolate$spawnWorker(factoryName, serializedReplyPort) {
|
|
var worker = new Worker(isolate$thisScript);
|
|
worker.onmessage = function(e) {
|
|
isolate$processWorkerMessage(worker, e);
|
|
};
|
|
var workerId = isolate$nextFreeWorkerId++;
|
|
// We also store the id on the worker itself so that we can unregister it.
|
|
worker.id = workerId;
|
|
isolate$workerRegistry.register(workerId, worker);
|
|
worker.postMessage({ command: 'start',
|
|
id: workerId,
|
|
replyTo: serializedReplyPort,
|
|
factoryName: factoryName });
|
|
}
|
|
|
|
function native_SendPortImpl__sendNow(message, replyTo) {
|
|
if (replyTo !== $Dart$Null && !(replyTo instanceof SendPortImpl$Dart)) {
|
|
throw "SendPort::send: Illegal replyTo type.";
|
|
}
|
|
message = isolate$serializeMessage(message);
|
|
replyTo = isolate$serializeMessage(replyTo);
|
|
var workerId = native_SendPortImpl__getWorkerId(this);
|
|
var isolateId = native_SendPortImpl__getIsolateId(this);
|
|
var receivePortId = native_SendPortImpl__getReceivePortId(this);
|
|
isolate$sendMessage(workerId, isolateId, receivePortId, message, replyTo);
|
|
}
|
|
|
|
function isolate$closeWorkerIfNecessary() {
|
|
if (!isolate$isolateRegistry.isEmpty()) return;
|
|
isolate$mainWorker.postMessage( { command: 'close' } );
|
|
}
|
|
|
|
function isolate$doOneEventLoopIteration() {
|
|
var CONTINUE_LOOP = true;
|
|
var STOP_LOOP = false;
|
|
var event = isolate$IsolateEvent.dequeue();
|
|
if (!event) {
|
|
if (isolate$inWorker) {
|
|
isolate$closeWorkerIfNecessary();
|
|
} else if (!isolate$isolateRegistry.isEmpty() &&
|
|
isolate$workerRegistry.isEmpty() &&
|
|
!isolate$supportsWorkers && (typeof(window) == 'undefined')) {
|
|
// This should only trigger when running on the command-line.
|
|
// We don't want this check to execute in the browser where the isolate
|
|
// might still be alive due to DOM callbacks.
|
|
// throw Error("Program exited with open ReceivePorts.");
|
|
}
|
|
return STOP_LOOP;
|
|
} else {
|
|
event.process();
|
|
return CONTINUE_LOOP;
|
|
}
|
|
}
|
|
|
|
function isolate$doRunEventLoop() {
|
|
if (typeof window != 'undefined' && window.setTimeout) {
|
|
(function next() {
|
|
var continueLoop = isolate$doOneEventLoopIteration();
|
|
if (!continueLoop) return;
|
|
// TODO(kasperl): It might turn out to be too expensive to call
|
|
// setTimeout for every single event. This needs more investigation.
|
|
window.setTimeout(next, 0);
|
|
})();
|
|
} else {
|
|
while (true) {
|
|
var continueLoop = isolate$doOneEventLoopIteration();
|
|
if (!continueLoop) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function isolate$runEventLoop() {
|
|
if (!isolate$inWorker) {
|
|
isolate$doRunEventLoop();
|
|
} else {
|
|
try {
|
|
isolate$doRunEventLoop();
|
|
} catch(e) {
|
|
// TODO(floitsch): try to send stack-trace to the other side.
|
|
isolate$mainWorker.postMessage({ command: 'error', msg: "" + e });
|
|
}
|
|
}
|
|
}
|
|
|
|
function RunEntry(entry, args) {
|
|
// Don't start the main loop again, if we are in a worker.
|
|
if (isolate$inWorker) return;
|
|
var isolate = new isolate$Isolate();
|
|
isolate$rootIsolate = isolate;
|
|
isolate$IsolateEvent.enqueue(isolate, function() {
|
|
entry(args);
|
|
});
|
|
isolate$runEventLoop();
|
|
|
|
// BUG(5151491): This should not be necessary, but because closures
|
|
// passed to the DOM as event handlers do not bind their isolate
|
|
// automatically we try to give them a reasonable context to live in
|
|
// by having a "default" isolate (the first one created).
|
|
isolate$current = isolate;
|
|
}
|
|
|
|
// ------- Message Serializing and Deserializing -------
|
|
|
|
function native_MessageTraverser__clearAttachedInfo(o) {
|
|
o['__MessageTraverser__attached_info__'] = (void 0);
|
|
}
|
|
|
|
function native_MessageTraverser__setAttachedInfo(o, info) {
|
|
o['__MessageTraverser__attached_info__'] = info;
|
|
}
|
|
|
|
function native_MessageTraverser__getAttachedInfo(o) {
|
|
return o['__MessageTraverser__attached_info__'];
|
|
}
|
|
|
|
function native_Serializer__newJsArray(len) {
|
|
return new Array(len);
|
|
}
|
|
|
|
function native_Serializer__jsArrayIndexSet(jsArray, index, val) {
|
|
jsArray[index] = val;
|
|
}
|
|
|
|
function native_Serializer__dartListToJsArrayNoCopy(list) {
|
|
if (list instanceof Array) {
|
|
RTT.removeTypeInfo(list);
|
|
return list;
|
|
} else {
|
|
var len = native__ArrayJsUtil__arrayLength(list);
|
|
var array = new Array(len);
|
|
for (var i = 0; i < len; i++) {
|
|
array[i] = INDEX$operator(list, i);
|
|
}
|
|
return array;
|
|
}
|
|
}
|
|
|
|
function native_Deserializer__isJsArray(x) {
|
|
return x instanceof Array;
|
|
}
|
|
|
|
function native_Deserializer__jsArrayIndex(x, index) {
|
|
return x[index];
|
|
}
|
|
|
|
function native_Deserializer__jsArrayLength(x) {
|
|
return x.length;
|
|
}
|
|
|
|
function isolate$serializeMessage(message) {
|
|
if (isolate$useWorkers || isolate$useWorkerSerializationProtocol) {
|
|
return native__IsolateJsUtil__serializeObject(message);
|
|
} else {
|
|
return native__IsolateJsUtil__copyObject(message);
|
|
}
|
|
}
|
|
|
|
function isolate$deserializeMessage(message) {
|
|
if (isolate$useWorkers || isolate$useWorkerSerializationProtocol) {
|
|
return native__IsolateJsUtil__deserializeMessage(message);
|
|
} else {
|
|
// Nothing more to do.
|
|
return message;
|
|
}
|
|
}
|