diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Base | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Base')
17 files changed, 6190 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js b/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js new file mode 100644 index 000000000..ba423aa31 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2007, 2008, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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. + */ + +WebInspector.roleSelectorForNode = function(node) +{ + // This is proposed syntax for CSS 4 computed role selector :role(foo) and subject to change. + // See http://lists.w3.org/Archives/Public/www-style/2013Jul/0104.html + var title = ""; + var role = node.computedRole(); + if (role) + title = ":role(" + role + ")"; + return title; +}; + +WebInspector.linkifyAccessibilityNodeReference = function(node) +{ + if (!node) + return null; + // Same as linkifyNodeReference except the link text has the classnames removed... + // ...for list brevity, and both text and title have roleSelectorForNode appended. + var link = WebInspector.linkifyNodeReference(node); + var tagIdSelector = link.title; + var classSelectorIndex = tagIdSelector.indexOf("."); + if (classSelectorIndex > -1) + tagIdSelector = tagIdSelector.substring(0, classSelectorIndex); + var roleSelector = WebInspector.roleSelectorForNode(node); + link.textContent = tagIdSelector + roleSelector; + link.title += roleSelector; + return link; +}; + +WebInspector.linkifyNodeReference = function(node, maxLength) +{ + let displayName = node.displayName; + if (!isNaN(maxLength)) + displayName = displayName.truncate(maxLength); + + let link = document.createElement("span"); + link.append(displayName); + link.setAttribute("role", "link"); + + link.title = displayName; + + let nodeType = node.nodeType(); + if ((nodeType !== Node.DOCUMENT_NODE || node.parentNode) && nodeType !== Node.TEXT_NODE) + link.className = "node-link"; + + link.addEventListener("click", WebInspector.domTreeManager.inspectElement.bind(WebInspector.domTreeManager, node.id)); + link.addEventListener("mouseover", WebInspector.domTreeManager.highlightDOMNode.bind(WebInspector.domTreeManager, node.id, "all")); + link.addEventListener("mouseout", WebInspector.domTreeManager.hideDOMNodeHighlight.bind(WebInspector.domTreeManager)); + + return link; +}; + +function createSVGElement(tagName) +{ + return document.createElementNS("http://www.w3.org/2000/svg", tagName); +} + +WebInspector.cssPath = function(node) +{ + console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode."); + if (node.nodeType() !== Node.ELEMENT_NODE) + return ""; + + let suffix = ""; + if (node.isPseudoElement()) { + suffix = "::" + node.pseudoType(); + node = node.parentNode; + } + + let components = []; + while (node) { + let component = WebInspector.cssPathComponent(node); + if (!component) + break; + components.push(component); + if (component.done) + break; + node = node.parentNode; + } + + components.reverse(); + return components.map((x) => x.value).join(" > ") + suffix; +}; + +WebInspector.cssPathComponent = function(node) +{ + console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode."); + console.assert(!node.isPseudoElement()); + if (node.nodeType() !== Node.ELEMENT_NODE) + return null; + + let nodeName = node.nodeNameInCorrectCase(); + let lowerNodeName = node.nodeName().toLowerCase(); + + // html, head, and body are unique nodes. + if (lowerNodeName === "body" || lowerNodeName === "head" || lowerNodeName === "html") + return {value: nodeName, done: true}; + + // #id is unique. + let id = node.getAttribute("id"); + if (id) + return {value: node.escapedIdSelector, done: true}; + + // Root node does not have siblings. + if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE) + return {value: nodeName, done: true}; + + // Find uniqueness among siblings. + // - look for a unique className + // - look for a unique tagName + // - fallback to nth-child() + + function classNames(node) { + let classAttribute = node.getAttribute("class"); + return classAttribute ? classAttribute.trim().split(/\s+/) : []; + } + + let nthChildIndex = -1; + let hasUniqueTagName = true; + let uniqueClasses = new Set(classNames(node)); + + let siblings = node.parentNode.children; + let elementIndex = 0; + for (let sibling of siblings) { + if (sibling.nodeType() !== Node.ELEMENT_NODE) + continue; + + elementIndex++; + if (sibling === node) { + nthChildIndex = elementIndex; + continue; + } + + if (sibling.nodeNameInCorrectCase() === nodeName) + hasUniqueTagName = false; + + if (uniqueClasses.size) { + let siblingClassNames = classNames(sibling); + for (let className of siblingClassNames) + uniqueClasses.delete(className); + } + } + + let selector = nodeName; + if (lowerNodeName === "input" && node.getAttribute("type") && !uniqueClasses.size) + selector += `[type="${node.getAttribute("type")}"]`; + if (!hasUniqueTagName) { + if (uniqueClasses.size) + selector += node.escapedClassSelector; + else + selector += `:nth-child(${nthChildIndex})`; + } + + return {value: selector, done: false}; +}; + +WebInspector.xpath = function(node) +{ + console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode."); + + if (node.nodeType() === Node.DOCUMENT_NODE) + return "/"; + + let components = []; + while (node) { + let component = WebInspector.xpathComponent(node); + if (!component) + break; + components.push(component); + if (component.done) + break; + node = node.parentNode; + } + + components.reverse(); + + let prefix = components.length && components[0].done ? "" : "/"; + return prefix + components.map((x) => x.value).join("/"); +}; + +WebInspector.xpathComponent = function(node) +{ + console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode."); + + let index = WebInspector.xpathIndex(node); + if (index === -1) + return null; + + let value; + + switch (node.nodeType()) { + case Node.DOCUMENT_NODE: + return {value: "", done: true}; + case Node.ELEMENT_NODE: + var id = node.getAttribute("id"); + if (id) + return {value: `//*[@id="${id}"]`, done: true}; + value = node.localName(); + break; + case Node.ATTRIBUTE_NODE: + value = `@${node.nodeName()}`; + break; + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + value = "text()"; + break; + case Node.COMMENT_NODE: + value = "comment()"; + break; + case Node.PROCESSING_INSTRUCTION_NODE: + value = "processing-instruction()"; + break; + default: + value = ""; + break; + } + + if (index > 0) + value += `[${index}]`; + + return {value, done: false}; +}; + +WebInspector.xpathIndex = function(node) +{ + // Root node. + if (!node.parentNode) + return 0; + + // No siblings. + let siblings = node.parentNode.children; + if (siblings.length <= 1) + return 0; + + // Find uniqueness among siblings. + // - look for a unique localName + // - fallback to index + + function isSimiliarNode(a, b) { + if (a === b) + return true; + + let aType = a.nodeType(); + let bType = b.nodeType(); + + if (aType === Node.ELEMENT_NODE && bType === Node.ELEMENT_NODE) + return a.localName() === b.localName(); + + // XPath CDATA and text() are the same. + if (aType === Node.CDATA_SECTION_NODE) + return aType === Node.TEXT_NODE; + if (bType === Node.CDATA_SECTION_NODE) + return bType === Node.TEXT_NODE; + + return aType === bType; + } + + let unique = true; + let xPathIndex = -1; + + let xPathIndexCounter = 1; // XPath indices start at 1. + for (let sibling of siblings) { + if (!isSimiliarNode(node, sibling)) + continue; + + if (node === sibling) { + xPathIndex = xPathIndexCounter; + if (!unique) + return xPathIndex; + } else { + unique = false; + if (xPathIndex !== -1) + return xPathIndex; + } + + xPathIndexCounter++; + } + + if (unique) + return 0; + + console.assert(xPathIndex > 0, "Should have found the node."); + return xPathIndex; +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/EventListener.js b/Source/WebInspectorUI/UserInterface/Base/EventListener.js new file mode 100644 index 000000000..b009f887d --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/EventListener.js @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014, 2015 Apple Inc. All rights reserved. + * Copyright (C) 2013, 2014 University of Washington. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +WebInspector.EventListener = class EventListener +{ + constructor(thisObject, fireOnce) + { + this._thisObject = thisObject; + this._emitter = null; + this._callback = null; + this._fireOnce = fireOnce; + } + + // Public + + connect(emitter, type, callback, usesCapture) + { + console.assert(!this._emitter && !this._callback, "EventListener already bound to a callback.", this); + console.assert(callback, "Missing callback for event: " + type); + console.assert(emitter, "Missing event emitter for event: " + type); + var emitterIsValid = emitter && (emitter instanceof WebInspector.Object || emitter instanceof Node || (typeof emitter.addEventListener === "function")); + console.assert(emitterIsValid, "Event emitter ", emitter, " (type:" + type + ") is null or does not implement Node or WebInspector.Object!"); + + if (!emitterIsValid || !type || !callback) + return; + + this._emitter = emitter; + this._type = type; + this._usesCapture = !!usesCapture; + + if (emitter instanceof Node) + callback = callback.bind(this._thisObject); + + if (this._fireOnce) { + var listener = this; + this._callback = function() { + listener.disconnect(); + callback.apply(this, arguments); + }; + } else + this._callback = callback; + + if (this._emitter instanceof Node) + this._emitter.addEventListener(this._type, this._callback, this._usesCapture); + else + this._emitter.addEventListener(this._type, this._callback, this._thisObject); + } + + disconnect() + { + console.assert(this._emitter && this._callback, "EventListener is not bound to a callback.", this); + + if (!this._emitter || !this._callback) + return; + + if (this._emitter instanceof Node) + this._emitter.removeEventListener(this._type, this._callback, this._usesCapture); + else + this._emitter.removeEventListener(this._type, this._callback, this._thisObject); + + if (this._fireOnce) + delete this._thisObject; + delete this._emitter; + delete this._type; + delete this._callback; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/EventListenerSet.js b/Source/WebInspectorUI/UserInterface/Base/EventListenerSet.js new file mode 100644 index 000000000..bc392c430 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/EventListenerSet.js @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014, 2015 Apple Inc. All rights reserved. + * Copyright (C) 2013, 2014 University of Washington. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +// This class supports adding and removing many listeners at once. +// Add DOM or Inspector event listeners to the set using `register()`. +// Use `install()` and `uninstall()` to enable or disable all listeners +// in the set at once. + +WebInspector.EventListenerSet = class EventListenerSet +{ + constructor(defaultThisObject, name) + { + this.name = name; + this._defaultThisObject = defaultThisObject; + + this._listeners = []; + this._installed = false; + } + + // Public + + register(emitter, type, callback, thisObject, usesCapture) + { + console.assert(callback, "Missing callback for event: " + type); + console.assert(type, "Tried to register listener for unknown event: " + type); + var emitterIsValid = emitter && (emitter instanceof WebInspector.Object || emitter instanceof Node || (typeof emitter.addEventListener === "function")); + console.assert(emitterIsValid, "Event emitter ", emitter, " (type:" + type + ") is null or does not implement Node or WebInspector.Object!"); + + if (!emitterIsValid || !type || !callback) + return; + + this._listeners.push({listener: new WebInspector.EventListener(thisObject || this._defaultThisObject), emitter, type, callback, usesCapture}); + } + + unregister() + { + if (this._installed) + this.uninstall(); + this._listeners = []; + } + + install() + { + console.assert(!this._installed, "Already installed listener group: " + this.name); + if (this._installed) + return; + + this._installed = true; + + for (var data of this._listeners) + data.listener.connect(data.emitter, data.type, data.callback, data.usesCapture); + } + + uninstall(unregisterListeners) + { + console.assert(this._installed, "Trying to uninstall listener group " + this.name + ", but it isn't installed."); + if (!this._installed) + return; + + this._installed = false; + + for (var data of this._listeners) + data.listener.disconnect(); + + if (unregisterListeners) + this._listeners = []; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js b/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js new file mode 100644 index 000000000..f04831881 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013, 2015 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +function useSVGSymbol(url, className, title) +{ + const svgNamespace = "http://www.w3.org/2000/svg"; + const xlinkNamespace = "http://www.w3.org/1999/xlink"; + + let svgElement = document.createElementNS(svgNamespace, "svg"); + svgElement.style.width = "100%"; + svgElement.style.height = "100%"; + + // URL must contain a fragment reference to a graphical element, like a symbol. If none is given + // append #root which all of our SVGs have on the top level <svg> element. + if (!url.includes("#")) + url += "#root"; + + let useElement = document.createElementNS(svgNamespace, "use"); + useElement.setAttributeNS(xlinkNamespace, "xlink:href", url); + svgElement.appendChild(useElement); + + let wrapper = document.createElement("div"); + wrapper.appendChild(svgElement); + + if (className) + wrapper.className = className; + if (title) + wrapper.title = title; + + return wrapper; +} diff --git a/Source/WebInspectorUI/UserInterface/Base/InspectorFrontendHostStub.js b/Source/WebInspectorUI/UserInterface/Base/InspectorFrontendHostStub.js new file mode 100644 index 000000000..505e9d4af --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/InspectorFrontendHostStub.js @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2013 Seokju Kwon (seokju.kwon@gmail.com) + * Copyright (C) 2013 Apple 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. + */ + +if (!window.InspectorFrontendHost) { + WebInspector.InspectorFrontendHostStub = function() + { + }; + + WebInspector.InspectorFrontendHostStub.prototype = { + // Public + + initializeWebSocket: function(url) + { + var socket = new WebSocket(url); + socket.addEventListener("open", socketReady.bind(this)); + + function socketReady() + { + this._socket = socket; + + this._socket.addEventListener("message", function(message) { InspectorBackend.dispatch(message.data); }); + this._socket.addEventListener("error", function(error) { console.error(error); }); + + this._sendPendingMessagesToBackendIfNeeded(); + } + }, + + bringToFront: function() + { + this._windowVisible = true; + }, + + closeWindow: function() + { + this._windowVisible = false; + }, + + userInterfaceLayoutDirection: function() + { + return "ltr"; + }, + + requestSetDockSide: function(side) + { + InspectorFrontendAPI.setDockSide(side); + }, + + setAttachedWindowHeight: function(height) + { + }, + + setAttachedWindowWidth: function(width) + { + }, + + startWindowDrag: function() + { + }, + + moveWindowBy: function(x, y) + { + }, + + loaded: function() + { + }, + + localizedStringsURL: function() + { + return undefined; + }, + + backendCommandsURL: function() + { + return undefined; + }, + + debuggableType: function() + { + return "web"; + }, + + inspectionLevel: function() + { + return 1; + }, + + inspectedURLChanged: function(title) + { + document.title = title; + }, + + copyText: function(text) + { + this._textToCopy = text; + if (!document.execCommand("copy")) + console.error("Clipboard access is denied"); + }, + + killText: function(text, shouldStartNewSequence) + { + }, + + openInNewTab: function(url) + { + window.open(url, "_blank"); + }, + + save: function(url, content, base64Encoded, forceSaveAs) + { + }, + + sendMessageToBackend: function(message) + { + if (!this._socket) { + if (!this._pendingMessages) + this._pendingMessages = []; + this._pendingMessages.push(message); + return; + } + + this._sendPendingMessagesToBackendIfNeeded(); + + this._socket.send(message); + }, + + platform: function() + { + return (navigator.platform.match(/mac|win|linux/i) || ["other"])[0].toLowerCase(); + }, + + beep: function() + { + }, + + showContextMenu: function(event, menuObject) + { + }, + + unbufferedLog: function() + { + console.log.apply(console, arguments); + }, + + setZoomFactor: function(zoom) + { + }, + + zoomFactor: function() + { + return 1; + }, + + // Private + + _sendPendingMessagesToBackendIfNeeded: function() + { + if (!this._pendingMessages) + return; + + for (var i = 0; i < this._pendingMessages.length; ++i) + this._socket.send(this._pendingMessages[i]); + + delete this._pendingMessages; + } + }; + + InspectorFrontendHost = new WebInspector.InspectorFrontendHostStub; + + WebInspector.dontLocalizeUserInterface = true; +} diff --git a/Source/WebInspectorUI/UserInterface/Base/LinkedList.js b/Source/WebInspectorUI/UserInterface/Base/LinkedList.js new file mode 100644 index 000000000..f9978af42 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/LinkedList.js @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +class LinkedList +{ + constructor() + { + this.head = new LinkedListNode; + this.head.next = this.head.prev = this.head; + this.length = 0; + } + + clear() + { + this.head.next = this.head.prev = this.head; + this.length = 0; + } + + get last() + { + return this.head.prev; + } + + push(item) + { + let newNode = new LinkedListNode(item); + let last = this.last; + let head = this.head; + + last.next = newNode; + newNode.next = head; + head.prev = newNode; + newNode.prev = last; + + this.length++; + + return newNode; + } + + remove(node) + { + if (!node) + return false; + + node.prev.next = node.next; + node.next.prev = node.prev; + + this.length--; + return true; + } + + forEach(callback) + { + let node = this.head; + for (let i = 0, length = this.length; i < length; i++) { + node = node.next; + let returnValue = callback(node.value, i); + if (returnValue === false) + return; + } + } + + toArray() + { + let node = this.head; + let i = this.length; + let result = new Array(i); + while (i--) { + node = node.prev; + result[i] = node.value; + } + return result; + } + + toJSON() + { + return this.toArray(); + } +} + + +class LinkedListNode +{ + constructor(value) + { + this.value = value; + this.prev = null; + this.next = null; + } +} diff --git a/Source/WebInspectorUI/UserInterface/Base/ListMultimap.js b/Source/WebInspectorUI/UserInterface/Base/ListMultimap.js new file mode 100644 index 000000000..a415fb68f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/ListMultimap.js @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +class ListMultimap +{ + constructor() + { + this._insertionOrderedEntries = new LinkedList; + this._keyMap = new Map; + } + + get size() + { + return this._insertionOrderedEntries.length; + } + + add(key, value) + { + let nodeMap = this._keyMap.get(key); + if (!nodeMap) { + nodeMap = new Map; + this._keyMap.set(key, nodeMap); + } + + let node = nodeMap.get(value); + if (!node) { + node = this._insertionOrderedEntries.push([key, value]); + nodeMap.set(value, node); + } + + return this; + } + + delete(key, value) + { + let nodeMap = this._keyMap.get(key); + if (!nodeMap) + return false; + + let node = nodeMap.get(value); + if (!node) + return false; + + nodeMap.delete(value); + this._insertionOrderedEntries.remove(node); + return true; + } + + deleteAll(key) + { + let nodeMap = this._keyMap.get(key); + if (!nodeMap) + return false; + + let list = this._insertionOrderedEntries; + let didDelete = false; + nodeMap.forEach(function(node) { + list.remove(node); + didDelete = true; + }); + + this._keyMap.delete(key); + return didDelete; + } + + has(key, value) + { + let nodeMap = this._keyMap.get(key); + if (!nodeMap) + return false; + + return nodeMap.has(value); + } + + clear() + { + this._keyMap = new Map; + this._insertionOrderedEntries = new LinkedList; + } + + forEach(callback) + { + this._insertionOrderedEntries.forEach(callback); + } + + toArray() + { + return this._insertionOrderedEntries.toArray(); + } + + toJSON() + { + return this.toArray(); + } +} diff --git a/Source/WebInspectorUI/UserInterface/Base/LoadLocalizedStrings.js b/Source/WebInspectorUI/UserInterface/Base/LoadLocalizedStrings.js new file mode 100644 index 000000000..84c6e08fe --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/LoadLocalizedStrings.js @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +(function() { + if (WebInspector.dontLocalizeUserInterface) + return; + + var localizedStringsURL = InspectorFrontendHost.localizedStringsURL(); + console.assert(localizedStringsURL); + if (localizedStringsURL) + document.write("<script src=\"" + localizedStringsURL + "\"></script>"); +})(); diff --git a/Source/WebInspectorUI/UserInterface/Base/MIMETypeUtilities.js b/Source/WebInspectorUI/UserInterface/Base/MIMETypeUtilities.js new file mode 100644 index 000000000..42bced52e --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/MIMETypeUtilities.js @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +WebInspector.fileExtensionForURL = function(url) +{ + var lastPathComponent = parseURL(url).lastPathComponent; + if (!lastPathComponent) + return ""; + + var index = lastPathComponent.indexOf("."); + if (index === -1) + return ""; + + return lastPathComponent.substr(index + 1); +}; + +WebInspector.mimeTypeForFileExtension = function(extension) +{ + const extensionToMIMEType = { + // Document types. + "html": "text/html", + "xhtml": "application/xhtml+xml", + "xml": "text/xml", + + // Script types. + "js": "text/javascript", + "json": "application/json", + "clj": "text/x-clojure", + "coffee": "text/x-coffeescript", + "ls": "text/x-livescript", + "ts": "text/typescript", + + // Stylesheet types. + "css": "text/css", + "less": "text/x-less", + "sass": "text/x-sass", + "scss": "text/x-scss", + + // Image types. + "bmp": "image/bmp", + "gif": "image/gif", + "jpeg": "image/jpeg", + "jpg": "image/jpeg", + "pdf": "application/pdf", + "png": "image/png", + "tif": "image/tiff", + "tiff": "image/tiff", + + // Font types and Media types are ignored for now. + + // Miscellaneous types. + "svg": "image/svg+xml", + "txt": "text/plain", + "xsl": "text/xsl" + }; + + return extensionToMIMEType[extension] || null; +}; + +WebInspector.fileExtensionForMIMEType = function(mimeType) +{ + const mimeTypeToExtension = { + // Document types. + "text/html": "html", + "application/xhtml+xml": "xhtml", + "text/xml": "xml", + + // Script types. + "text/javascript": "js", + "application/json": "json", + "text/x-clojure": "clj", + "text/x-coffeescript": "coffee", + "text/x-livescript": "ls", + "text/typescript": "ts", + + // Stylesheet types. + "text/css": "css", + "text/x-less": "less", + "text/x-sass": "sass", + "text/x-scss": "scss", + + // Image types. + "image/bmp": "bmp", + "image/gif": "gif", + "image/jpeg": "jpg", + "application/pdf": "pdf", + "image/png": "png", + "image/tiff": "tiff", + + // Font types and Media types are ignored for now. + + // Miscellaneous types. + "image/svg+xml": "svg", + "text/plain": "txt", + "text/xsl": "xsl", + }; + + let extension = mimeTypeToExtension[mimeType]; + return extension ? `.${extension}` : null; +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/Main.js b/Source/WebInspectorUI/UserInterface/Base/Main.js new file mode 100644 index 000000000..11495f4ee --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/Main.js @@ -0,0 +1,2693 @@ +/* + * Copyright (C) 2013-2017 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +WebInspector.ContentViewCookieType = { + ApplicationCache: "application-cache", + CookieStorage: "cookie-storage", + Database: "database", + DatabaseTable: "database-table", + DOMStorage: "dom-storage", + Resource: "resource", // includes Frame too. + Timelines: "timelines" +}; + +WebInspector.DebuggableType = { + Web: "web", + JavaScript: "javascript" +}; + +WebInspector.SelectedSidebarPanelCookieKey = "selected-sidebar-panel"; +WebInspector.TypeIdentifierCookieKey = "represented-object-type"; + +WebInspector.StateRestorationType = { + Load: "state-restoration-load", + Navigation: "state-restoration-navigation", + Delayed: "state-restoration-delayed", +}; + +WebInspector.LayoutDirection = { + System: "system", + LTR: "ltr", + RTL: "rtl", +}; + +WebInspector.loaded = function() +{ + // Initialize WebSocket to communication. + this._initializeWebSocketIfNeeded(); + + this.debuggableType = InspectorFrontendHost.debuggableType() === "web" ? WebInspector.DebuggableType.Web : WebInspector.DebuggableType.JavaScript; + this.hasExtraDomains = false; + + // Register observers for events from the InspectorBackend. + if (InspectorBackend.registerInspectorDispatcher) + InspectorBackend.registerInspectorDispatcher(new WebInspector.InspectorObserver); + if (InspectorBackend.registerPageDispatcher) + InspectorBackend.registerPageDispatcher(new WebInspector.PageObserver); + if (InspectorBackend.registerConsoleDispatcher) + InspectorBackend.registerConsoleDispatcher(new WebInspector.ConsoleObserver); + if (InspectorBackend.registerNetworkDispatcher) + InspectorBackend.registerNetworkDispatcher(new WebInspector.NetworkObserver); + if (InspectorBackend.registerDOMDispatcher) + InspectorBackend.registerDOMDispatcher(new WebInspector.DOMObserver); + if (InspectorBackend.registerDebuggerDispatcher) + InspectorBackend.registerDebuggerDispatcher(new WebInspector.DebuggerObserver); + if (InspectorBackend.registerHeapDispatcher) + InspectorBackend.registerHeapDispatcher(new WebInspector.HeapObserver); + if (InspectorBackend.registerMemoryDispatcher) + InspectorBackend.registerMemoryDispatcher(new WebInspector.MemoryObserver); + if (InspectorBackend.registerDatabaseDispatcher) + InspectorBackend.registerDatabaseDispatcher(new WebInspector.DatabaseObserver); + if (InspectorBackend.registerDOMStorageDispatcher) + InspectorBackend.registerDOMStorageDispatcher(new WebInspector.DOMStorageObserver); + if (InspectorBackend.registerApplicationCacheDispatcher) + InspectorBackend.registerApplicationCacheDispatcher(new WebInspector.ApplicationCacheObserver); + if (InspectorBackend.registerScriptProfilerDispatcher) + InspectorBackend.registerScriptProfilerDispatcher(new WebInspector.ScriptProfilerObserver); + if (InspectorBackend.registerTimelineDispatcher) + InspectorBackend.registerTimelineDispatcher(new WebInspector.TimelineObserver); + if (InspectorBackend.registerCSSDispatcher) + InspectorBackend.registerCSSDispatcher(new WebInspector.CSSObserver); + if (InspectorBackend.registerLayerTreeDispatcher) + InspectorBackend.registerLayerTreeDispatcher(new WebInspector.LayerTreeObserver); + if (InspectorBackend.registerRuntimeDispatcher) + InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeObserver); + if (InspectorBackend.registerWorkerDispatcher) + InspectorBackend.registerWorkerDispatcher(new WebInspector.WorkerObserver); + if (InspectorBackend.registerReplayDispatcher) + InspectorBackend.registerReplayDispatcher(new WebInspector.ReplayObserver); + + // Main backend target. + WebInspector.mainTarget = new WebInspector.MainTarget; + + // Enable agents. + InspectorAgent.enable(); + + // Perform one-time tasks. + WebInspector.CSSCompletions.requestCSSCompletions(); + + // Listen for the ProvisionalLoadStarted event before registering for events so our code gets called before any managers or sidebars. + // This lets us save a state cookie before any managers or sidebars do any resets that would affect state (namely TimelineManager). + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this); + + // Create the singleton managers next, before the user interface elements, so the user interface can register + // as event listeners on these managers. + this.targetManager = new WebInspector.TargetManager; + this.branchManager = new WebInspector.BranchManager; + this.frameResourceManager = new WebInspector.FrameResourceManager; + this.storageManager = new WebInspector.StorageManager; + this.domTreeManager = new WebInspector.DOMTreeManager; + this.cssStyleManager = new WebInspector.CSSStyleManager; + this.logManager = new WebInspector.LogManager; + this.issueManager = new WebInspector.IssueManager; + this.analyzerManager = new WebInspector.AnalyzerManager; + this.runtimeManager = new WebInspector.RuntimeManager; + this.heapManager = new WebInspector.HeapManager; + this.memoryManager = new WebInspector.MemoryManager; + this.applicationCacheManager = new WebInspector.ApplicationCacheManager; + this.timelineManager = new WebInspector.TimelineManager; + this.debuggerManager = new WebInspector.DebuggerManager; + this.sourceMapManager = new WebInspector.SourceMapManager; + this.layerTreeManager = new WebInspector.LayerTreeManager; + this.dashboardManager = new WebInspector.DashboardManager; + this.probeManager = new WebInspector.ProbeManager; + this.workerManager = new WebInspector.WorkerManager; + this.replayManager = new WebInspector.ReplayManager; + + // Enable the Console Agent after creating the singleton managers. + ConsoleAgent.enable(); + + // Tell the backend we are initialized after all our initialization messages have been sent. + setTimeout(function() { + // COMPATIBILITY (iOS 8): Inspector.initialized did not exist yet. + if (InspectorAgent.initialized) + InspectorAgent.initialized(); + }, 0); + + // Register for events. + this.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStarted, this._captureDidStart, this); + this.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this); + this.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this); + this.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.InspectModeStateChanged, this._inspectModeStateChanged, this); + this.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, this._domNodeWasInspected, this); + this.storageManager.addEventListener(WebInspector.StorageManager.Event.DOMStorageObjectWasInspected, this._storageWasInspected, this); + this.storageManager.addEventListener(WebInspector.StorageManager.Event.DatabaseWasInspected, this._storageWasInspected, this); + this.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.MainFrameDidChange, this._mainFrameDidChange, this); + this.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasAdded, this._frameWasAdded, this); + + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); + + document.addEventListener("DOMContentLoaded", this.contentLoaded.bind(this)); + + // Create settings. + this._showingSplitConsoleSetting = new WebInspector.Setting("showing-split-console", false); + this._splitConsoleHeightSetting = new WebInspector.Setting("split-console-height", 150); + + this._openTabsSetting = new WebInspector.Setting("open-tab-types", ["elements", "network", "resources", "timeline", "debugger", "storage", "console"]); + this._selectedTabIndexSetting = new WebInspector.Setting("selected-tab-index", 0); + + this.showShadowDOMSetting = new WebInspector.Setting("show-shadow-dom", false); + this.showReplayInterfaceSetting = new WebInspector.Setting("show-web-replay", false); + + // COMPATIBILITY (iOS 8): Page.enableTypeProfiler did not exist. + this.showJavaScriptTypeInformationSetting = new WebInspector.Setting("show-javascript-type-information", false); + this.showJavaScriptTypeInformationSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showJavaScriptTypeInformationSettingChanged, this); + if (this.showJavaScriptTypeInformationSetting.value && window.RuntimeAgent && RuntimeAgent.enableTypeProfiler) + RuntimeAgent.enableTypeProfiler(); + + this.enableControlFlowProfilerSetting = new WebInspector.Setting("enable-control-flow-profiler", false); + this.enableControlFlowProfilerSetting.addEventListener(WebInspector.Setting.Event.Changed, this._enableControlFlowProfilerSettingChanged, this); + if (this.enableControlFlowProfilerSetting.value && window.RuntimeAgent && RuntimeAgent.enableControlFlowProfiler) + RuntimeAgent.enableControlFlowProfiler(); + + // COMPATIBILITY (iOS 8): Page.setShowPaintRects did not exist. + this.showPaintRectsSetting = new WebInspector.Setting("show-paint-rects", false); + if (this.showPaintRectsSetting.value && window.PageAgent && PageAgent.setShowPaintRects) + PageAgent.setShowPaintRects(true); + + this.showPrintStylesSetting = new WebInspector.Setting("show-print-styles", false); + if (this.showPrintStylesSetting.value && window.PageAgent) + PageAgent.setEmulatedMedia("print"); + + this.setZoomFactor(WebInspector.settings.zoomFactor.value); + + this.mouseCoords = { + x: 0, + y: 0 + }; + + this.visible = false; + + this._windowKeydownListeners = []; +}; + +WebInspector.contentLoaded = function() +{ + // If there was an uncaught exception earlier during loading, then + // abort loading more content. We could be in an inconsistent state. + if (window.__uncaughtExceptions) + return; + + // Register for global events. + document.addEventListener("beforecopy", this._beforecopy.bind(this)); + document.addEventListener("copy", this._copy.bind(this)); + + document.addEventListener("click", this._mouseWasClicked.bind(this)); + document.addEventListener("dragover", this._dragOver.bind(this)); + document.addEventListener("focus", WebInspector._focusChanged.bind(this), true); + + window.addEventListener("focus", this._windowFocused.bind(this)); + window.addEventListener("blur", this._windowBlurred.bind(this)); + window.addEventListener("resize", this._windowResized.bind(this)); + window.addEventListener("keydown", this._windowKeyDown.bind(this)); + window.addEventListener("keyup", this._windowKeyUp.bind(this)); + window.addEventListener("mousedown", this._mouseDown.bind(this), true); + window.addEventListener("mousemove", this._mouseMoved.bind(this), true); + window.addEventListener("pagehide", this._pageHidden.bind(this)); + window.addEventListener("contextmenu", this._contextMenuRequested.bind(this)); + + // Add platform style classes so the UI can be tweaked per-platform. + document.body.classList.add(WebInspector.Platform.name + "-platform"); + if (WebInspector.Platform.isNightlyBuild) + document.body.classList.add("nightly-build"); + + if (WebInspector.Platform.name === "mac") { + document.body.classList.add(WebInspector.Platform.version.name); + + if (WebInspector.Platform.version.release >= 11) + document.body.classList.add("latest-mac"); + else + document.body.classList.add("legacy-mac"); + } + + document.body.classList.add(this.debuggableType); + document.body.setAttribute("dir", this.resolvedLayoutDirection()); + + function setTabSize() { + document.body.style.tabSize = WebInspector.settings.tabSize.value; + } + WebInspector.settings.tabSize.addEventListener(WebInspector.Setting.Event.Changed, setTabSize); + setTabSize(); + + function setInvalidCharacterClassName() { + document.body.classList.toggle("show-invalid-characters", WebInspector.settings.showInvalidCharacters.value); + } + WebInspector.settings.showInvalidCharacters.addEventListener(WebInspector.Setting.Event.Changed, setInvalidCharacterClassName); + setInvalidCharacterClassName(); + + function setWhitespaceCharacterClassName() { + document.body.classList.toggle("show-whitespace-characters", WebInspector.settings.showWhitespaceCharacters.value); + } + WebInspector.settings.showWhitespaceCharacters.addEventListener(WebInspector.Setting.Event.Changed, setWhitespaceCharacterClassName); + setWhitespaceCharacterClassName(); + + this.settingsTabContentView = new WebInspector.SettingsTabContentView; + + this._settingsKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Comma, this._showSettingsTab.bind(this)); + + // Create the user interface elements. + this.toolbar = new WebInspector.Toolbar(document.getElementById("toolbar"), null, true); + this.toolbar.displayMode = WebInspector.Toolbar.DisplayMode.IconOnly; + this.toolbar.sizeMode = WebInspector.Toolbar.SizeMode.Small; + + this.tabBar = new WebInspector.TabBar(document.getElementById("tab-bar")); + this.tabBar.addEventListener(WebInspector.TabBar.Event.OpenDefaultTab, this._openDefaultTab, this); + + this._contentElement = document.getElementById("content"); + this._contentElement.setAttribute("role", "main"); + this._contentElement.setAttribute("aria-label", WebInspector.UIString("Content")); + + const disableBackForward = true; + const disableFindBanner = false; + this.splitContentBrowser = new WebInspector.ContentBrowser(document.getElementById("split-content-browser"), this, disableBackForward, disableFindBanner); + this.splitContentBrowser.navigationBar.element.addEventListener("mousedown", this._consoleResizerMouseDown.bind(this)); + + this.clearKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "K", this._clear.bind(this)); + + this.quickConsole = new WebInspector.QuickConsole(document.getElementById("quick-console")); + this.quickConsole.addEventListener(WebInspector.QuickConsole.Event.DidResize, this._quickConsoleDidResize, this); + + this._consoleRepresentedObject = new WebInspector.LogObject; + this._consoleTreeElement = new WebInspector.LogTreeElement(this._consoleRepresentedObject); + this.consoleContentView = WebInspector.splitContentBrowser.contentViewForRepresentedObject(this._consoleRepresentedObject); + this.consoleLogViewController = this.consoleContentView.logViewController; + this.breakpointPopoverController = new WebInspector.BreakpointPopoverController; + + // FIXME: The sidebars should be flipped in RTL languages. + this.navigationSidebar = new WebInspector.Sidebar(document.getElementById("navigation-sidebar"), WebInspector.Sidebar.Sides.Left); + this.navigationSidebar.addEventListener(WebInspector.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this); + + this.detailsSidebar = new WebInspector.Sidebar(document.getElementById("details-sidebar"), WebInspector.Sidebar.Sides.Right, null, null, WebInspector.UIString("Details"), true); + this.detailsSidebar.addEventListener(WebInspector.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this); + + this.searchKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "F", this._focusSearchField.bind(this)); + this._findKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "F", this._find.bind(this)); + this._saveKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "S", this._save.bind(this)); + this._saveAsKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "S", this._saveAs.bind(this)); + + this.openResourceKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "O", this._showOpenResourceDialog.bind(this)); + new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "P", this._showOpenResourceDialog.bind(this)); + + this.navigationSidebarKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "0", this.toggleNavigationSidebar.bind(this)); + this.detailsSidebarKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Option, "0", this.toggleDetailsSidebar.bind(this)); + + let boundIncreaseZoom = this._increaseZoom.bind(this); + let boundDecreaseZoom = this._decreaseZoom.bind(this); + this._increaseZoomKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Plus, boundIncreaseZoom); + this._decreaseZoomKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Minus, boundDecreaseZoom); + this._increaseZoomKeyboardShortcut2 = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, WebInspector.KeyboardShortcut.Key.Plus, boundIncreaseZoom); + this._decreaseZoomKeyboardShortcut2 = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, WebInspector.KeyboardShortcut.Key.Minus, boundDecreaseZoom); + this._resetZoomKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "0", this._resetZoom.bind(this)); + + this._showTabAtIndexKeyboardShortcuts = [1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Option, `${i}`, this._showTabAtIndex.bind(this, i))); + this._openNewTabKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Option, "T", this.showNewTabTab.bind(this)); + + this.tabBrowser = new WebInspector.TabBrowser(document.getElementById("tab-browser"), this.tabBar, this.navigationSidebar, this.detailsSidebar); + this.tabBrowser.addEventListener(WebInspector.TabBrowser.Event.SelectedTabContentViewDidChange, this._tabBrowserSelectedTabContentViewDidChange, this); + + this._reloadPageKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "R", this._reloadPage.bind(this)); + this._reloadPageIgnoringCacheKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "R", this._reloadPageIgnoringCache.bind(this)); + this._reloadPageKeyboardShortcut.implicitlyPreventsDefault = this._reloadPageIgnoringCacheKeyboardShortcut.implicitlyPreventsDefault = false; + + this._consoleTabKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Option | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "C", this._showConsoleTab.bind(this)); + this._quickConsoleKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, WebInspector.KeyboardShortcut.Key.Apostrophe, this._focusConsolePrompt.bind(this)); + + this._inspectModeKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "C", this._toggleInspectMode.bind(this)); + + this._undoKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "Z", this._undoKeyboardShortcut.bind(this)); + this._redoKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "Z", this._redoKeyboardShortcut.bind(this)); + this._undoKeyboardShortcut.implicitlyPreventsDefault = this._redoKeyboardShortcut.implicitlyPreventsDefault = false; + + this.toggleBreakpointsKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "Y", this.debuggerToggleBreakpoints.bind(this)); + this.pauseOrResumeKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "Y", this.debuggerPauseResumeToggle.bind(this)); + this.stepOverKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.F6, this.debuggerStepOver.bind(this)); + this.stepIntoKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.F7, this.debuggerStepInto.bind(this)); + this.stepOutKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.F8, this.debuggerStepOut.bind(this)); + + this.pauseOrResumeAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Backslash, this.debuggerPauseResumeToggle.bind(this)); + this.stepOverAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.SingleQuote, this.debuggerStepOver.bind(this)); + this.stepIntoAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Semicolon, this.debuggerStepInto.bind(this)); + this.stepOutAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Semicolon, this.debuggerStepOut.bind(this)); + + this._closeToolbarButton = new WebInspector.ControlToolbarItem("dock-close", WebInspector.UIString("Close"), "Images/Close.svg", 16, 14); + this._closeToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this.close, this); + + this._undockToolbarButton = new WebInspector.ButtonToolbarItem("undock", WebInspector.UIString("Detach into separate window"), null, "Images/Undock.svg"); + this._undockToolbarButton.element.classList.add(WebInspector.Popover.IgnoreAutoDismissClassName); + this._undockToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._undock, this); + + let dockImage = WebInspector.resolvedLayoutDirection() === WebInspector.LayoutDirection.RTL ? "Images/DockLeft.svg" : "Images/DockRight.svg"; + this._dockToSideToolbarButton = new WebInspector.ButtonToolbarItem("dock-right", WebInspector.UIString("Dock to side of window"), null, dockImage); + this._dockToSideToolbarButton.element.classList.add(WebInspector.Popover.IgnoreAutoDismissClassName); + + let dockToSideCallback = WebInspector.resolvedLayoutDirection() === WebInspector.LayoutDirection.RTL ? this._dockLeft : this._dockRight; + this._dockToSideToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, dockToSideCallback, this); + + this._dockBottomToolbarButton = new WebInspector.ButtonToolbarItem("dock-bottom", WebInspector.UIString("Dock to bottom of window"), null, "Images/DockBottom.svg"); + this._dockBottomToolbarButton.element.classList.add(WebInspector.Popover.IgnoreAutoDismissClassName); + this._dockBottomToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._dockBottom, this); + + this._togglePreviousDockConfigurationKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "D", this._togglePreviousDockConfiguration.bind(this)); + + var toolTip; + if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript) + toolTip = WebInspector.UIString("Restart (%s)").format(this._reloadPageKeyboardShortcut.displayName); + else + toolTip = WebInspector.UIString("Reload page (%s)\nReload ignoring cache (%s)").format(this._reloadPageKeyboardShortcut.displayName, this._reloadPageIgnoringCacheKeyboardShortcut.displayName); + + this._reloadToolbarButton = new WebInspector.ButtonToolbarItem("reload", toolTip, null, "Images/ReloadToolbar.svg"); + this._reloadToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._reloadPageClicked, this); + + this._downloadToolbarButton = new WebInspector.ButtonToolbarItem("download", WebInspector.UIString("Download Web Archive"), null, "Images/DownloadArrow.svg"); + this._downloadToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._downloadWebArchive, this); + + this._updateReloadToolbarButton(); + this._updateDownloadToolbarButton(); + + // The toolbar button for node inspection. + if (this.debuggableType === WebInspector.DebuggableType.Web) { + var toolTip = WebInspector.UIString("Start element selection (%s)").format(WebInspector._inspectModeKeyboardShortcut.displayName); + var activatedToolTip = WebInspector.UIString("Stop element selection (%s)").format(WebInspector._inspectModeKeyboardShortcut.displayName); + this._inspectModeToolbarButton = new WebInspector.ActivateButtonToolbarItem("inspect", toolTip, activatedToolTip, null, "Images/Crosshair.svg"); + this._inspectModeToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleInspectMode, this); + } + + this._dashboardContainer = new WebInspector.DashboardContainerView; + this._dashboardContainer.showDashboardViewForRepresentedObject(this.dashboardManager.dashboards.default); + + this._searchToolbarItem = new WebInspector.SearchBar("inspector-search", WebInspector.UIString("Search"), null, true); + this._searchToolbarItem.addEventListener(WebInspector.SearchBar.Event.TextChanged, this._searchTextDidChange, this); + + this.toolbar.addToolbarItem(this._closeToolbarButton, WebInspector.Toolbar.Section.Control); + + this.toolbar.addToolbarItem(this._undockToolbarButton, WebInspector.Toolbar.Section.Left); + this.toolbar.addToolbarItem(this._dockToSideToolbarButton, WebInspector.Toolbar.Section.Left); + this.toolbar.addToolbarItem(this._dockBottomToolbarButton, WebInspector.Toolbar.Section.Left); + + this.toolbar.addToolbarItem(this._reloadToolbarButton, WebInspector.Toolbar.Section.CenterLeft); + this.toolbar.addToolbarItem(this._downloadToolbarButton, WebInspector.Toolbar.Section.CenterLeft); + + this.toolbar.addToolbarItem(this._dashboardContainer.toolbarItem, WebInspector.Toolbar.Section.Center); + + if (this._inspectModeToolbarButton) + this.toolbar.addToolbarItem(this._inspectModeToolbarButton, WebInspector.Toolbar.Section.CenterRight); + + this.toolbar.addToolbarItem(this._searchToolbarItem, WebInspector.Toolbar.Section.Right); + + this.resourceDetailsSidebarPanel = new WebInspector.ResourceDetailsSidebarPanel; + this.domNodeDetailsSidebarPanel = new WebInspector.DOMNodeDetailsSidebarPanel; + this.cssStyleDetailsSidebarPanel = new WebInspector.CSSStyleDetailsSidebarPanel; + this.applicationCacheDetailsSidebarPanel = new WebInspector.ApplicationCacheDetailsSidebarPanel; + this.indexedDatabaseDetailsSidebarPanel = new WebInspector.IndexedDatabaseDetailsSidebarPanel; + this.scopeChainDetailsSidebarPanel = new WebInspector.ScopeChainDetailsSidebarPanel; + this.probeDetailsSidebarPanel = new WebInspector.ProbeDetailsSidebarPanel; + + if (window.LayerTreeAgent) + this.layerTreeDetailsSidebarPanel = new WebInspector.LayerTreeDetailsSidebarPanel; + + this.modifierKeys = {altKey: false, metaKey: false, shiftKey: false}; + + let dockedResizerElement = document.getElementById("docked-resizer"); + dockedResizerElement.classList.add(WebInspector.Popover.IgnoreAutoDismissClassName); + dockedResizerElement.addEventListener("mousedown", this._dockedResizerMouseDown.bind(this)); + + this._dockingAvailable = false; + + this._updateDockNavigationItems(); + this._setupViewHierarchy(); + + // These tabs are always available for selecting, modulo isTabAllowed(). + // Other tabs may be engineering-only or toggled at runtime if incomplete. + let productionTabClasses = [ + WebInspector.ConsoleTabContentView, + WebInspector.DebuggerTabContentView, + WebInspector.ElementsTabContentView, + WebInspector.NetworkTabContentView, + WebInspector.NewTabContentView, + WebInspector.ResourcesTabContentView, + WebInspector.SearchTabContentView, + WebInspector.SettingsTabContentView, + WebInspector.StorageTabContentView, + WebInspector.TimelineTabContentView, + ]; + + this._knownTabClassesByType = new Map; + // Set tab classes directly. The public API triggers other updates and + // notifications that won't work or have no listeners at this point. + for (let tabClass of productionTabClasses) + this._knownTabClassesByType.set(tabClass.Type, tabClass); + + this._pendingOpenTabs = []; + + // Previously we may have stored duplicates in this setting. Avoid creating duplicate tabs. + let openTabTypes = this._openTabsSetting.value; + let seenTabTypes = new Set; + + for (let i = 0; i < openTabTypes.length; ++i) { + let tabType = openTabTypes[i]; + + if (seenTabTypes.has(tabType)) + continue; + seenTabTypes.add(tabType); + + if (!this.isTabTypeAllowed(tabType)) { + this._pendingOpenTabs.push({tabType, index: i}); + continue; + } + + let tabContentView = this._createTabContentViewForType(tabType); + if (!tabContentView) + continue; + this.tabBrowser.addTabForContentView(tabContentView, true); + } + + this._restoreCookieForOpenTabs(WebInspector.StateRestorationType.Load); + + this.tabBar.selectedTabBarItem = this._selectedTabIndexSetting.value; + + if (!this.tabBar.selectedTabBarItem) + this.tabBar.selectedTabBarItem = 0; + + if (!this.tabBar.normalTabCount) + this.showNewTabTab(); + + // Listen to the events after restoring the saved tabs to avoid recursion. + this.tabBar.addEventListener(WebInspector.TabBar.Event.TabBarItemAdded, this._rememberOpenTabs, this); + this.tabBar.addEventListener(WebInspector.TabBar.Event.TabBarItemRemoved, this._rememberOpenTabs, this); + this.tabBar.addEventListener(WebInspector.TabBar.Event.TabBarItemsReordered, this._rememberOpenTabs, this); + + // Signal that the frontend is now ready to receive messages. + InspectorFrontendAPI.loadCompleted(); + + // Tell the InspectorFrontendHost we loaded, which causes the window to display + // and pending InspectorFrontendAPI commands to be sent. + InspectorFrontendHost.loaded(); + + this._updateSplitConsoleHeight(this._splitConsoleHeightSetting.value); + + if (this._showingSplitConsoleSetting.value) + this.showSplitConsole(); + + // Store this on the window in case the WebInspector global gets corrupted. + window.__frontendCompletedLoad = true; + + if (this.runBootstrapOperations) + this.runBootstrapOperations(); +}; + +WebInspector.isTabTypeAllowed = function(tabType) +{ + let tabClass = this._knownTabClassesByType.get(tabType); + if (!tabClass) + return false; + + return tabClass.isTabAllowed(); +}; + +WebInspector.knownTabClasses = function() +{ + return new Set(this._knownTabClassesByType.values()); +}; + +WebInspector._showOpenResourceDialog = function() +{ + if (!this._openResourceDialog) + this._openResourceDialog = new WebInspector.OpenResourceDialog(this); + + if (this._openResourceDialog.visible) + return; + + this._openResourceDialog.present(this._contentElement); +}; + +WebInspector._createTabContentViewForType = function(tabType) +{ + let tabClass = this._knownTabClassesByType.get(tabType); + if (!tabClass) { + console.error("Unknown tab type", tabType); + return null; + } + + console.assert(WebInspector.TabContentView.isPrototypeOf(tabClass)); + return new tabClass; +}; + +WebInspector._rememberOpenTabs = function() +{ + let seenTabTypes = new Set; + let openTabs = []; + + for (let tabBarItem of this.tabBar.tabBarItems) { + let tabContentView = tabBarItem.representedObject; + if (!(tabContentView instanceof WebInspector.TabContentView)) + continue; + if (!tabContentView.constructor.shouldSaveTab()) + continue; + console.assert(tabContentView.type, "Tab type can't be null, undefined, or empty string", tabContentView.type, tabContentView); + openTabs.push(tabContentView.type); + seenTabTypes.add(tabContentView.type); + } + + // Keep currently unsupported tabs in the setting at their previous index. + for (let {tabType, index} of this._pendingOpenTabs) { + if (seenTabTypes.has(tabType)) + continue; + openTabs.insertAtIndex(tabType, index); + seenTabTypes.add(tabType); + } + + this._openTabsSetting.value = openTabs; +}; + +WebInspector._openDefaultTab = function(event) +{ + this.showNewTabTab(); +}; + +WebInspector._showSettingsTab = function(event) +{ + this.tabBrowser.showTabForContentView(this.settingsTabContentView); +}; + +WebInspector._tryToRestorePendingTabs = function() +{ + let stillPendingOpenTabs = []; + for (let {tabType, index} of this._pendingOpenTabs) { + if (!this.isTabTypeAllowed(tabType)) { + stillPendingOpenTabs.push({tabType, index}); + continue; + } + + let tabContentView = this._createTabContentViewForType(tabType); + if (!tabContentView) + continue; + + this.tabBrowser.addTabForContentView(tabContentView, true, index); + + tabContentView.restoreStateFromCookie(WebInspector.StateRestorationType.Load); + } + + this._pendingOpenTabs = stillPendingOpenTabs; + + this.tabBrowser.tabBar.updateNewTabTabBarItemState(); +}; + +WebInspector.showNewTabTab = function(shouldAnimate) +{ + if (!this.isNewTabWithTypeAllowed(WebInspector.NewTabContentView.Type)) + return; + + let tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.NewTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.NewTabContentView; + this.tabBrowser.showTabForContentView(tabContentView, !shouldAnimate); +}; + +WebInspector.isNewTabWithTypeAllowed = function(tabType) +{ + let tabClass = this._knownTabClassesByType.get(tabType); + if (!tabClass || !tabClass.isTabAllowed()) + return false; + + // Only allow one tab per class for now. + for (let tabBarItem of this.tabBar.tabBarItems) { + let tabContentView = tabBarItem.representedObject; + if (!(tabContentView instanceof WebInspector.TabContentView)) + continue; + if (tabContentView.constructor === tabClass) + return false; + } + + if (tabClass === WebInspector.NewTabContentView) { + let allTabs = Array.from(this.knownTabClasses()); + let addableTabs = allTabs.filter((tabClass) => !tabClass.isEphemeral()); + let canMakeNewTab = addableTabs.some((tabClass) => WebInspector.isNewTabWithTypeAllowed(tabClass.Type)); + return canMakeNewTab; + } + + return true; +}; + +WebInspector.createNewTabWithType = function(tabType, options = {}) +{ + console.assert(this.isNewTabWithTypeAllowed(tabType)); + + let {referencedView, shouldReplaceTab, shouldShowNewTab} = options; + console.assert(!referencedView || referencedView instanceof WebInspector.TabContentView, referencedView); + console.assert(!shouldReplaceTab || referencedView, "Must provide a reference view to replace a tab."); + + let tabContentView = this._createTabContentViewForType(tabType); + const suppressAnimations = true; + let insertionIndex = referencedView ? this.tabBar.tabBarItems.indexOf(referencedView.tabBarItem) : undefined; + this.tabBrowser.addTabForContentView(tabContentView, suppressAnimations, insertionIndex); + + if (shouldReplaceTab) + this.tabBrowser.closeTabForContentView(referencedView, suppressAnimations); + + if (shouldShowNewTab) + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.registerTabClass = function(tabClass) +{ + console.assert(WebInspector.TabContentView.isPrototypeOf(tabClass)); + if (!WebInspector.TabContentView.isPrototypeOf(tabClass)) + return; + + if (this._knownTabClassesByType.has(tabClass.Type)) + return; + + this._knownTabClassesByType.set(tabClass.Type, tabClass); + + this._tryToRestorePendingTabs(); + this.notifications.dispatchEventToListeners(WebInspector.Notification.TabTypesChanged); +}; + +WebInspector.activateExtraDomains = function(domains) +{ + this.hasExtraDomains = true; + + for (var domain of domains) { + var agent = InspectorBackend.activateDomain(domain); + if (agent.enable) + agent.enable(); + } + + this.notifications.dispatchEventToListeners(WebInspector.Notification.ExtraDomainsActivated, {"domains": domains}); + + WebInspector.CSSCompletions.requestCSSCompletions(); + + this._updateReloadToolbarButton(); + this._updateDownloadToolbarButton(); + this._tryToRestorePendingTabs(); +}; + +WebInspector.contentBrowserTreeElementForRepresentedObject = function(contentBrowser, representedObject) +{ + // The console does not have a sidebar, so return a tree element here so something is shown. + if (representedObject === this._consoleRepresentedObject) + return this._consoleTreeElement; + return null; +}; + +WebInspector.updateWindowTitle = function() +{ + var mainFrame = this.frameResourceManager.mainFrame; + if (!mainFrame) + return; + + var urlComponents = mainFrame.mainResource.urlComponents; + + var lastPathComponent; + try { + lastPathComponent = decodeURIComponent(urlComponents.lastPathComponent || ""); + } catch (e) { + lastPathComponent = urlComponents.lastPathComponent; + } + + // Build a title based on the URL components. + if (urlComponents.host && lastPathComponent) + var title = this.displayNameForHost(urlComponents.host) + " \u2014 " + lastPathComponent; + else if (urlComponents.host) + var title = this.displayNameForHost(urlComponents.host); + else if (lastPathComponent) + var title = lastPathComponent; + else + var title = mainFrame.url; + + // The name "inspectedURLChanged" sounds like the whole URL is required, however this is only + // used for updating the window title and it can be any string. + InspectorFrontendHost.inspectedURLChanged(title); +}; + +WebInspector.updateDockingAvailability = function(available) +{ + this._dockingAvailable = available; + + this._updateDockNavigationItems(); +}; + +WebInspector.updateDockedState = function(side) +{ + if (this._dockConfiguration === side) + return; + + this._previousDockConfiguration = this._dockConfiguration; + + if (!this._previousDockConfiguration) { + if (side === WebInspector.DockConfiguration.Right || side === WebInspector.DockConfiguration.Left) + this._previousDockConfiguration = WebInspector.DockConfiguration.Bottom; + else + this._previousDockConfiguration = WebInspector.resolvedLayoutDirection() === WebInspector.LayoutDirection.RTL ? WebInspector.DockConfiguration.Left : WebInspector.DockConfiguration.Right; + } + + this._dockConfiguration = side; + + this.docked = side !== WebInspector.DockConfiguration.Undocked; + + this._ignoreToolbarModeDidChangeEvents = true; + + if (side === WebInspector.DockConfiguration.Bottom) { + document.body.classList.add("docked", WebInspector.DockConfiguration.Bottom); + document.body.classList.remove("window-inactive", WebInspector.DockConfiguration.Right, WebInspector.DockConfiguration.Left); + } else if (side === WebInspector.DockConfiguration.Right) { + document.body.classList.add("docked", WebInspector.DockConfiguration.Right); + document.body.classList.remove("window-inactive", WebInspector.DockConfiguration.Bottom, WebInspector.DockConfiguration.Left); + } else if (side === WebInspector.DockConfiguration.Left) { + document.body.classList.add("docked", WebInspector.DockConfiguration.Left); + document.body.classList.remove("window-inactive", WebInspector.DockConfiguration.Bottom, WebInspector.DockConfiguration.Right); + } else + document.body.classList.remove("docked", WebInspector.DockConfiguration.Right, WebInspector.DockConfiguration.Left, WebInspector.DockConfiguration.Bottom); + + this._ignoreToolbarModeDidChangeEvents = false; + + this._updateDockNavigationItems(); + + if (!this.dockedConfigurationSupportsSplitContentBrowser() && !this.doesCurrentTabSupportSplitContentBrowser()) + this.hideSplitConsole(); +}; + +WebInspector.updateVisibilityState = function(visible) +{ + this.visible = visible; + this.notifications.dispatchEventToListeners(WebInspector.Notification.VisibilityStateDidChange); +}; + +WebInspector.handlePossibleLinkClick = function(event, frame, alwaysOpenExternally) +{ + var anchorElement = event.target.enclosingNodeOrSelfWithNodeName("a"); + if (!anchorElement || !anchorElement.href) + return false; + + if (WebInspector.isBeingEdited(anchorElement)) { + // Don't follow the link when it is being edited. + return false; + } + + // Prevent the link from navigating, since we don't do any navigation by following links normally. + event.preventDefault(); + event.stopPropagation(); + + this.openURL(anchorElement.href, frame, false, anchorElement.lineNumber); + + return true; +}; + +WebInspector.openURL = function(url, frame, alwaysOpenExternally, lineNumber) +{ + console.assert(url); + if (!url) + return; + + console.assert(typeof lineNumber === "undefined" || typeof lineNumber === "number", "lineNumber should be a number."); + + // If alwaysOpenExternally is not defined, base it off the command/meta key for the current event. + if (alwaysOpenExternally === undefined || alwaysOpenExternally === null) + alwaysOpenExternally = window.event ? window.event.metaKey : false; + + if (alwaysOpenExternally) { + InspectorFrontendHost.openInNewTab(url); + return; + } + + var searchChildFrames = false; + if (!frame) { + frame = this.frameResourceManager.mainFrame; + searchChildFrames = true; + } + + console.assert(frame); + + // WebInspector.Frame.resourceForURL does not check the main resource, only sub-resources. So check both. + let simplifiedURL = removeURLFragment(url); + var resource = frame.url === simplifiedURL ? frame.mainResource : frame.resourceForURL(simplifiedURL, searchChildFrames); + if (resource) { + var position = new WebInspector.SourceCodePosition(lineNumber, 0); + this.showSourceCode(resource, position); + return; + } + + InspectorFrontendHost.openInNewTab(url); +}; + +WebInspector.close = function() +{ + if (this._isClosing) + return; + + this._isClosing = true; + + InspectorFrontendHost.closeWindow(); +}; + +WebInspector.saveDataToFile = function(saveData, forceSaveAs) +{ + console.assert(saveData); + if (!saveData) + return; + + if (typeof saveData.customSaveHandler === "function") { + saveData.customSaveHandler(forceSaveAs); + return; + } + + console.assert(saveData.url); + console.assert(saveData.content); + if (!saveData.url || !saveData.content) + return; + + let suggestedName = parseURL(saveData.url).lastPathComponent; + if (!suggestedName) { + suggestedName = WebInspector.UIString("Untitled"); + let dataURLTypeMatch = /^data:([^;]+)/.exec(saveData.url); + if (dataURLTypeMatch) + suggestedName += WebInspector.fileExtensionForMIMEType(dataURLTypeMatch[1]) || ""; + } + + if (typeof saveData.content === "string") { + const base64Encoded = false; + InspectorFrontendHost.save(suggestedName, saveData.content, base64Encoded, forceSaveAs || saveData.forceSaveAs); + return; + } + + let fileReader = new FileReader; + fileReader.readAsDataURL(saveData.content); + fileReader.addEventListener("loadend", () => { + let dataURLComponents = parseDataURL(fileReader.result); + + const base64Encoded = true; + InspectorFrontendHost.save(suggestedName, dataURLComponents.data, base64Encoded, forceSaveAs || saveData.forceSaveAs); + }); +}; + +WebInspector.isConsoleFocused = function() +{ + return this.quickConsole.prompt.focused; +}; + +WebInspector.isShowingSplitConsole = function() +{ + return !this.splitContentBrowser.element.classList.contains("hidden"); +}; + +WebInspector.dockedConfigurationSupportsSplitContentBrowser = function() +{ + return this._dockConfiguration !== WebInspector.DockConfiguration.Bottom; +}; + +WebInspector.doesCurrentTabSupportSplitContentBrowser = function() +{ + var currentContentView = this.tabBrowser.selectedTabContentView; + return !currentContentView || currentContentView.supportsSplitContentBrowser; +}; + +WebInspector.toggleSplitConsole = function() +{ + if (!this.doesCurrentTabSupportSplitContentBrowser()) { + this.showConsoleTab(); + return; + } + + if (this.isShowingSplitConsole()) + this.hideSplitConsole(); + else + this.showSplitConsole(); +}; + +WebInspector.showSplitConsole = function() +{ + if (!this.doesCurrentTabSupportSplitContentBrowser()) { + this.showConsoleTab(); + return; + } + + this.splitContentBrowser.element.classList.remove("hidden"); + + this._showingSplitConsoleSetting.value = true; + + if (this.splitContentBrowser.currentContentView !== this.consoleContentView) { + // Be sure to close the view in the tab content browser before showing it in the + // split content browser. We can only show a content view in one browser at a time. + if (this.consoleContentView.parentContainer) + this.consoleContentView.parentContainer.closeContentView(this.consoleContentView); + this.splitContentBrowser.showContentView(this.consoleContentView); + } else { + // This causes the view to know it was shown and focus the prompt. + this.splitContentBrowser.shown(); + } + + this.quickConsole.consoleLogVisibilityChanged(true); +}; + +WebInspector.hideSplitConsole = function() +{ + if (!this.isShowingSplitConsole()) + return; + + this.splitContentBrowser.element.classList.add("hidden"); + + this._showingSplitConsoleSetting.value = false; + + // This causes the view to know it was hidden. + this.splitContentBrowser.hidden(); + + this.quickConsole.consoleLogVisibilityChanged(false); +}; + +WebInspector.showConsoleTab = function(requestedScope) +{ + requestedScope = requestedScope || WebInspector.LogContentView.Scopes.All; + + this.hideSplitConsole(); + + this.consoleContentView.scopeBar.item(requestedScope).selected = true; + + this.showRepresentedObject(this._consoleRepresentedObject); + + console.assert(this.isShowingConsoleTab()); +}; + +WebInspector.isShowingConsoleTab = function() +{ + return this.tabBrowser.selectedTabContentView instanceof WebInspector.ConsoleTabContentView; +}; + +WebInspector.showElementsTab = function() +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.ElementsTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.ElementsTabContentView; + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.showDebuggerTab = function(breakpointToSelect) +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.DebuggerTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.DebuggerTabContentView; + + if (breakpointToSelect instanceof WebInspector.Breakpoint) + tabContentView.revealAndSelectBreakpoint(breakpointToSelect); + + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.isShowingDebuggerTab = function() +{ + return this.tabBrowser.selectedTabContentView instanceof WebInspector.DebuggerTabContentView; +}; + +WebInspector.showResourcesTab = function() +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.ResourcesTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.ResourcesTabContentView; + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.showStorageTab = function() +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.StorageTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.StorageTabContentView; + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.showNetworkTab = function() +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.NetworkTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.NetworkTabContentView; + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.showTimelineTab = function() +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.TimelineTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.TimelineTabContentView; + this.tabBrowser.showTabForContentView(tabContentView); +}; + +WebInspector.unlocalizedString = function(string) +{ + // Intentionally do nothing, since this is for engineering builds + // (such as in Debug UI) or in text that is standardized in English. + // For example, CSS property names and values are never localized. + return string; +}; + +WebInspector.UIString = function(string, vararg) +{ + if (WebInspector.dontLocalizeUserInterface) + return string; + + if (window.localizedStrings && string in window.localizedStrings) + return window.localizedStrings[string]; + + if (!this._missingLocalizedStrings) + this._missingLocalizedStrings = {}; + + if (!(string in this._missingLocalizedStrings)) { + console.error("Localized string \"" + string + "\" was not found."); + this._missingLocalizedStrings[string] = true; + } + + return "LOCALIZED STRING NOT FOUND"; +}; + +WebInspector.indentString = function() +{ + if (WebInspector.settings.indentWithTabs.value) + return "\t"; + return " ".repeat(WebInspector.settings.indentUnit.value); +}; + +WebInspector.restoreFocusFromElement = function(element) +{ + if (element && element.isSelfOrAncestor(this.currentFocusElement)) + this.previousFocusElement.focus(); +}; + +WebInspector.toggleNavigationSidebar = function(event) +{ + if (!this.navigationSidebar.collapsed || !this.navigationSidebar.sidebarPanels.length) { + this.navigationSidebar.collapsed = true; + return; + } + + if (!this.navigationSidebar.selectedSidebarPanel) + this.navigationSidebar.selectedSidebarPanel = this.navigationSidebar.sidebarPanels[0]; + this.navigationSidebar.collapsed = false; +}; + +WebInspector.toggleDetailsSidebar = function(event) +{ + if (!this.detailsSidebar.collapsed || !this.detailsSidebar.sidebarPanels.length) { + this.detailsSidebar.collapsed = true; + return; + } + + if (!this.detailsSidebar.selectedSidebarPanel) + this.detailsSidebar.selectedSidebarPanel = this.detailsSidebar.sidebarPanels[0]; + this.detailsSidebar.collapsed = false; +}; + +WebInspector.tabContentViewClassForRepresentedObject = function(representedObject) +{ + if (representedObject instanceof WebInspector.DOMTree) + return WebInspector.ElementsTabContentView; + + if (representedObject instanceof WebInspector.TimelineRecording) + return WebInspector.TimelineTabContentView; + + // We only support one console tab right now. So this isn't an instanceof check. + if (representedObject === this._consoleRepresentedObject) + return WebInspector.ConsoleTabContentView; + + if (WebInspector.debuggerManager.paused) { + if (representedObject instanceof WebInspector.Script) + return WebInspector.DebuggerTabContentView; + + if (representedObject instanceof WebInspector.Resource && (representedObject.type === WebInspector.Resource.Type.Document || representedObject.type === WebInspector.Resource.Type.Script)) + return WebInspector.DebuggerTabContentView; + } + + if (representedObject instanceof WebInspector.Frame || representedObject instanceof WebInspector.Resource || representedObject instanceof WebInspector.Script) + return WebInspector.ResourcesTabContentView; + + // FIXME: Move Content Flows to the Elements tab? + if (representedObject instanceof WebInspector.ContentFlow) + return WebInspector.ResourcesTabContentView; + + // FIXME: Move these to a Storage tab. + if (representedObject instanceof WebInspector.DOMStorageObject || representedObject instanceof WebInspector.CookieStorageObject || + representedObject instanceof WebInspector.DatabaseTableObject || representedObject instanceof WebInspector.DatabaseObject || + representedObject instanceof WebInspector.ApplicationCacheFrame || representedObject instanceof WebInspector.IndexedDatabaseObjectStore || + representedObject instanceof WebInspector.IndexedDatabaseObjectStoreIndex) + return WebInspector.ResourcesTabContentView; + + if (representedObject instanceof WebInspector.Collection) + return WebInspector.CollectionContentView; + + return null; +}; + +WebInspector.tabContentViewForRepresentedObject = function(representedObject, options = {}) +{ + let tabContentView = this.tabBrowser.bestTabContentViewForRepresentedObject(representedObject, options); + if (tabContentView) + return tabContentView; + + var tabContentViewClass = this.tabContentViewClassForRepresentedObject(representedObject); + if (!tabContentViewClass) { + console.error("Unknown representedObject, couldn't create TabContentView.", representedObject); + return null; + } + + tabContentView = new tabContentViewClass; + + this.tabBrowser.addTabForContentView(tabContentView); + + return tabContentView; +}; + +WebInspector.showRepresentedObject = function(representedObject, cookie, options = {}) +{ + let tabContentView = this.tabContentViewForRepresentedObject(representedObject, options); + console.assert(tabContentView); + if (!tabContentView) + return; + + this.tabBrowser.showTabForContentView(tabContentView); + tabContentView.showRepresentedObject(representedObject, cookie); +}; + +WebInspector.showMainFrameDOMTree = function(nodeToSelect, options = {}) +{ + console.assert(WebInspector.frameResourceManager.mainFrame); + if (!WebInspector.frameResourceManager.mainFrame) + return; + this.showRepresentedObject(WebInspector.frameResourceManager.mainFrame.domTree, {nodeToSelect}, options); +}; + +WebInspector.showSourceCodeForFrame = function(frameIdentifier, options = {}) +{ + var frame = WebInspector.frameResourceManager.frameForIdentifier(frameIdentifier); + if (!frame) { + this._frameIdentifierToShowSourceCodeWhenAvailable = frameIdentifier; + return; + } + + this._frameIdentifierToShowSourceCodeWhenAvailable = undefined; + + this.showRepresentedObject(frame, null, options); +}; + +WebInspector.showSourceCode = function(sourceCode, options = {}) +{ + const positionToReveal = options.positionToReveal; + + console.assert(!positionToReveal || positionToReveal instanceof WebInspector.SourceCodePosition, positionToReveal); + var representedObject = sourceCode; + + if (representedObject instanceof WebInspector.Script) { + // A script represented by a resource should always show the resource. + representedObject = representedObject.resource || representedObject; + } + + var cookie = positionToReveal ? {lineNumber: positionToReveal.lineNumber, columnNumber: positionToReveal.columnNumber} : {}; + this.showRepresentedObject(representedObject, cookie, options); +}; + +WebInspector.showSourceCodeLocation = function(sourceCodeLocation, options = {}) +{ + this.showSourceCode(sourceCodeLocation.displaySourceCode, Object.shallowMerge(options, { + positionToReveal: sourceCodeLocation.displayPosition() + })); +}; + +WebInspector.showOriginalUnformattedSourceCodeLocation = function(sourceCodeLocation, options = {}) +{ + this.showSourceCode(sourceCodeLocation.sourceCode, Object.shallowMerge(options, { + positionToReveal: sourceCodeLocation.position(), + forceUnformatted: true + })); +}; + +WebInspector.showOriginalOrFormattedSourceCodeLocation = function(sourceCodeLocation, options = {}) +{ + this.showSourceCode(sourceCodeLocation.sourceCode, Object.shallowMerge(options, { + positionToReveal: sourceCodeLocation.formattedPosition() + })); +}; + +WebInspector.showOriginalOrFormattedSourceCodeTextRange = function(sourceCodeTextRange, options = {}) +{ + var textRangeToSelect = sourceCodeTextRange.formattedTextRange; + this.showSourceCode(sourceCodeTextRange.sourceCode, Object.shallowMerge(options, { + positionToReveal: textRangeToSelect.startPosition(), + textRangeToSelect + })); +}; + +WebInspector.showResourceRequest = function(resource, options = {}) +{ + this.showRepresentedObject(resource, {[WebInspector.ResourceClusterContentView.ContentViewIdentifierCookieKey]: WebInspector.ResourceClusterContentView.RequestIdentifier}, options); +}; + +WebInspector.debuggerToggleBreakpoints = function(event) +{ + WebInspector.debuggerManager.breakpointsEnabled = !WebInspector.debuggerManager.breakpointsEnabled; +}; + +WebInspector.debuggerPauseResumeToggle = function(event) +{ + if (WebInspector.debuggerManager.paused) + WebInspector.debuggerManager.resume(); + else + WebInspector.debuggerManager.pause(); +}; + +WebInspector.debuggerStepOver = function(event) +{ + WebInspector.debuggerManager.stepOver(); +}; + +WebInspector.debuggerStepInto = function(event) +{ + WebInspector.debuggerManager.stepInto(); +}; + +WebInspector.debuggerStepOut = function(event) +{ + WebInspector.debuggerManager.stepOut(); +}; + +WebInspector._searchTextDidChange = function(event) +{ + var tabContentView = this.tabBrowser.bestTabContentViewForClass(WebInspector.SearchTabContentView); + if (!tabContentView) + tabContentView = new WebInspector.SearchTabContentView; + + var searchQuery = this._searchToolbarItem.text; + this._searchToolbarItem.text = ""; + + this.tabBrowser.showTabForContentView(tabContentView); + + tabContentView.performSearch(searchQuery); +}; + +WebInspector._focusSearchField = function(event) +{ + if (this.tabBrowser.selectedTabContentView instanceof WebInspector.SearchTabContentView) { + this.tabBrowser.selectedTabContentView.focusSearchField(); + return; + } + + this._searchToolbarItem.focus(); +}; + +WebInspector._focusChanged = function(event) +{ + // Make a caret selection inside the focused element if there isn't a range selection and there isn't already + // a caret selection inside. This is needed (at least) to remove caret from console when focus is moved. + // The selection change should not apply to text fields and text areas either. + + if (WebInspector.isEventTargetAnEditableField(event)) { + // Still update the currentFocusElement if inside of a CodeMirror editor. + var codeMirrorEditorElement = event.target.enclosingNodeOrSelfWithClass("CodeMirror"); + if (codeMirrorEditorElement && codeMirrorEditorElement !== this.currentFocusElement) { + this.previousFocusElement = this.currentFocusElement; + this.currentFocusElement = codeMirrorEditorElement; + } + return; + } + + var selection = window.getSelection(); + if (!selection.isCollapsed) + return; + + var element = event.target; + + if (element !== this.currentFocusElement) { + this.previousFocusElement = this.currentFocusElement; + this.currentFocusElement = element; + } + + if (element.isInsertionCaretInside()) + return; + + var selectionRange = element.ownerDocument.createRange(); + selectionRange.setStart(element, 0); + selectionRange.setEnd(element, 0); + + selection.removeAllRanges(); + selection.addRange(selectionRange); +}; + +WebInspector._mouseWasClicked = function(event) +{ + this.handlePossibleLinkClick(event); +}; + +WebInspector._dragOver = function(event) +{ + // Do nothing if another event listener handled the event already. + if (event.defaultPrevented) + return; + + // Allow dropping into editable areas. + if (WebInspector.isEventTargetAnEditableField(event)) + return; + + // Prevent the drop from being accepted. + event.dataTransfer.dropEffect = "none"; + event.preventDefault(); +}; + +WebInspector._captureDidStart = function(event) +{ + this._dashboardContainer.showDashboardViewForRepresentedObject(this.dashboardManager.dashboards.replay); +}; + +WebInspector._debuggerDidPause = function(event) +{ + // FIXME: <webkit.org/b/###> Web Inspector: Preference for Auto Showing Scope Chain sidebar on pause + this.showDebuggerTab(); + + this._dashboardContainer.showDashboardViewForRepresentedObject(this.dashboardManager.dashboards.debugger); + + InspectorFrontendHost.bringToFront(); +}; + +WebInspector._debuggerDidResume = function(event) +{ + this._dashboardContainer.closeDashboardViewForRepresentedObject(this.dashboardManager.dashboards.debugger); +}; + +WebInspector._frameWasAdded = function(event) +{ + if (!this._frameIdentifierToShowSourceCodeWhenAvailable) + return; + + var frame = event.data.frame; + if (frame.id !== this._frameIdentifierToShowSourceCodeWhenAvailable) + return; + + function delayedWork() + { + this.showSourceCodeForFrame(frame.id); + } + + // Delay showing the frame since FrameWasAdded is called before MainFrameChanged. + // Calling showSourceCodeForFrame before MainFrameChanged will show the frame then close it. + setTimeout(delayedWork.bind(this)); +}; + +WebInspector._mainFrameDidChange = function(event) +{ + this._updateDownloadToolbarButton(); + + this.updateWindowTitle(); +}; + +WebInspector._mainResourceDidChange = function(event) +{ + if (!event.target.isMainFrame()) + return; + + this._inProvisionalLoad = false; + + // Run cookie restoration after we are sure all of the Tabs and NavigationSidebarPanels + // have updated with respect to the main resource change. + setTimeout(this._restoreCookieForOpenTabs.bind(this, WebInspector.StateRestorationType.Navigation)); + + this._updateDownloadToolbarButton(); + + this.updateWindowTitle(); +}; + +WebInspector._provisionalLoadStarted = function(event) +{ + if (!event.target.isMainFrame()) + return; + + this._saveCookieForOpenTabs(); + + this._inProvisionalLoad = true; +}; + +WebInspector._restoreCookieForOpenTabs = function(restorationType) +{ + for (var tabBarItem of this.tabBar.tabBarItems) { + var tabContentView = tabBarItem.representedObject; + if (!(tabContentView instanceof WebInspector.TabContentView)) + continue; + tabContentView.restoreStateFromCookie(restorationType); + } +}; + +WebInspector._saveCookieForOpenTabs = function() +{ + for (var tabBarItem of this.tabBar.tabBarItems) { + var tabContentView = tabBarItem.representedObject; + if (!(tabContentView instanceof WebInspector.TabContentView)) + continue; + tabContentView.saveStateToCookie(); + } +}; + +WebInspector._windowFocused = function(event) +{ + if (event.target.document.nodeType !== Node.DOCUMENT_NODE) + return; + + // FIXME: We should use the :window-inactive pseudo class once https://webkit.org/b/38927 is fixed. + document.body.classList.remove(this.docked ? "window-docked-inactive" : "window-inactive"); +}; + +WebInspector._windowBlurred = function(event) +{ + if (event.target.document.nodeType !== Node.DOCUMENT_NODE) + return; + + // FIXME: We should use the :window-inactive pseudo class once https://webkit.org/b/38927 is fixed. + document.body.classList.add(this.docked ? "window-docked-inactive" : "window-inactive"); +}; + +WebInspector._windowResized = function(event) +{ + this.toolbar.updateLayout(WebInspector.View.LayoutReason.Resize); + this.tabBar.updateLayout(WebInspector.View.LayoutReason.Resize); + this._tabBrowserSizeDidChange(); +}; + +WebInspector._updateModifierKeys = function(event) +{ + var didChange = this.modifierKeys.altKey !== event.altKey || this.modifierKeys.metaKey !== event.metaKey || this.modifierKeys.shiftKey !== event.shiftKey; + + this.modifierKeys = {altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey}; + + if (didChange) + this.notifications.dispatchEventToListeners(WebInspector.Notification.GlobalModifierKeysDidChange, event); +}; + +WebInspector._windowKeyDown = function(event) +{ + this._updateModifierKeys(event); +}; + +WebInspector._windowKeyUp = function(event) +{ + this._updateModifierKeys(event); +}; + +WebInspector._mouseDown = function(event) +{ + if (this.toolbar.element.isSelfOrAncestor(event.target)) + this._toolbarMouseDown(event); +}; + +WebInspector._mouseMoved = function(event) +{ + this._updateModifierKeys(event); + this.mouseCoords = { + x: event.pageX, + y: event.pageY + }; +}; + +WebInspector._pageHidden = function(event) +{ + this._saveCookieForOpenTabs(); +}; + +WebInspector._contextMenuRequested = function(event) +{ + let proposedContextMenu; + + // This is setting is only defined in engineering builds. + if (WebInspector.isDebugUIEnabled()) { + proposedContextMenu = WebInspector.ContextMenu.createFromEvent(event); + proposedContextMenu.appendSeparator(); + proposedContextMenu.appendItem(WebInspector.unlocalizedString("Reload Web Inspector"), () => { + window.location.reload(); + }); + + let protocolSubMenu = proposedContextMenu.appendSubMenuItem(WebInspector.unlocalizedString("Protocol Debugging"), null, false); + let isCapturingTraffic = InspectorBackend.activeTracer instanceof WebInspector.CapturingProtocolTracer; + + protocolSubMenu.appendCheckboxItem(WebInspector.unlocalizedString("Capture Trace"), () => { + if (isCapturingTraffic) + InspectorBackend.activeTracer = null; + else + InspectorBackend.activeTracer = new WebInspector.CapturingProtocolTracer; + }, isCapturingTraffic); + + protocolSubMenu.appendSeparator(); + + protocolSubMenu.appendItem(WebInspector.unlocalizedString("Export Trace\u2026"), () => { + const forceSaveAs = true; + WebInspector.saveDataToFile(InspectorBackend.activeTracer.trace.saveData, forceSaveAs); + }, !isCapturingTraffic); + } else { + const onlyExisting = true; + proposedContextMenu = WebInspector.ContextMenu.createFromEvent(event, onlyExisting); + } + + if (proposedContextMenu) + proposedContextMenu.show(); +}; + +WebInspector.isDebugUIEnabled = function() +{ + return WebInspector.showDebugUISetting && WebInspector.showDebugUISetting.value; +}; + +WebInspector._undock = function(event) +{ + InspectorFrontendHost.requestSetDockSide(WebInspector.DockConfiguration.Undocked); +}; + +WebInspector._dockBottom = function(event) +{ + InspectorFrontendHost.requestSetDockSide(WebInspector.DockConfiguration.Bottom); +}; + +WebInspector._dockRight = function(event) +{ + InspectorFrontendHost.requestSetDockSide(WebInspector.DockConfiguration.Right); +}; + +WebInspector._dockLeft = function(event) +{ + InspectorFrontendHost.requestSetDockSide(WebInspector.DockConfiguration.Left); +}; + +WebInspector._togglePreviousDockConfiguration = function(event) +{ + InspectorFrontendHost.requestSetDockSide(this._previousDockConfiguration); +}; + +WebInspector._updateDockNavigationItems = function() +{ + if (this._dockingAvailable || this.docked) { + this._closeToolbarButton.hidden = !this.docked; + this._undockToolbarButton.hidden = this._dockConfiguration === WebInspector.DockConfiguration.Undocked; + this._dockBottomToolbarButton.hidden = this._dockConfiguration === WebInspector.DockConfiguration.Bottom; + this._dockToSideToolbarButton.hidden = this._dockConfiguration === WebInspector.DockConfiguration.Right || this._dockConfiguration === WebInspector.DockConfiguration.Left; + } else { + this._closeToolbarButton.hidden = true; + this._undockToolbarButton.hidden = true; + this._dockBottomToolbarButton.hidden = true; + this._dockToSideToolbarButton.hidden = true; + } +}; + +WebInspector._tabBrowserSizeDidChange = function() +{ + this.tabBrowser.updateLayout(WebInspector.View.LayoutReason.Resize); + this.splitContentBrowser.updateLayout(WebInspector.View.LayoutReason.Resize); + this.quickConsole.updateLayout(WebInspector.View.LayoutReason.Resize); +}; + +WebInspector._quickConsoleDidResize = function(event) +{ + this.tabBrowser.updateLayout(WebInspector.View.LayoutReason.Resize); +}; + +WebInspector._sidebarWidthDidChange = function(event) +{ + this._tabBrowserSizeDidChange(); +}; + +WebInspector._setupViewHierarchy = function() +{ + let rootView = WebInspector.View.rootView(); + rootView.addSubview(this.toolbar); + rootView.addSubview(this.tabBar); + rootView.addSubview(this.navigationSidebar); + rootView.addSubview(this.tabBrowser); + rootView.addSubview(this.splitContentBrowser); + rootView.addSubview(this.quickConsole); + rootView.addSubview(this.detailsSidebar); +}; + +WebInspector._tabBrowserSelectedTabContentViewDidChange = function(event) +{ + if (this.tabBar.selectedTabBarItem && this.tabBar.selectedTabBarItem.representedObject.constructor.shouldSaveTab()) + this._selectedTabIndexSetting.value = this.tabBar.tabBarItems.indexOf(this.tabBar.selectedTabBarItem); + + if (!this.doesCurrentTabSupportSplitContentBrowser()) + this.hideSplitConsole(); + + if (!this.isShowingSplitConsole()) + this.quickConsole.consoleLogVisibilityChanged(this.isShowingConsoleTab()); +}; + +WebInspector._initializeWebSocketIfNeeded = function() +{ + if (!InspectorFrontendHost.initializeWebSocket) + return; + + var queryParams = parseLocationQueryParameters(); + + if ("ws" in queryParams) + var url = "ws://" + queryParams.ws; + else if ("page" in queryParams) { + var page = queryParams.page; + var host = "host" in queryParams ? queryParams.host : window.location.host; + var url = "ws://" + host + "/devtools/page/" + page; + } + + if (!url) + return; + + InspectorFrontendHost.initializeWebSocket(url); +}; + +WebInspector._updateSplitConsoleHeight = function(height) +{ + const minimumHeight = 64; + const maximumHeight = window.innerHeight * 0.55; + + height = Math.max(minimumHeight, Math.min(height, maximumHeight)); + + this.splitContentBrowser.element.style.height = height + "px"; +}; + +WebInspector._consoleResizerMouseDown = function(event) +{ + if (event.button !== 0 || event.ctrlKey) + return; + + // Only start dragging if the target is one of the elements that we expect. + if (!event.target.classList.contains("navigation-bar") && !event.target.classList.contains("flexible-space")) + return; + + var resizerElement = event.target; + var mouseOffset = resizerElement.offsetHeight - (event.pageY - resizerElement.totalOffsetTop); + + function dockedResizerDrag(event) + { + if (event.button !== 0) + return; + + var height = window.innerHeight - event.pageY - mouseOffset; + + this._splitConsoleHeightSetting.value = height; + + this._updateSplitConsoleHeight(height); + + this.quickConsole.dispatchEventToListeners(WebInspector.QuickConsole.Event.DidResize); + } + + function dockedResizerDragEnd(event) + { + if (event.button !== 0) + return; + + this.elementDragEnd(event); + } + + this.elementDragStart(resizerElement, dockedResizerDrag.bind(this), dockedResizerDragEnd.bind(this), event, "row-resize"); +}; + +WebInspector._toolbarMouseDown = function(event) +{ + if (event.ctrlKey) + return; + + if (this._dockConfiguration === WebInspector.DockConfiguration.Right || this._dockConfiguration === WebInspector.DockConfiguration.Left) + return; + + if (this.docked) + this._dockedResizerMouseDown(event); + else + this._moveWindowMouseDown(event); +}; + +WebInspector._dockedResizerMouseDown = function(event) +{ + if (event.button !== 0 || event.ctrlKey) + return; + + if (!this.docked) + return; + + // Only start dragging if the target is one of the elements that we expect. + if (event.target.id !== "docked-resizer" && !event.target.classList.contains("toolbar") && + !event.target.classList.contains("flexible-space") && !event.target.classList.contains("item-section")) + return; + + event[WebInspector.Popover.EventPreventDismissSymbol] = true; + + let windowProperty = this._dockConfiguration === WebInspector.DockConfiguration.Bottom ? "innerHeight" : "innerWidth"; + let eventScreenProperty = this._dockConfiguration === WebInspector.DockConfiguration.Bottom ? "screenY" : "screenX"; + let eventClientProperty = this._dockConfiguration === WebInspector.DockConfiguration.Bottom ? "clientY" : "clientX"; + + var resizerElement = event.target; + var firstClientPosition = event[eventClientProperty]; + var lastScreenPosition = event[eventScreenProperty]; + + function dockedResizerDrag(event) + { + if (event.button !== 0) + return; + + var position = event[eventScreenProperty]; + var delta = position - lastScreenPosition; + var clientPosition = event[eventClientProperty]; + + lastScreenPosition = position; + + if (this._dockConfiguration === WebInspector.DockConfiguration.Left) { + // If the mouse is travelling rightward but is positioned left of the resizer, ignore the event. + if (delta > 0 && clientPosition < firstClientPosition) + return; + + // If the mouse is travelling leftward but is positioned to the right of the resizer, ignore the event. + if (delta < 0 && clientPosition > window[windowProperty]) + return; + + // We later subtract the delta from the current position, but since the inspected view and inspector view + // are flipped when docked to left, we want dragging to have the opposite effect from docked to right. + delta *= -1; + } else { + // If the mouse is travelling downward/rightward but is positioned above/left of the resizer, ignore the event. + if (delta > 0 && clientPosition < firstClientPosition) + return; + + // If the mouse is travelling upward/leftward but is positioned below/right of the resizer, ignore the event. + if (delta < 0 && clientPosition > firstClientPosition) + return; + } + + let dimension = Math.max(0, window[windowProperty] - delta); + // If zoomed in/out, there be greater/fewer document pixels shown, but the inspector's + // width or height should be the same in device pixels regardless of the document zoom. + dimension *= this.getZoomFactor(); + + if (this._dockConfiguration === WebInspector.DockConfiguration.Bottom) + InspectorFrontendHost.setAttachedWindowHeight(dimension); + else + InspectorFrontendHost.setAttachedWindowWidth(dimension); + } + + function dockedResizerDragEnd(event) + { + if (event.button !== 0) + return; + + WebInspector.elementDragEnd(event); + } + + WebInspector.elementDragStart(resizerElement, dockedResizerDrag.bind(this), dockedResizerDragEnd.bind(this), event, this._dockConfiguration === WebInspector.DockConfiguration.Bottom ? "row-resize" : "col-resize"); +}; + +WebInspector._moveWindowMouseDown = function(event) +{ + console.assert(!this.docked); + + if (event.button !== 0 || event.ctrlKey) + return; + + // Only start dragging if the target is one of the elements that we expect. + if (!event.target.classList.contains("toolbar") && !event.target.classList.contains("flexible-space") && + !event.target.classList.contains("item-section")) + return; + + event[WebInspector.Popover.EventPreventDismissSymbol] = true; + + if (WebInspector.Platform.name === "mac") { + // New Mac releases can start a window drag. + if (WebInspector.Platform.version.release >= 11) { + InspectorFrontendHost.startWindowDrag(); + event.preventDefault(); + return; + } + + // Ignore dragging on the top of the toolbar on Mac if the system handles it. + if (WebInspector.Platform.version.release === 10) { + const windowDragHandledTitleBarHeight = 22; + if (event.pageY < windowDragHandledTitleBarHeight) { + event.preventDefault(); + return; + } + } + } + + var lastScreenX = event.screenX; + var lastScreenY = event.screenY; + + function toolbarDrag(event) + { + if (event.button !== 0) + return; + + var x = event.screenX - lastScreenX; + var y = event.screenY - lastScreenY; + + InspectorFrontendHost.moveWindowBy(x, y); + + lastScreenX = event.screenX; + lastScreenY = event.screenY; + } + + function toolbarDragEnd(event) + { + if (event.button !== 0) + return; + + WebInspector.elementDragEnd(event); + } + + WebInspector.elementDragStart(event.target, toolbarDrag, toolbarDragEnd, event, "default"); +}; + +WebInspector._storageWasInspected = function(event) +{ + this.showStorageTab(); +}; + +WebInspector._domNodeWasInspected = function(event) +{ + this.domTreeManager.highlightDOMNodeForTwoSeconds(event.data.node.id); + + InspectorFrontendHost.bringToFront(); + + this.showElementsTab(); + this.showMainFrameDOMTree(event.data.node); +}; + +WebInspector._inspectModeStateChanged = function(event) +{ + this._inspectModeToolbarButton.activated = this.domTreeManager.inspectModeEnabled; +}; + +WebInspector._toggleInspectMode = function(event) +{ + this.domTreeManager.inspectModeEnabled = !this.domTreeManager.inspectModeEnabled; +}; + +WebInspector._downloadWebArchive = function(event) +{ + this.archiveMainFrame(); +}; + +WebInspector._reloadPage = function(event) +{ + if (!window.PageAgent) + return; + + PageAgent.reload(); + event.preventDefault(); +}; + +WebInspector._reloadPageClicked = function(event) +{ + // Ignore cache when the shift key is pressed. + PageAgent.reload(window.event ? window.event.shiftKey : false); +}; + +WebInspector._reloadPageIgnoringCache = function(event) +{ + if (!window.PageAgent) + return; + + PageAgent.reload(true); + event.preventDefault(); +}; + +WebInspector._updateReloadToolbarButton = function() +{ + if (!window.PageAgent) { + this._reloadToolbarButton.hidden = true; + return; + } + + this._reloadToolbarButton.hidden = false; +}; + +WebInspector._updateDownloadToolbarButton = function() +{ + // COMPATIBILITY (iOS 7): Page.archive did not exist yet. + if (!window.PageAgent || !PageAgent.archive || this.debuggableType !== WebInspector.DebuggableType.Web) { + this._downloadToolbarButton.hidden = true; + return; + } + + if (this._downloadingPage) { + this._downloadToolbarButton.enabled = false; + return; + } + + this._downloadToolbarButton.enabled = this.canArchiveMainFrame(); +}; + +WebInspector._toggleInspectMode = function(event) +{ + this.domTreeManager.inspectModeEnabled = !this.domTreeManager.inspectModeEnabled; +}; + +WebInspector._showConsoleTab = function(event) +{ + this.showConsoleTab(); +}; + +WebInspector._focusConsolePrompt = function(event) +{ + this.quickConsole.prompt.focus(); +}; + +WebInspector._focusedContentBrowser = function() +{ + if (this.tabBrowser.element.isSelfOrAncestor(this.currentFocusElement) || document.activeElement === document.body) { + var tabContentView = this.tabBrowser.selectedTabContentView; + if (tabContentView instanceof WebInspector.ContentBrowserTabContentView) + return tabContentView.contentBrowser; + return null; + } + + if (this.splitContentBrowser.element.isSelfOrAncestor(this.currentFocusElement) + || (WebInspector.isShowingSplitConsole() && this.quickConsole.element.isSelfOrAncestor(this.currentFocusElement))) + return this.splitContentBrowser; + + return null; +}; + +WebInspector._focusedContentView = function() +{ + if (this.tabBrowser.element.isSelfOrAncestor(this.currentFocusElement) || document.activeElement === document.body) { + var tabContentView = this.tabBrowser.selectedTabContentView; + if (tabContentView instanceof WebInspector.ContentBrowserTabContentView) + return tabContentView.contentBrowser.currentContentView; + return tabContentView; + } + + if (this.splitContentBrowser.element.isSelfOrAncestor(this.currentFocusElement) + || (WebInspector.isShowingSplitConsole() && this.quickConsole.element.isSelfOrAncestor(this.currentFocusElement))) + return this.splitContentBrowser.currentContentView; + + return null; +}; + +WebInspector._focusedOrVisibleContentBrowser = function() +{ + let focusedContentBrowser = this._focusedContentBrowser(); + if (focusedContentBrowser) + return focusedContentBrowser; + + var tabContentView = this.tabBrowser.selectedTabContentView; + if (tabContentView instanceof WebInspector.ContentBrowserTabContentView) + return tabContentView.contentBrowser; + + return null; +}; + +WebInspector.focusedOrVisibleContentView = function() +{ + let focusedContentView = this._focusedContentView(); + if (focusedContentView) + return focusedContentView; + + var tabContentView = this.tabBrowser.selectedTabContentView; + if (tabContentView instanceof WebInspector.ContentBrowserTabContentView) + return tabContentView.contentBrowser.currentContentView; + return tabContentView; +}; + +WebInspector._beforecopy = function(event) +{ + var selection = window.getSelection(); + + // If there is no selection, see if the focused element or focused ContentView can handle the copy event. + if (selection.isCollapsed && !WebInspector.isEventTargetAnEditableField(event)) { + var focusedCopyHandler = this.currentFocusElement && this.currentFocusElement.copyHandler; + if (focusedCopyHandler && typeof focusedCopyHandler.handleBeforeCopyEvent === "function") { + focusedCopyHandler.handleBeforeCopyEvent(event); + if (event.defaultPrevented) + return; + } + + var focusedContentView = this._focusedContentView(); + if (focusedContentView && typeof focusedContentView.handleCopyEvent === "function") { + event.preventDefault(); + return; + } + + return; + } + + if (selection.isCollapsed) + return; + + // Say we can handle it (by preventing default) to remove word break characters. + event.preventDefault(); +}; + +WebInspector._find = function(event) +{ + let contentBrowser = this._focusedOrVisibleContentBrowser(); + if (!contentBrowser) + return; + + contentBrowser.showFindBanner(); +}; + +WebInspector._save = function(event) +{ + var contentView = this.focusedOrVisibleContentView(); + if (!contentView || !contentView.supportsSave) + return; + + WebInspector.saveDataToFile(contentView.saveData); +}; + +WebInspector._saveAs = function(event) +{ + var contentView = this.focusedOrVisibleContentView(); + if (!contentView || !contentView.supportsSave) + return; + + WebInspector.saveDataToFile(contentView.saveData, true); +}; + +WebInspector._clear = function(event) +{ + let contentView = this.focusedOrVisibleContentView(); + if (!contentView || typeof contentView.handleClearShortcut !== "function") { + // If the current content view is unable to handle this event, clear the console to reset + // the dashboard counters. + this.logManager.requestClearMessages(); + return; + } + + contentView.handleClearShortcut(event); +}; + +WebInspector._copy = function(event) +{ + var selection = window.getSelection(); + + // If there is no selection, pass the copy event on to the focused element or focused ContentView. + if (selection.isCollapsed && !WebInspector.isEventTargetAnEditableField(event)) { + var focusedCopyHandler = this.currentFocusElement && this.currentFocusElement.copyHandler; + if (focusedCopyHandler && typeof focusedCopyHandler.handleCopyEvent === "function") { + focusedCopyHandler.handleCopyEvent(event); + if (event.defaultPrevented) + return; + } + + var focusedContentView = this._focusedContentView(); + if (focusedContentView && typeof focusedContentView.handleCopyEvent === "function") { + focusedContentView.handleCopyEvent(event); + return; + } + + return; + } + + if (selection.isCollapsed) + return; + + // Remove word break characters from the selection before putting it on the pasteboard. + var selectionString = selection.toString().removeWordBreakCharacters(); + event.clipboardData.setData("text/plain", selectionString); + event.preventDefault(); +}; + +WebInspector._increaseZoom = function(event) +{ + const epsilon = 0.0001; + const maximumZoom = 2.4; + let currentZoom = this.getZoomFactor(); + if (currentZoom + epsilon >= maximumZoom) { + InspectorFrontendHost.beep(); + return; + } + + this.setZoomFactor(Math.min(maximumZoom, currentZoom + 0.2)); +}; + +WebInspector._decreaseZoom = function(event) +{ + const epsilon = 0.0001; + const minimumZoom = 0.6; + let currentZoom = this.getZoomFactor(); + if (currentZoom - epsilon <= minimumZoom) { + InspectorFrontendHost.beep(); + return; + } + + this.setZoomFactor(Math.max(minimumZoom, currentZoom - 0.2)); +}; + +WebInspector._resetZoom = function(event) +{ + this.setZoomFactor(1); +}; + +WebInspector.getZoomFactor = function() +{ + return WebInspector.settings.zoomFactor.value; +}; + +WebInspector.setZoomFactor = function(factor) +{ + InspectorFrontendHost.setZoomFactor(factor); + // Round-trip through the frontend host API in case the requested factor is not used. + WebInspector.settings.zoomFactor.value = InspectorFrontendHost.zoomFactor(); +}; + +WebInspector.resolvedLayoutDirection = function() +{ + let layoutDirection = WebInspector.settings.layoutDirection.value; + if (layoutDirection === WebInspector.LayoutDirection.System) + layoutDirection = InspectorFrontendHost.userInterfaceLayoutDirection(); + + return layoutDirection; +} + +WebInspector.setLayoutDirection = function(value) +{ + if (!Object.values(WebInspector.LayoutDirection).includes(value)) + WebInspector.reportInternalError("Unknown layout direction requested: " + value); + + if (value === WebInspector.settings.layoutDirection.value) + return; + + WebInspector.settings.layoutDirection.value = value; + + if (value === WebInspector.LayoutDirection.RTL && this._dockConfiguration === WebInspector.DockConfiguration.Right) + this._dockLeft(); + + if (value === WebInspector.LayoutDirection.LTR && this._dockConfiguration === WebInspector.DockConfiguration.Left) + this._dockRight(); + + window.location.reload(); +}; + +WebInspector._showTabAtIndex = function(i, event) +{ + if (i <= WebInspector.tabBar.tabBarItems.length) + WebInspector.tabBar.selectedTabBarItem = i - 1; +}; + +WebInspector._showJavaScriptTypeInformationSettingChanged = function(event) +{ + if (this.showJavaScriptTypeInformationSetting.value) { + for (let target of WebInspector.targets) + target.RuntimeAgent.enableTypeProfiler(); + } else { + for (let target of WebInspector.targets) + target.RuntimeAgent.disableTypeProfiler(); + } +}; + +WebInspector._enableControlFlowProfilerSettingChanged = function(event) +{ + if (this.enableControlFlowProfilerSetting.value) { + for (let target of WebInspector.targets) + target.RuntimeAgent.enableControlFlowProfiler(); + } else { + for (let target of WebInspector.targets) + target.RuntimeAgent.disableControlFlowProfiler(); + } +}; + +WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor, eventTarget) +{ + if (WebInspector._elementDraggingEventListener || WebInspector._elementEndDraggingEventListener) + WebInspector.elementDragEnd(event); + + if (element) { + // Install glass pane + if (WebInspector._elementDraggingGlassPane) + WebInspector._elementDraggingGlassPane.remove(); + + var glassPane = document.createElement("div"); + glassPane.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;opacity:0;z-index:1"; + glassPane.id = "glass-pane-for-drag"; + element.ownerDocument.body.appendChild(glassPane); + WebInspector._elementDraggingGlassPane = glassPane; + } + + WebInspector._elementDraggingEventListener = dividerDrag; + WebInspector._elementEndDraggingEventListener = elementDragEnd; + + var targetDocument = event.target.ownerDocument; + + WebInspector._elementDraggingEventTarget = eventTarget || targetDocument; + WebInspector._elementDraggingEventTarget.addEventListener("mousemove", dividerDrag, true); + WebInspector._elementDraggingEventTarget.addEventListener("mouseup", elementDragEnd, true); + + targetDocument.body.style.cursor = cursor; + + event.preventDefault(); +}; + +WebInspector.elementDragEnd = function(event) +{ + WebInspector._elementDraggingEventTarget.removeEventListener("mousemove", WebInspector._elementDraggingEventListener, true); + WebInspector._elementDraggingEventTarget.removeEventListener("mouseup", WebInspector._elementEndDraggingEventListener, true); + + event.target.ownerDocument.body.style.removeProperty("cursor"); + + if (WebInspector._elementDraggingGlassPane) + WebInspector._elementDraggingGlassPane.remove(); + + delete WebInspector._elementDraggingGlassPane; + delete WebInspector._elementDraggingEventTarget; + delete WebInspector._elementDraggingEventListener; + delete WebInspector._elementEndDraggingEventListener; + + event.preventDefault(); +}; + +WebInspector.createMessageTextView = function(message, isError) +{ + var messageElement = document.createElement("div"); + messageElement.className = "message-text-view"; + if (isError) + messageElement.classList.add("error"); + + messageElement.textContent = message; + + return messageElement; +}; + +WebInspector.createGoToArrowButton = function() +{ + var button = document.createElement("button"); + button.addEventListener("mousedown", (event) => { event.stopPropagation(); }, true); + button.className = "go-to-arrow"; + button.tabIndex = -1; + return button; +}; + +WebInspector.createSourceCodeLocationLink = function(sourceCodeLocation, dontFloat, useGoToArrowButton) +{ + console.assert(sourceCodeLocation); + if (!sourceCodeLocation) + return null; + + var linkElement = document.createElement("a"); + linkElement.className = "go-to-link"; + WebInspector.linkifyElement(linkElement, sourceCodeLocation); + sourceCodeLocation.populateLiveDisplayLocationTooltip(linkElement); + + if (useGoToArrowButton) + linkElement.appendChild(WebInspector.createGoToArrowButton()); + else + sourceCodeLocation.populateLiveDisplayLocationString(linkElement, "textContent"); + + if (dontFloat) + linkElement.classList.add("dont-float"); + + return linkElement; +}; + +WebInspector.linkifyLocation = function(url, lineNumber, columnNumber, className) +{ + var sourceCode = WebInspector.sourceCodeForURL(url); + + if (!sourceCode) { + var anchor = document.createElement("a"); + anchor.href = url; + anchor.lineNumber = lineNumber; + if (className) + anchor.className = className; + anchor.append(WebInspector.displayNameForURL(url) + ":" + lineNumber); + return anchor; + } + + var sourceCodeLocation = sourceCode.createSourceCodeLocation(lineNumber, columnNumber); + var linkElement = WebInspector.createSourceCodeLocationLink(sourceCodeLocation, true); + if (className) + linkElement.classList.add(className); + return linkElement; +}; + +WebInspector.linkifyElement = function(linkElement, sourceCodeLocation) { + console.assert(sourceCodeLocation); + + function showSourceCodeLocation(event) + { + event.stopPropagation(); + event.preventDefault(); + + if (event.metaKey) + this.showOriginalUnformattedSourceCodeLocation(sourceCodeLocation); + else + this.showSourceCodeLocation(sourceCodeLocation); + } + + linkElement.addEventListener("click", showSourceCodeLocation.bind(this)); +}; + +WebInspector.sourceCodeForURL = function(url) +{ + var sourceCode = WebInspector.frameResourceManager.resourceForURL(url); + if (!sourceCode) { + sourceCode = WebInspector.debuggerManager.scriptsForURL(url, WebInspector.assumingMainTarget())[0]; + if (sourceCode) + sourceCode = sourceCode.resource || sourceCode; + } + return sourceCode || null; +}; + +WebInspector.linkifyURLAsNode = function(url, linkText, classes) +{ + if (!linkText) + linkText = url; + + classes = (classes ? classes + " " : ""); + + var a = document.createElement("a"); + a.href = url; + a.className = classes; + + a.textContent = linkText; + a.style.maxWidth = "100%"; + + return a; +}; + +WebInspector.linkifyStringAsFragmentWithCustomLinkifier = function(string, linkifier) +{ + var container = document.createDocumentFragment(); + var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/; + var lineColumnRegEx = /:(\d+)(:(\d+))?$/; + + while (string) { + var linkString = linkStringRegEx.exec(string); + if (!linkString) + break; + + linkString = linkString[0]; + var linkIndex = string.indexOf(linkString); + var nonLink = string.substring(0, linkIndex); + container.append(nonLink); + + if (linkString.startsWith("data:") || linkString.startsWith("javascript:") || linkString.startsWith("mailto:")) { + container.append(linkString); + string = string.substring(linkIndex + linkString.length, string.length); + continue; + } + + var title = linkString; + var realURL = linkString.startsWith("www.") ? "http://" + linkString : linkString; + var lineColumnMatch = lineColumnRegEx.exec(realURL); + if (lineColumnMatch) + realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length); + + var lineNumber; + if (lineColumnMatch) + lineNumber = parseInt(lineColumnMatch[1]) - 1; + + var linkNode = linkifier(title, realURL, lineNumber); + container.appendChild(linkNode); + string = string.substring(linkIndex + linkString.length, string.length); + } + + if (string) + container.append(string); + + return container; +}; + +WebInspector.linkifyStringAsFragment = function(string) +{ + function linkifier(title, url, lineNumber) + { + var urlNode = WebInspector.linkifyURLAsNode(url, title, undefined); + if (lineNumber !== undefined) + urlNode.lineNumber = lineNumber; + + return urlNode; + } + + return WebInspector.linkifyStringAsFragmentWithCustomLinkifier(string, linkifier); +}; + +WebInspector.createResourceLink = function(resource, className) +{ + function handleClick(event) + { + event.stopPropagation(); + event.preventDefault(); + + WebInspector.showRepresentedObject(resource); + } + + let linkNode = document.createElement("a"); + linkNode.classList.add("resource-link", className); + linkNode.title = resource.url; + linkNode.textContent = (resource.urlComponents.lastPathComponent || resource.url).insertWordBreakCharacters(); + linkNode.addEventListener("click", handleClick.bind(this)); + return linkNode; +}; + +WebInspector._undoKeyboardShortcut = function(event) +{ + if (!this.isEditingAnyField() && !this.isEventTargetAnEditableField(event)) { + this.undo(); + event.preventDefault(); + } +}; + +WebInspector._redoKeyboardShortcut = function(event) +{ + if (!this.isEditingAnyField() && !this.isEventTargetAnEditableField(event)) { + this.redo(); + event.preventDefault(); + } +}; + +WebInspector.undo = function() +{ + DOMAgent.undo(); +}; + +WebInspector.redo = function() +{ + DOMAgent.redo(); +}; + +WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes) +{ + changes = changes || []; + var highlightNodes = []; + var lineText = element.textContent; + var ownerDocument = element.ownerDocument; + var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + var snapshotLength = textNodeSnapshot.snapshotLength; + if (snapshotLength === 0) + return highlightNodes; + + var nodeRanges = []; + var rangeEndOffset = 0; + for (var i = 0; i < snapshotLength; ++i) { + var range = {}; + range.offset = rangeEndOffset; + range.length = textNodeSnapshot.snapshotItem(i).textContent.length; + rangeEndOffset = range.offset + range.length; + nodeRanges.push(range); + } + + var startIndex = 0; + for (var i = 0; i < resultRanges.length; ++i) { + var startOffset = resultRanges[i].offset; + var endOffset = startOffset + resultRanges[i].length; + + while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset) + startIndex++; + var endIndex = startIndex; + while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset) + endIndex++; + if (endIndex === snapshotLength) + break; + + var highlightNode = ownerDocument.createElement("span"); + highlightNode.className = styleClass; + highlightNode.textContent = lineText.substring(startOffset, endOffset); + + var lastTextNode = textNodeSnapshot.snapshotItem(endIndex); + var lastText = lastTextNode.textContent; + lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset); + changes.push({node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent}); + + if (startIndex === endIndex) { + lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode); + changes.push({node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement}); + highlightNodes.push(highlightNode); + + var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset)); + lastTextNode.parentElement.insertBefore(prefixNode, highlightNode); + changes.push({node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement}); + } else { + var firstTextNode = textNodeSnapshot.snapshotItem(startIndex); + var firstText = firstTextNode.textContent; + var anchorElement = firstTextNode.nextSibling; + + firstTextNode.parentElement.insertBefore(highlightNode, anchorElement); + changes.push({node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement}); + highlightNodes.push(highlightNode); + + firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset); + changes.push({node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent}); + + for (var j = startIndex + 1; j < endIndex; j++) { + var textNode = textNodeSnapshot.snapshotItem(j); + var text = textNode.textContent; + textNode.textContent = ""; + changes.push({node: textNode, type: "changed", oldText: text, newText: textNode.textContent}); + } + } + startIndex = endIndex; + nodeRanges[startIndex].offset = endOffset; + nodeRanges[startIndex].length = lastTextNode.textContent.length; + + } + return highlightNodes; +}; + +WebInspector.revertDomChanges = function(domChanges) +{ + for (var i = domChanges.length - 1; i >= 0; --i) { + var entry = domChanges[i]; + switch (entry.type) { + case "added": + entry.node.remove(); + break; + case "changed": + entry.node.textContent = entry.oldText; + break; + } + } +}; + +WebInspector.archiveMainFrame = function() +{ + this._downloadingPage = true; + this._updateDownloadToolbarButton(); + + PageAgent.archive((error, data) => { + this._downloadingPage = false; + this._updateDownloadToolbarButton(); + + if (error) + return; + + let mainFrame = WebInspector.frameResourceManager.mainFrame; + let archiveName = mainFrame.mainResource.urlComponents.host || mainFrame.mainResource.displayName || "Archive"; + let url = "web-inspector:///" + encodeURI(archiveName) + ".webarchive"; + + InspectorFrontendHost.save(url, data, true, true); + }); +}; + +WebInspector.canArchiveMainFrame = function() +{ + // COMPATIBILITY (iOS 7): Page.archive did not exist yet. + if (!PageAgent.archive || this.debuggableType !== WebInspector.DebuggableType.Web) + return false; + + if (!WebInspector.frameResourceManager.mainFrame || !WebInspector.frameResourceManager.mainFrame.mainResource) + return false; + + return WebInspector.Resource.typeFromMIMEType(WebInspector.frameResourceManager.mainFrame.mainResource.mimeType) === WebInspector.Resource.Type.Document; +}; + +WebInspector.addWindowKeydownListener = function(listener) +{ + if (typeof listener.handleKeydownEvent !== "function") + return; + + this._windowKeydownListeners.push(listener); + + this._updateWindowKeydownListener(); +}; + +WebInspector.removeWindowKeydownListener = function(listener) +{ + this._windowKeydownListeners.remove(listener); + + this._updateWindowKeydownListener(); +}; + +WebInspector._updateWindowKeydownListener = function() +{ + if (this._windowKeydownListeners.length === 1) + window.addEventListener("keydown", WebInspector._sharedWindowKeydownListener, true); + else if (!this._windowKeydownListeners.length) + window.removeEventListener("keydown", WebInspector._sharedWindowKeydownListener, true); +}; + +WebInspector._sharedWindowKeydownListener = function(event) +{ + for (var i = WebInspector._windowKeydownListeners.length - 1; i >= 0; --i) { + if (WebInspector._windowKeydownListeners[i].handleKeydownEvent(event)) { + event.stopImmediatePropagation(); + event.preventDefault(); + break; + } + } +}; + +WebInspector.reportInternalError = function(errorOrString, details={}) +{ + // The 'details' object includes additional information from the caller as free-form string keys and values. + // Each key and value will be shown in the uncaught exception reporter, console error message, or in + // a pre-filled bug report generated for this internal error. + + let error = (errorOrString instanceof Error) ? errorOrString : new Error(errorOrString); + error.details = details; + + // The error will be displayed in the Uncaught Exception Reporter sheet if DebugUI is enabled. + if (WebInspector.isDebugUIEnabled()) { + // This assert allows us to stop the debugger at an internal exception. It doesn't re-throw + // exceptions because the original exception would be lost through window.onerror. + // This workaround can be removed once <https://webkit.org/b/158192> is fixed. + console.assert(false, "An internal exception was thrown.", error); + handleInternalException(error); + } else + console.error(error); +}; + +Object.defineProperty(WebInspector, "targets", +{ + get() { return this.targetManager.targets; } +}); + +// Many places assume the main target because they cannot yet be +// used by reached by Worker debugging. Eventually, once all +// Worker domains have been implemented, all of these must be +// handled properly. +WebInspector.assumingMainTarget = function() +{ + return WebInspector.mainTarget; +}; + +// OpenResourceDialog delegate + +WebInspector.dialogWasDismissed = function(dialog) +{ + let representedObject = dialog.representedObject; + if (!representedObject) + return; + + WebInspector.showRepresentedObject(representedObject, dialog.cookie); +}; + +WebInspector.DockConfiguration = { + Right: "right", + Left: "left", + Bottom: "bottom", + Undocked: "undocked", +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/Object.js b/Source/WebInspectorUI/UserInterface/Base/Object.js new file mode 100644 index 000000000..2a91983af --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/Object.js @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2008, 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +WebInspector.Object = class WebInspectorObject +{ + constructor() + { + this._listeners = null; + } + + // Static + + static addEventListener(eventType, listener, thisObject) + { + thisObject = thisObject || null; + + console.assert(eventType, "Object.addEventListener: invalid event type ", eventType, "(listener: ", listener, "thisObject: ", thisObject, ")"); + if (!eventType) + return null; + + console.assert(listener, "Object.addEventListener: invalid listener ", listener, "(event type: ", eventType, "thisObject: ", thisObject, ")"); + if (!listener) + return null; + + if (!this._listeners) + this._listeners = new Map(); + + let listenersTable = this._listeners.get(eventType); + if (!listenersTable) { + listenersTable = new ListMultimap(); + this._listeners.set(eventType, listenersTable); + } + + listenersTable.add(thisObject, listener); + return listener; + } + + static singleFireEventListener(eventType, listener, thisObject) + { + let wrappedCallback = function() { + this.removeEventListener(eventType, wrappedCallback, null); + listener.apply(thisObject, arguments); + }.bind(this); + + this.addEventListener(eventType, wrappedCallback, null); + return wrappedCallback; + } + + static removeEventListener(eventType, listener, thisObject) + { + eventType = eventType || null; + listener = listener || null; + thisObject = thisObject || null; + + if (!this._listeners) + return; + + if (thisObject && !eventType) { + this._listeners.forEach(function(listenersTable) { + let listenerPairs = listenersTable.toArray(); + for (let i = 0, length = listenerPairs.length; i < length; ++i) { + let existingThisObject = listenerPairs[i][0]; + if (existingThisObject === thisObject) + listenersTable.deleteAll(existingThisObject); + } + }); + + return; + } + + let listenersTable = this._listeners.get(eventType); + if (!listenersTable || listenersTable.size === 0) + return; + + let didDelete = listenersTable.delete(thisObject, listener); + console.assert(didDelete, "removeEventListener cannot remove " + eventType.toString() + " because it doesn't exist."); + } + + static awaitEvent(eventType) + { + let wrapper = new WebInspector.WrappedPromise; + this.singleFireEventListener(eventType, (event) => wrapper.resolve(event)); + return wrapper.promise; + } + + // Only used by tests. + static hasEventListeners(eventType) + { + if (!this._listeners) + return false; + + let listenersTable = this._listeners.get(eventType); + return listenersTable && listenersTable.size > 0; + } + + // This should only be used within regression tests to detect leaks. + static retainedObjectsWithPrototype(proto) + { + let results = new Set; + + if (this._listeners) { + this._listeners.forEach(function(listenersTable, eventType) { + listenersTable.forEach(function(pair) { + let thisObject = pair[0]; + if (thisObject instanceof proto) + results.add(thisObject); + }); + }); + } + + return results; + } + + // Public + + addEventListener() { return WebInspector.Object.addEventListener.apply(this, arguments); } + singleFireEventListener() { return WebInspector.Object.singleFireEventListener.apply(this, arguments); } + removeEventListener() { return WebInspector.Object.removeEventListener.apply(this, arguments); } + awaitEvent() { return WebInspector.Object.awaitEvent.apply(this, arguments); } + hasEventListeners() { return WebInspector.Object.hasEventListeners.apply(this, arguments); } + retainedObjectsWithPrototype() { return WebInspector.Object.retainedObjectsWithPrototype.apply(this, arguments); } + + dispatchEventToListeners(eventType, eventData) + { + let event = new WebInspector.Event(this, eventType, eventData); + + function dispatch(object) + { + if (!object || event._stoppedPropagation) + return; + + let listenerTypesMap = object._listeners; + if (!listenerTypesMap || !object.hasOwnProperty("_listeners")) + return; + + console.assert(listenerTypesMap instanceof Map); + + let listenersTable = listenerTypesMap.get(eventType); + if (!listenersTable) + return; + + // Make a copy with slice so mutations during the loop doesn't affect us. + let listeners = listenersTable.toArray(); + + // Iterate over the listeners and call them. Stop if stopPropagation is called. + for (let i = 0, length = listeners.length; i < length; ++i) { + let [thisObject, listener] = listeners[i]; + listener.call(thisObject, event); + if (event._stoppedPropagation) + break; + } + } + + // Dispatch to listeners of this specific object. + dispatch(this); + + // Allow propagation again so listeners on the constructor always have a crack at the event. + event._stoppedPropagation = false; + + // Dispatch to listeners on all constructors up the prototype chain, including the immediate constructor. + let constructor = this.constructor; + while (constructor) { + dispatch(constructor); + + if (!constructor.prototype.__proto__) + break; + + constructor = constructor.prototype.__proto__.constructor; + } + + return event.defaultPrevented; + } +}; + +WebInspector.Event = class Event +{ + constructor(target, type, data) + { + this.target = target; + this.type = type; + this.data = data; + this.defaultPrevented = false; + this._stoppedPropagation = false; + } + + stopPropagation() + { + this._stoppedPropagation = true; + } + + preventDefault() + { + this.defaultPrevented = true; + } +}; + +WebInspector.notifications = new WebInspector.Object; + +WebInspector.Notification = { + GlobalModifierKeysDidChange: "global-modifiers-did-change", + PageArchiveStarted: "page-archive-started", + PageArchiveEnded: "page-archive-ended", + ExtraDomainsActivated: "extra-domains-activated", + TabTypesChanged: "tab-types-changed", + DebugUIEnabledDidChange: "debug-ui-enabled-did-change", + VisibilityStateDidChange: "visibility-state-did-change", +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/Platform.js b/Source/WebInspectorUI/UserInterface/Base/Platform.js new file mode 100644 index 000000000..65764ec7d --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/Platform.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +WebInspector.Platform = { + name: InspectorFrontendHost.platform(), + isNightlyBuild: false, + version: { + base: 0, + release: 0, + name: "" + } +}; + +(function () { + // Check for a nightly build by looking for a plus in the version number and a small number of stylesheets (indicating combined resources). + var versionMatch = / AppleWebKit\/([^ ]+)/.exec(navigator.userAgent); + if (versionMatch && versionMatch[1].indexOf("+") !== -1 && document.styleSheets.length < 10) + WebInspector.Platform.isNightlyBuild = true; + + var osVersionMatch = / Mac OS X (\d+)_(\d+)/.exec(navigator.appVersion); + if (osVersionMatch && osVersionMatch[1] === "10") { + WebInspector.Platform.version.base = 10; + WebInspector.Platform.version.release = parseInt(osVersionMatch[2]); + switch (osVersionMatch[2]) { + case "12": + WebInspector.Platform.version.name = "sierra"; + break; + case "11": + WebInspector.Platform.version.name = "el-capitan"; + break; + case "10": + WebInspector.Platform.version.name = "yosemite"; + break; + default: + WebInspector.Platform.version.name = "unknown-mac"; + break; + } + } +})(); diff --git a/Source/WebInspectorUI/UserInterface/Base/Setting.js b/Source/WebInspectorUI/UserInterface/Base/Setting.js new file mode 100644 index 000000000..257524e3f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/Setting.js @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2013 Apple 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. + */ + +WebInspector.Setting = class Setting extends WebInspector.Object +{ + constructor(name, defaultValue) + { + super(); + + this._name = name; + + let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel() : 1; + let levelString = inspectionLevel > 1 ? "-" + inspectionLevel : ""; + this._localStorageKey = `com.apple.WebInspector${levelString}.${name}`; + this._defaultValue = defaultValue; + } + + // Public + + get name() + { + return this._name; + } + + get value() + { + if ("_value" in this) + return this._value; + + // Make a copy of the default value so changes to object values don't modify the default value. + this._value = JSON.parse(JSON.stringify(this._defaultValue)); + + if (!window.InspectorTest && window.localStorage && this._localStorageKey in window.localStorage) { + try { + this._value = JSON.parse(window.localStorage[this._localStorageKey]); + } catch (e) { + delete window.localStorage[this._localStorageKey]; + } + } + + return this._value; + } + + set value(value) + { + if (this._value === value) + return; + + this._value = value; + + if (!window.InspectorTest && window.localStorage) { + try { + // Use Object.shallowEqual to properly compare objects. + if (Object.shallowEqual(this._value, this._defaultValue)) + delete window.localStorage[this._localStorageKey]; + else + window.localStorage[this._localStorageKey] = JSON.stringify(this._value); + } catch (e) { + console.error("Error saving setting with name: " + this._name); + } + } + + this.dispatchEventToListeners(WebInspector.Setting.Event.Changed, this._value, {name: this._name}); + } + + reset() + { + // Make a copy of the default value so changes to object values don't modify the default value. + this.value = JSON.parse(JSON.stringify(this._defaultValue)); + } +}; + +WebInspector.Setting.Event = { + Changed: "setting-changed" +}; + +WebInspector.settings = { + enableLineWrapping: new WebInspector.Setting("enable-line-wrapping", false), + indentUnit: new WebInspector.Setting("indent-unit", 4), + tabSize: new WebInspector.Setting("tab-size", 4), + indentWithTabs: new WebInspector.Setting("indent-with-tabs", false), + showWhitespaceCharacters: new WebInspector.Setting("show-whitespace-characters", false), + showInvalidCharacters: new WebInspector.Setting("show-invalid-characters", false), + clearLogOnNavigate: new WebInspector.Setting("clear-log-on-navigate", true), + clearNetworkOnNavigate: new WebInspector.Setting("clear-network-on-navigate", true), + zoomFactor: new WebInspector.Setting("zoom-factor", 1), + // FIXME: change initial value to 'system' once we are happy with RTL support. + // This will cause Web Inspector to use the system user interface layout direction. + layoutDirection: new WebInspector.Setting("layout-direction", "ltr"), +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/URLUtilities.js b/Source/WebInspectorUI/UserInterface/Base/URLUtilities.js new file mode 100644 index 000000000..e3935f504 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/URLUtilities.js @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +// FIXME: <https://webkit.org/b/165155> Web Inspector: Use URL constructor to better handle all kinds of URLs + +function removeURLFragment(url) +{ + var hashIndex = url.indexOf("#"); + if (hashIndex >= 0) + return url.substring(0, hashIndex); + return url; +} + +function relativePath(path, basePath) +{ + console.assert(path.charAt(0) === "/"); + console.assert(basePath.charAt(0) === "/"); + + var pathComponents = path.split("/"); + var baseComponents = basePath.replace(/\/$/, "").split("/"); + var finalComponents = []; + + var index = 1; + for (; index < pathComponents.length && index < baseComponents.length; ++index) { + if (pathComponents[index] !== baseComponents[index]) + break; + } + + for (var i = index; i < baseComponents.length; ++i) + finalComponents.push(".."); + + for (var i = index; i < pathComponents.length; ++i) + finalComponents.push(pathComponents[i]); + + return finalComponents.join("/"); +} + +function parseSecurityOrigin(securityOrigin) +{ + securityOrigin = securityOrigin ? securityOrigin.trim() : ""; + + var match = securityOrigin.match(/^([^:]+):\/\/([^\/:]*)(?::([\d]+))?$/i); + if (!match) + return {scheme: null, host: null, port: null}; + + var scheme = match[1].toLowerCase(); + var host = match[2].toLowerCase(); + var port = Number(match[3]) || null; + + return {scheme, host, port}; +} + +function parseDataURL(url) +{ + if (!url.startsWith("data:")) + return null; + + // data:[<media type>][;charset=<character set>][;base64],<data> + let match = url.match(/^data:([^;,]*)?(?:;charset=([^;,]*?))?(;base64)?,(.*)$/); + if (!match) + return null; + + let scheme = "data"; + let mimeType = match[1] || "text/plain"; + let charset = match[2] || "US-ASCII"; + let base64 = !!match[3]; + let data = decodeURIComponent(match[4]); + + return {scheme, mimeType, charset, base64, data}; +} + +function parseURL(url) +{ + url = url ? url.trim() : ""; + + if (url.startsWith("data:")) + return {scheme: "data", host: null, port: null, path: null, queryString: null, fragment: null, lastPathComponent: null}; + + var match = url.match(/^([^\/:]+):\/\/([^\/#:]*)(?::([\d]+))?(?:(\/[^#]*)?(?:#(.*))?)?$/i); + if (!match) + return {scheme: null, host: null, port: null, path: null, queryString: null, fragment: null, lastPathComponent: null}; + + var scheme = match[1].toLowerCase(); + var host = match[2].toLowerCase(); + var port = Number(match[3]) || null; + var wholePath = match[4] || null; + var fragment = match[5] || null; + var path = wholePath; + var queryString = null; + + // Split the path and the query string. + if (wholePath) { + var indexOfQuery = wholePath.indexOf("?"); + if (indexOfQuery !== -1) { + path = wholePath.substring(0, indexOfQuery); + queryString = wholePath.substring(indexOfQuery + 1); + } + path = resolveDotsInPath(path); + } + + // Find last path component. + var lastPathComponent = null; + if (path && path !== "/") { + // Skip the trailing slash if there is one. + var endOffset = path[path.length - 1] === "/" ? 1 : 0; + var lastSlashIndex = path.lastIndexOf("/", path.length - 1 - endOffset); + if (lastSlashIndex !== -1) + lastPathComponent = path.substring(lastSlashIndex + 1, path.length - endOffset); + } + + return {scheme, host, port, path, queryString, fragment, lastPathComponent}; +} + +function absoluteURL(partialURL, baseURL) +{ + partialURL = partialURL ? partialURL.trim() : ""; + + // Return data and javascript URLs as-is. + if (partialURL.startsWith("data:") || partialURL.startsWith("javascript:") || partialURL.startsWith("mailto:")) + return partialURL; + + // If the URL has a scheme it is already a full URL, so return it. + if (parseURL(partialURL).scheme) + return partialURL; + + // If there is no partial URL, just return the base URL. + if (!partialURL) + return baseURL || null; + + var baseURLComponents = parseURL(baseURL); + + // The base URL needs to be an absolute URL. Return null if it isn't. + if (!baseURLComponents.scheme) + return null; + + // A URL that starts with "//" is a full URL without the scheme. Use the base URL scheme. + if (partialURL[0] === "/" && partialURL[1] === "/") + return baseURLComponents.scheme + ":" + partialURL; + + // The path can be null for URLs that have just a scheme and host (like "http://apple.com"). So make the path be "/". + if (!baseURLComponents.path) + baseURLComponents.path = "/"; + + // Generate the base URL prefix that is used in the rest of the cases. + var baseURLPrefix = baseURLComponents.scheme + "://" + baseURLComponents.host + (baseURLComponents.port ? (":" + baseURLComponents.port) : ""); + + // A URL that starts with "?" is just a query string that gets applied to the base URL (replacing the base URL query string and fragment). + if (partialURL[0] === "?") + return baseURLPrefix + baseURLComponents.path + partialURL; + + // A URL that starts with "/" is an absolute path that gets applied to the base URL (replacing the base URL path, query string and fragment). + if (partialURL[0] === "/") + return baseURLPrefix + resolveDotsInPath(partialURL); + + // A URL that starts with "#" is just a fragment that gets applied to the base URL (replacing the base URL fragment, maintaining the query string). + if (partialURL[0] === "#") { + let queryStringComponent = baseURLComponents.queryString ? "?" + baseURLComponents.queryString : ""; + return baseURLPrefix + baseURLComponents.path + queryStringComponent + partialURL; + } + + // Generate the base path that is used in the final case by removing everything after the last "/" from the base URL's path. + var basePath = baseURLComponents.path.substring(0, baseURLComponents.path.lastIndexOf("/")) + "/"; + return baseURLPrefix + resolveDotsInPath(basePath + partialURL); +} + +function parseLocationQueryParameters(arrayResult) +{ + // The first character is always the "?". + return parseQueryString(window.location.search.substring(1), arrayResult); +} + +function parseQueryString(queryString, arrayResult) +{ + if (!queryString) + return arrayResult ? [] : {}; + + function decode(string) + { + try { + // Replace "+" with " " then decode percent encoded values. + return decodeURIComponent(string.replace(/\+/g, " ")); + } catch (e) { + return string; + } + } + + var parameters = arrayResult ? [] : {}; + var parameterStrings = queryString.split("&"); + for (var i = 0; i < parameterStrings.length; ++i) { + var pair = parameterStrings[i].split("=").map(decode); + if (arrayResult) + parameters.push({name: pair[0], value: pair[1]}); + else + parameters[pair[0]] = pair[1]; + } + + return parameters; +} + +WebInspector.displayNameForURL = function(url, urlComponents) +{ + if (url.startsWith("data:")) + return WebInspector.truncateURL(url); + + if (!urlComponents) + urlComponents = parseURL(url); + + var displayName; + try { + displayName = decodeURIComponent(urlComponents.lastPathComponent || ""); + } catch (e) { + displayName = urlComponents.lastPathComponent; + } + + return displayName || WebInspector.displayNameForHost(urlComponents.host) || url; +}; + +WebInspector.truncateURL = function(url, multiline = false, dataURIMaxSize = 6) +{ + if (!url.startsWith("data:")) + return url; + + const dataIndex = url.indexOf(",") + 1; + let header = url.slice(0, dataIndex); + if (multiline) + header += "\n"; + + const data = url.slice(dataIndex); + if (data.length < dataURIMaxSize) + return header + data; + + const firstChunk = data.slice(0, Math.ceil(dataURIMaxSize / 2)); + const ellipsis = "\u2026"; + const middleChunk = multiline ? `\n${ellipsis}\n` : ellipsis; + const lastChunk = data.slice(-Math.floor(dataURIMaxSize / 2)); + return header + firstChunk + middleChunk + lastChunk; +}; + +WebInspector.displayNameForHost = function(host) +{ + // FIXME <rdar://problem/11237413>: This should decode punycode hostnames. + return host; +}; diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js new file mode 100644 index 000000000..06c2f1cb5 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js @@ -0,0 +1,1509 @@ +/* + * Copyright (C) 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 emDash = "\u2014"; +var enDash = "\u2013"; +var figureDash = "\u2012"; +var ellipsis = "\u2026"; + +Object.defineProperty(Object, "shallowCopy", +{ + value: function(object) + { + // Make a new object and copy all the key/values. The values are not copied. + var copy = {}; + var keys = Object.keys(object); + for (var i = 0; i < keys.length; ++i) + copy[keys[i]] = object[keys[i]]; + return copy; + } +}); + +Object.defineProperty(Object, "shallowEqual", +{ + value: function(a, b) + { + // Checks if two objects have the same top-level properties. + + // Only objects can proceed. + if (!(a instanceof Object) || !(b instanceof Object)) + return false; + + // Check for strict equality in case they are the same object. + if (a === b) + return true; + + // Use an optimized version of shallowEqual for arrays. + if (Array.isArray(a) && Array.isArray(b)) + return Array.shallowEqual(a, b); + + if (a.constructor !== b.constructor) + return false; + + var aKeys = Object.keys(a); + var bKeys = Object.keys(b); + + // Check that each object has the same number of keys. + if (aKeys.length !== bKeys.length) + return false; + + // Check if all the keys and their values are equal. + for (var i = 0; i < aKeys.length; ++i) { + // Check that b has the same key as a. + if (!(aKeys[i] in b)) + return false; + + // Check that the values are strict equal since this is only + // a shallow check, not a recursive one. + if (a[aKeys[i]] !== b[aKeys[i]]) + return false; + } + + return true; + } +}); + +Object.defineProperty(Object, "shallowMerge", +{ + value(a, b) + { + let result = Object.shallowCopy(a); + let keys = Object.keys(b); + for (let i = 0; i < keys.length; ++i) { + console.assert(!result.hasOwnProperty(keys[i]) || result[keys[i]] === b[keys[i]], keys[i]); + result[keys[i]] = b[keys[i]]; + } + return result; + } +}); + +Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey", +{ + value: function(key) + { + if (this.hasOwnProperty(key)) + return this[key]; + + var lowerCaseKey = key.toLowerCase(); + for (var currentKey in this) { + if (currentKey.toLowerCase() === lowerCaseKey) + return this[currentKey]; + } + + return undefined; + } +}); + +Object.defineProperty(Map, "fromObject", +{ + value: function(object) + { + let map = new Map; + for (let key in object) + map.set(key, object[key]); + return map; + } +}); + +Object.defineProperty(Map.prototype, "take", +{ + value: function(key) + { + var deletedValue = this.get(key); + this.delete(key); + return deletedValue; + } +}); + +Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithClass", +{ + value: function(className) + { + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) + if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className)) + return node; + return null; + } +}); + +Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeNameInArray", +{ + value: function(nameArray) + { + var lowerCaseNameArray = nameArray.map(function(name) { return name.toLowerCase(); }); + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) { + for (var i = 0; i < nameArray.length; ++i) { + if (node.nodeName.toLowerCase() === lowerCaseNameArray[i]) + return node; + } + } + + return null; + } +}); + +Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName", +{ + value: function(nodeName) + { + return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); + } +}); + +Object.defineProperty(Node.prototype, "isAncestor", +{ + value: function(node) + { + if (!node) + return false; + + var currentNode = node.parentNode; + while (currentNode) { + if (this === currentNode) + return true; + currentNode = currentNode.parentNode; + } + + return false; + } +}); + +Object.defineProperty(Node.prototype, "isDescendant", +{ + value: function(descendant) + { + return !!descendant && descendant.isAncestor(this); + } +}); + + +Object.defineProperty(Node.prototype, "isSelfOrAncestor", +{ + value: function(node) + { + return !!node && (node === this || this.isAncestor(node)); + } +}); + + +Object.defineProperty(Node.prototype, "isSelfOrDescendant", +{ + value: function(node) + { + return !!node && (node === this || this.isDescendant(node)); + } +}); + +Object.defineProperty(Node.prototype, "traverseNextNode", +{ + value: function(stayWithin) + { + var node = this.firstChild; + if (node) + return node; + + if (stayWithin && this === stayWithin) + return null; + + node = this.nextSibling; + if (node) + return node; + + node = this; + while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) + node = node.parentNode; + if (!node) + return null; + + return node.nextSibling; + } +}); + +Object.defineProperty(Node.prototype, "traversePreviousNode", +{ + value: function(stayWithin) + { + if (stayWithin && this === stayWithin) + return null; + var node = this.previousSibling; + while (node && node.lastChild) + node = node.lastChild; + if (node) + return node; + return this.parentNode; + } +}); + + +Object.defineProperty(Node.prototype, "rangeOfWord", +{ + value: function(offset, stopCharacters, stayWithinNode, direction) + { + var startNode; + var startOffset = 0; + var endNode; + var endOffset = 0; + + if (!stayWithinNode) + stayWithinNode = this; + + if (!direction || direction === "backward" || direction === "both") { + var node = this; + while (node) { + if (node === stayWithinNode) { + if (!startNode) + startNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1)); + for (var i = start; i >= 0; --i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + startNode = node; + startOffset = i + 1; + break; + } + } + } + + if (startNode) + break; + + node = node.traversePreviousNode(stayWithinNode); + } + + if (!startNode) { + startNode = stayWithinNode; + startOffset = 0; + } + } else { + startNode = this; + startOffset = offset; + } + + if (!direction || direction === "forward" || direction === "both") { + node = this; + while (node) { + if (node === stayWithinNode) { + if (!endNode) + endNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? offset : 0); + for (var i = start; i < node.nodeValue.length; ++i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + endNode = node; + endOffset = i; + break; + } + } + } + + if (endNode) + break; + + node = node.traverseNextNode(stayWithinNode); + } + + if (!endNode) { + endNode = stayWithinNode; + endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; + } + } else { + endNode = this; + endOffset = offset; + } + + var result = this.ownerDocument.createRange(); + result.setStart(startNode, startOffset); + result.setEnd(endNode, endOffset); + + return result; + + } +}); + +Object.defineProperty(Element.prototype, "realOffsetWidth", +{ + get: function() + { + return this.getBoundingClientRect().width; + } +}); + +Object.defineProperty(Element.prototype, "realOffsetHeight", +{ + get: function() + { + return this.getBoundingClientRect().height; + } +}); + +Object.defineProperty(Element.prototype, "totalOffsetLeft", +{ + get: function() + { + return this.getBoundingClientRect().left; + } +}); + +Object.defineProperty(Element.prototype, "totalOffsetTop", +{ + get: function() + { + return this.getBoundingClientRect().top; + } +}); + +Object.defineProperty(Element.prototype, "removeChildren", +{ + value: function() + { + // This has been tested to be the fastest removal method. + if (this.firstChild) + this.textContent = ""; + } +}); + +Object.defineProperty(Element.prototype, "isInsertionCaretInside", +{ + value: function() + { + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + var selectionRange = selection.getRangeAt(0); + return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this); + } +}); + +Object.defineProperty(Element.prototype, "removeMatchingStyleClasses", +{ + value: function(classNameRegex) + { + var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)"); + if (regex.test(this.className)) + this.className = this.className.replace(regex, " "); + } +}); + +Object.defineProperty(Element.prototype, "createChild", +{ + value: function(elementName, className) + { + var element = this.ownerDocument.createElement(elementName); + if (className) + element.className = className; + this.appendChild(element); + return element; + } +}); + +Object.defineProperty(Element.prototype, "isScrolledToBottom", +{ + value: function() + { + // This code works only for 0-width border + return this.scrollTop + this.clientHeight === this.scrollHeight; + } +}); + +Object.defineProperty(Element.prototype, "recalculateStyles", +{ + value: function() + { + this.ownerDocument.defaultView.getComputedStyle(this); + } +}); + +Object.defineProperty(DocumentFragment.prototype, "createChild", +{ + value: Element.prototype.createChild +}); + +Object.defineProperty(Array, "shallowEqual", +{ + value: function(a, b) + { + if (!Array.isArray(a) || !Array.isArray(b)) + return false; + + if (a === b) + return true; + + let length = a.length; + + if (length !== b.length) + return false; + + for (let i = 0; i < length; ++i) { + if (a[i] === b[i]) + continue; + + if (!Object.shallowEqual(a[i], b[i])) + return false; + } + + return true; + } +}); + +Object.defineProperty(Array.prototype, "lastValue", +{ + get: function() + { + if (!this.length) + return undefined; + return this[this.length - 1]; + } +}); + +Object.defineProperty(Array.prototype, "remove", +{ + value: function(value, onlyFirst) + { + for (var i = this.length - 1; i >= 0; --i) { + if (this[i] === value) { + this.splice(i, 1); + if (onlyFirst) + return; + } + } + } +}); + +Object.defineProperty(Array.prototype, "toggleIncludes", +{ + value: function(value, force) + { + let exists = this.includes(value); + if (exists === !!force) + return; + + if (exists) + this.remove(value); + else + this.push(value); + } +}); + +Object.defineProperty(Array.prototype, "insertAtIndex", +{ + value: function(value, index) + { + this.splice(index, 0, value); + } +}); + +Object.defineProperty(Array.prototype, "keySet", +{ + value: function() + { + let keys = Object.create(null); + for (var i = 0; i < this.length; ++i) + keys[this[i]] = true; + return keys; + } +}); + +Object.defineProperty(Array.prototype, "partition", +{ + value: function(callback) + { + let positive = []; + let negative = []; + for (let i = 0; i < this.length; ++i) { + let value = this[i]; + if (callback(value)) + positive.push(value); + else + negative.push(value); + } + return [positive, negative]; + } +}); + +Object.defineProperty(String.prototype, "isLowerCase", +{ + value: function() + { + return String(this) === this.toLowerCase(); + } +}); + +Object.defineProperty(String.prototype, "isUpperCase", +{ + value: function() + { + return String(this) === this.toUpperCase(); + } +}); + +Object.defineProperty(String.prototype, "trimMiddle", +{ + value: function(maxLength) + { + if (this.length <= maxLength) + return this; + var leftHalf = maxLength >> 1; + var rightHalf = maxLength - leftHalf - 1; + return this.substr(0, leftHalf) + ellipsis + this.substr(this.length - rightHalf, rightHalf); + } +}); + +Object.defineProperty(String.prototype, "trimEnd", +{ + value: function(maxLength) + { + if (this.length <= maxLength) + return this; + return this.substr(0, maxLength - 1) + ellipsis; + } +}); + +Object.defineProperty(String.prototype, "truncate", +{ + value: function(maxLength) + { + "use strict"; + + if (this.length <= maxLength) + return this; + + let clipped = this.slice(0, maxLength); + let indexOfLastWhitespace = clipped.search(/\s\S*$/); + if (indexOfLastWhitespace > Math.floor(maxLength / 2)) + clipped = clipped.slice(0, indexOfLastWhitespace - 1); + + return clipped + ellipsis; + } +}); + +Object.defineProperty(String.prototype, "collapseWhitespace", +{ + value: function() + { + return this.replace(/[\s\xA0]+/g, " "); + } +}); + +Object.defineProperty(String.prototype, "removeWhitespace", +{ + value: function() + { + return this.replace(/[\s\xA0]+/g, ""); + } +}); + +Object.defineProperty(String.prototype, "escapeCharacters", +{ + value: function(chars) + { + var foundChar = false; + for (var i = 0; i < chars.length; ++i) { + if (this.indexOf(chars.charAt(i)) !== -1) { + foundChar = true; + break; + } + } + + if (!foundChar) + return this; + + var result = ""; + for (var i = 0; i < this.length; ++i) { + if (chars.indexOf(this.charAt(i)) !== -1) + result += "\\"; + result += this.charAt(i); + } + + return result; + } +}); + +Object.defineProperty(String.prototype, "escapeForRegExp", +{ + value: function() + { + return this.escapeCharacters("^[]{}()\\.$*+?|"); + } +}); + +Object.defineProperty(String.prototype, "capitalize", +{ + value: function() + { + return this.charAt(0).toUpperCase() + this.slice(1); + } +}); + +Object.defineProperty(String, "tokenizeFormatString", +{ + value: function(format) + { + var tokens = []; + var substitutionIndex = 0; + + function addStringToken(str) + { + tokens.push({type: "string", value: str}); + } + + function addSpecifierToken(specifier, precision, substitutionIndex) + { + tokens.push({type: "specifier", specifier, precision, substitutionIndex}); + } + + var index = 0; + for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { + addStringToken(format.substring(index, precentIndex)); + index = precentIndex + 1; + + if (format[index] === "%") { + addStringToken("%"); + ++index; + continue; + } + + if (!isNaN(format[index])) { + // The first character is a number, it might be a substitution index. + var number = parseInt(format.substring(index), 10); + while (!isNaN(format[index])) + ++index; + + // If the number is greater than zero and ends with a "$", + // then this is a substitution index. + if (number > 0 && format[index] === "$") { + substitutionIndex = (number - 1); + ++index; + } + } + + const defaultPrecision = 6; + + let precision = defaultPrecision; + if (format[index] === ".") { + // This is a precision specifier. If no digit follows the ".", + // then use the default precision of six digits (ISO C99 specification). + ++index; + + precision = parseInt(format.substring(index), 10); + if (isNaN(precision)) + precision = defaultPrecision; + + while (!isNaN(format[index])) + ++index; + } + + addSpecifierToken(format[index], precision, substitutionIndex); + + ++substitutionIndex; + ++index; + } + + addStringToken(format.substring(index)); + + return tokens; + } +}); + +Object.defineProperty(String.prototype, "hash", +{ + get: function() + { + // Matches the wtf/Hasher.h (SuperFastHash) algorithm. + + // Arbitrary start value to avoid mapping all 0's to all 0's. + const stringHashingStartValue = 0x9e3779b9; + + var result = stringHashingStartValue; + var pendingCharacter = null; + for (var i = 0; i < this.length; ++i) { + var currentCharacter = this[i].charCodeAt(0); + if (pendingCharacter === null) { + pendingCharacter = currentCharacter; + continue; + } + + result += pendingCharacter; + result = (result << 16) ^ ((currentCharacter << 11) ^ result); + result += result >> 11; + + pendingCharacter = null; + } + + // Handle the last character in odd length strings. + if (pendingCharacter !== null) { + result += pendingCharacter; + result ^= result << 11; + result += result >> 17; + } + + // Force "avalanching" of final 31 bits. + result ^= result << 3; + result += result >> 5; + result ^= result << 2; + result += result >> 15; + result ^= result << 10; + + // Prevent 0 and negative results. + return (0xffffffff + result + 1).toString(36); + } +}); + +Object.defineProperty(String, "standardFormatters", +{ + value: { + d: function(substitution) + { + return parseInt(substitution); + }, + + f: function(substitution, token) + { + let value = parseFloat(substitution); + if (isNaN(value)) + return NaN; + + let options = { + minimumFractionDigits: token.precision, + maximumFractionDigits: token.precision, + useGrouping: false + }; + return value.toLocaleString(undefined, options); + }, + + s: function(substitution) + { + return substitution; + } + } +}); + +Object.defineProperty(String, "format", +{ + value: function(format, substitutions, formatters, initialValue, append) + { + if (!format || !substitutions || !substitutions.length) + return {formattedResult: append(initialValue, format), unusedSubstitutions: substitutions}; + + function prettyFunctionName() + { + return "String.format(\"" + format + "\", \"" + Array.from(substitutions).join("\", \"") + "\")"; + } + + function warn(msg) + { + console.warn(prettyFunctionName() + ": " + msg); + } + + function error(msg) + { + console.error(prettyFunctionName() + ": " + msg); + } + + var result = initialValue; + var tokens = String.tokenizeFormatString(format); + var usedSubstitutionIndexes = {}; + + for (var i = 0; i < tokens.length; ++i) { + var token = tokens[i]; + + if (token.type === "string") { + result = append(result, token.value); + continue; + } + + if (token.type !== "specifier") { + error("Unknown token type \"" + token.type + "\" found."); + continue; + } + + if (token.substitutionIndex >= substitutions.length) { + // If there are not enough substitutions for the current substitutionIndex + // just output the format specifier literally and move on. + error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); + result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); + continue; + } + + usedSubstitutionIndexes[token.substitutionIndex] = true; + + if (!(token.specifier in formatters)) { + // Encountered an unsupported format character, treat as a string. + warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); + result = append(result, substitutions[token.substitutionIndex]); + continue; + } + + result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); + } + + var unusedSubstitutions = []; + for (var i = 0; i < substitutions.length; ++i) { + if (i in usedSubstitutionIndexes) + continue; + unusedSubstitutions.push(substitutions[i]); + } + + return {formattedResult: result, unusedSubstitutions}; + } +}); + +Object.defineProperty(String.prototype, "format", +{ + value: function() + { + return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; + } +}); + +Object.defineProperty(String.prototype, "insertWordBreakCharacters", +{ + value: function() + { + // Add zero width spaces after characters that are good to break after. + // Otherwise a string with no spaces will not break and overflow its container. + // This is mainly used on URL strings, so the characters are tailored for URLs. + return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b"); + } +}); + +Object.defineProperty(String.prototype, "removeWordBreakCharacters", +{ + value: function() + { + // Undoes what insertWordBreakCharacters did. + return this.replace(/\u200b/g, ""); + } +}); + +Object.defineProperty(String.prototype, "getMatchingIndexes", +{ + value: function(needle) + { + var indexesOfNeedle = []; + var index = this.indexOf(needle); + + while (index >= 0) { + indexesOfNeedle.push(index); + index = this.indexOf(needle, index + 1); + } + + return indexesOfNeedle; + } +}); + +Object.defineProperty(String.prototype, "levenshteinDistance", +{ + value: function(s) + { + var m = this.length; + var n = s.length; + var d = new Array(m + 1); + + for (var i = 0; i <= m; ++i) { + d[i] = new Array(n + 1); + d[i][0] = i; + } + + for (var j = 0; j <= n; ++j) + d[0][j] = j; + + for (var j = 1; j <= n; ++j) { + for (var i = 1; i <= m; ++i) { + if (this[i - 1] === s[j - 1]) + d[i][j] = d[i - 1][j - 1]; + else { + var deletion = d[i - 1][j] + 1; + var insertion = d[i][j - 1] + 1; + var substitution = d[i - 1][j - 1] + 1; + d[i][j] = Math.min(deletion, insertion, substitution); + } + } + } + + return d[m][n]; + } +}); + +Object.defineProperty(String.prototype, "toCamelCase", +{ + value: function() + { + return this.toLowerCase().replace(/[^\w]+(\w)/g, (match, group) => group.toUpperCase()); + } +}); + +Object.defineProperty(String.prototype, "hasMatchingEscapedQuotes", +{ + value: function() + { + return /^\"(?:[^\"\\]|\\.)*\"$/.test(this) || /^\'(?:[^\'\\]|\\.)*\'$/.test(this); + } +}); + +Object.defineProperty(Math, "roundTo", +{ + value: function(num, step) + { + return Math.round(num / step) * step; + } +}); + +Object.defineProperty(Number, "constrain", +{ + value: function(num, min, max) + { + if (max < min) + return min; + + if (num < min) + num = min; + else if (num > max) + num = max; + return num; + } +}); + +Object.defineProperty(Number, "percentageString", +{ + value: function(fraction, precision = 1) + { + console.assert(fraction >= 0 && fraction <= 1); + return fraction.toLocaleString(undefined, {minimumFractionDigits: precision, style: "percent"}); + } +}); + +Object.defineProperty(Number, "secondsToMillisecondsString", +{ + value: function(seconds, higherResolution) + { + let ms = seconds * 1000; + + if (higherResolution) + return WebInspector.UIString("%.2fms").format(ms); + return WebInspector.UIString("%.1fms").format(ms); + } +}); + +Object.defineProperty(Number, "secondsToString", +{ + value: function(seconds, higherResolution) + { + let ms = seconds * 1000; + if (!ms) + return WebInspector.UIString("%.0fms").format(0); + + if (Math.abs(ms) < 10) { + if (higherResolution) + return WebInspector.UIString("%.3fms").format(ms); + return WebInspector.UIString("%.2fms").format(ms); + } + + if (Math.abs(ms) < 100) { + if (higherResolution) + return WebInspector.UIString("%.2fms").format(ms); + return WebInspector.UIString("%.1fms").format(ms); + } + + if (Math.abs(ms) < 1000) { + if (higherResolution) + return WebInspector.UIString("%.1fms").format(ms); + return WebInspector.UIString("%.0fms").format(ms); + } + + // Do not go over seconds when in high resolution mode. + if (higherResolution || Math.abs(seconds) < 60) + return WebInspector.UIString("%.2fs").format(seconds); + + let minutes = seconds / 60; + if (Math.abs(minutes) < 60) + return WebInspector.UIString("%.1fmin").format(minutes); + + let hours = minutes / 60; + if (Math.abs(hours) < 24) + return WebInspector.UIString("%.1fhrs").format(hours); + + let days = hours / 24; + return WebInspector.UIString("%.1f days").format(days); + } +}); + +Object.defineProperty(Number, "bytesToString", +{ + value: function(bytes, higherResolution) + { + if (higherResolution === undefined) + higherResolution = true; + + if (Math.abs(bytes) < 1024) + return WebInspector.UIString("%.0f B").format(bytes); + + let kilobytes = bytes / 1024; + if (Math.abs(kilobytes) < 1024) { + if (higherResolution || Math.abs(kilobytes) < 10) + return WebInspector.UIString("%.2f KB").format(kilobytes); + return WebInspector.UIString("%.1f KB").format(kilobytes); + } + + let megabytes = kilobytes / 1024; + if (higherResolution || Math.abs(megabytes) < 10) + return WebInspector.UIString("%.2f MB").format(megabytes); + return WebInspector.UIString("%.1f MB").format(megabytes); + } +}); + +Object.defineProperty(Number, "abbreviate", +{ + value: function(num) + { + if (num < 1000) + return num; + + if (num < 1000000) + return WebInspector.UIString("%.1fK").format(Math.round(num / 100) / 10); + + if (num < 1000000000) + return WebInspector.UIString("%.1fM").format(Math.round(num / 100000) / 10); + + return WebInspector.UIString("%.1fB").format(Math.round(num / 100000000) / 10); + } +}); + +Object.defineProperty(Number.prototype, "maxDecimals", +{ + value(decimals) + { + let power = 10 ** decimals; + return Math.round(this * power) / power; + } +}); + +Object.defineProperty(Uint32Array, "isLittleEndian", +{ + value: function() + { + if ("_isLittleEndian" in this) + return this._isLittleEndian; + + var buffer = new ArrayBuffer(4); + var longData = new Uint32Array(buffer); + var data = new Uint8Array(buffer); + + longData[0] = 0x0a0b0c0d; + + this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a; + + return this._isLittleEndian; + } +}); + +function isEmptyObject(object) +{ + for (var property in object) + return false; + return true; +} + +function isEnterKey(event) +{ + // Check if this is an IME event. + return event.keyCode !== 229 && event.keyIdentifier === "Enter"; +} + +function resolveDotsInPath(path) +{ + if (!path) + return path; + + if (path.indexOf("./") === -1) + return path; + + console.assert(path.charAt(0) === "/"); + + var result = []; + + var components = path.split("/"); + for (var i = 0; i < components.length; ++i) { + var component = components[i]; + + // Skip over "./". + if (component === ".") + continue; + + // Rewind one component for "../". + if (component === "..") { + if (result.length === 1) + continue; + result.pop(); + continue; + } + + result.push(component); + } + + return result.join("/"); +} + +function parseMIMEType(fullMimeType) +{ + if (!fullMimeType) + return {type: fullMimeType, boundary: null, encoding: null}; + + var typeParts = fullMimeType.split(/\s*;\s*/); + console.assert(typeParts.length >= 1); + + var type = typeParts[0]; + var boundary = null; + var encoding = null; + + for (var i = 1; i < typeParts.length; ++i) { + var subparts = typeParts[i].split(/\s*=\s*/); + if (subparts.length !== 2) + continue; + + if (subparts[0].toLowerCase() === "boundary") + boundary = subparts[1]; + else if (subparts[0].toLowerCase() === "charset") + encoding = subparts[1].replace("^\"|\"$", ""); // Trim quotes. + } + + return {type, boundary: boundary || null, encoding: encoding || null}; +} + +function simpleGlobStringToRegExp(globString, regExpFlags) +{ + // Only supports "*" globs. + + if (!globString) + return null; + + // Escape everything from String.prototype.escapeForRegExp except "*". + var regexString = globString.escapeCharacters("^[]{}()\\.$+?|"); + + // Unescape all doubly escaped backslashes in front of escaped asterisks. + // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\". + // This makes "\*" match a literal "*" instead of using the "*" for globbing. + regexString = regexString.replace(/\\\\\*/g, "\\*"); + + // The following regex doesn't match an asterisk that has a backslash in front. + // It also catches consecutive asterisks so they collapse down when replaced. + var unescapedAsteriskRegex = /(^|[^\\])\*+/g; + if (unescapedAsteriskRegex.test(globString)) { + // Replace all unescaped asterisks with ".*". + regexString = regexString.replace(unescapedAsteriskRegex, "$1.*"); + + // Match edge boundaries when there is an asterisk to better meet the expectations + // of the user. When someone types "*.js" they don't expect "foo.json" to match. They + // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow + // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js". + // When there isn't an asterisk the regexString is just a substring search. + regexString = "\\b" + regexString + "\\b"; + } + + return new RegExp(regexString, regExpFlags); +} + +Object.defineProperty(Array.prototype, "lowerBound", +{ + // Return index of the leftmost element that is equal or greater + // than the specimen object. If there's no such element (i.e. all + // elements are smaller than the specimen) returns array.length. + // The function works for sorted array. + value: function(object, comparator) + { + function defaultComparator(a, b) + { + return a - b; + } + comparator = comparator || defaultComparator; + var l = 0; + var r = this.length; + while (l < r) { + var m = (l + r) >> 1; + if (comparator(object, this[m]) > 0) + l = m + 1; + else + r = m; + } + return r; + } +}); + +Object.defineProperty(Array.prototype, "upperBound", +{ + // Return index of the leftmost element that is greater + // than the specimen object. If there's no such element (i.e. all + // elements are smaller than the specimen) returns array.length. + // The function works for sorted array. + value: function(object, comparator) + { + function defaultComparator(a, b) + { + return a - b; + } + comparator = comparator || defaultComparator; + var l = 0; + var r = this.length; + while (l < r) { + var m = (l + r) >> 1; + if (comparator(object, this[m]) >= 0) + l = m + 1; + else + r = m; + } + return r; + } +}); + +Object.defineProperty(Array.prototype, "binaryIndexOf", +{ + value: function(value, comparator) + { + var index = this.lowerBound(value, comparator); + return index < this.length && comparator(value, this[index]) === 0 ? index : -1; + } +}); + +(function() { + // The `debounce` function lets you call any function on an object with a delay + // and if the function keeps getting called, the delay gets reset. Since `debounce` + // returns a Proxy, you can cache it and call multiple functions with the same delay. + + // Use: object.debounce(200).foo("Argument 1", "Argument 2") + // Note: The last call's arguments get used for the delayed call. + + const debounceTimeoutSymbol = Symbol("debounce-timeout"); + const debounceSoonProxySymbol = Symbol("debounce-soon-proxy"); + + Object.defineProperty(Object.prototype, "soon", + { + get: function() + { + if (!this[debounceSoonProxySymbol]) + this[debounceSoonProxySymbol] = this.debounce(0); + return this[debounceSoonProxySymbol]; + } + }); + + Object.defineProperty(Object.prototype, "debounce", + { + value: function(delay) + { + console.assert(delay >= 0); + + return new Proxy(this, { + get(target, property, receiver) { + return (...args) => { + let original = target[property]; + console.assert(typeof original === "function"); + + if (original[debounceTimeoutSymbol]) + clearTimeout(original[debounceTimeoutSymbol]); + + let performWork = () => { + original[debounceTimeoutSymbol] = undefined; + original.apply(target, args); + }; + + original[debounceTimeoutSymbol] = setTimeout(performWork, delay); + }; + } + }); + } + }); + + Object.defineProperty(Function.prototype, "cancelDebounce", + { + value: function() + { + if (!this[debounceTimeoutSymbol]) + return; + + clearTimeout(this[debounceTimeoutSymbol]); + this[debounceTimeoutSymbol] = undefined; + } + }); + + const requestAnimationFrameSymbol = Symbol("peform-on-animation-frame"); + const requestAnimationFrameProxySymbol = Symbol("perform-on-animation-frame-proxy"); + + Object.defineProperty(Object.prototype, "onNextFrame", + { + get: function() + { + if (!this[requestAnimationFrameProxySymbol]) { + this[requestAnimationFrameProxySymbol] = new Proxy(this, { + get(target, property, receiver) { + return (...args) => { + let original = target[property]; + console.assert(typeof original === "function"); + + if (original[requestAnimationFrameSymbol]) + return; + + let performWork = () => { + original[requestAnimationFrameSymbol] = undefined; + original.apply(target, args); + }; + + original[requestAnimationFrameSymbol] = requestAnimationFrame(performWork); + }; + } + }); + } + + return this[requestAnimationFrameProxySymbol]; + } + }); +})(); + +function appendWebInspectorSourceURL(string) +{ + if (string.includes("//# sourceURL")) + return string; + return "\n//# sourceURL=__WebInspectorInternal__\n" + string; +} + +function appendWebInspectorConsoleEvaluationSourceURL(string) +{ + if (string.includes("//# sourceURL")) + return string; + return "\n//# sourceURL=__WebInspectorConsoleEvaluation__\n" + string; +} + +function isWebInspectorInternalScript(url) +{ + return url === "__WebInspectorInternal__"; +} + +function isWebInspectorConsoleEvaluationScript(url) +{ + return url === "__WebInspectorConsoleEvaluation__"; +} + +function isWebKitInjectedScript(url) +{ + return url && url.startsWith("__InjectedScript_") && url.endsWith(".js"); +} + +function isWebKitInternalScript(url) +{ + if (isWebInspectorConsoleEvaluationScript(url)) + return false; + + if (isWebKitInjectedScript(url)) + return true; + + return url && url.startsWith("__Web") && url.endsWith("__"); +} + +function isFunctionStringNativeCode(str) +{ + return str.endsWith("{\n [native code]\n}"); +} + +function isTextLikelyMinified(content) +{ + const autoFormatMaxCharactersToCheck = 5000; + const autoFormatWhitespaceRatio = 0.2; + + let whitespaceScore = 0; + let size = Math.min(autoFormatMaxCharactersToCheck, content.length); + + for (let i = 0; i < size; i++) { + let char = content[i]; + + if (char === " ") + whitespaceScore++; + else if (char === "\t") + whitespaceScore += 4; + else if (char === "\n") + whitespaceScore += 8; + } + + let ratio = whitespaceScore / size; + return ratio < autoFormatWhitespaceRatio; +} + +function doubleQuotedString(str) +{ + return "\"" + str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; +} + +function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) +{ + if (insertionIndexAfter) { + return list.upperBound(object, comparator); + } else { + return list.lowerBound(object, comparator); + } +} + +function insertObjectIntoSortedArray(object, array, comparator) +{ + array.splice(insertionIndexForObjectInListSortedByFunction(object, array, comparator), 0, object); +} + +function decodeBase64ToBlob(base64Data, mimeType) +{ + mimeType = mimeType || ''; + + const sliceSize = 1024; + var byteCharacters = atob(base64Data); + var bytesLength = byteCharacters.length; + var slicesCount = Math.ceil(bytesLength / sliceSize); + var byteArrays = new Array(slicesCount); + + for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { + var begin = sliceIndex * sliceSize; + var end = Math.min(begin + sliceSize, bytesLength); + + var bytes = new Array(end - begin); + for (var offset = begin, i = 0 ; offset < end; ++i, ++offset) + bytes[i] = byteCharacters[offset].charCodeAt(0); + + byteArrays[sliceIndex] = new Uint8Array(bytes); + } + + return new Blob(byteArrays, {type: mimeType}); +} + +// FIXME: This can be removed when WEB_TIMING is enabled for all platforms. +function timestamp() +{ + return window.performance ? performance.now() : Date.now(); +} + +if (!window.handlePromiseException) { + window.handlePromiseException = function handlePromiseException(error) + { + console.error("Uncaught exception in Promise", error); + }; +} diff --git a/Source/WebInspectorUI/UserInterface/Base/WebInspector.js b/Source/WebInspectorUI/UserInterface/Base/WebInspector.js new file mode 100644 index 000000000..15460bb44 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/WebInspector.js @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 WebInspector = {}; // Namespace diff --git a/Source/WebInspectorUI/UserInterface/Base/YieldableTask.js b/Source/WebInspectorUI/UserInterface/Base/YieldableTask.js new file mode 100644 index 000000000..e7ceb5119 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Base/YieldableTask.js @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +WebInspector.YieldableTask = class YieldableTask extends WebInspector.Object +{ + constructor(delegate, items, options={}) + { + super(); + + let {workInterval, idleInterval} = options; + console.assert(!workInterval || workInterval > 0, workInterval); + console.assert(!idleInterval || idleInterval > 0, idleInterval); + + console.assert(delegate && typeof delegate.yieldableTaskWillProcessItem === "function", "Delegate provide an implementation of method 'yieldableTaskWillProcessItem'."); + + console.assert(items instanceof Object && Symbol.iterator in items, "Argument `items` must subclass Object and be iterable.", items); + + // Milliseconds to run before the task should yield. + this._workInterval = workInterval || 10; + // Milliseconds to idle before asynchronously resuming the task. + this._idleInterval = idleInterval || 0; + + this._delegate = delegate; + + this._items = items; + this._idleTimeoutIdentifier = undefined; + this._processing = false; + this._processing = false; + this._cancelled = false; + } + + // Public + + get processing() { return this._processing; } + get cancelled() { return this._cancelled; } + + get idleInterval() { return this._idleInterval; } + get workInterval() { return this._workInterval; } + + start() + { + console.assert(!this._processing); + if (this._processing) + return; + + console.assert(!this._cancelled); + if (this._cancelled) + return; + + function* createIteratorForProcessingItems() + { + let startTime = Date.now(); + let processedItems = []; + + for (let item of this._items) { + if (this._cancelled) + break; + + this._delegate.yieldableTaskWillProcessItem(this, item); + processedItems.push(item); + + // Calling out to the delegate may cause the task to be cancelled. + if (this._cancelled) + break; + + let elapsedTime = Date.now() - startTime; + if (elapsedTime > this._workInterval) { + let returnedItems = processedItems.slice(); + processedItems = []; + this._willYield(returnedItems, elapsedTime); + + yield; + + startTime = Date.now(); + } + } + + // The task sends a fake yield notification to the delegate so that + // the delegate receives notification of all processed items before finishing. + if (processedItems.length) + this._willYield(processedItems, Date.now() - startTime); + } + + this._processing = true; + this._pendingItemsIterator = createIteratorForProcessingItems.call(this); + this._processPendingItems(); + } + + cancel() + { + if (!this._processing) + return; + + this._cancelled = true; + } + + // Private + + _processPendingItems() + { + console.assert(this._processing); + + if (this._cancelled) + return; + + if (!this._pendingItemsIterator.next().done) { + this._idleTimeoutIdentifier = setTimeout(() => { this._processPendingItems(); }, this._idleInterval); + return; + } + + this._didFinish(); + } + + _willYield(processedItems, elapsedTime) + { + if (typeof this._delegate.yieldableTaskDidYield === "function") + this._delegate.yieldableTaskDidYield(this, processedItems, elapsedTime); + } + + _didFinish() + { + this._processing = false; + this._pendingItemsIterator = null; + + if (this._idleTimeoutIdentifier) { + clearTimeout(this._idleTimeoutIdentifier); + this._idleTimeoutIdentifier = undefined; + } + + if (typeof this._delegate.yieldableTaskDidFinish === "function") + this._delegate.yieldableTaskDidFinish(this); + } +}; + |