diff --git a/examples/smoketest-webworker/index.html b/examples/smoketest-webworker/index.html
new file mode 100644
index 0000000..39bfc07
--- /dev/null
+++ b/examples/smoketest-webworker/index.html
@@ -0,0 +1,20 @@
+
+
+
+ JS Marketplace: Smoketest with Web Workers
+
+
+
+
+
+
+
+ Smoketest with Web Workers
+
+
+
diff --git a/examples/smoketest-webworker/index.js b/examples/smoketest-webworker/index.js
new file mode 100644
index 0000000..5ec3112
--- /dev/null
+++ b/examples/smoketest-webworker/index.js
@@ -0,0 +1,46 @@
+var G;
+$(document).ready(function () {
+ var World = Minimart.World;
+ var Actor = Minimart.Actor;
+ var sub = Minimart.sub;
+ var pub = Minimart.pub;
+ var __ = Minimart.__;
+ var _$ = Minimart._$;
+
+ G = new Minimart.Ground(function () {
+ console.log('starting ground boot');
+
+ World.spawn({
+ name: 'GestaltDisplay',
+ handleEvent: function (e) {
+ if (e.type === "routes") {
+ var gd = document.getElementById('gestalt-display');
+ var t = document.createTextNode(G.world.textProcessTree() + '\n' +
+ e.gestalt.pretty());
+ gd.innerHTML = '';
+ gd.appendChild(t);
+ }
+ }
+ }, [sub(__, 0, 10), pub(__, 0, 10)]);
+
+ World.spawn(new Actor(function () {
+ this.counter = 0;
+ this.step = function () {
+ if (this.listenerExists && this.counter < 10) {
+ World.send(["beep", this.counter++]);
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ Actor.advertise(function () { return ["beep", __]; });
+ Actor.observeSubscribers(
+ function () { return ["beep", __]; },
+ { presence: "listenerExists" });
+ }));
+
+ World.spawn(new Minimart.Worker("worker.js"));
+ });
+ G.startStepping();
+});
diff --git a/examples/smoketest-webworker/worker.js b/examples/smoketest-webworker/worker.js
new file mode 100644
index 0000000..2460a18
--- /dev/null
+++ b/examples/smoketest-webworker/worker.js
@@ -0,0 +1,19 @@
+importScripts("../../dist/minimart.js");
+var World = Minimart.World;
+var Actor = Minimart.Actor;
+var sub = Minimart.sub;
+var pub = Minimart.pub;
+var __ = Minimart.__;
+var _$ = Minimart._$;
+
+new Minimart.WorkerGround(function () {
+ console.log('starting worker boot');
+ World.spawn(new Actor(function () {
+ Actor.subscribe(
+ function () { return ["beep", _$("counter")]; },
+ { metaLevel: 1 },
+ function (counter) {
+ console.log("beep!", counter);
+ });
+ }));
+}).startStepping();
diff --git a/src/main.js b/src/main.js
index f957905..7156a0f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -10,3 +10,7 @@ module.exports.Ground = require("./ground.js").Ground;
module.exports.Actor = require("./actor.js").Actor;
module.exports.Spy = require("./spy.js").Spy;
module.exports.WakeDetector = require("./wake-detector.js").WakeDetector;
+
+var Worker = require("./worker.js");
+module.exports.Worker = Worker.Worker;
+module.exports.WorkerGround = Worker.WorkerGround;
diff --git a/src/worker.js b/src/worker.js
new file mode 100644
index 0000000..407571c
--- /dev/null
+++ b/src/worker.js
@@ -0,0 +1,56 @@
+/* Web Worker interface */
+var Ground = require("./ground.js").Ground;
+var Util = require("./util.js");
+var Codec = require("./codec.js");
+
+var Minimart = require("./minimart.js");
+var World = Minimart.World;
+var sub = Minimart.sub;
+var pub = Minimart.pub;
+var __ = Minimart.__;
+
+var BuiltinWorker = typeof window !== 'undefined' && window.Worker;
+
+///////////////////////////////////////////////////////////////////////////
+
+function Worker(scriptUrl) {
+ this.scriptUrl = scriptUrl;
+ this.w = new BuiltinWorker(scriptUrl);
+}
+
+Worker.prototype.boot = function () {
+ this.w.onmessage = World.wrap(function (e) {
+ console.log("Received from worker", JSON.stringify(e.data));
+ World.current().enqueueAction(World.activePid(), Codec.decodeAction(e.data));
+ });
+};
+
+Worker.prototype.handleEvent = function (e) {
+ console.log("Sending to worker", JSON.stringify(Codec.encodeEvent(e)));
+ this.w.postMessage(Codec.encodeEvent(e));
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+function WorkerGround(bootFn) {
+ var self = this;
+ Ground.call(this, bootFn);
+ onmessage = function (e) {
+ console.log("Received from main page", JSON.stringify(e.data));
+ self.world.handleEvent(Codec.decodeEvent(e.data));
+ self.startStepping();
+ };
+}
+
+WorkerGround.prototype = Util.extend({}, Ground.prototype);
+
+WorkerGround.prototype.enqueueAction = function (pid, action) {
+ console.log("Sending to main page", JSON.stringify(Codec.encodeAction(action)));
+ postMessage(Codec.encodeAction(action));
+ console.log("Sent to main page");
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+module.exports.Worker = Worker;
+module.exports.WorkerGround = WorkerGround;