diff options
Diffstat (limited to 'WebCore/inspector/front-end/InjectedFakeWorker.js')
-rw-r--r-- | WebCore/inspector/front-end/InjectedFakeWorker.js | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/WebCore/inspector/front-end/InjectedFakeWorker.js b/WebCore/inspector/front-end/InjectedFakeWorker.js new file mode 100644 index 0000000..7176844 --- /dev/null +++ b/WebCore/inspector/front-end/InjectedFakeWorker.js @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var InjectedFakeWorker = function(InjectedScriptHost, inspectedWindow, injectedScriptId) +{ + +Worker = function(url) +{ + var impl = new FakeWorker(this, url); + if (impl === null) + return null; + + this.isFake = true; + this.postMessage = bind(impl.postMessage, impl); + this.terminate = bind(impl.terminate, impl); + + function onmessageGetter() + { + return impl.channel.port1.onmessage; + } + function onmessageSetter(callback) + { + impl.channel.port1.onmessage = callback; + } + this.__defineGetter__("onmessage", onmessageGetter); + this.__defineSetter__("onmessage", onmessageSetter); + this.addEventListener = bind(impl.channel.port1.addEventListener, impl.channel.port1); + this.removeEventListener = bind(impl.channel.port1.removeEventListener, impl.channel.port1); + this.dispatchEvent = bind(impl.channel.port1.dispatchEvent, impl.channel.port1); +} + +function FakeWorker(worker, url) +{ + var scriptURL = this._expandURLAndCheckOrigin(document.baseURI, location.href, url); + + this._worker = worker; + this._id = InjectedScriptHost.nextWorkerId(); + this.channel = new MessageChannel(); + this._listeners = []; + this._buildWorker(scriptURL); + + InjectedScriptHost.didCreateWorker(this._id, scriptURL.url, false); +} + +FakeWorker.prototype = { + postMessage: function(msg, opt_ports) + { + if (this._frame != null) + this.channel.port1.postMessage.apply(this.channel.port1, arguments); + else if (this._pendingMessages) + this._pendingMessages.push(arguments) + else + this._pendingMessages = [ arguments ]; + }, + + terminate: function() + { + InjectedScriptHost.didDestroyWorker(this._id); + + this.channel.port1.close(); + this.channel.port2.close(); + if (this._frame != null) + this._frame.frameElement.parentNode.removeChild(this._frame.frameElement); + this._frame = null; + this._worker = null; // Break reference loop. + }, + + _buildWorker: function(url) + { + var code = this._loadScript(url.url); + var iframeElement = document.createElement("iframe"); + iframeElement.style.display = "none"; + + this._document = document; + iframeElement.onload = bind(this._onWorkerFrameLoaded, this, iframeElement, url, code); + + if (document.body) + this._attachWorkerFrameToDocument(iframeElement, url, code); + else + window.addEventListener("load", bind(this._attachWorkerFrameToDocument, this, iframeElement), false); + }, + + _attachWorkerFrameToDocument: function(iframeElement) + { + document.body.appendChild(iframeElement); + }, + + _onWorkerFrameLoaded: function(iframeElement, url, code) + { + var frame = iframeElement.contentWindow; + this._frame = frame; + this._setupWorkerContext(frame, url); + + var frameContents = '(function() { var location = __devtools.location; var window; ' + code + '})();\n' + '//@ sourceURL=' + url.url; + + frame.eval(frameContents); + if (this._pendingMessages) { + for (var msg = 0; msg < this._pendingMessages.length; ++msg) + this.postMessage.apply(this, this._pendingMessages[msg]); + delete this._pendingMessages; + } + }, + + _setupWorkerContext: function(workerFrame, url) + { + workerFrame.__devtools = { + handleException: bind(this._handleException, this), + location: url.mockLocation() + }; + + var self = this; + + function onmessageGetter() + { + return self.channel.port2.onmessage ? self.channel.port2.onmessage.originalCallback : null; + } + + function onmessageSetter(callback) + { + var wrappedCallback = bind(self._callbackWrapper, self, callback); + wrappedCallback.originalCallback = callback; + self.channel.port2.onmessage = wrappedCallback; + } + + workerFrame.__defineGetter__("onmessage", onmessageGetter); + workerFrame.__defineSetter__("onmessage", onmessageSetter); + workerFrame.addEventListener = bind(this._addEventListener, this); + workerFrame.removeEventListener = bind(this._removeEventListener, this); + workerFrame.dispatchEvent = bind(this.channel.port2.dispatchEvent, this.channel.port2); + workerFrame.postMessage = bind(this.channel.port2.postMessage, this.channel.port2); + workerFrame.importScripts = bind(this._importScripts, this, workerFrame); + workerFrame.close = bind(this.terminate, this); + }, + + _addEventListener: function(type, callback, useCapture) + { + var wrappedCallback = bind(this._callbackWrapper, this, callback); + wrappedCallback.originalCallback = callback; + wrappedCallback.type = type; + wrappedCallback.useCapture = Boolean(useCapture); + + this.channel.port2.addEventListener(type, wrappedCallback, useCapture); + this._listeners.push(wrappedCallback); + }, + + _removeEventListener: function(type, callback, useCapture) + { + var listeners = this._listeners; + for (var i = 0; i < listeners.length; ++i) { + if (listeners[i].originalCallback === callback && + listeners[i].type === type && + listeners[i].useCapture === Boolean(useCapture)) { + this.channel.port2.removeEventListener(type, listeners[i], useCapture); + listeners[i] = listeners[listeners.length - 1]; + listeners.pop(); + break; + } + } + }, + + _callbackWrapper: function(callback, msg) + { + // Shortcut -- if no exception handlers installed, avoid try/catch so as not to obscure line number. + if (!this._frame.onerror && !this._worker.onerror) { + callback(msg); + return; + } + + try { + callback(msg); + } catch (e) { + this._handleException(e, this._frame.onerror, this._worker.onerror); + } + }, + + _handleException: function(e) + { + // NB: it should be an ErrorEvent, but creating it from script is not + // currently supported, so emulate it on top of plain vanilla Event. + var errorEvent = this._document.createEvent("Event"); + errorEvent.initEvent("Event", false, false); + errorEvent.message = "Uncaught exception"; + + for (var i = 1; i < arguments.length; ++i) { + if (arguments[i] && arguments[i](errorEvent)) + return; + } + + throw e; + }, + + _importScripts: function(targetFrame) + { + for (var i = 1; i < arguments.length; ++i) { + var workerOrigin = targetFrame.__devtools.location.href; + var url = this._expandURLAndCheckOrigin(workerOrigin, workerOrigin, arguments[i]); + targetFrame.eval(this._loadScript(url.url) + "\n//@ sourceURL= " + url.url); + } + }, + + _loadScript: function(url) + { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.send(null); + + var text = xhr.responseText; + if (xhr.status != 0 && xhr.status/100 !== 2) { // We're getting status === 0 when using file://. + console.error("Failed to load worker: " + url + "[" + xhr.status + "]"); + text = ""; // We've got error message, not worker code. + } + return text; + }, + + _expandURLAndCheckOrigin: function(baseURL, origin, url) + { + var scriptURL = new URL(baseURL).completeWith(url); + + if (!scriptURL.sameOrigin(origin)) + throw new DOMCoreException("SECURITY_ERR",18); + return scriptURL; + } +}; + +function URL(url) +{ + this.url = url; + this.split(); +} + +URL.prototype = { + urlRegEx: (/^(http[s]?|file):\/\/([^\/:]*)(:[\d]+)?(?:(\/[^#?]*)(\?[^#]*)?(?:#(.*))?)?$/i), + + split: function() + { + function emptyIfNull(str) + { + return str == null ? "" : str; + } + var parts = this.urlRegEx.exec(this.url); + + this.schema = parts[1]; + this.host = parts[2]; + this.port = emptyIfNull(parts[3]); + this.path = emptyIfNull(parts[4]); + this.query = emptyIfNull(parts[5]); + this.fragment = emptyIfNull(parts[6]); + }, + + mockLocation: function() + { + var host = this.host.replace(/^[^@]*@/, ""); + + return { + href: this.url, + protocol: this.schema + ":", + host: host, + hostname: host, + port: this.port, + pathname: this.path, + search: this.query, + hash: this.fragment + }; + }, + + completeWith: function(url) + { + if (url === "" || /^[^/]*:/.exec(url)) // If given absolute url, return as is now. + return new URL(url); + + var relParts = /^([^#?]*)(.*)$/.exec(url); // => [ url, path, query-andor-fragment ] + + var path = (relParts[1].slice(0, 1) === "/" ? "" : this.path.replace(/[^/]*$/, "")) + relParts[1]; + path = path.replace(/(\/\.)+(\/|$)/g, "/").replace(/[^/]*\/\.\.(\/|$)/g, ""); + + return new URL(this.schema + "://" + this.host + this.port + path + relParts[2]); + }, + + sameOrigin: function(url) + { + function normalizePort(schema, port) + { + var portNo = port.slice(1); + return (schema === "https" && portNo == 443 || schema === "http" && portNo == 80) ? "" : port; + } + + var other = new URL(url); + + return this.schema === other.schema && + this.host === other.host && + normalizePort(this.schema, this.port) === normalizePort(other.schema, other.port); + } +}; + +function DOMCoreException(name, code) +{ + function formatError() + { + return "Error: " + this.message; + } + + this.name = name; + this.message = name + ": DOM Exception " + code; + this.code = code; + this.toString = bind(formatError, this); +} + +function bind(func, thisObject) +{ + var args = Array.prototype.slice.call(arguments, 2); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); }; +} + +function noop() +{ +} + +} |