1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.Object}
34 */
35WebInspector.WorkerManager = function()
36{
37    this._workerIdToWindow = {};
38    InspectorBackend.registerWorkerDispatcher(new WebInspector.WorkerDispatcher(this));
39}
40
41WebInspector.WorkerManager.isWorkerFrontend = function()
42{
43    return !!WebInspector.queryParamsObject["dedicatedWorkerId"] ||
44           !!WebInspector.queryParamsObject["isSharedWorker"];
45}
46
47WebInspector.WorkerManager.isDedicatedWorkerFrontend = function()
48{
49    return !!WebInspector.queryParamsObject["dedicatedWorkerId"];
50}
51
52WebInspector.WorkerManager.loaded = function()
53{
54    var workerId = WebInspector.queryParamsObject["dedicatedWorkerId"];
55    if (workerId)
56        WebInspector.WorkerManager._initializeDedicatedWorkerFrontend(workerId);
57    else
58        WebInspector.workerManager = new WebInspector.WorkerManager();
59}
60
61WebInspector.WorkerManager.loadCompleted = function()
62{
63    // Make sure script execution of dedicated worker is resumed and then paused
64    // on the first script statement in case we autoattached to it.
65    if (WebInspector.queryParamsObject["workerPaused"]) {
66        DebuggerAgent.pause();
67        RuntimeAgent.run(calculateTitle);
68    } else if (WebInspector.WorkerManager.isWorkerFrontend())
69        calculateTitle();
70
71    function calculateTitle()
72    {
73        WebInspector.WorkerManager._calculateWorkerInspectorTitle();
74    }
75
76    if (WebInspector.workerManager)
77        WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, WebInspector.workerManager._mainFrameNavigated, WebInspector.workerManager);
78}
79
80WebInspector.WorkerManager._initializeDedicatedWorkerFrontend = function(workerId)
81{
82    function receiveMessage(event)
83    {
84        var message = event.data;
85        InspectorBackend.dispatch(message);
86    }
87    window.addEventListener("message", receiveMessage, true);
88
89
90    InspectorBackend.sendMessageObjectToBackend = function(message)
91    {
92        window.opener.postMessage({workerId: workerId, command: "sendMessageToBackend", message: message}, "*");
93    }
94}
95
96WebInspector.WorkerManager._calculateWorkerInspectorTitle = function()
97{
98    var expression = "location.href";
99    if (WebInspector.queryParamsObject["isSharedWorker"])
100        expression += " + (this.name ? ' (' + this.name + ')' : '')";
101    RuntimeAgent.evaluate.invoke({expression:expression, doNotPauseOnExceptionsAndMuteConsole:true, returnByValue: true}, evalCallback.bind(this));
102
103    /**
104     * @param {?Protocol.Error} error
105     * @param {!RuntimeAgent.RemoteObject} result
106     * @param {boolean=} wasThrown
107     */
108    function evalCallback(error, result, wasThrown)
109    {
110        if (error || wasThrown) {
111            console.error(error);
112            return;
113        }
114        InspectorFrontendHost.inspectedURLChanged(result.value);
115    }
116}
117
118WebInspector.WorkerManager.Events = {
119    WorkerAdded: "worker-added",
120    WorkerRemoved: "worker-removed",
121    WorkersCleared: "workers-cleared",
122}
123
124WebInspector.WorkerManager.prototype = {
125    _workerCreated: function(workerId, url, inspectorConnected)
126     {
127        if (inspectorConnected)
128            this._openInspectorWindow(workerId, true);
129        this.dispatchEventToListeners(WebInspector.WorkerManager.Events.WorkerAdded, {workerId: workerId, url: url, inspectorConnected: inspectorConnected});
130     },
131
132    _workerTerminated: function(workerId)
133     {
134        this.closeWorkerInspector(workerId);
135        this.dispatchEventToListeners(WebInspector.WorkerManager.Events.WorkerRemoved, workerId);
136     },
137
138    _sendMessageToWorkerInspector: function(workerId, message)
139    {
140        var workerInspectorWindow = this._workerIdToWindow[workerId];
141        if (workerInspectorWindow)
142            workerInspectorWindow.postMessage(message, "*");
143    },
144
145    openWorkerInspector: function(workerId)
146    {
147        var existingInspector = this._workerIdToWindow[workerId];
148        if (existingInspector) {
149            existingInspector.focus();
150            return;
151        }
152
153        this._openInspectorWindow(workerId, false);
154        WorkerAgent.connectToWorker(workerId);
155    },
156
157    _openInspectorWindow: function(workerId, workerIsPaused)
158    {
159        var search = window.location.search;
160        var hash = window.location.hash;
161        var url = window.location.href;
162        // Make sure hash is in rear
163        url = url.replace(hash, "");
164        url += (search ? "&dedicatedWorkerId=" : "?dedicatedWorkerId=") + workerId;
165        if (workerIsPaused)
166            url += "&workerPaused=true";
167        url = url.replace("docked=true&", "");
168        url = url.replace("can_dock=true&", "");
169        url += hash;
170        var width = WebInspector.settings.workerInspectorWidth.get();
171        var height = WebInspector.settings.workerInspectorHeight.get();
172        // Set location=0 just to make sure the front-end will be opened in a separate window, not in new tab.
173        var workerInspectorWindow = window.open(url, undefined, "location=0,width=" + width + ",height=" + height);
174        workerInspectorWindow.addEventListener("resize", this._onWorkerInspectorResize.bind(this, workerInspectorWindow), false);
175        this._workerIdToWindow[workerId] = workerInspectorWindow;
176        workerInspectorWindow.addEventListener("beforeunload", this._workerInspectorClosing.bind(this, workerId), true);
177
178        // Listen to beforeunload in detached state and to the InspectorClosing event in case of attached inspector.
179        window.addEventListener("unload", this._pageInspectorClosing.bind(this), true);
180    },
181
182    closeWorkerInspector: function(workerId)
183    {
184        var workerInspectorWindow = this._workerIdToWindow[workerId];
185        if (workerInspectorWindow)
186            workerInspectorWindow.close();
187    },
188
189    _mainFrameNavigated: function(event)
190    {
191        for (var workerId in this._workerIdToWindow)
192            this.closeWorkerInspector(workerId);
193        this.dispatchEventToListeners(WebInspector.WorkerManager.Events.WorkersCleared);
194    },
195
196    _pageInspectorClosing: function()
197    {
198        this._ignoreWorkerInspectorClosing = true;
199        for (var workerId in this._workerIdToWindow) {
200            this._workerIdToWindow[workerId].close();
201            WorkerAgent.disconnectFromWorker(parseInt(workerId, 10));
202        }
203    },
204
205    _onWorkerInspectorResize: function(workerInspectorWindow)
206    {
207        var doc = workerInspectorWindow.document;
208        WebInspector.settings.workerInspectorWidth.set(doc.width);
209        WebInspector.settings.workerInspectorHeight.set(doc.height);
210    },
211
212    _workerInspectorClosing: function(workerId, event)
213    {
214        if (event.target.location.href === "about:blank")
215            return;
216        if (this._ignoreWorkerInspectorClosing)
217            return;
218        delete this._workerIdToWindow[workerId];
219        WorkerAgent.disconnectFromWorker(workerId);
220    },
221
222    _disconnectedFromWorker: function()
223    {
224        var screen = new WebInspector.WorkerTerminatedScreen();
225        WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, screen.hide, screen);
226        screen.showModal();
227    },
228
229    __proto__: WebInspector.Object.prototype
230}
231
232/**
233 * @constructor
234 * @implements {WorkerAgent.Dispatcher}
235 */
236WebInspector.WorkerDispatcher = function(workerManager)
237{
238    this._workerManager = workerManager;
239    window.addEventListener("message", this._receiveMessage.bind(this), true);
240}
241
242WebInspector.WorkerDispatcher.prototype = {
243    _receiveMessage: function(event)
244    {
245        var workerId = event.data["workerId"];
246        workerId = parseInt(workerId, 10);
247        var command = event.data.command;
248        var message = event.data.message;
249
250        if (command == "sendMessageToBackend")
251            WorkerAgent.sendMessageToWorker(workerId, message);
252    },
253
254    workerCreated: function(workerId, url, inspectorConnected)
255    {
256        this._workerManager._workerCreated(workerId, url, inspectorConnected);
257    },
258
259    workerTerminated: function(workerId)
260    {
261        this._workerManager._workerTerminated(workerId);
262    },
263
264    dispatchMessageFromWorker: function(workerId, message)
265    {
266        this._workerManager._sendMessageToWorkerInspector(workerId, message);
267    },
268
269    disconnectedFromWorker: function()
270    {
271        this._workerManager._disconnectedFromWorker();
272    }
273}
274
275/**
276 * @constructor
277 * @extends {WebInspector.HelpScreen}
278 */
279WebInspector.WorkerTerminatedScreen = function()
280{
281    WebInspector.HelpScreen.call(this, WebInspector.UIString("Inspected worker terminated"));
282    var p = this.contentElement.createChild("p");
283    p.classList.add("help-section");
284    p.textContent = WebInspector.UIString("Inspected worker has terminated. Once it restarts we will attach to it automatically.");
285}
286
287WebInspector.WorkerTerminatedScreen.prototype = {
288    /**
289     * @override
290     */
291    willHide: function()
292    {
293        WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this.hide, this);
294        WebInspector.HelpScreen.prototype.willHide.call(this);
295    },
296
297    __proto__: WebInspector.HelpScreen.prototype
298}
299