--- a/devtools/server/actors/child-process.js
+++ b/devtools/server/actors/child-process.js
@@ -9,17 +9,17 @@ const Services = require("Services");
const { ChromeDebuggerActor } = require("devtools/server/actors/thread");
const { WebConsoleActor } = require("devtools/server/actors/webconsole");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
const { ActorPool } = require("devtools/server/main");
const { assert } = require("devtools/shared/DevToolsUtils");
const { TabSources } = require("./utils/TabSources");
-loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
+loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker/worker-list", true);
function ChildProcessActor(connection) {
this.conn = connection;
this._contextPool = new ActorPool(this.conn);
this.conn.addActorPool(this._contextPool);
this.threadActor = null;
// Use a see-everything debugger
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -68,17 +68,16 @@ DevToolsModules(
'webaudio.js',
'webbrowser.js',
'webconsole.js',
'webextension-inspected-window.js',
'webextension-parent.js',
'webextension.js',
'webgl.js',
'window.js',
- 'worker-list.js',
'worker.js',
)
with Files('animation.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Animation Inspector')
with Files('breakpoint.js'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -34,17 +34,17 @@ const InspectorUtils = require("Inspecto
const EXTENSION_CONTENT_JSM = "resource://gre/modules/ExtensionContent.jsm";
const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
const { browsingContextTargetSpec } = require("devtools/shared/specs/targets/browsing-context");
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
-loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
+loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker/worker-list", true);
loader.lazyImporter(this, "ExtensionContent", EXTENSION_CONTENT_JSM);
loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
loader.lazyRequireGetter(this, "getSheetText", "devtools/server/actors/stylesheets", true);
function getWindowID(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -10,18 +10,18 @@ var { Ci } = require("chrome");
var Services = require("Services");
var { DebuggerServer } = require("devtools/server/main");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
loader.lazyRequireGetter(this, "FrameTargetActorProxy", "devtools/server/actors/targets/frame-proxy", true);
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
loader.lazyRequireGetter(this, "WebExtensionParentActor", "devtools/server/actors/webextension-parent", true);
-loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
-loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker-list", true);
+loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker/worker-list", true);
+loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker/worker-list", true);
loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
/**
* Browser-specific actors.
*/
/**
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -2,40 +2,28 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci } = require("chrome");
const ChromeUtils = require("ChromeUtils");
const { DebuggerServer } = require("devtools/server/main");
-const Services = require("Services");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const protocol = require("devtools/shared/protocol");
-const {
- workerSpec,
- pushSubscriptionSpec,
- serviceWorkerRegistrationSpec,
- serviceWorkerSpec,
-} = require("devtools/shared/specs/worker");
+const { workerSpec } = require("devtools/shared/specs/worker/worker");
loader.lazyRequireGetter(this, "ChromeUtils");
XPCOMUtils.defineLazyServiceGetter(
this, "swm",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager"
);
-XPCOMUtils.defineLazyServiceGetter(
- this, "PushService",
- "@mozilla.org/push/Service;1",
- "nsIPushService"
-);
-
const WorkerActor = protocol.ActorClassWithSpec(workerSpec, {
initialize(conn, dbg) {
protocol.Actor.prototype.initialize.call(this, conn);
this._dbg = dbg;
this._attached = false;
this._threadActor = null;
this._transport = null;
},
@@ -187,231 +175,8 @@ const WorkerActor = protocol.ActorClassW
}
this._dbg.removeListener(this);
this._attached = false;
}
});
exports.WorkerActor = WorkerActor;
-
-const PushSubscriptionActor = protocol.ActorClassWithSpec(pushSubscriptionSpec, {
- initialize(conn, subscription) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._subscription = subscription;
- },
-
- form(detail) {
- if (detail === "actorid") {
- return this.actorID;
- }
- const subscription = this._subscription;
- return {
- actor: this.actorID,
- endpoint: subscription.endpoint,
- pushCount: subscription.pushCount,
- lastPush: subscription.lastPush,
- quota: subscription.quota
- };
- },
-
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- this._subscription = null;
- },
-});
-
-const ServiceWorkerActor = protocol.ActorClassWithSpec(serviceWorkerSpec, {
- initialize(conn, worker) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._worker = worker;
- },
-
- form() {
- if (!this._worker) {
- return null;
- }
-
- return {
- url: this._worker.scriptSpec,
- state: this._worker.state,
- fetch: this._worker.handlesFetchEvents
- };
- },
-
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- this._worker = null;
- },
-});
-
-// Lazily load the service-worker-process.js process script only once.
-let _serviceWorkerProcessScriptLoaded = false;
-
-const ServiceWorkerRegistrationActor =
-protocol.ActorClassWithSpec(serviceWorkerRegistrationSpec, {
- /**
- * Create the ServiceWorkerRegistrationActor
- * @param DebuggerServerConnection conn
- * The server connection.
- * @param ServiceWorkerRegistrationInfo registration
- * The registration's information.
- */
- initialize(conn, registration) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._conn = conn;
- this._registration = registration;
- this._pushSubscriptionActor = null;
- this._registration.addListener(this);
-
- const {installingWorker, waitingWorker, activeWorker} = registration;
- this._installingWorker = new ServiceWorkerActor(conn, installingWorker);
- this._waitingWorker = new ServiceWorkerActor(conn, waitingWorker);
- this._activeWorker = new ServiceWorkerActor(conn, activeWorker);
-
- Services.obs.addObserver(this, PushService.subscriptionModifiedTopic);
- },
-
- onChange() {
- this._installingWorker.destroy();
- this._waitingWorker.destroy();
- this._activeWorker.destroy();
-
- const {installingWorker, waitingWorker, activeWorker} = this._registration;
- this._installingWorker = new ServiceWorkerActor(this._conn, installingWorker);
- this._waitingWorker = new ServiceWorkerActor(this._conn, waitingWorker);
- this._activeWorker = new ServiceWorkerActor(this._conn, activeWorker);
-
- this.emit("registration-changed");
- },
-
- form(detail) {
- if (detail === "actorid") {
- return this.actorID;
- }
- const registration = this._registration;
- const installingWorker = this._installingWorker.form();
- const waitingWorker = this._waitingWorker.form();
- const activeWorker = this._activeWorker.form();
-
- const newestWorker = (activeWorker || waitingWorker || installingWorker);
-
- const isE10s = Services.appinfo.browserTabsRemoteAutostart;
- return {
- actor: this.actorID,
- scope: registration.scope,
- url: registration.scriptSpec,
- installingWorker,
- waitingWorker,
- activeWorker,
- fetch: newestWorker && newestWorker.fetch,
- // - In e10s: only active registrations are available.
- // - In non-e10s: registrations always have at least one worker, if the worker is
- // active, the registration is active.
- active: isE10s ? true : !!activeWorker,
- lastUpdateTime: registration.lastUpdateTime,
- };
- },
-
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic);
- this._registration.removeListener(this);
- this._registration = null;
- if (this._pushSubscriptionActor) {
- this._pushSubscriptionActor.destroy();
- }
- this._pushSubscriptionActor = null;
-
- this._installingWorker.destroy();
- this._waitingWorker.destroy();
- this._activeWorker.destroy();
-
- this._installingWorker = null;
- this._waitingWorker = null;
- this._activeWorker = null;
- },
-
- /**
- * Standard observer interface to listen to push messages and changes.
- */
- observe(subject, topic, data) {
- const scope = this._registration.scope;
- if (data !== scope) {
- // This event doesn't concern us, pretend nothing happened.
- return;
- }
- switch (topic) {
- case PushService.subscriptionModifiedTopic:
- if (this._pushSubscriptionActor) {
- this._pushSubscriptionActor.destroy();
- this._pushSubscriptionActor = null;
- }
- this.emit("push-subscription-modified");
- break;
- }
- },
-
- start() {
- if (!_serviceWorkerProcessScriptLoaded) {
- Services.ppmm.loadProcessScript(
- "resource://devtools/server/actors/worker/service-worker-process.js", true);
- _serviceWorkerProcessScriptLoaded = true;
- }
-
- // XXX: Send the permissions down to the content process before starting
- // the service worker within the content process. As we don't know what
- // content process we're starting the service worker in (as we're using a
- // broadcast channel to talk to it), we just broadcast the permissions to
- // everyone as well.
- //
- // This call should be replaced with a proper implementation when
- // ServiceWorker debugging is improved to support multiple content processes
- // correctly.
- Services.perms.broadcastPermissionsForPrincipalToAllContentProcesses(
- this._registration.principal);
-
- Services.ppmm.broadcastAsyncMessage("serviceWorkerRegistration:start", {
- scope: this._registration.scope
- });
- return { type: "started" };
- },
-
- unregister() {
- const { principal, scope } = this._registration;
- const unregisterCallback = {
- unregisterSucceeded: function() {},
- unregisterFailed: function() {
- console.error("Failed to unregister the service worker for " + scope);
- },
- QueryInterface: ChromeUtils.generateQI(
- [Ci.nsIServiceWorkerUnregisterCallback])
- };
- swm.propagateUnregister(principal, unregisterCallback, scope);
-
- return { type: "unregistered" };
- },
-
- getPushSubscription() {
- const registration = this._registration;
- let pushSubscriptionActor = this._pushSubscriptionActor;
- if (pushSubscriptionActor) {
- return Promise.resolve(pushSubscriptionActor);
- }
- return new Promise((resolve, reject) => {
- PushService.getSubscription(
- registration.scope,
- registration.principal,
- (result, subscription) => {
- if (!subscription) {
- resolve(null);
- return;
- }
- pushSubscriptionActor = new PushSubscriptionActor(this._conn, subscription);
- this._pushSubscriptionActor = pushSubscriptionActor;
- resolve(pushSubscriptionActor);
- }
- );
- });
- },
-});
-
-exports.ServiceWorkerRegistrationActor = ServiceWorkerRegistrationActor;
--- a/devtools/server/actors/worker/moz.build
+++ b/devtools/server/actors/worker/moz.build
@@ -1,9 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'service-worker-process.js',
+ 'service-worker.js',
+ 'worker-list.js',
)
copy from devtools/server/actors/worker.js
copy to devtools/server/actors/worker/service-worker.js
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker/service-worker.js
@@ -1,203 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci } = require("chrome");
const ChromeUtils = require("ChromeUtils");
-const { DebuggerServer } = require("devtools/server/main");
const Services = require("Services");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const protocol = require("devtools/shared/protocol");
const {
- workerSpec,
pushSubscriptionSpec,
serviceWorkerRegistrationSpec,
serviceWorkerSpec,
-} = require("devtools/shared/specs/worker");
+} = require("devtools/shared/specs/worker/service-worker");
loader.lazyRequireGetter(this, "ChromeUtils");
XPCOMUtils.defineLazyServiceGetter(
this, "swm",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager"
);
XPCOMUtils.defineLazyServiceGetter(
this, "PushService",
"@mozilla.org/push/Service;1",
"nsIPushService"
);
-const WorkerActor = protocol.ActorClassWithSpec(workerSpec, {
- initialize(conn, dbg) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._dbg = dbg;
- this._attached = false;
- this._threadActor = null;
- this._transport = null;
- },
-
- form(detail) {
- if (detail === "actorid") {
- return this.actorID;
- }
- const form = {
- actor: this.actorID,
- consoleActor: this._consoleActor,
- url: this._dbg.url,
- type: this._dbg.type
- };
- if (this._dbg.type === Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- const registration = this._getServiceWorkerRegistrationInfo();
- form.scope = registration.scope;
- const newestWorker = (registration.activeWorker ||
- registration.waitingWorker ||
- registration.installingWorker);
- form.fetch = newestWorker && newestWorker.handlesFetchEvents;
- }
- return form;
- },
-
- attach() {
- if (this._dbg.isClosed) {
- return { error: "closed" };
- }
-
- if (!this._attached) {
- // Automatically disable their internal timeout that shut them down
- // Should be refactored by having actors specific to service workers
- if (this._dbg.type == Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- const worker = this._getServiceWorkerInfo();
- if (worker) {
- worker.attachDebugger();
- }
- }
- this._dbg.addListener(this);
- this._attached = true;
- }
-
- return {
- type: "attached",
- url: this._dbg.url
- };
- },
-
- detach() {
- if (!this._attached) {
- return { error: "wrongState" };
- }
-
- this._detach();
-
- return { type: "detached" };
- },
-
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- if (this._attached) {
- this._detach();
- }
- },
-
- connect(options) {
- if (!this._attached) {
- return { error: "wrongState" };
- }
-
- if (this._threadActor !== null) {
- return {
- type: "connected",
- threadActor: this._threadActor
- };
- }
-
- return DebuggerServer.connectToWorker(
- this.conn, this._dbg, this.actorID, options
- ).then(({ threadActor, transport, consoleActor }) => {
- this._threadActor = threadActor;
- this._transport = transport;
- this._consoleActor = consoleActor;
-
- return {
- type: "connected",
- threadActor: this._threadActor,
- consoleActor: this._consoleActor
- };
- }, (error) => {
- return { error: error.toString() };
- });
- },
-
- push() {
- if (this._dbg.type !== Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- return { error: "wrongType" };
- }
- const registration = this._getServiceWorkerRegistrationInfo();
- const originAttributes = ChromeUtils.originAttributesToSuffix(
- this._dbg.principal.originAttributes);
- swm.sendPushEvent(originAttributes, registration.scope);
- return { type: "pushed" };
- },
-
- onClose() {
- if (this._attached) {
- this._detach();
- }
-
- this.conn.sendActorEvent(this.actorID, "close");
- },
-
- onError(filename, lineno, message) {
- reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n");
- },
-
- _getServiceWorkerRegistrationInfo() {
- return swm.getRegistrationByPrincipal(this._dbg.principal, this._dbg.url);
- },
-
- _getServiceWorkerInfo() {
- const registration = this._getServiceWorkerRegistrationInfo();
- return registration.getWorkerByID(this._dbg.serviceWorkerID);
- },
-
- _detach() {
- if (this._threadActor !== null) {
- this._transport.close();
- this._transport = null;
- this._threadActor = null;
- }
-
- // If the worker is already destroyed, nsIWorkerDebugger.type throws
- // (_dbg.closed appears to be false when it throws)
- let type;
- try {
- type = this._dbg.type;
- } catch (e) {
- // nothing
- }
-
- if (type == Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- const worker = this._getServiceWorkerInfo();
- if (worker) {
- worker.detachDebugger();
- }
- }
-
- this._dbg.removeListener(this);
- this._attached = false;
- }
-});
-
-exports.WorkerActor = WorkerActor;
-
const PushSubscriptionActor = protocol.ActorClassWithSpec(pushSubscriptionSpec, {
initialize(conn, subscription) {
protocol.Actor.prototype.initialize.call(this, conn);
this._subscription = subscription;
},
form(detail) {
if (detail === "actorid") {
rename from devtools/server/actors/worker-list.js
rename to devtools/server/actors/worker/worker-list.js
--- a/devtools/server/actors/worker-list.js
+++ b/devtools/server/actors/worker/worker-list.js
@@ -2,17 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci } = require("chrome");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
loader.lazyRequireGetter(this, "WorkerActor", "devtools/server/actors/worker", true);
-loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActor", "devtools/server/actors/worker", true);
+loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActor", "devtools/server/actors/worker/service-worker", true);
XPCOMUtils.defineLazyServiceGetter(
this, "wdm",
"@mozilla.org/dom/workers/workerdebuggermanager;1",
"nsIWorkerDebuggerManager"
);
XPCOMUtils.defineLazyServiceGetter(
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -250,18 +250,23 @@ const Types = exports.__TypesForTests =
front: null,
},
{
types: ["gl-shader", "gl-program", "webgl"],
spec: "devtools/shared/specs/webgl",
front: "devtools/shared/fronts/webgl",
},
{
- types: ["worker", "pushSubscription", "serviceWorkerRegistration", "serviceWorker"],
- spec: "devtools/shared/specs/worker",
+ types: ["pushSubscription", "serviceWorkerRegistration", "serviceWorker"],
+ spec: "devtools/shared/specs/worker/service-worker",
+ front: null,
+ },
+ {
+ types: ["worker"],
+ spec: "devtools/shared/specs/worker/worker",
front: null,
},
];
const lazySpecs = new Map();
const lazyFronts = new Map();
// Convert the human readable `Types` list into efficient maps
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -1,16 +1,17 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'targets',
+ 'worker',
]
DevToolsModules(
'accessibility.js',
'actor-registry.js',
'addons.js',
'animation.js',
'breakpoint.js',
@@ -48,10 +49,9 @@ DevToolsModules(
'symbol-iterator.js',
'symbol.js',
'timeline.js',
'webaudio.js',
'webconsole.js',
'webextension-inspected-window.js',
'webextension-parent.js',
'webgl.js',
- 'worker.js'
)
copy from devtools/server/actors/worker/moz.build
copy to devtools/shared/specs/worker/moz.build
--- a/devtools/server/actors/worker/moz.build
+++ b/devtools/shared/specs/worker/moz.build
@@ -1,9 +1,10 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
- 'service-worker-process.js',
+ 'service-worker.js',
+ 'worker.js',
)
rename from devtools/shared/specs/worker.js
rename to devtools/shared/specs/worker/service-worker.js
--- a/devtools/shared/specs/worker.js
+++ b/devtools/shared/specs/worker/service-worker.js
@@ -1,41 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
-
-const workerSpec = generateActorSpec({
- typeName: "worker",
-
- methods: {
- attach: {
- request: {},
- response: RetVal("json")
- },
- detach: {
- request: {},
- response: RetVal("json")
- },
- connect: {
- request: {
- options: Arg(0, "json"),
- },
- response: RetVal("json")
- },
- push: {
- request: {},
- response: RetVal("json")
- },
- },
-});
-
-exports.workerSpec = workerSpec;
+const {RetVal, generateActorSpec} = require("devtools/shared/protocol");
const pushSubscriptionSpec = generateActorSpec({
typeName: "pushSubscription",
});
exports.pushSubscriptionSpec = pushSubscriptionSpec;
const serviceWorkerRegistrationSpec = generateActorSpec({
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/worker/worker.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const workerSpec = generateActorSpec({
+ typeName: "worker",
+
+ methods: {
+ attach: {
+ request: {},
+ response: RetVal("json")
+ },
+ detach: {
+ request: {},
+ response: RetVal("json")
+ },
+ connect: {
+ request: {
+ options: Arg(0, "json"),
+ },
+ response: RetVal("json")
+ },
+ push: {
+ request: {},
+ response: RetVal("json")
+ },
+ },
+});
+
+exports.workerSpec = workerSpec;