initial import
[vuplus_webkit] / Source / WebCore / inspector / front-end / InjectedFakeWorker.js
1 /*
2  * Copyright (C) 2010 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 var InjectedFakeWorker = function(InjectedScriptHost, inspectedWindow, injectedScriptId)
32 {
33
34 Worker = function(url)
35 {
36     var impl = new FakeWorker(this, url);
37     if (impl === null)
38         return null;
39
40     this.isFake = true;
41     this.postMessage = bind(impl.postMessage, impl);
42     this.terminate = bind(impl.terminate, impl);
43
44     function onmessageGetter()
45     {
46         return impl.channel.port1.onmessage;
47     }
48     function onmessageSetter(callback)
49     {
50         impl.channel.port1.onmessage = callback;
51     }
52     this.__defineGetter__("onmessage", onmessageGetter);
53     this.__defineSetter__("onmessage", onmessageSetter);
54     this.addEventListener = bind(impl.channel.port1.addEventListener, impl.channel.port1);
55     this.removeEventListener = bind(impl.channel.port1.removeEventListener, impl.channel.port1);
56     this.dispatchEvent = bind(impl.channel.port1.dispatchEvent, impl.channel.port1);
57 }
58
59 function FakeWorker(worker, url)
60 {
61     var scriptURL = this._expandURLAndCheckOrigin(document.baseURI, location.href, url);
62
63     this._worker = worker;
64     this._id = InjectedScriptHost.nextWorkerId();
65     this.channel = new MessageChannel();
66     this._listeners = [];
67     this._buildWorker(scriptURL);
68
69     InjectedScriptHost.didCreateWorker(this._id, scriptURL.url, false);
70 }
71
72 FakeWorker.prototype = {
73     postMessage: function(msg, opt_ports)
74     {
75         if (this._frame != null)
76             this.channel.port1.postMessage.apply(this.channel.port1, arguments);
77         else if (this._pendingMessages)
78             this._pendingMessages.push(arguments)
79         else
80             this._pendingMessages = [ arguments ];
81     },
82
83     terminate: function()
84     {
85         InjectedScriptHost.didDestroyWorker(this._id);
86
87         this.channel.port1.close();
88         this.channel.port2.close();
89         if (this._frame != null)
90             this._frame.frameElement.parentNode.removeChild(this._frame.frameElement);
91         this._frame = null;
92         this._worker = null; // Break reference loop.
93     },
94
95     _buildWorker: function(url)
96     {
97         var code = this._loadScript(url.url);
98         var iframeElement = document.createElement("iframe");
99         iframeElement.style.display = "none";
100
101         this._document = document;
102         iframeElement.onload = bind(this._onWorkerFrameLoaded, this, iframeElement, url, code);
103
104         if (document.body)
105             this._attachWorkerFrameToDocument(iframeElement, url, code);
106         else
107             window.addEventListener("load", bind(this._attachWorkerFrameToDocument, this, iframeElement), false);
108     },
109
110     _attachWorkerFrameToDocument: function(iframeElement)
111     {
112         document.body.appendChild(iframeElement);
113     },
114
115     _onWorkerFrameLoaded: function(iframeElement, url, code)
116     {
117         var frame = iframeElement.contentWindow;
118         this._frame = frame;
119         this._setupWorkerContext(frame, url);
120
121         var frameContents = '(function() { var location = __devtools.location; var window; ' + code + '})();\n' + '//@ sourceURL=' + url.url;
122
123         frame.eval(frameContents);
124         if (this._pendingMessages) {
125             for (var msg = 0; msg < this._pendingMessages.length; ++msg)
126                 this.postMessage.apply(this, this._pendingMessages[msg]);
127             delete this._pendingMessages;
128         }
129     },
130
131     _setupWorkerContext: function(workerFrame, url)
132     {
133         workerFrame.__devtools = {
134             handleException: bind(this._handleException, this),
135             location: url.mockLocation()
136         };
137
138         var self = this;
139
140         function onmessageGetter()
141         {
142             return self.channel.port2.onmessage ? self.channel.port2.onmessage.originalCallback : null;
143         }
144
145         function onmessageSetter(callback)
146         {
147             var wrappedCallback = bind(self._callbackWrapper, self, callback);
148             wrappedCallback.originalCallback = callback;
149             self.channel.port2.onmessage = wrappedCallback;
150         }
151
152         workerFrame.__defineGetter__("onmessage", onmessageGetter);
153         workerFrame.__defineSetter__("onmessage", onmessageSetter);
154         workerFrame.addEventListener = bind(this._addEventListener, this);
155         workerFrame.removeEventListener = bind(this._removeEventListener, this);
156         workerFrame.dispatchEvent = bind(this.channel.port2.dispatchEvent, this.channel.port2);
157         workerFrame.postMessage = bind(this.channel.port2.postMessage, this.channel.port2);
158         workerFrame.importScripts = bind(this._importScripts, this, workerFrame);
159         workerFrame.close = bind(this.terminate, this);
160     },
161
162     _addEventListener: function(type, callback, useCapture)
163     {
164         var wrappedCallback = bind(this._callbackWrapper, this, callback);
165         wrappedCallback.originalCallback = callback;
166         wrappedCallback.type = type;
167         wrappedCallback.useCapture = Boolean(useCapture);
168
169         this.channel.port2.addEventListener(type, wrappedCallback, useCapture);
170         this._listeners.push(wrappedCallback);
171     },
172
173     _removeEventListener: function(type, callback, useCapture)
174     {
175         var listeners = this._listeners;
176         for (var i = 0; i < listeners.length; ++i) {
177             if (listeners[i].originalCallback === callback &&
178                 listeners[i].type === type &&
179                 listeners[i].useCapture === Boolean(useCapture)) {
180                 this.channel.port2.removeEventListener(type, listeners[i], useCapture);
181                 listeners[i] = listeners[listeners.length - 1];
182                 listeners.pop();
183                 break;
184             }
185         }
186     },
187
188     _callbackWrapper: function(callback, msg)
189     {
190         // Shortcut -- if no exception handlers installed, avoid try/catch so as not to obscure line number.
191         if (!this._frame.onerror && !this._worker.onerror) {
192             callback(msg);
193             return;
194         }
195
196         try {
197             callback(msg);
198         } catch (e) {
199             this._handleException(e, this._frame.onerror, this._worker.onerror);
200         }
201     },
202
203     _handleException: function(e)
204     {
205         // NB: it should be an ErrorEvent, but creating it from script is not
206         // currently supported, so emulate it on top of plain vanilla Event.
207         var errorEvent = this._document.createEvent("Event");
208         errorEvent.initEvent("Event", false, false);
209         errorEvent.message = "Uncaught exception";
210
211         for (var i = 1; i < arguments.length; ++i) {
212             if (arguments[i] && arguments[i](errorEvent))
213                 return;
214         }
215
216         throw e;
217     },
218
219     _importScripts: function(targetFrame)
220     {
221         for (var i = 1; i < arguments.length; ++i) {
222             var workerOrigin = targetFrame.__devtools.location.href;
223             var url = this._expandURLAndCheckOrigin(workerOrigin, workerOrigin, arguments[i]);
224             targetFrame.eval(this._loadScript(url.url) + "\n//@ sourceURL= " + url.url);
225         }
226     },
227
228     _loadScript: function(url)
229     {
230         var xhr = new XMLHttpRequest();
231         xhr.open("GET", url, false);
232         xhr.send(null);
233
234         var text = xhr.responseText;
235         if (xhr.status != 0 && xhr.status/100 !== 2) { // We're getting status === 0 when using file://.
236             console.error("Failed to load worker: " + url + "[" + xhr.status + "]");
237             text = ""; // We've got error message, not worker code.
238         }
239         return text;
240     },
241
242     _expandURLAndCheckOrigin: function(baseURL, origin, url)
243     {
244         var scriptURL = new URL(baseURL).completeWith(url);
245
246         if (!scriptURL.sameOrigin(origin))
247             throw new DOMCoreException("SECURITY_ERR",18);
248         return scriptURL;
249     }
250 };
251
252 function URL(url)
253 {
254     this.url = url;
255     this.split();
256 }
257
258 URL.prototype = {
259     urlRegEx: (/^(http[s]?|file):\/\/([^\/:]*)(:[\d]+)?(?:(\/[^#?]*)(\?[^#]*)?(?:#(.*))?)?$/i),
260
261     split: function()
262     {
263         function emptyIfNull(str)
264         {
265             return str == null ? "" : str;
266         }
267         var parts = this.urlRegEx.exec(this.url);
268
269         this.schema = parts[1];
270         this.host = parts[2];
271         this.port = emptyIfNull(parts[3]);
272         this.path = emptyIfNull(parts[4]);
273         this.query = emptyIfNull(parts[5]);
274         this.fragment = emptyIfNull(parts[6]);
275     },
276
277     mockLocation: function()
278     {
279         var host = this.host.replace(/^[^@]*@/, "");
280
281         return {
282             href:     this.url,
283             protocol: this.schema + ":",
284             host:     host,
285             hostname: host,
286             port:     this.port,
287             pathname: this.path,
288             search:   this.query,
289             hash:     this.fragment
290         };
291     },
292
293     completeWith: function(url)
294     {
295         if (url === "" || /^[^/]*:/.exec(url)) // If given absolute url, return as is now.
296             return new URL(url);
297
298         var relParts = /^([^#?]*)(.*)$/.exec(url); // => [ url, path, query-andor-fragment ]
299
300         var path = (relParts[1].slice(0, 1) === "/" ? "" : this.path.replace(/[^/]*$/, "")) + relParts[1];
301         path = path.replace(/(\/\.)+(\/|$)/g, "/").replace(/[^/]*\/\.\.(\/|$)/g, "");
302
303         return new URL(this.schema + "://" + this.host + this.port + path + relParts[2]);
304     },
305
306     sameOrigin: function(url)
307     {
308         function normalizePort(schema, port)
309         {
310             var portNo = port.slice(1);
311             return (schema === "https" && portNo == 443 || schema === "http" && portNo == 80) ? "" : port;
312         }
313
314         var other = new URL(url);
315
316         return this.schema === other.schema &&
317             this.host === other.host &&
318             normalizePort(this.schema, this.port) === normalizePort(other.schema, other.port);
319     }
320 };
321
322 function DOMCoreException(name, code)
323 {
324     function formatError()
325     {
326         return "Error: " + this.message;
327     }
328
329     this.name = name;
330     this.message = name + ": DOM Exception " + code;
331     this.code = code;
332     this.toString = bind(formatError, this);
333 }
334
335 function bind(func, thisObject)
336 {
337     var args = Array.prototype.slice.call(arguments, 2);
338     return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); };
339 }
340
341 function noop()
342 {
343 }
344
345 }