2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
31 var InjectedFakeWorker = function(InjectedScriptHost, inspectedWindow, injectedScriptId)
34 Worker = function(url)
36 var impl = new FakeWorker(this, url);
41 this.postMessage = bind(impl.postMessage, impl);
42 this.terminate = bind(impl.terminate, impl);
44 function onmessageGetter()
46 return impl.channel.port1.onmessage;
48 function onmessageSetter(callback)
50 impl.channel.port1.onmessage = callback;
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);
59 function FakeWorker(worker, url)
61 var scriptURL = this._expandURLAndCheckOrigin(document.baseURI, location.href, url);
63 this._worker = worker;
64 this._id = InjectedScriptHost.nextWorkerId();
65 this.channel = new MessageChannel();
67 this._buildWorker(scriptURL);
69 InjectedScriptHost.didCreateWorker(this._id, scriptURL.url, false);
72 FakeWorker.prototype = {
73 postMessage: function(msg, opt_ports)
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)
80 this._pendingMessages = [ arguments ];
85 InjectedScriptHost.didDestroyWorker(this._id);
87 this.channel.port1.close();
88 this.channel.port2.close();
89 if (this._frame != null)
90 this._frame.frameElement.parentNode.removeChild(this._frame.frameElement);
92 this._worker = null; // Break reference loop.
95 _buildWorker: function(url)
97 var code = this._loadScript(url.url);
98 var iframeElement = document.createElement("iframe");
99 iframeElement.style.display = "none";
101 this._document = document;
102 iframeElement.onload = bind(this._onWorkerFrameLoaded, this, iframeElement, url, code);
105 this._attachWorkerFrameToDocument(iframeElement, url, code);
107 window.addEventListener("load", bind(this._attachWorkerFrameToDocument, this, iframeElement), false);
110 _attachWorkerFrameToDocument: function(iframeElement)
112 document.body.appendChild(iframeElement);
115 _onWorkerFrameLoaded: function(iframeElement, url, code)
117 var frame = iframeElement.contentWindow;
119 this._setupWorkerContext(frame, url);
121 var frameContents = '(function() { var location = __devtools.location; var window; ' + code + '})();\n' + '//@ sourceURL=' + url.url;
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;
131 _setupWorkerContext: function(workerFrame, url)
133 workerFrame.__devtools = {
134 handleException: bind(this._handleException, this),
135 location: url.mockLocation()
140 function onmessageGetter()
142 return self.channel.port2.onmessage ? self.channel.port2.onmessage.originalCallback : null;
145 function onmessageSetter(callback)
147 var wrappedCallback = bind(self._callbackWrapper, self, callback);
148 wrappedCallback.originalCallback = callback;
149 self.channel.port2.onmessage = wrappedCallback;
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);
162 _addEventListener: function(type, callback, useCapture)
164 var wrappedCallback = bind(this._callbackWrapper, this, callback);
165 wrappedCallback.originalCallback = callback;
166 wrappedCallback.type = type;
167 wrappedCallback.useCapture = Boolean(useCapture);
169 this.channel.port2.addEventListener(type, wrappedCallback, useCapture);
170 this._listeners.push(wrappedCallback);
173 _removeEventListener: function(type, callback, useCapture)
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];
188 _callbackWrapper: function(callback, msg)
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) {
199 this._handleException(e, this._frame.onerror, this._worker.onerror);
203 _handleException: function(e)
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";
211 for (var i = 1; i < arguments.length; ++i) {
212 if (arguments[i] && arguments[i](errorEvent))
219 _importScripts: function(targetFrame)
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);
228 _loadScript: function(url)
230 var xhr = new XMLHttpRequest();
231 xhr.open("GET", url, false);
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.
242 _expandURLAndCheckOrigin: function(baseURL, origin, url)
244 var scriptURL = new URL(baseURL).completeWith(url);
246 if (!scriptURL.sameOrigin(origin))
247 throw new DOMCoreException("SECURITY_ERR",18);
259 urlRegEx: (/^(http[s]?|file):\/\/([^\/:]*)(:[\d]+)?(?:(\/[^#?]*)(\?[^#]*)?(?:#(.*))?)?$/i),
263 function emptyIfNull(str)
265 return str == null ? "" : str;
267 var parts = this.urlRegEx.exec(this.url);
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]);
277 mockLocation: function()
279 var host = this.host.replace(/^[^@]*@/, "");
283 protocol: this.schema + ":",
293 completeWith: function(url)
295 if (url === "" || /^[^/]*:/.exec(url)) // If given absolute url, return as is now.
298 var relParts = /^([^#?]*)(.*)$/.exec(url); // => [ url, path, query-andor-fragment ]
300 var path = (relParts[1].slice(0, 1) === "/" ? "" : this.path.replace(/[^/]*$/, "")) + relParts[1];
301 path = path.replace(/(\/\.)+(\/|$)/g, "/").replace(/[^/]*\/\.\.(\/|$)/g, "");
303 return new URL(this.schema + "://" + this.host + this.port + path + relParts[2]);
306 sameOrigin: function(url)
308 function normalizePort(schema, port)
310 var portNo = port.slice(1);
311 return (schema === "https" && portNo == 443 || schema === "http" && portNo == 80) ? "" : port;
314 var other = new URL(url);
316 return this.schema === other.schema &&
317 this.host === other.host &&
318 normalizePort(this.schema, this.port) === normalizePort(other.schema, other.port);
322 function DOMCoreException(name, code)
324 function formatError()
326 return "Error: " + this.message;
330 this.message = name + ": DOM Exception " + code;
332 this.toString = bind(formatError, this);
335 function bind(func, thisObject)
337 var args = Array.prototype.slice.call(arguments, 2);
338 return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); };