moved to root
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.BlobUtilities = class BlobUtilities {
|
||||
static blobForContent(content, base64Encoded, mimeType)
|
||||
{
|
||||
if (base64Encoded)
|
||||
return BlobUtilities.decodeBase64ToBlob(content, mimeType);
|
||||
return BlobUtilities.textToBlob(content, mimeType);
|
||||
}
|
||||
|
||||
static decodeBase64ToBlob(base64Data, mimeType)
|
||||
{
|
||||
mimeType = mimeType || "";
|
||||
|
||||
const sliceSize = 1024;
|
||||
let byteCharacters = atob(base64Data);
|
||||
let bytesLength = byteCharacters.length;
|
||||
let slicesCount = Math.ceil(bytesLength / sliceSize);
|
||||
let byteArrays = new Array(slicesCount);
|
||||
|
||||
for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
|
||||
let begin = sliceIndex * sliceSize;
|
||||
let end = Math.min(begin + sliceSize, bytesLength);
|
||||
|
||||
let bytes = new Array(end - begin);
|
||||
for (let 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});
|
||||
}
|
||||
|
||||
static textToBlob(text, mimeType)
|
||||
{
|
||||
return new Blob([text], {type: mimeType});
|
||||
}
|
||||
|
||||
static blobAsText(blob, callback)
|
||||
{
|
||||
console.assert(blob instanceof Blob);
|
||||
let fileReader = new FileReader;
|
||||
fileReader.addEventListener("loadend", () => { callback(fileReader.result); });
|
||||
fileReader.readAsText(blob);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Igalia S.L.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
if (!window.InspectorFrontendHost) {
|
||||
WI.BrowserInspectorFrontendHost = class BrowserInspectorFrontendHost {
|
||||
|
||||
constructor()
|
||||
{
|
||||
this._pendingMessages = null;
|
||||
this._socket = null;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get supportsShowCertificate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
get isRemote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
get inspectionLevel()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
get debuggableInfo()
|
||||
{
|
||||
return {
|
||||
debuggableType: "web-page",
|
||||
targetPlatformName: undefined,
|
||||
targetBuildVersion: undefined,
|
||||
targetProductVersion: undefined,
|
||||
targetIsSimulator: false,
|
||||
};
|
||||
}
|
||||
|
||||
get platform()
|
||||
{
|
||||
const match = navigator.platform.match(/mac|win|linux/i);
|
||||
if (match) {
|
||||
const platform = match[0].toLowerCase();
|
||||
if (platform == "win")
|
||||
return "windows";
|
||||
return platform;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
get platformVersionName()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
get supportsDiagnosticLogging()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsWebExtensions()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
connect()
|
||||
{
|
||||
const queryParams = parseQueryString(window.location.search.substring(1));
|
||||
let url = "ws" in queryParams ? "ws://" + queryParams.ws : null;
|
||||
if (!url)
|
||||
return;
|
||||
|
||||
const socket = new WebSocket(url);
|
||||
socket.addEventListener("message", message => InspectorBackend.dispatch(message.data));
|
||||
socket.addEventListener("error", console.error);
|
||||
socket.addEventListener("open", () => { this._socket = socket; });
|
||||
socket.addEventListener("close", () => {
|
||||
this._socket = null;
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
|
||||
loaded()
|
||||
{
|
||||
WI.updateVisibilityState(true);
|
||||
}
|
||||
|
||||
closeWindow()
|
||||
{
|
||||
this._windowVisible = false;
|
||||
}
|
||||
|
||||
reopen()
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this.reopen();
|
||||
}
|
||||
|
||||
bringToFront()
|
||||
{
|
||||
this._windowVisible = true;
|
||||
}
|
||||
|
||||
inspectedURLChanged(title)
|
||||
{
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
showCertificate(certificate)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
setZoomFactor(zoom)
|
||||
{
|
||||
}
|
||||
|
||||
zoomFactor()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
setForcedAppearance(appearance)
|
||||
{
|
||||
}
|
||||
|
||||
userInterfaceLayoutDirection()
|
||||
{
|
||||
return "ltr";
|
||||
}
|
||||
|
||||
supportsDockSide(side)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
requestDockSide(side)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
setAttachedWindowHeight(height)
|
||||
{
|
||||
}
|
||||
|
||||
setAttachedWindowWidth(width)
|
||||
{
|
||||
}
|
||||
|
||||
setSheetRect(x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
startWindowDrag()
|
||||
{
|
||||
}
|
||||
|
||||
moveWindowBy(x, y)
|
||||
{
|
||||
}
|
||||
|
||||
copyText(text)
|
||||
{
|
||||
this.killText(text, false, true);
|
||||
}
|
||||
|
||||
killText(text, shouldPrependToKillRing, shouldStartNewSequence)
|
||||
{
|
||||
// FIXME: restore focus to previously focused element.
|
||||
let textarea = document.createElement("textarea");
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
if (shouldStartNewSequence) {
|
||||
textarea.textContent = text;
|
||||
} else {
|
||||
textarea.select();
|
||||
if (!document.execCommand("paste"))
|
||||
console.error("BrowserInspectorFrontendHost.killText: could not paste from clipboard");
|
||||
|
||||
if (shouldPrependToKillRing)
|
||||
textarea.textContent = text + textarea.textContent;
|
||||
else
|
||||
textarea.textContent = textarea.textContent + text;
|
||||
}
|
||||
|
||||
textarea.select();
|
||||
|
||||
if (!document.execCommand("copy"))
|
||||
console.error("BrowserInspectorFrontendHost.copyText: could not copy to clipboard");
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
openURLExternally(url)
|
||||
{
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
|
||||
canSave(saveMode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
save(saveDatas, forceSaveAs)
|
||||
{
|
||||
// FIXME: Create a Blob from the content, get an object URL, open it to trigger a download.
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
canLoad()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
load(path)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
getPath(file)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
canPickColorFromScreen()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pickColorFromScreen()
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
revealFileExternally(path)
|
||||
{
|
||||
}
|
||||
|
||||
getCurrentX(context)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
getCurrentY(context)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
setPath(context, path2d)
|
||||
{
|
||||
console.log("setPath", context, path2d);
|
||||
}
|
||||
|
||||
showContextMenu(event, items)
|
||||
{
|
||||
this._contextMenu = WI.SoftContextMenu(items);
|
||||
this._contextMenu.show(event);
|
||||
}
|
||||
|
||||
dispatchEventAsContextMenuEvent(event)
|
||||
{
|
||||
if (this._contextMenu)
|
||||
this._contextMenu.show(event);
|
||||
}
|
||||
|
||||
sendMessageToBackend(message)
|
||||
{
|
||||
if (!this._socket) {
|
||||
if (!this._pendingMessages)
|
||||
this._pendingMessages = [];
|
||||
this._pendingMessages.push(message);
|
||||
} else {
|
||||
this._sendPendingMessagesToBackendIfNeeded();
|
||||
this._socket.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
unbufferedLog(message)
|
||||
{
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
isUnderTest()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
beep()
|
||||
{
|
||||
// FIXME: Implement using Audio/AudioContext.
|
||||
}
|
||||
|
||||
inspectInspector()
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
isBeingInspected()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setAllowsInspectingInspector(allow)
|
||||
{
|
||||
}
|
||||
|
||||
logDiagnosticEvent(eventName, content)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
didShowExtensionTab(extensionID, extensionTabID, extensionFrame)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
didHideExtensionTab(extensionID, extensionTabID)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
didNavigateExtensionTab(extensionID, extensionTabID, newURL)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
inspectedPageDidNavigate(newURL)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
evaluateScriptInExtensionTab(extensionFrame, scriptSource)
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_sendPendingMessagesToBackendIfNeeded()
|
||||
{
|
||||
if (this._pendingMessages) {
|
||||
this._pendingMessages.forEach(message => this._socket.send(message));
|
||||
this._pendingMessages = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
InspectorFrontendHost = new WI.BrowserInspectorFrontendHost();
|
||||
|
||||
WI.dontLocalizeUserInterface = true;
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.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;
|
||||
};
|
||||
|
||||
WI.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 = WI.linkifyNodeReference(node);
|
||||
var tagIdSelector = link.title;
|
||||
var classSelectorIndex = tagIdSelector.indexOf(".");
|
||||
if (classSelectorIndex > -1)
|
||||
tagIdSelector = tagIdSelector.substring(0, classSelectorIndex);
|
||||
var roleSelector = WI.roleSelectorForNode(node);
|
||||
link.textContent = tagIdSelector + roleSelector;
|
||||
link.title += roleSelector;
|
||||
return link;
|
||||
};
|
||||
|
||||
WI.linkifyStyleable = function(styleable)
|
||||
{
|
||||
console.assert(styleable instanceof WI.DOMStyleable, styleable);
|
||||
let displayName = styleable.displayName;
|
||||
let link = document.createElement("span");
|
||||
link.append(displayName);
|
||||
return WI.linkifyNodeReferenceElement(styleable.node, link, {displayName});
|
||||
};
|
||||
|
||||
WI.linkifyNodeReference = function(node, options = {})
|
||||
{
|
||||
let displayName = node.displayName;
|
||||
if (!isNaN(options.maxLength))
|
||||
displayName = displayName.truncate(options.maxLength);
|
||||
|
||||
let link = document.createElement("span");
|
||||
link.append(displayName);
|
||||
return WI.linkifyNodeReferenceElement(node, link, {...options, displayName});
|
||||
};
|
||||
|
||||
WI.linkifyNodeReferenceElement = function(node, element, options = {})
|
||||
{
|
||||
element.setAttribute("role", "link");
|
||||
element.title = options.displayName || node.displayName;
|
||||
|
||||
let nodeType = node.nodeType();
|
||||
if (!options.ignoreClick && (nodeType !== Node.DOCUMENT_NODE || node.parentNode) && nodeType !== Node.TEXT_NODE)
|
||||
element.classList.add("node-link");
|
||||
|
||||
WI.bindInteractionsForNodeToElement(node, element, options);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
WI.bindInteractionsForNodeToElement = function(node, element, options = {}) {
|
||||
if (!options.ignoreClick) {
|
||||
element.addEventListener("click", (event) => {
|
||||
WI.domManager.inspectElement(node.id, {
|
||||
initiatorHint: WI.TabBrowser.TabNavigationInitiator.LinkClick,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
element.addEventListener("mouseover", (event) => {
|
||||
node.highlight();
|
||||
});
|
||||
|
||||
element.addEventListener("mouseout", (event) => {
|
||||
WI.domManager.hideDOMNodeHighlight();
|
||||
});
|
||||
|
||||
element.addEventListener("contextmenu", (event) => {
|
||||
let contextMenu = WI.ContextMenu.createFromEvent(event);
|
||||
WI.appendContextMenuItemsForDOMNode(contextMenu, node, options);
|
||||
});
|
||||
};
|
||||
|
||||
function createSVGElement(tagName)
|
||||
{
|
||||
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
|
||||
}
|
||||
|
||||
WI.cssPath = function(node, options = {})
|
||||
{
|
||||
console.assert(node instanceof WI.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 = WI.cssPathComponent(node, options);
|
||||
if (!component)
|
||||
break;
|
||||
components.push(component);
|
||||
if (component.done)
|
||||
break;
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
components.reverse();
|
||||
return components.map((x) => x.value).join(" > ") + suffix;
|
||||
};
|
||||
|
||||
WI.cssPathComponent = function(node, options = {})
|
||||
{
|
||||
console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
|
||||
console.assert(!node.isPseudoElement());
|
||||
if (node.nodeType() !== Node.ELEMENT_NODE)
|
||||
return null;
|
||||
|
||||
let nodeName = node.nodeNameInCorrectCase();
|
||||
|
||||
// Root node does not have siblings.
|
||||
if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE)
|
||||
return {value: nodeName, done: true};
|
||||
|
||||
if (options.full) {
|
||||
function getUniqueAttributes(domNode) {
|
||||
let uniqueAttributes = new Map;
|
||||
for (let attribute of domNode.attributes()) {
|
||||
let values = [attribute.value];
|
||||
if (attribute.name === "id" || attribute.name === "class")
|
||||
values = attribute.value.split(/\s+/);
|
||||
uniqueAttributes.set(attribute.name, new Set(values));
|
||||
}
|
||||
return uniqueAttributes;
|
||||
}
|
||||
|
||||
let nodeIndex = 0;
|
||||
let needsNthChild = false;
|
||||
let uniqueAttributes = getUniqueAttributes(node);
|
||||
node.parentNode.children.forEach((child, i) => {
|
||||
if (child.nodeType() !== Node.ELEMENT_NODE)
|
||||
return;
|
||||
|
||||
if (child === node) {
|
||||
nodeIndex = i;
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsNthChild || child.nodeNameInCorrectCase() !== nodeName)
|
||||
return;
|
||||
|
||||
let childUniqueAttributes = getUniqueAttributes(child);
|
||||
let subsetCount = 0;
|
||||
for (let [name, values] of uniqueAttributes) {
|
||||
let childValues = childUniqueAttributes.get(name);
|
||||
if (childValues && values.size <= childValues.size && values.isSubsetOf(childValues))
|
||||
++subsetCount;
|
||||
}
|
||||
|
||||
if (subsetCount === uniqueAttributes.size)
|
||||
needsNthChild = true;
|
||||
});
|
||||
|
||||
function selectorForAttribute(values, prefix = "", shouldCSSEscape = false) {
|
||||
if (!values || !values.size)
|
||||
return "";
|
||||
values = Array.from(values);
|
||||
values = values.filter((value) => value && value.length);
|
||||
if (!values.length)
|
||||
return "";
|
||||
values = values.map((value) => shouldCSSEscape ? CSS.escape(value) : value.escapeCharacters("\""));
|
||||
return prefix + values.join(prefix);
|
||||
}
|
||||
|
||||
let selector = nodeName;
|
||||
selector += selectorForAttribute(uniqueAttributes.get("id"), "#", true);
|
||||
selector += selectorForAttribute(uniqueAttributes.get("class"), ".", true);
|
||||
for (let [attribute, values] of uniqueAttributes) {
|
||||
if (attribute !== "id" && attribute !== "class")
|
||||
selector += `[${attribute}="${selectorForAttribute(values)}"]`;
|
||||
}
|
||||
|
||||
if (needsNthChild)
|
||||
selector += `:nth-child(${nodeIndex + 1})`;
|
||||
|
||||
return {value: selector, done: false};
|
||||
}
|
||||
|
||||
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};
|
||||
|
||||
// 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};
|
||||
};
|
||||
|
||||
WI.xpath = function(node)
|
||||
{
|
||||
console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
|
||||
|
||||
if (node.nodeType() === Node.DOCUMENT_NODE)
|
||||
return "/";
|
||||
|
||||
let components = [];
|
||||
while (node) {
|
||||
let component = WI.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("/");
|
||||
};
|
||||
|
||||
WI.xpathComponent = function(node)
|
||||
{
|
||||
console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
|
||||
|
||||
let index = WI.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};
|
||||
};
|
||||
|
||||
WI.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;
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
// Debouncer wraps a function and continues to delay its invocation as long as
|
||||
// clients continue to delay its firing. The most recent delay call overrides
|
||||
// previous calls. Delays may be timeouts, animation frames, or microtasks.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// let debouncer = new Debouncer(() => { this.refresh() });
|
||||
// element.addEventListener("keydown", (event) => { debouncer.delayForTime(100); });
|
||||
//
|
||||
// Will ensure `refresh` will not happen until no keyevent has happened in 100ms:
|
||||
//
|
||||
// 0ms 100ms 200ms 300ms 400ms
|
||||
// time: |-------------|-------------|-------------|-------------|
|
||||
// delay: ^ ^ ^ ^ ^ ^ ^ ^
|
||||
// refreshes: * (1)
|
||||
//
|
||||
// When the wrapped function is actually called, it will be given the most recent set of arguments.
|
||||
|
||||
class Debouncer
|
||||
{
|
||||
constructor(callback)
|
||||
{
|
||||
console.assert(typeof callback === "function");
|
||||
|
||||
this._callback = callback;
|
||||
this._lastArguments = [];
|
||||
|
||||
this._timeoutIdentifier = undefined;
|
||||
this._animationFrameIdentifier = undefined;
|
||||
this._promiseIdentifier = undefined;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
force()
|
||||
{
|
||||
this._lastArguments = arguments;
|
||||
this._execute();
|
||||
}
|
||||
|
||||
delayForTime(time, ...args)
|
||||
{
|
||||
console.assert(time >= 0);
|
||||
|
||||
this.cancel();
|
||||
|
||||
this._lastArguments = args;
|
||||
|
||||
this._timeoutIdentifier = setTimeout(() => {
|
||||
this._execute();
|
||||
}, time);
|
||||
}
|
||||
|
||||
delayForFrame()
|
||||
{
|
||||
this.cancel();
|
||||
|
||||
this._lastArguments = arguments;
|
||||
|
||||
this._animationFrameIdentifier = requestAnimationFrame(() => {
|
||||
this._execute();
|
||||
});
|
||||
}
|
||||
|
||||
delayForMicrotask()
|
||||
{
|
||||
this.cancel();
|
||||
|
||||
this._lastArguments = arguments;
|
||||
|
||||
let promiseIdentifier = Symbol("next-microtask");
|
||||
|
||||
this._promiseIdentifier = promiseIdentifier;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this._promiseIdentifier === promiseIdentifier)
|
||||
this._execute();
|
||||
});
|
||||
}
|
||||
|
||||
cancel()
|
||||
{
|
||||
this._lastArguments = [];
|
||||
|
||||
if (this._timeoutIdentifier) {
|
||||
clearTimeout(this._timeoutIdentifier);
|
||||
this._timeoutIdentifier = undefined;
|
||||
}
|
||||
|
||||
if (this._animationFrameIdentifier) {
|
||||
cancelAnimationFrame(this._animationFrameIdentifier);
|
||||
this._animationFrameIdentifier = undefined;
|
||||
}
|
||||
|
||||
if (this._promiseIdentifier)
|
||||
this._promiseIdentifier = undefined;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_execute()
|
||||
{
|
||||
let args = this._lastArguments;
|
||||
|
||||
this.cancel();
|
||||
|
||||
this._callback.apply(undefined, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.DebuggableType = {
|
||||
ITML: "itml",
|
||||
JavaScript: "javascript",
|
||||
Page: "page",
|
||||
ServiceWorker: "service-worker",
|
||||
WebPage: "web-page",
|
||||
};
|
||||
|
||||
WI.DebuggableType.fromString = function(type) {
|
||||
switch (type) {
|
||||
case "itml":
|
||||
return WI.DebuggableType.ITML;
|
||||
case "javascript":
|
||||
return WI.DebuggableType.JavaScript;
|
||||
case "page":
|
||||
return WI.DebuggableType.Page;
|
||||
case "service-worker":
|
||||
return WI.DebuggableType.ServiceWorker;
|
||||
case "web-page":
|
||||
return WI.DebuggableType.WebPage;
|
||||
}
|
||||
|
||||
console.assert(false, "Unknown debuggable type", type);
|
||||
return null;
|
||||
};
|
||||
|
||||
WI.DebuggableType.supportedTargetTypes = function(debuggableType) {
|
||||
let targetTypes = new Set;
|
||||
|
||||
switch (debuggableType) {
|
||||
case WI.DebuggableType.ITML:
|
||||
targetTypes.add(WI.TargetType.ITML);
|
||||
break;
|
||||
|
||||
case WI.DebuggableType.JavaScript:
|
||||
targetTypes.add(WI.TargetType.JavaScript);
|
||||
break;
|
||||
|
||||
case WI.DebuggableType.Page:
|
||||
targetTypes.add(WI.TargetType.Page);
|
||||
targetTypes.add(WI.TargetType.Worker);
|
||||
break;
|
||||
|
||||
case WI.DebuggableType.ServiceWorker:
|
||||
targetTypes.add(WI.TargetType.ServiceWorker);
|
||||
break;
|
||||
|
||||
case WI.DebuggableType.WebPage:
|
||||
targetTypes.add(WI.TargetType.Page);
|
||||
targetTypes.add(WI.TargetType.WebPage);
|
||||
targetTypes.add(WI.TargetType.Worker);
|
||||
break;
|
||||
}
|
||||
|
||||
console.assert(targetTypes.size, "Unknown debuggable type", debuggableType);
|
||||
return targetTypes;
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
WI.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(emitter, `Missing event emitter for event: ${type}.`);
|
||||
console.assert(type, "Missing event type.");
|
||||
console.assert(callback, `Missing callback for event: ${type}.`);
|
||||
var emitterIsValid = emitter && (emitter instanceof WI.Object || emitter instanceof Node || (typeof emitter.addEventListener === "function"));
|
||||
console.assert(emitterIsValid, "Event emitter ", emitter, ` (type: ${type}) is null or does not implement Node or WI.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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
|
||||
WI.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(emitter, `Missing event emitter for event: ${type}.`);
|
||||
console.assert(type, "Missing event type.");
|
||||
console.assert(callback, `Missing callback for event: ${type}.`);
|
||||
var emitterIsValid = emitter && (emitter instanceof WI.Object || emitter instanceof Node || (typeof emitter.addEventListener === "function"));
|
||||
console.assert(emitterIsValid, "Event emitter ", emitter, ` (type: ${type}) is null or does not implement Node or WI.Object.`);
|
||||
|
||||
if (!emitterIsValid || !type || !callback)
|
||||
return;
|
||||
|
||||
this._listeners.push({listener: new WI.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 = [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.FileUtilities = class FileUtilities {
|
||||
static screenshotString()
|
||||
{
|
||||
let date = new Date;
|
||||
let values = [
|
||||
date.getFullYear(),
|
||||
Number.zeroPad(date.getMonth() + 1, 2),
|
||||
Number.zeroPad(date.getDate(), 2),
|
||||
Number.zeroPad(date.getHours(), 2),
|
||||
Number.zeroPad(date.getMinutes(), 2),
|
||||
Number.zeroPad(date.getSeconds(), 2),
|
||||
];
|
||||
return WI.UIString("Screen Shot %s-%s-%s at %s.%s.%s").format(...values);
|
||||
}
|
||||
|
||||
static sanitizeFilename(filename)
|
||||
{
|
||||
return filename.replace(/:+/g, "-");
|
||||
}
|
||||
|
||||
static inspectorURLForFilename(filename)
|
||||
{
|
||||
return "web-inspector:///" + encodeURIComponent(FileUtilities.sanitizeFilename(filename));
|
||||
}
|
||||
|
||||
static canSave(saveMode)
|
||||
{
|
||||
console.assert(Object.values(WI.FileUtilities.SaveMode).includes(saveMode), saveMode);
|
||||
return InspectorFrontendHost.canSave(saveMode);
|
||||
}
|
||||
|
||||
static async save(saveMode, fileVariants, forceSaveAs)
|
||||
{
|
||||
console.assert(WI.FileUtilities.canSave(saveMode), saveMode);
|
||||
|
||||
console.assert(fileVariants);
|
||||
if (!fileVariants) {
|
||||
InspectorFrontendHost.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
let isFileVariantsMode = saveMode === WI.FileUtilities.SaveMode.FileVariants;
|
||||
if (isFileVariantsMode)
|
||||
forceSaveAs = true;
|
||||
|
||||
if (typeof fileVariants.customSaveHandler === "function") {
|
||||
fileVariants.customSaveHandler(forceSaveAs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFileVariantsMode && !Array.isArray(fileVariants))
|
||||
fileVariants = [fileVariants];
|
||||
|
||||
console.assert(Array.isArray(fileVariants), fileVariants);
|
||||
if (!Array.isArray(fileVariants)) {
|
||||
InspectorFrontendHost.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = fileVariants.map((fileVariant) => {
|
||||
let content = fileVariant.content;
|
||||
console.assert(content, fileVariant);
|
||||
if (!content)
|
||||
return null;
|
||||
|
||||
let displayType = fileVariant.displayType || "";
|
||||
console.assert(!isFileVariantsMode || fileVariant.displayType, fileVariant);
|
||||
if (!fileVariant.displayType && isFileVariantsMode)
|
||||
return null;
|
||||
|
||||
let suggestedName = fileVariant.suggestedName;
|
||||
if (!suggestedName) {
|
||||
let url = fileVariant.url || "";
|
||||
suggestedName = parseURL(url).lastPathComponent;
|
||||
if (!suggestedName) {
|
||||
suggestedName = WI.UIString("Untitled");
|
||||
let dataURLTypeMatch = /^data:([^;]+)/.exec(url);
|
||||
if (dataURLTypeMatch) {
|
||||
let fileExtension = WI.fileExtensionForMIMEType(dataURLTypeMatch[1]);
|
||||
if (fileExtension)
|
||||
suggestedName += "." + fileExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
let url = WI.FileUtilities.inspectorURLForFilename(suggestedName);
|
||||
|
||||
if (typeof content === "string") {
|
||||
return Promise.resolve({
|
||||
displayType,
|
||||
url,
|
||||
content,
|
||||
base64Encoded: !!fileVariant.base64Encoded,
|
||||
});
|
||||
}
|
||||
|
||||
let wrappedPromise = new WI.WrappedPromise;
|
||||
let fileReader = new FileReader;
|
||||
fileReader.addEventListener("loadend", () => {
|
||||
wrappedPromise.resolve({
|
||||
displayType,
|
||||
url,
|
||||
content: parseDataURL(fileReader.result).data,
|
||||
base64Encoded: true,
|
||||
});
|
||||
});
|
||||
fileReader.readAsDataURL(content);
|
||||
return wrappedPromise.promise;
|
||||
});
|
||||
if (promises.includes(null)) {
|
||||
InspectorFrontendHost.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
let saveDatas = await Promise.all(promises);
|
||||
|
||||
console.assert(isFileVariantsMode || saveDatas.length === 1, saveDatas);
|
||||
console.assert(!isFileVariantsMode || new Set(saveDatas.map((saveData) => saveData.displayType)).size === saveDatas.length, saveDatas);
|
||||
console.assert(!isFileVariantsMode || new Set(saveDatas.map((saveData) => WI.urlWithoutExtension(saveData.url))).size === 1, saveDatas);
|
||||
|
||||
InspectorFrontendHost.save(saveDatas, !!forceSaveAs);
|
||||
}
|
||||
|
||||
static import(callback, {multiple} = {})
|
||||
{
|
||||
let inputElement = document.createElement("input");
|
||||
inputElement.type = "file";
|
||||
inputElement.value = null;
|
||||
inputElement.multiple = !!multiple;
|
||||
inputElement.addEventListener("change", (event) => {
|
||||
callback(inputElement.files);
|
||||
});
|
||||
|
||||
inputElement.click();
|
||||
|
||||
// Cache the last used import element so that it doesn't get GCd while the native file
|
||||
// picker is shown, which would prevent the "change" event listener from firing.
|
||||
FileUtilities.importInputElement = inputElement;
|
||||
}
|
||||
|
||||
static importText(callback, options = {})
|
||||
{
|
||||
FileUtilities.import((files) => {
|
||||
FileUtilities.readText(files, callback);
|
||||
}, options);
|
||||
}
|
||||
|
||||
static importJSON(callback, options = {})
|
||||
{
|
||||
FileUtilities.import((files) => {
|
||||
FileUtilities.readJSON(files, callback);
|
||||
}, options);
|
||||
}
|
||||
|
||||
static importData(callback, options = {})
|
||||
{
|
||||
FileUtilities.import((files) => {
|
||||
FileUtilities.readData(files, callback);
|
||||
}, options);
|
||||
}
|
||||
|
||||
static async readText(fileOrList, callback)
|
||||
{
|
||||
await FileUtilities._read(fileOrList, async (file, result) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
let reader = new FileReader;
|
||||
reader.addEventListener("loadend", (event) => {
|
||||
result.text = reader.result;
|
||||
resolve(event);
|
||||
});
|
||||
reader.addEventListener("error", reject);
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
|
||||
static async readJSON(fileOrList, callback)
|
||||
{
|
||||
await WI.FileUtilities.readText(fileOrList, async (result) => {
|
||||
if (result.text && !result.error) {
|
||||
try {
|
||||
result.json = JSON.parse(result.text);
|
||||
} catch (e) {
|
||||
result.error = e;
|
||||
}
|
||||
}
|
||||
|
||||
await callback(result);
|
||||
});
|
||||
}
|
||||
|
||||
static async readData(fileOrList, callback)
|
||||
{
|
||||
await FileUtilities._read(fileOrList, async (file, result) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
let reader = new FileReader;
|
||||
reader.addEventListener("loadend", (event) => {
|
||||
let {mimeType, base64, data} = parseDataURL(reader.result);
|
||||
|
||||
// In case no mime type was determined, try to derive one from the file extension.
|
||||
if (!mimeType || mimeType === "text/plain") {
|
||||
let extension = WI.fileExtensionForFilename(result.filename);
|
||||
if (extension)
|
||||
mimeType = WI.mimeTypeForFileExtension(extension);
|
||||
}
|
||||
|
||||
result.mimeType = mimeType;
|
||||
result.base64Encoded = base64;
|
||||
result.content = data;
|
||||
|
||||
resolve(event);
|
||||
});
|
||||
reader.addEventListener("error", reject);
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
static async _read(fileOrList, operation, callback)
|
||||
{
|
||||
console.assert(fileOrList instanceof File || fileOrList instanceof FileList);
|
||||
|
||||
let files = [];
|
||||
if (fileOrList instanceof File)
|
||||
files.push(fileOrList);
|
||||
else if (fileOrList instanceof FileList)
|
||||
files = Array.from(fileOrList);
|
||||
|
||||
for (let file of files) {
|
||||
let result = {
|
||||
filename: file.name,
|
||||
};
|
||||
|
||||
try {
|
||||
await operation(file, result);
|
||||
} catch (e) {
|
||||
result.error = e;
|
||||
}
|
||||
|
||||
await callback(result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Keep in sync with `InspectorFrontendClient::SaveMode` and `InspectorFrontendHost::SaveMode`.
|
||||
WI.FileUtilities.SaveMode = {
|
||||
SingleFile: "single-file",
|
||||
FileVariants: "file-variants",
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.HTTPUtilities = class HTTPUtilities {
|
||||
static statusTextForStatusCode(code)
|
||||
{
|
||||
console.assert(typeof code === "number");
|
||||
|
||||
switch (code) {
|
||||
case 0: return "OK";
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 308: return "Permanent Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Time-out";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-URI Too Large";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Requested range not satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Time-out";
|
||||
case 505: return "HTTP Version not supported";
|
||||
}
|
||||
|
||||
if (code < 200)
|
||||
return "Continue";
|
||||
if (code < 300)
|
||||
return "OK";
|
||||
if (code < 400)
|
||||
return "Multiple Choices";
|
||||
if (code < 500)
|
||||
return "Bad Request";
|
||||
|
||||
return "Internal Server Error";
|
||||
}
|
||||
};
|
||||
|
||||
WI.HTTPUtilities.RequestMethod = {
|
||||
CONNECT: "CONNECT",
|
||||
DELETE: "DELETE",
|
||||
GET: "GET",
|
||||
HEAD: "HEAD",
|
||||
OPTIONS: "OPTIONS",
|
||||
PATCH: "PATCH",
|
||||
POST: "POST",
|
||||
PUT: "PUT",
|
||||
TRACE: "TRACE",
|
||||
};
|
||||
|
||||
WI.HTTPUtilities.RequestMethodsWithBody = new Set([
|
||||
WI.HTTPUtilities.RequestMethod.DELETE,
|
||||
WI.HTTPUtilities.RequestMethod.PATCH,
|
||||
WI.HTTPUtilities.RequestMethod.POST,
|
||||
WI.HTTPUtilities.RequestMethod.PUT,
|
||||
]);
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
// The following should only be used for response local override file mapping.
|
||||
|
||||
Object.defineProperty(File.prototype, "getPath", {
|
||||
value() {
|
||||
return InspectorFrontendHost.getPath(this);
|
||||
},
|
||||
});
|
||||
|
||||
// The following should only be used for rendering (and interacting with) canvas 2D recordings.
|
||||
|
||||
Object.defineProperty(CanvasRenderingContext2D.prototype, "currentX", {
|
||||
get() {
|
||||
return InspectorFrontendHost.getCurrentX(this);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(CanvasRenderingContext2D.prototype, "currentY", {
|
||||
get() {
|
||||
return InspectorFrontendHost.getCurrentY(this);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(CanvasRenderingContext2D.prototype, "getPath", {
|
||||
value() {
|
||||
return InspectorFrontendHost.getPath(this);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(CanvasRenderingContext2D.prototype, "setPath", {
|
||||
value(path) {
|
||||
return InspectorFrontendHost.setPath(this, path);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2017 Devin Rousso <webkit@devinrousso.com>. 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.
|
||||
*/
|
||||
|
||||
WI.ImageUtilities = class ImageUtilities {
|
||||
static 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;
|
||||
}
|
||||
|
||||
static promisifyLoad(src)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image;
|
||||
let resolveWithImage = () => { resolve(image); };
|
||||
image.addEventListener("load", resolveWithImage);
|
||||
image.addEventListener("error", resolveWithImage);
|
||||
image.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
static scratchCanvasContext2D(callback)
|
||||
{
|
||||
if (!WI.ImageUtilities._scratchContext2D)
|
||||
WI.ImageUtilities._scratchContext2D = document.createElement("canvas").getContext("2d");
|
||||
|
||||
let context = WI.ImageUtilities._scratchContext2D;
|
||||
|
||||
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
|
||||
context.save();
|
||||
callback(context);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
static imageFromImageBitmap(data)
|
||||
{
|
||||
console.assert(data instanceof ImageBitmap);
|
||||
|
||||
let image = null;
|
||||
WI.ImageUtilities.scratchCanvasContext2D((context) => {
|
||||
context.canvas.width = data.width;
|
||||
context.canvas.height = data.height;
|
||||
context.drawImage(data, 0, 0);
|
||||
|
||||
image = new Image;
|
||||
image.src = context.canvas.toDataURL();
|
||||
});
|
||||
return image;
|
||||
}
|
||||
|
||||
static imageFromImageData(data)
|
||||
{
|
||||
console.assert(data instanceof ImageData);
|
||||
|
||||
let image = null;
|
||||
WI.ImageUtilities.scratchCanvasContext2D((context) => {
|
||||
context.canvas.width = data.width;
|
||||
context.canvas.height = data.height;
|
||||
context.putImageData(data, 0, 0);
|
||||
|
||||
image = new Image;
|
||||
image.src = context.canvas.toDataURL();
|
||||
});
|
||||
return image;
|
||||
}
|
||||
|
||||
static imageFromCanvasGradient(gradient, width, height)
|
||||
{
|
||||
console.assert(gradient instanceof CanvasGradient);
|
||||
|
||||
let image = null;
|
||||
WI.ImageUtilities.scratchCanvasContext2D((context) => {
|
||||
context.canvas.width = width;
|
||||
context.canvas.height = height;
|
||||
context.fillStyle = gradient;
|
||||
context.fillRect(0, 0, width, height);
|
||||
|
||||
image = new Image;
|
||||
image.src = context.canvas.toDataURL();
|
||||
});
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
WI.ImageUtilities._scratchContext2D = null;
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 IterableWeakSet
|
||||
{
|
||||
constructor(items = [])
|
||||
{
|
||||
this._wrappers = new Set;
|
||||
this._wrapperForItem = new WeakMap;
|
||||
|
||||
for (let item of items)
|
||||
this.add(item);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get size()
|
||||
{
|
||||
let size = 0;
|
||||
for (let wrapper of this._wrappers) {
|
||||
if (wrapper.deref())
|
||||
++size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
has(item)
|
||||
{
|
||||
let result = this._wrapperForItem.has(item);
|
||||
console.assert(Array.from(this._wrappers).some((wrapper) => wrapper.deref() === item) === result, this, item);
|
||||
return result;
|
||||
}
|
||||
|
||||
add(item)
|
||||
{
|
||||
console.assert(typeof item === "object", item);
|
||||
console.assert(item !== null, item);
|
||||
|
||||
if (this.has(item))
|
||||
return;
|
||||
|
||||
let wrapper = new WeakRef(item);
|
||||
this._wrappers.add(wrapper);
|
||||
this._wrapperForItem.set(item, wrapper);
|
||||
this._finalizationRegistry.register(item, {weakThis: new WeakRef(this), wrapper}, wrapper);
|
||||
}
|
||||
|
||||
delete(item)
|
||||
{
|
||||
return !!this.take(item);
|
||||
}
|
||||
|
||||
take(item)
|
||||
{
|
||||
let wrapper = this._wrapperForItem.get(item);
|
||||
if (!wrapper)
|
||||
return undefined;
|
||||
|
||||
let itemDeleted = this._wrapperForItem.delete(item);
|
||||
console.assert(itemDeleted, this, item);
|
||||
|
||||
let wrapperDeleted = this._wrappers.delete(wrapper);
|
||||
console.assert(wrapperDeleted, this, item);
|
||||
|
||||
this._finalizationRegistry.unregister(wrapper);
|
||||
|
||||
console.assert(wrapper.deref() === item, this, item);
|
||||
return item;
|
||||
}
|
||||
|
||||
clear()
|
||||
{
|
||||
for (let wrapper of this._wrappers) {
|
||||
this._wrapperForItem.delete(wrapper);
|
||||
this._finalizationRegistry.unregister(wrapper);
|
||||
}
|
||||
|
||||
this._wrappers.clear();
|
||||
}
|
||||
|
||||
keys()
|
||||
{
|
||||
return this.values();
|
||||
}
|
||||
|
||||
*values()
|
||||
{
|
||||
for (let wrapper of this._wrappers) {
|
||||
let item = wrapper.deref();
|
||||
console.assert(!item === !this._wrapperForItem.has(item), this, item);
|
||||
if (item)
|
||||
yield item;
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.iterator]()
|
||||
{
|
||||
return this.values();
|
||||
}
|
||||
|
||||
copy()
|
||||
{
|
||||
return new IterableWeakSet(this.toJSON());
|
||||
}
|
||||
|
||||
toJSON()
|
||||
{
|
||||
return Array.from(this);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
get _finalizationRegistry()
|
||||
{
|
||||
return IterableWeakSet._finalizationRegistry ??= new FinalizationRegistry(function(heldValue) {
|
||||
heldValue.weakThis.deref()?._wrappers.delete(heldValue.wrapper);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 (WI.dontLocalizeUserInterface)
|
||||
return;
|
||||
|
||||
let localizedStringsURL = InspectorFrontendHost.localizedStringsURL;
|
||||
console.assert(localizedStringsURL);
|
||||
if (localizedStringsURL)
|
||||
document.write("<script src=\"" + localizedStringsURL + "\"></script>");
|
||||
})();
|
||||
|
||||
WI.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;
|
||||
};
|
||||
|
||||
WI.UIString = function(string, key, comment)
|
||||
{
|
||||
"use strict";
|
||||
|
||||
if (WI.dontLocalizeUserInterface)
|
||||
return string;
|
||||
|
||||
// UIString(string, comment)
|
||||
if (arguments.length === 2) {
|
||||
comment = key;
|
||||
key = undefined;
|
||||
}
|
||||
|
||||
key = key || string;
|
||||
|
||||
if (window.localizedStrings && key in window.localizedStrings)
|
||||
return window.localizedStrings[key];
|
||||
|
||||
if (!window.localizedStrings)
|
||||
console.error(`Attempted to load localized string "${key}" before localizedStrings was initialized.`, comment);
|
||||
|
||||
if (!this._missingLocalizedStrings)
|
||||
this._missingLocalizedStrings = {};
|
||||
|
||||
if (!(key in this._missingLocalizedStrings)) {
|
||||
console.error(`Localized string "${key}" was not found.`, comment);
|
||||
this._missingLocalizedStrings[key] = true;
|
||||
}
|
||||
|
||||
return "LOCALIZED STRING NOT FOUND";
|
||||
};
|
||||
|
||||
WI.repeatedUIString = {};
|
||||
|
||||
WI.repeatedUIString.timelineRecordLayout = function() {
|
||||
return WI.UIString("Layout", "Layout @ Timeline record", "Layout phase timeline records");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.timelineRecordPaint = function() {
|
||||
return WI.UIString("Paint", "Paint @ Timeline record", "Paint (render) phase timeline records");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.timelineRecordComposite = function() {
|
||||
return WI.UIString("Composite", "Composite @ Timeline record", "Composite phase timeline records, where graphic layers are combined");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.debuggerStatements = function() {
|
||||
return WI.UIString("Debugger Statements", "Debugger Statements @ JavaScript Breakpoint", "Break (pause) on debugger statements");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allExceptions = function() {
|
||||
return WI.UIString("All Exceptions", "All Exceptions @ JavaScript Breakpoint", "Break (pause) on all exceptions");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.uncaughtExceptions = function() {
|
||||
return WI.UIString("Uncaught Exceptions", "Uncaught Exceptions @ JavaScript Breakpoint", "Break (pause) on uncaught (unhandled) exceptions");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.assertionFailures = function() {
|
||||
return WI.UIString("Assertion Failures", "Assertion Failures @ JavaScript Breakpoint", "Break (pause) when console.assert() fails");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allMicrotasks = function() {
|
||||
return WI.UIString("All Microtasks", "All Microtasks @ JavaScript Breakpoint", "Break (pause) on all microtasks");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allAnimationFrames = function() {
|
||||
return WI.UIString("All Animation Frames", "All Animation Frames @ Event Breakpoint", "Break (pause) on All animation frames");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allIntervals = function() {
|
||||
return WI.UIString("All Intervals", "All Intervals @ Event Breakpoint", "Break (pause) on all intervals");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allEvents = function() {
|
||||
return WI.UIString("All Events", "All Events @ Event Breakpoint", "Break (pause) on all events");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allTimeouts = function() {
|
||||
return WI.UIString("All Timeouts", "All Timeouts @ Event Breakpoint", "Break (pause) on all timeouts");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.allRequests = function() {
|
||||
return WI.UIString("All Requests", "A submenu item of 'Break on' that breaks (pauses) before all network requests");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.fetch = function() {
|
||||
return WI.UIString("Fetch", "Resource loaded via 'fetch' method");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.revealInDOMTree = function() {
|
||||
return WI.UIString("Reveal in DOM Tree", "Open Elements tab and select this node in DOM tree");
|
||||
};
|
||||
|
||||
WI.repeatedUIString.showTransparencyGridTooltip = function() {
|
||||
return WI.UIString("Show transparency grid", "Show transparency grid (tooltip)", "Tooltip for showing the checkered transparency grid under images and canvases")
|
||||
};
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.fileExtensionForFilename = function(filename)
|
||||
{
|
||||
if (!filename)
|
||||
return null;
|
||||
|
||||
let index = filename.lastIndexOf(".");
|
||||
if (index === -1)
|
||||
return null;
|
||||
|
||||
if (index === filename.length - 1)
|
||||
return null;
|
||||
|
||||
return filename.substr(index + 1);
|
||||
};
|
||||
|
||||
WI.fileExtensionForURL = function(url)
|
||||
{
|
||||
let lastPathComponent = parseURL(url).lastPathComponent;
|
||||
return WI.fileExtensionForFilename(lastPathComponent);
|
||||
};
|
||||
|
||||
WI.mimeTypeForFileExtension = function(extension)
|
||||
{
|
||||
const extensionToMIMEType = {
|
||||
// Document types.
|
||||
"html": "text/html",
|
||||
"xhtml": "application/xhtml+xml",
|
||||
"xml": "text/xml",
|
||||
|
||||
// Script types.
|
||||
"js": "text/javascript",
|
||||
"mjs": "text/javascript",
|
||||
"json": "application/json",
|
||||
"clj": "text/x-clojure",
|
||||
"coffee": "text/x-coffeescript",
|
||||
"ls": "text/x-livescript",
|
||||
"ts": "text/typescript",
|
||||
"ps": "application/postscript",
|
||||
"jsx": "text/jsx",
|
||||
|
||||
// Stylesheet types.
|
||||
"css": "text/css",
|
||||
"less": "text/x-less",
|
||||
"sass": "text/x-sass",
|
||||
"scss": "text/x-scss",
|
||||
|
||||
// Image types.
|
||||
"avif": "image/avif",
|
||||
"bmp": "image/bmp",
|
||||
"gif": "image/gif",
|
||||
"ico": "image/x-icon",
|
||||
"jp2": "image/jp2",
|
||||
"jpeg": "image/jpeg",
|
||||
"jpg": "image/jpeg",
|
||||
"jxl": "image/jxl",
|
||||
"pdf": "application/pdf",
|
||||
"png": "image/png",
|
||||
"tif": "image/tiff",
|
||||
"tiff": "image/tiff",
|
||||
"webp": "image/webp",
|
||||
"xbm": "image/x-xbitmap",
|
||||
|
||||
"ogx": "application/ogg",
|
||||
"ogg": "audio/ogg",
|
||||
"oga": "audio/ogg",
|
||||
"ogv": "video/ogg",
|
||||
|
||||
// Annodex
|
||||
"anx": "application/annodex",
|
||||
"axa": "audio/annodex",
|
||||
"axv": "video/annodex",
|
||||
"spx": "audio/speex",
|
||||
|
||||
// WebM
|
||||
"webm": "video/webm",
|
||||
|
||||
// MPEG
|
||||
"m1a": "audio/mpeg",
|
||||
"m2a": "audio/mpeg",
|
||||
"mpg": "video/mpeg",
|
||||
"m15": "video/mpeg",
|
||||
"m1s": "video/mpeg",
|
||||
"m1v": "video/mpeg",
|
||||
"m75": "video/mpeg",
|
||||
"mpa": "video/mpeg",
|
||||
"mpeg": "video/mpeg",
|
||||
"mpm": "video/mpeg",
|
||||
"mpv": "video/mpeg",
|
||||
|
||||
// MPEG playlist
|
||||
"m3u8": "application/x-mpegurl",
|
||||
"m3url": "audio/x-mpegurl",
|
||||
"m3u": "audio/x-mpegurl",
|
||||
|
||||
// MPEG-4
|
||||
"m4v": "video/x-m4v",
|
||||
"m4a": "audio/x-m4a",
|
||||
"m4b": "audio/x-m4b",
|
||||
"m4p": "audio/x-m4p",
|
||||
|
||||
// MP3
|
||||
"mp3": "audio/mp3",
|
||||
|
||||
// MPEG-2
|
||||
"mp2": "video/x-mpeg2",
|
||||
"vob": "video/mpeg2",
|
||||
"mod": "video/mpeg2",
|
||||
"m2ts": "video/m2ts",
|
||||
"m2t": "video/x-m2ts",
|
||||
|
||||
// 3GP/3GP2
|
||||
"3gpp": "audio/3gpp",
|
||||
"3g2": "audio/3gpp2",
|
||||
"amc": "application/x-mpeg",
|
||||
|
||||
// AAC
|
||||
"aac": "audio/aac",
|
||||
"adts": "audio/aac",
|
||||
"m4r": "audio/x-aac",
|
||||
|
||||
// CoreAudio File
|
||||
"caf": "audio/x-caf",
|
||||
"gsm": "audio/x-gsm",
|
||||
|
||||
// ADPCM
|
||||
"wav": "audio/x-wav",
|
||||
|
||||
// Text Track
|
||||
"vtt": "text/vtt",
|
||||
|
||||
// Font
|
||||
"woff": "font/woff",
|
||||
"woff2": "font/woff2",
|
||||
"otf": "font/otf",
|
||||
"ttf": "font/ttf",
|
||||
"sfnt": "font/sfnt",
|
||||
|
||||
// Miscellaneous types.
|
||||
"svg": "image/svg+xml",
|
||||
"txt": "text/plain",
|
||||
"xsl": "text/xsl"
|
||||
};
|
||||
|
||||
return extensionToMIMEType[extension] || null;
|
||||
};
|
||||
|
||||
WI.fileExtensionForMIMEType = function(mimeType)
|
||||
{
|
||||
if (!mimeType)
|
||||
return null;
|
||||
|
||||
const mimeTypeToExtension = {
|
||||
// Document types.
|
||||
"text/html": "html",
|
||||
"application/xhtml+xml": "xhtml",
|
||||
"application/xml": "xml",
|
||||
"text/xml": "xml",
|
||||
|
||||
// Script types.
|
||||
"application/ecmascript": "js",
|
||||
"application/javascript": "js",
|
||||
"application/x-ecmascript": "js",
|
||||
"application/x-javascript": "js",
|
||||
"text/ecmascript": "js",
|
||||
"text/javascript": "js",
|
||||
"text/javascript1.0": "js",
|
||||
"text/javascript1.1": "js",
|
||||
"text/javascript1.2": "js",
|
||||
"text/javascript1.3": "js",
|
||||
"text/javascript1.4": "js",
|
||||
"text/javascript1.5": "js",
|
||||
"text/jscript": "js",
|
||||
"text/x-ecmascript": "js",
|
||||
"text/x-javascript": "js",
|
||||
"application/json": "json",
|
||||
"text/x-clojure": "clj",
|
||||
"text/x-coffeescript": "coffee",
|
||||
"text/livescript": "ls",
|
||||
"text/x-livescript": "ls",
|
||||
"text/typescript": "ts",
|
||||
"application/postscript": "ps",
|
||||
"text/jsx": "jsx",
|
||||
|
||||
// Stylesheet types.
|
||||
"text/css": "css",
|
||||
"text/x-less": "less",
|
||||
"text/x-sass": "sass",
|
||||
"text/x-scss": "scss",
|
||||
|
||||
// Image types.
|
||||
"image/avif": "avif",
|
||||
"image/bmp": "bmp",
|
||||
"image/gif": "gif",
|
||||
"image/vnd.microsoft.icon": "ico",
|
||||
"image/x-icon": "ico",
|
||||
"image/jp2": "jp2",
|
||||
"image/jpeg": "jpg",
|
||||
"image/jxl": "jxl",
|
||||
"application/pdf": "pdf",
|
||||
"text/pdf": "pdf",
|
||||
"image/png": "png",
|
||||
"image/tiff": "tiff",
|
||||
"image/webp": "webp",
|
||||
"image/x-xbitmap": "xbm",
|
||||
|
||||
// Ogg
|
||||
"application/ogg": "ogx",
|
||||
"audio/ogg": "ogg",
|
||||
|
||||
// Annodex
|
||||
"application/annodex": "anx",
|
||||
"audio/annodex": "axa",
|
||||
"video/annodex": "axv",
|
||||
"audio/speex": "spx",
|
||||
|
||||
// WebM
|
||||
"video/webm": "webm",
|
||||
"audio/webm": "webm",
|
||||
|
||||
// MPEG
|
||||
"video/mpeg": "mpeg",
|
||||
|
||||
// MPEG playlist
|
||||
"application/vnd.apple.mpegurl": "m3u8",
|
||||
"application/mpegurl": "m3u8",
|
||||
"application/x-mpegurl": "m3u8",
|
||||
"audio/mpegurl": "m3u",
|
||||
"audio/x-mpegurl": "m3u",
|
||||
|
||||
// MPEG-4
|
||||
"video/x-m4v": "m4v",
|
||||
"audio/x-m4a": "m4a",
|
||||
"audio/x-m4b": "m4b",
|
||||
"audio/x-m4p": "m4p",
|
||||
"audio/mp4": "m4a",
|
||||
|
||||
// MP3
|
||||
"audio/mp3": "mp3",
|
||||
"audio/x-mp3": "mp3",
|
||||
"audio/x-mpeg": "mp3",
|
||||
|
||||
// MPEG-2
|
||||
"video/x-mpeg2": "mp2",
|
||||
"video/mpeg2": "vob",
|
||||
"video/m2ts": "m2ts",
|
||||
"video/x-m2ts": "m2t",
|
||||
|
||||
// 3GP/3GP2
|
||||
"audio/3gpp": "3gpp",
|
||||
"audio/3gpp2": "3g2",
|
||||
"application/x-mpeg": "amc",
|
||||
|
||||
// AAC
|
||||
"audio/aac": "aac",
|
||||
"audio/x-aac": "m4r",
|
||||
|
||||
// CoreAudio File
|
||||
"audio/x-caf": "caf",
|
||||
"audio/x-gsm": "gsm",
|
||||
|
||||
// ADPCM
|
||||
"audio/x-wav": "wav",
|
||||
"audio/vnd.wave": "wav",
|
||||
|
||||
// Text Track
|
||||
"text/vtt": "vtt",
|
||||
|
||||
// Font
|
||||
"font/woff": "woff",
|
||||
"font/woff2": "woff2",
|
||||
"font/otf": "otf",
|
||||
"font/ttf": "ttf",
|
||||
"font/sfnt": "sfnt",
|
||||
|
||||
// Miscellaneous types.
|
||||
"image/svg+xml": "svg",
|
||||
"text/plain": "txt",
|
||||
"text/xsl": "xsl",
|
||||
};
|
||||
|
||||
let extension = mimeTypeToExtension[mimeType];
|
||||
if (extension)
|
||||
return extension;
|
||||
|
||||
if (mimeType.endsWith("+json"))
|
||||
return "json";
|
||||
if (mimeType.endsWith("+xml"))
|
||||
return "xml";
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
WI.shouldTreatMIMETypeAsText = function(mimeType)
|
||||
{
|
||||
if (!mimeType)
|
||||
return false;
|
||||
|
||||
if (mimeType.startsWith("text/"))
|
||||
return true;
|
||||
|
||||
if (mimeType.endsWith("+json") || mimeType.endsWith("+xml"))
|
||||
return true;
|
||||
|
||||
let extension = WI.fileExtensionForMIMEType(mimeType);
|
||||
if (extension === "xml")
|
||||
return true;
|
||||
|
||||
// Various script and JSON mime types.
|
||||
if (extension === "js" || extension === "json")
|
||||
return true;
|
||||
|
||||
// Various media text mime types.
|
||||
if (extension === "m3u8" || extension === "m3u")
|
||||
return true;
|
||||
|
||||
if (mimeType.startsWith("application/"))
|
||||
return mimeType.endsWith("script") || mimeType.endsWith("json") || mimeType.endsWith("xml");
|
||||
|
||||
return false;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2020 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 Multimap
|
||||
{
|
||||
constructor(items = [])
|
||||
{
|
||||
this._map = new Map;
|
||||
|
||||
for (let [key, value] of items)
|
||||
this.add(key, value);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get size()
|
||||
{
|
||||
return this._map.size;
|
||||
}
|
||||
|
||||
has(key, value)
|
||||
{
|
||||
let valueSet = this._map.get(key);
|
||||
if (!valueSet)
|
||||
return false;
|
||||
return value === undefined || valueSet.has(value);
|
||||
}
|
||||
|
||||
get(key)
|
||||
{
|
||||
return this._map.get(key);
|
||||
}
|
||||
|
||||
add(key, value)
|
||||
{
|
||||
let valueSet = this._map.get(key);
|
||||
if (!valueSet) {
|
||||
valueSet = new Set;
|
||||
this._map.set(key, valueSet);
|
||||
}
|
||||
valueSet.add(value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(key, value)
|
||||
{
|
||||
// Allow an entire key to be removed by not passing a value.
|
||||
if (arguments.length === 1)
|
||||
return this._map.delete(key);
|
||||
|
||||
let valueSet = this._map.get(key);
|
||||
if (!valueSet)
|
||||
return false;
|
||||
|
||||
let deleted = valueSet.delete(value);
|
||||
|
||||
if (!valueSet.size)
|
||||
this._map.delete(key);
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
take(key, value)
|
||||
{
|
||||
// Allow an entire key to be removed by not passing a value.
|
||||
if (arguments.length === 1)
|
||||
return this._map.take(key);
|
||||
|
||||
let valueSet = this._map.get(key);
|
||||
if (!valueSet)
|
||||
return undefined;
|
||||
|
||||
let result = valueSet.take(value);
|
||||
if (!valueSet.size)
|
||||
this._map.delete(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
clear()
|
||||
{
|
||||
this._map.clear();
|
||||
}
|
||||
|
||||
keys()
|
||||
{
|
||||
return this._map.keys();
|
||||
}
|
||||
|
||||
*values()
|
||||
{
|
||||
for (let valueSet of this._map.values()) {
|
||||
for (let value of valueSet)
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
|
||||
sets()
|
||||
{
|
||||
return this._map.entries();
|
||||
}
|
||||
|
||||
*[Symbol.iterator]()
|
||||
{
|
||||
for (let [key, valueSet] of this._map) {
|
||||
for (let value of valueSet)
|
||||
yield [key, value];
|
||||
}
|
||||
}
|
||||
|
||||
copy()
|
||||
{
|
||||
return new Multimap(this.toJSON());
|
||||
}
|
||||
|
||||
toJSON()
|
||||
{
|
||||
return Array.from(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.Object = class WebInspectorObject
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this._listeners = null;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static addEventListener(eventType, listener, thisObject)
|
||||
{
|
||||
console.assert(typeof eventType === "string", this, eventType, listener, thisObject);
|
||||
console.assert(typeof listener === "function", this, eventType, listener, thisObject);
|
||||
console.assert(typeof thisObject === "object" || window.InspectorTest || window.ProtocolTest, this, eventType, listener, thisObject);
|
||||
|
||||
thisObject ??= this;
|
||||
|
||||
let data = {
|
||||
listener,
|
||||
thisObjectWeakRef: new WeakRef(thisObject),
|
||||
};
|
||||
|
||||
WI.Object._listenerThisObjectFinalizationRegistry.register(thisObject, {eventTargetWeakRef: new WeakRef(this), eventType, data}, data);
|
||||
|
||||
this._listeners ??= new Multimap;
|
||||
this._listeners.add(eventType, data);
|
||||
|
||||
console.assert(Array.from(this._listeners.get(eventType)).filter((item) => item.listener === listener && item.thisObjectWeakRef.deref() === thisObject).length === 1, this, eventType, listener, thisObject);
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
||||
static singleFireEventListener(eventType, listener, thisObject)
|
||||
{
|
||||
let eventTargetWeakRef = new WeakRef(this);
|
||||
return this.addEventListener(eventType, function wrappedCallback() {
|
||||
eventTargetWeakRef.deref()?.removeEventListener(eventType, wrappedCallback, this);
|
||||
listener.apply(this, arguments);
|
||||
}, thisObject);
|
||||
}
|
||||
|
||||
static awaitEvent(eventType, thisObject)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
this.singleFireEventListener(eventType, resolve, thisObject);
|
||||
});
|
||||
}
|
||||
|
||||
static removeEventListener(eventType, listener, thisObject)
|
||||
{
|
||||
console.assert(this._listeners, this, eventType, listener, thisObject);
|
||||
console.assert(typeof eventType === "string", this, eventType, listener, thisObject);
|
||||
console.assert(typeof listener === "function", this, eventType, listener, thisObject);
|
||||
console.assert(typeof thisObject === "object" || window.InspectorTest || window.ProtocolTest, this, eventType, listener, thisObject);
|
||||
|
||||
if (!this._listeners)
|
||||
return;
|
||||
|
||||
thisObject ??= this;
|
||||
|
||||
let listenersForEventType = this._listeners.get(eventType);
|
||||
console.assert(listenersForEventType, this, eventType, listener, thisObject);
|
||||
if (!listenersForEventType)
|
||||
return;
|
||||
|
||||
let didDelete = false;
|
||||
for (let data of listenersForEventType) {
|
||||
let unwrapped = data.thisObjectWeakRef.deref();
|
||||
if (!unwrapped || unwrapped !== thisObject || data.listener !== listener)
|
||||
continue;
|
||||
|
||||
if (this._listeners.delete(eventType, data))
|
||||
didDelete = true;
|
||||
WI.Object._listenerThisObjectFinalizationRegistry.unregister(data);
|
||||
}
|
||||
console.assert(didDelete, this, eventType, listener, thisObject);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
addEventListener() { return WI.Object.addEventListener.apply(this, arguments); }
|
||||
singleFireEventListener() { return WI.Object.singleFireEventListener.apply(this, arguments); }
|
||||
awaitEvent() { return WI.Object.awaitEvent.apply(this, arguments); }
|
||||
removeEventListener() { return WI.Object.removeEventListener.apply(this, arguments); }
|
||||
|
||||
dispatchEventToListeners(eventType, eventData)
|
||||
{
|
||||
let event = new WI.Event(this, eventType, eventData);
|
||||
|
||||
function dispatch(object)
|
||||
{
|
||||
if (!object || event._stoppedPropagation)
|
||||
return;
|
||||
|
||||
let listeners = object._listeners;
|
||||
if (!listeners || !object.hasOwnProperty("_listeners") || !listeners.size)
|
||||
return;
|
||||
|
||||
let listenersForEventType = listeners.get(eventType);
|
||||
if (!listenersForEventType)
|
||||
return;
|
||||
|
||||
// Copy the set of listeners so we don't have to worry about mutating while iterating.
|
||||
for (let data of Array.from(listenersForEventType)) {
|
||||
let unwrapped = data.thisObjectWeakRef.deref();
|
||||
if (!unwrapped)
|
||||
continue;
|
||||
|
||||
data.listener.call(unwrapped, 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;
|
||||
}
|
||||
|
||||
// Test
|
||||
|
||||
static hasEventListeners(eventType)
|
||||
{
|
||||
console.assert(window.InspectorTest || window.ProtocolTest);
|
||||
return this._listeners?.has(eventType);
|
||||
}
|
||||
|
||||
static activelyListeningObjectsWithPrototype(proto)
|
||||
{
|
||||
console.assert(window.InspectorTest || window.ProtocolTest);
|
||||
let results = new Set;
|
||||
if (this._listeners) {
|
||||
for (let data of this._listeners.values()) {
|
||||
let unwrapped = data.thisObjectWeakRef.deref();
|
||||
if (unwrapped instanceof proto)
|
||||
results.add(unwrapped);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
hasEventListeners() { return WI.Object.hasEventListeners.apply(this, arguments); }
|
||||
activelyListeningObjectsWithPrototype() { return WI.Object.activelyListeningObjectsWithPrototype.apply(this, arguments); }
|
||||
};
|
||||
|
||||
WI.Object._listenerThisObjectFinalizationRegistry = new FinalizationRegistry((heldValue) => {
|
||||
heldValue.eventTargetWeakRef.deref()?._listeners.delete(heldValue.eventType, heldValue.data);
|
||||
});
|
||||
|
||||
WI.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;
|
||||
}
|
||||
};
|
||||
|
||||
WI.notifications = new WI.Object;
|
||||
|
||||
WI.Notification = {
|
||||
GlobalModifierKeysDidChange: "global-modifiers-did-change",
|
||||
PageArchiveStarted: "page-archive-started",
|
||||
PageArchiveEnded: "page-archive-ended",
|
||||
ExtraDomainsActivated: "extra-domains-activated", // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
VisibilityStateDidChange: "visibility-state-did-change",
|
||||
TransitionPageTarget: "transition-page-target",
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
WI.ObjectStore = class ObjectStore
|
||||
{
|
||||
constructor(name, options = {})
|
||||
{
|
||||
this._name = name;
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static supported()
|
||||
{
|
||||
return (!window.InspectorTest || WI.ObjectStore.__testObjectStore) && window.indexedDB;
|
||||
}
|
||||
|
||||
static async reset()
|
||||
{
|
||||
if (WI.ObjectStore._database)
|
||||
WI.ObjectStore._database.close();
|
||||
|
||||
await window.indexedDB.deleteDatabase(ObjectStore._databaseName);
|
||||
}
|
||||
|
||||
static get _databaseName()
|
||||
{
|
||||
let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel : 1;
|
||||
let levelString = (inspectionLevel > 1) ? "-" + inspectionLevel : "";
|
||||
return "com.apple.WebInspector" + levelString;
|
||||
}
|
||||
|
||||
static _open(callback)
|
||||
{
|
||||
if (WI.ObjectStore._database) {
|
||||
callback(WI.ObjectStore._database);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(WI.ObjectStore._databaseCallbacks)) {
|
||||
WI.ObjectStore._databaseCallbacks.push(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
WI.ObjectStore._databaseCallbacks = [callback];
|
||||
|
||||
const version = 8; // Increment this for every edit to `WI.objectStores`.
|
||||
|
||||
let databaseRequest = window.indexedDB.open(WI.ObjectStore._databaseName, version);
|
||||
databaseRequest.addEventListener("upgradeneeded", (event) => {
|
||||
let database = databaseRequest.result;
|
||||
|
||||
let objectStores = Object.values(WI.objectStores);
|
||||
if (WI.ObjectStore.__testObjectStore)
|
||||
objectStores.push(WI.ObjectStore.__testObjectStore);
|
||||
|
||||
let existingNames = new Set;
|
||||
for (let objectStore of objectStores) {
|
||||
if (!database.objectStoreNames.contains(objectStore._name))
|
||||
database.createObjectStore(objectStore._name, objectStore._options);
|
||||
|
||||
existingNames.add(objectStore._name);
|
||||
}
|
||||
|
||||
for (let objectStoreName of database.objectStoreNames) {
|
||||
if (!existingNames.has(objectStoreName))
|
||||
database.deleteObjectStore(objectStoreName);
|
||||
}
|
||||
});
|
||||
databaseRequest.addEventListener("success", (successEvent) => {
|
||||
WI.ObjectStore._database = databaseRequest.result;
|
||||
WI.ObjectStore._database.addEventListener("close", (closeEvent) => {
|
||||
WI.ObjectStore._database = null;
|
||||
});
|
||||
|
||||
for (let databaseCallback of WI.ObjectStore._databaseCallbacks)
|
||||
databaseCallback(WI.ObjectStore._database);
|
||||
|
||||
WI.ObjectStore._databaseCallbacks = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get keyPath()
|
||||
{
|
||||
return (this._options || {}).keyPath;
|
||||
}
|
||||
|
||||
associateObject(object, key, value)
|
||||
{
|
||||
if (typeof value === "object")
|
||||
value = this._resolveKeyPath(value, key).value;
|
||||
|
||||
let resolved = this._resolveKeyPath(object, key);
|
||||
resolved.object[resolved.key] = value;
|
||||
}
|
||||
|
||||
async get(...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return undefined;
|
||||
|
||||
return this._operation("readonly", (objectStore) => objectStore.get(...args));
|
||||
}
|
||||
|
||||
async getAll(...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return [];
|
||||
|
||||
return this._operation("readonly", (objectStore) => objectStore.getAll(...args));
|
||||
}
|
||||
|
||||
async getAllKeys(...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return [];
|
||||
|
||||
return this._operation("readonly", (objectStore) => objectStore.getAllKeys(...args));
|
||||
}
|
||||
|
||||
async put(...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return undefined;
|
||||
|
||||
return this._operation("readwrite", (objectStore) => objectStore.put(...args));
|
||||
}
|
||||
|
||||
async putObject(object, ...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return undefined;
|
||||
|
||||
console.assert(typeof object.toJSON === "function", "ObjectStore cannot store an object without JSON serialization", object.constructor.name);
|
||||
let result = await this.put(object.toJSON(WI.ObjectStore.toJSONSymbol), ...args);
|
||||
this.associateObject(object, args[0], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async delete(...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return undefined;
|
||||
|
||||
return this._operation("readwrite", (objectStore) => objectStore.delete(...args));
|
||||
}
|
||||
|
||||
async deleteObject(object, ...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return undefined;
|
||||
|
||||
return this.delete(this._resolveKeyPath(object).value, ...args);
|
||||
}
|
||||
|
||||
async clear(...args)
|
||||
{
|
||||
if (!WI.ObjectStore.supported())
|
||||
return undefined;
|
||||
|
||||
return this._operation("readwrite", (objectStore) => objectStore.clear(...args));
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_resolveKeyPath(object, keyPath)
|
||||
{
|
||||
keyPath = keyPath || this._options.keyPath || "";
|
||||
|
||||
let parts = keyPath.split(".");
|
||||
let key = parts.splice(-1, 1);
|
||||
while (parts.length) {
|
||||
if (!object.hasOwnProperty(parts[0]))
|
||||
break;
|
||||
object = object[parts.shift()];
|
||||
}
|
||||
|
||||
if (parts.length)
|
||||
key = parts.join(".") + "." + key;
|
||||
|
||||
return {
|
||||
object,
|
||||
key,
|
||||
value: object[key],
|
||||
};
|
||||
}
|
||||
|
||||
async _operation(mode, func)
|
||||
{
|
||||
// IndexedDB transactions will auto-close if there are no active operations at the end of a
|
||||
// microtask, so we need to do everything using event listeners instead of promises.
|
||||
return new Promise((resolve, reject) => {
|
||||
WI.ObjectStore._open((database) => {
|
||||
let transaction = database.transaction([this._name], mode);
|
||||
let objectStore = transaction.objectStore(this._name);
|
||||
let request = null;
|
||||
|
||||
try {
|
||||
request = func(objectStore);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
function listener(event) {
|
||||
transaction.removeEventListener("complete", listener);
|
||||
transaction.removeEventListener("error", listener);
|
||||
request.removeEventListener("success", listener);
|
||||
request.removeEventListener("error", listener);
|
||||
|
||||
if (request.error) {
|
||||
reject(request.error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(request.result);
|
||||
}
|
||||
transaction.addEventListener("complete", listener, {once: true});
|
||||
transaction.addEventListener("error", listener, {once: true});
|
||||
request.addEventListener("success", listener, {once: true});
|
||||
request.addEventListener("error", listener, {once: true});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
WI.ObjectStore._database = null;
|
||||
WI.ObjectStore._databaseCallbacks = null;
|
||||
|
||||
WI.ObjectStore.toJSONSymbol = Symbol("ObjectStore-toJSON");
|
||||
|
||||
// Be sure to update the `version` above when making changes.
|
||||
WI.objectStores = {
|
||||
// Version 1
|
||||
audits: new WI.ObjectStore("audit-manager-tests", {keyPath: "__id", autoIncrement: true}),
|
||||
|
||||
// Version 2
|
||||
breakpoints: new WI.ObjectStore("debugger-breakpoints", {keyPath: "__id"}),
|
||||
|
||||
// Version 3
|
||||
domBreakpoints: new WI.ObjectStore("dom-debugger-dom-breakpoints", {keyPath: "__id"}),
|
||||
eventBreakpoints: new WI.ObjectStore("dom-debugger-event-breakpoints", {keyPath: "__id"}),
|
||||
urlBreakpoints: new WI.ObjectStore("dom-debugger-url-breakpoints", {keyPath: "__id"}),
|
||||
|
||||
// Version 4
|
||||
localResourceOverrides: new WI.ObjectStore("local-resource-overrides", {keyPath: "__id"}),
|
||||
|
||||
// Version 5
|
||||
general: new WI.ObjectStore("general"),
|
||||
|
||||
// Version 6
|
||||
cssPropertyNameCounts: new WI.ObjectStore("css-property-name-counts"),
|
||||
|
||||
// Version 7
|
||||
symbolicBreakpoints: new WI.ObjectStore("debugger-symbolic-breakpoints", {keyPath: "__id"}),
|
||||
|
||||
// Version 8
|
||||
consoleSnippets: new WI.ObjectStore("console-snippets", {keyPath: "__id"}),
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.Platform = {
|
||||
name: InspectorFrontendHost.platform,
|
||||
version: {
|
||||
name: InspectorFrontendHost.platformVersionName,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
WI.ReferencePage = class ReferencePage {
|
||||
constructor(page, {topic} = {})
|
||||
{
|
||||
console.assert(page instanceof WI.ReferencePage || typeof page === "string", page);
|
||||
console.assert(!(page instanceof WI.ReferencePage && page._page instanceof WI.ReferencePage), page);
|
||||
console.assert(!(page instanceof WI.ReferencePage && page.topic), page);
|
||||
console.assert(!topic || typeof topic === "string", topic);
|
||||
|
||||
if (page instanceof WI.ReferencePage)
|
||||
page = page.page;
|
||||
this._page = page;
|
||||
|
||||
this._topic = topic || "";
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get page() { return this._page; }
|
||||
get topic() { return this._topic; }
|
||||
|
||||
createLinkElement()
|
||||
{
|
||||
let url = "https://webkit.org/web-inspector/" + this._page + "/";
|
||||
if (this._topic)
|
||||
url += "#" + this._topic;
|
||||
|
||||
let wrapper = document.createElement("span");
|
||||
wrapper.className = "reference-page-link-container";
|
||||
|
||||
let link = wrapper.appendChild(document.createElement("a"));
|
||||
link.className = "reference-page-link";
|
||||
link.href = link.title = url;
|
||||
link.textContent = "?";
|
||||
link.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
WI.openURL(link.href, {alwaysOpenExternally: true});
|
||||
});
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
};
|
||||
|
||||
WI.ReferencePage.AuditTab = new WI.ReferencePage("audit-tab");
|
||||
WI.ReferencePage.AuditTab.AuditResults = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "audit-results"});
|
||||
WI.ReferencePage.AuditTab.CreatingAudits = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "creating-audits"});
|
||||
WI.ReferencePage.AuditTab.EditingAudits = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "editing-audits"});
|
||||
WI.ReferencePage.AuditTab.RunningAudits = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "running-audits"});
|
||||
|
||||
WI.ReferencePage.DOMBreakpoints = new WI.ReferencePage("dom-breakpoints");
|
||||
WI.ReferencePage.DOMBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.DOMBreakpoints, {topic: "configuration"});
|
||||
|
||||
WI.ReferencePage.DeviceSettings = new WI.ReferencePage("device-settings");
|
||||
WI.ReferencePage.DeviceSettings.Configuration = new WI.ReferencePage(WI.ReferencePage.DeviceSettings, {topic: "configuration"});
|
||||
|
||||
WI.ReferencePage.ElementsTab = new WI.ReferencePage("elements-tab");
|
||||
WI.ReferencePage.ElementsTab.DOMTree = new WI.ReferencePage(WI.ReferencePage.ElementsTab, {topic: "dom-tree"});
|
||||
|
||||
WI.ReferencePage.EventBreakpoints = new WI.ReferencePage("event-breakpoints");
|
||||
WI.ReferencePage.EventBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.EventBreakpoints, {topic: "configuration"});
|
||||
|
||||
WI.ReferencePage.JavaScriptBreakpoints = new WI.ReferencePage("javascript-breakpoints");
|
||||
WI.ReferencePage.JavaScriptBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.JavaScriptBreakpoints, {topic: "configuration"});
|
||||
|
||||
WI.ReferencePage.LayersTab = new WI.ReferencePage("layers-tab");
|
||||
|
||||
WI.ReferencePage.LocalOverrides = new WI.ReferencePage("local-overrides");
|
||||
WI.ReferencePage.LocalOverrides.ConfiguringLocalOverrides = new WI.ReferencePage(WI.ReferencePage.LocalOverrides, {topic: "configuring-local-overrides"});
|
||||
|
||||
WI.ReferencePage.NetworkTab = new WI.ReferencePage("network-tab");
|
||||
WI.ReferencePage.NetworkTab.CookiesPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "cookies-pane"});
|
||||
WI.ReferencePage.NetworkTab.HeadersPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "headers-pane"});
|
||||
WI.ReferencePage.NetworkTab.PreviewPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "preview-pane"});
|
||||
WI.ReferencePage.NetworkTab.SecurityPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "security-pane"});
|
||||
WI.ReferencePage.NetworkTab.SizesPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "sizes-pane"});
|
||||
WI.ReferencePage.NetworkTab.TimingPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "timing-pane"});
|
||||
|
||||
WI.ReferencePage.SymbolicBreakpoints = new WI.ReferencePage("symbolic-breakpoints");
|
||||
WI.ReferencePage.SymbolicBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.SymbolicBreakpoints, {topic: "configuration"});
|
||||
|
||||
WI.ReferencePage.TimelinesTab = new WI.ReferencePage("timelines-tab");
|
||||
WI.ReferencePage.TimelinesTab.CPUTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "cpu-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.EventsView = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "events-view"});
|
||||
WI.ReferencePage.TimelinesTab.FramesView = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "frames-view"});
|
||||
WI.ReferencePage.TimelinesTab.JavaScriptAllocationsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "javascript-allocations-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.JavaScriptAndEventsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "javascript-events-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.LayoutAndRenderingTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "layout-rendering-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.MediaAndAnimationsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "media-animations-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.MemoryTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "memory-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.NetworkRequestsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "network-timeline"});
|
||||
WI.ReferencePage.TimelinesTab.ScreenshotsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "screenshots-timeline"});
|
||||
|
||||
WI.ReferencePage.URLBreakpoints = new WI.ReferencePage("url-breakpoints");
|
||||
WI.ReferencePage.URLBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.URLBreakpoints, {topic: "configuration"});
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
WI.SearchUtilities = class SearchUtilities {
|
||||
static get defaultSettings()
|
||||
{
|
||||
return {
|
||||
caseSensitive: WI.settings.searchCaseSensitive,
|
||||
regularExpression: WI.settings.searchRegularExpression,
|
||||
};
|
||||
}
|
||||
|
||||
static createSettings(namePrefix)
|
||||
{
|
||||
let settings = {};
|
||||
for (let [key, defaultSetting] of Object.entries(WI.SearchUtilities.defaultSettings)) {
|
||||
let setting = new WI.Setting(namePrefix + "-" + defaultSetting.name, defaultSetting.value);
|
||||
defaultSetting.addEventListener(WI.Setting.Event.Changed, function(event) {
|
||||
this.value = defaultSetting.value;
|
||||
}, setting);
|
||||
settings[key] = setting;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
static searchRegExpForString(query, settings = {})
|
||||
{
|
||||
return WI.SearchUtilities._regExpForString(query, settings, {global: true});
|
||||
}
|
||||
|
||||
static filterRegExpForString(query, settings = {})
|
||||
{
|
||||
return WI.SearchUtilities._regExpForString(query, settings);
|
||||
}
|
||||
|
||||
static createSettingsButton(settings)
|
||||
{
|
||||
console.assert(!isEmptyObject(settings));
|
||||
|
||||
let button = document.createElement("button");
|
||||
button.classList.add("search-settings");
|
||||
button.tabIndex = -1;
|
||||
WI.addMouseDownContextMenuHandlers(button, (contextMenu) => {
|
||||
if (settings.caseSensitive) {
|
||||
contextMenu.appendCheckboxItem(WI.UIString("Case Sensitive", "Case Sensitive @ Context Menu", "Context menu label for whether searches should be case sensitive."), () => {
|
||||
settings.caseSensitive.value = !settings.caseSensitive.value;
|
||||
}, settings.caseSensitive.value);
|
||||
}
|
||||
|
||||
if (settings.regularExpression) {
|
||||
contextMenu.appendCheckboxItem(WI.UIString("Regular Expression", "Regular Expression @ Context Menu", "Context menu label for whether searches should be treated as regular expressions."), () => {
|
||||
settings.regularExpression.value = !settings.regularExpression.value;
|
||||
}, settings.regularExpression.value);
|
||||
}
|
||||
});
|
||||
|
||||
button.appendChild(WI.ImageUtilities.useSVGSymbol("Images/Gear.svg", "glyph"));
|
||||
|
||||
function toggleActive() {
|
||||
button.classList.toggle("active", Object.values(settings).some((setting) => !!setting.value));
|
||||
}
|
||||
settings.caseSensitive.addEventListener(WI.Setting.Event.Changed, toggleActive, button);
|
||||
settings.regularExpression.addEventListener(WI.Setting.Event.Changed, toggleActive, button);
|
||||
toggleActive();
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
static _regExpForString(query, settings = {}, options = {})
|
||||
{
|
||||
function checkSetting(setting) {
|
||||
return setting instanceof WI.Setting ? setting.value : !!setting;
|
||||
}
|
||||
|
||||
console.assert((typeof query === "string" && query) || query instanceof RegExp);
|
||||
|
||||
if (!checkSetting(settings.regularExpression)) {
|
||||
try {
|
||||
query = simpleGlobStringToRegExp(String(query));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
console.assert((typeof query === "string" && query) || query instanceof RegExp);
|
||||
|
||||
let flags = "";
|
||||
if (options.global)
|
||||
flags += "g"
|
||||
if (!checkSetting(settings.caseSensitive))
|
||||
flags += "i";
|
||||
|
||||
try {
|
||||
return new RegExp(query, flags);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.Setting = class Setting extends WI.Object
|
||||
{
|
||||
constructor(name, defaultValue)
|
||||
{
|
||||
super();
|
||||
|
||||
this._name = name;
|
||||
|
||||
this._defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static migrateValue(key)
|
||||
{
|
||||
let localStorageKey = WI.Setting._localStorageKeyPrefix + key;
|
||||
|
||||
let value = undefined;
|
||||
if (!window.InspectorTest && window.localStorage) {
|
||||
let item = window.localStorage.getItem(localStorageKey);
|
||||
if (item !== null) {
|
||||
try {
|
||||
value = JSON.parse(item);
|
||||
} catch { }
|
||||
|
||||
window.localStorage.removeItem(localStorageKey);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static reset()
|
||||
{
|
||||
let prefix = WI.Setting._localStorageKeyPrefix;
|
||||
|
||||
let keysToRemove = [];
|
||||
for (let i = 0; i < window.localStorage.length; ++i) {
|
||||
let key = window.localStorage.key(i);
|
||||
if (key.startsWith(prefix))
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
|
||||
for (let key of keysToRemove)
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get name() { return this._name; }
|
||||
get defaultValue() { return this._defaultValue; }
|
||||
|
||||
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) {
|
||||
let key = WI.Setting._localStorageKeyPrefix + this._name;
|
||||
let item = window.localStorage.getItem(key);
|
||||
if (item !== null) {
|
||||
try {
|
||||
this._value = JSON.parse(item);
|
||||
} catch {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value)
|
||||
{
|
||||
if (this._value === value)
|
||||
return;
|
||||
|
||||
this._value = value;
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
save()
|
||||
{
|
||||
if (!window.InspectorTest && window.localStorage) {
|
||||
let key = WI.Setting._localStorageKeyPrefix + this._name;
|
||||
try {
|
||||
if (Object.shallowEqual(this._value, this._defaultValue))
|
||||
window.localStorage.removeItem(key);
|
||||
else
|
||||
window.localStorage.setItem(key, JSON.stringify(this._value));
|
||||
} catch {
|
||||
console.error("Error saving setting with name: " + this._name);
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatchEventToListeners(WI.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));
|
||||
}
|
||||
};
|
||||
|
||||
WI.Setting._localStorageKeyPrefix = (function() {
|
||||
let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel : 1;
|
||||
let levelString = inspectionLevel > 1 ? "-" + inspectionLevel : "";
|
||||
return `com.apple.WebInspector${levelString}.`;
|
||||
})();
|
||||
|
||||
WI.Setting.isFirstLaunch = !!window.InspectorTest || (window.localStorage && Object.keys(window.localStorage).every((key) => !key.startsWith(WI.Setting._localStorageKeyPrefix)));
|
||||
|
||||
WI.Setting.Event = {
|
||||
Changed: "setting-changed"
|
||||
};
|
||||
|
||||
WI.EngineeringSetting = class EngineeringSetting extends WI.Setting
|
||||
{
|
||||
get value()
|
||||
{
|
||||
if (WI.engineeringSettingsAllowed())
|
||||
return super.value;
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
set value(value)
|
||||
{
|
||||
console.assert(WI.engineeringSettingsAllowed());
|
||||
if (WI.engineeringSettingsAllowed())
|
||||
super.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
WI.DebugSetting = class DebugSetting extends WI.Setting
|
||||
{
|
||||
get value()
|
||||
{
|
||||
if (WI.isDebugUIEnabled())
|
||||
return super.value;
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
set value(value)
|
||||
{
|
||||
console.assert(WI.isDebugUIEnabled());
|
||||
if (WI.isDebugUIEnabled())
|
||||
super.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
WI.settings = {
|
||||
blackboxBreakpointEvaluations: new WI.Setting("blackbox-breakpoint-evaluations", true),
|
||||
canvasRecordingAutoCaptureEnabled: new WI.Setting("canvas-recording-auto-capture-enabled", false),
|
||||
canvasRecordingAutoCaptureFrameCount: new WI.Setting("canvas-recording-auto-capture-frame-count", 1),
|
||||
consoleAutoExpandTrace: new WI.Setting("console-auto-expand-trace", true),
|
||||
consoleSavedResultAlias: new WI.Setting("console-saved-result-alias", ""),
|
||||
cssChangesPerNode: new WI.Setting("css-changes-per-node", false),
|
||||
clearLogOnNavigate: new WI.Setting("clear-log-on-navigate", true),
|
||||
clearNetworkOnNavigate: new WI.Setting("clear-network-on-navigate", true),
|
||||
cpuTimelineThreadDetailsExpanded: new WI.Setting("cpu-timeline-thread-details-expanded", false),
|
||||
domTreeDeemphasizesNodesThatAreNotRendered: new WI.Setting("dom-tree-deemphasizes-nodes-that-are-not-rendered", true),
|
||||
emulateInUserGesture: new WI.Setting("emulate-in-user-gesture", false),
|
||||
enableControlFlowProfiler: new WI.Setting("enable-control-flow-profiler", false),
|
||||
enableElementsTabIndependentStylesDetailsSidebarPanel: new WI.Setting("elements-tab-independent-styles-details-panel", true),
|
||||
enableLineWrapping: new WI.Setting("enable-line-wrapping", true),
|
||||
flexOverlayShowOrderNumbers: new WI.Setting("flex-overlay-show-order-numbers", false),
|
||||
frontendAppearance: new WI.Setting("frontend-appearance", "system"),
|
||||
gridOverlayShowAreaNames: new WI.Setting("grid-overlay-show-area-names", false),
|
||||
gridOverlayShowExtendedGridLines: new WI.Setting("grid-overlay-show-extended-grid-lines", false),
|
||||
gridOverlayShowLineNames: new WI.Setting("grid-overlay-show-line-names", false),
|
||||
gridOverlayShowLineNumbers: new WI.Setting("grid-overlay-show-line-numbers", true),
|
||||
gridOverlayShowTrackSizes: new WI.Setting("grid-overlay-show-track-sizes", true),
|
||||
groupMediaRequestsByDOMNode: new WI.Setting("group-media-requests-by-dom-node", WI.Setting.migrateValue("group-by-dom-node") || false),
|
||||
indentUnit: new WI.Setting("indent-unit", 4),
|
||||
indentWithTabs: new WI.Setting("indent-with-tabs", false),
|
||||
resourceCachingDisabled: new WI.Setting("disable-resource-caching", false),
|
||||
searchCaseSensitive: new WI.Setting("search-case-sensitive", false),
|
||||
searchFromSelection: new WI.Setting("search-from-selection", false),
|
||||
searchRegularExpression: new WI.Setting("search-regular-expression", false),
|
||||
selectedNetworkDetailContentViewIdentifier: new WI.Setting("network-detail-content-view-identifier", "preview"),
|
||||
sourceMapsEnabled: new WI.Setting("source-maps-enabled", true),
|
||||
showConsoleMessageTimestamps: new WI.Setting("show-console-message-timestamps", false),
|
||||
showCSSPropertySyntaxInDocumentationPopover: new WI.Setting("show-css-property-syntax-in-documentation-popover", false),
|
||||
showCanvasPath: new WI.Setting("show-canvas-path", false),
|
||||
showImageGrid: new WI.Setting("show-image-grid", true),
|
||||
showInvisibleCharacters: new WI.Setting("show-invisible-characters", !!WI.Setting.migrateValue("show-invalid-characters")),
|
||||
showJavaScriptTypeInformation: new WI.Setting("show-javascript-type-information", false),
|
||||
showRulers: new WI.Setting("show-rulers", false),
|
||||
showRulersDuringElementSelection: new WI.Setting("show-rulers-during-element-selection", true),
|
||||
showScopeChainOnPause: new WI.Setting("show-scope-chain-sidebar", true),
|
||||
showWhitespaceCharacters: new WI.Setting("show-whitespace-characters", false),
|
||||
tabSize: new WI.Setting("tab-size", 4),
|
||||
timelinesAutoStop: new WI.Setting("timelines-auto-stop", true),
|
||||
timelineOverviewGroupBySourceCode: new WI.Setting("timeline-overview-group-by-source-code", true),
|
||||
zoomFactor: new WI.Setting("zoom-factor", 1),
|
||||
|
||||
// Experimental
|
||||
experimentalEnableStylesJumpToEffective: new WI.Setting("experimental-styles-jump-to-effective", false),
|
||||
experimentalEnableStylesJumpToVariableDeclaration: new WI.Setting("experimental-styles-jump-to-variable-declaration", false),
|
||||
experimentalAllowInspectingInspector: new WI.Setting("experimental-allow-inspecting-inspector", false),
|
||||
experimentalCSSSortPropertyNameAutocompletionByUsage: new WI.Setting("experimental-css-sort-property-name-autocompletion-by-usage", true),
|
||||
experimentalEnableNetworkEmulatedCondition: new WI.Setting("experimental-enable-network-emulated-condition", false),
|
||||
|
||||
// Protocol
|
||||
protocolLogAsText: new WI.Setting("protocol-log-as-text", false),
|
||||
protocolAutoLogMessages: new WI.Setting("protocol-auto-log-messages", false),
|
||||
protocolAutoLogTimeStats: new WI.Setting("protocol-auto-log-time-stats", false),
|
||||
protocolFilterMultiplexingBackendMessages: new WI.Setting("protocol-filter-multiplexing-backend-messages", true),
|
||||
|
||||
// Engineering
|
||||
engineeringShowInternalExecutionContexts: new WI.EngineeringSetting("engineering-show-internal-execution-contexts", false),
|
||||
engineeringShowInternalScripts: new WI.EngineeringSetting("engineering-show-internal-scripts", false),
|
||||
engineeringPauseForInternalScripts: new WI.EngineeringSetting("engineering-pause-for-internal-scripts", false),
|
||||
engineeringShowInternalObjectsInHeapSnapshot: new WI.EngineeringSetting("engineering-show-internal-objects-in-heap-snapshot", false),
|
||||
engineeringShowPrivateSymbolsInHeapSnapshot: new WI.EngineeringSetting("engineering-show-private-symbols-in-heap-snapshot", false),
|
||||
engineeringAllowEditingUserAgentShadowTrees: new WI.EngineeringSetting("engineering-allow-editing-user-agent-shadow-trees", false),
|
||||
|
||||
// Debug
|
||||
debugShowConsoleEvaluations: new WI.DebugSetting("debug-show-console-evaluations", false),
|
||||
debugOutlineFocusedElement: new WI.DebugSetting("debug-outline-focused-element", false),
|
||||
debugEnableLayoutFlashing: new WI.DebugSetting("debug-enable-layout-flashing", false),
|
||||
debugEnableStyleEditingDebugMode: new WI.DebugSetting("debug-enable-style-editing-debug-mode", false),
|
||||
debugEnableUncaughtExceptionReporter: new WI.DebugSetting("debug-enable-uncaught-exception-reporter", true),
|
||||
debugEnableDiagnosticLogging: new WI.DebugSetting("debug-enable-diagnostic-logging", true),
|
||||
debugAutoLogDiagnosticEvents: new WI.DebugSetting("debug-auto-log-diagnostic-events", false),
|
||||
debugLayoutDirection: new WI.DebugSetting("debug-layout-direction-override", "system"),
|
||||
debugShowMockWebExtensionTab: new WI.DebugSetting("debug-show-mock-web-extension-tab", false),
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.TargetType = {
|
||||
ITML: "itml",
|
||||
JavaScript: "javascript",
|
||||
Page: "page",
|
||||
ServiceWorker: "service-worker",
|
||||
WebPage: "web-page",
|
||||
Worker: "worker",
|
||||
};
|
||||
|
||||
WI.TargetType.all = Object.values(WI.TargetType);
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
// Throttler wraps a function and ensures it doesn't get called more than once every `delay` ms.
|
||||
// The first fire will always trigger synchronous, and subsequent calls that need to be throttled
|
||||
// will happen later asynchronously.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// let throttler = new Throttler(250, () => { this.refresh() });
|
||||
// element.addEventListener("keydown", (event) => { throttler.fire(); });
|
||||
//
|
||||
// Will ensure `refresh` happens no more than once every 250ms no matter how often `fire` is called:
|
||||
//
|
||||
// 0ms 250ms 500ms
|
||||
// time: |---------------------------|---------------------------|
|
||||
// fire: ^ ^ ^ ^ ^ ^ ^ ^
|
||||
// refreshes: * (1) * (2) * (3)
|
||||
//
|
||||
// When the wrapped function is actually called, it will be given the most recent set of arguments.
|
||||
|
||||
class Throttler
|
||||
{
|
||||
constructor(callback, delay)
|
||||
{
|
||||
console.assert(typeof callback === "function");
|
||||
console.assert(delay >= 0);
|
||||
|
||||
this._callback = callback;
|
||||
this._delay = delay;
|
||||
|
||||
this._lastArguments = [];
|
||||
|
||||
this._timeoutIdentifier = undefined;
|
||||
this._lastFireTime = -this._delay;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
force()
|
||||
{
|
||||
this._lastArguments = arguments;
|
||||
this._execute();
|
||||
}
|
||||
|
||||
fire()
|
||||
{
|
||||
this._lastArguments = arguments;
|
||||
|
||||
let remaining = this._delay - (Date.now() - this._lastFireTime);
|
||||
if (remaining <= 0) {
|
||||
this._execute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._timeoutIdentifier)
|
||||
return;
|
||||
|
||||
this._timeoutIdentifier = setTimeout(() => {
|
||||
this._execute();
|
||||
}, remaining);
|
||||
}
|
||||
|
||||
cancel()
|
||||
{
|
||||
this._lastArguments = [];
|
||||
|
||||
if (this._timeoutIdentifier) {
|
||||
clearTimeout(this._timeoutIdentifier);
|
||||
this._timeoutIdentifier = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_execute()
|
||||
{
|
||||
this._lastFireTime = Date.now();
|
||||
|
||||
let args = this._lastArguments;
|
||||
|
||||
this.cancel();
|
||||
|
||||
this._callback.apply(undefined, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* 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 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() : "";
|
||||
|
||||
let match = securityOrigin.match(/^(?<scheme>[^:]+):\/\/(?<host>[^\/:]*)(?::(?<port>[\d]+))?$/i);
|
||||
if (!match)
|
||||
return {scheme: null, host: null, port: null};
|
||||
|
||||
let scheme = match.groups.scheme.toLowerCase();
|
||||
let host = match.groups.host.toLowerCase();
|
||||
let port = Number(match.groups.port) || 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:(?<mime>[^;,]*)?(?:;charset=(?<charset>[^;,]*?))?(?<base64>;base64)?,(?<data>.*)$/);
|
||||
if (!match)
|
||||
return null;
|
||||
|
||||
let scheme = "data";
|
||||
let mimeType = match.groups.mime || "text/plain";
|
||||
let charset = match.groups.charset || "US-ASCII";
|
||||
let base64 = !!match.groups.base64;
|
||||
let data = decodeURIComponent(match.groups.data);
|
||||
|
||||
return {scheme, mimeType, charset, base64, data};
|
||||
}
|
||||
|
||||
function parseURL(url)
|
||||
{
|
||||
let result = {
|
||||
scheme: null,
|
||||
userinfo: null,
|
||||
host: null,
|
||||
port: null,
|
||||
origin: null,
|
||||
path: null,
|
||||
queryString: null,
|
||||
fragment: null,
|
||||
lastPathComponent: null,
|
||||
};
|
||||
|
||||
// dataURLs should be handled by `parseDataURL`.
|
||||
if (url && url.startsWith("data:")) {
|
||||
result.scheme = "data";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Internal sourceURLs will fail in URL constructor anyways.
|
||||
if (isWebKitInternalScript(url))
|
||||
return result;
|
||||
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.scheme = parsed.protocol.slice(0, -1); // remove trailing ":"
|
||||
|
||||
if (parsed.username)
|
||||
result.userinfo = parsed.username;
|
||||
if (parsed.password)
|
||||
result.userinfo = (result.userinfo || "") + ":" + parsed.password;
|
||||
|
||||
if (parsed.hostname)
|
||||
result.host = parsed.hostname;
|
||||
|
||||
if (parsed.port)
|
||||
result.port = Number(parsed.port);
|
||||
|
||||
if (parsed.origin && parsed.origin !== "null")
|
||||
result.origin = parsed.origin;
|
||||
else if (result.scheme && result.host) {
|
||||
result.origin = result.scheme + "://" + result.host;
|
||||
if (result.port)
|
||||
result.origin += ":" + result.port;
|
||||
}
|
||||
|
||||
if (parsed.pathname)
|
||||
result.path = parsed.pathname;
|
||||
|
||||
if (parsed.search)
|
||||
result.queryString = parsed.search.substring(1); // remove leading "?"
|
||||
|
||||
if (parsed.hash)
|
||||
result.fragment = parsed.hash.substring(1); // remove leading "#"
|
||||
|
||||
// Find last path component.
|
||||
if (result.path && result.path !== "/") {
|
||||
// Skip the trailing slash if there is one.
|
||||
let endOffset = result.path.endsWith("/") ? 1 : 0;
|
||||
let lastSlashIndex = result.path.lastIndexOf("/", result.path.length - 1 - endOffset);
|
||||
if (lastSlashIndex !== -1)
|
||||
result.lastPathComponent = result.path.substring(lastSlashIndex + 1, result.path.length - endOffset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 parseQueryString(queryString, arrayResult)
|
||||
{
|
||||
if (!queryString)
|
||||
return arrayResult ? [] : {};
|
||||
|
||||
function decode(string)
|
||||
{
|
||||
try {
|
||||
// Replace "+" with " " then decode percent encoded values.
|
||||
return decodeURIComponent(string.replace(/\+/g, " "));
|
||||
} catch {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
var parameters = arrayResult ? [] : {};
|
||||
for (let parameterString of queryString.split("&")) {
|
||||
let index = parameterString.indexOf("=");
|
||||
if (index === -1)
|
||||
index = parameterString.length;
|
||||
|
||||
let name = decode(parameterString.substring(0, index));
|
||||
let value = decode(parameterString.substring(index + 1));
|
||||
|
||||
if (arrayResult)
|
||||
parameters.push({name, value});
|
||||
else
|
||||
parameters[name] = value;
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
WI.displayNameForURL = function(url, urlComponents, options = {})
|
||||
{
|
||||
if (url.startsWith("data:"))
|
||||
return WI.truncateURL(url);
|
||||
|
||||
if (!urlComponents)
|
||||
urlComponents = parseURL(url);
|
||||
|
||||
var displayName;
|
||||
try {
|
||||
displayName = decodeURIComponent(urlComponents.lastPathComponent || "");
|
||||
} catch {
|
||||
displayName = urlComponents.lastPathComponent;
|
||||
}
|
||||
|
||||
if (options.allowDirectoryAsName && (urlComponents.path === "/" || (displayName && urlComponents.path.endsWith(displayName + "/"))))
|
||||
displayName = "/";
|
||||
|
||||
return displayName || WI.displayNameForHost(urlComponents.host) || url;
|
||||
};
|
||||
|
||||
WI.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;
|
||||
};
|
||||
|
||||
WI.urlWithoutExtension = function(urlString)
|
||||
{
|
||||
let url = null;
|
||||
try {
|
||||
url = new URL(urlString);
|
||||
} catch { }
|
||||
if (!url)
|
||||
return urlString;
|
||||
|
||||
let firstDotInLastPathComponentIndex = url.pathname.indexOf(".", url.pathname.lastIndexOf("/"));
|
||||
if (firstDotInLastPathComponentIndex !== -1)
|
||||
url.pathname = url.pathname.substring(0, firstDotInLastPathComponentIndex);
|
||||
|
||||
url.search = "";
|
||||
url.hash = "";
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
WI.urlWithoutFragment = function(urlString)
|
||||
{
|
||||
try {
|
||||
let url = new URL(urlString);
|
||||
if (url.hash) {
|
||||
url.hash = "";
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// URL.toString with an empty hash leaves the hash symbol, so we strip it.
|
||||
let result = url.toString();
|
||||
if (result.endsWith("#"))
|
||||
return result.substring(0, result.length - 1);
|
||||
|
||||
return result;
|
||||
} catch { }
|
||||
|
||||
return urlString;
|
||||
};
|
||||
|
||||
WI.displayNameForHost = function(host)
|
||||
{
|
||||
let extensionName = WI.browserManager.extensionNameForId(host);
|
||||
if (extensionName)
|
||||
return extensionName;
|
||||
|
||||
// FIXME <rdar://problem/11237413>: This should decode punycode hostnames.
|
||||
|
||||
return host;
|
||||
};
|
||||
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.3
|
||||
WI.h2Authority = function(components)
|
||||
{
|
||||
let {scheme, userinfo, host, port} = components;
|
||||
let result = host || "";
|
||||
|
||||
// The authority MUST NOT include the deprecated "userinfo"
|
||||
// subcomponent for "http" or "https" schemed URIs.
|
||||
if (userinfo && (scheme !== "http" && scheme !== "https"))
|
||||
result = userinfo + "@" + result;
|
||||
if (port)
|
||||
result += ":" + port;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.3
|
||||
WI.h2Path = function(components)
|
||||
{
|
||||
let {scheme, path, queryString} = components;
|
||||
let result = path || "";
|
||||
|
||||
// The ":path" pseudo-header field includes the path and query parts
|
||||
// of the target URI. [...] This pseudo-header field MUST NOT be empty
|
||||
// for "http" or "https" URIs; "http" or "https" URIs that do not contain
|
||||
// a path component MUST include a value of '/'.
|
||||
if (!path && (scheme === "http" || scheme === "https"))
|
||||
result = "/";
|
||||
if (queryString)
|
||||
result += "?" + queryString;
|
||||
|
||||
return result;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 WI = {}; // Namespace
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.YieldableTask = class YieldableTask
|
||||
{
|
||||
constructor(delegate, items, options = {})
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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: AnimationManager lacks advanced multi-target support. (Animations per-target)
|
||||
|
||||
WI.AnimationManager = class AnimationManager
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this._enabled = false;
|
||||
this._animationCollection = new WI.AnimationCollection;
|
||||
this._animationIdMap = new Map;
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["Animation"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "Animation");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
// COMPATIBILITY (iOS 13.1): Animation.enable did not exist yet.
|
||||
if (target.hasCommand("Animation.enable"))
|
||||
target.AnimationAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get animationCollection() { return this._animationCollection; }
|
||||
|
||||
get supported()
|
||||
{
|
||||
// COMPATIBILITY (iOS 13.1): Animation.enable did not exist yet.
|
||||
return InspectorBackend.hasCommand("Animation.enable");
|
||||
}
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY (iOS 13.1): Animation.disable did not exist yet.
|
||||
if (target.hasCommand("Animation.disable"))
|
||||
target.AnimationAgent.disable();
|
||||
}
|
||||
|
||||
this._animationCollection.clear();
|
||||
this._animationIdMap.clear();
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
// AnimationObserver
|
||||
|
||||
animationCreated(animationPayload)
|
||||
{
|
||||
console.assert(!this._animationIdMap.has(animationPayload.animationId), `Animation already exists with id ${animationPayload.animationId}.`);
|
||||
|
||||
let animation = WI.Animation.fromPayload(animationPayload);
|
||||
this._animationCollection.add(animation);
|
||||
this._animationIdMap.set(animation.animationId, animation);
|
||||
}
|
||||
|
||||
nameChanged(animationId, name)
|
||||
{
|
||||
let animation = this._animationIdMap.get(animationId);
|
||||
console.assert(animation);
|
||||
if (!animation)
|
||||
return;
|
||||
|
||||
animation.nameChanged(name);
|
||||
}
|
||||
|
||||
effectChanged(animationId, effect)
|
||||
{
|
||||
let animation = this._animationIdMap.get(animationId);
|
||||
console.assert(animation);
|
||||
if (!animation)
|
||||
return;
|
||||
|
||||
animation.effectChanged(effect);
|
||||
}
|
||||
|
||||
targetChanged(animationId, effect)
|
||||
{
|
||||
let animation = this._animationIdMap.get(animationId);
|
||||
console.assert(animation);
|
||||
if (!animation)
|
||||
return;
|
||||
|
||||
animation.targetChanged(effect);
|
||||
}
|
||||
|
||||
animationDestroyed(animationId)
|
||||
{
|
||||
let animation = this._animationIdMap.take(animationId);
|
||||
console.assert(animation);
|
||||
if (!animation)
|
||||
return;
|
||||
|
||||
this._animationCollection.remove(animation);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_handleMainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
WI.Animation.resetUniqueDisplayNameNumbers();
|
||||
|
||||
this._animationCollection.clear();
|
||||
this._animationIdMap.clear();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2014 Saam Barati <saambarati1@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.Annotator = class Annotator extends WI.Object
|
||||
{
|
||||
constructor(sourceCodeTextEditor)
|
||||
{
|
||||
super();
|
||||
|
||||
console.assert(sourceCodeTextEditor instanceof WI.SourceCodeTextEditor, sourceCodeTextEditor);
|
||||
|
||||
this._sourceCodeTextEditor = sourceCodeTextEditor;
|
||||
this._timeoutIdentifier = null;
|
||||
this._isActive = false;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get sourceCodeTextEditor()
|
||||
{
|
||||
return this._sourceCodeTextEditor;
|
||||
}
|
||||
|
||||
isActive()
|
||||
{
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
pause()
|
||||
{
|
||||
this._clearTimeoutIfNeeded();
|
||||
this._isActive = false;
|
||||
}
|
||||
|
||||
resume()
|
||||
{
|
||||
this._clearTimeoutIfNeeded();
|
||||
this._isActive = true;
|
||||
this.insertAnnotations();
|
||||
}
|
||||
|
||||
refresh()
|
||||
{
|
||||
console.assert(this._isActive);
|
||||
if (!this._isActive)
|
||||
return;
|
||||
|
||||
this._clearTimeoutIfNeeded();
|
||||
this.insertAnnotations();
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this._clearTimeoutIfNeeded();
|
||||
this._isActive = true;
|
||||
this.clearAnnotations();
|
||||
this.insertAnnotations();
|
||||
}
|
||||
|
||||
clear()
|
||||
{
|
||||
this.pause();
|
||||
this.clearAnnotations();
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
insertAnnotations()
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
}
|
||||
|
||||
clearAnnotations()
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_clearTimeoutIfNeeded()
|
||||
{
|
||||
if (this._timeoutIdentifier) {
|
||||
clearTimeout(this._timeoutIdentifier);
|
||||
this._timeoutIdentifier = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.AppController = class AppController extends WI.AppControllerBase
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._debuggableType = WI.DebuggableType.fromString(InspectorFrontendHost.debuggableInfo.debuggableType);
|
||||
console.assert(this._debuggableType);
|
||||
if (!this._debuggableType)
|
||||
this._debuggableType = WI.DebuggableType.JavaScript;
|
||||
}
|
||||
|
||||
// Properties.
|
||||
|
||||
get debuggableType() { return this._debuggableType; }
|
||||
|
||||
// API.
|
||||
|
||||
activateExtraDomains(domains)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(this._debuggableType === WI.DebuggableType.JavaScript);
|
||||
console.assert(WI.targets.length === 1);
|
||||
|
||||
let target = WI.mainTarget;
|
||||
console.assert(target instanceof WI.DirectBackendTarget);
|
||||
console.assert(target.type === WI.TargetType.JavaScript);
|
||||
|
||||
if (this._debuggableType === WI.DebuggableType.ITML || target.type === WI.TargetType.ITML)
|
||||
throw new Error("Extra domains have already been activated, cannot activate again.");
|
||||
|
||||
this._debuggableType = WI.DebuggableType.ITML;
|
||||
target._type = WI.TargetType.ITML;
|
||||
|
||||
for (let domain of domains) {
|
||||
InspectorBackend.activateDomain(domain);
|
||||
|
||||
target.activateExtraDomain(domain);
|
||||
|
||||
let manager = WI.managers.find((manager) => manager.domains && manager.domains.includes(domain));
|
||||
if (manager)
|
||||
manager.activateExtraDomain(domain);
|
||||
else if (target.hasCommand(domain + ".enable"))
|
||||
target._agents[domain].enable();
|
||||
}
|
||||
|
||||
// FIXME: all code within WI.activateExtraDomains should be distributed elsewhere.
|
||||
WI.activateExtraDomains(domains);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.NotImplementedError = class NotImplementedError extends Error
|
||||
{
|
||||
constructor(message = "This method is not implemented.")
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
static subclassMustOverride()
|
||||
{
|
||||
return new WI.NotImplementedError("This method must be overridden by a subclass.");
|
||||
}
|
||||
};
|
||||
|
||||
WI.AppControllerBase = class AppControllerBase
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this._initialized = false;
|
||||
|
||||
this._extensionController = new WI.WebInspectorExtensionController;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get debuggableType() { throw WI.NotImplementedError.subclassMustOverride(); }
|
||||
get extensionController() { return this._extensionController; }
|
||||
|
||||
// Since various members of the app controller depend on the global singleton to exist,
|
||||
// some initialization needs to happen after the app controller has been constructed.
|
||||
initialize()
|
||||
{
|
||||
if (this._initialized)
|
||||
throw new Error("App controller is already initialized.");
|
||||
|
||||
this._initialized = true;
|
||||
|
||||
// FIXME: eventually all code within WI.loaded should be distributed elsewhere.
|
||||
WI.loaded();
|
||||
}
|
||||
|
||||
isWebDebuggable()
|
||||
{
|
||||
return this.debuggableType === WI.DebuggableType.Page
|
||||
|| this.debuggableType === WI.DebuggableType.WebPage;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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: ApplicationCacheManager lacks advanced multi-target support. (ApplciationCache objects per-target)
|
||||
|
||||
WI.ApplicationCacheManager = class ApplicationCacheManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["ApplicationCache"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "ApplicationCache");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("ApplicationCache")) {
|
||||
target.ApplicationCacheAgent.enable();
|
||||
target.ApplicationCacheAgent.getFramesWithManifests(this._framesWithManifestsLoaded.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get online() { return this._online; }
|
||||
|
||||
get applicationCacheObjects()
|
||||
{
|
||||
var applicationCacheObjects = [];
|
||||
for (var id in this._applicationCacheObjects)
|
||||
applicationCacheObjects.push(this._applicationCacheObjects[id]);
|
||||
return applicationCacheObjects;
|
||||
}
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._reset();
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY (iOS 13): ApplicationCache.disable did not exist yet.
|
||||
if (target.hasCommand("ApplicationCache.disable"))
|
||||
target.ApplicationCacheAgent.disable();
|
||||
}
|
||||
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
requestApplicationCache(frame, callback)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
function callbackWrapper(error, applicationCache)
|
||||
{
|
||||
if (error) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(applicationCache);
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.ApplicationCacheAgent.getApplicationCacheForFrame(frame.id, callbackWrapper);
|
||||
}
|
||||
|
||||
// ApplicationCacheObserver
|
||||
|
||||
networkStateUpdated(isNowOnline)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
this._online = isNowOnline;
|
||||
|
||||
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.NetworkStateUpdated, {online: this._online});
|
||||
}
|
||||
|
||||
applicationCacheStatusUpdated(frameId, manifestURL, status)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let frame = WI.networkManager.frameForIdentifier(frameId);
|
||||
if (!frame)
|
||||
return;
|
||||
|
||||
this._frameManifestUpdated(frame, manifestURL, status);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_reset()
|
||||
{
|
||||
this._online = true;
|
||||
this._applicationCacheObjects = {};
|
||||
|
||||
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.Cleared);
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (event.target.isMainFrame()) {
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
if (target.hasDomain("ApplicationCache"))
|
||||
target.ApplicationCacheAgent.getManifestForFrame(event.target.id, this._manifestForFrameLoaded.bind(this, event.target.id));
|
||||
}
|
||||
|
||||
_childFrameWasRemoved(event)
|
||||
{
|
||||
this._frameManifestRemoved(event.data.childFrame);
|
||||
}
|
||||
|
||||
_manifestForFrameLoaded(frameId, error, manifestURL)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
var frame = WI.networkManager.frameForIdentifier(frameId);
|
||||
if (!frame)
|
||||
return;
|
||||
|
||||
// A frame can go away between `ApplicationCache.getManifestForFrame` being called and the
|
||||
// response being received.
|
||||
if (error) {
|
||||
WI.reportInternalError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!manifestURL)
|
||||
this._frameManifestRemoved(frame);
|
||||
}
|
||||
|
||||
_framesWithManifestsLoaded(error, framesWithManifests)
|
||||
{
|
||||
if (error) {
|
||||
WI.reportInternalError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < framesWithManifests.length; ++i) {
|
||||
var frame = WI.networkManager.frameForIdentifier(framesWithManifests[i].frameId);
|
||||
if (!frame)
|
||||
continue;
|
||||
|
||||
this._frameManifestUpdated(frame, framesWithManifests[i].manifestURL, framesWithManifests[i].status);
|
||||
}
|
||||
}
|
||||
|
||||
_frameManifestUpdated(frame, manifestURL, status)
|
||||
{
|
||||
if (status === WI.ApplicationCacheManager.Status.Uncached) {
|
||||
this._frameManifestRemoved(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!manifestURL)
|
||||
return;
|
||||
|
||||
var manifestFrame = this._applicationCacheObjects[frame.id];
|
||||
if (manifestFrame && manifestURL !== manifestFrame.manifest.manifestURL)
|
||||
this._frameManifestRemoved(frame);
|
||||
|
||||
var oldStatus = manifestFrame ? manifestFrame.status : -1;
|
||||
var statusChanged = manifestFrame && status !== oldStatus;
|
||||
if (manifestFrame)
|
||||
manifestFrame.status = status;
|
||||
|
||||
if (!this._applicationCacheObjects[frame.id]) {
|
||||
var cacheManifest = new WI.ApplicationCacheManifest(manifestURL);
|
||||
this._applicationCacheObjects[frame.id] = new WI.ApplicationCacheFrame(frame, cacheManifest, status);
|
||||
|
||||
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestAdded, {frameManifest: this._applicationCacheObjects[frame.id]});
|
||||
}
|
||||
|
||||
if (statusChanged)
|
||||
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, {frameManifest: this._applicationCacheObjects[frame.id]});
|
||||
}
|
||||
|
||||
_frameManifestRemoved(frame)
|
||||
{
|
||||
if (!this._applicationCacheObjects[frame.id])
|
||||
return;
|
||||
|
||||
delete this._applicationCacheObjects[frame.id];
|
||||
|
||||
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestRemoved, {frame});
|
||||
}
|
||||
};
|
||||
|
||||
WI.ApplicationCacheManager.Event = {
|
||||
Cleared: "application-cache-manager-cleared",
|
||||
FrameManifestAdded: "application-cache-manager-frame-manifest-added",
|
||||
FrameManifestRemoved: "application-cache-manager-frame-manifest-removed",
|
||||
FrameManifestStatusChanged: "application-cache-manager-frame-manifest-status-changed",
|
||||
NetworkStateUpdated: "application-cache-manager-network-state-updated"
|
||||
};
|
||||
|
||||
WI.ApplicationCacheManager.Status = {
|
||||
Uncached: 0,
|
||||
Idle: 1,
|
||||
Checking: 2,
|
||||
Downloading: 3,
|
||||
UpdateReady: 4,
|
||||
Obsolete: 5
|
||||
};
|
||||
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
WI.AuditManager = class AuditManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._tests = [];
|
||||
this._results = [];
|
||||
|
||||
this._runningState = WI.AuditManager.RunningState.Inactive;
|
||||
this._runningTests = [];
|
||||
|
||||
this._disabledDefaultTestsSetting = new WI.Setting("audit-disabled-default-tests", []);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static synthesizeWarning(message)
|
||||
{
|
||||
message = WI.UIString("Audit Warning: %s").format(message);
|
||||
|
||||
if (window.InspectorTest) {
|
||||
console.warn(message);
|
||||
return;
|
||||
}
|
||||
|
||||
let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Warning, message);
|
||||
consoleMessage.shouldRevealConsole = true;
|
||||
|
||||
WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
|
||||
}
|
||||
|
||||
static synthesizeError(message)
|
||||
{
|
||||
message = WI.UIString("Audit Error: %s").format(message);
|
||||
|
||||
if (window.InspectorTest) {
|
||||
console.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message);
|
||||
consoleMessage.shouldRevealConsole = true;
|
||||
|
||||
WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get tests() { return this._tests; }
|
||||
get results() { return this._results; }
|
||||
get runningState() { return this._runningState; }
|
||||
|
||||
get editing()
|
||||
{
|
||||
return this._runningState === WI.AuditManager.RunningState.Disabled;
|
||||
}
|
||||
|
||||
set editing(editing)
|
||||
{
|
||||
console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
|
||||
if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
|
||||
return;
|
||||
|
||||
let runningState = editing ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
|
||||
console.assert(runningState !== this._runningState);
|
||||
if (runningState === this._runningState)
|
||||
return;
|
||||
|
||||
this._runningState = runningState;
|
||||
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.EditingChanged);
|
||||
|
||||
if (!this.editing) {
|
||||
WI.objectStores.audits.clear();
|
||||
|
||||
let disabledDefaultTests = [];
|
||||
let saveDisabledDefaultTest = (test) => {
|
||||
if (test.supported && test.disabled)
|
||||
disabledDefaultTests.push(test.name);
|
||||
|
||||
if (test instanceof WI.AuditTestGroup) {
|
||||
for (let child of test.tests)
|
||||
saveDisabledDefaultTest(child);
|
||||
}
|
||||
};
|
||||
|
||||
for (let test of this._tests) {
|
||||
if (test.default)
|
||||
saveDisabledDefaultTest(test);
|
||||
else
|
||||
WI.objectStores.audits.putObject(test);
|
||||
}
|
||||
|
||||
this._disabledDefaultTestsSetting.value = disabledDefaultTests;
|
||||
}
|
||||
}
|
||||
|
||||
async start(tests)
|
||||
{
|
||||
console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
|
||||
if (this._runningState !== WI.AuditManager.RunningState.Inactive)
|
||||
return null;
|
||||
|
||||
if (tests && tests.length)
|
||||
tests = tests.filter((test) => typeof test === "object" && test instanceof WI.AuditTestBase);
|
||||
else
|
||||
tests = this._tests;
|
||||
|
||||
console.assert(tests.length);
|
||||
if (!tests.length)
|
||||
return null;
|
||||
|
||||
let mainResource = WI.networkManager.mainFrame.mainResource;
|
||||
|
||||
this._runningState = WI.AuditManager.RunningState.Active;
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
|
||||
|
||||
this._runningTests = tests;
|
||||
for (let test of this._runningTests)
|
||||
test.clearResult();
|
||||
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.TestScheduled);
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
|
||||
await Promise.chain(this._runningTests.map((test) => async () => {
|
||||
if (this._runningState !== WI.AuditManager.RunningState.Active)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("Audit"))
|
||||
await target.AuditAgent.setup();
|
||||
|
||||
let topLevelTest = test.topLevelTest;
|
||||
console.assert(topLevelTest || window.InspectorTest, "No matching top-level test found", test);
|
||||
if (topLevelTest)
|
||||
await topLevelTest.runSetup();
|
||||
|
||||
await test.start();
|
||||
|
||||
if (target.hasDomain("Audit"))
|
||||
await target.AuditAgent.teardown();
|
||||
}));
|
||||
|
||||
let result = this._runningTests.map((test) => test.result).filter((result) => !!result);
|
||||
|
||||
this._runningState = WI.AuditManager.RunningState.Inactive;
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
|
||||
|
||||
this._runningTests = [];
|
||||
|
||||
this._addResult(result);
|
||||
|
||||
if (mainResource !== WI.networkManager.mainFrame.mainResource) {
|
||||
// Navigated while tests were running.
|
||||
for (let test of this._tests)
|
||||
test.clearResult();
|
||||
}
|
||||
|
||||
return this._results.lastValue === result ? result : null;
|
||||
}
|
||||
|
||||
stop()
|
||||
{
|
||||
console.assert(this._runningState === WI.AuditManager.RunningState.Active);
|
||||
if (this._runningState !== WI.AuditManager.RunningState.Active)
|
||||
return;
|
||||
|
||||
this._runningState = WI.AuditManager.RunningState.Stopping;
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
|
||||
|
||||
for (let test of this._runningTests)
|
||||
test.stop();
|
||||
}
|
||||
|
||||
async processJSON({json, error})
|
||||
{
|
||||
if (error) {
|
||||
WI.AuditManager.synthesizeError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof json !== "object" || json === null) {
|
||||
WI.AuditManager.synthesizeError(WI.UIString("invalid JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.type !== WI.AuditTestCase.TypeIdentifier && json.type !== WI.AuditTestGroup.TypeIdentifier
|
||||
&& json.type !== WI.AuditTestCaseResult.TypeIdentifier && json.type !== WI.AuditTestGroupResult.TypeIdentifier) {
|
||||
WI.AuditManager.synthesizeError(WI.UIString("unknown %s \u0022%s\u0022").format(WI.unlocalizedString("type"), json.type));
|
||||
return;
|
||||
}
|
||||
|
||||
let object = await WI.AuditTestGroup.fromPayload(json) || await WI.AuditTestCase.fromPayload(json) || await WI.AuditTestGroupResult.fromPayload(json) || await WI.AuditTestCaseResult.fromPayload(json);
|
||||
if (!object)
|
||||
return;
|
||||
|
||||
if (object instanceof WI.AuditTestBase)
|
||||
this.addTest(object, {save: true});
|
||||
else if (object instanceof WI.AuditTestResultBase)
|
||||
this._addResult(object);
|
||||
|
||||
WI.showRepresentedObject(object);
|
||||
}
|
||||
|
||||
export(saveMode, object)
|
||||
{
|
||||
console.assert(object instanceof WI.AuditTestCase || object instanceof WI.AuditTestGroup || object instanceof WI.AuditTestCaseResult || object instanceof WI.AuditTestGroupResult, object);
|
||||
|
||||
function dataForObject(object) {
|
||||
return {
|
||||
displayType: object instanceof WI.AuditTestResultBase ? WI.UIString("Result") : WI.UIString("Audit"),
|
||||
content: JSON.stringify(object),
|
||||
suggestedName: object.name + (object instanceof WI.AuditTestResultBase ? ".result" : ".audit"),
|
||||
};
|
||||
}
|
||||
|
||||
let data = [dataForObject(object)];
|
||||
|
||||
if (saveMode === WI.FileUtilities.SaveMode.FileVariants && object instanceof WI.AuditTestBase && object.result)
|
||||
data.push(dataForObject(object.result));
|
||||
|
||||
WI.FileUtilities.save(saveMode, data);
|
||||
}
|
||||
|
||||
loadStoredTests()
|
||||
{
|
||||
if (this._tests.length)
|
||||
return;
|
||||
|
||||
this._addDefaultTests();
|
||||
|
||||
WI.objectStores.audits.getAll().then(async (tests) => {
|
||||
for (let payload of tests) {
|
||||
let test = await WI.AuditTestGroup.fromPayload(payload) || await WI.AuditTestCase.fromPayload(payload);
|
||||
if (!test)
|
||||
continue;
|
||||
|
||||
const key = null;
|
||||
WI.objectStores.audits.associateObject(test, key, payload);
|
||||
|
||||
this.addTest(test);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addTest(test, {save} = {})
|
||||
{
|
||||
console.assert(test instanceof WI.AuditTestBase, test);
|
||||
console.assert(!this._tests.includes(test), test);
|
||||
|
||||
this._tests.push(test);
|
||||
|
||||
if (save)
|
||||
WI.objectStores.audits.putObject(test);
|
||||
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
|
||||
}
|
||||
|
||||
removeTest(test)
|
||||
{
|
||||
console.assert(this.editing);
|
||||
console.assert(test instanceof WI.AuditTestBase, test);
|
||||
console.assert(this._tests.includes(test) || test.default, test);
|
||||
|
||||
if (test.default) {
|
||||
test.clearResult();
|
||||
|
||||
if (test.disabled) {
|
||||
InspectorFrontendHost.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
test.disabled = true;
|
||||
|
||||
let disabledTests = this._disabledDefaultTestsSetting.value.slice();
|
||||
disabledTests.push(test.name);
|
||||
this._disabledDefaultTestsSetting.value = disabledTests;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.assert(test.editable, test);
|
||||
|
||||
this._tests.remove(test);
|
||||
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test});
|
||||
|
||||
WI.objectStores.audits.deleteObject(test);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_addResult(result)
|
||||
{
|
||||
if (!result || (Array.isArray(result) && !result.length))
|
||||
return;
|
||||
|
||||
this._results.push(result);
|
||||
|
||||
this.dispatchEventToListeners(WI.AuditManager.Event.TestCompleted, {
|
||||
result,
|
||||
index: this._results.length - 1,
|
||||
});
|
||||
}
|
||||
|
||||
_handleFrameMainResourceDidChange(event)
|
||||
{
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
if (this._runningState === WI.AuditManager.RunningState.Active)
|
||||
this.stop();
|
||||
else {
|
||||
for (let test of this._tests)
|
||||
test.clearResult();
|
||||
}
|
||||
}
|
||||
|
||||
_addDefaultTests()
|
||||
{
|
||||
console.assert(WI.DefaultAudits, "Default audits not loaded.");
|
||||
if (!WI.DefaultAudits)
|
||||
return;
|
||||
|
||||
const defaultTests = [
|
||||
new WI.AuditTestGroup(WI.UIString("Demo Audit"), [
|
||||
new WI.AuditTestGroup(WI.UIString("Result Levels"), [
|
||||
new WI.AuditTestCase("level-pass", WI.DefaultAudits.levelPass.toString(), {description: WI.UIString("This is what the result of a passing test with no data looks like.")}),
|
||||
new WI.AuditTestCase("level-warn", WI.DefaultAudits.levelWarn.toString(), {description: WI.UIString("This is what the result of a warning test with no data looks like.")}),
|
||||
new WI.AuditTestCase("level-fail", WI.DefaultAudits.levelFail.toString(), {description: WI.UIString("This is what the result of a failing test with no data looks like.")}),
|
||||
new WI.AuditTestCase("level-error", WI.DefaultAudits.levelError.toString(), {description: WI.UIString("This is what the result of a test that threw an error with no data looks like.")}),
|
||||
new WI.AuditTestCase("level-unsupported", WI.DefaultAudits.levelUnsupported.toString(), {description: WI.UIString("This is what the result of an unsupported test with no data looks like.")}),
|
||||
], {description: WI.UIString("These are all of the different test result levels.")}),
|
||||
new WI.AuditTestGroup(WI.UIString("Result Data"), [
|
||||
new WI.AuditTestCase("data-domNodes", WI.DefaultAudits.dataDOMNodes.toString(), {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with the <body> element.")}),
|
||||
new WI.AuditTestCase("data-domAttributes", WI.DefaultAudits.dataDOMAttributes.toString(), {description: WI.UIString("This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.")}),
|
||||
new WI.AuditTestCase("data-errors", WI.DefaultAudits.dataErrors.toString(), {description: WI.UIString("This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.")}),
|
||||
new WI.AuditTestCase("data-custom", WI.DefaultAudits.dataCustom.toString(), {description: WI.UIString("This is an example of how custom result data is shown."), supports: 3}),
|
||||
], {description: WI.UIString("These are example tests that demonstrate all of the different types of data that can be returned with the test result.")}),
|
||||
new WI.AuditTestGroup(WI.UIString("Specially Exposed Data"), [
|
||||
new WI.AuditTestGroup(WI.UIString("Accessibility"), [
|
||||
new WI.AuditTestCase("getElementsByComputedRole", WI.DefaultAudits.getElementsByComputedRole.toString(), {description: WI.UIString("This is an example test that uses %s to find elements with a computed role of \u201Clink\u201D.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getElementsByComputedRole")), supports: 1}),
|
||||
new WI.AuditTestCase("getActiveDescendant", WI.DefaultAudits.getActiveDescendant.toString(), {description: WI.UIString("This is an example test that uses %s to find any element that meets criteria for active descendant (\u201C%s\u201D) of the <body> element, if it exists.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getActiveDescendant"), WI.unlocalizedString("aria-activedescendant")), supports: 1}),
|
||||
new WI.AuditTestCase("getChildNodes", WI.DefaultAudits.getChildNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find child nodes of the <body> element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getChildNodes")), supports: 1}),
|
||||
new WI.AuditTestCase("getComputedProperties", WI.DefaultAudits.getComputedProperties.toString(), {description: WI.UIString("This is an example test that uses %s to find a variety of accessibility information about the <body> element.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getComputedProperties")), supports: 3}),
|
||||
new WI.AuditTestCase("getControlledNodes", WI.DefaultAudits.getControlledNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes controlled (\u201C%s\u201D) by the <body> element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getControlledNodes"), WI.unlocalizedString("aria-controls")), supports: 1}),
|
||||
new WI.AuditTestCase("getFlowedNodes", WI.DefaultAudits.getFlowedNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes flowed to (\u201C%s\u201D) from the <body> element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getFlowedNodes"), WI.unlocalizedString("aria-flowto")), supports: 1}),
|
||||
new WI.AuditTestCase("getMouseEventNode", WI.DefaultAudits.getMouseEventNode.toString(), {description: WI.UIString("This is an example test that uses %s to find the node that would handle mouse events for the <body> element, if applicable.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getMouseEventNode")), supports: 1}),
|
||||
new WI.AuditTestCase("getOwnedNodes", WI.DefaultAudits.getOwnedNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes owned (\u201C%s\u201D) by the <body> element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getOwnedNodes"), WI.unlocalizedString("aria-owns")), supports: 1}),
|
||||
new WI.AuditTestCase("getParentNode", WI.DefaultAudits.getParentNode.toString(), {description: WI.UIString("This is an example test that uses %s to find the parent node of the <body> element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getParentNode")), supports: 1}),
|
||||
new WI.AuditTestCase("getSelectedChildNodes", WI.DefaultAudits.getSelectedChildNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all child nodes that are selected (\u201C%s\u201D) of the <body> element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getSelectedChildNodes"), WI.unlocalizedString("aria-selected")), supports: 1}),
|
||||
], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility")), supports: 1}),
|
||||
new WI.AuditTestGroup(WI.UIString("DOM"), [
|
||||
new WI.AuditTestCase("hasEventListeners", WI.DefaultAudits.hasEventListeners.toString(), {description: WI.UIString("This is an example test that uses %s to find data indicating whether the <body> element has any event listeners.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.hasEventListeners")), supports: 3}),
|
||||
new WI.AuditTestCase("hasEventListeners-click", WI.DefaultAudits.hasEventListenersClick.toString(), {description: WI.UIString("This is an example test that uses %s to find data indicating whether the <body> element has any click event listeners.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.hasEventListenersClick")), supports: 3}),
|
||||
], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about DOM nodes.").format(WI.unlocalizedString("WebInspectorAudit.DOM")), supports: 1}),
|
||||
new WI.AuditTestGroup(WI.UIString("Resources"), [
|
||||
new WI.AuditTestCase("getResources", WI.DefaultAudits.getResources.toString(), {description: WI.UIString("This is an example test that uses %s to find basic information about each resource.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getResources")), supports: 3}),
|
||||
new WI.AuditTestCase("getResourceContent", WI.DefaultAudits.getResourceContent.toString(), {description: WI.UIString("This is an example test that uses %s to find the contents of the main resource.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getResourceContent")), supports: 3}),
|
||||
], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about loaded resources.").format(WI.unlocalizedString("WebInspectorAudit.Resources")), supports: 2}),
|
||||
], {description: WI.UIString("These are example tests that demonstrate how to use %s to access information not normally available to JavaScript.").format(WI.unlocalizedString("WebInspectorAudit")), supports: 1}),
|
||||
new WI.AuditTestCase("unsupported", WI.DefaultAudits.unsupported.toString(), {description: WI.UIString("This is an example of a test that will not run because it is unsupported."), supports: Infinity}),
|
||||
], {description: WI.UIString("These are example tests that demonstrate the functionality and structure of audits.")}),
|
||||
new WI.AuditTestGroup(WI.UIString("Accessibility"), [
|
||||
new WI.AuditTestCase("testMenuRoleForRequiredChildren", WI.DefaultAudits.testMenuRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D and \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("menu"), WI.unlocalizedString("menubar")), supports: 1}),
|
||||
new WI.AuditTestCase("testGridRoleForRequiredChildren", WI.DefaultAudits.testGridRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("grid")), supports: 1}),
|
||||
new WI.AuditTestCase("testForAriaLabelledBySpelling", WI.DefaultAudits.testForAriaLabelledBySpelling.toString(), {description: WI.UIString("Ensure that \u201C%s\u201D is spelled correctly.").format(WI.unlocalizedString("aria-labelledby")), supports: 1}),
|
||||
new WI.AuditTestCase("testForMultipleBanners", WI.DefaultAudits.testForMultipleBanners.toString(), {description: WI.UIString("Ensure that only one banner is used on the page."), supports: 1}),
|
||||
new WI.AuditTestCase("testForLinkLabels", WI.DefaultAudits.testForLinkLabels.toString(), {description: WI.UIString("Ensure that links have accessible labels for assistive technology."), supports: 1}),
|
||||
new WI.AuditTestCase("testRowGroupRoleForRequiredChildren", WI.DefaultAudits.testRowGroupRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("rowgroup")), supports: 1}),
|
||||
new WI.AuditTestCase("testTableRoleForRequiredChildren", WI.DefaultAudits.testTableRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("table")), supports: 1}),
|
||||
new WI.AuditTestCase("testForMultipleLiveRegions", WI.DefaultAudits.testForMultipleLiveRegions.toString(), {description: WI.UIString("Ensure that only one live region is used on the page."), supports: 1}),
|
||||
new WI.AuditTestCase("testListBoxRoleForRequiredChildren", WI.DefaultAudits.testListBoxRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("listbox")), supports: 1}),
|
||||
new WI.AuditTestCase("testImageLabels", WI.DefaultAudits.testImageLabels.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have accessible labels for assistive technology.").format(WI.unlocalizedString("img")), supports: 1}),
|
||||
new WI.AuditTestCase("testForAriaHiddenFalse", WI.DefaultAudits.testForAriaHiddenFalse.toString(), {description: WI.UIString("Ensure aria-hidden=\u0022%s\u0022 is not used.").format(WI.unlocalizedString("false")), supports: 1}),
|
||||
new WI.AuditTestCase("testTreeRoleForRequiredChildren", WI.DefaultAudits.testTreeRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("tree")), supports: 1}),
|
||||
new WI.AuditTestCase("testRadioGroupRoleForRequiredChildren", WI.DefaultAudits.testRadioGroupRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("radiogroup")), supports: 1}),
|
||||
new WI.AuditTestCase("testFeedRoleForRequiredChildren", WI.DefaultAudits.testFeedRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("feed")), supports: 1}),
|
||||
new WI.AuditTestCase("testTabListRoleForRequiredChildren", WI.DefaultAudits.testTabListRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("tablist")), supports: 1}),
|
||||
new WI.AuditTestCase("testButtonLabels", WI.DefaultAudits.testButtonLabels.toString(), {description: WI.UIString("Ensure that buttons have accessible labels for assistive technology."), supports: 1}),
|
||||
new WI.AuditTestCase("testRowRoleForRequiredChildren", WI.DefaultAudits.testRowRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("row")), supports: 1}),
|
||||
new WI.AuditTestCase("testListRoleForRequiredChildren", WI.DefaultAudits.testListRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("list")), supports: 1}),
|
||||
new WI.AuditTestCase("testComboBoxRoleForRequiredChildren", WI.DefaultAudits.testComboBoxRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("combobox")), supports: 1}),
|
||||
new WI.AuditTestCase("testForMultipleMainContentSections", WI.DefaultAudits.testForMultipleMainContentSections.toString(), {description: WI.UIString("Ensure that only one main content section is used on the page."), supports: 1}),
|
||||
new WI.AuditTestCase("testDialogsForLabels", WI.DefaultAudits.testDialogsForLabels.toString(), {description: WI.UIString("Ensure that dialogs have accessible labels for assistive technology."), supports: 1}),
|
||||
new WI.AuditTestCase("testForInvalidAriaHiddenValue", WI.DefaultAudits.testForInvalidAriaHiddenValue.toString(), {description: WI.UIString("Ensure that values for \u201C%s\u201D are valid.").format(WI.unlocalizedString("aria-hidden")), supports: 1})
|
||||
], {description: WI.UIString("Diagnoses common accessibility problems affecting screen readers and other assistive technology.")}),
|
||||
];
|
||||
|
||||
let checkDisabledDefaultTest = (test) => {
|
||||
test.markAsDefault();
|
||||
|
||||
if (this._disabledDefaultTestsSetting.value.includes(test.name))
|
||||
test.disabled = true;
|
||||
|
||||
if (test instanceof WI.AuditTestGroup) {
|
||||
for (let child of test.tests)
|
||||
checkDisabledDefaultTest(child);
|
||||
}
|
||||
};
|
||||
|
||||
for (let test of defaultTests) {
|
||||
checkDisabledDefaultTest(test);
|
||||
|
||||
this.addTest(test);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WI.AuditManager.RunningState = {
|
||||
Disabled: "disabled",
|
||||
Inactive: "inactive",
|
||||
Active: "active",
|
||||
Stopping: "stopping",
|
||||
};
|
||||
|
||||
WI.AuditManager.Event = {
|
||||
EditingChanged: "audit-manager-editing-changed",
|
||||
RunningStateChanged: "audit-manager-running-state-changed",
|
||||
TestAdded: "audit-manager-test-added",
|
||||
TestCompleted: "audit-manager-test-completed",
|
||||
TestRemoved: "audit-manager-test-removed",
|
||||
TestScheduled: "audit-manager-test-scheduled",
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2015 Saam Barati <saambarati1@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.BasicBlockAnnotator = class BasicBlockAnnotator extends WI.Annotator
|
||||
{
|
||||
constructor(sourceCodeTextEditor, script)
|
||||
{
|
||||
super(sourceCodeTextEditor);
|
||||
|
||||
this._script = script;
|
||||
this._basicBlockMarkers = new Map; // Only contains unexecuted basic blocks.
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
clearAnnotations()
|
||||
{
|
||||
for (var key of this._basicBlockMarkers.keys())
|
||||
this._clearRangeForBasicBlockMarker(key);
|
||||
}
|
||||
|
||||
insertAnnotations()
|
||||
{
|
||||
if (!this.isActive())
|
||||
return;
|
||||
this._script.requestContent().then(this._annotateBasicBlockExecutionRanges.bind(this));
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_calculateBasicBlockPositions(basicBlocks, content)
|
||||
{
|
||||
if (!basicBlocks || !basicBlocks.length)
|
||||
return;
|
||||
|
||||
let lineEndings = [];
|
||||
let lineEndingLengths = [];
|
||||
let pattern = /\r\n?|\n/g;
|
||||
let match = pattern.exec(content);
|
||||
while (match) {
|
||||
lineEndings.push(match.index);
|
||||
lineEndingLengths.push(match[0].length);
|
||||
match = pattern.exec(content);
|
||||
}
|
||||
|
||||
function offsetToPosition(offset) {
|
||||
let lineNumber = lineEndings.upperBound(offset - 1);
|
||||
if (lineNumber) {
|
||||
let previousLine = lineNumber - 1;
|
||||
var columnNumber = offset - lineEndings[previousLine] - lineEndingLengths[previousLine];
|
||||
} else
|
||||
var columnNumber = offset;
|
||||
return new WI.SourceCodePosition(lineNumber, columnNumber);
|
||||
}
|
||||
|
||||
for (let block of basicBlocks) {
|
||||
block.startPosition = offsetToPosition(block.startOffset);
|
||||
block.endPosition = offsetToPosition(block.endOffset);
|
||||
}
|
||||
}
|
||||
|
||||
_annotateBasicBlockExecutionRanges()
|
||||
{
|
||||
let content = this._script.content;
|
||||
console.assert(content, "Missing script content for basic block annotations.");
|
||||
if (!content)
|
||||
return;
|
||||
|
||||
var sourceID = this._script.id;
|
||||
var startTime = Date.now();
|
||||
|
||||
this._script.target.RuntimeAgent.getBasicBlocks(sourceID, function(error, basicBlocks) {
|
||||
if (error) {
|
||||
console.error("Error in getting basic block locations: " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isActive())
|
||||
return;
|
||||
|
||||
this._calculateBasicBlockPositions(basicBlocks, content);
|
||||
|
||||
let {startPosition, endPosition} = this.sourceCodeTextEditor.visibleRangePositions();
|
||||
basicBlocks = basicBlocks.filter(function(block) {
|
||||
// Viewport: [--]
|
||||
// Block: [--]
|
||||
if (block.startPosition.isAfter(endPosition))
|
||||
return false;
|
||||
|
||||
// Viewport: [--]
|
||||
// Block: [--]
|
||||
if (block.endPosition.isBefore(startPosition))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
for (var block of basicBlocks) {
|
||||
var key = block.startOffset + ":" + block.endOffset;
|
||||
var hasKey = this._basicBlockMarkers.has(key);
|
||||
var hasExecuted = block.hasExecuted;
|
||||
if (hasKey && hasExecuted)
|
||||
this._clearRangeForBasicBlockMarker(key);
|
||||
else if (!hasKey && !hasExecuted) {
|
||||
var marker = this._highlightTextForBasicBlock(block);
|
||||
this._basicBlockMarkers.set(key, marker);
|
||||
}
|
||||
}
|
||||
|
||||
var totalTime = Date.now() - startTime;
|
||||
var timeoutTime = Number.constrain(30 * totalTime, 500, 5000);
|
||||
this._timeoutIdentifier = setTimeout(this.insertAnnotations.bind(this), timeoutTime);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
_highlightTextForBasicBlock(basicBlock)
|
||||
{
|
||||
console.assert(basicBlock.startOffset <= basicBlock.endOffset && basicBlock.startOffset >= 0 && basicBlock.endOffset >= 0, "" + basicBlock.startOffset + ":" + basicBlock.endOffset);
|
||||
console.assert(!basicBlock.hasExecuted);
|
||||
|
||||
let startPosition = this.sourceCodeTextEditor.originalPositionToCurrentPosition(basicBlock.startPosition);
|
||||
let endPosition = this.sourceCodeTextEditor.originalPositionToCurrentPosition(basicBlock.endPosition);
|
||||
if (this._isTextRangeOnlyClosingBrace(startPosition, endPosition))
|
||||
return null;
|
||||
|
||||
return this.sourceCodeTextEditor.addStyleToTextRange(startPosition, endPosition, WI.BasicBlockAnnotator.HasNotExecutedClassName);
|
||||
}
|
||||
|
||||
_isTextRangeOnlyClosingBrace(startPosition, endPosition)
|
||||
{
|
||||
var isOnlyClosingBrace = /^\s*\}$/;
|
||||
return isOnlyClosingBrace.test(this.sourceCodeTextEditor.getTextInRange(startPosition, endPosition));
|
||||
}
|
||||
|
||||
_clearRangeForBasicBlockMarker(key)
|
||||
{
|
||||
console.assert(this._basicBlockMarkers.has(key));
|
||||
var marker = this._basicBlockMarkers.get(key);
|
||||
if (marker)
|
||||
marker.clear();
|
||||
this._basicBlockMarkers.delete(key);
|
||||
}
|
||||
};
|
||||
|
||||
WI.BasicBlockAnnotator.HasNotExecutedClassName = "basic-block-has-not-executed";
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.BranchManager = class BranchManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
initialize()
|
||||
{
|
||||
this._originalBranch = new WI.Branch(WI.UIString("Original"), null, true);
|
||||
this._currentBranch = this._originalBranch.fork(WI.UIString("Working Copy"));
|
||||
this._branches = [this._originalBranch, this._currentBranch];
|
||||
}
|
||||
|
||||
get branches()
|
||||
{
|
||||
return this._branches;
|
||||
}
|
||||
|
||||
get currentBranch()
|
||||
{
|
||||
return this._currentBranch;
|
||||
}
|
||||
|
||||
set currentBranch(branch)
|
||||
{
|
||||
console.assert(branch instanceof WI.Branch);
|
||||
if (!(branch instanceof WI.Branch))
|
||||
return;
|
||||
|
||||
this._currentBranch.revert();
|
||||
|
||||
this._currentBranch = branch;
|
||||
|
||||
this._currentBranch.apply();
|
||||
}
|
||||
|
||||
createBranch(displayName, fromBranch)
|
||||
{
|
||||
if (!fromBranch)
|
||||
fromBranch = this._originalBranch;
|
||||
|
||||
console.assert(fromBranch instanceof WI.Branch);
|
||||
if (!(fromBranch instanceof WI.Branch))
|
||||
return null;
|
||||
|
||||
var newBranch = fromBranch.fork(displayName);
|
||||
this._branches.push(newBranch);
|
||||
return newBranch;
|
||||
}
|
||||
|
||||
deleteBranch(branch)
|
||||
{
|
||||
console.assert(branch instanceof WI.Branch);
|
||||
if (!(branch instanceof WI.Branch))
|
||||
return;
|
||||
|
||||
console.assert(branch !== this._originalBranch);
|
||||
if (branch === this._originalBranch)
|
||||
return;
|
||||
|
||||
this._branches.remove(branch);
|
||||
|
||||
if (branch === this._currentBranch)
|
||||
this._currentBranch = this._originalBranch;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.BreakpointLogMessageLexer = class BreakpointLogMessageLexer extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._stateFunctions = {
|
||||
[WI.BreakpointLogMessageLexer.State.Expression]: this._expression,
|
||||
[WI.BreakpointLogMessageLexer.State.PlainText]: this._plainText,
|
||||
[WI.BreakpointLogMessageLexer.State.PossiblePlaceholder]: this._possiblePlaceholder,
|
||||
[WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral]: this._regExpOrStringLiteral,
|
||||
};
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
tokenize(input)
|
||||
{
|
||||
this.reset();
|
||||
this._input = input;
|
||||
|
||||
while (this._index < this._input.length) {
|
||||
let stateFunction = this._stateFunctions[this._states.lastValue];
|
||||
console.assert(stateFunction);
|
||||
if (!stateFunction) {
|
||||
this.reset();
|
||||
return null;
|
||||
}
|
||||
|
||||
stateFunction.call(this);
|
||||
}
|
||||
|
||||
// Needed for trailing plain text.
|
||||
this._finishPlainText();
|
||||
|
||||
return this._tokens;
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this._input = "";
|
||||
this._buffer = "";
|
||||
|
||||
this._index = 0;
|
||||
this._states = [WI.BreakpointLogMessageLexer.State.PlainText];
|
||||
this._literalStartCharacter = "";
|
||||
this._curlyBraceDepth = 0;
|
||||
this._tokens = [];
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_finishPlainText()
|
||||
{
|
||||
this._appendToken(WI.BreakpointLogMessageLexer.TokenType.PlainText);
|
||||
}
|
||||
|
||||
_finishExpression()
|
||||
{
|
||||
this._appendToken(WI.BreakpointLogMessageLexer.TokenType.Expression);
|
||||
}
|
||||
|
||||
_appendToken(type)
|
||||
{
|
||||
if (!this._buffer)
|
||||
return;
|
||||
|
||||
this._tokens.push({type, data: this._buffer});
|
||||
this._buffer = "";
|
||||
}
|
||||
|
||||
_consume()
|
||||
{
|
||||
console.assert(this._index < this._input.length);
|
||||
|
||||
let character = this._peek();
|
||||
this._index++;
|
||||
return character;
|
||||
}
|
||||
|
||||
_peek()
|
||||
{
|
||||
return this._input[this._index] || null;
|
||||
}
|
||||
|
||||
// States
|
||||
|
||||
_expression()
|
||||
{
|
||||
let character = this._consume();
|
||||
|
||||
if (character === "}") {
|
||||
if (this._curlyBraceDepth === 0) {
|
||||
this._finishExpression();
|
||||
|
||||
console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.Expression);
|
||||
this._states.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
this._curlyBraceDepth--;
|
||||
}
|
||||
|
||||
this._buffer += character;
|
||||
|
||||
if (character === "/" || character === "\"" || character === "'") {
|
||||
this._literalStartCharacter = character;
|
||||
this._states.push(WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral);
|
||||
} else if (character === "{")
|
||||
this._curlyBraceDepth++;
|
||||
}
|
||||
|
||||
_plainText()
|
||||
{
|
||||
let character = this._peek();
|
||||
|
||||
if (character === "$")
|
||||
this._states.push(WI.BreakpointLogMessageLexer.State.PossiblePlaceholder);
|
||||
else {
|
||||
this._buffer += character;
|
||||
this._consume();
|
||||
}
|
||||
}
|
||||
|
||||
_possiblePlaceholder()
|
||||
{
|
||||
let character = this._consume();
|
||||
console.assert(character === "$");
|
||||
let nextCharacter = this._peek();
|
||||
|
||||
console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.PossiblePlaceholder);
|
||||
this._states.pop();
|
||||
|
||||
if (nextCharacter === "{") {
|
||||
this._finishPlainText();
|
||||
this._consume();
|
||||
this._states.push(WI.BreakpointLogMessageLexer.State.Expression);
|
||||
} else
|
||||
this._buffer += character;
|
||||
}
|
||||
|
||||
_regExpOrStringLiteral()
|
||||
{
|
||||
let character = this._consume();
|
||||
this._buffer += character;
|
||||
|
||||
if (character === "\\") {
|
||||
if (this._peek() !== null)
|
||||
this._buffer += this._consume();
|
||||
return;
|
||||
}
|
||||
|
||||
if (character === this._literalStartCharacter) {
|
||||
console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral);
|
||||
this._states.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WI.BreakpointLogMessageLexer.State = {
|
||||
Expression: Symbol("expression"),
|
||||
PlainText: Symbol("plain-text"),
|
||||
PossiblePlaceholder: Symbol("possible-placeholder"),
|
||||
RegExpOrStringLiteral: Symbol("regexp-or-string-literal"),
|
||||
};
|
||||
|
||||
WI.BreakpointLogMessageLexer.TokenType = {
|
||||
PlainText: "token-type-plain-text",
|
||||
Expression: "token-type-expression",
|
||||
};
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.BreakpointPopoverController = class BreakpointPopoverController extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._breakpoint = null;
|
||||
this._popover = null;
|
||||
this._popoverContentElement = null;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
appendContextMenuItems(contextMenu, breakpoint, breakpointDisplayElement)
|
||||
{
|
||||
console.assert(document.body.contains(breakpointDisplayElement), "Breakpoint popover display element must be in the DOM.");
|
||||
|
||||
const editBreakpoint = () => {
|
||||
console.assert(!this._popover, "Breakpoint popover already exists.");
|
||||
if (this._popover)
|
||||
return;
|
||||
|
||||
this._createPopoverContent(breakpoint);
|
||||
this._popover = new WI.Popover(this);
|
||||
this._popover.content = this._popoverContentElement;
|
||||
|
||||
let bounds = WI.Rect.rectFromClientRect(breakpointDisplayElement.getBoundingClientRect());
|
||||
bounds.origin.x -= 1; // Move the anchor left one pixel so it looks more centered.
|
||||
this._popover.present(bounds.pad(2), [WI.RectEdge.MAX_Y]);
|
||||
};
|
||||
|
||||
const removeBreakpoint = () => {
|
||||
WI.debuggerManager.removeBreakpoint(breakpoint);
|
||||
};
|
||||
|
||||
const toggleBreakpoint = () => {
|
||||
breakpoint.disabled = !breakpoint.disabled;
|
||||
};
|
||||
|
||||
const toggleAutoContinue = () => {
|
||||
breakpoint.autoContinue = !breakpoint.autoContinue;
|
||||
};
|
||||
|
||||
const revealOriginalSourceCodeLocation = () => {
|
||||
const options = {
|
||||
ignoreNetworkTab: true,
|
||||
ignoreSearchTab: true,
|
||||
};
|
||||
WI.showOriginalOrFormattedSourceCodeLocation(breakpoint.sourceCodeLocation, options);
|
||||
};
|
||||
|
||||
if (WI.debuggerManager.isBreakpointEditable(breakpoint))
|
||||
contextMenu.appendItem(WI.UIString("Edit Breakpoint\u2026"), editBreakpoint);
|
||||
|
||||
if (breakpoint.autoContinue && !breakpoint.disabled) {
|
||||
contextMenu.appendItem(WI.UIString("Disable Breakpoint"), toggleBreakpoint);
|
||||
contextMenu.appendItem(WI.UIString("Cancel Automatic Continue"), toggleAutoContinue);
|
||||
} else if (!breakpoint.disabled)
|
||||
contextMenu.appendItem(WI.UIString("Disable Breakpoint"), toggleBreakpoint);
|
||||
else
|
||||
contextMenu.appendItem(WI.UIString("Enable Breakpoint"), toggleBreakpoint);
|
||||
|
||||
if (!breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length)
|
||||
contextMenu.appendItem(WI.UIString("Set to Automatically Continue"), toggleAutoContinue);
|
||||
|
||||
if (WI.debuggerManager.isBreakpointRemovable(breakpoint))
|
||||
contextMenu.appendItem(WI.UIString("Delete Breakpoint"), removeBreakpoint);
|
||||
|
||||
if (breakpoint._sourceCodeLocation.hasMappedLocation()) {
|
||||
contextMenu.appendSeparator();
|
||||
contextMenu.appendItem(WI.UIString("Reveal in Original Resource"), revealOriginalSourceCodeLocation);
|
||||
}
|
||||
}
|
||||
|
||||
// CodeMirrorCompletionController delegate
|
||||
|
||||
completionControllerShouldAllowEscapeCompletion()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_createPopoverContent(breakpoint)
|
||||
{
|
||||
console.assert(!this._popoverContentElement, "Popover content element already exists.");
|
||||
if (this._popoverContentElement)
|
||||
return;
|
||||
|
||||
this._breakpoint = breakpoint;
|
||||
this._popoverContentElement = document.createElement("div");
|
||||
this._popoverContentElement.className = "edit-breakpoint-popover-content";
|
||||
|
||||
let checkboxElement = document.createElement("input");
|
||||
checkboxElement.type = "checkbox";
|
||||
checkboxElement.checked = !this._breakpoint.disabled;
|
||||
checkboxElement.addEventListener("change", this._popoverToggleEnabledCheckboxChanged.bind(this));
|
||||
|
||||
let checkboxLabel = document.createElement("label");
|
||||
checkboxLabel.className = "toggle";
|
||||
checkboxLabel.appendChild(checkboxElement);
|
||||
checkboxLabel.append(this._breakpoint.sourceCodeLocation.displayLocationString());
|
||||
|
||||
let table = document.createElement("table");
|
||||
|
||||
let conditionRow = table.appendChild(document.createElement("tr"));
|
||||
let conditionHeader = conditionRow.appendChild(document.createElement("th"));
|
||||
let conditionData = conditionRow.appendChild(document.createElement("td"));
|
||||
let conditionLabel = conditionHeader.appendChild(document.createElement("label"));
|
||||
conditionLabel.textContent = WI.UIString("Condition");
|
||||
let conditionEditorElement = conditionData.appendChild(document.createElement("div"));
|
||||
conditionEditorElement.classList.add("edit-breakpoint-popover-condition", WI.SyntaxHighlightedStyleClassName);
|
||||
|
||||
this._conditionCodeMirror = WI.CodeMirrorEditor.create(conditionEditorElement, {
|
||||
extraKeys: {Tab: false},
|
||||
lineWrapping: false,
|
||||
mode: "text/javascript",
|
||||
matchBrackets: true,
|
||||
placeholder: WI.UIString("Conditional expression"),
|
||||
scrollbarStyle: null,
|
||||
value: this._breakpoint.condition || "",
|
||||
});
|
||||
|
||||
let conditionCodeMirrorInputField = this._conditionCodeMirror.getInputField();
|
||||
conditionCodeMirrorInputField.id = "codemirror-condition-input-field";
|
||||
conditionLabel.setAttribute("for", conditionCodeMirrorInputField.id);
|
||||
|
||||
this._conditionCodeMirrorEscapeOrEnterKeyHandler = this._conditionCodeMirrorEscapeOrEnterKey.bind(this);
|
||||
this._conditionCodeMirror.addKeyMap({
|
||||
"Esc": this._conditionCodeMirrorEscapeOrEnterKeyHandler,
|
||||
"Enter": this._conditionCodeMirrorEscapeOrEnterKeyHandler,
|
||||
});
|
||||
|
||||
this._conditionCodeMirror.on("change", this._conditionCodeMirrorChanged.bind(this));
|
||||
this._conditionCodeMirror.on("beforeChange", this._conditionCodeMirrorBeforeChange.bind(this));
|
||||
|
||||
let completionController = new WI.CodeMirrorCompletionController(this._conditionCodeMirror, this);
|
||||
completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider);
|
||||
|
||||
// CodeMirror needs a refresh after the popover displays, to layout, otherwise it doesn't appear.
|
||||
setTimeout(() => {
|
||||
this._conditionCodeMirror.refresh();
|
||||
this._conditionCodeMirror.focus();
|
||||
}, 0);
|
||||
|
||||
// COMPATIBILITY (iOS 9): Legacy backends don't support breakpoint ignore count. Since support
|
||||
// can't be tested directly, check for CSS.getSupportedSystemFontFamilyNames.
|
||||
// FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed.
|
||||
if (InspectorBackend.domains.CSS.getSupportedSystemFontFamilyNames) {
|
||||
let ignoreCountRow = table.appendChild(document.createElement("tr"));
|
||||
let ignoreCountHeader = ignoreCountRow.appendChild(document.createElement("th"));
|
||||
let ignoreCountLabel = ignoreCountHeader.appendChild(document.createElement("label"));
|
||||
let ignoreCountData = ignoreCountRow.appendChild(document.createElement("td"));
|
||||
this._ignoreCountInput = ignoreCountData.appendChild(document.createElement("input"));
|
||||
this._ignoreCountInput.id = "edit-breakpoint-popover-ignore";
|
||||
this._ignoreCountInput.type = "number";
|
||||
this._ignoreCountInput.min = 0;
|
||||
this._ignoreCountInput.value = 0;
|
||||
this._ignoreCountInput.addEventListener("change", this._popoverIgnoreInputChanged.bind(this));
|
||||
|
||||
ignoreCountLabel.setAttribute("for", this._ignoreCountInput.id);
|
||||
ignoreCountLabel.textContent = WI.UIString("Ignore");
|
||||
|
||||
this._ignoreCountText = ignoreCountData.appendChild(document.createElement("span"));
|
||||
this._updateIgnoreCountText();
|
||||
}
|
||||
|
||||
let actionRow = table.appendChild(document.createElement("tr"));
|
||||
let actionHeader = actionRow.appendChild(document.createElement("th"));
|
||||
let actionData = this._actionsContainer = actionRow.appendChild(document.createElement("td"));
|
||||
let actionLabel = actionHeader.appendChild(document.createElement("label"));
|
||||
actionLabel.textContent = WI.UIString("Action");
|
||||
|
||||
if (!this._breakpoint.actions.length)
|
||||
this._popoverActionsCreateAddActionButton();
|
||||
else {
|
||||
this._popoverContentElement.classList.add(WI.BreakpointPopoverController.WidePopoverClassName);
|
||||
for (let i = 0; i < this._breakpoint.actions.length; ++i) {
|
||||
let breakpointActionView = new WI.BreakpointActionView(this._breakpoint.actions[i], this, true);
|
||||
this._popoverActionsInsertBreakpointActionView(breakpointActionView, i);
|
||||
}
|
||||
}
|
||||
|
||||
let optionsRow = this._popoverOptionsRowElement = table.appendChild(document.createElement("tr"));
|
||||
if (!this._breakpoint.actions.length)
|
||||
optionsRow.classList.add(WI.BreakpointPopoverController.HiddenStyleClassName);
|
||||
let optionsHeader = optionsRow.appendChild(document.createElement("th"));
|
||||
let optionsData = optionsRow.appendChild(document.createElement("td"));
|
||||
let optionsLabel = optionsHeader.appendChild(document.createElement("label"));
|
||||
let optionsCheckbox = this._popoverOptionsCheckboxElement = optionsData.appendChild(document.createElement("input"));
|
||||
let optionsCheckboxLabel = optionsData.appendChild(document.createElement("label"));
|
||||
optionsCheckbox.id = "edit-breakpoint-popover-auto-continue";
|
||||
optionsCheckbox.type = "checkbox";
|
||||
optionsCheckbox.checked = this._breakpoint.autoContinue;
|
||||
optionsCheckbox.addEventListener("change", this._popoverToggleAutoContinueCheckboxChanged.bind(this));
|
||||
optionsLabel.textContent = WI.UIString("Options");
|
||||
optionsCheckboxLabel.setAttribute("for", optionsCheckbox.id);
|
||||
optionsCheckboxLabel.textContent = WI.UIString("Automatically continue after evaluating");
|
||||
|
||||
this._popoverContentElement.appendChild(checkboxLabel);
|
||||
this._popoverContentElement.appendChild(table);
|
||||
}
|
||||
|
||||
_popoverToggleEnabledCheckboxChanged(event)
|
||||
{
|
||||
this._breakpoint.disabled = !event.target.checked;
|
||||
}
|
||||
|
||||
_conditionCodeMirrorChanged(codeMirror, change)
|
||||
{
|
||||
this._breakpoint.condition = (codeMirror.getValue() || "").trim();
|
||||
}
|
||||
|
||||
_conditionCodeMirrorBeforeChange(codeMirror, change)
|
||||
{
|
||||
if (change.update) {
|
||||
let newText = change.text.join("").replace(/\n/g, "");
|
||||
change.update(change.from, change.to, [newText]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_conditionCodeMirrorEscapeOrEnterKey()
|
||||
{
|
||||
if (!this._popover)
|
||||
return;
|
||||
|
||||
this._popover.dismiss();
|
||||
}
|
||||
|
||||
_popoverIgnoreInputChanged(event)
|
||||
{
|
||||
let ignoreCount = 0;
|
||||
if (event.target.value) {
|
||||
ignoreCount = parseInt(event.target.value, 10);
|
||||
if (isNaN(ignoreCount) || ignoreCount < 0)
|
||||
ignoreCount = 0;
|
||||
}
|
||||
|
||||
this._ignoreCountInput.value = ignoreCount;
|
||||
this._breakpoint.ignoreCount = ignoreCount;
|
||||
|
||||
this._updateIgnoreCountText();
|
||||
}
|
||||
|
||||
_popoverToggleAutoContinueCheckboxChanged(event)
|
||||
{
|
||||
this._breakpoint.autoContinue = event.target.checked;
|
||||
}
|
||||
|
||||
_popoverActionsCreateAddActionButton()
|
||||
{
|
||||
this._popoverContentElement.classList.remove(WI.BreakpointPopoverController.WidePopoverClassName);
|
||||
this._actionsContainer.removeChildren();
|
||||
|
||||
let addActionButton = this._actionsContainer.appendChild(document.createElement("button"));
|
||||
addActionButton.textContent = WI.UIString("Add Action");
|
||||
addActionButton.addEventListener("click", this._popoverActionsAddActionButtonClicked.bind(this));
|
||||
}
|
||||
|
||||
_popoverActionsAddActionButtonClicked(event)
|
||||
{
|
||||
this._popoverContentElement.classList.add(WI.BreakpointPopoverController.WidePopoverClassName);
|
||||
this._actionsContainer.removeChildren();
|
||||
|
||||
let newAction = this._breakpoint.createAction(WI.BreakpointAction.Type.Log);
|
||||
let newBreakpointActionView = new WI.BreakpointActionView(newAction, this);
|
||||
this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, -1);
|
||||
this._popoverOptionsRowElement.classList.remove(WI.BreakpointPopoverController.HiddenStyleClassName);
|
||||
this._popover.update();
|
||||
}
|
||||
|
||||
_popoverActionsInsertBreakpointActionView(breakpointActionView, index)
|
||||
{
|
||||
if (index === -1)
|
||||
this._actionsContainer.appendChild(breakpointActionView.element);
|
||||
else {
|
||||
let nextElement = this._actionsContainer.children[index + 1] || null;
|
||||
this._actionsContainer.insertBefore(breakpointActionView.element, nextElement);
|
||||
}
|
||||
}
|
||||
|
||||
_updateIgnoreCountText()
|
||||
{
|
||||
if (this._breakpoint.ignoreCount === 1)
|
||||
this._ignoreCountText.textContent = WI.UIString("time before stopping");
|
||||
else
|
||||
this._ignoreCountText.textContent = WI.UIString("times before stopping");
|
||||
}
|
||||
|
||||
breakpointActionViewAppendActionView(breakpointActionView, newAction)
|
||||
{
|
||||
let newBreakpointActionView = new WI.BreakpointActionView(newAction, this);
|
||||
|
||||
let index = 0;
|
||||
let children = this._actionsContainer.children;
|
||||
for (let i = 0; children.length; ++i) {
|
||||
if (children[i] === breakpointActionView.element) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, index);
|
||||
this._popoverOptionsRowElement.classList.remove(WI.BreakpointPopoverController.HiddenStyleClassName);
|
||||
|
||||
this._popover.update();
|
||||
}
|
||||
|
||||
breakpointActionViewRemoveActionView(breakpointActionView)
|
||||
{
|
||||
breakpointActionView.element.remove();
|
||||
|
||||
if (!this._actionsContainer.children.length) {
|
||||
this._popoverActionsCreateAddActionButton();
|
||||
this._popoverOptionsRowElement.classList.add(WI.BreakpointPopoverController.HiddenStyleClassName);
|
||||
this._popoverOptionsCheckboxElement.checked = false;
|
||||
}
|
||||
|
||||
this._popover.update();
|
||||
}
|
||||
|
||||
breakpointActionViewResized(breakpointActionView)
|
||||
{
|
||||
this._popover.update();
|
||||
}
|
||||
|
||||
willDismissPopover(popover)
|
||||
{
|
||||
console.assert(this._popover === popover);
|
||||
this._popoverContentElement = null;
|
||||
this._popoverOptionsRowElement = null;
|
||||
this._popoverOptionsCheckboxElement = null;
|
||||
this._actionsContainer = null;
|
||||
this._popover = null;
|
||||
}
|
||||
|
||||
didDismissPopover(popover)
|
||||
{
|
||||
// Remove Evaluate and Probe actions that have no data.
|
||||
let emptyActions = this._breakpoint.actions.filter(function(action) {
|
||||
if (action.type !== WI.BreakpointAction.Type.Evaluate && action.type !== WI.BreakpointAction.Type.Probe)
|
||||
return false;
|
||||
return !(action.data && action.data.trim());
|
||||
});
|
||||
|
||||
for (let action of emptyActions)
|
||||
this._breakpoint.removeAction(action);
|
||||
|
||||
this._breakpoint = null;
|
||||
}
|
||||
};
|
||||
|
||||
WI.BreakpointPopoverController.WidePopoverClassName = "wide";
|
||||
WI.BreakpointPopoverController.HiddenStyleClassName = "hidden";
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
WI.BrowserManager = class BrowserManager
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this._enabled = false;
|
||||
this._extensionNameIdentifierMap = new Map;
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
// COMPATIBILITY (iOS 13.4): Browser did not exist yet.
|
||||
if (target.hasDomain("Browser"))
|
||||
target.BrowserAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
for (let target of WI.targetManager.allTargets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
for (let target of WI.targetManager.allTargets) {
|
||||
// COMPATIBILITY (iOS 13.4): Browser did not exist yet.
|
||||
if (target.hasDomain("Browser"))
|
||||
target.BrowserAgent.disable();
|
||||
}
|
||||
|
||||
this._extensionNameIdentifierMap.clear();
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
isExtensionScheme(scheme)
|
||||
{
|
||||
return scheme && scheme.endsWith("-extension");
|
||||
}
|
||||
|
||||
extensionNameForId(extensionId)
|
||||
{
|
||||
return this._extensionNameIdentifierMap.get(extensionId) || null;
|
||||
}
|
||||
|
||||
extensionNameForURL(url)
|
||||
{
|
||||
console.assert(this.isExtensionScheme(parseURL(url).scheme));
|
||||
|
||||
let match = url.match(/^[a-z\-]*extension:\/\/([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\//);
|
||||
if (!match)
|
||||
return null;
|
||||
|
||||
return this.extensionNameForId(match[1]);
|
||||
}
|
||||
|
||||
extensionNameForExecutionContext(context)
|
||||
{
|
||||
console.assert(context instanceof WI.ExecutionContext);
|
||||
console.assert(context.type === WI.ExecutionContext.Type.User);
|
||||
|
||||
let match = context.name.match(/^[A-Za-z]*ExtensionWorld-([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})$/);
|
||||
if (!match)
|
||||
return null;
|
||||
|
||||
return this.extensionNameForId(match[1]);
|
||||
}
|
||||
|
||||
// BrowserObserver
|
||||
|
||||
extensionsEnabled(extensions)
|
||||
{
|
||||
for (let {extensionId, name} of extensions) {
|
||||
console.assert(!this._extensionNameIdentifierMap.has(extensionId), `Extension already exists with id '${extensionId}'.`);
|
||||
|
||||
this._extensionNameIdentifierMap.set(extensionId, name);
|
||||
}
|
||||
}
|
||||
|
||||
extensionsDisabled(extensionIds)
|
||||
{
|
||||
for (let extensionId of extensionIds) {
|
||||
let name = this._extensionNameIdentifierMap.take(extensionId);
|
||||
console.assert(name, `Missing extension with id '${extensionId}'.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,894 @@
|
||||
/*
|
||||
* 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: CSSManager lacks advanced multi-target support. (Stylesheets per-target)
|
||||
|
||||
WI.CSSManager = class CSSManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceAdded, this);
|
||||
WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._resourceContentDidChange, this);
|
||||
WI.Resource.addEventListener(WI.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this);
|
||||
|
||||
WI.DOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._nodeAttributesDidChange, this);
|
||||
WI.DOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._nodeAttributesDidChange, this);
|
||||
WI.DOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this);
|
||||
|
||||
this._colorFormatSetting = new WI.Setting("default-color-format", WI.Color.Format.Original);
|
||||
|
||||
this._styleSheetIdentifierMap = new Map;
|
||||
this._styleSheetFrameURLMap = new Map;
|
||||
this._nodeStylesMap = {};
|
||||
this._modifiedStyles = new Map;
|
||||
this._defaultUserPreferences = new Map;
|
||||
this._overridenUserPreferences = new Map;
|
||||
this._propertyNameCompletions = null;
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (target.hasDomain("CSS"))
|
||||
target.CSSAgent.enable();
|
||||
}
|
||||
|
||||
initializeCSSPropertyNameCompletions(target)
|
||||
{
|
||||
console.assert(target.hasDomain("CSS"));
|
||||
|
||||
if (this._propertyNameCompletions)
|
||||
return;
|
||||
|
||||
target.CSSAgent.getSupportedCSSProperties((error, cssProperties) => {
|
||||
if (error)
|
||||
return;
|
||||
|
||||
this._propertyNameCompletions = new WI.CSSPropertyNameCompletions(cssProperties);
|
||||
|
||||
WI.CSSKeywordCompletions.addCustomCompletions(cssProperties);
|
||||
|
||||
// CodeMirror is not included by tests so we shouldn't assume it always exists.
|
||||
// If it isn't available we skip MIME type associations.
|
||||
if (!window.CodeMirror)
|
||||
return;
|
||||
|
||||
let propertyNamesForCodeMirror = {};
|
||||
let valueKeywordsForCodeMirror = {"inherit": true, "initial": true, "unset": true, "revert": true, "revert-layer": true, "var": true, "env": true};
|
||||
let colorKeywordsForCodeMirror = {};
|
||||
|
||||
function nameForCodeMirror(name) {
|
||||
// CodeMirror parses the vendor prefix separate from the property or keyword name,
|
||||
// so we need to strip vendor prefixes from our names. Also strip function parenthesis.
|
||||
return name.replace(/^-[^-]+-/, "").replace(/\(\)$/, "").toLowerCase();
|
||||
}
|
||||
|
||||
for (let property of cssProperties) {
|
||||
// Properties can also be value keywords, like when used in a transition.
|
||||
// So we add them to both lists.
|
||||
let codeMirrorPropertyName = nameForCodeMirror(property.name);
|
||||
propertyNamesForCodeMirror[codeMirrorPropertyName] = true;
|
||||
valueKeywordsForCodeMirror[codeMirrorPropertyName] = true;
|
||||
}
|
||||
|
||||
for (let propertyName in WI.CSSKeywordCompletions._propertyKeywordMap) {
|
||||
let keywords = WI.CSSKeywordCompletions._propertyKeywordMap[propertyName];
|
||||
for (let keyword of keywords) {
|
||||
// Skip numbers, like the ones defined for font-weight.
|
||||
if (keyword === WI.CSSKeywordCompletions.AllPropertyNamesPlaceholder || !isNaN(Number(keyword)))
|
||||
continue;
|
||||
valueKeywordsForCodeMirror[nameForCodeMirror(keyword)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let color of WI.CSSKeywordCompletions._colors)
|
||||
colorKeywordsForCodeMirror[nameForCodeMirror(color)] = true;
|
||||
|
||||
// TODO: Remove these keywords once they are built-in codemirror or once we get values from WebKit itself.
|
||||
valueKeywordsForCodeMirror["conic-gradient"] = true;
|
||||
valueKeywordsForCodeMirror["repeating-conic-gradient"] = true;
|
||||
|
||||
function updateCodeMirrorCSSMode(mimeType) {
|
||||
let modeSpec = CodeMirror.resolveMode(mimeType);
|
||||
|
||||
console.assert(modeSpec.propertyKeywords);
|
||||
console.assert(modeSpec.valueKeywords);
|
||||
console.assert(modeSpec.colorKeywords);
|
||||
|
||||
modeSpec.propertyKeywords = propertyNamesForCodeMirror;
|
||||
modeSpec.valueKeywords = valueKeywordsForCodeMirror;
|
||||
modeSpec.colorKeywords = colorKeywordsForCodeMirror;
|
||||
|
||||
CodeMirror.defineMIME(mimeType, modeSpec);
|
||||
}
|
||||
|
||||
updateCodeMirrorCSSMode("text/css");
|
||||
updateCodeMirrorCSSMode("text/x-scss");
|
||||
});
|
||||
|
||||
if (target.hasCommand("CSS.getSupportedSystemFontFamilyNames")) {
|
||||
target.CSSAgent.getSupportedSystemFontFamilyNames((error, fontFamilyNames) =>{
|
||||
if (error)
|
||||
return;
|
||||
|
||||
WI.CSSKeywordCompletions.addPropertyCompletionValues("font-family", fontFamilyNames);
|
||||
WI.CSSKeywordCompletions.addPropertyCompletionValues("font", fontFamilyNames);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static supportsInspectorStyleSheet()
|
||||
{
|
||||
return InspectorBackend.hasCommand("CSS.createStyleSheet");
|
||||
}
|
||||
|
||||
static protocolStyleSheetOriginToEnum(origin)
|
||||
{
|
||||
switch (origin) {
|
||||
case InspectorBackend.Enum.CSS.StyleSheetOrigin.User:
|
||||
return WI.CSSStyleSheet.Type.User;
|
||||
|
||||
case InspectorBackend.Enum.CSS.StyleSheetOrigin.UserAgent:
|
||||
return WI.CSSStyleSheet.Type.UserAgent;
|
||||
|
||||
case InspectorBackend.Enum.CSS.StyleSheetOrigin.Inspector:
|
||||
return WI.CSSStyleSheet.Type.Inspector;
|
||||
}
|
||||
|
||||
// COMPATIBILITY (iOS 14): CSS.StyleSheetOrigin.Regular was replaced with CSS.StyleSheetOrigin.Author.
|
||||
console.assert(!InspectorBackend.Enum.CSS.StyleSheetOrigin.Author || origin === InspectorBackend.Enum.CSS.StyleSheetOrigin.Author);
|
||||
console.assert(!InspectorBackend.Enum.CSS.StyleSheetOrigin.Regular || origin === InspectorBackend.Enum.CSS.StyleSheetOrigin.Regular);
|
||||
return WI.CSSStyleSheet.Type.Author;
|
||||
}
|
||||
|
||||
static protocolGroupingTypeToEnum(type)
|
||||
{
|
||||
// COMPATIBILITY (iOS 13): CSS.Grouping did not exist yet.
|
||||
if (!InspectorBackend.Enum.CSS.Grouping) {
|
||||
switch (type) {
|
||||
case "mediaRule":
|
||||
return WI.CSSGrouping.Type.MediaRule;
|
||||
case "importRule":
|
||||
return WI.CSSGrouping.Type.MediaImportRule;
|
||||
case "linkedSheet":
|
||||
return WI.CSSGrouping.Type.MediaLinkNode;
|
||||
case "inlineSheet":
|
||||
return WI.CSSGrouping.Type.MediaStyleNode;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static displayNameForPseudoId(pseudoId)
|
||||
{
|
||||
// Compatibility (iOS 12.2): CSS.PseudoId did not exist.
|
||||
if (!InspectorBackend.Enum.CSS.PseudoId) {
|
||||
switch (pseudoId) {
|
||||
case 1: // PseudoId.FirstLine
|
||||
return WI.unlocalizedString("::first-line");
|
||||
case 2: // PseudoId.FirstLetter
|
||||
return WI.unlocalizedString("::first-letter");
|
||||
case 3: // PseudoId.Marker
|
||||
return WI.unlocalizedString("::marker");
|
||||
case 4: // PseudoId.Before
|
||||
return WI.unlocalizedString("::before");
|
||||
case 5: // PseudoId.After
|
||||
return WI.unlocalizedString("::after");
|
||||
case 6: // PseudoId.Selection
|
||||
return WI.unlocalizedString("::selection");
|
||||
case 7: // PseudoId.Scrollbar
|
||||
return WI.unlocalizedString("::scrollbar");
|
||||
case 8: // PseudoId.ScrollbarThumb
|
||||
return WI.unlocalizedString("::scrollbar-thumb");
|
||||
case 9: // PseudoId.ScrollbarButton
|
||||
return WI.unlocalizedString("::scrollbar-button");
|
||||
case 10: // PseudoId.ScrollbarTrack
|
||||
return WI.unlocalizedString("::scrollbar-track");
|
||||
case 11: // PseudoId.ScrollbarTrackPiece
|
||||
return WI.unlocalizedString("::scrollbar-track-piece");
|
||||
case 12: // PseudoId.ScrollbarCorner
|
||||
return WI.unlocalizedString("::scrollbar-corner");
|
||||
case 13: // PseudoId.Resizer
|
||||
return WI.unlocalizedString("::resizer");
|
||||
|
||||
default:
|
||||
console.error("Unknown pseudo id", pseudoId);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
switch (pseudoId) {
|
||||
case CSSManager.PseudoSelectorNames.FirstLine:
|
||||
return WI.unlocalizedString("::first-line");
|
||||
case CSSManager.PseudoSelectorNames.FirstLetter:
|
||||
return WI.unlocalizedString("::first-letter");
|
||||
case CSSManager.PseudoSelectorNames.Highlight:
|
||||
return WI.unlocalizedString("::highlight");
|
||||
case CSSManager.PseudoSelectorNames.Marker:
|
||||
return WI.unlocalizedString("::marker");
|
||||
case CSSManager.PseudoSelectorNames.Before:
|
||||
return WI.unlocalizedString("::before");
|
||||
case CSSManager.PseudoSelectorNames.After:
|
||||
return WI.unlocalizedString("::after");
|
||||
case CSSManager.PseudoSelectorNames.Selection:
|
||||
return WI.unlocalizedString("::selection");
|
||||
case CSSManager.PseudoSelectorNames.Backdrop:
|
||||
return WI.unlocalizedString("::backdrop");
|
||||
case CSSManager.PseudoSelectorNames.Scrollbar:
|
||||
return WI.unlocalizedString("::scrollbar");
|
||||
case CSSManager.PseudoSelectorNames.ScrollbarThumb:
|
||||
return WI.unlocalizedString("::scrollbar-thumb");
|
||||
case CSSManager.PseudoSelectorNames.ScrollbarButton:
|
||||
return WI.unlocalizedString("::scrollbar-button");
|
||||
case CSSManager.PseudoSelectorNames.ScrollbarTrack:
|
||||
return WI.unlocalizedString("::scrollbar-track");
|
||||
case CSSManager.PseudoSelectorNames.ScrollbarTrackPiece:
|
||||
return WI.unlocalizedString("::scrollbar-track-piece");
|
||||
case CSSManager.PseudoSelectorNames.ScrollbarCorner:
|
||||
return WI.unlocalizedString("::scrollbar-corner");
|
||||
case CSSManager.PseudoSelectorNames.Resizer:
|
||||
return WI.unlocalizedString("::resizer");
|
||||
|
||||
default:
|
||||
console.error("Unknown pseudo id", pseudoId);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static displayNameForForceablePseudoClass(pseudoClass)
|
||||
{
|
||||
switch (pseudoClass) {
|
||||
case WI.CSSManager.ForceablePseudoClass.Active:
|
||||
return WI.unlocalizedString(":active");
|
||||
case WI.CSSManager.ForceablePseudoClass.Focus:
|
||||
return WI.unlocalizedString(":focus");
|
||||
case WI.CSSManager.ForceablePseudoClass.FocusVisible:
|
||||
return WI.unlocalizedString(":focus-visible");
|
||||
case WI.CSSManager.ForceablePseudoClass.FocusWithin:
|
||||
return WI.unlocalizedString(":focus-within");
|
||||
case WI.CSSManager.ForceablePseudoClass.Hover:
|
||||
return WI.unlocalizedString(":hover");
|
||||
case WI.CSSManager.ForceablePseudoClass.Target:
|
||||
return WI.unlocalizedString(":target");
|
||||
case WI.CSSManager.ForceablePseudoClass.Visited:
|
||||
return WI.unlocalizedString(":visited");
|
||||
}
|
||||
|
||||
console.assert(false, "Unknown pseudo class", pseudoClass);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get propertyNameCompletions() { return this._propertyNameCompletions; }
|
||||
|
||||
get overridenUserPreferences() { return this._overridenUserPreferences; }
|
||||
|
||||
get defaultUserPreferences() { return this._defaultUserPreferences; }
|
||||
|
||||
get overridenUserPreferences() { return this._overridenUserPreferences; }
|
||||
|
||||
get preferredColorFormat()
|
||||
{
|
||||
return this._colorFormatSetting.value;
|
||||
}
|
||||
|
||||
get styleSheets()
|
||||
{
|
||||
return Array.from(this._styleSheetIdentifierMap.values());
|
||||
}
|
||||
|
||||
get supportsOverrideUserPreference()
|
||||
{
|
||||
return InspectorBackend.hasCommand("Page.overrideUserPreference") && this._defaultUserPreferences.size;
|
||||
}
|
||||
|
||||
get supportsOverrideColorScheme()
|
||||
{
|
||||
// A backend for a platform that does not support color schemes will not dispatch an initial event (Page.defaultAppearanceDidChange or Page.defaultUserPreferencesDidChange)
|
||||
// with the default value for the color scheme preference which gets stored on the frontend.
|
||||
|
||||
// COMPATIBILITY (macOS 13.0, iOS 16.0): `PrefersColorScheme` value for `Page.UserPreferenceName` did not exist yet.
|
||||
return this._defaultUserPreferences.has(InspectorBackend.Enum.Page.UserPreferenceName?.PrefersColorScheme) || this._defaultUserPreferences.has(WI.CSSManager.ForcedAppearancePreference);
|
||||
}
|
||||
|
||||
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()`
|
||||
setForcedAppearance(name)
|
||||
{
|
||||
let commandArguments = {};
|
||||
|
||||
switch (name) {
|
||||
case WI.CSSManager.Appearance.Light:
|
||||
commandArguments.appearance = InspectorBackend.Enum.Page.Appearance.Light;
|
||||
break;
|
||||
|
||||
case WI.CSSManager.Appearance.Dark:
|
||||
commandArguments.appearance = InspectorBackend.Enum.Page.Appearance.Dark;
|
||||
break;
|
||||
|
||||
case null:
|
||||
// COMPATIBILITY (iOS 14): the `appearance`` parameter of `Page.setForcedAppearance` was not optional.
|
||||
// Since support can't be tested directly, check for the `options`` parameter of `DOMDebugger.setDOMBreakpoint` (iOS 14.0+).
|
||||
// FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed.
|
||||
if (!InspectorBackend.hasCommand("DOMDebugger.setDOMBreakpoint", "options"))
|
||||
commandArguments.appearance = "";
|
||||
break;
|
||||
|
||||
default:
|
||||
console.assert(false, "Unknown appearance", name);
|
||||
return;
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
return target.PageAgent.setForcedAppearance.invoke(commandArguments);
|
||||
}
|
||||
|
||||
set layoutContextTypeChangedMode(layoutContextTypeChangedMode)
|
||||
{
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY (iOS 14.5): CSS.setLayoutContextTypeChangedMode did not exist.
|
||||
if (target.hasCommand("CSS.setLayoutContextTypeChangedMode"))
|
||||
target.CSSAgent.setLayoutContextTypeChangedMode(layoutContextTypeChangedMode);
|
||||
}
|
||||
}
|
||||
|
||||
canForcePseudoClass(pseudoClass)
|
||||
{
|
||||
if (!InspectorBackend.hasCommand("CSS.forcePseudoState"))
|
||||
return false;
|
||||
|
||||
if (!pseudoClass)
|
||||
return true;
|
||||
|
||||
switch (pseudoClass) {
|
||||
case WI.CSSManager.ForceablePseudoClass.Active:
|
||||
case WI.CSSManager.ForceablePseudoClass.Focus:
|
||||
case WI.CSSManager.ForceablePseudoClass.Hover:
|
||||
case WI.CSSManager.ForceablePseudoClass.Visited:
|
||||
return true;
|
||||
|
||||
case WI.CSSManager.ForceablePseudoClass.FocusVisible:
|
||||
case WI.CSSManager.ForceablePseudoClass.FocusWithin:
|
||||
case WI.CSSManager.ForceablePseudoClass.Target:
|
||||
// COMPATIBILITY (macOS 12.3, iOS 15.4): CSS.ForceablePseudoClass did not exist yet.
|
||||
return !!InspectorBackend.Enum.CSS.ForceablePseudoClass;
|
||||
}
|
||||
|
||||
console.assert(false, "Unknown pseudo class", pseudoClass);
|
||||
return false;
|
||||
}
|
||||
|
||||
overrideUserPreference(preference, value)
|
||||
{
|
||||
let promises = [];
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY (macOS 13.0, iOS 16.0): `Page.overrideUserPreference()` did not exist yet.
|
||||
if (target.hasCommand("Page.overrideUserPreference") && InspectorBackend.Enum.Page.UserPreferenceName[preference])
|
||||
promises.push(target.PageAgent.overrideUserPreference(preference, value));
|
||||
|
||||
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()`
|
||||
if (preference === WI.CSSManager.ForcedAppearancePreference && target.hasCommand("Page.setForcedAppearance"))
|
||||
promises.push(this.setForcedAppearance(value || null));
|
||||
}
|
||||
|
||||
if (value)
|
||||
this._overridenUserPreferences.set(preference, value);
|
||||
else
|
||||
this._overridenUserPreferences.delete(preference);
|
||||
|
||||
Promise.allSettled(promises).then(() => {
|
||||
this.mediaQueryResultChanged();
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.OverridenUserPreferencesDidChange);
|
||||
})
|
||||
}
|
||||
|
||||
propertyNameHasOtherVendorPrefix(name)
|
||||
{
|
||||
if (!name || name.length < 4 || name.charAt(0) !== "-")
|
||||
return false;
|
||||
|
||||
var match = name.match(/^(?:-moz-|-ms-|-o-|-epub-)/);
|
||||
if (!match)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
propertyValueHasOtherVendorKeyword(value)
|
||||
{
|
||||
var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/);
|
||||
if (!match)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
canonicalNameForPropertyName(name)
|
||||
{
|
||||
if (!name || name.length < 8 || name.charAt(0) !== "-")
|
||||
return name;
|
||||
|
||||
// Keep in sync with prefix list from Source/WebInspectorUI/Scripts/update-inspector-css-documentation
|
||||
var match = name.match(/^(?:-webkit-|-khtml-|-apple-)(.+)/);
|
||||
if (!match)
|
||||
return name;
|
||||
|
||||
return match[1];
|
||||
}
|
||||
|
||||
styleSheetForIdentifier(id)
|
||||
{
|
||||
let styleSheet = this._styleSheetIdentifierMap.get(id);
|
||||
if (styleSheet)
|
||||
return styleSheet;
|
||||
|
||||
styleSheet = new WI.CSSStyleSheet(id);
|
||||
this._styleSheetIdentifierMap.set(id, styleSheet);
|
||||
return styleSheet;
|
||||
}
|
||||
|
||||
stylesForNode(node)
|
||||
{
|
||||
if (node.id in this._nodeStylesMap)
|
||||
return this._nodeStylesMap[node.id];
|
||||
|
||||
var styles = new WI.DOMNodeStyles(node);
|
||||
this._nodeStylesMap[node.id] = styles;
|
||||
return styles;
|
||||
}
|
||||
|
||||
inspectorStyleSheetsForFrame(frame)
|
||||
{
|
||||
return this.styleSheets.filter((styleSheet) => styleSheet.isInspectorStyleSheet() && styleSheet.parentFrame === frame);
|
||||
}
|
||||
|
||||
preferredInspectorStyleSheetForFrame(frame, callback)
|
||||
{
|
||||
var inspectorStyleSheets = this.inspectorStyleSheetsForFrame(frame);
|
||||
for (let styleSheet of inspectorStyleSheets) {
|
||||
if (styleSheet[WI.CSSManager.PreferredInspectorStyleSheetSymbol]) {
|
||||
callback(styleSheet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.CSSAgent.createStyleSheet(frame.id, function(error, styleSheetId) {
|
||||
if (error || !styleSheetId) {
|
||||
WI.reportInternalError(error || styleSheetId);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = null;
|
||||
let styleSheet = WI.cssManager.styleSheetForIdentifier(styleSheetId);
|
||||
styleSheet.updateInfo(url, frame, styleSheet.origin, styleSheet.isInlineStyleTag(), styleSheet.startLineNumber, styleSheet.startColumnNumber);
|
||||
styleSheet[WI.CSSManager.PreferredInspectorStyleSheetSymbol] = true;
|
||||
callback(styleSheet);
|
||||
});
|
||||
}
|
||||
|
||||
mediaTypeChanged()
|
||||
{
|
||||
// Act the same as if media queries changed.
|
||||
this.mediaQueryResultChanged();
|
||||
}
|
||||
|
||||
get modifiedStyles()
|
||||
{
|
||||
return Array.from(this._modifiedStyles.values());
|
||||
}
|
||||
|
||||
addModifiedStyle(style)
|
||||
{
|
||||
this._modifiedStyles.set(style.stringId, style);
|
||||
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.ModifiedStylesChanged);
|
||||
}
|
||||
|
||||
getModifiedStyle(style)
|
||||
{
|
||||
return this._modifiedStyles.get(style.stringId);
|
||||
}
|
||||
|
||||
removeModifiedStyle(style)
|
||||
{
|
||||
this._modifiedStyles.delete(style.stringId);
|
||||
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.ModifiedStylesChanged);
|
||||
}
|
||||
|
||||
// PageObserver
|
||||
|
||||
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.defaultAppearanceDidChange` was removed in favor of `Page.defaultUserPreferencesDidChange`
|
||||
defaultAppearanceDidChange(protocolName)
|
||||
{
|
||||
let appearance = null;
|
||||
|
||||
switch (protocolName) {
|
||||
case InspectorBackend.Enum.Page.Appearance.Light:
|
||||
appearance = WI.CSSManager.Appearance.Light;
|
||||
break;
|
||||
|
||||
case InspectorBackend.Enum.Page.Appearance.Dark:
|
||||
appearance = WI.CSSManager.Appearance.Dark;
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error("Unknown default appearance name:", protocolName);
|
||||
break;
|
||||
}
|
||||
|
||||
this.mediaQueryResultChanged();
|
||||
|
||||
this._defaultUserPreferences.set(WI.CSSManager.ForcedAppearancePreference, appearance);
|
||||
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.DefaultUserPreferencesDidChange);
|
||||
}
|
||||
|
||||
defaultUserPreferencesDidChange(userPreferences)
|
||||
{
|
||||
this._defaultUserPreferences.clear();
|
||||
|
||||
for (let userPreference of userPreferences)
|
||||
this._defaultUserPreferences.set(userPreference.name, userPreference.value)
|
||||
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.DefaultUserPreferencesDidChange);
|
||||
}
|
||||
|
||||
// CSSObserver
|
||||
|
||||
mediaQueryResultChanged()
|
||||
{
|
||||
for (var key in this._nodeStylesMap)
|
||||
this._nodeStylesMap[key].mediaQueryResultDidChange();
|
||||
}
|
||||
|
||||
styleSheetChanged(styleSheetIdentifier)
|
||||
{
|
||||
var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier);
|
||||
console.assert(styleSheet);
|
||||
|
||||
// Do not observe inline styles
|
||||
if (styleSheet.isInlineStyleAttributeStyleSheet())
|
||||
return;
|
||||
|
||||
if (!styleSheet.noteContentDidChange())
|
||||
return;
|
||||
|
||||
this._updateResourceContent(styleSheet);
|
||||
}
|
||||
|
||||
styleSheetAdded(styleSheetInfo)
|
||||
{
|
||||
console.assert(!this._styleSheetIdentifierMap.has(styleSheetInfo.styleSheetId), "Attempted to add a CSSStyleSheet but identifier was already in use");
|
||||
let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
|
||||
let parentFrame = WI.networkManager.frameForIdentifier(styleSheetInfo.frameId);
|
||||
let origin = WI.CSSManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
|
||||
styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn);
|
||||
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.StyleSheetAdded, {styleSheet});
|
||||
}
|
||||
|
||||
styleSheetRemoved(styleSheetIdentifier)
|
||||
{
|
||||
let styleSheet = this._styleSheetIdentifierMap.get(styleSheetIdentifier);
|
||||
console.assert(styleSheet, "Attempted to remove a CSSStyleSheet that was not tracked");
|
||||
if (!styleSheet)
|
||||
return;
|
||||
|
||||
this._styleSheetIdentifierMap.delete(styleSheetIdentifier);
|
||||
|
||||
this.dispatchEventToListeners(WI.CSSManager.Event.StyleSheetRemoved, {styleSheet});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_nodePseudoClassesDidChange(event)
|
||||
{
|
||||
var node = event.target;
|
||||
|
||||
for (var key in this._nodeStylesMap) {
|
||||
var nodeStyles = this._nodeStylesMap[key];
|
||||
if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node))
|
||||
continue;
|
||||
nodeStyles.pseudoClassesDidChange(node);
|
||||
}
|
||||
}
|
||||
|
||||
_nodeAttributesDidChange(event)
|
||||
{
|
||||
var node = event.target;
|
||||
|
||||
for (var key in this._nodeStylesMap) {
|
||||
var nodeStyles = this._nodeStylesMap[key];
|
||||
if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node))
|
||||
continue;
|
||||
nodeStyles.attributeDidChange(node, event.data.name);
|
||||
}
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
// Clear our maps when the main frame navigates.
|
||||
|
||||
this._styleSheetIdentifierMap.clear();
|
||||
this._styleSheetFrameURLMap.clear();
|
||||
this._modifiedStyles.clear();
|
||||
|
||||
this._nodeStylesMap = {};
|
||||
}
|
||||
|
||||
_resourceAdded(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
var resource = event.data.resource;
|
||||
console.assert(resource);
|
||||
|
||||
if (resource.type !== WI.Resource.Type.StyleSheet)
|
||||
return;
|
||||
|
||||
this._clearStyleSheetsForResource(resource);
|
||||
}
|
||||
|
||||
_resourceTypeDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Resource);
|
||||
|
||||
var resource = event.target;
|
||||
if (resource.type !== WI.Resource.Type.StyleSheet)
|
||||
return;
|
||||
|
||||
this._clearStyleSheetsForResource(resource);
|
||||
}
|
||||
|
||||
_clearStyleSheetsForResource(resource)
|
||||
{
|
||||
// Clear known stylesheets for this URL and frame. This will cause the style sheets to
|
||||
// be updated next time _fetchInfoForAllStyleSheets is called.
|
||||
this._styleSheetIdentifierMap.delete(this._frameURLMapKey(resource.parentFrame, resource.url));
|
||||
}
|
||||
|
||||
_frameURLMapKey(frame, url)
|
||||
{
|
||||
return frame.id + ":" + url;
|
||||
}
|
||||
|
||||
_lookupStyleSheetForResource(resource, callback)
|
||||
{
|
||||
this._lookupStyleSheet(resource.parentFrame, resource.url, callback);
|
||||
}
|
||||
|
||||
_lookupStyleSheet(frame, url, callback)
|
||||
{
|
||||
console.assert(frame instanceof WI.Frame);
|
||||
|
||||
let key = this._frameURLMapKey(frame, url);
|
||||
|
||||
function styleSheetsFetched()
|
||||
{
|
||||
callback(this._styleSheetFrameURLMap.get(key) || null);
|
||||
}
|
||||
|
||||
let styleSheet = this._styleSheetFrameURLMap.get(key) || null;
|
||||
if (styleSheet)
|
||||
callback(styleSheet);
|
||||
else
|
||||
this._fetchInfoForAllStyleSheets(styleSheetsFetched.bind(this));
|
||||
}
|
||||
|
||||
_fetchInfoForAllStyleSheets(callback)
|
||||
{
|
||||
console.assert(typeof callback === "function");
|
||||
|
||||
function processStyleSheets(error, styleSheets)
|
||||
{
|
||||
this._styleSheetFrameURLMap.clear();
|
||||
|
||||
if (error) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
for (let styleSheetInfo of styleSheets) {
|
||||
let parentFrame = WI.networkManager.frameForIdentifier(styleSheetInfo.frameId);
|
||||
let origin = WI.CSSManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
|
||||
|
||||
let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
|
||||
styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn);
|
||||
|
||||
let key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL);
|
||||
this._styleSheetFrameURLMap.set(key, styleSheet);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.CSSAgent.getAllStyleSheets(processStyleSheets.bind(this));
|
||||
}
|
||||
|
||||
_resourceContentDidChange(event)
|
||||
{
|
||||
var resource = event.target;
|
||||
if (resource === this._ignoreResourceContentDidChangeEventForResource)
|
||||
return;
|
||||
|
||||
// Ignore changes to resource overrides, those are not live on the page.
|
||||
if (resource.localResourceOverride)
|
||||
return;
|
||||
|
||||
// Ignore if it isn't a CSS style sheet.
|
||||
if (resource.type !== WI.Resource.Type.StyleSheet || resource.syntheticMIMEType !== "text/css")
|
||||
return;
|
||||
|
||||
function applyStyleSheetChanges()
|
||||
{
|
||||
function styleSheetFound(styleSheet)
|
||||
{
|
||||
resource.__pendingChangeTimeout.cancel();
|
||||
|
||||
console.assert(styleSheet);
|
||||
if (!styleSheet)
|
||||
return;
|
||||
|
||||
// To prevent updating a TextEditor's content while the user is typing in it we want to
|
||||
// ignore the next _updateResourceContent call.
|
||||
resource.__ignoreNextUpdateResourceContent = true;
|
||||
|
||||
let revision = styleSheet.editableRevision;
|
||||
revision.updateRevisionContent(resource.content);
|
||||
}
|
||||
|
||||
this._lookupStyleSheetForResource(resource, styleSheetFound.bind(this));
|
||||
}
|
||||
|
||||
if (!resource.__pendingChangeTimeout)
|
||||
resource.__pendingChangeTimeout = new Throttler(applyStyleSheetChanges.bind(this), 100);
|
||||
resource.__pendingChangeTimeout.fire();
|
||||
}
|
||||
|
||||
_updateResourceContent(styleSheet)
|
||||
{
|
||||
console.assert(styleSheet);
|
||||
|
||||
function fetchedStyleSheetContent(parameters)
|
||||
{
|
||||
styleSheet.__pendingChangeTimeout.cancel();
|
||||
|
||||
let representedObject = parameters.sourceCode;
|
||||
|
||||
console.assert(representedObject.url);
|
||||
if (!representedObject.url)
|
||||
return;
|
||||
|
||||
if (!styleSheet.isInspectorStyleSheet()) {
|
||||
// Only try to update stylesheet resources. Other resources, like documents, can contain
|
||||
// multiple stylesheets and we don't have the source ranges to update those.
|
||||
representedObject = representedObject.parentFrame.resourcesForURL(representedObject.url).find((resource) => resource.type === WI.Resource.Type.StyleSheet);
|
||||
if (!representedObject)
|
||||
return;
|
||||
}
|
||||
|
||||
if (representedObject.__ignoreNextUpdateResourceContent) {
|
||||
representedObject.__ignoreNextUpdateResourceContent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._ignoreResourceContentDidChangeEventForResource = representedObject;
|
||||
|
||||
let revision = representedObject.editableRevision;
|
||||
if (styleSheet.isInspectorStyleSheet()) {
|
||||
revision.updateRevisionContent(representedObject.content);
|
||||
styleSheet.dispatchEventToListeners(WI.SourceCode.Event.ContentDidChange);
|
||||
} else
|
||||
revision.updateRevisionContent(parameters.content);
|
||||
|
||||
this._ignoreResourceContentDidChangeEventForResource = null;
|
||||
}
|
||||
|
||||
function styleSheetReady()
|
||||
{
|
||||
styleSheet.requestContent().then(fetchedStyleSheetContent.bind(this));
|
||||
}
|
||||
|
||||
function applyStyleSheetChanges()
|
||||
{
|
||||
if (styleSheet.url)
|
||||
styleSheetReady.call(this);
|
||||
else
|
||||
this._fetchInfoForAllStyleSheets(styleSheetReady.bind(this));
|
||||
}
|
||||
|
||||
if (!styleSheet.__pendingChangeTimeout)
|
||||
styleSheet.__pendingChangeTimeout = new Throttler(applyStyleSheetChanges.bind(this), 100);
|
||||
styleSheet.__pendingChangeTimeout.fire();
|
||||
}
|
||||
};
|
||||
|
||||
WI.CSSManager.Event = {
|
||||
StyleSheetAdded: "css-manager-style-sheet-added",
|
||||
StyleSheetRemoved: "css-manager-style-sheet-removed",
|
||||
ModifiedStylesChanged: "css-manager-modified-styles-changed",
|
||||
DefaultUserPreferencesDidChange: "css-manager-default-user-preferences-did-change",
|
||||
OverridenUserPreferencesDidChange: "css-manager-overriden-user-preferences-did-change",
|
||||
};
|
||||
|
||||
WI.CSSManager.UserPreferenceDefaultValue = "System";
|
||||
|
||||
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()`
|
||||
WI.CSSManager.ForcedAppearancePreference = "ForcedAppearancePreference";
|
||||
WI.CSSManager.Appearance = {
|
||||
Light: "Light",
|
||||
Dark: "Dark",
|
||||
};
|
||||
|
||||
WI.CSSManager.PseudoSelectorNames = {
|
||||
After: "after",
|
||||
Before: "before",
|
||||
Backdrop: "backdrop",
|
||||
FirstLetter: "first-letter",
|
||||
FirstLine: "first-line",
|
||||
Highlight: "highlight",
|
||||
Marker: "marker",
|
||||
Resizer: "resizer",
|
||||
Scrollbar: "scrollbar",
|
||||
ScrollbarButton: "scrollbar-button",
|
||||
ScrollbarCorner: "scrollbar-corner",
|
||||
ScrollbarThumb: "scrollbar-thumb",
|
||||
ScrollbarTrack: "scrollbar-track",
|
||||
ScrollbarTrackPiece: "scrollbar-track-piece",
|
||||
Selection: "selection",
|
||||
};
|
||||
|
||||
WI.CSSManager.LayoutContextTypeChangedMode = {
|
||||
Observed: "observed",
|
||||
All: "all",
|
||||
};
|
||||
|
||||
WI.CSSManager.PseudoElementNames = ["before", "after"];
|
||||
|
||||
WI.CSSManager.ForceablePseudoClass = {
|
||||
Active: "active",
|
||||
Focus: "focus",
|
||||
FocusVisible: "focus-visible",
|
||||
FocusWithin: "focus-within",
|
||||
Hover: "hover",
|
||||
Target: "target",
|
||||
Visited: "visited",
|
||||
};
|
||||
|
||||
WI.CSSManager.PreferredInspectorStyleSheetSymbol = Symbol("css-manager-preferred-inspector-style-sheet");
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
WI.CSSQueryController = class CSSQueryController extends WI.QueryController
|
||||
{
|
||||
constructor(values)
|
||||
{
|
||||
console.assert(Array.isArray(values), values);
|
||||
|
||||
super();
|
||||
|
||||
this._values = values || [];
|
||||
this._cachedSpecialCharacterIndicesForValueMap = new Map;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
addValues(values)
|
||||
{
|
||||
console.assert(Array.isArray(values), values);
|
||||
if (!values.length)
|
||||
return;
|
||||
|
||||
this._values.pushAll(values);
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this._values = [];
|
||||
this._cachedSpecialCharacterIndicesForValueMap.clear();
|
||||
}
|
||||
|
||||
executeQuery(query)
|
||||
{
|
||||
if (!query || !this._values.length)
|
||||
return [];
|
||||
|
||||
query = query.toLowerCase();
|
||||
|
||||
let results = [];
|
||||
|
||||
for (let value of this._values) {
|
||||
if (!this._cachedSpecialCharacterIndicesForValueMap.has(value))
|
||||
this._cachedSpecialCharacterIndicesForValueMap.set(value, this._findSpecialCharacterIndicesInPropertyName(value));
|
||||
|
||||
let matches = this.findQueryMatches(query, value.toLowerCase(), this._cachedSpecialCharacterIndicesForValueMap.get(value));
|
||||
if (matches.length)
|
||||
results.push(new WI.QueryResult(value, matches));
|
||||
}
|
||||
|
||||
return results.sort((a, b) => {
|
||||
if (a.rank === b.rank)
|
||||
return WI.CSSProperty.sortPreferringNonPrefixed(a.value, b.value);
|
||||
return b.rank - a.rank;
|
||||
});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_findSpecialCharacterIndicesInPropertyName(propertyName)
|
||||
{
|
||||
return this.findSpecialCharacterIndices(propertyName, "-_");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.CallFrameTreeController = class CallFrameTreeController extends WI.Object
|
||||
{
|
||||
constructor(treeOutline)
|
||||
{
|
||||
console.assert(treeOutline instanceof WI.TreeOutline);
|
||||
|
||||
super();
|
||||
|
||||
this._treeOutline = treeOutline;
|
||||
|
||||
if (this._treeOutline.selectable)
|
||||
this._treeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
|
||||
else
|
||||
this._treeOutline.addEventListener(WI.TreeOutline.Event.ElementClicked, this._treeElementClicked, this);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get treeOutline() { return this._treeOutline; }
|
||||
|
||||
get callFrames()
|
||||
{
|
||||
return this._callFrames;
|
||||
}
|
||||
|
||||
set callFrames(callFrames)
|
||||
{
|
||||
callFrames = callFrames || [];
|
||||
if (this._callFrames === callFrames)
|
||||
return;
|
||||
|
||||
this._callFrames = callFrames;
|
||||
|
||||
this._treeOutline.removeChildren();
|
||||
|
||||
for (let callFrame of this._callFrames)
|
||||
this._treeOutline.appendChild(new WI.CallFrameTreeElement(callFrame));
|
||||
}
|
||||
|
||||
disconnect()
|
||||
{
|
||||
this._treeOutline.removeEventListener(null, null, this);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_treeElementClicked(event)
|
||||
{
|
||||
this._showSourceCodeLocation(event.data.treeElement);
|
||||
}
|
||||
|
||||
_treeSelectionDidChange(event)
|
||||
{
|
||||
this._showSourceCodeLocation(this._treeOutline.selectedTreeElement);
|
||||
}
|
||||
|
||||
_showSourceCodeLocation(treeElement)
|
||||
{
|
||||
let callFrame = treeElement.callFrame;
|
||||
if (!callFrame.sourceCodeLocation)
|
||||
return;
|
||||
|
||||
WI.showSourceCodeLocation(callFrame.sourceCodeLocation, {
|
||||
ignoreNetworkTab: true,
|
||||
ignoreSearchTab: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
// FIXME: CanvasManager lacks advanced multi-target support. (Canvases per-target)
|
||||
|
||||
WI.CanvasManager = class CanvasManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
this._canvasCollection = new WI.CanvasCollection;
|
||||
this._canvasIdentifierMap = new Map;
|
||||
this._shaderProgramIdentifierMap = new Map;
|
||||
this._savedRecordings = new Set;
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["Canvas"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "Canvas");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("Canvas")) {
|
||||
target.CanvasAgent.enable();
|
||||
|
||||
// COMPATIBILITY (iOS 12): Canvas.setRecordingAutoCaptureFrameCount did not exist yet.
|
||||
if (target.hasCommand("Canvas.setRecordingAutoCaptureFrameCount") && WI.settings.canvasRecordingAutoCaptureEnabled.value && WI.settings.canvasRecordingAutoCaptureFrameCount.value)
|
||||
target.CanvasAgent.setRecordingAutoCaptureFrameCount(WI.settings.canvasRecordingAutoCaptureFrameCount.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static supportsRecordingAutoCapture()
|
||||
{
|
||||
return InspectorBackend.hasCommand("Canvas.setRecordingAutoCaptureFrameCount");
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get canvasCollection() { return this._canvasCollection; }
|
||||
get savedRecordings() { return this._savedRecordings; }
|
||||
|
||||
get shaderPrograms()
|
||||
{
|
||||
return Array.from(this._shaderProgramIdentifierMap.values());
|
||||
}
|
||||
|
||||
async processJSON({filename, json, error})
|
||||
{
|
||||
if (error) {
|
||||
WI.Recording.synthesizeError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof json !== "object" || json === null) {
|
||||
WI.Recording.synthesizeError(WI.UIString("invalid JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
let recording = WI.Recording.fromPayload(json);
|
||||
if (!recording)
|
||||
return;
|
||||
|
||||
let extensionStart = filename.lastIndexOf(".");
|
||||
if (extensionStart !== -1)
|
||||
filename = filename.substring(0, extensionStart);
|
||||
recording.createDisplayName(filename);
|
||||
|
||||
this._savedRecordings.add(recording);
|
||||
|
||||
this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingSaved, {recording, imported: true, initiatedByUser: true});
|
||||
}
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("Canvas"))
|
||||
target.CanvasAgent.disable();
|
||||
}
|
||||
|
||||
this._canvasCollection.clear();
|
||||
this._canvasIdentifierMap.clear();
|
||||
this._shaderProgramIdentifierMap.clear();
|
||||
this._savedRecordings.clear();
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
setRecordingAutoCaptureFrameCount(enabled, count)
|
||||
{
|
||||
console.assert(!isNaN(count) && count >= 0);
|
||||
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY (iOS 12): Canvas.setRecordingAutoCaptureFrameCount did not exist yet.
|
||||
if (target.hasCommand("Canvas.setRecordingAutoCaptureFrameCount"))
|
||||
target.CanvasAgent.setRecordingAutoCaptureFrameCount(enabled ? count : 0);
|
||||
}
|
||||
|
||||
WI.settings.canvasRecordingAutoCaptureEnabled.value = enabled && count;
|
||||
WI.settings.canvasRecordingAutoCaptureFrameCount.value = count;
|
||||
}
|
||||
|
||||
// CanvasObserver
|
||||
|
||||
canvasAdded(canvasPayload)
|
||||
{
|
||||
console.assert(!this._canvasIdentifierMap.has(canvasPayload.canvasId), `Canvas already exists with id ${canvasPayload.canvasId}.`);
|
||||
|
||||
let canvas = WI.Canvas.fromPayload(canvasPayload);
|
||||
this._canvasCollection.add(canvas);
|
||||
this._canvasIdentifierMap.set(canvas.identifier, canvas);
|
||||
}
|
||||
|
||||
canvasRemoved(canvasIdentifier)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.take(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
this._saveRecordings(canvas);
|
||||
|
||||
this._canvasCollection.remove(canvas);
|
||||
|
||||
for (let program of canvas.shaderProgramCollection)
|
||||
this._shaderProgramIdentifierMap.delete(program.identifier);
|
||||
|
||||
canvas.shaderProgramCollection.clear();
|
||||
}
|
||||
|
||||
canvasMemoryChanged(canvasIdentifier, memoryCost)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
canvas.memoryCost = memoryCost;
|
||||
}
|
||||
|
||||
clientNodesChanged(canvasIdentifier)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
canvas.clientNodesChanged();
|
||||
}
|
||||
|
||||
recordingStarted(canvasIdentifier, initiator)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
canvas.recordingStarted(initiator);
|
||||
}
|
||||
|
||||
recordingProgress(canvasIdentifier, framesPayload, bufferUsed)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
canvas.recordingProgress(framesPayload, bufferUsed);
|
||||
}
|
||||
|
||||
recordingFinished(canvasIdentifier, recordingPayload)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
canvas.recordingFinished(recordingPayload);
|
||||
}
|
||||
|
||||
extensionEnabled(canvasIdentifier, extension)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
canvas.enableExtension(extension);
|
||||
}
|
||||
|
||||
programCreated(shaderProgramPayload)
|
||||
{
|
||||
let canvas = this._canvasIdentifierMap.get(shaderProgramPayload.canvasId);
|
||||
console.assert(canvas);
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
let programId = shaderProgramPayload.programId;
|
||||
console.assert(!this._shaderProgramIdentifierMap.has(programId), `ShaderProgram already exists with id ${programId}.`);
|
||||
|
||||
// COMPATIBILITY (iOS 13.0): `Canvas.ShaderProgram.programType` did not exist yet.
|
||||
let programType = shaderProgramPayload.programType;
|
||||
if (!programType)
|
||||
programType = WI.ShaderProgram.ProgramType.Render;
|
||||
|
||||
let options = {};
|
||||
|
||||
// COMPATIBILITY (iOS 13.0): `Canvas.ShaderProgram.sharesVertexFragmentShader` did not exist yet.
|
||||
if (shaderProgramPayload.sharesVertexFragmentShader)
|
||||
options.sharesVertexFragmentShader = true;
|
||||
|
||||
let program = new WI.ShaderProgram(programId, programType, canvas, options);
|
||||
this._shaderProgramIdentifierMap.set(program.identifier, program);
|
||||
|
||||
canvas.shaderProgramCollection.add(program);
|
||||
}
|
||||
|
||||
programDeleted(programIdentifier)
|
||||
{
|
||||
let program = this._shaderProgramIdentifierMap.take(programIdentifier);
|
||||
console.assert(program);
|
||||
if (!program)
|
||||
return;
|
||||
|
||||
program.canvas.shaderProgramCollection.remove(program);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_saveRecordings(canvas)
|
||||
{
|
||||
for (let recording of canvas.recordingCollection) {
|
||||
recording.source = null;
|
||||
recording.createDisplayName(recording.displayName);
|
||||
this._savedRecordings.add(recording);
|
||||
this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingSaved, {recording});
|
||||
}
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
WI.Canvas.resetUniqueDisplayNameNumbers();
|
||||
|
||||
for (let canvas of this._canvasIdentifierMap.values())
|
||||
this._saveRecordings(canvas);
|
||||
|
||||
this._canvasCollection.clear();
|
||||
this._canvasIdentifierMap.clear();
|
||||
this._shaderProgramIdentifierMap.clear();
|
||||
}
|
||||
};
|
||||
|
||||
WI.CanvasManager.Event = {
|
||||
RecordingSaved: "canvas-manager-recording-saved",
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorBezierEditingController = class CodeMirrorBezierEditingController extends WI.CodeMirrorEditingController
|
||||
{
|
||||
// Public
|
||||
|
||||
get initialValue()
|
||||
{
|
||||
return WI.CubicBezier.fromString(this.text);
|
||||
}
|
||||
|
||||
get cssClassName()
|
||||
{
|
||||
return "cubic-bezier";
|
||||
}
|
||||
|
||||
popoverWillPresent(popover)
|
||||
{
|
||||
this._bezierEditor = new WI.BezierEditor;
|
||||
this._bezierEditor.addEventListener(WI.BezierEditor.Event.BezierChanged, this._bezierEditorBezierChanged, this);
|
||||
popover.content = this._bezierEditor.element;
|
||||
}
|
||||
|
||||
popoverDidPresent(popover)
|
||||
{
|
||||
this._bezierEditor.bezier = this.value;
|
||||
}
|
||||
|
||||
popoverDidDismiss(popover)
|
||||
{
|
||||
this._bezierEditor.removeListeners();
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_bezierEditorBezierChanged(event)
|
||||
{
|
||||
this.value = event.data.bezier;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorColorEditingController = class CodeMirrorColorEditingController extends WI.CodeMirrorEditingController
|
||||
{
|
||||
// Public
|
||||
|
||||
get initialValue()
|
||||
{
|
||||
return WI.Color.fromString(this.text);
|
||||
}
|
||||
|
||||
get cssClassName()
|
||||
{
|
||||
return "color";
|
||||
}
|
||||
|
||||
popoverWillPresent(popover)
|
||||
{
|
||||
this._colorPicker = new WI.ColorPicker;
|
||||
this._colorPicker.addEventListener(WI.ColorPicker.Event.ColorChanged, this._colorPickerColorChanged, this);
|
||||
popover.content = this._colorPicker.element;
|
||||
}
|
||||
|
||||
popoverDidPresent(popover)
|
||||
{
|
||||
this._colorPicker.color = this._value;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_colorPickerColorChanged(event)
|
||||
{
|
||||
this.value = event.target.color;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.CodeMirror .CodeMirror-lines .completion-hint {
|
||||
text-decoration: none !important;
|
||||
opacity: 0.4;
|
||||
}
|
||||
@@ -0,0 +1,926 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorCompletionController = class CodeMirrorCompletionController extends WI.Object
|
||||
{
|
||||
constructor(mode, codeMirror, delegate, stopCharactersRegex)
|
||||
{
|
||||
console.assert(Object.values(WI.CodeMirrorCompletionController.Mode).includes(mode), mode);
|
||||
console.assert(codeMirror instanceof CodeMirror, codeMirror);
|
||||
|
||||
super();
|
||||
|
||||
this._mode = mode;
|
||||
this._codeMirror = codeMirror;
|
||||
this._stopCharactersRegex = stopCharactersRegex || null;
|
||||
this._delegate = delegate || null;
|
||||
|
||||
this._startOffset = NaN;
|
||||
this._endOffset = NaN;
|
||||
this._lineNumber = NaN;
|
||||
this._prefix = "";
|
||||
this._noEndingSemicolon = false;
|
||||
this._completions = [];
|
||||
this._extendedCompletionProviders = {};
|
||||
|
||||
this._suggestionsView = new WI.CompletionSuggestionsView(this);
|
||||
|
||||
this._keyMap = {
|
||||
"Up": this._handleUpKey.bind(this),
|
||||
"Down": this._handleDownKey.bind(this),
|
||||
"Right": this._handleRightOrEnterKey.bind(this),
|
||||
"Esc": this._handleEscapeKey.bind(this),
|
||||
"Enter": this._handleRightOrEnterKey.bind(this),
|
||||
"Tab": this._handleTabKey.bind(this),
|
||||
"Cmd-A": this._handleHideKey.bind(this),
|
||||
"Cmd-Z": this._handleHideKey.bind(this),
|
||||
"Shift-Cmd-Z": this._handleHideKey.bind(this),
|
||||
"Cmd-Y": this._handleHideKey.bind(this)
|
||||
};
|
||||
|
||||
this._handleChangeListener = this._handleChange.bind(this);
|
||||
this._handleCursorActivityListener = this._handleCursorActivity.bind(this);
|
||||
this._handleHideActionListener = this._handleHideAction.bind(this);
|
||||
|
||||
this._codeMirror.addKeyMap(this._keyMap);
|
||||
|
||||
this._codeMirror.on("change", this._handleChangeListener);
|
||||
this._codeMirror.on("cursorActivity", this._handleCursorActivityListener);
|
||||
this._codeMirror.on("blur", this._handleHideActionListener);
|
||||
this._codeMirror.on("scroll", this._handleHideActionListener);
|
||||
|
||||
this._updatePromise = null;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get mode() { return this._mode; }
|
||||
|
||||
addExtendedCompletionProvider(modeName, provider)
|
||||
{
|
||||
this._extendedCompletionProviders[modeName] = provider;
|
||||
}
|
||||
|
||||
updateCompletions(completions, implicitSuffix)
|
||||
{
|
||||
if (isNaN(this._startOffset) || isNaN(this._endOffset) || isNaN(this._lineNumber))
|
||||
return;
|
||||
|
||||
if (!completions || !completions.length) {
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
this._completions = completions;
|
||||
|
||||
if (typeof implicitSuffix === "string")
|
||||
this._implicitSuffix = implicitSuffix;
|
||||
|
||||
var from = {line: this._lineNumber, ch: this._startOffset};
|
||||
var to = {line: this._lineNumber, ch: this._endOffset};
|
||||
|
||||
var firstCharCoords = this._codeMirror.cursorCoords(from);
|
||||
var lastCharCoords = this._codeMirror.cursorCoords(to);
|
||||
var bounds = new WI.Rect(firstCharCoords.left, firstCharCoords.top, lastCharCoords.right - firstCharCoords.left, firstCharCoords.bottom - firstCharCoords.top);
|
||||
|
||||
// Try to restore the previous selected index, otherwise just select the first.
|
||||
var index = this._currentCompletion ? completions.indexOf(this._currentCompletion) : 0;
|
||||
if (index === -1)
|
||||
index = 0;
|
||||
|
||||
if (this._forced || completions.length > 1 || completions[index] !== this._prefix) {
|
||||
// Update and show the suggestion list.
|
||||
this._suggestionsView.update(completions, index);
|
||||
this._suggestionsView.show(bounds);
|
||||
} else if (this._implicitSuffix) {
|
||||
// The prefix and the completion exactly match, but there is an implicit suffix.
|
||||
// Just hide the suggestion list and keep the completion hint for the implicit suffix.
|
||||
this._suggestionsView.hide();
|
||||
} else {
|
||||
// The prefix and the completion exactly match, hide the completions. Return early so
|
||||
// the completion hint isn't updated.
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
this._applyCompletionHint(completions[index]);
|
||||
|
||||
this._resolveUpdatePromise(WI.CodeMirrorCompletionController.UpdatePromise.CompletionsFound);
|
||||
}
|
||||
|
||||
isCompletionChange(change)
|
||||
{
|
||||
return this._ignoreChange || change.origin === WI.CodeMirrorCompletionController.CompletionOrigin || change.origin === WI.CodeMirrorCompletionController.DeleteCompletionOrigin;
|
||||
}
|
||||
|
||||
isShowingCompletions()
|
||||
{
|
||||
return this._suggestionsView.visible || (this._completionHintMarker && this._completionHintMarker.find());
|
||||
}
|
||||
|
||||
isHandlingClickEvent()
|
||||
{
|
||||
return this._suggestionsView.isHandlingClickEvent();
|
||||
}
|
||||
|
||||
commitCurrentCompletion()
|
||||
{
|
||||
this._removeCompletionHint(true, true);
|
||||
|
||||
let replacementText = this._currentReplacementText;
|
||||
if (!replacementText)
|
||||
return;
|
||||
|
||||
let from = {line: this._lineNumber, ch: this._startOffset};
|
||||
let cursor = {line: this._lineNumber, ch: this._endOffset};
|
||||
let to = {line: this._lineNumber, ch: this._startOffset + replacementText.length};
|
||||
|
||||
let lastChar = this._currentCompletion.charAt(this._currentCompletion.length - 1);
|
||||
let isClosing = ")]}".indexOf(lastChar);
|
||||
if (isClosing !== -1)
|
||||
to.ch -= 1 + this._implicitSuffix.length;
|
||||
|
||||
this._codeMirror.replaceRange(replacementText, from, cursor, WI.CodeMirrorCompletionController.CompletionOrigin);
|
||||
|
||||
// Don't call _removeLastChangeFromHistory here to allow the committed completion to be undone.
|
||||
|
||||
this._codeMirror.setCursor(to);
|
||||
|
||||
this.hideCompletions();
|
||||
}
|
||||
|
||||
hideCompletions()
|
||||
{
|
||||
this._suggestionsView.hide();
|
||||
|
||||
this._removeCompletionHint();
|
||||
|
||||
this._startOffset = NaN;
|
||||
this._endOffset = NaN;
|
||||
this._lineNumber = NaN;
|
||||
this._prefix = "";
|
||||
this._completions = [];
|
||||
this._implicitSuffix = "";
|
||||
this._forced = false;
|
||||
|
||||
delete this._currentCompletion;
|
||||
delete this._ignoreNextCursorActivity;
|
||||
|
||||
this._resolveUpdatePromise(WI.CodeMirrorCompletionController.UpdatePromise.NoCompletionsFound);
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
this._codeMirror.removeKeyMap(this._keyMap);
|
||||
|
||||
this._codeMirror.off("change", this._handleChangeListener);
|
||||
this._codeMirror.off("cursorActivity", this._handleCursorActivityListener);
|
||||
this._codeMirror.off("blur", this._handleHideActionListener);
|
||||
this._codeMirror.off("scroll", this._handleHideActionListener);
|
||||
}
|
||||
|
||||
completeAtCurrentPositionIfNeeded(force)
|
||||
{
|
||||
this._resolveUpdatePromise(WI.CodeMirrorCompletionController.UpdatePromise.Canceled);
|
||||
|
||||
var update = this._updatePromise = new WI.WrappedPromise;
|
||||
|
||||
this._completeAtCurrentPosition(force);
|
||||
|
||||
return update.promise;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
completionSuggestionsSelectedCompletion(suggestionsView, completionText)
|
||||
{
|
||||
this._applyCompletionHint(completionText);
|
||||
}
|
||||
|
||||
completionSuggestionsClickedCompletion(suggestionsView, completionText)
|
||||
{
|
||||
// The clicked suggestion causes the editor to loose focus. Restore it so the user can keep typing.
|
||||
this._codeMirror.focus();
|
||||
|
||||
this._applyCompletionHint(completionText);
|
||||
this._commitCompletionHint();
|
||||
}
|
||||
|
||||
set noEndingSemicolon(noEndingSemicolon)
|
||||
{
|
||||
this._noEndingSemicolon = noEndingSemicolon;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_resolveUpdatePromise(message)
|
||||
{
|
||||
if (!this._updatePromise)
|
||||
return;
|
||||
|
||||
this._updatePromise.resolve(message);
|
||||
this._updatePromise = null;
|
||||
}
|
||||
|
||||
get _currentReplacementText()
|
||||
{
|
||||
return (this._currentCompletion ?? "") + (this._implicitSuffix ?? "");
|
||||
}
|
||||
|
||||
_hasPendingCompletion()
|
||||
{
|
||||
return !isNaN(this._startOffset) && !isNaN(this._endOffset) && !isNaN(this._lineNumber);
|
||||
}
|
||||
|
||||
_notifyCompletionsHiddenSoon()
|
||||
{
|
||||
function notify()
|
||||
{
|
||||
if (this._completionHintMarker)
|
||||
return;
|
||||
|
||||
if (this._delegate && typeof this._delegate.completionControllerCompletionsHidden === "function")
|
||||
this._delegate.completionControllerCompletionsHidden(this);
|
||||
}
|
||||
|
||||
if (this._notifyCompletionsHiddenIfNeededTimeout)
|
||||
clearTimeout(this._notifyCompletionsHiddenIfNeededTimeout);
|
||||
this._notifyCompletionsHiddenIfNeededTimeout = setTimeout(notify.bind(this), WI.CodeMirrorCompletionController.CompletionsHiddenDelay);
|
||||
}
|
||||
|
||||
_createCompletionHintMarker(position, text)
|
||||
{
|
||||
var container = document.createElement("span");
|
||||
container.classList.add(WI.CodeMirrorCompletionController.CompletionHintStyleClassName);
|
||||
container.textContent = text;
|
||||
|
||||
container.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
this._commitCompletionHint();
|
||||
|
||||
// The clicked hint marker causes the editor to loose focus. Restore it so the user can keep typing.
|
||||
setTimeout(() => { this._codeMirror.focus(); }, 0);
|
||||
});
|
||||
|
||||
this._completionHintMarker = this._codeMirror.setUniqueBookmark(position, {widget: container, insertLeft: true});
|
||||
}
|
||||
|
||||
_applyCompletionHint(completionText)
|
||||
{
|
||||
console.assert(completionText);
|
||||
if (!completionText)
|
||||
return;
|
||||
|
||||
function update()
|
||||
{
|
||||
this._currentCompletion = completionText;
|
||||
|
||||
this._removeCompletionHint(true, true);
|
||||
|
||||
var replacementText = this._currentReplacementText;
|
||||
|
||||
var from = {line: this._lineNumber, ch: this._startOffset};
|
||||
var cursor = {line: this._lineNumber, ch: this._endOffset};
|
||||
var currentText = this._codeMirror.getRange(from, cursor);
|
||||
|
||||
this._createCompletionHintMarker(cursor, replacementText.replace(currentText, ""));
|
||||
}
|
||||
|
||||
this._ignoreChange = true;
|
||||
this._ignoreNextCursorActivity = true;
|
||||
|
||||
this._codeMirror.operation(update.bind(this));
|
||||
|
||||
delete this._ignoreChange;
|
||||
}
|
||||
|
||||
_commitCompletionHint()
|
||||
{
|
||||
this._ignoreChange = true;
|
||||
this._ignoreNextCursorActivity = true;
|
||||
|
||||
this._codeMirror.operation(this.commitCurrentCompletion.bind(this));
|
||||
|
||||
delete this._ignoreChange;
|
||||
}
|
||||
|
||||
_removeLastChangeFromHistory()
|
||||
{
|
||||
var history = this._codeMirror.getHistory();
|
||||
|
||||
// We don't expect a undone history. But if there is one clear it. If could lead to undefined behavior.
|
||||
console.assert(!history.undone.length);
|
||||
history.undone = [];
|
||||
|
||||
// Pop the last item from the done history.
|
||||
console.assert(history.done.length);
|
||||
history.done.pop();
|
||||
|
||||
this._codeMirror.setHistory(history);
|
||||
}
|
||||
|
||||
_removeCompletionHint(nonatomic, dontRestorePrefix)
|
||||
{
|
||||
if (!this._completionHintMarker)
|
||||
return;
|
||||
|
||||
this._notifyCompletionsHiddenSoon();
|
||||
|
||||
function clearMarker(marker)
|
||||
{
|
||||
if (!marker)
|
||||
return;
|
||||
|
||||
var range = marker.find();
|
||||
if (range)
|
||||
marker.clear();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function update()
|
||||
{
|
||||
this._completionHintMarker = clearMarker(this._completionHintMarker);
|
||||
|
||||
if (dontRestorePrefix)
|
||||
return;
|
||||
|
||||
console.assert(!isNaN(this._startOffset));
|
||||
console.assert(!isNaN(this._endOffset));
|
||||
console.assert(!isNaN(this._lineNumber));
|
||||
|
||||
var from = {line: this._lineNumber, ch: this._startOffset};
|
||||
var to = {line: this._lineNumber, ch: this._endOffset};
|
||||
|
||||
this._codeMirror.replaceRange(this._prefix, from, to, WI.CodeMirrorCompletionController.DeleteCompletionOrigin);
|
||||
this._removeLastChangeFromHistory();
|
||||
}
|
||||
|
||||
if (nonatomic) {
|
||||
update.call(this);
|
||||
return;
|
||||
}
|
||||
|
||||
this._ignoreChange = true;
|
||||
|
||||
this._codeMirror.operation(update.bind(this));
|
||||
|
||||
delete this._ignoreChange;
|
||||
}
|
||||
|
||||
_scanStringForExpression(modeName, string, startOffset, direction, allowMiddleAndEmpty, includeStopCharacter, ignoreInitialUnmatchedOpenBracket, stopCharactersRegex)
|
||||
{
|
||||
console.assert(direction === -1 || direction === 1);
|
||||
|
||||
var stopCharactersRegex = stopCharactersRegex || this._stopCharactersRegex || WI.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap[modeName] || WI.CodeMirrorCompletionController.GenericStopCharactersRegex;
|
||||
|
||||
function isStopCharacter(character)
|
||||
{
|
||||
return stopCharactersRegex.test(character);
|
||||
}
|
||||
|
||||
function isOpenBracketCharacter(character)
|
||||
{
|
||||
return WI.CodeMirrorCompletionController.OpenBracketCharactersRegex.test(character);
|
||||
}
|
||||
|
||||
function isCloseBracketCharacter(character)
|
||||
{
|
||||
return WI.CodeMirrorCompletionController.CloseBracketCharactersRegex.test(character);
|
||||
}
|
||||
|
||||
function matchingBracketCharacter(character)
|
||||
{
|
||||
return WI.CodeMirrorCompletionController.MatchingBrackets[character];
|
||||
}
|
||||
|
||||
var endOffset = Math.min(startOffset, string.length);
|
||||
|
||||
var endOfLineOrWord = endOffset === string.length || isStopCharacter(string.charAt(endOffset));
|
||||
|
||||
if (!endOfLineOrWord && !allowMiddleAndEmpty)
|
||||
return null;
|
||||
|
||||
var bracketStack = [];
|
||||
var bracketOffsetStack = [];
|
||||
|
||||
var startOffset = endOffset;
|
||||
var firstOffset = endOffset + direction;
|
||||
for (var i = firstOffset; direction > 0 ? i < string.length : i >= 0; i += direction) {
|
||||
var character = string.charAt(i);
|
||||
|
||||
// Ignore stop characters when we are inside brackets.
|
||||
if (isStopCharacter(character) && !bracketStack.length)
|
||||
break;
|
||||
|
||||
if (isCloseBracketCharacter(character)) {
|
||||
bracketStack.push(character);
|
||||
bracketOffsetStack.push(i);
|
||||
} else if (isOpenBracketCharacter(character)) {
|
||||
if ((!ignoreInitialUnmatchedOpenBracket || i !== firstOffset) && (!bracketStack.length || matchingBracketCharacter(character) !== bracketStack.lastValue))
|
||||
break;
|
||||
|
||||
bracketOffsetStack.pop();
|
||||
bracketStack.pop();
|
||||
}
|
||||
|
||||
startOffset = i + (direction > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
if (bracketOffsetStack.length)
|
||||
startOffset = bracketOffsetStack.pop() + 1;
|
||||
|
||||
if (includeStopCharacter && startOffset > 0 && startOffset < string.length)
|
||||
startOffset += direction;
|
||||
|
||||
if (direction > 0) {
|
||||
var tempEndOffset = endOffset;
|
||||
endOffset = startOffset;
|
||||
startOffset = tempEndOffset;
|
||||
}
|
||||
|
||||
return {string: string.substring(startOffset, endOffset), startOffset, endOffset};
|
||||
}
|
||||
|
||||
_completeAtCurrentPosition(force)
|
||||
{
|
||||
if (this._codeMirror.somethingSelected()) {
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
this._removeCompletionHint(true, true);
|
||||
|
||||
var cursor = this._codeMirror.getCursor();
|
||||
var token = this._codeMirror.getTokenAt(cursor);
|
||||
|
||||
// Don't try to complete inside comments or strings.
|
||||
if (token.type && /\b(comment|string)\b/.test(token.type)) {
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
var mode = this._codeMirror.getMode();
|
||||
var innerMode = CodeMirror.innerMode(mode, token.state).mode;
|
||||
var modeName = innerMode.alternateName || innerMode.name;
|
||||
|
||||
var lineNumber = cursor.line;
|
||||
var lineString = this._codeMirror.getLine(lineNumber);
|
||||
|
||||
var backwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, -1, force);
|
||||
if (!backwardScanResult) {
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
var forwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, 1, true, true);
|
||||
var suffix = forwardScanResult.string;
|
||||
|
||||
this._ignoreNextCursorActivity = true;
|
||||
|
||||
this._startOffset = backwardScanResult.startOffset;
|
||||
this._endOffset = backwardScanResult.endOffset;
|
||||
this._lineNumber = lineNumber;
|
||||
this._prefix = backwardScanResult.string;
|
||||
this._completions = [];
|
||||
this._implicitSuffix = "";
|
||||
this._forced = force;
|
||||
|
||||
var baseExpressionStopCharactersRegex = WI.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap[modeName];
|
||||
if (baseExpressionStopCharactersRegex)
|
||||
var baseScanResult = this._scanStringForExpression(modeName, lineString, this._startOffset, -1, true, false, true, baseExpressionStopCharactersRegex);
|
||||
|
||||
if (!force && !backwardScanResult.string && (!baseScanResult || !baseScanResult.string)) {
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultCompletions = [];
|
||||
|
||||
switch (modeName) {
|
||||
case "css":
|
||||
defaultCompletions = this._generateCSSCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
|
||||
break;
|
||||
case "javascript":
|
||||
defaultCompletions = this._generateJavaScriptCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
|
||||
break;
|
||||
}
|
||||
|
||||
var extendedCompletionsProvider = this._extendedCompletionProviders[modeName];
|
||||
if (extendedCompletionsProvider) {
|
||||
extendedCompletionsProvider.completionControllerCompletionsNeeded(this, defaultCompletions, baseScanResult ? baseScanResult.string : null, this._prefix, suffix, force);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._delegate && typeof this._delegate.completionControllerCompletionsNeeded === "function")
|
||||
this._delegate.completionControllerCompletionsNeeded(this, this._prefix, defaultCompletions, baseScanResult ? baseScanResult.string : null, suffix, force);
|
||||
else
|
||||
this.updateCompletions(defaultCompletions);
|
||||
}
|
||||
|
||||
_generateCSSCompletions(mainToken, base, suffix)
|
||||
{
|
||||
// We support completion inside CSS block context and functions.
|
||||
if (mainToken.state.state === "media" || mainToken.state.state === "top")
|
||||
return [];
|
||||
|
||||
// Don't complete in the middle of a property name.
|
||||
if (/^[a-z]/i.test(suffix))
|
||||
return [];
|
||||
|
||||
var token = mainToken;
|
||||
var lineNumber = this._lineNumber;
|
||||
|
||||
let getPreviousToken = () => {
|
||||
// Found the beginning of the line. Go to the previous line.
|
||||
if (!token.start) {
|
||||
--lineNumber;
|
||||
|
||||
// No more lines, stop.
|
||||
if (lineNumber < 0)
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._codeMirror.getTokenAt({line: lineNumber, ch: token.start ? token.start : Number.MAX_VALUE});
|
||||
};
|
||||
|
||||
// Inside a function, determine the function name.
|
||||
if (token.state.state === "parens") {
|
||||
// Scan backwards looking for the function paren boundary.
|
||||
while (token && token.state.state === "parens" && token.string !== "(")
|
||||
token = getPreviousToken();
|
||||
|
||||
// The immediately preceding token should have the function name.
|
||||
if (token)
|
||||
token = getPreviousToken();
|
||||
|
||||
// No completions if no function name found.
|
||||
if (!token)
|
||||
return [];
|
||||
|
||||
let functionName = token.string;
|
||||
if (!functionName)
|
||||
return [];
|
||||
|
||||
let functionCompletions = WI.CSSKeywordCompletions.forFunction(functionName).startsWith(this._prefix);
|
||||
|
||||
if (this._delegate && this._delegate.completionControllerCSSFunctionValuesNeeded)
|
||||
functionCompletions = this._delegate.completionControllerCSSFunctionValuesNeeded(this, functionName, functionCompletions);
|
||||
|
||||
return functionCompletions;
|
||||
}
|
||||
|
||||
// Scan backwards looking for the current property.
|
||||
while (token.state.state === "prop") {
|
||||
let previousToken = getPreviousToken();
|
||||
if (!previousToken)
|
||||
break;
|
||||
token = previousToken;
|
||||
}
|
||||
|
||||
// If we have a property token and it's not the main token, then we are working on
|
||||
// the value for that property and should complete allowed values.
|
||||
if (mainToken !== token && token.type && /\bproperty\b/.test(token.type)) {
|
||||
var propertyName = token.string;
|
||||
|
||||
// If there is a suffix and it isn't a semicolon, then we should use a space since
|
||||
// the user is editing in the middle. Likewise if the suffix starts with an open
|
||||
// paren we are changing a function name so don't add a suffix.
|
||||
this._implicitSuffix = " ";
|
||||
if (suffix === ";")
|
||||
this._implicitSuffix = this._noEndingSemicolon ? "" : ";";
|
||||
else if (suffix.startsWith("("))
|
||||
this._implicitSuffix = "";
|
||||
|
||||
// Don't use an implicit suffix if it would be the same as the existing suffix.
|
||||
if (this._implicitSuffix === suffix)
|
||||
this._implicitSuffix = "";
|
||||
|
||||
let completions = WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(this._prefix);
|
||||
|
||||
if (suffix.startsWith("("))
|
||||
completions = completions.map((x) => x.replace(/\(\)$/, ""));
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
this._implicitSuffix = suffix !== ":" ? ": " : "";
|
||||
|
||||
// Complete property names.
|
||||
return WI.cssManager.propertyNameCompletions.startsWith(this._prefix);
|
||||
}
|
||||
|
||||
_generateJavaScriptCompletions(mainToken, base, suffix)
|
||||
{
|
||||
// If there is a base expression then we should not attempt to match any keywords or variables.
|
||||
// Allow only open bracket characters at the end of the base, otherwise leave completions with
|
||||
// a base up to the delegate to figure out.
|
||||
if (base && !/[({[]$/.test(base))
|
||||
return [];
|
||||
|
||||
var matchingWords = [];
|
||||
|
||||
var prefix = this._prefix;
|
||||
|
||||
var localState = mainToken.state.localState ? mainToken.state.localState : mainToken.state;
|
||||
|
||||
var declaringVariable = localState.lexical.type === "vardef";
|
||||
var insideSwitch = localState.lexical.prev ? localState.lexical.prev.info === "switch" : false;
|
||||
var insideBlock = localState.lexical.prev ? localState.lexical.prev.type === "}" : false;
|
||||
var insideParenthesis = localState.lexical.type === ")";
|
||||
var insideBrackets = localState.lexical.type === "]";
|
||||
|
||||
// FIXME: Include module keywords if we know this is a module environment.
|
||||
// var moduleKeywords = ["default", "export", "import"];
|
||||
|
||||
const allKeywords = [
|
||||
"break", "case", "catch", "class", "const", "continue", "debugger", "default",
|
||||
"delete", "do", "else", "extends", "false", "finally", "for", "function",
|
||||
"if", "in", "Infinity", "instanceof", "let", "NaN", "new", "null", "of",
|
||||
"return", "static", "super", "switch", "this", "throw", "true", "try",
|
||||
"typeof", "undefined", "var", "void", "while", "with", "yield"
|
||||
];
|
||||
const valueKeywords = ["false", "Infinity", "NaN", "null", "this", "true", "undefined", "globalThis"];
|
||||
|
||||
const allowedKeywordsInsideBlocks = new Set(allKeywords);
|
||||
const allowedKeywordsWhenDeclaringVariable = new Set(valueKeywords);
|
||||
const allowedKeywordsInsideParenthesis = new Set(valueKeywords.concat(["class", "function"]));
|
||||
const allowedKeywordsInsideBrackets = allowedKeywordsInsideParenthesis;
|
||||
const allowedKeywordsOnlyInsideSwitch = new Set(["case", "default"]);
|
||||
|
||||
function matchKeywords(keywords)
|
||||
{
|
||||
for (let keyword of keywords) {
|
||||
if (!insideSwitch && allowedKeywordsOnlyInsideSwitch.has(keyword))
|
||||
continue;
|
||||
if (insideBlock && !allowedKeywordsInsideBlocks.has(keyword))
|
||||
continue;
|
||||
if (insideBrackets && !allowedKeywordsInsideBrackets.has(keyword))
|
||||
continue;
|
||||
if (insideParenthesis && !allowedKeywordsInsideParenthesis.has(keyword))
|
||||
continue;
|
||||
if (declaringVariable && !allowedKeywordsWhenDeclaringVariable.has(keyword))
|
||||
continue;
|
||||
if (!keyword.startsWith(prefix))
|
||||
continue;
|
||||
matchingWords.push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
function matchVariables()
|
||||
{
|
||||
function filterVariables(variables)
|
||||
{
|
||||
for (var variable = variables; variable; variable = variable.next) {
|
||||
// Don't match the variable if this token is in a variable declaration.
|
||||
// Otherwise the currently typed text will always match and that isn't useful.
|
||||
if (declaringVariable && variable.name === prefix)
|
||||
continue;
|
||||
|
||||
if (variable.name.startsWith(prefix) && !matchingWords.includes(variable.name))
|
||||
matchingWords.push(variable.name);
|
||||
}
|
||||
}
|
||||
|
||||
var context = localState.context;
|
||||
while (context) {
|
||||
if (context.vars)
|
||||
filterVariables(context.vars);
|
||||
context = context.prev;
|
||||
}
|
||||
|
||||
if (localState.localVars)
|
||||
filterVariables(localState.localVars);
|
||||
if (localState.globalVars)
|
||||
filterVariables(localState.globalVars);
|
||||
}
|
||||
|
||||
switch (suffix.substring(0, 1)) {
|
||||
case "":
|
||||
case " ":
|
||||
matchVariables();
|
||||
matchKeywords(allKeywords);
|
||||
break;
|
||||
|
||||
case ".":
|
||||
case "[":
|
||||
matchVariables();
|
||||
matchKeywords(["false", "Infinity", "NaN", "this", "true"]);
|
||||
break;
|
||||
|
||||
case "(":
|
||||
matchVariables();
|
||||
matchKeywords(["catch", "else", "for", "function", "if", "return", "switch", "throw", "while", "with", "yield"]);
|
||||
break;
|
||||
|
||||
case "{":
|
||||
matchKeywords(["do", "else", "finally", "return", "try", "yield"]);
|
||||
break;
|
||||
|
||||
case ":":
|
||||
if (insideSwitch)
|
||||
matchKeywords(["case", "default"]);
|
||||
break;
|
||||
|
||||
case ";":
|
||||
matchVariables();
|
||||
matchKeywords(valueKeywords);
|
||||
matchKeywords(["break", "continue", "debugger", "return", "void"]);
|
||||
break;
|
||||
}
|
||||
|
||||
return matchingWords;
|
||||
}
|
||||
|
||||
_handleUpKey(codeMirror)
|
||||
{
|
||||
if (!this._hasPendingCompletion())
|
||||
return CodeMirror.Pass;
|
||||
|
||||
if (!this.isShowingCompletions())
|
||||
return;
|
||||
|
||||
this._suggestionsView.selectPrevious();
|
||||
}
|
||||
|
||||
_handleDownKey(codeMirror)
|
||||
{
|
||||
if (!this._hasPendingCompletion())
|
||||
return CodeMirror.Pass;
|
||||
|
||||
if (!this.isShowingCompletions())
|
||||
return;
|
||||
|
||||
this._suggestionsView.selectNext();
|
||||
}
|
||||
|
||||
_handleRightOrEnterKey(codeMirror)
|
||||
{
|
||||
if (!this._hasPendingCompletion())
|
||||
return CodeMirror.Pass;
|
||||
|
||||
if (!this.isShowingCompletions())
|
||||
return;
|
||||
|
||||
this._commitCompletionHint();
|
||||
}
|
||||
|
||||
_handleEscapeKey(codeMirror)
|
||||
{
|
||||
var delegateImplementsShouldAllowEscapeCompletion = this._delegate && typeof this._delegate.completionControllerShouldAllowEscapeCompletion === "function";
|
||||
if (this._hasPendingCompletion())
|
||||
this.hideCompletions();
|
||||
else if (this._codeMirror.getOption("readOnly"))
|
||||
return CodeMirror.Pass;
|
||||
else if (!delegateImplementsShouldAllowEscapeCompletion || this._delegate.completionControllerShouldAllowEscapeCompletion(this))
|
||||
this._completeAtCurrentPosition(true);
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
_handleTabKey(codeMirror)
|
||||
{
|
||||
if (!this._hasPendingCompletion())
|
||||
return CodeMirror.Pass;
|
||||
|
||||
if (!this.isShowingCompletions())
|
||||
return;
|
||||
|
||||
console.assert(this._completions.length);
|
||||
if (!this._completions.length)
|
||||
return;
|
||||
|
||||
console.assert(this._currentCompletion);
|
||||
if (!this._currentCompletion)
|
||||
return;
|
||||
|
||||
// Commit the current completion if there is only one suggestion.
|
||||
if (this._completions.length === 1) {
|
||||
this._commitCompletionHint();
|
||||
return;
|
||||
}
|
||||
|
||||
var prefixLength = this._prefix.length;
|
||||
|
||||
var commonPrefix = this._completions[0];
|
||||
for (var i = 1; i < this._completions.length; ++i) {
|
||||
var completion = this._completions[i];
|
||||
var lastIndex = Math.min(commonPrefix.length, completion.length);
|
||||
for (var j = prefixLength; j < lastIndex; ++j) {
|
||||
if (commonPrefix[j] !== completion[j]) {
|
||||
commonPrefix = commonPrefix.substr(0, j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the current completion if there is no common prefix that is longer.
|
||||
if (commonPrefix === this._prefix) {
|
||||
this._commitCompletionHint();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the prefix to the common prefix so _applyCompletionHint will insert the
|
||||
// common prefix as commited text. Adjust _endOffset to match the new prefix.
|
||||
this._prefix = commonPrefix;
|
||||
this._endOffset = this._startOffset + commonPrefix.length;
|
||||
|
||||
this._applyCompletionHint(this._currentCompletion);
|
||||
}
|
||||
|
||||
_handleChange(codeMirror, change)
|
||||
{
|
||||
if (this.isCompletionChange(change))
|
||||
return;
|
||||
|
||||
this._ignoreNextCursorActivity = true;
|
||||
|
||||
if (!change.origin || change.origin.charAt(0) !== "+") {
|
||||
this.hideCompletions();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only complete on delete if we are showing completions already.
|
||||
if (change.origin === "+delete" && !this._hasPendingCompletion())
|
||||
return;
|
||||
|
||||
this._completeAtCurrentPosition(false);
|
||||
}
|
||||
|
||||
_handleCursorActivity(codeMirror)
|
||||
{
|
||||
if (this._ignoreChange)
|
||||
return;
|
||||
|
||||
if (this._ignoreNextCursorActivity) {
|
||||
delete this._ignoreNextCursorActivity;
|
||||
return;
|
||||
}
|
||||
|
||||
this.hideCompletions();
|
||||
}
|
||||
|
||||
_handleHideKey(codeMirror)
|
||||
{
|
||||
this.hideCompletions();
|
||||
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
_handleHideAction(codeMirror)
|
||||
{
|
||||
// Clicking a suggestion causes the editor to blur. We don't want to hide completions in this case.
|
||||
if (this.isHandlingClickEvent())
|
||||
return;
|
||||
|
||||
this.hideCompletions();
|
||||
}
|
||||
};
|
||||
|
||||
WI.CodeMirrorCompletionController.Mode = {
|
||||
Basic: "basic",
|
||||
EventBreakpoint: "event-breakpoint",
|
||||
ExceptionBreakpoint: "exception-breakpoint",
|
||||
FullConsoleCommandLineAPI: "full-console-command-line-api",
|
||||
PausedConsoleCommandLineAPI: "paused-console-command-line-api",
|
||||
};
|
||||
|
||||
WI.CodeMirrorCompletionController.UpdatePromise = {
|
||||
Canceled: "code-mirror-completion-controller-canceled",
|
||||
CompletionsFound: "code-mirror-completion-controller-completions-found",
|
||||
NoCompletionsFound: "code-mirror-completion-controller-no-completions-found"
|
||||
};
|
||||
|
||||
WI.CodeMirrorCompletionController.GenericStopCharactersRegex = /[\s=:;,]/;
|
||||
WI.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap = {"css": /[\s:;,{}()]/, "javascript": /[\s=:;,!+\-*/%&|^~?<>.{}()[\]]/};
|
||||
WI.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap = {"javascript": /[\s=:;,!+\-*/%&|^~?<>]/};
|
||||
WI.CodeMirrorCompletionController.OpenBracketCharactersRegex = /[({[]/;
|
||||
WI.CodeMirrorCompletionController.CloseBracketCharactersRegex = /[)}\]]/;
|
||||
WI.CodeMirrorCompletionController.MatchingBrackets = {"{": "}", "(": ")", "[": "]", "}": "{", ")": "(", "]": "["};
|
||||
WI.CodeMirrorCompletionController.CompletionHintStyleClassName = "completion-hint";
|
||||
WI.CodeMirrorCompletionController.CompletionsHiddenDelay = 250;
|
||||
WI.CodeMirrorCompletionController.CompletionTypingDelay = 250;
|
||||
WI.CodeMirrorCompletionController.CompletionOrigin = "+completion";
|
||||
WI.CodeMirrorCompletionController.DeleteCompletionOrigin = "+delete-completion";
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.CodeMirror.drag-to-adjust .CodeMirror-lines {
|
||||
cursor: col-resize;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorDragToAdjustNumberController = class CodeMirrorDragToAdjustNumberController extends WI.Object
|
||||
{
|
||||
constructor(codeMirror)
|
||||
{
|
||||
super();
|
||||
|
||||
this._codeMirror = codeMirror;
|
||||
|
||||
this._dragToAdjustController = new WI.DragToAdjustController(this);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get enabled()
|
||||
{
|
||||
return this._dragToAdjustController.enabled;
|
||||
}
|
||||
|
||||
set enabled(enabled)
|
||||
{
|
||||
if (this.enabled === enabled)
|
||||
return;
|
||||
|
||||
this._dragToAdjustController.element = this._codeMirror.getWrapperElement();
|
||||
this._dragToAdjustController.enabled = enabled;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
dragToAdjustControllerActiveStateChanged(dragToAdjustController)
|
||||
{
|
||||
if (!dragToAdjustController.active)
|
||||
this._hoveredTokenInfo = null;
|
||||
}
|
||||
|
||||
dragToAdjustControllerCanBeActivated(dragToAdjustController)
|
||||
{
|
||||
return !this._codeMirror.getOption("readOnly");
|
||||
}
|
||||
|
||||
dragToAdjustControllerCanBeAdjusted(dragToAdjustController)
|
||||
{
|
||||
|
||||
return this._hoveredTokenInfo && this._hoveredTokenInfo.containsNumber;
|
||||
}
|
||||
|
||||
dragToAdjustControllerWasAdjustedByAmount(dragToAdjustController, amount)
|
||||
{
|
||||
this._codeMirror.alterNumberInRange(amount, this._hoveredTokenInfo.startPosition, this._hoveredTokenInfo.endPosition, false);
|
||||
}
|
||||
|
||||
dragToAdjustControllerDidReset(dragToAdjustController)
|
||||
{
|
||||
this._hoveredTokenInfo = null;
|
||||
}
|
||||
|
||||
dragToAdjustControllerCanAdjustObjectAtPoint(dragToAdjustController, point)
|
||||
{
|
||||
var position = this._codeMirror.coordsChar({left: point.x, top: point.y});
|
||||
var token = this._codeMirror.getTokenAt(position);
|
||||
|
||||
if (!token || !token.type || !token.string) {
|
||||
if (this._hoveredTokenInfo)
|
||||
dragToAdjustController.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stop right here if we're hovering the same token as we were last time.
|
||||
if (this._hoveredTokenInfo && this._hoveredTokenInfo.line === position.line &&
|
||||
this._hoveredTokenInfo.token.start === token.start && this._hoveredTokenInfo.token.end === token.end)
|
||||
return this._hoveredTokenInfo.token.type.indexOf("number") !== -1;
|
||||
|
||||
var containsNumber = token.type.indexOf("number") !== -1;
|
||||
this._hoveredTokenInfo = {
|
||||
token,
|
||||
line: position.line,
|
||||
containsNumber,
|
||||
startPosition: {
|
||||
ch: token.start,
|
||||
line: position.line
|
||||
},
|
||||
endPosition: {
|
||||
ch: token.end,
|
||||
line: position.line
|
||||
}
|
||||
};
|
||||
|
||||
return containsNumber;
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.defineOption("dragToAdjustNumbers", true, function(codeMirror, value, oldValue) {
|
||||
if (!codeMirror.dragToAdjustNumberController)
|
||||
codeMirror.dragToAdjustNumberController = new WI.CodeMirrorDragToAdjustNumberController(codeMirror);
|
||||
codeMirror.dragToAdjustNumberController.enabled = value;
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorEditingController = class CodeMirrorEditingController extends WI.Object
|
||||
{
|
||||
constructor(codeMirror, marker)
|
||||
{
|
||||
super();
|
||||
|
||||
this._codeMirror = codeMirror;
|
||||
this._marker = marker;
|
||||
this._delegate = null;
|
||||
|
||||
this._range = marker.range;
|
||||
|
||||
// The value must support .toString() and .copy() methods.
|
||||
this._value = this.initialValue;
|
||||
|
||||
this._keyboardShortcutEsc = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Escape);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get marker()
|
||||
{
|
||||
return this._marker;
|
||||
}
|
||||
|
||||
get range()
|
||||
{
|
||||
return this._range;
|
||||
}
|
||||
|
||||
get value()
|
||||
{
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value)
|
||||
{
|
||||
this.text = value.toString();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
get delegate()
|
||||
{
|
||||
return this._delegate;
|
||||
}
|
||||
|
||||
set delegate(delegate)
|
||||
{
|
||||
this._delegate = delegate;
|
||||
}
|
||||
|
||||
get text()
|
||||
{
|
||||
var from = {line: this._range.startLine, ch: this._range.startColumn};
|
||||
var to = {line: this._range.endLine, ch: this._range.endColumn};
|
||||
return this._codeMirror.getRange(from, to);
|
||||
}
|
||||
|
||||
set text(text)
|
||||
{
|
||||
var from = {line: this._range.startLine, ch: this._range.startColumn};
|
||||
var to = {line: this._range.endLine, ch: this._range.endColumn};
|
||||
this._codeMirror.replaceRange(text, from, to);
|
||||
|
||||
var lines = text.split("\n");
|
||||
var endLine = this._range.startLine + lines.length - 1;
|
||||
var endColumn = lines.length > 1 ? lines.lastValue.length : this._range.startColumn + text.length;
|
||||
this._range = new WI.TextRange(this._range.startLine, this._range.startColumn, endLine, endColumn);
|
||||
}
|
||||
|
||||
get initialValue()
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
return this.text;
|
||||
}
|
||||
|
||||
get cssClassName()
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
return "";
|
||||
}
|
||||
|
||||
get popover()
|
||||
{
|
||||
return this._popover;
|
||||
}
|
||||
|
||||
get popoverPreferredEdges()
|
||||
{
|
||||
// Best to display the popover to the left or above the edited range since its end position may change, but not its start
|
||||
// position. This way we minimize the chances of overlaying the edited range as it changes.
|
||||
return [WI.RectEdge.MIN_X, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X];
|
||||
}
|
||||
|
||||
popoverTargetFrameWithRects(rects)
|
||||
{
|
||||
return WI.Rect.unionOfRects(rects);
|
||||
}
|
||||
|
||||
presentHoverMenu()
|
||||
{
|
||||
if (!this.cssClassName)
|
||||
return;
|
||||
|
||||
this._hoverMenu = new WI.HoverMenu(this);
|
||||
this._hoverMenu.element.classList.add(this.cssClassName);
|
||||
this._rects = this._marker.rects;
|
||||
this._hoverMenu.present(this._rects);
|
||||
}
|
||||
|
||||
dismissHoverMenu(discrete)
|
||||
{
|
||||
if (!this._hoverMenu)
|
||||
return;
|
||||
|
||||
this._hoverMenu.dismiss(discrete);
|
||||
}
|
||||
|
||||
popoverWillPresent(popover)
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
}
|
||||
|
||||
popoverDidPresent(popover)
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
}
|
||||
|
||||
popoverDidDismiss(popover)
|
||||
{
|
||||
// Implemented by subclasses.
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
handleKeydownEvent(event)
|
||||
{
|
||||
if (!this._keyboardShortcutEsc.matchesEvent(event) || !this._popover.visible)
|
||||
return false;
|
||||
|
||||
this.value = this._originalValue;
|
||||
this._popover.dismiss();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hoverMenuButtonWasPressed(hoverMenu)
|
||||
{
|
||||
this._popover = new WI.Popover(this);
|
||||
this.popoverWillPresent(this._popover);
|
||||
this._popover.present(this.popoverTargetFrameWithRects(this._rects).pad(2), this.popoverPreferredEdges);
|
||||
this.popoverDidPresent(this._popover);
|
||||
|
||||
WI.addWindowKeydownListener(this);
|
||||
|
||||
hoverMenu.dismiss();
|
||||
|
||||
if (this._delegate && typeof this._delegate.editingControllerDidStartEditing === "function")
|
||||
this._delegate.editingControllerDidStartEditing(this);
|
||||
|
||||
this._originalValue = this._value.copy();
|
||||
}
|
||||
|
||||
didDismissPopover(popover)
|
||||
{
|
||||
delete this._popover;
|
||||
delete this._originalValue;
|
||||
|
||||
WI.removeWindowKeydownListener(this);
|
||||
this.popoverDidDismiss();
|
||||
|
||||
if (this._delegate && typeof this._delegate.editingControllerDidFinishEditing === "function")
|
||||
this._delegate.editingControllerDidFinishEditing(this);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorGradientEditingController = class CodeMirrorGradientEditingController extends WI.CodeMirrorEditingController
|
||||
{
|
||||
// Public
|
||||
|
||||
get initialValue()
|
||||
{
|
||||
return WI.Gradient.fromString(this.text);
|
||||
}
|
||||
|
||||
get cssClassName()
|
||||
{
|
||||
return "gradient";
|
||||
}
|
||||
|
||||
get popoverPreferredEdges()
|
||||
{
|
||||
// Since the gradient editor can resize to be quite tall, let's avoid displaying the popover
|
||||
// above the edited value so that it may not change which edge it attaches to upon editing a stop.
|
||||
return [WI.RectEdge.MIN_X, WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X];
|
||||
}
|
||||
|
||||
popoverTargetFrameWithRects(rects)
|
||||
{
|
||||
// If a gradient is defined across several lines, we probably want to use the first line only
|
||||
// as a target frame for the editor since we may reformat the gradient value to fit on a single line.
|
||||
return rects[0];
|
||||
}
|
||||
|
||||
popoverWillPresent(popover)
|
||||
{
|
||||
function handleColorPickerToggled(event)
|
||||
{
|
||||
popover.update();
|
||||
}
|
||||
|
||||
this._gradientEditor = new WI.GradientEditor;
|
||||
this._gradientEditor.addEventListener(WI.GradientEditor.Event.GradientChanged, this._gradientEditorGradientChanged, this);
|
||||
this._gradientEditor.addEventListener(WI.GradientEditor.Event.ColorPickerToggled, handleColorPickerToggled, this);
|
||||
popover.content = this._gradientEditor.element;
|
||||
}
|
||||
|
||||
popoverDidPresent(popover)
|
||||
{
|
||||
this._gradientEditor.gradient = this.value;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_gradientEditorGradientChanged(event)
|
||||
{
|
||||
this.value = event.data.gradient;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Devin Rousso <webkit@devinrousso.com>. 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorSpringEditingController = class CodeMirrorSpringEditingController extends WI.CodeMirrorEditingController
|
||||
{
|
||||
// Public
|
||||
|
||||
get initialValue()
|
||||
{
|
||||
return WI.Spring.fromString(this.text);
|
||||
}
|
||||
|
||||
get cssClassName()
|
||||
{
|
||||
return "spring";
|
||||
}
|
||||
|
||||
popoverWillPresent(popover)
|
||||
{
|
||||
this._springEditor = new WI.SpringEditor;
|
||||
this._springEditor.addEventListener(WI.SpringEditor.Event.SpringChanged, this._springEditorSpringChanged, this);
|
||||
popover.content = this._springEditor.element;
|
||||
}
|
||||
|
||||
popoverDidPresent(popover)
|
||||
{
|
||||
this._springEditor.spring = this.value;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_springEditorSpringChanged(event)
|
||||
{
|
||||
this.value = event.data.spring;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorTextKillController = class CodeMirrorTextKillController extends WI.Object
|
||||
{
|
||||
constructor(codeMirror)
|
||||
{
|
||||
super();
|
||||
|
||||
console.assert(codeMirror);
|
||||
|
||||
this._codeMirror = codeMirror;
|
||||
this._expectingChangeEventForKill = false;
|
||||
this._nextKillStartsNewSequence = true;
|
||||
this._shouldPrependToKillRing = false;
|
||||
|
||||
this._handleTextChangeListener = this._handleTextChange.bind(this);
|
||||
this._handleEditorBlurListener = this._handleEditorBlur.bind(this);
|
||||
this._handleSelectionOrCaretChangeListener = this._handleSelectionOrCaretChange.bind(this);
|
||||
|
||||
// FIXME: these keybindings match CodeMirror's default keymap for OS X.
|
||||
// They should probably be altered for Windows / Linux someday.
|
||||
this._codeMirror.addKeyMap({
|
||||
// Overrides for the 'emacsy' keymap.
|
||||
"Ctrl-K": this._handleTextKillCommand.bind(this, "killLine", false),
|
||||
"Alt-D": this._handleTextKillCommand.bind(this, "delWordAfter", false),
|
||||
// Overrides for the 'macDefault' keymap.
|
||||
"Alt-Delete": this._handleTextKillCommand.bind(this, "delGroupAfter", false),
|
||||
"Cmd-Backspace": this._handleTextKillCommand.bind(this, "delWrappedLineLeft", true),
|
||||
"Cmd-Delete": this._handleTextKillCommand.bind(this, "delWrappedLineRight", false),
|
||||
"Alt-Backspace": this._handleTextKillCommand.bind(this, "delGroupBefore", true),
|
||||
"Ctrl-Alt-Backspace": this._handleTextKillCommand.bind(this, "delGroupAfter", false),
|
||||
});
|
||||
}
|
||||
|
||||
_handleTextKillCommand(command, prependsToKillRing, codeMirror)
|
||||
{
|
||||
// Read-only mode is dynamic in some editors, so check every time
|
||||
// and ignore the shortcut if in read-only mode.
|
||||
if (this._codeMirror.getOption("readOnly"))
|
||||
return;
|
||||
|
||||
this._shouldPrependToKillRing = prependsToKillRing;
|
||||
|
||||
// Don't add the listener if it's still registered because
|
||||
// a previous empty kill didn't generate change events.
|
||||
if (!this._expectingChangeEventForKill)
|
||||
this._codeMirror.on("changes", this._handleTextChangeListener);
|
||||
|
||||
this._expectingChangeEventForKill = true;
|
||||
this._codeMirror.execCommand(command);
|
||||
}
|
||||
|
||||
_handleTextChange(codeMirror, changes)
|
||||
{
|
||||
this._codeMirror.off("changes", this._handleTextChangeListener);
|
||||
|
||||
// Sometimes a second change event fires after removing the listener
|
||||
// if you perform an "empty kill" and type after moving the caret.
|
||||
if (!this._expectingChangeEventForKill)
|
||||
return;
|
||||
|
||||
this._expectingChangeEventForKill = false;
|
||||
|
||||
// It doesn't make sense to get more than one change per kill.
|
||||
console.assert(changes.length === 1);
|
||||
let change = changes[0];
|
||||
|
||||
// If an "empty kill" is followed by up/down or typing,
|
||||
// the empty kill won't fire a change event, then we'll get an
|
||||
// unrelated change event that shouldn't be treated as a kill.
|
||||
if (change.origin !== "+delete")
|
||||
return;
|
||||
|
||||
// When killed text includes a newline, CodeMirror returns
|
||||
// strange change objects. Special-case for when this could happen.
|
||||
let killedText;
|
||||
if (change.to.line === change.from.line + 1 && change.removed.length === 2) {
|
||||
// An entire line was deleted, including newline (deleteLine).
|
||||
if (change.removed[0].length && !change.removed[1].length)
|
||||
killedText = change.removed[0] + "\n";
|
||||
// A newline was killed by itself (Ctrl-K).
|
||||
else
|
||||
killedText = "\n";
|
||||
} else {
|
||||
console.assert(change.removed.length === 1);
|
||||
killedText = change.removed[0];
|
||||
}
|
||||
|
||||
InspectorFrontendHost.killText(killedText, this._shouldPrependToKillRing, this._nextKillStartsNewSequence);
|
||||
|
||||
// If the editor loses focus or the caret / selection changes
|
||||
// (not as a result of the kill), then the next kill should
|
||||
// start a new kill ring sequence.
|
||||
this._nextKillStartsNewSequence = false;
|
||||
this._codeMirror.on("blur", this._handleEditorBlurListener);
|
||||
this._codeMirror.on("cursorActivity", this._handleSelectionOrCaretChangeListener);
|
||||
}
|
||||
|
||||
_handleEditorBlur(codeMirror)
|
||||
{
|
||||
this._nextKillStartsNewSequence = true;
|
||||
this._codeMirror.off("blur", this._handleEditorBlurListener);
|
||||
this._codeMirror.off("cursorActivity", this._handleSelectionOrCaretChangeListener);
|
||||
}
|
||||
|
||||
_handleSelectionOrCaretChange(codeMirror)
|
||||
{
|
||||
if (this._expectingChangeEventForKill)
|
||||
return;
|
||||
|
||||
this._nextKillStartsNewSequence = true;
|
||||
this._codeMirror.off("blur", this._handleEditorBlurListener);
|
||||
this._codeMirror.off("cursorActivity", this._handleSelectionOrCaretChangeListener);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.CodeMirror .jump-to-symbol-highlight {
|
||||
color: blue !important;
|
||||
text-decoration: underline !important;
|
||||
cursor: pointer !important;
|
||||
-webkit-text-stroke-width: 0 !important;
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.CodeMirrorTokenTrackingController = class CodeMirrorTokenTrackingController extends WI.Object
|
||||
{
|
||||
constructor(codeMirror, delegate)
|
||||
{
|
||||
super();
|
||||
|
||||
console.assert(codeMirror);
|
||||
|
||||
this._codeMirror = codeMirror;
|
||||
this._delegate = delegate || null;
|
||||
this._mode = WI.CodeMirrorTokenTrackingController.Mode.None;
|
||||
|
||||
this._mouseOverDelayDuration = 0;
|
||||
this._mouseOutReleaseDelayDuration = 0;
|
||||
this._classNameForHighlightedRange = null;
|
||||
|
||||
this._enabled = false;
|
||||
this._tracking = false;
|
||||
this._previousTokenInfo = null;
|
||||
this._hoveredMarker = null;
|
||||
this._ignoreNextMouseMove = false;
|
||||
|
||||
const hidePopover = this._hidePopover.bind(this);
|
||||
|
||||
this._codeMirror.addKeyMap({
|
||||
"Cmd-Enter": this._handleCommandEnterKey.bind(this),
|
||||
"Esc": hidePopover
|
||||
});
|
||||
|
||||
this._codeMirror.on("cursorActivity", hidePopover);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get delegate()
|
||||
{
|
||||
return this._delegate;
|
||||
}
|
||||
|
||||
set delegate(x)
|
||||
{
|
||||
this._delegate = x;
|
||||
}
|
||||
|
||||
get enabled()
|
||||
{
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
set enabled(enabled)
|
||||
{
|
||||
if (this._enabled === enabled)
|
||||
return;
|
||||
|
||||
this._enabled = enabled;
|
||||
|
||||
var wrapper = this._codeMirror.getWrapperElement();
|
||||
if (enabled) {
|
||||
wrapper.addEventListener("mouseenter", this);
|
||||
wrapper.addEventListener("mouseleave", this);
|
||||
this._updateHoveredTokenInfo({left: WI.mouseCoords.x, top: WI.mouseCoords.y});
|
||||
this._startTracking();
|
||||
} else {
|
||||
wrapper.removeEventListener("mouseenter", this);
|
||||
wrapper.removeEventListener("mouseleave", this);
|
||||
this._stopTracking();
|
||||
}
|
||||
}
|
||||
|
||||
get mode()
|
||||
{
|
||||
return this._mode;
|
||||
}
|
||||
|
||||
set mode(mode)
|
||||
{
|
||||
var oldMode = this._mode;
|
||||
|
||||
this._mode = mode || WI.CodeMirrorTokenTrackingController.Mode.None;
|
||||
|
||||
if (oldMode !== this._mode && this._tracking && this._previousTokenInfo)
|
||||
this._processNewHoveredToken(this._previousTokenInfo);
|
||||
}
|
||||
|
||||
get mouseOverDelayDuration()
|
||||
{
|
||||
return this._mouseOverDelayDuration;
|
||||
}
|
||||
|
||||
set mouseOverDelayDuration(x)
|
||||
{
|
||||
console.assert(x >= 0);
|
||||
this._mouseOverDelayDuration = Math.max(x, 0);
|
||||
}
|
||||
|
||||
get mouseOutReleaseDelayDuration()
|
||||
{
|
||||
return this._mouseOutReleaseDelayDuration;
|
||||
}
|
||||
|
||||
set mouseOutReleaseDelayDuration(x)
|
||||
{
|
||||
console.assert(x >= 0);
|
||||
this._mouseOutReleaseDelayDuration = Math.max(x, 0);
|
||||
}
|
||||
|
||||
get classNameForHighlightedRange()
|
||||
{
|
||||
return this._classNameForHighlightedRange;
|
||||
}
|
||||
|
||||
set classNameForHighlightedRange(x)
|
||||
{
|
||||
this._classNameForHighlightedRange = x || null;
|
||||
}
|
||||
|
||||
get candidate()
|
||||
{
|
||||
return this._candidate;
|
||||
}
|
||||
|
||||
get hoveredMarker()
|
||||
{
|
||||
return this._hoveredMarker;
|
||||
}
|
||||
|
||||
set hoveredMarker(hoveredMarker)
|
||||
{
|
||||
this._hoveredMarker = hoveredMarker;
|
||||
}
|
||||
|
||||
highlightLastHoveredRange()
|
||||
{
|
||||
if (this._candidate)
|
||||
this.highlightRange(this._candidate.hoveredTokenRange);
|
||||
}
|
||||
|
||||
highlightRange(range)
|
||||
{
|
||||
// Nothing to do if we're trying to highlight the same range.
|
||||
if (this._codeMirrorMarkedText && this._codeMirrorMarkedText.className === this._classNameForHighlightedRange) {
|
||||
var highlightedRange = this._codeMirrorMarkedText.find();
|
||||
if (!highlightedRange)
|
||||
return;
|
||||
if (WI.compareCodeMirrorPositions(highlightedRange.from, range.start) === 0 &&
|
||||
WI.compareCodeMirrorPositions(highlightedRange.to, range.end) === 0)
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeHighlightedRange();
|
||||
|
||||
var className = this._classNameForHighlightedRange || "";
|
||||
this._codeMirrorMarkedText = this._codeMirror.markText(range.start, range.end, {className});
|
||||
|
||||
window.addEventListener("mousemove", this, true);
|
||||
}
|
||||
|
||||
removeHighlightedRange()
|
||||
{
|
||||
if (!this._codeMirrorMarkedText)
|
||||
return;
|
||||
|
||||
this._codeMirrorMarkedText.clear();
|
||||
this._codeMirrorMarkedText = null;
|
||||
|
||||
window.removeEventListener("mousemove", this, true);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_startTracking()
|
||||
{
|
||||
if (this._tracking)
|
||||
return;
|
||||
|
||||
this._tracking = true;
|
||||
this._ignoreNextMouseMove = false;
|
||||
|
||||
var wrapper = this._codeMirror.getWrapperElement();
|
||||
wrapper.addEventListener("mousemove", this, true);
|
||||
wrapper.addEventListener("mouseout", this, false);
|
||||
wrapper.addEventListener("mousedown", this, false);
|
||||
wrapper.addEventListener("mouseup", this, false);
|
||||
wrapper.addEventListener("mousewheel", this, false);
|
||||
window.addEventListener("blur", this, true);
|
||||
}
|
||||
|
||||
_stopTracking()
|
||||
{
|
||||
if (!this._tracking)
|
||||
return;
|
||||
|
||||
this._tracking = false;
|
||||
this._candidate = null;
|
||||
|
||||
var wrapper = this._codeMirror.getWrapperElement();
|
||||
wrapper.removeEventListener("mousemove", this, true);
|
||||
wrapper.removeEventListener("mouseout", this, false);
|
||||
wrapper.removeEventListener("mousedown", this, false);
|
||||
wrapper.removeEventListener("mouseup", this, false);
|
||||
wrapper.removeEventListener("mousewheel", this, false);
|
||||
window.removeEventListener("blur", this, true);
|
||||
window.removeEventListener("mousemove", this, true);
|
||||
|
||||
this._resetTrackingStates();
|
||||
}
|
||||
|
||||
handleEvent(event)
|
||||
{
|
||||
switch (event.type) {
|
||||
case "mouseenter":
|
||||
this._mouseEntered(event);
|
||||
break;
|
||||
case "mouseleave":
|
||||
this._mouseLeft(event);
|
||||
break;
|
||||
case "mousemove":
|
||||
if (event.currentTarget === window)
|
||||
this._mouseMovedWithMarkedText(event);
|
||||
else
|
||||
this._mouseMovedOverEditor(event);
|
||||
break;
|
||||
case "mouseout":
|
||||
// Only deal with a mouseout event that has the editor wrapper as the target.
|
||||
if (!event.currentTarget.contains(event.relatedTarget))
|
||||
this._mouseMovedOutOfEditor(event);
|
||||
break;
|
||||
case "mousedown":
|
||||
this._mouseButtonWasPressedOverEditor(event);
|
||||
break;
|
||||
case "mouseup":
|
||||
this._mouseButtonWasReleasedOverEditor(event);
|
||||
break;
|
||||
case "mousewheel":
|
||||
this._ignoreNextMouseMove = true;
|
||||
break;
|
||||
case "blur":
|
||||
this._windowLostFocus(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_handleCommandEnterKey(codeMirror)
|
||||
{
|
||||
const tokenInfo = this._getTokenInfoForPosition(codeMirror.getCursor("head"));
|
||||
tokenInfo.triggeredBy = WI.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard;
|
||||
this._processNewHoveredToken(tokenInfo);
|
||||
}
|
||||
|
||||
_hidePopover()
|
||||
{
|
||||
if (!this._candidate)
|
||||
return CodeMirror.Pass;
|
||||
|
||||
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function") {
|
||||
const forceHidePopover = true;
|
||||
this._delegate.tokenTrackingControllerHighlightedRangeReleased(this, forceHidePopover);
|
||||
}
|
||||
}
|
||||
|
||||
_mouseEntered(event)
|
||||
{
|
||||
if (!this._tracking)
|
||||
this._startTracking();
|
||||
}
|
||||
|
||||
_mouseLeft(event)
|
||||
{
|
||||
this._stopTracking();
|
||||
}
|
||||
|
||||
_mouseMovedWithMarkedText(event)
|
||||
{
|
||||
if (this._candidate && this._candidate.triggeredBy === WI.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard)
|
||||
return;
|
||||
|
||||
var shouldRelease = !event.target.classList.contains(this._classNameForHighlightedRange);
|
||||
if (shouldRelease && this._delegate && typeof this._delegate.tokenTrackingControllerCanReleaseHighlightedRange === "function")
|
||||
shouldRelease = this._delegate.tokenTrackingControllerCanReleaseHighlightedRange(this, event.target);
|
||||
|
||||
if (shouldRelease) {
|
||||
if (!this._markedTextMouseoutTimer)
|
||||
this._markedTextMouseoutTimer = setTimeout(this._markedTextIsNoLongerHovered.bind(this), this._mouseOutReleaseDelayDuration);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._markedTextMouseoutTimer)
|
||||
clearTimeout(this._markedTextMouseoutTimer);
|
||||
|
||||
this._markedTextMouseoutTimer = 0;
|
||||
}
|
||||
|
||||
_markedTextIsNoLongerHovered()
|
||||
{
|
||||
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function")
|
||||
this._delegate.tokenTrackingControllerHighlightedRangeReleased(this);
|
||||
|
||||
this._markedTextMouseoutTimer = 0;
|
||||
}
|
||||
|
||||
_mouseMovedOverEditor(event)
|
||||
{
|
||||
if (this._ignoreNextMouseMove) {
|
||||
this._ignoreNextMouseMove = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateHoveredTokenInfo({left: event.pageX, top: event.pageY});
|
||||
}
|
||||
|
||||
_updateHoveredTokenInfo(mouseCoords)
|
||||
{
|
||||
// Get the position in the text and the token at that position.
|
||||
var position = this._codeMirror.coordsChar(mouseCoords);
|
||||
var token = this._codeMirror.getTokenAt(position);
|
||||
|
||||
if (!token || !token.type || !token.string) {
|
||||
if (this._hoveredMarker && this._delegate && typeof this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker === "function") {
|
||||
if (!this._codeMirror.findMarksAt(position).includes(this._hoveredMarker.codeMirrorTextMarker))
|
||||
this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker(this, this._hoveredMarker);
|
||||
}
|
||||
|
||||
this._resetTrackingStates();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop right here if we're hovering the same token as we were last time.
|
||||
if (this._previousTokenInfo &&
|
||||
this._previousTokenInfo.position.line === position.line &&
|
||||
this._previousTokenInfo.token.start === token.start &&
|
||||
this._previousTokenInfo.token.end === token.end)
|
||||
return;
|
||||
|
||||
// We have a new hovered token.
|
||||
var tokenInfo = this._previousTokenInfo = this._getTokenInfoForPosition(position);
|
||||
|
||||
if (/\bmeta\b/.test(token.type)) {
|
||||
let nextTokenPosition = Object.shallowCopy(position);
|
||||
nextTokenPosition.ch = tokenInfo.token.end + 1;
|
||||
|
||||
let nextToken = this._codeMirror.getTokenAt(nextTokenPosition);
|
||||
if (nextToken && nextToken.type && !/\bmeta\b/.test(nextToken.type)) {
|
||||
console.assert(tokenInfo.token.end === nextToken.start);
|
||||
|
||||
tokenInfo.token.type = nextToken.type;
|
||||
tokenInfo.token.string = tokenInfo.token.string + nextToken.string;
|
||||
tokenInfo.token.end = nextToken.end;
|
||||
}
|
||||
} else {
|
||||
let previousTokenPosition = Object.shallowCopy(position);
|
||||
previousTokenPosition.ch = tokenInfo.token.start - 1;
|
||||
|
||||
let previousToken = this._codeMirror.getTokenAt(previousTokenPosition);
|
||||
if (previousToken && previousToken.type && /\bmeta\b/.test(previousToken.type)) {
|
||||
console.assert(tokenInfo.token.start === previousToken.end);
|
||||
|
||||
tokenInfo.token.string = previousToken.string + tokenInfo.token.string;
|
||||
tokenInfo.token.start = previousToken.start;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._tokenHoverTimer)
|
||||
clearTimeout(this._tokenHoverTimer);
|
||||
|
||||
this._tokenHoverTimer = 0;
|
||||
|
||||
if (this._codeMirrorMarkedText || !this._mouseOverDelayDuration)
|
||||
this._processNewHoveredToken(tokenInfo);
|
||||
else
|
||||
this._tokenHoverTimer = setTimeout(this._processNewHoveredToken.bind(this, tokenInfo), this._mouseOverDelayDuration);
|
||||
}
|
||||
|
||||
_getTokenInfoForPosition(position)
|
||||
{
|
||||
var token = this._codeMirror.getTokenAt(position);
|
||||
var innerMode = CodeMirror.innerMode(this._codeMirror.getMode(), token.state);
|
||||
var codeMirrorModeName = innerMode.mode.alternateName || innerMode.mode.name;
|
||||
return {
|
||||
token,
|
||||
position,
|
||||
innerMode,
|
||||
modeName: codeMirrorModeName
|
||||
};
|
||||
}
|
||||
|
||||
_mouseMovedOutOfEditor(event)
|
||||
{
|
||||
if (this._tokenHoverTimer)
|
||||
clearTimeout(this._tokenHoverTimer);
|
||||
|
||||
this._tokenHoverTimer = 0;
|
||||
this._previousTokenInfo = null;
|
||||
this._selectionMayBeInProgress = false;
|
||||
}
|
||||
|
||||
_mouseButtonWasPressedOverEditor(event)
|
||||
{
|
||||
this._selectionMayBeInProgress = true;
|
||||
}
|
||||
|
||||
_mouseButtonWasReleasedOverEditor(event)
|
||||
{
|
||||
this._selectionMayBeInProgress = false;
|
||||
this._mouseMovedOverEditor(event);
|
||||
|
||||
if (this._codeMirrorMarkedText && this._previousTokenInfo) {
|
||||
var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
|
||||
var marks = this._codeMirror.findMarksAt(position);
|
||||
for (var i = 0; i < marks.length; ++i) {
|
||||
if (marks[i] === this._codeMirrorMarkedText) {
|
||||
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeWasClicked === "function")
|
||||
this._delegate.tokenTrackingControllerHighlightedRangeWasClicked(this);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_windowLostFocus(event)
|
||||
{
|
||||
this._resetTrackingStates();
|
||||
}
|
||||
|
||||
_processNewHoveredToken(tokenInfo)
|
||||
{
|
||||
console.assert(tokenInfo);
|
||||
|
||||
if (this._selectionMayBeInProgress)
|
||||
return;
|
||||
|
||||
this._candidate = null;
|
||||
|
||||
switch (this._mode) {
|
||||
case WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
|
||||
this._candidate = this._processNonSymbolToken(tokenInfo);
|
||||
break;
|
||||
case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
|
||||
case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
|
||||
this._candidate = this._processJavaScriptExpression(tokenInfo);
|
||||
break;
|
||||
case WI.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
|
||||
this._candidate = this._processMarkedToken(tokenInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this._candidate)
|
||||
return;
|
||||
|
||||
this._candidate.triggeredBy = tokenInfo.triggeredBy;
|
||||
|
||||
if (this._markedTextMouseoutTimer)
|
||||
clearTimeout(this._markedTextMouseoutTimer);
|
||||
|
||||
this._markedTextMouseoutTimer = 0;
|
||||
|
||||
if (this._delegate && typeof this._delegate.tokenTrackingControllerNewHighlightCandidate === "function")
|
||||
this._delegate.tokenTrackingControllerNewHighlightCandidate(this, this._candidate);
|
||||
}
|
||||
|
||||
_processNonSymbolToken(tokenInfo)
|
||||
{
|
||||
// Ignore any symbol tokens.
|
||||
var type = tokenInfo.token.type;
|
||||
if (!type)
|
||||
return null;
|
||||
|
||||
var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
|
||||
var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
|
||||
|
||||
return {
|
||||
hoveredToken: tokenInfo.token,
|
||||
hoveredTokenRange: {start: startPosition, end: endPosition},
|
||||
};
|
||||
}
|
||||
|
||||
_processJavaScriptExpression(tokenInfo)
|
||||
{
|
||||
// Only valid within JavaScript.
|
||||
if (tokenInfo.modeName !== "javascript")
|
||||
return null;
|
||||
|
||||
var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
|
||||
var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
|
||||
|
||||
function tokenIsInRange(token, range)
|
||||
{
|
||||
return token.line >= range.start.line && token.ch >= range.start.ch &&
|
||||
token.line <= range.end.line && token.ch <= range.end.ch;
|
||||
}
|
||||
|
||||
// If the hovered token is within a selection, use the selection as our expression.
|
||||
if (this._codeMirror.somethingSelected()) {
|
||||
var selectionRange = {
|
||||
start: this._codeMirror.getCursor("start"),
|
||||
end: this._codeMirror.getCursor("end")
|
||||
};
|
||||
|
||||
if (tokenIsInRange(startPosition, selectionRange) || tokenIsInRange(endPosition, selectionRange)) {
|
||||
return {
|
||||
hoveredToken: tokenInfo.token,
|
||||
hoveredTokenRange: selectionRange,
|
||||
expression: this._codeMirror.getSelection(),
|
||||
expressionRange: selectionRange,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// We only handle vars, definitions, properties, and the keyword 'this'.
|
||||
var type = tokenInfo.token.type;
|
||||
var isProperty = type.indexOf("property") !== -1;
|
||||
var isKeyword = type.indexOf("keyword") !== -1;
|
||||
if (!isProperty && !isKeyword && type.indexOf("variable") === -1 && type.indexOf("def") === -1)
|
||||
return null;
|
||||
|
||||
// Not object literal property names, but yes if an object literal shorthand property, which is a variable.
|
||||
let state = tokenInfo.innerMode.state;
|
||||
if (isProperty && state.lexical && state.lexical.type === "}") {
|
||||
// Peek ahead to see if the next token is "}" or ",". If it is, we are a shorthand and therefore a variable.
|
||||
let shorthand = false;
|
||||
let mode = tokenInfo.innerMode.mode;
|
||||
let position = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
|
||||
WI.walkTokens(this._codeMirror, mode, position, function(tokenType, string) {
|
||||
if (tokenType)
|
||||
return false;
|
||||
if (string === "(")
|
||||
return false;
|
||||
if (string === "," || string === "}") {
|
||||
shorthand = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!shorthand)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only the "this" keyword.
|
||||
if (isKeyword && tokenInfo.token.string !== "this")
|
||||
return null;
|
||||
|
||||
// Work out the full hovered expression.
|
||||
var expression = tokenInfo.token.string;
|
||||
var expressionStartPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
|
||||
while (true) {
|
||||
var token = this._codeMirror.getTokenAt(expressionStartPosition);
|
||||
if (!token)
|
||||
break;
|
||||
|
||||
var isDot = !token.type && token.string === ".";
|
||||
var isExpression = token.type && token.type.includes("m-javascript");
|
||||
if (!isDot && !isExpression)
|
||||
break;
|
||||
|
||||
if (isExpression) {
|
||||
// Disallow operators. We want the hovered expression to be just a single operand.
|
||||
// Also, some operators can modify values, such as pre-increment and assignment operators.
|
||||
if (token.type.includes("operator"))
|
||||
break;
|
||||
|
||||
// Don't break out of a template string quasi group.
|
||||
if (token.type.includes("string-2"))
|
||||
break;
|
||||
}
|
||||
|
||||
expression = token.string + expression;
|
||||
expressionStartPosition.ch = token.start;
|
||||
}
|
||||
|
||||
// Return the candidate for this token and expression.
|
||||
return {
|
||||
hoveredToken: tokenInfo.token,
|
||||
hoveredTokenRange: {start: startPosition, end: endPosition},
|
||||
expression,
|
||||
expressionRange: {start: expressionStartPosition, end: endPosition},
|
||||
};
|
||||
}
|
||||
|
||||
_processMarkedToken(tokenInfo)
|
||||
{
|
||||
return this._processNonSymbolToken(tokenInfo);
|
||||
}
|
||||
|
||||
_resetTrackingStates()
|
||||
{
|
||||
if (this._tokenHoverTimer)
|
||||
clearTimeout(this._tokenHoverTimer);
|
||||
|
||||
this._tokenHoverTimer = 0;
|
||||
|
||||
this._selectionMayBeInProgress = false;
|
||||
this._previousTokenInfo = null;
|
||||
this.removeHighlightedRange();
|
||||
}
|
||||
};
|
||||
|
||||
WI.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName = "jump-to-symbol-highlight";
|
||||
|
||||
WI.CodeMirrorTokenTrackingController.Mode = {
|
||||
None: "none",
|
||||
NonSymbolTokens: "non-symbol-tokens",
|
||||
JavaScriptExpression: "javascript-expression",
|
||||
JavaScriptTypeInformation: "javascript-type-information",
|
||||
MarkedTokens: "marked-tokens"
|
||||
};
|
||||
|
||||
WI.CodeMirrorTokenTrackingController.TriggeredBy = {
|
||||
Keyboard: "keyboard",
|
||||
Hover: "hover"
|
||||
};
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2015 Tobias Reiss <tobi+webkit@basecode.de>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.ConsoleManager = class ConsoleManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._warningCount = 0;
|
||||
this._errorCount = 0;
|
||||
this._issues = [];
|
||||
|
||||
this._lastMessageLevel = null;
|
||||
this._clearMessagesRequested = false;
|
||||
this._isNewPageOrReload = false;
|
||||
this._remoteObjectsToRelease = null;
|
||||
|
||||
this._customLoggingChannels = [];
|
||||
|
||||
this._snippets = new Set;
|
||||
this._restoringSnippets = false;
|
||||
|
||||
WI.ConsoleSnippet.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleSnippetContentChanged, this);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
|
||||
WI.Target.registerInitializationPromise((async () => {
|
||||
let serializedSnippets = await WI.objectStores.consoleSnippets.getAll();
|
||||
|
||||
this._restoringSnippets = true;
|
||||
for (let serializedSnippet of serializedSnippets) {
|
||||
let snippet = WI.ConsoleSnippet.fromJSON(serializedSnippet);
|
||||
|
||||
const key = null;
|
||||
WI.objectStores.consoleSnippets.associateObject(snippet, key, serializedSnippet);
|
||||
|
||||
this.addSnippet(snippet);
|
||||
}
|
||||
this._restoringSnippets = false;
|
||||
})());
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static supportsLogChannels()
|
||||
{
|
||||
return InspectorBackend.hasCommand("Console.getLoggingChannels");
|
||||
}
|
||||
|
||||
static issueMatchSourceCode(issue, sourceCode)
|
||||
{
|
||||
if (sourceCode instanceof WI.SourceMapResource)
|
||||
return issue.sourceCodeLocation && issue.sourceCodeLocation.displaySourceCode === sourceCode;
|
||||
if (sourceCode instanceof WI.Resource)
|
||||
return issue.url === sourceCode.url && (!issue.sourceCodeLocation || issue.sourceCodeLocation.sourceCode === sourceCode);
|
||||
if (sourceCode instanceof WI.Script)
|
||||
return issue.sourceCodeLocation && issue.sourceCodeLocation.sourceCode === sourceCode;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get warningCount() { return this._warningCount; }
|
||||
get errorCount() { return this._errorCount; }
|
||||
get snippets() { return this._snippets; }
|
||||
get customLoggingChannels() { return this._customLoggingChannels; }
|
||||
|
||||
issuesForSourceCode(sourceCode)
|
||||
{
|
||||
var issues = [];
|
||||
|
||||
for (var i = 0; i < this._issues.length; ++i) {
|
||||
var issue = this._issues[i];
|
||||
if (WI.ConsoleManager.issueMatchSourceCode(issue, sourceCode))
|
||||
issues.push(issue);
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
releaseRemoteObjectWithConsoleClear(remoteObject)
|
||||
{
|
||||
if (!this._remoteObjectsToRelease)
|
||||
this._remoteObjectsToRelease = new Set;
|
||||
this._remoteObjectsToRelease.add(remoteObject);
|
||||
}
|
||||
|
||||
addSnippet(snippet)
|
||||
{
|
||||
console.assert(snippet instanceof WI.ConsoleSnippet, snippet);
|
||||
console.assert(!this._snippets.has(snippet), snippet);
|
||||
console.assert(!this._snippets.some((existingSnippet) => snippet.contentIdentifier === existingSnippet.contentIdentifier), snippet);
|
||||
|
||||
this._snippets.add(snippet);
|
||||
|
||||
if (!this._restoringSnippets)
|
||||
WI.objectStores.consoleSnippets.putObject(snippet);
|
||||
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.SnippetAdded, {snippet});
|
||||
}
|
||||
|
||||
removeSnippet(snippet)
|
||||
{
|
||||
console.assert(snippet instanceof WI.ConsoleSnippet, snippet);
|
||||
console.assert(this._snippets.has(snippet), snippet);
|
||||
|
||||
this._snippets.delete(snippet);
|
||||
|
||||
if (!this._restoringSnippets)
|
||||
WI.objectStores.consoleSnippets.deleteObject(snippet);
|
||||
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.SnippetRemoved, {snippet});
|
||||
}
|
||||
|
||||
// ConsoleObserver
|
||||
|
||||
messageWasAdded(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, requestId, timestamp)
|
||||
{
|
||||
// FIXME: Get a request from request ID.
|
||||
|
||||
if (parameters)
|
||||
parameters = parameters.map((x) => WI.RemoteObject.fromPayload(x, target));
|
||||
|
||||
// COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`.
|
||||
if (Array.isArray(stackTrace))
|
||||
stackTrace = {callFrames: stackTrace};
|
||||
if (stackTrace)
|
||||
stackTrace = WI.StackTrace.fromPayload(target, stackTrace);
|
||||
|
||||
const request = null;
|
||||
let message = new WI.ConsoleMessage(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, request, timestamp);
|
||||
|
||||
this._incrementMessageLevelCount(message.level, message.repeatCount);
|
||||
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.MessageAdded, {message});
|
||||
|
||||
if (message.level === WI.ConsoleMessage.MessageLevel.Warning || message.level === WI.ConsoleMessage.MessageLevel.Error) {
|
||||
let issue = new WI.IssueMessage(message);
|
||||
this._issues.push(issue);
|
||||
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.IssueAdded, {issue});
|
||||
}
|
||||
}
|
||||
|
||||
messagesCleared()
|
||||
{
|
||||
if (this._remoteObjectsToRelease) {
|
||||
for (let remoteObject of this._remoteObjectsToRelease)
|
||||
remoteObject.release();
|
||||
this._remoteObjectsToRelease = null;
|
||||
}
|
||||
|
||||
WI.ConsoleCommandResultMessage.clearMaximumSavedResultIndex();
|
||||
|
||||
if (this._clearMessagesRequested) {
|
||||
// Frontend requested "clear console" and Backend successfully completed the request.
|
||||
this._clearMessagesRequested = false;
|
||||
|
||||
this._warningCount = 0;
|
||||
this._errorCount = 0;
|
||||
this._issues = [];
|
||||
|
||||
this._lastMessageLevel = null;
|
||||
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.Cleared);
|
||||
} else {
|
||||
// Received an unrequested clear console event.
|
||||
// This could be for a navigation or other reasons (like console.clear()).
|
||||
// If this was a reload, we may not want to dispatch WI.ConsoleManager.Event.Cleared.
|
||||
// To detect if this is a reload we wait a turn and check if there was a main resource change reload.
|
||||
setTimeout(this._delayedMessagesCleared.bind(this), 0);
|
||||
}
|
||||
}
|
||||
|
||||
messageRepeatCountUpdated(count, timestamp)
|
||||
{
|
||||
this._incrementMessageLevelCount(this._lastMessageLevel, 1);
|
||||
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.PreviousMessageRepeatCountUpdated, {count, timestamp});
|
||||
}
|
||||
|
||||
requestClearMessages()
|
||||
{
|
||||
this._clearMessagesRequested = true;
|
||||
|
||||
for (let target of WI.targets)
|
||||
target.ConsoleAgent.clearMessages();
|
||||
}
|
||||
|
||||
initializeLogChannels(target)
|
||||
{
|
||||
console.assert(target.hasDomain("Console"));
|
||||
|
||||
if (!WI.ConsoleManager.supportsLogChannels())
|
||||
return;
|
||||
|
||||
if (this._customLoggingChannels.length)
|
||||
return;
|
||||
|
||||
target.ConsoleAgent.getLoggingChannels((error, channels) => {
|
||||
if (error)
|
||||
return;
|
||||
|
||||
this._customLoggingChannels = channels.map(WI.LoggingChannel.fromPayload);
|
||||
});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_incrementMessageLevelCount(level, count)
|
||||
{
|
||||
switch (level) {
|
||||
case WI.ConsoleMessage.MessageLevel.Warning:
|
||||
this._warningCount += count;
|
||||
break;
|
||||
case WI.ConsoleMessage.MessageLevel.Error:
|
||||
this._errorCount += count;
|
||||
break;
|
||||
}
|
||||
|
||||
this._lastMessageLevel = level;
|
||||
}
|
||||
|
||||
_delayedMessagesCleared()
|
||||
{
|
||||
if (this._isNewPageOrReload) {
|
||||
this._isNewPageOrReload = false;
|
||||
|
||||
if (!WI.settings.clearLogOnNavigate.value)
|
||||
return;
|
||||
}
|
||||
|
||||
this._warningCount = 0;
|
||||
this._errorCount = 0;
|
||||
this._issues = [];
|
||||
|
||||
this._lastMessageLevel = null;
|
||||
|
||||
// A console.clear() or command line clear() happened.
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.Cleared);
|
||||
}
|
||||
|
||||
_handleSnippetContentChanged(event)
|
||||
{
|
||||
let snippet = event.target;
|
||||
|
||||
console.assert(this._snippets.has(snippet), snippet);
|
||||
|
||||
WI.objectStores.consoleSnippets.putObject(snippet);
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
this._isNewPageOrReload = true;
|
||||
|
||||
let timestamp = Date.now();
|
||||
let wasReloaded = event.data.oldMainResource && event.data.oldMainResource.url === event.target.mainResource.url;
|
||||
this.dispatchEventToListeners(WI.ConsoleManager.Event.SessionStarted, {timestamp, wasReloaded});
|
||||
|
||||
WI.ConsoleCommandResultMessage.clearMaximumSavedResultIndex();
|
||||
}
|
||||
};
|
||||
|
||||
WI.ConsoleManager.Event = {
|
||||
SessionStarted: "console-manager-session-was-started",
|
||||
Cleared: "console-manager-cleared",
|
||||
MessageAdded: "console-manager-message-added",
|
||||
IssueAdded: "console-manager-issue-added",
|
||||
PreviousMessageRepeatCountUpdated: "console-manager-previous-message-repeat-count-updated",
|
||||
SnippetAdded: "console-manager-snippet-added",
|
||||
SnippetRemoved: "console-manager-snippet-removed",
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,899 @@
|
||||
/*
|
||||
* Copyright (C) 2009, 2010 Google Inc. All rights reserved.
|
||||
* Copyright (C) 2009 Joseph Pecoraro
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// FIXME: DOMManager lacks advanced multi-target support. (DOMNodes per-target)
|
||||
|
||||
WI.DOMManager = class DOMManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._idToDOMNode = {};
|
||||
this._document = null;
|
||||
this._documentPromise = null;
|
||||
this._attributeLoadNodeIds = {};
|
||||
this._restoreSelectedNodeIsAllowed = true;
|
||||
this._loadNodeAttributesTimeout = 0;
|
||||
this._inspectedNode = null;
|
||||
|
||||
this._breakpointsForEventListeners = new Map;
|
||||
|
||||
this._hasRequestedDocument = false;
|
||||
this._pendingDocumentRequestCallbacks = null;
|
||||
|
||||
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleEventBreakpointDisabledStateChanged, this);
|
||||
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleEventBreakpointEditablePropertyChanged, this);
|
||||
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleEventBreakpointEditablePropertyChanged, this);
|
||||
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleEventBreakpointEditablePropertyChanged, this);
|
||||
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleEventBreakpointActionsChanged, this);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
// FIXME: This should be improved when adding better DOM multi-target support since it is really per-target.
|
||||
// This currently uses a setTimeout since it doesn't need to happen immediately, and DOMManager uses the
|
||||
// global DOMAgent to request the document, so we want to make sure we've transitioned the global agents
|
||||
// to this target if necessary.
|
||||
if (target.hasDomain("DOM")) {
|
||||
setTimeout(() => {
|
||||
this.ensureDocument();
|
||||
});
|
||||
|
||||
if (WI.engineeringSettingsAllowed()) {
|
||||
if (DOMManager.supportsEditingUserAgentShadowTrees({target}))
|
||||
target.DOMAgent.setAllowEditingUserAgentShadowTrees(WI.settings.engineeringAllowEditingUserAgentShadowTrees.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transitionPageTarget()
|
||||
{
|
||||
this._documentUpdated();
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static buildHighlightConfig(mode)
|
||||
{
|
||||
mode = mode || "all";
|
||||
|
||||
let highlightConfig = {showInfo: mode === "all"};
|
||||
|
||||
if (mode === "all" || mode === "content")
|
||||
highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66};
|
||||
|
||||
if (mode === "all" || mode === "padding")
|
||||
highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66};
|
||||
|
||||
if (mode === "all" || mode === "border")
|
||||
highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66};
|
||||
|
||||
if (mode === "all" || mode === "margin")
|
||||
highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66};
|
||||
|
||||
return highlightConfig;
|
||||
}
|
||||
|
||||
static wrapClientCallback(callback)
|
||||
{
|
||||
if (!callback)
|
||||
return null;
|
||||
|
||||
return function(error, result) {
|
||||
if (error)
|
||||
console.error("Error during DOMAgent operation: " + error);
|
||||
callback(error ? null : result);
|
||||
};
|
||||
}
|
||||
|
||||
static supportsEventListenerBreakpoints()
|
||||
{
|
||||
return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener")
|
||||
&& InspectorBackend.hasCommand("DOM.removeBreakpointForEventListener");
|
||||
}
|
||||
|
||||
static supportsEventListenerBreakpointConfiguration()
|
||||
{
|
||||
// COMPATIBILITY (iOS 14): DOM.setBreakpointForEventListener did not have an "options" parameter yet.
|
||||
return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener", "options");
|
||||
}
|
||||
|
||||
static supportsEditingUserAgentShadowTrees({frontendOnly, target} = {})
|
||||
{
|
||||
target = target || InspectorBackend;
|
||||
return WI.settings.engineeringAllowEditingUserAgentShadowTrees.value
|
||||
&& (frontendOnly || target.hasCommand("DOM.setAllowEditingUserAgentShadowTrees"));
|
||||
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get inspectedNode() { return this._inspectedNode; }
|
||||
|
||||
get eventListenerBreakpoints()
|
||||
{
|
||||
return Array.from(this._breakpointsForEventListeners.values());
|
||||
}
|
||||
|
||||
*attachedNodes({filter} = {})
|
||||
{
|
||||
if (!this._document)
|
||||
return;
|
||||
|
||||
filter ??= (node) => true;
|
||||
|
||||
// Traverse the node tree in the same order items would appear if the entire tree were expanded in order to
|
||||
// provide a predictable order for the results.
|
||||
let currentBranch = [this._document];
|
||||
while (currentBranch.length) {
|
||||
let currentNode = currentBranch.at(-1);
|
||||
|
||||
if (filter(currentNode))
|
||||
yield currentNode;
|
||||
|
||||
// The `::before` pseudo element is the first child of any node.
|
||||
let beforePseudoElement = currentNode.beforePseudoElement();
|
||||
if (beforePseudoElement && filter(beforePseudoElement))
|
||||
yield beforePseudoElement;
|
||||
|
||||
let firstChild = currentNode.children?.[0];
|
||||
if (firstChild) {
|
||||
currentBranch.push(firstChild);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (currentBranch.length) {
|
||||
let parent = currentBranch.pop();
|
||||
|
||||
// The `::after` pseudo element is the last child of any node.
|
||||
let parentAfterPseudoElement = parent.afterPseudoElement();
|
||||
if (parentAfterPseudoElement && filter(parentAfterPseudoElement))
|
||||
yield parentAfterPseudoElement;
|
||||
|
||||
if (parent.nextSibling) {
|
||||
currentBranch.push(parent.nextSibling);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestDocument(callback)
|
||||
{
|
||||
if (typeof callback !== "function")
|
||||
return this._requestDocumentWithPromise();
|
||||
|
||||
this._requestDocumentWithCallback(callback);
|
||||
}
|
||||
|
||||
ensureDocument()
|
||||
{
|
||||
this.requestDocument(function(){});
|
||||
}
|
||||
|
||||
pushNodeToFrontend(objectId, callback)
|
||||
{
|
||||
let target = WI.assumingMainTarget();
|
||||
this._dispatchWhenDocumentAvailable((callbackWrapper) => {
|
||||
target.DOMAgent.requestNode(objectId, callbackWrapper);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
pushNodeByPathToFrontend(path, callback)
|
||||
{
|
||||
let target = WI.assumingMainTarget();
|
||||
this._dispatchWhenDocumentAvailable((callbackWrapper) => {
|
||||
target.DOMAgent.pushNodeByPathToFrontend(path, callbackWrapper);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
// DOMObserver
|
||||
|
||||
willDestroyDOMNode(nodeId)
|
||||
{
|
||||
let node = this._idToDOMNode[nodeId];
|
||||
node.markDestroyed();
|
||||
delete this._idToDOMNode[nodeId];
|
||||
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node});
|
||||
}
|
||||
|
||||
didAddEventListener(nodeId)
|
||||
{
|
||||
let node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged);
|
||||
}
|
||||
|
||||
willRemoveEventListener(nodeId)
|
||||
{
|
||||
let node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged);
|
||||
}
|
||||
|
||||
didFireEvent(nodeId, eventName, timestamp, data)
|
||||
{
|
||||
let node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node.didFireEvent(eventName, timestamp, data);
|
||||
}
|
||||
|
||||
powerEfficientPlaybackStateChanged(nodeId, timestamp, isPowerEfficient)
|
||||
{
|
||||
let node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node.powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient);
|
||||
}
|
||||
|
||||
// CSSObserver
|
||||
|
||||
nodeLayoutFlagsChanged(nodeId, layoutFlags)
|
||||
{
|
||||
let domNode = this._idToDOMNode[nodeId];
|
||||
console.assert(domNode instanceof WI.DOMNode, domNode, nodeId);
|
||||
if (!domNode)
|
||||
return;
|
||||
|
||||
domNode.layoutFlags = layoutFlags;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_dispatchWhenDocumentAvailable(func, callback)
|
||||
{
|
||||
var callbackWrapper = DOMManager.wrapClientCallback(callback);
|
||||
|
||||
function onDocumentAvailable()
|
||||
{
|
||||
if (this._document)
|
||||
func(callbackWrapper);
|
||||
else {
|
||||
if (callbackWrapper)
|
||||
callbackWrapper("No document");
|
||||
}
|
||||
}
|
||||
this.requestDocument(onDocumentAvailable.bind(this));
|
||||
}
|
||||
|
||||
_requestDocumentWithPromise()
|
||||
{
|
||||
if (this._documentPromise)
|
||||
return this._documentPromise.promise;
|
||||
|
||||
this._documentPromise = new WI.WrappedPromise;
|
||||
if (this._document)
|
||||
this._documentPromise.resolve(this._document);
|
||||
else {
|
||||
this._requestDocumentWithCallback((doc) => {
|
||||
this._documentPromise.resolve(doc);
|
||||
});
|
||||
}
|
||||
|
||||
return this._documentPromise.promise;
|
||||
}
|
||||
|
||||
_requestDocumentWithCallback(callback)
|
||||
{
|
||||
if (this._document) {
|
||||
callback(this._document);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._pendingDocumentRequestCallbacks)
|
||||
this._pendingDocumentRequestCallbacks.push(callback);
|
||||
else
|
||||
this._pendingDocumentRequestCallbacks = [callback];
|
||||
|
||||
if (this._hasRequestedDocument)
|
||||
return;
|
||||
|
||||
if (!WI.pageTarget)
|
||||
return;
|
||||
|
||||
if (!WI.pageTarget.hasDomain("DOM"))
|
||||
return;
|
||||
|
||||
this._hasRequestedDocument = true;
|
||||
|
||||
WI.pageTarget.DOMAgent.getDocument((error, root) => {
|
||||
if (!error)
|
||||
this._setDocument(root);
|
||||
|
||||
for (let callback of this._pendingDocumentRequestCallbacks)
|
||||
callback(this._document);
|
||||
|
||||
this._pendingDocumentRequestCallbacks = null;
|
||||
});
|
||||
}
|
||||
|
||||
_attributeModified(nodeId, name, value)
|
||||
{
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node._setAttribute(name, value);
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name});
|
||||
node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name});
|
||||
}
|
||||
|
||||
_attributeRemoved(nodeId, name)
|
||||
{
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node._removeAttribute(name);
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.AttributeRemoved, {node, name});
|
||||
node.dispatchEventToListeners(WI.DOMNode.Event.AttributeRemoved, {name});
|
||||
}
|
||||
|
||||
_inlineStyleInvalidated(nodeIds)
|
||||
{
|
||||
for (var nodeId of nodeIds)
|
||||
this._attributeLoadNodeIds[nodeId] = true;
|
||||
if (this._loadNodeAttributesTimeout)
|
||||
return;
|
||||
this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
|
||||
}
|
||||
|
||||
_loadNodeAttributes()
|
||||
{
|
||||
function callback(nodeId, error, attributes)
|
||||
{
|
||||
if (error) {
|
||||
console.error("Error during DOMAgent operation: " + error);
|
||||
return;
|
||||
}
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
if (node) {
|
||||
node._setAttributesPayload(attributes);
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name: "style"});
|
||||
node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name: "style"});
|
||||
}
|
||||
}
|
||||
|
||||
this._loadNodeAttributesTimeout = 0;
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
|
||||
for (var nodeId in this._attributeLoadNodeIds) {
|
||||
if (!(nodeId in this._idToDOMNode))
|
||||
continue;
|
||||
var nodeIdAsNumber = parseInt(nodeId);
|
||||
target.DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
|
||||
}
|
||||
this._attributeLoadNodeIds = {};
|
||||
}
|
||||
|
||||
_characterDataModified(nodeId, newValue)
|
||||
{
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
node._nodeValue = newValue;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.CharacterDataModified, {node});
|
||||
}
|
||||
|
||||
nodeForId(nodeId)
|
||||
{
|
||||
return this._idToDOMNode[nodeId] || null;
|
||||
}
|
||||
|
||||
_documentUpdated()
|
||||
{
|
||||
this._setDocument(null);
|
||||
}
|
||||
|
||||
_setDocument(payload)
|
||||
{
|
||||
for (let node of Object.values(this._idToDOMNode))
|
||||
node.markDestroyed();
|
||||
|
||||
this._idToDOMNode = {};
|
||||
|
||||
for (let breakpoint of this._breakpointsForEventListeners.values())
|
||||
WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint});
|
||||
this._breakpointsForEventListeners.clear();
|
||||
|
||||
let newDocument = null;
|
||||
if (payload && "nodeId" in payload)
|
||||
newDocument = new WI.DOMNode(this, null, false, payload);
|
||||
|
||||
if (this._document === newDocument)
|
||||
return;
|
||||
|
||||
this._document = newDocument;
|
||||
|
||||
// Force the promise to be recreated so that it resolves to the new document.
|
||||
this._documentPromise = null;
|
||||
|
||||
if (!this._document)
|
||||
this._hasRequestedDocument = false;
|
||||
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.DocumentUpdated, {document: this._document});
|
||||
}
|
||||
|
||||
_setDetachedRoot(payload)
|
||||
{
|
||||
new WI.DOMNode(this, null, false, payload);
|
||||
}
|
||||
|
||||
_setChildNodes(parentId, payloads)
|
||||
{
|
||||
if (!parentId && payloads.length) {
|
||||
this._setDetachedRoot(payloads[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = this._idToDOMNode[parentId];
|
||||
|
||||
if (parent.children) {
|
||||
for (let node of parent.children)
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent});
|
||||
}
|
||||
|
||||
parent._setChildrenPayload(payloads);
|
||||
|
||||
for (let node of parent.children)
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent});
|
||||
}
|
||||
|
||||
_childNodeCountUpdated(nodeId, newValue)
|
||||
{
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
node.childNodeCount = newValue;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.ChildNodeCountUpdated, node);
|
||||
}
|
||||
|
||||
_childNodeInserted(parentId, prevId, payload)
|
||||
{
|
||||
var parent = this._idToDOMNode[parentId];
|
||||
var prev = this._idToDOMNode[prevId];
|
||||
var node = parent._insertChild(prev, payload);
|
||||
this._idToDOMNode[node.id] = node;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent});
|
||||
}
|
||||
|
||||
_childNodeRemoved(parentId, nodeId)
|
||||
{
|
||||
var parent = this._idToDOMNode[parentId];
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
parent._removeChild(node);
|
||||
this._unbind(node);
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent});
|
||||
}
|
||||
|
||||
_customElementStateChanged(elementId, newState)
|
||||
{
|
||||
const node = this._idToDOMNode[elementId];
|
||||
node._customElementState = newState;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.CustomElementStateChanged, {node});
|
||||
}
|
||||
|
||||
_pseudoElementAdded(parentId, pseudoElement)
|
||||
{
|
||||
var parent = this._idToDOMNode[parentId];
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
var node = new WI.DOMNode(this, parent.ownerDocument, false, pseudoElement);
|
||||
node.parentNode = parent;
|
||||
this._idToDOMNode[node.id] = node;
|
||||
console.assert(!parent.pseudoElements().get(node.pseudoType()));
|
||||
parent.pseudoElements().set(node.pseudoType(), node);
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent});
|
||||
}
|
||||
|
||||
_pseudoElementRemoved(parentId, pseudoElementId)
|
||||
{
|
||||
var pseudoElement = this._idToDOMNode[pseudoElementId];
|
||||
if (!pseudoElement)
|
||||
return;
|
||||
|
||||
var parent = pseudoElement.parentNode;
|
||||
console.assert(parent);
|
||||
console.assert(parent.id === parentId);
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
parent._removeChild(pseudoElement);
|
||||
this._unbind(pseudoElement);
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node: pseudoElement, parent});
|
||||
}
|
||||
|
||||
_unbind(node)
|
||||
{
|
||||
node.markDestroyed();
|
||||
|
||||
delete this._idToDOMNode[node.id];
|
||||
|
||||
for (let i = 0; node.children && i < node.children.length; ++i)
|
||||
this._unbind(node.children[i]);
|
||||
|
||||
let templateContent = node.templateContent();
|
||||
if (templateContent)
|
||||
this._unbind(templateContent);
|
||||
|
||||
for (let pseudoElement of node.pseudoElements().values())
|
||||
this._unbind(pseudoElement);
|
||||
|
||||
// FIXME: Handle shadow roots.
|
||||
}
|
||||
|
||||
get restoreSelectedNodeIsAllowed()
|
||||
{
|
||||
return this._restoreSelectedNodeIsAllowed;
|
||||
}
|
||||
|
||||
inspectElement(nodeId, options = {})
|
||||
{
|
||||
var node = this._idToDOMNode[nodeId];
|
||||
if (!node || !node.ownerDocument)
|
||||
return;
|
||||
|
||||
// This code path is hit by "Reveal in DOM Tree" and clicking element links/console widgets.
|
||||
// Unless overridden by callers, assume that this is navigation is initiated by a Inspect mode.
|
||||
let initiatorHint = options.initiatorHint || WI.TabBrowser.TabNavigationInitiator.Inspect;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.DOMNodeWasInspected, {node, initiatorHint});
|
||||
|
||||
this._inspectModeEnabled = false;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged);
|
||||
}
|
||||
|
||||
inspectNodeObject(remoteObject)
|
||||
{
|
||||
this._restoreSelectedNodeIsAllowed = false;
|
||||
|
||||
function nodeAvailable(nodeId)
|
||||
{
|
||||
remoteObject.release();
|
||||
|
||||
console.assert(nodeId);
|
||||
if (!nodeId)
|
||||
return;
|
||||
|
||||
this.inspectElement(nodeId);
|
||||
|
||||
// Re-resolve the node in the console's object group when adding to the console.
|
||||
let domNode = this.nodeForId(nodeId);
|
||||
WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup).then((remoteObject) => {
|
||||
WI.consoleLogViewController.appendImmediateExecutionWithResult(WI.UIString("Selected Element"), remoteObject, {addSpecialUserLogClass: true});
|
||||
});
|
||||
}
|
||||
|
||||
remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
|
||||
}
|
||||
|
||||
highlightDOMNodeList(nodes, mode)
|
||||
{
|
||||
if (this._hideDOMNodeHighlightTimeout) {
|
||||
clearTimeout(this._hideDOMNodeHighlightTimeout);
|
||||
this._hideDOMNodeHighlightTimeout = undefined;
|
||||
}
|
||||
|
||||
let nodeIds = [];
|
||||
for (let node of nodes) {
|
||||
console.assert(node instanceof WI.DOMNode, node);
|
||||
console.assert(!node.destroyed, node);
|
||||
if (node.destroyed)
|
||||
continue;
|
||||
nodeIds.push(node.id);
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.DOMAgent.highlightNodeList(nodeIds, DOMManager.buildHighlightConfig(mode));
|
||||
}
|
||||
|
||||
highlightSelector(selectorText, frameId, mode)
|
||||
{
|
||||
if (this._hideDOMNodeHighlightTimeout) {
|
||||
clearTimeout(this._hideDOMNodeHighlightTimeout);
|
||||
this._hideDOMNodeHighlightTimeout = undefined;
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.DOMAgent.highlightSelector(DOMManager.buildHighlightConfig(mode), selectorText, frameId);
|
||||
}
|
||||
|
||||
highlightRect(rect, usePageCoordinates)
|
||||
{
|
||||
let target = WI.assumingMainTarget();
|
||||
target.DOMAgent.highlightRect.invoke({
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: {r: 111, g: 168, b: 220, a: 0.66},
|
||||
outlineColor: {r: 255, g: 229, b: 153, a: 0.66},
|
||||
usePageCoordinates
|
||||
});
|
||||
}
|
||||
|
||||
hideDOMNodeHighlight()
|
||||
{
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasCommand("DOM.hideHighlight"))
|
||||
target.DOMAgent.hideHighlight();
|
||||
}
|
||||
}
|
||||
|
||||
highlightDOMNodeForTwoSeconds(nodeId)
|
||||
{
|
||||
let node = this._idToDOMNode[nodeId];
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node.highlight();
|
||||
|
||||
this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
|
||||
}
|
||||
|
||||
get inspectModeEnabled()
|
||||
{
|
||||
return this._inspectModeEnabled;
|
||||
}
|
||||
|
||||
set inspectModeEnabled(enabled)
|
||||
{
|
||||
if (enabled === this._inspectModeEnabled)
|
||||
return;
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
let commandArguments = {
|
||||
enabled,
|
||||
highlightConfig: DOMManager.buildHighlightConfig(),
|
||||
showRulers: WI.settings.showRulersDuringElementSelection.value,
|
||||
};
|
||||
target.DOMAgent.setInspectModeEnabled.invoke(commandArguments, (error) => {
|
||||
if (error) {
|
||||
WI.reportInternalError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this._inspectModeEnabled = enabled;
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged);
|
||||
});
|
||||
}
|
||||
|
||||
setInspectedNode(node)
|
||||
{
|
||||
console.assert(node instanceof WI.DOMNode);
|
||||
if (node === this._inspectedNode)
|
||||
return;
|
||||
|
||||
console.assert(!node.destroyed, node);
|
||||
if (node.destroyed)
|
||||
return;
|
||||
|
||||
let callback = (error) => {
|
||||
console.assert(!error, error);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
let lastInspectedNode = this._inspectedNode;
|
||||
this._inspectedNode = node;
|
||||
|
||||
this.dispatchEventToListeners(WI.DOMManager.Event.InspectedNodeChanged, {lastInspectedNode});
|
||||
};
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.DOMAgent.setInspectedNode(node.id, callback);
|
||||
}
|
||||
|
||||
getSupportedEventNames(callback)
|
||||
{
|
||||
let target = WI.assumingMainTarget();
|
||||
if (!target.hasCommand("DOM.getSupportedEventNames"))
|
||||
return Promise.resolve(new Set);
|
||||
|
||||
if (!this._getSupportedEventNamesPromise) {
|
||||
this._getSupportedEventNamesPromise = target.DOMAgent.getSupportedEventNames()
|
||||
.then(({eventNames}) => new Set(eventNames));
|
||||
}
|
||||
|
||||
return this._getSupportedEventNamesPromise;
|
||||
}
|
||||
|
||||
setEventListenerDisabled(eventListener, disabled)
|
||||
{
|
||||
let target = WI.assumingMainTarget();
|
||||
target.DOMAgent.setEventListenerDisabled(eventListener.eventListenerId, disabled);
|
||||
}
|
||||
|
||||
setBreakpointForEventListener(eventListener)
|
||||
{
|
||||
let breakpoint = this._breakpointsForEventListeners.get(eventListener.eventListenerId);
|
||||
if (breakpoint) {
|
||||
console.assert(breakpoint.disabled);
|
||||
breakpoint.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
breakpoint = new WI.EventBreakpoint(WI.EventBreakpoint.Type.Listener, {eventName: eventListener.type, eventListener});
|
||||
console.assert(!breakpoint.disabled);
|
||||
|
||||
this._breakpointsForEventListeners.set(eventListener.eventListenerId, breakpoint);
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("DOM"))
|
||||
this._setEventBreakpoint(breakpoint, target);
|
||||
}
|
||||
|
||||
WI.debuggerManager.addProbesForBreakpoint(breakpoint);
|
||||
|
||||
WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointAdded, {breakpoint});
|
||||
}
|
||||
|
||||
removeBreakpointForEventListener(eventListener)
|
||||
{
|
||||
let breakpoint = this._breakpointsForEventListeners.take(eventListener.eventListenerId);
|
||||
if (!breakpoint)
|
||||
return;
|
||||
|
||||
// Disable the breakpoint first, so removing actions doesn't re-add the breakpoint.
|
||||
breakpoint.disabled = true;
|
||||
breakpoint.clearActions();
|
||||
|
||||
WI.debuggerManager.removeProbesForBreakpoint(breakpoint);
|
||||
|
||||
WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint});
|
||||
}
|
||||
|
||||
removeEventListenerBreakpointsForNode(domNode)
|
||||
{
|
||||
for (let breakpoint of Array.from(this._breakpointsForEventListeners.values())) {
|
||||
let eventListener = breakpoint.eventListener;
|
||||
if (eventListener.nodeId === domNode.id)
|
||||
this.removeBreakpointForEventListener(eventListener);
|
||||
}
|
||||
}
|
||||
|
||||
breakpointForEventListenerId(eventListenerId)
|
||||
{
|
||||
return this._breakpointsForEventListeners.get(eventListenerId) || null;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_setEventBreakpoint(breakpoint, target)
|
||||
{
|
||||
console.assert(!breakpoint.disabled, breakpoint);
|
||||
|
||||
let eventListener = breakpoint.eventListener;
|
||||
console.assert(eventListener);
|
||||
|
||||
if (!WI.debuggerManager.breakpointsDisabledTemporarily)
|
||||
WI.debuggerManager.breakpointsEnabled = true;
|
||||
|
||||
target.DOMAgent.setBreakpointForEventListener.invoke({
|
||||
eventListenerId: eventListener.eventListenerId,
|
||||
options: breakpoint.optionsToProtocol(),
|
||||
});
|
||||
}
|
||||
|
||||
_removeEventBreakpoint(breakpoint, target)
|
||||
{
|
||||
let eventListener = breakpoint.eventListener;
|
||||
console.assert(eventListener);
|
||||
|
||||
target.DOMAgent.removeBreakpointForEventListener(eventListener.eventListenerId);
|
||||
}
|
||||
|
||||
_handleEventBreakpointDisabledStateChanged(event)
|
||||
{
|
||||
let breakpoint = event.target;
|
||||
|
||||
// Non-specific event listener breakpoints are handled by `DOMDebuggerManager`.
|
||||
if (!breakpoint.eventListener)
|
||||
return;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (!target.hasDomain("DOM"))
|
||||
continue;
|
||||
|
||||
if (breakpoint.disabled)
|
||||
this._removeEventBreakpoint(breakpoint, target);
|
||||
else
|
||||
this._setEventBreakpoint(breakpoint, target);
|
||||
}
|
||||
}
|
||||
|
||||
_handleEventBreakpointEditablePropertyChanged(event)
|
||||
{
|
||||
let breakpoint = event.target;
|
||||
|
||||
// Non-specific event listener breakpoints are handled by `DOMDebuggerManager`.
|
||||
if (!breakpoint.eventListener)
|
||||
return;
|
||||
|
||||
if (breakpoint.disabled)
|
||||
return;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
// Clear the old breakpoint from the backend before setting the new one.
|
||||
this._removeEventBreakpoint(breakpoint, target);
|
||||
this._setEventBreakpoint(breakpoint, target);
|
||||
}
|
||||
}
|
||||
|
||||
_handleEventBreakpointActionsChanged(event)
|
||||
{
|
||||
let breakpoint = event.target;
|
||||
|
||||
// Non-specific event listener breakpoints are handled by `DOMDebuggerManager`.
|
||||
if (!breakpoint.eventListener)
|
||||
return;
|
||||
|
||||
this._handleEventBreakpointEditablePropertyChanged(event);
|
||||
|
||||
WI.debuggerManager.updateProbesForBreakpoint(breakpoint);
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
this._restoreSelectedNodeIsAllowed = true;
|
||||
|
||||
this.ensureDocument();
|
||||
|
||||
WI.DOMNode.resetDefaultLayoutOverlayConfiguration();
|
||||
}
|
||||
};
|
||||
|
||||
WI.DOMManager.Event = {
|
||||
AttributeModified: "dom-manager-attribute-modified",
|
||||
AttributeRemoved: "dom-manager-attribute-removed",
|
||||
CharacterDataModified: "dom-manager-character-data-modified",
|
||||
NodeInserted: "dom-manager-node-inserted",
|
||||
NodeRemoved: "dom-manager-node-removed",
|
||||
CustomElementStateChanged: "dom-manager-custom-element-state-changed",
|
||||
DocumentUpdated: "dom-manager-document-updated",
|
||||
ChildNodeCountUpdated: "dom-manager-child-node-count-updated",
|
||||
DOMNodeWasInspected: "dom-manager-dom-node-was-inspected",
|
||||
InspectModeStateChanged: "dom-manager-inspect-mode-state-changed",
|
||||
InspectedNodeChanged: "dom-manager-inspected-node-changed",
|
||||
};
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2013 Samsung Electronics. 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: DOMStorageManager lacks advanced multi-target support. (DOMStorage per-target)
|
||||
|
||||
WI.DOMStorageManager = class DOMStorageManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["DOMStorage"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "DOMStorage");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("DOMStorage"))
|
||||
target.DOMStorageAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get domStorageObjects() { return this._domStorageObjects; }
|
||||
|
||||
get cookieStorageObjects()
|
||||
{
|
||||
var cookieStorageObjects = [];
|
||||
for (var host in this._cookieStorageObjects)
|
||||
cookieStorageObjects.push(this._cookieStorageObjects[host]);
|
||||
return cookieStorageObjects;
|
||||
}
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._reset();
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.addEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("DOMStorage"))
|
||||
target.DOMStorageAgent.disable();
|
||||
}
|
||||
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// DOMStorageObserver
|
||||
|
||||
itemsCleared(storageId)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let domStorage = this._domStorageForIdentifier(storageId);
|
||||
if (domStorage)
|
||||
domStorage.itemsCleared(storageId);
|
||||
}
|
||||
|
||||
itemRemoved(storageId, key)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let domStorage = this._domStorageForIdentifier(storageId);
|
||||
if (domStorage)
|
||||
domStorage.itemRemoved(key);
|
||||
}
|
||||
|
||||
itemAdded(storageId, key, value)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let domStorage = this._domStorageForIdentifier(storageId);
|
||||
if (domStorage)
|
||||
domStorage.itemAdded(key, value);
|
||||
}
|
||||
|
||||
itemUpdated(storageId, key, oldValue, newValue)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let domStorage = this._domStorageForIdentifier(storageId);
|
||||
if (domStorage)
|
||||
domStorage.itemUpdated(key, oldValue, newValue);
|
||||
}
|
||||
|
||||
// InspectorObserver
|
||||
|
||||
inspectDOMStorage(id)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
var domStorage = this._domStorageForIdentifier(id);
|
||||
console.assert(domStorage);
|
||||
if (!domStorage)
|
||||
return;
|
||||
this.dispatchEventToListeners(WI.DOMStorageManager.Event.DOMStorageObjectWasInspected, {domStorage});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_reset()
|
||||
{
|
||||
this._domStorageObjects = [];
|
||||
this._cookieStorageObjects = {};
|
||||
|
||||
this.dispatchEventToListeners(DOMStorageManager.Event.Cleared);
|
||||
|
||||
let mainFrame = WI.networkManager.mainFrame;
|
||||
if (mainFrame) {
|
||||
this._addDOMStorageIfNeeded(mainFrame);
|
||||
this._addCookieStorageIfNeeded(mainFrame);
|
||||
}
|
||||
}
|
||||
|
||||
_domStorageForIdentifier(id)
|
||||
{
|
||||
for (var storageObject of this._domStorageObjects) {
|
||||
// The id is an object, so we need to compare the properties using Object.shallowEqual.
|
||||
if (Object.shallowEqual(storageObject.id, id))
|
||||
return storageObject;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_addDOMStorageIfNeeded(frame)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (!InspectorBackend.hasDomain("DOMStorage"))
|
||||
return;
|
||||
|
||||
// Don't show storage if we don't have a security origin (about:blank).
|
||||
if (!frame.securityOrigin || frame.securityOrigin === "://")
|
||||
return;
|
||||
|
||||
// FIXME: Consider passing the other parts of the origin along.
|
||||
|
||||
let addDOMStorage = (isLocalStorage) => {
|
||||
let identifier = {securityOrigin: frame.securityOrigin, isLocalStorage};
|
||||
if (this._domStorageForIdentifier(identifier))
|
||||
return;
|
||||
|
||||
let domStorage = new WI.DOMStorageObject(identifier, frame.mainResource.urlComponents.host, identifier.isLocalStorage);
|
||||
this._domStorageObjects.push(domStorage);
|
||||
this.dispatchEventToListeners(DOMStorageManager.Event.DOMStorageObjectWasAdded, {domStorage});
|
||||
};
|
||||
addDOMStorage(true);
|
||||
addDOMStorage(false);
|
||||
}
|
||||
|
||||
_addCookieStorageIfNeeded(frame)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (!InspectorBackend.hasCommand("Page.getCookies"))
|
||||
return;
|
||||
|
||||
// Add the host of the frame that changed the main resource to the list of hosts there could be cookies for.
|
||||
let host = parseURL(frame.url).host;
|
||||
if (!host)
|
||||
return;
|
||||
|
||||
if (this._cookieStorageObjects[host])
|
||||
return;
|
||||
|
||||
this._cookieStorageObjects[host] = new WI.CookieStorageObject(host);
|
||||
this.dispatchEventToListeners(WI.DOMStorageManager.Event.CookieStorageObjectWasAdded, {cookieStorage: this._cookieStorageObjects[host]});
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (event.target.isMainFrame()) {
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
|
||||
this._addCookieStorageIfNeeded(event.target);
|
||||
}
|
||||
|
||||
_securityOriginDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
this._addDOMStorageIfNeeded(event.target);
|
||||
}
|
||||
};
|
||||
|
||||
WI.DOMStorageManager.Event = {
|
||||
CookieStorageObjectWasAdded: "dom-storage-manager-cookie-storage-object-was-added",
|
||||
DOMStorageObjectWasAdded: "dom-storage-manager-dom-storage-object-was-added",
|
||||
DOMStorageObjectWasInspected: "dom-storage-manager-dom-storage-object-was-inspected",
|
||||
Cleared: "dom-storage-manager-cleared",
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2013 Samsung Electronics. 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: DatabaseManager lacks advanced multi-target support. (DataBase per-target)
|
||||
|
||||
WI.DatabaseManager = class DatabaseManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["Database"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "Database");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("Database"))
|
||||
target.DatabaseAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get databases() { return this._databaseObjects; }
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._reset();
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("Database"))
|
||||
target.DatabaseAgent.disable();
|
||||
}
|
||||
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// DatabaseObserver
|
||||
|
||||
databaseWasAdded(id, host, name, version)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
var database = new WI.DatabaseObject(id, host, name, version);
|
||||
|
||||
this._databaseObjects.push(database);
|
||||
this.dispatchEventToListeners(WI.DatabaseManager.Event.DatabaseWasAdded, {database});
|
||||
}
|
||||
|
||||
// InspectorObserver
|
||||
|
||||
inspectDatabase(id)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
var database = this._databaseForIdentifier(id);
|
||||
console.assert(database);
|
||||
if (!database)
|
||||
return;
|
||||
this.dispatchEventToListeners(WI.DatabaseManager.Event.DatabaseWasInspected, {database});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_reset()
|
||||
{
|
||||
this._databaseObjects = [];
|
||||
|
||||
this.dispatchEventToListeners(WI.DatabaseManager.Event.Cleared);
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (event.target.isMainFrame())
|
||||
this._reset();
|
||||
}
|
||||
|
||||
_databaseForIdentifier(id)
|
||||
{
|
||||
for (var i = 0; i < this._databaseObjects.length; ++i) {
|
||||
if (this._databaseObjects[i].id === id)
|
||||
return this._databaseObjects[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
WI.DatabaseManager.Event = {
|
||||
DatabaseWasAdded: "database-manager-database-was-added",
|
||||
DatabaseWasInspected: "database-manager-database-was-inspected",
|
||||
Cleared: "database-manager-cleared",
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.DiagnosticController = class DiagnosticController
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this._diagnosticLoggingAvailable = false;
|
||||
this._recorders = new Set;
|
||||
|
||||
this._autoLogDiagnosticEventsToConsole = WI.settings.debugAutoLogDiagnosticEvents.value;
|
||||
this._logToConsoleMethod = window.InspectorTest ? InspectorTest.log.bind(InspectorTest) : console.log;
|
||||
|
||||
WI.settings.debugEnableDiagnosticLogging.addEventListener(WI.Setting.Event.Changed, this._debugEnableDiagnosticLoggingSettingDidChange, this);
|
||||
WI.settings.debugAutoLogDiagnosticEvents.addEventListener(WI.Setting.Event.Changed, this._debugAutoLogDiagnosticEventsSettingDidChange, this);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get diagnosticLoggingAvailable()
|
||||
{
|
||||
return this._diagnosticLoggingAvailable;
|
||||
}
|
||||
|
||||
set diagnosticLoggingAvailable(available)
|
||||
{
|
||||
if (this._diagnosticLoggingAvailable === available)
|
||||
return;
|
||||
|
||||
this._diagnosticLoggingAvailable = available;
|
||||
this._updateRecorderStates();
|
||||
}
|
||||
|
||||
addRecorder(recorder)
|
||||
{
|
||||
console.assert(!this._recorders.has(recorder), "Tried to add the same diagnostic recorder more than once.");
|
||||
this._recorders.add(recorder);
|
||||
this._updateRecorderStates();
|
||||
}
|
||||
|
||||
logDiagnosticEvent(eventName, payload)
|
||||
{
|
||||
// Don't rely on a diagnostic logging delegate to unit test frontend diagnostics code.
|
||||
if (window.InspectorTest) {
|
||||
this._logToConsoleMethod(`Received diagnostic event: ${eventName} => ${JSON.stringify(payload)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._autoLogDiagnosticEventsToConsole)
|
||||
this._logToConsoleMethod(eventName, payload);
|
||||
|
||||
InspectorFrontendHost.logDiagnosticEvent(eventName, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_debugEnableDiagnosticLoggingSettingDidChange()
|
||||
{
|
||||
this._updateRecorderStates();
|
||||
}
|
||||
|
||||
_debugAutoLogDiagnosticEventsSettingDidChange()
|
||||
{
|
||||
this._autoLogDiagnosticEventsToConsole = WI.settings.debugAutoLogDiagnosticEvents.value;
|
||||
}
|
||||
|
||||
_updateRecorderStates()
|
||||
{
|
||||
let isActive = this._diagnosticLoggingAvailable && WI.settings.debugEnableDiagnosticLogging.value;
|
||||
for (let recorder of this._recorders)
|
||||
recorder.active = isActive;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.DiagnosticEventRecorder = class DiagnosticEventRecorder
|
||||
{
|
||||
constructor(name, controller)
|
||||
{
|
||||
console.assert(controller instanceof WI.DiagnosticController, controller);
|
||||
|
||||
this._name = name;
|
||||
this._active = false;
|
||||
this._controller = controller;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get name() { return this._name; }
|
||||
|
||||
get active()
|
||||
{
|
||||
return this._active;
|
||||
}
|
||||
|
||||
set active(value)
|
||||
{
|
||||
if (this._active === value)
|
||||
return;
|
||||
|
||||
this._active = value;
|
||||
|
||||
if (this._active)
|
||||
this.setup();
|
||||
else
|
||||
this.teardown();
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
logDiagnosticEvent(eventName, payload)
|
||||
{
|
||||
if (this._active)
|
||||
this._controller.logDiagnosticEvent(eventName, payload);
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
throw WI.NotImplementedError.subclassMustOverride();
|
||||
}
|
||||
|
||||
teardown()
|
||||
{
|
||||
throw WI.NotImplementedError.subclassMustOverride();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Antoine Quint
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.DragToAdjustController = class DragToAdjustController
|
||||
{
|
||||
constructor(delegate)
|
||||
{
|
||||
this._delegate = delegate;
|
||||
|
||||
this._element = null;
|
||||
this._active = false;
|
||||
this._enabled = false;
|
||||
this._dragging = false;
|
||||
this._tracksMouseClickAndDrag = false;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get element()
|
||||
{
|
||||
return this._element;
|
||||
}
|
||||
|
||||
set element(element)
|
||||
{
|
||||
this._element = element;
|
||||
}
|
||||
|
||||
get enabled()
|
||||
{
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
set enabled(enabled)
|
||||
{
|
||||
if (this._enabled === enabled)
|
||||
return;
|
||||
|
||||
if (enabled) {
|
||||
this._element.addEventListener("mouseenter", this);
|
||||
this._element.addEventListener("mouseleave", this);
|
||||
} else {
|
||||
this._element.removeEventListener("mouseenter", this);
|
||||
this._element.removeEventListener("mouseleave", this);
|
||||
}
|
||||
}
|
||||
|
||||
get active()
|
||||
{
|
||||
return this._active;
|
||||
}
|
||||
|
||||
set active(active)
|
||||
{
|
||||
if (!this._element)
|
||||
return;
|
||||
|
||||
if (this._active === active)
|
||||
return;
|
||||
|
||||
if (active) {
|
||||
WI.notifications.addEventListener(WI.Notification.GlobalModifierKeysDidChange, this._modifiersDidChange, this);
|
||||
this._element.addEventListener("mousemove", this);
|
||||
} else {
|
||||
WI.notifications.removeEventListener(WI.Notification.GlobalModifierKeysDidChange, this._modifiersDidChange, this);
|
||||
this._element.removeEventListener("mousemove", this);
|
||||
this._setTracksMouseClickAndDrag(false);
|
||||
}
|
||||
|
||||
this._active = active;
|
||||
|
||||
if (this._delegate && typeof this._delegate.dragToAdjustControllerActiveStateChanged === "function")
|
||||
this._delegate.dragToAdjustControllerActiveStateChanged(this);
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this._setTracksMouseClickAndDrag(false);
|
||||
this._element.classList.remove(WI.DragToAdjustController.StyleClassName);
|
||||
|
||||
if (this._delegate && typeof this._delegate.dragToAdjustControllerDidReset === "function")
|
||||
this._delegate.dragToAdjustControllerDidReset(this);
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
handleEvent(event)
|
||||
{
|
||||
switch (event.type) {
|
||||
case "mouseenter":
|
||||
if (!this._dragging) {
|
||||
if (this._delegate && typeof this._delegate.dragToAdjustControllerCanBeActivated === "function")
|
||||
this.active = this._delegate.dragToAdjustControllerCanBeActivated(this);
|
||||
else
|
||||
this.active = true;
|
||||
}
|
||||
break;
|
||||
case "mouseleave":
|
||||
if (!this._dragging)
|
||||
this.active = false;
|
||||
break;
|
||||
case "mousemove":
|
||||
if (this._dragging)
|
||||
this._mouseWasDragged(event);
|
||||
else
|
||||
this._mouseMoved(event);
|
||||
break;
|
||||
case "mousedown":
|
||||
this._mouseWasPressed(event);
|
||||
break;
|
||||
case "mouseup":
|
||||
this._mouseWasReleased(event);
|
||||
break;
|
||||
case "contextmenu":
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_setDragging(dragging)
|
||||
{
|
||||
if (this._dragging === dragging)
|
||||
return;
|
||||
|
||||
console.assert(window.event);
|
||||
if (dragging)
|
||||
WI.elementDragStart(this._element, this, this, window.event, "col-resize", window);
|
||||
else
|
||||
WI.elementDragEnd(window.event);
|
||||
|
||||
this._dragging = dragging;
|
||||
}
|
||||
|
||||
_setTracksMouseClickAndDrag(tracksMouseClickAndDrag)
|
||||
{
|
||||
if (this._tracksMouseClickAndDrag === tracksMouseClickAndDrag)
|
||||
return;
|
||||
|
||||
if (tracksMouseClickAndDrag) {
|
||||
this._element.classList.add(WI.DragToAdjustController.StyleClassName);
|
||||
window.addEventListener("mousedown", this, true);
|
||||
window.addEventListener("contextmenu", this, true);
|
||||
} else {
|
||||
this._element.classList.remove(WI.DragToAdjustController.StyleClassName);
|
||||
window.removeEventListener("mousedown", this, true);
|
||||
window.removeEventListener("contextmenu", this, true);
|
||||
this._setDragging(false);
|
||||
}
|
||||
|
||||
this._tracksMouseClickAndDrag = tracksMouseClickAndDrag;
|
||||
}
|
||||
|
||||
_modifiersDidChange(event)
|
||||
{
|
||||
var canBeAdjusted = WI.modifierKeys.altKey;
|
||||
if (canBeAdjusted && this._delegate && typeof this._delegate.dragToAdjustControllerCanBeAdjusted === "function")
|
||||
canBeAdjusted = this._delegate.dragToAdjustControllerCanBeAdjusted(this);
|
||||
|
||||
this._setTracksMouseClickAndDrag(canBeAdjusted);
|
||||
}
|
||||
|
||||
_mouseMoved(event)
|
||||
{
|
||||
var canBeAdjusted = event.altKey;
|
||||
if (canBeAdjusted && this._delegate && typeof this._delegate.dragToAdjustControllerCanAdjustObjectAtPoint === "function")
|
||||
canBeAdjusted = this._delegate.dragToAdjustControllerCanAdjustObjectAtPoint(this, WI.Point.fromEvent(event));
|
||||
|
||||
this._setTracksMouseClickAndDrag(canBeAdjusted);
|
||||
}
|
||||
|
||||
_mouseWasPressed(event)
|
||||
{
|
||||
this._lastX = event.screenX;
|
||||
|
||||
this._setDragging(true);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
_mouseWasDragged(event)
|
||||
{
|
||||
var x = event.screenX;
|
||||
var amount = x - this._lastX;
|
||||
|
||||
if (Math.abs(amount) < 1)
|
||||
return;
|
||||
|
||||
this._lastX = x;
|
||||
|
||||
if (event.ctrlKey)
|
||||
amount /= 10;
|
||||
else if (event.shiftKey)
|
||||
amount *= 10;
|
||||
|
||||
if (this._delegate && typeof this._delegate.dragToAdjustControllerWasAdjustedByAmount === "function")
|
||||
this._delegate.dragToAdjustControllerWasAdjustedByAmount(this, amount);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
_mouseWasReleased(event)
|
||||
{
|
||||
this._setDragging(false);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
WI.DragToAdjustController.StyleClassName = "drag-to-adjust";
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
WI.ExtensionTabActivationDiagnosticEventRecorder = class ExtensionTabActivationDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
|
||||
{
|
||||
constructor(controller)
|
||||
{
|
||||
super("ExtensionTabActivation", controller);
|
||||
|
||||
this._reportedExtensionTabIDs = new Set;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
setup()
|
||||
{
|
||||
WI.tabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
|
||||
}
|
||||
|
||||
teardown()
|
||||
{
|
||||
WI.tabBrowser.removeEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_selectedTabContentViewDidChange(event)
|
||||
{
|
||||
let selectedTab = event.data.incomingTab;
|
||||
if (!(selectedTab instanceof WI.WebInspectorExtensionTabContentView))
|
||||
return;
|
||||
|
||||
let extension = selectedTab.extension;
|
||||
console.assert(extension instanceof WI.WebInspectorExtension, "Extension tab should have an associated extension.");
|
||||
|
||||
// Only report the first selection of an extension tab.
|
||||
if (this._reportedExtensionTabIDs.has(selectedTab.extensionTabID))
|
||||
return;
|
||||
|
||||
this._reportedExtensionTabIDs.add(selectedTab.extensionTabID);
|
||||
|
||||
this.logDiagnosticEvent(this.name, {
|
||||
extensionBundleIdentifier: extension.extensionBundleIdentifier,
|
||||
extensionTabName: selectedTab.tabInfo().displayName,
|
||||
activeExtensionTabCount: WI.sharedApp.extensionController.activeExtensionTabContentViews().length,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.Formatter = class Formatter
|
||||
{
|
||||
constructor(codeMirror, builder)
|
||||
{
|
||||
console.assert(codeMirror);
|
||||
console.assert(builder);
|
||||
|
||||
this._codeMirror = codeMirror;
|
||||
this._builder = builder;
|
||||
|
||||
this._lastToken = null;
|
||||
this._lastContent = "";
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
format(from, to)
|
||||
{
|
||||
console.assert(this._builder.originalContent === null);
|
||||
if (this._builder.originalContent !== null)
|
||||
return;
|
||||
|
||||
var outerMode = this._codeMirror.getMode();
|
||||
var content = this._codeMirror.getRange(from, to);
|
||||
var state = CodeMirror.copyState(outerMode, this._codeMirror.getTokenAt(from).state);
|
||||
this._builder.setOriginalContent(content);
|
||||
|
||||
var lineOffset = 0;
|
||||
var lines = content.split("\n");
|
||||
for (var i = 0; i < lines.length; ++i) {
|
||||
var line = lines[i];
|
||||
var startOfNewLine = true;
|
||||
var firstTokenOnLine = true;
|
||||
var stream = new CodeMirror.StringStream(line);
|
||||
while (!stream.eol()) {
|
||||
var innerMode = CodeMirror.innerMode(outerMode, state);
|
||||
var token = outerMode.token(stream, state);
|
||||
var isWhiteSpace = token === null && /^\s*$/.test(stream.current());
|
||||
this._handleToken(innerMode.mode, token, state, stream, lineOffset + stream.start, isWhiteSpace, startOfNewLine, firstTokenOnLine);
|
||||
stream.start = stream.pos;
|
||||
startOfNewLine = false;
|
||||
if (firstTokenOnLine && !isWhiteSpace)
|
||||
firstTokenOnLine = false;
|
||||
}
|
||||
|
||||
if (firstTokenOnLine)
|
||||
this._handleEmptyLine();
|
||||
|
||||
lineOffset += line.length + 1; // +1 for the "\n" removed in split.
|
||||
this._handleLineEnding(lineOffset - 1); // -1 for the index of the "\n".
|
||||
}
|
||||
|
||||
this._builder.finish();
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_handleToken(mode, token, state, stream, originalPosition, isWhiteSpace, startOfNewLine, firstTokenOnLine)
|
||||
{
|
||||
// String content of the token.
|
||||
var content = stream.current();
|
||||
|
||||
// Start of a new line. Insert a newline to be safe if code was not-ASI safe. These are collapsed.
|
||||
if (startOfNewLine)
|
||||
this._builder.appendNewline();
|
||||
|
||||
// Whitespace. Remove all spaces or collapse to a single space.
|
||||
if (isWhiteSpace) {
|
||||
this._builder.appendSpace();
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid some hooks for content in comments.
|
||||
var isComment = token && /\bcomment\b/.test(token);
|
||||
|
||||
if (mode.modifyStateForTokenPre)
|
||||
mode.modifyStateForTokenPre(this._lastToken, this._lastContent, token, state, content, isComment);
|
||||
|
||||
// Should we remove the last whitespace?
|
||||
if (this._builder.lastTokenWasWhitespace && mode.removeLastWhitespace(this._lastToken, this._lastContent, token, state, content, isComment))
|
||||
this._builder.removeLastWhitespace();
|
||||
|
||||
// Should we remove the last newline?
|
||||
if (this._builder.lastTokenWasNewline && mode.removeLastNewline(this._lastToken, this._lastContent, token, state, content, isComment, firstTokenOnLine))
|
||||
this._builder.removeLastNewline();
|
||||
|
||||
// Add whitespace after the last token?
|
||||
if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceAfterLastToken(this._lastToken, this._lastContent, token, state, content, isComment))
|
||||
this._builder.appendSpace();
|
||||
|
||||
// Add whitespace before this token?
|
||||
if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
|
||||
this._builder.appendSpace();
|
||||
|
||||
// Should we dedent before this token?
|
||||
var dedents = mode.dedentsBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment);
|
||||
while (dedents-- > 0)
|
||||
this._builder.dedent();
|
||||
|
||||
// Should we add a newline before this token?
|
||||
if (mode.newlineBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
|
||||
this._builder.appendNewline();
|
||||
|
||||
// Should we indent before this token?
|
||||
if (mode.indentBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
|
||||
this._builder.indent();
|
||||
|
||||
// Append token.
|
||||
this._builder.appendToken(content, originalPosition);
|
||||
|
||||
// Let the pretty printer update any state it keeps track of.
|
||||
if (mode.modifyStateForTokenPost)
|
||||
mode.modifyStateForTokenPost(this._lastToken, this._lastContent, token, state, content, isComment);
|
||||
|
||||
// Should we indent or dedent after this token?
|
||||
if (!isComment && mode.indentAfterToken(this._lastToken, this._lastContent, token, state, content, isComment))
|
||||
this._builder.indent();
|
||||
|
||||
// Should we add newlines after this token?
|
||||
var newlines = mode.newlinesAfterToken(this._lastToken, this._lastContent, token, state, content, isComment);
|
||||
if (newlines)
|
||||
this._builder.appendMultipleNewlines(newlines);
|
||||
|
||||
// Record this token as the last token.
|
||||
this._lastToken = token;
|
||||
this._lastContent = content;
|
||||
}
|
||||
|
||||
_handleEmptyLine()
|
||||
{
|
||||
// Preserve original whitespace only lines by adding a newline.
|
||||
// However, don't do this if the builder just added multiple newlines.
|
||||
if (!(this._builder.lastTokenWasNewline && this._builder.lastNewlineAppendWasMultiple))
|
||||
this._builder.appendNewline(true);
|
||||
}
|
||||
|
||||
_handleLineEnding(originalNewLinePosition)
|
||||
{
|
||||
// Record the original line ending.
|
||||
this._builder.addOriginalLineEnding(originalNewLinePosition);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.FormatterSourceMap = class FormatterSourceMap extends WI.Object
|
||||
{
|
||||
constructor(originalLineEndings, formattedLineEndings, mapping)
|
||||
{
|
||||
super();
|
||||
|
||||
this._originalLineEndings = originalLineEndings;
|
||||
this._formattedLineEndings = formattedLineEndings;
|
||||
this._mapping = mapping;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static fromSourceMapData({originalLineEndings, formattedLineEndings, mapping})
|
||||
{
|
||||
return new WI.FormatterSourceMap(originalLineEndings, formattedLineEndings, mapping);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
originalToFormatted(lineNumber, columnNumber)
|
||||
{
|
||||
var originalPosition = this._locationToPosition(this._originalLineEndings, lineNumber || 0, columnNumber || 0);
|
||||
return this.originalPositionToFormatted(originalPosition);
|
||||
}
|
||||
|
||||
originalPositionToFormatted(originalPosition)
|
||||
{
|
||||
var formattedPosition = this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition);
|
||||
return this._positionToLocation(this._formattedLineEndings, formattedPosition);
|
||||
}
|
||||
|
||||
originalPositionToFormattedPosition(originalPosition)
|
||||
{
|
||||
return this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition);
|
||||
}
|
||||
|
||||
formattedToOriginal(lineNumber, columnNumber)
|
||||
{
|
||||
var originalPosition = this.formattedToOriginalOffset(lineNumber, columnNumber);
|
||||
return this._positionToLocation(this._originalLineEndings, originalPosition);
|
||||
}
|
||||
|
||||
formattedToOriginalOffset(lineNumber, columnNumber)
|
||||
{
|
||||
var formattedPosition = this._locationToPosition(this._formattedLineEndings, lineNumber || 0, columnNumber || 0);
|
||||
var originalPosition = this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition);
|
||||
return originalPosition;
|
||||
}
|
||||
|
||||
formattedPositionToOriginalPosition(formattedPosition)
|
||||
{
|
||||
return this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_locationToPosition(lineEndings, lineNumber, columnNumber)
|
||||
{
|
||||
var lineOffset = lineNumber ? lineEndings[lineNumber - 1] + 1 : 0;
|
||||
return lineOffset + columnNumber;
|
||||
}
|
||||
|
||||
_positionToLocation(lineEndings, position)
|
||||
{
|
||||
var lineNumber = lineEndings.upperBound(position - 1);
|
||||
if (!lineNumber)
|
||||
var columnNumber = position;
|
||||
else
|
||||
var columnNumber = position - lineEndings[lineNumber - 1] - 1;
|
||||
return {lineNumber, columnNumber};
|
||||
}
|
||||
|
||||
_convertPosition(positions1, positions2, positionInPosition1)
|
||||
{
|
||||
var index = positions1.upperBound(positionInPosition1) - 1;
|
||||
var convertedPosition = positions2[index] + positionInPosition1 - positions1[index];
|
||||
if (index < positions2.length - 1 && convertedPosition > positions2[index + 1])
|
||||
convertedPosition = positions2[index + 1];
|
||||
return convertedPosition;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
WI.GestureController = class GestureController
|
||||
{
|
||||
constructor(target, delegate, {container, supportsScale, supportsTranslate})
|
||||
{
|
||||
console.assert(target instanceof Node, target);
|
||||
console.assert(!container || container instanceof Node, container);
|
||||
console.assert(!supportsScale || typeof delegate.gestureControllerDidScale === "function", delegate.gestureControllerDidScale);
|
||||
console.assert(!supportsTranslate || typeof delegate.gestureControllerDidTranslate === "function", delegate.gestureControllerDidTranslate);
|
||||
console.assert(supportsScale || supportsTranslate, "expects at least one gesture");
|
||||
|
||||
this._target = target;
|
||||
this._delegate = delegate;
|
||||
|
||||
this._scale = 1;
|
||||
this._translate = {x: 0, y: 0};
|
||||
|
||||
this._mouseWheelDelta = 0;
|
||||
|
||||
container ||= target;
|
||||
|
||||
this._supportsScale = supportsScale || false;
|
||||
if (this._supportsScale) {
|
||||
container.addEventListener("wheel", this._handleWheel.bind(this));
|
||||
container.addEventListener("gesturestart", this._handleGestureStart.bind(this));
|
||||
container.addEventListener("gesturechange", this._handleGestureChange.bind(this));
|
||||
container.addEventListener("gestureend", this._handleGestureEnd.bind(this));
|
||||
}
|
||||
|
||||
this._supportsTranslate = supportsTranslate || false;
|
||||
if (this._supportsTranslate) {
|
||||
console.assert(!container.draggable, "cannot have both a translate gesture and dragging");
|
||||
container.addEventListener("mousedown", this._handleMouseDown.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get scale()
|
||||
{
|
||||
return this._scale;
|
||||
}
|
||||
|
||||
set scale(scale)
|
||||
{
|
||||
console.assert(this._supportsScale);
|
||||
|
||||
scale = Number.constrain(scale, 0.01, 100);
|
||||
if (scale === this._scale)
|
||||
return;
|
||||
|
||||
this._scale = scale;
|
||||
|
||||
this._delegate.gestureControllerDidScale(this);
|
||||
}
|
||||
|
||||
get translate()
|
||||
{
|
||||
return this._translate;
|
||||
}
|
||||
|
||||
set translate(translate)
|
||||
{
|
||||
console.assert(this._supportsTranslate);
|
||||
|
||||
if (translate.x === this._translate.x && translate.y === this._translate.y)
|
||||
return;
|
||||
|
||||
this._translate = translate;
|
||||
|
||||
this._delegate.gestureControllerDidTranslate(this);
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this.scale = 1;
|
||||
this.translate = {x: 0, y: 0};
|
||||
|
||||
this._mouseWheelDelta = 0;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_startScaleInteraction(event)
|
||||
{
|
||||
this._scaleInteractionStartScale = this._scale;
|
||||
if (this._supportsTranslate)
|
||||
this._scaleInteractionStartTranslate = this._translate;
|
||||
|
||||
if (event.target === this._target) {
|
||||
let elementBounds = this._target.getBoundingClientRect();
|
||||
this._scaleInteractionStartPosition = {
|
||||
x: (event.pageX - elementBounds.left - (elementBounds.width / 2)) / this._scaleInteractionStartScale,
|
||||
y: (event.pageY - elementBounds.top - (elementBounds.height / 2)) / this._scaleInteractionStartScale,
|
||||
};
|
||||
} else
|
||||
this._scaleInteractionStartPosition = {x: 0, y: 0};
|
||||
}
|
||||
|
||||
_updateScaleInteraction(scale)
|
||||
{
|
||||
this.scale = this._scaleInteractionStartScale * scale;
|
||||
|
||||
if (this._supportsTranslate) {
|
||||
this.translate = {
|
||||
x: this._scaleInteractionStartTranslate.x - (this._scaleInteractionStartPosition.x * (this._scale - this._scaleInteractionStartScale)),
|
||||
y: this._scaleInteractionStartTranslate.y - (this._scaleInteractionStartPosition.y * (this._scale - this._scaleInteractionStartScale)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_endScaleInteraction() {
|
||||
this._scaleInteractionStartScale = NaN;
|
||||
if (this._supportsTranslate)
|
||||
this._scaleInteractionStartTranslate = null;
|
||||
|
||||
this._scaleInteractionStartPosition = null;
|
||||
}
|
||||
|
||||
_handleWheel(event)
|
||||
{
|
||||
// Ignore wheel events while handing gestures.
|
||||
if (this._handlingGesture)
|
||||
return;
|
||||
|
||||
// Require twice the vertical delta to overcome horizontal scrolling.
|
||||
// This prevents most cases of inadvertent zooming for slightly diagonal scrolls.
|
||||
if (Math.abs(event.deltaX) >= Math.abs(event.deltaY) * 0.5)
|
||||
return;
|
||||
|
||||
let deviceDirection = event.webkitDirectionInvertedFromDevice ? -1 : 1;
|
||||
let delta = (event.deltaZ || event.deltaY || event.deltaX) * deviceDirection / 1000;
|
||||
|
||||
// Reset accumulated wheel delta when direction changes.
|
||||
if (delta < 0 && this._mouseWheelDelta >= 0 || delta >= 0 && this._mouseWheelDelta < 0)
|
||||
this._mouseWheelDelta = 0;
|
||||
|
||||
this._mouseWheelDelta += delta;
|
||||
|
||||
this._startScaleInteraction(event);
|
||||
this._updateScaleInteraction(1 - this._mouseWheelDelta);
|
||||
this._endScaleInteraction();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
_handleGestureStart(event)
|
||||
{
|
||||
console.assert(!this._handlingGesture);
|
||||
this._handlingGesture = true;
|
||||
|
||||
this._startScaleInteraction(event);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
_handleGestureChange(event)
|
||||
{
|
||||
console.assert(this._handlingGesture);
|
||||
|
||||
this._updateScaleInteraction(event.scale);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
_handleGestureEnd(event)
|
||||
{
|
||||
console.assert(this._handlingGesture);
|
||||
|
||||
this._handlingGesture = false;
|
||||
|
||||
this._endScaleInteraction();
|
||||
}
|
||||
|
||||
_handleMouseDown(event)
|
||||
{
|
||||
if (event.target.draggable)
|
||||
return;
|
||||
|
||||
if (event.button !== 0)
|
||||
return;
|
||||
|
||||
this._translateInteractionStartTranslate = this._translate;
|
||||
|
||||
this._translateInteractionStartPosition = {
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
};
|
||||
|
||||
console.assert(!this._boundHandleMouseMove);
|
||||
this._boundHandleMouseMove = this._handleMouseMove.bind(this);
|
||||
window.addEventListener("mousemove", this._boundHandleMouseMove, {capture: true});
|
||||
|
||||
console.assert(!this._boundHandleMouseUp);
|
||||
this._boundHandleMouseUp = this._handleMouseUp.bind(this);
|
||||
window.addEventListener("mouseup", this._boundHandleMouseUp, {capture: true});
|
||||
}
|
||||
|
||||
_handleMouseMove(event)
|
||||
{
|
||||
this.translate = {
|
||||
x: this._translateInteractionStartTranslate.x + (event.pageX - this._translateInteractionStartPosition.x),
|
||||
y: this._translateInteractionStartTranslate.y + (event.pageY - this._translateInteractionStartPosition.y),
|
||||
};
|
||||
}
|
||||
|
||||
_handleMouseUp(event)
|
||||
{
|
||||
window.removeEventListener("mousemove", this._boundHandleMouseMove, {capture: true});
|
||||
this._boundHandleMouseMove = null;
|
||||
|
||||
window.removeEventListener("mouseup", this._boundHandleMouseUp, {capture: true});
|
||||
this._boundHandleMouseUp = null;
|
||||
|
||||
this._translateInteractionStartTranslate = null;
|
||||
this._translateInteractionStartPosition = null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Copyright (C) 2017-2018 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.
|
||||
*/
|
||||
|
||||
// HTTP Archive (HAR) format - Version 1.2
|
||||
// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html#sec-har-object-types-creator
|
||||
// http://www.softwareishard.com/blog/har-12-spec/
|
||||
|
||||
WI.HARBuilder = class HARBuilder
|
||||
{
|
||||
static async buildArchive(resources)
|
||||
{
|
||||
let promises = [];
|
||||
for (let resource of resources) {
|
||||
console.assert(resource.finished);
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
// Always resolve.
|
||||
resource.requestContent().then(
|
||||
(x) => resolve(x),
|
||||
() => resolve(null)
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
let contents = await Promise.all(promises);
|
||||
console.assert(contents.length === resources.length);
|
||||
|
||||
return {
|
||||
log: {
|
||||
version: "1.2",
|
||||
creator: HARBuilder.creator(),
|
||||
pages: HARBuilder.pages(),
|
||||
entries: resources.map((resource, index) => HARBuilder.entry(resource, contents[index])),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static creator()
|
||||
{
|
||||
return {
|
||||
name: "WebKit Web Inspector",
|
||||
version: "1.0",
|
||||
};
|
||||
}
|
||||
|
||||
static pages()
|
||||
{
|
||||
return [{
|
||||
startedDateTime: HARBuilder.date(WI.networkManager.mainFrame.mainResource.requestSentDate),
|
||||
id: "page_0",
|
||||
title: WI.networkManager.mainFrame.url || "",
|
||||
pageTimings: HARBuilder.pageTimings(),
|
||||
}];
|
||||
}
|
||||
|
||||
static pageTimings()
|
||||
{
|
||||
let result = {};
|
||||
|
||||
let domContentReadyEventTimestamp = WI.networkManager.mainFrame.domContentReadyEventTimestamp;
|
||||
if (!isNaN(domContentReadyEventTimestamp))
|
||||
result.onContentLoad = domContentReadyEventTimestamp * 1000;
|
||||
|
||||
let loadEventTimestamp = WI.networkManager.mainFrame.loadEventTimestamp;
|
||||
if (!isNaN(loadEventTimestamp))
|
||||
result.onLoad = loadEventTimestamp * 1000;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static entry(resource, content)
|
||||
{
|
||||
let entry = {
|
||||
pageref: "page_0",
|
||||
startedDateTime: HARBuilder.date(resource.requestSentDate),
|
||||
time: 0,
|
||||
request: HARBuilder.request(resource),
|
||||
response: HARBuilder.response(resource, content),
|
||||
cache: HARBuilder.cache(resource),
|
||||
timings: HARBuilder.timings(resource),
|
||||
};
|
||||
|
||||
if (resource.timingData.startTime && resource.timingData.responseEnd)
|
||||
entry.time = (resource.timingData.responseEnd - resource.timingData.startTime) * 1000;
|
||||
if (resource.remoteAddress) {
|
||||
entry.serverIPAddress = HARBuilder.ipAddress(resource.remoteAddress);
|
||||
|
||||
// WebKit Custom Field `_serverPort`.
|
||||
if (entry.serverIPAddress)
|
||||
entry._serverPort = HARBuilder.port(resource.remoteAddress);
|
||||
}
|
||||
if (resource.connectionIdentifier)
|
||||
entry.connection = "" + resource.connectionIdentifier;
|
||||
|
||||
// CFNetwork Custom Field `_fetchType`.
|
||||
if (resource.responseSource !== WI.Resource.ResponseSource.Unknown)
|
||||
entry._fetchType = HARBuilder.fetchType(resource.responseSource);
|
||||
|
||||
// WebKit Custom Field `_priority`.
|
||||
if (resource.priority !== WI.Resource.NetworkPriority.Unknown)
|
||||
entry._priority = HARBuilder.priority(resource.priority);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static request(resource)
|
||||
{
|
||||
let result = {
|
||||
method: resource.requestMethod || "",
|
||||
url: resource.url || "",
|
||||
httpVersion: WI.Resource.displayNameForProtocol(resource.protocol) || "",
|
||||
cookies: HARBuilder.cookies(resource.requestCookies, null),
|
||||
headers: HARBuilder.headers(resource.requestHeaders),
|
||||
queryString: resource.queryStringParameters || [],
|
||||
headersSize: !isNaN(resource.requestHeadersTransferSize) ? resource.requestHeadersTransferSize : -1,
|
||||
bodySize: !isNaN(resource.requestBodyTransferSize) ? resource.requestBodyTransferSize : -1,
|
||||
};
|
||||
|
||||
if (resource.requestData)
|
||||
result.postData = HARBuilder.postData(resource);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static response(resource, content)
|
||||
{
|
||||
let result = {
|
||||
status: resource.statusCode || 0,
|
||||
statusText: resource.statusText || "",
|
||||
httpVersion: WI.Resource.displayNameForProtocol(resource.protocol) || "",
|
||||
cookies: HARBuilder.cookies(resource.responseCookies, resource.requestSentDate),
|
||||
headers: HARBuilder.headers(resource.responseHeaders),
|
||||
content: HARBuilder.content(resource, content),
|
||||
redirectURL: resource.responseHeaders.valueForCaseInsensitiveKey("Location") || "",
|
||||
headersSize: !isNaN(resource.responseHeadersTransferSize) ? resource.responseHeadersTransferSize : -1,
|
||||
bodySize: !isNaN(resource.responseBodyTransferSize) ? resource.responseBodyTransferSize : -1,
|
||||
};
|
||||
|
||||
// Chrome Custom Field `_transferSize`.
|
||||
if (!isNaN(resource.networkTotalTransferSize))
|
||||
result._transferSize = resource.networkTotalTransferSize;
|
||||
|
||||
// Chrome Custom Field `_error`.
|
||||
if (resource.failureReasonText)
|
||||
result._error = resource.failureReasonText;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static cookies(cookies, requestSentDate)
|
||||
{
|
||||
let result = [];
|
||||
|
||||
for (let cookie of cookies) {
|
||||
let json = {
|
||||
name: cookie.name,
|
||||
value: cookie.value,
|
||||
};
|
||||
|
||||
if (cookie.type === WI.Cookie.Type.Response) {
|
||||
if (cookie.path)
|
||||
json.path = cookie.path;
|
||||
if (cookie.domain)
|
||||
json.domain = cookie.domain;
|
||||
json.expires = HARBuilder.date(cookie.expirationDate(requestSentDate));
|
||||
json.httpOnly = cookie.httpOnly;
|
||||
json.secure = cookie.secure;
|
||||
if (cookie.sameSite !== WI.Cookie.SameSiteType.None)
|
||||
json.sameSite = cookie.sameSite;
|
||||
}
|
||||
|
||||
result.push(json);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static headers(headers)
|
||||
{
|
||||
let result = [];
|
||||
|
||||
for (let key in headers)
|
||||
result.push({name: key, value: headers[key]});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static content(resource, content)
|
||||
{
|
||||
let encodedSize = !isNaN(resource.networkEncodedSize) ? resource.networkEncodedSize : resource.estimatedNetworkEncodedSize;
|
||||
let decodedSize = !isNaN(resource.networkDecodedSize) ? resource.networkDecodedSize : resource.size;
|
||||
|
||||
if (isNaN(decodedSize))
|
||||
decodedSize = 0;
|
||||
if (isNaN(encodedSize))
|
||||
encodedSize = 0;
|
||||
|
||||
let result = {
|
||||
size: decodedSize,
|
||||
compression: decodedSize - encodedSize,
|
||||
mimeType: resource.mimeType || "x-unknown",
|
||||
};
|
||||
|
||||
if (content) {
|
||||
if (content.rawContent)
|
||||
result.text = content.rawContent;
|
||||
if (content.rawBase64Encoded)
|
||||
result.encoding = "base64";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static postData(resource)
|
||||
{
|
||||
return {
|
||||
mimeType: resource.requestDataContentType || "",
|
||||
text: resource.requestData,
|
||||
params: resource.requestFormParameters || [],
|
||||
};
|
||||
}
|
||||
|
||||
static cache(resource)
|
||||
{
|
||||
// FIXME: <https://webkit.org/b/178682> Web Inspector: Include <cache> details in HAR Export
|
||||
// http://www.softwareishard.com/blog/har-12-spec/#cache
|
||||
return {};
|
||||
}
|
||||
|
||||
static timings(resource)
|
||||
{
|
||||
// FIXME: <https://webkit.org/b/195694> Web Inspector: HAR Extension for Redirect Timing Info
|
||||
// Chrome has Custom Fields `_blocked_queueing` and `_blocked_proxy`.
|
||||
|
||||
let result = {
|
||||
blocked: -1,
|
||||
dns: -1,
|
||||
connect: -1,
|
||||
ssl: -1,
|
||||
send: 0,
|
||||
wait: 0,
|
||||
receive: 0,
|
||||
};
|
||||
|
||||
if (resource.timingData.startTime && resource.timingData.responseEnd) {
|
||||
let {startTime, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd} = resource.timingData;
|
||||
result.blocked = ((domainLookupStart || connectStart || requestStart) - startTime) * 1000;
|
||||
if (domainLookupStart)
|
||||
result.dns = ((domainLookupEnd || connectStart || requestStart) - domainLookupStart) * 1000;
|
||||
if (connectStart)
|
||||
result.connect = ((connectEnd || requestStart) - connectStart) * 1000;
|
||||
if (secureConnectionStart)
|
||||
result.ssl = ((connectEnd || requestStart) - secureConnectionStart) * 1000;
|
||||
|
||||
// If all the time before requestStart was included in blocked, then make send time zero
|
||||
// as send time is essentially just blocked time after dns / connection time, and we
|
||||
// do not want to double count it.
|
||||
result.send = (domainLookupEnd || connectEnd) ? (requestStart - (connectEnd || domainLookupEnd)) * 1000 : 0;
|
||||
|
||||
result.wait = (responseStart - requestStart) * 1000;
|
||||
result.receive = (responseEnd - responseStart) * 1000;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
static ipAddress(remoteAddress)
|
||||
{
|
||||
// IP Address, without port.
|
||||
if (!remoteAddress)
|
||||
return "";
|
||||
|
||||
// NOTE: Resource.remoteAddress always includes the port at the end.
|
||||
// So this always strips the last part.
|
||||
return remoteAddress.replace(/:\d+$/, "");
|
||||
}
|
||||
|
||||
static port(remoteAddress)
|
||||
{
|
||||
// IP Address, without port.
|
||||
if (!remoteAddress)
|
||||
return undefined;
|
||||
|
||||
// NOTE: Resource.remoteAddress always includes the port at the end.
|
||||
// So this always matches the last part.
|
||||
let index = remoteAddress.lastIndexOf(":");
|
||||
if (!index)
|
||||
return undefined;
|
||||
|
||||
let portString = remoteAddress.substr(index + 1);
|
||||
let port = parseInt(portString);
|
||||
if (isNaN(port))
|
||||
return undefined;
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
static date(date)
|
||||
{
|
||||
// ISO 8601
|
||||
if (!date)
|
||||
return "";
|
||||
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
static fetchType(responseSource)
|
||||
{
|
||||
switch (responseSource) {
|
||||
case WI.Resource.ResponseSource.Network:
|
||||
return "Network Load";
|
||||
case WI.Resource.ResponseSource.MemoryCache:
|
||||
return "Memory Cache";
|
||||
case WI.Resource.ResponseSource.DiskCache:
|
||||
return "Disk Cache";
|
||||
case WI.Resource.ResponseSource.ServiceWorker:
|
||||
return "Service Worker";
|
||||
case WI.Resource.ResponseSource.InspectorOverride:
|
||||
return "Inspector Override";
|
||||
}
|
||||
|
||||
console.assert();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static priority(priority)
|
||||
{
|
||||
switch (priority) {
|
||||
case WI.Resource.NetworkPriority.Low:
|
||||
return "low";
|
||||
case WI.Resource.NetworkPriority.Medium:
|
||||
return "medium";
|
||||
case WI.Resource.NetworkPriority.High:
|
||||
return "high";
|
||||
}
|
||||
|
||||
console.assert();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Consuming.
|
||||
|
||||
static dateFromHARDate(isoString)
|
||||
{
|
||||
return Date.parse(isoString);
|
||||
}
|
||||
|
||||
static protocolFromHARProtocol(protocol)
|
||||
{
|
||||
switch (protocol) {
|
||||
case "HTTP/2":
|
||||
return "h2";
|
||||
case "HTTP/1.0":
|
||||
return "http/1.0";
|
||||
case "HTTP/1.1":
|
||||
return "http/1.1";
|
||||
case "SPDY/2":
|
||||
return "spdy/2";
|
||||
case "SPDY/3":
|
||||
return "spdy/3";
|
||||
case "SPDY/3.1":
|
||||
return "spdy/3.1";
|
||||
}
|
||||
|
||||
if (protocol)
|
||||
console.warn("Unknown HAR protocol value", protocol);
|
||||
return null;
|
||||
}
|
||||
|
||||
static responseSourceFromHARFetchType(fetchType)
|
||||
{
|
||||
switch (fetchType) {
|
||||
case "Network Load":
|
||||
return WI.Resource.ResponseSource.Network;
|
||||
case "Memory Cache":
|
||||
return WI.Resource.ResponseSource.MemoryCache;
|
||||
case "Disk Cache":
|
||||
return WI.Resource.ResponseSource.DiskCache;
|
||||
case "Service Worker":
|
||||
return WI.Resource.ResponseSource.ServiceWorker;
|
||||
case "Inspector Override":
|
||||
return WI.Resource.ResponseSource.InspectorOverride;
|
||||
}
|
||||
|
||||
if (fetchType)
|
||||
console.warn("Unknown HAR _fetchType value", fetchType);
|
||||
return WI.Resource.ResponseSource.Other;
|
||||
}
|
||||
|
||||
static networkPriorityFromHARPriority(priority)
|
||||
{
|
||||
switch (priority) {
|
||||
case "low":
|
||||
return WI.Resource.NetworkPriority.Low;
|
||||
case "medium":
|
||||
return WI.Resource.NetworkPriority.Medium;
|
||||
case "high":
|
||||
return WI.Resource.NetworkPriority.High;
|
||||
}
|
||||
|
||||
if (priority)
|
||||
console.warn("Unknown HAR priority value", priority);
|
||||
return WI.Resource.NetworkPriority.Unknown;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
// FIXME: HeapManager lacks advanced multi-target support. (Instruments/Profilers per-target)
|
||||
|
||||
WI.HeapManager = class HeapManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["Heap"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "Heap");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("Heap"))
|
||||
target.HeapAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
enable()
|
||||
{
|
||||
if (this._enabled)
|
||||
return;
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("Heap"))
|
||||
target.HeapAgent.disable();
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
snapshot(callback)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
|
||||
if (error)
|
||||
console.error(error);
|
||||
callback(error, timestamp, snapshotStringData);
|
||||
});
|
||||
}
|
||||
|
||||
getPreview(node, callback)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
console.assert(node instanceof WI.HeapSnapshotNodeProxy);
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.HeapAgent.getPreview(node.id, (error, string, functionDetails, preview) => {
|
||||
if (error)
|
||||
console.error(error);
|
||||
callback(error, string, functionDetails, preview);
|
||||
});
|
||||
}
|
||||
|
||||
getRemoteObject(node, objectGroup, callback)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
console.assert(node instanceof WI.HeapSnapshotNodeProxy);
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.HeapAgent.getRemoteObject(node.id, objectGroup, (error, result) => {
|
||||
if (error)
|
||||
console.error(error);
|
||||
callback(error, result);
|
||||
});
|
||||
}
|
||||
|
||||
// HeapObserver
|
||||
|
||||
garbageCollected(target, payload)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
// FIXME: <https://webkit.org/b/167323> Web Inspector: Enable Memory profiling in Workers
|
||||
if (target !== WI.mainTarget)
|
||||
return;
|
||||
|
||||
let collection = WI.GarbageCollection.fromPayload(payload);
|
||||
this.dispatchEventToListeners(WI.HeapManager.Event.GarbageCollected, {collection});
|
||||
}
|
||||
};
|
||||
|
||||
WI.HeapManager.Event = {
|
||||
GarbageCollected: "heap-manager-garbage-collected"
|
||||
};
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2013 Samsung Electronics. 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: IndexedDBManager lacks advanced multi-target support. (IndexedDatabase per-target)
|
||||
|
||||
WI.IndexedDBManager = class IndexedDBManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
this._requestedSecurityOrigins = new Set;
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["IndexedDB"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "IndexedDB");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("IndexedDB"))
|
||||
target.IndexedDBAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get indexedDatabases() { return this._indexedDatabases; }
|
||||
|
||||
enable()
|
||||
{
|
||||
console.assert(!this._enabled);
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._reset();
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.addEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("IndexedDB"))
|
||||
target.IndexedDBAgent.disable();
|
||||
}
|
||||
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
|
||||
WI.Frame.removeEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
requestIndexedDatabaseData(objectStore, objectStoreIndex, startEntryIndex, maximumEntryCount, callback)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
console.assert(InspectorBackend.hasDomain("IndexedDB"));
|
||||
console.assert(objectStore);
|
||||
console.assert(callback);
|
||||
|
||||
function processData(error, entryPayloads, moreAvailable)
|
||||
{
|
||||
if (error) {
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = [];
|
||||
|
||||
for (var entryPayload of entryPayloads) {
|
||||
var entry = {};
|
||||
entry.primaryKey = WI.RemoteObject.fromPayload(entryPayload.primaryKey);
|
||||
entry.key = WI.RemoteObject.fromPayload(entryPayload.key);
|
||||
entry.value = WI.RemoteObject.fromPayload(entryPayload.value);
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
callback(entries, moreAvailable);
|
||||
}
|
||||
|
||||
var requestArguments = {
|
||||
securityOrigin: objectStore.parentDatabase.securityOrigin,
|
||||
databaseName: objectStore.parentDatabase.name,
|
||||
objectStoreName: objectStore.name,
|
||||
indexName: objectStoreIndex && objectStoreIndex.name || "",
|
||||
skipCount: startEntryIndex || 0,
|
||||
pageSize: maximumEntryCount || 100
|
||||
};
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.IndexedDBAgent.requestData.invoke(requestArguments, processData);
|
||||
}
|
||||
|
||||
clearObjectStore(objectStore)
|
||||
{
|
||||
console.assert(this._enabled);
|
||||
|
||||
let securityOrigin = objectStore.parentDatabase.securityOrigin;
|
||||
let databaseName = objectStore.parentDatabase.name;
|
||||
let objectStoreName = objectStore.name;
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.IndexedDBAgent.clearObjectStore(securityOrigin, databaseName, objectStoreName);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_reset()
|
||||
{
|
||||
this._indexedDatabases = [];
|
||||
this._requestedSecurityOrigins.clear();
|
||||
this.dispatchEventToListeners(WI.IndexedDBManager.Event.Cleared);
|
||||
|
||||
let mainFrame = WI.networkManager.mainFrame;
|
||||
if (mainFrame)
|
||||
this._addIndexedDBDatabasesIfNeeded(mainFrame);
|
||||
}
|
||||
|
||||
_addIndexedDBDatabasesIfNeeded(frame)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
if (!target.hasDomain("IndexedDB"))
|
||||
return;
|
||||
|
||||
var securityOrigin = frame.securityOrigin;
|
||||
|
||||
// Don't show storage if we don't have a security origin (about:blank).
|
||||
if (!securityOrigin || securityOrigin === "://")
|
||||
return;
|
||||
|
||||
if (this._requestedSecurityOrigins.has(securityOrigin))
|
||||
return;
|
||||
|
||||
this._requestedSecurityOrigins.add(securityOrigin);
|
||||
|
||||
function processDatabaseNames(error, names)
|
||||
{
|
||||
if (error || !names)
|
||||
return;
|
||||
|
||||
for (var name of names)
|
||||
target.IndexedDBAgent.requestDatabase(securityOrigin, name, processDatabase.bind(this));
|
||||
}
|
||||
|
||||
function processDatabase(error, databasePayload)
|
||||
{
|
||||
if (error || !databasePayload)
|
||||
return;
|
||||
|
||||
var objectStores = databasePayload.objectStores.map(processObjectStore);
|
||||
var indexedDatabase = new WI.IndexedDatabase(databasePayload.name, securityOrigin, databasePayload.version, objectStores);
|
||||
|
||||
this._indexedDatabases.push(indexedDatabase);
|
||||
this.dispatchEventToListeners(WI.IndexedDBManager.Event.IndexedDatabaseWasAdded, {indexedDatabase});
|
||||
}
|
||||
|
||||
function processKeyPath(keyPathPayload)
|
||||
{
|
||||
switch (keyPathPayload.type) {
|
||||
case InspectorBackend.Enum.IndexedDB.KeyPathType.Null:
|
||||
return null;
|
||||
case InspectorBackend.Enum.IndexedDB.KeyPathType.String:
|
||||
return keyPathPayload.string;
|
||||
case InspectorBackend.Enum.IndexedDB.KeyPathType.Array:
|
||||
return keyPathPayload.array;
|
||||
default:
|
||||
console.error("Unknown KeyPath type:", keyPathPayload.type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function processObjectStore(objectStorePayload)
|
||||
{
|
||||
var keyPath = processKeyPath(objectStorePayload.keyPath);
|
||||
var indexes = objectStorePayload.indexes.map(processObjectStoreIndex);
|
||||
return new WI.IndexedDatabaseObjectStore(objectStorePayload.name, keyPath, objectStorePayload.autoIncrement, indexes);
|
||||
}
|
||||
|
||||
function processObjectStoreIndex(objectStoreIndexPayload)
|
||||
{
|
||||
var keyPath = processKeyPath(objectStoreIndexPayload.keyPath);
|
||||
return new WI.IndexedDatabaseObjectStoreIndex(objectStoreIndexPayload.name, keyPath, objectStoreIndexPayload.unique, objectStoreIndexPayload.multiEntry);
|
||||
}
|
||||
|
||||
target.IndexedDBAgent.requestDatabaseNames(securityOrigin, processDatabaseNames.bind(this));
|
||||
}
|
||||
|
||||
_mainResourceDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
if (event.target.isMainFrame())
|
||||
this._reset();
|
||||
}
|
||||
|
||||
_securityOriginDidChange(event)
|
||||
{
|
||||
console.assert(event.target instanceof WI.Frame);
|
||||
|
||||
this._addIndexedDBDatabasesIfNeeded(event.target);
|
||||
}
|
||||
};
|
||||
|
||||
WI.IndexedDBManager.Event = {
|
||||
IndexedDatabaseWasAdded: "indexed-db-manager-indexed-database-was-added",
|
||||
Cleared: "indexed-db-manager-cleared",
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.InspectedTargetTypesDiagnosticEventRecorder = class InspectedTargetTypesDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
|
||||
{
|
||||
constructor(controller)
|
||||
{
|
||||
super("InspectedTargetTypes", controller);
|
||||
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static get initialDelayBeforeSamplingInterval()
|
||||
{
|
||||
return 5 * 1000; // In milliseconds.
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
setup()
|
||||
{
|
||||
// If it's been less than 5 seconds since the frontend loaded, wait a bit.
|
||||
if (performance.now() - WI.frontendCompletedLoadTimestamp < InspectedTargetTypesDiagnosticEventRecorder.initialDelayBeforeSamplingInterval)
|
||||
this._startInitialDelayBeforeSamplingTimer();
|
||||
else
|
||||
this._sampleInspectedTarget();
|
||||
|
||||
}
|
||||
|
||||
teardown()
|
||||
{
|
||||
this._stopInitialDelayBeforeSamplingTimer();
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_startInitialDelayBeforeSamplingTimer()
|
||||
{
|
||||
if (this._initialDelayBeforeSamplingTimerIdentifier) {
|
||||
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
|
||||
// All intervals are in milliseconds.
|
||||
let maximumInitialDelay = InspectedTargetTypesDiagnosticEventRecorder.initialDelayBeforeSamplingInterval;
|
||||
let elapsedTime = performance.now() - WI.frontendCompletedLoadTimestamp;
|
||||
let remainingTime = maximumInitialDelay - elapsedTime;
|
||||
let initialDelay = Number.constrain(remainingTime, 0, maximumInitialDelay);
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = setTimeout(this._sampleInspectedTarget.bind(this), initialDelay);
|
||||
}
|
||||
|
||||
_stopInitialDelayBeforeSamplingTimer()
|
||||
{
|
||||
if (this._initialDelayBeforeSamplingTimerIdentifier) {
|
||||
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
_sampleInspectedTarget()
|
||||
{
|
||||
this._stopInitialDelayBeforeSamplingTimer();
|
||||
|
||||
this.logDiagnosticEvent(this.name, {
|
||||
debuggableType: this._determineDebuggableType(),
|
||||
targetPlatformName: this._determineTargetPlatformName(),
|
||||
targetBuildVersion: this._determineTargetBuildVersion(),
|
||||
targetProductVersion: this._determineTargetProductVersion(),
|
||||
targetIsSimulator: this._determineTargetIsSimulator(),
|
||||
});
|
||||
}
|
||||
|
||||
_determineDebuggableType()
|
||||
{
|
||||
this._ensureCachedDebuggableInfo();
|
||||
|
||||
return this._cachedDebuggableInfo.debuggableType;
|
||||
}
|
||||
|
||||
_determineTargetPlatformName()
|
||||
{
|
||||
this._ensureCachedDebuggableInfo();
|
||||
|
||||
return this._cachedDebuggableInfo.targetPlatformName;
|
||||
}
|
||||
|
||||
_determineTargetBuildVersion()
|
||||
{
|
||||
this._ensureCachedDebuggableInfo();
|
||||
|
||||
return this._cachedDebuggableInfo.targetBuildVersion;
|
||||
}
|
||||
|
||||
_determineTargetProductVersion()
|
||||
{
|
||||
this._ensureCachedDebuggableInfo();
|
||||
|
||||
return this._cachedDebuggableInfo.targetProductVersion;
|
||||
}
|
||||
|
||||
_determineTargetIsSimulator()
|
||||
{
|
||||
this._ensureCachedDebuggableInfo();
|
||||
|
||||
return !!this._cachedDebuggableInfo.targetIsSimulator;
|
||||
}
|
||||
|
||||
_ensureCachedDebuggableInfo()
|
||||
{
|
||||
if (this._cachedDebuggableInfo)
|
||||
return;
|
||||
|
||||
let debuggableInfo = InspectorFrontendHost.debuggableInfo;
|
||||
this._cachedDebuggableInfo = {
|
||||
debuggableType: WI.DebuggableType.fromString(debuggableInfo.debuggableType) || WI.DebuggableType.JavaScript,
|
||||
targetPlatformName: debuggableInfo.targetPlatformName || "Unknown",
|
||||
targetBuildVersion: debuggableInfo.targetBuildVersion || "Unknown",
|
||||
targetProductVersion: debuggableInfo.targetProductVersion || "Unknown",
|
||||
targetIsSimulator: debuggableInfo.targetIsSimulator,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.JavaScriptLogViewController = class JavaScriptLogViewController extends WI.Object
|
||||
{
|
||||
constructor(element, scrollElement, textPrompt, delegate, historySettingIdentifier)
|
||||
{
|
||||
super();
|
||||
|
||||
console.assert(textPrompt instanceof WI.ConsolePrompt);
|
||||
console.assert(historySettingIdentifier);
|
||||
|
||||
this._element = element;
|
||||
this._scrollElement = scrollElement;
|
||||
|
||||
this._promptHistorySetting = new WI.Setting(historySettingIdentifier, null);
|
||||
|
||||
this._prompt = textPrompt;
|
||||
this._prompt.delegate = this;
|
||||
this._prompt.history = this._promptHistorySetting.value;
|
||||
|
||||
this.delegate = delegate;
|
||||
|
||||
this._cleared = true;
|
||||
this._previousMessageView = null;
|
||||
this._lastCommitted = {text: "", special: false};
|
||||
this._repeatCountWasInterrupted = false;
|
||||
|
||||
this._sessions = [];
|
||||
this._currentSessionOrGroup = null;
|
||||
|
||||
this.messagesAlternateClearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._element);
|
||||
|
||||
this._messagesFindNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._element);
|
||||
this._messagesFindPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._element);
|
||||
|
||||
this._promptAlternateClearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._prompt.element);
|
||||
this._promptFindNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._prompt.element);
|
||||
this._promptFindPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._prompt.element);
|
||||
|
||||
WI.settings.showConsoleMessageTimestamps.addEventListener(WI.Setting.Event.Changed, this._handleShowConsoleMessageTimestampsSettingChanged, this);
|
||||
|
||||
this._pendingMessagesForSessionOrGroup = new Map;
|
||||
this._scheduledRenderIdentifier = 0;
|
||||
|
||||
this._consoleMessageViews = [];
|
||||
this._showTimestamps = WI.settings.showConsoleMessageTimestamps.value;
|
||||
|
||||
this.startNewSession();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
clear()
|
||||
{
|
||||
this._cleared = true;
|
||||
|
||||
const clearPreviousSessions = true;
|
||||
this.startNewSession(clearPreviousSessions, {newSessionReason: WI.ConsoleSession.NewSessionReason.ConsoleCleared});
|
||||
}
|
||||
|
||||
startNewSession(clearPreviousSessions = false, data = {})
|
||||
{
|
||||
if (clearPreviousSessions) {
|
||||
this._pendingMessagesForSessionOrGroup.clear();
|
||||
|
||||
if (this._sessions.length) {
|
||||
for (let session of this._sessions)
|
||||
session.element.remove();
|
||||
|
||||
this._sessions = [];
|
||||
this._currentSessionOrGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
// First session shows the time when the console was opened.
|
||||
if (!this._sessions.length)
|
||||
data.timestamp = Date.now();
|
||||
|
||||
let lastSession = this._sessions.lastValue;
|
||||
|
||||
// Remove empty session.
|
||||
if (lastSession && !lastSession.hasMessages() && !this._pendingMessagesForSessionOrGroup.has(lastSession)) {
|
||||
this._sessions.pop();
|
||||
lastSession.element.remove();
|
||||
}
|
||||
|
||||
let consoleSession = new WI.ConsoleSession(data);
|
||||
|
||||
this._previousMessageView = null;
|
||||
this._lastCommitted = {text: "", special: false};
|
||||
this._repeatCountWasInterrupted = false;
|
||||
|
||||
this._sessions.push(consoleSession);
|
||||
this._currentSessionOrGroup = consoleSession;
|
||||
|
||||
this._element.appendChild(consoleSession.element);
|
||||
|
||||
// Make sure the new session is visible.
|
||||
consoleSession.element.scrollIntoView();
|
||||
}
|
||||
|
||||
appendImmediateExecutionWithResult(text, result, {addSpecialUserLogClass, shouldRevealConsole, handleClick} = {})
|
||||
{
|
||||
console.assert(result instanceof WI.RemoteObject);
|
||||
|
||||
if (this._lastCommitted.text !== text || this._lastCommitted.special !== addSpecialUserLogClass) {
|
||||
let classNames = [];
|
||||
if (addSpecialUserLogClass)
|
||||
classNames.push("special-user-log");
|
||||
|
||||
let commandMessageView = new WI.ConsoleCommandView(text, {classNames, handleClick});
|
||||
this._appendConsoleMessageView(commandMessageView, true);
|
||||
this._lastCommitted = {text, special: addSpecialUserLogClass};
|
||||
}
|
||||
|
||||
function saveResultCallback(savedResultIndex)
|
||||
{
|
||||
let commandResultMessage = new WI.ConsoleCommandResultMessage(result.target, result, false, savedResultIndex, shouldRevealConsole);
|
||||
let commandResultMessageView = new WI.ConsoleMessageView(commandResultMessage);
|
||||
this._appendConsoleMessageView(commandResultMessageView, true);
|
||||
}
|
||||
|
||||
WI.runtimeManager.saveResult(result, saveResultCallback.bind(this));
|
||||
}
|
||||
|
||||
appendConsoleMessage(consoleMessage)
|
||||
{
|
||||
var consoleMessageView = new WI.ConsoleMessageView(consoleMessage);
|
||||
this._appendConsoleMessageView(consoleMessageView);
|
||||
return consoleMessageView;
|
||||
}
|
||||
|
||||
updatePreviousMessageRepeatCount(count, timestamp)
|
||||
{
|
||||
console.assert(this._previousMessageView);
|
||||
if (!this._previousMessageView)
|
||||
return false;
|
||||
|
||||
var previousIgnoredCount = this._previousMessageView[WI.JavaScriptLogViewController.IgnoredRepeatCount] || 0;
|
||||
var previousVisibleCount = this._previousMessageView.repeatCount;
|
||||
this._previousMessageView.timestamp = timestamp;
|
||||
|
||||
if (!this._repeatCountWasInterrupted) {
|
||||
this._previousMessageView.repeatCount = count - previousIgnoredCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
var consoleMessage = this._previousMessageView.message;
|
||||
var duplicatedConsoleMessageView = new WI.ConsoleMessageView(consoleMessage);
|
||||
duplicatedConsoleMessageView[WI.JavaScriptLogViewController.IgnoredRepeatCount] = previousIgnoredCount + previousVisibleCount;
|
||||
duplicatedConsoleMessageView.repeatCount = 1;
|
||||
this._appendConsoleMessageView(duplicatedConsoleMessageView);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
isScrolledToBottom()
|
||||
{
|
||||
// Lie about being scrolled to the bottom if we have a pending request to scroll to the bottom soon.
|
||||
return this._scrollToBottomTimeout || this._scrollElement.isScrolledToBottom();
|
||||
}
|
||||
|
||||
scrollToBottom()
|
||||
{
|
||||
if (this._scrollToBottomTimeout)
|
||||
return;
|
||||
|
||||
function delayedWork()
|
||||
{
|
||||
this._scrollToBottomTimeout = null;
|
||||
this._scrollElement.scrollTop = this._scrollElement.scrollHeight;
|
||||
}
|
||||
|
||||
// Don't scroll immediately so we are not causing excessive layouts when there
|
||||
// are many messages being added at once.
|
||||
this._scrollToBottomTimeout = setTimeout(delayedWork.bind(this), 0);
|
||||
}
|
||||
|
||||
requestClearMessages()
|
||||
{
|
||||
WI.consoleManager.requestClearMessages();
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
consolePromptHistoryDidChange(prompt)
|
||||
{
|
||||
this._promptHistorySetting.value = this._prompt.history;
|
||||
}
|
||||
|
||||
consolePromptShouldCommitText(prompt, text, cursorIsAtLastPosition, handler)
|
||||
{
|
||||
// Always commit the text if we are not at the last position.
|
||||
if (!cursorIsAtLastPosition) {
|
||||
handler(true);
|
||||
return;
|
||||
}
|
||||
|
||||
function parseFinished(error, result, message, range)
|
||||
{
|
||||
handler(result !== InspectorBackend.Enum.Runtime.SyntaxErrorType.Recoverable);
|
||||
}
|
||||
|
||||
WI.runtimeManager.activeExecutionContext.target.RuntimeAgent.parse(text, parseFinished.bind(this));
|
||||
}
|
||||
|
||||
consolePromptTextCommitted(prompt, text)
|
||||
{
|
||||
console.assert(text);
|
||||
|
||||
if (this._lastCommitted.text !== text || this._lastCommitted.special) {
|
||||
let commandMessageView = new WI.ConsoleCommandView(text);
|
||||
this._appendConsoleMessageView(commandMessageView, true);
|
||||
this._lastCommitted = {text, special: false};
|
||||
}
|
||||
|
||||
function printResult(result, wasThrown, savedResultIndex)
|
||||
{
|
||||
if (!result || this._cleared)
|
||||
return;
|
||||
|
||||
let shouldRevealConsole = true;
|
||||
let commandResultMessage = new WI.ConsoleCommandResultMessage(result.target, result, wasThrown, savedResultIndex, shouldRevealConsole);
|
||||
let commandResultMessageView = new WI.ConsoleMessageView(commandResultMessage);
|
||||
this._appendConsoleMessageView(commandResultMessageView, true);
|
||||
}
|
||||
|
||||
let options = {
|
||||
objectGroup: WI.RuntimeManager.ConsoleObjectGroup,
|
||||
includeCommandLineAPI: true,
|
||||
doNotPauseOnExceptionsAndMuteConsole: false,
|
||||
returnByValue: false,
|
||||
generatePreview: true,
|
||||
saveResult: true,
|
||||
emulateUserGesture: WI.settings.emulateInUserGesture.value,
|
||||
sourceURLAppender: appendWebInspectorConsoleEvaluationSourceURL,
|
||||
};
|
||||
|
||||
WI.runtimeManager.evaluateInInspectedWindow(text, options, printResult.bind(this));
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_handleFindNextShortcut()
|
||||
{
|
||||
this.delegate.highlightNextSearchMatch();
|
||||
}
|
||||
|
||||
_handleFindPreviousShortcut()
|
||||
{
|
||||
this.delegate.highlightPreviousSearchMatch();
|
||||
}
|
||||
|
||||
_appendConsoleMessageView(messageView, repeatCountWasInterrupted)
|
||||
{
|
||||
let pendingMessagesForSession = this._pendingMessagesForSessionOrGroup.get(this._currentSessionOrGroup);
|
||||
if (!pendingMessagesForSession) {
|
||||
pendingMessagesForSession = [];
|
||||
this._pendingMessagesForSessionOrGroup.set(this._currentSessionOrGroup, pendingMessagesForSession);
|
||||
}
|
||||
pendingMessagesForSession.push(messageView);
|
||||
this._consoleMessageViews.push(messageView);
|
||||
|
||||
this._cleared = false;
|
||||
this._repeatCountWasInterrupted = repeatCountWasInterrupted || false;
|
||||
|
||||
if (!repeatCountWasInterrupted)
|
||||
this._previousMessageView = messageView;
|
||||
|
||||
if (messageView.message && messageView.message.source !== WI.ConsoleMessage.MessageSource.JS)
|
||||
this._lastCommitted = {test: "", special: false};
|
||||
|
||||
if (WI.consoleContentView.isAttached)
|
||||
this.renderPendingMessagesSoon();
|
||||
|
||||
if (!WI.isShowingConsoleTab() && messageView.message && messageView.message.shouldRevealConsole)
|
||||
WI.showSplitConsole();
|
||||
}
|
||||
|
||||
renderPendingMessages()
|
||||
{
|
||||
if (this._scheduledRenderIdentifier) {
|
||||
cancelAnimationFrame(this._scheduledRenderIdentifier);
|
||||
this._scheduledRenderIdentifier = 0;
|
||||
}
|
||||
|
||||
if (!this._pendingMessagesForSessionOrGroup.size)
|
||||
return;
|
||||
|
||||
let wasScrolledToBottom = this.isScrolledToBottom();
|
||||
let savedCurrentConsoleGroup = this._currentSessionOrGroup;
|
||||
let lastMessageView = null;
|
||||
|
||||
const maxMessagesPerFrame = 100;
|
||||
let renderedMessages = 0;
|
||||
for (let [session, messages] of this._pendingMessagesForSessionOrGroup) {
|
||||
this._currentSessionOrGroup = session;
|
||||
|
||||
let messagesToRender = messages.splice(0, maxMessagesPerFrame - renderedMessages);
|
||||
for (let message of messagesToRender) {
|
||||
message.render();
|
||||
this._didRenderConsoleMessageView(message);
|
||||
}
|
||||
|
||||
lastMessageView = messagesToRender.lastValue;
|
||||
|
||||
if (!messages.length)
|
||||
this._pendingMessagesForSessionOrGroup.delete(session);
|
||||
|
||||
renderedMessages += messagesToRender.length;
|
||||
if (renderedMessages >= maxMessagesPerFrame)
|
||||
break;
|
||||
}
|
||||
|
||||
this._currentSessionOrGroup = savedCurrentConsoleGroup;
|
||||
|
||||
this._currentSessionOrGroup.element.classList.toggle("timestamps-visible", this._showTimestamps);
|
||||
|
||||
if (wasScrolledToBottom || lastMessageView instanceof WI.ConsoleCommandView || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Result || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Image)
|
||||
this.scrollToBottom();
|
||||
|
||||
WI.quickConsole.needsLayout();
|
||||
|
||||
if (this._pendingMessagesForSessionOrGroup.size)
|
||||
this.renderPendingMessagesSoon();
|
||||
}
|
||||
|
||||
renderPendingMessagesSoon()
|
||||
{
|
||||
if (this._scheduledRenderIdentifier)
|
||||
return;
|
||||
|
||||
this._scheduledRenderIdentifier = requestAnimationFrame(() => this.renderPendingMessages());
|
||||
}
|
||||
|
||||
_didRenderConsoleMessageView(messageView)
|
||||
{
|
||||
var type = messageView instanceof WI.ConsoleCommandView ? null : messageView.message.type;
|
||||
if (type === WI.ConsoleMessage.MessageType.EndGroup) {
|
||||
var parentGroup = this._currentSessionOrGroup.parentGroup;
|
||||
if (parentGroup)
|
||||
this._currentSessionOrGroup = parentGroup;
|
||||
} else {
|
||||
if (type === WI.ConsoleMessage.MessageType.StartGroup || type === WI.ConsoleMessage.MessageType.StartGroupCollapsed) {
|
||||
var group = new WI.ConsoleGroup(this._currentSessionOrGroup);
|
||||
var groupElement = group.render(messageView);
|
||||
this._currentSessionOrGroup.append(groupElement);
|
||||
this._currentSessionOrGroup = group;
|
||||
} else
|
||||
this._currentSessionOrGroup.addMessageView(messageView);
|
||||
}
|
||||
|
||||
if (this.delegate && typeof this.delegate.didAppendConsoleMessageView === "function")
|
||||
this.delegate.didAppendConsoleMessageView(messageView);
|
||||
}
|
||||
|
||||
_handleShowConsoleMessageTimestampsSettingChanged()
|
||||
{
|
||||
this._showTimestamps = WI.settings.showConsoleMessageTimestamps.value;
|
||||
this._currentSessionOrGroup.element.classList.toggle("timestamps-visible", this._showTimestamps);
|
||||
if (this._showTimestamps) {
|
||||
for (let consoleMessageView of this._consoleMessageViews) {
|
||||
if (consoleMessageView instanceof WI.ConsoleMessageView)
|
||||
consoleMessageView.renderTimestamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WI.JavaScriptLogViewController.CachedPropertiesDuration = 30_000;
|
||||
WI.JavaScriptLogViewController.IgnoredRepeatCount = Symbol("ignored-repeat-count");
|
||||
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
Object.defineProperty(WI, "javaScriptRuntimeCompletionProvider",
|
||||
{
|
||||
get: function()
|
||||
{
|
||||
if (!WI.JavaScriptRuntimeCompletionProvider._instance)
|
||||
WI.JavaScriptRuntimeCompletionProvider._instance = new WI.JavaScriptRuntimeCompletionProvider;
|
||||
return WI.JavaScriptRuntimeCompletionProvider._instance;
|
||||
}
|
||||
});
|
||||
|
||||
WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvider extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
console.assert(!WI.JavaScriptRuntimeCompletionProvider._instance);
|
||||
|
||||
this._ongoingCompletionRequests = 0;
|
||||
|
||||
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._clearLastProperties, this);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static get _commandLineAPIKeys()
|
||||
{
|
||||
if (!JavaScriptRuntimeCompletionProvider.__cachedCommandLineAPIKeys) {
|
||||
JavaScriptRuntimeCompletionProvider.__cachedCommandLineAPIKeys = [
|
||||
"$_",
|
||||
"assert",
|
||||
"clear",
|
||||
"count",
|
||||
"countReset",
|
||||
"debug",
|
||||
"dir",
|
||||
"dirxml",
|
||||
"error",
|
||||
"group",
|
||||
"groupCollapsed",
|
||||
"groupEnd",
|
||||
"info",
|
||||
"inspect",
|
||||
"keys",
|
||||
"log",
|
||||
"profile",
|
||||
"profileEnd",
|
||||
"queryHolders",
|
||||
"queryInstances",
|
||||
"queryObjects",
|
||||
"record",
|
||||
"recordEnd",
|
||||
"screenshot",
|
||||
"table",
|
||||
"takeHeapSnapshot",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"timeLog",
|
||||
"timeStamp",
|
||||
"trace",
|
||||
"values",
|
||||
"warn",
|
||||
];
|
||||
}
|
||||
return JavaScriptRuntimeCompletionProvider.__cachedCommandLineAPIKeys;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
completionControllerCompletionsNeeded(completionController, defaultCompletions, base, prefix, suffix, forced)
|
||||
{
|
||||
// Don't allow non-forced empty prefix completions unless the base is that start of property access.
|
||||
if (!forced && !prefix && !/[.[]$/.test(base)) {
|
||||
completionController.updateCompletions(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the base ends with an open parentheses or open curly bracket then treat it like there is
|
||||
// no base so we get global object completions.
|
||||
if (/[({]$/.test(base))
|
||||
base = "";
|
||||
|
||||
var lastBaseIndex = base.length - 1;
|
||||
var dotNotation = base[lastBaseIndex] === ".";
|
||||
var bracketNotation = base[lastBaseIndex] === "[";
|
||||
|
||||
if (dotNotation || bracketNotation) {
|
||||
base = base.substring(0, lastBaseIndex);
|
||||
|
||||
// Don't suggest anything for an empty base that is using dot notation.
|
||||
// Bracket notation with an empty base will be treated as an array.
|
||||
if (!base && dotNotation) {
|
||||
completionController.updateCompletions(defaultCompletions);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow non-forced empty prefix completions if the user is entering a number, since it might be a float.
|
||||
// But allow number completions if the base already has a decimal, so "10.0." will suggest Number properties.
|
||||
if (!forced && !prefix && dotNotation && base.indexOf(".") === -1 && parseInt(base, 10) == base) {
|
||||
completionController.updateCompletions(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// An empty base with bracket notation is not property access, it is an array.
|
||||
// Clear the bracketNotation flag so completions are not quoted.
|
||||
if (!base && bracketNotation)
|
||||
bracketNotation = false;
|
||||
}
|
||||
|
||||
// Start an completion request. We must now decrement before calling completionController.updateCompletions.
|
||||
this._incrementOngoingCompletionRequests();
|
||||
|
||||
// If the base is the same as the last time, we can reuse the property names we have already gathered.
|
||||
// Doing this eliminates delay caused by the async nature of the code below and it only calls getters
|
||||
// and functions once instead of repetitively. Sure, there can be difference each time the base is evaluated,
|
||||
// but this optimization gives us more of a win. We clear the cache after 30 seconds or when stepping in the
|
||||
// debugger to make sure we don't use stale properties in most cases.
|
||||
if (this._lastMode === completionController.mode && this._lastBase === base && this._lastPropertyNames) {
|
||||
receivedPropertyNames.call(this, this._lastPropertyNames);
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastMode = completionController.mode;
|
||||
this._lastBase = base;
|
||||
this._lastPropertyNames = null;
|
||||
|
||||
var activeCallFrame = WI.debuggerManager.activeCallFrame;
|
||||
if (!base && activeCallFrame && !this._alwaysEvaluateInWindowContext)
|
||||
activeCallFrame.collectScopeChainVariableNames(receivedPropertyNames.bind(this));
|
||||
else {
|
||||
let options = {objectGroup: "completion", includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: false, generatePreview: false, saveResult: false};
|
||||
WI.runtimeManager.evaluateInInspectedWindow(base, options, evaluated.bind(this));
|
||||
}
|
||||
|
||||
function updateLastPropertyNames(propertyNames)
|
||||
{
|
||||
if (this._clearLastPropertiesTimeout)
|
||||
clearTimeout(this._clearLastPropertiesTimeout);
|
||||
this._clearLastPropertiesTimeout = setTimeout(this._clearLastProperties.bind(this), WI.JavaScriptLogViewController.CachedPropertiesDuration);
|
||||
|
||||
this._lastPropertyNames = propertyNames || [];
|
||||
}
|
||||
|
||||
function evaluated(result, wasThrown)
|
||||
{
|
||||
if (wasThrown || !result || result.type === "undefined" || (result.type === "object" && result.subtype === "null")) {
|
||||
this._decrementOngoingCompletionRequests();
|
||||
|
||||
updateLastPropertyNames.call(this, []);
|
||||
completionController.updateCompletions(defaultCompletions);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function inspectedPage_evalResult_getArrayCompletions(primitiveType)
|
||||
{
|
||||
var array = this;
|
||||
var arrayLength;
|
||||
|
||||
var resultSet = {};
|
||||
for (var o = array; o; o = o.__proto__) {
|
||||
try {
|
||||
if (o === array && o.length) {
|
||||
// If the array type has a length, don't include a list of all the indexes.
|
||||
// Include it at the end and the frontend can build the list.
|
||||
arrayLength = o.length;
|
||||
} else {
|
||||
var names = Object.getOwnPropertyNames(o);
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
resultSet[names[i]] = true;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (arrayLength)
|
||||
resultSet["length"] = arrayLength;
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
function inspectedPage_evalResult_getCompletions(primitiveType)
|
||||
{
|
||||
var object;
|
||||
if (primitiveType === "string")
|
||||
object = new String("");
|
||||
else if (primitiveType === "number")
|
||||
object = new Number(0);
|
||||
else if (primitiveType === "boolean")
|
||||
object = new Boolean(false);
|
||||
else if (primitiveType === "symbol")
|
||||
object = Symbol();
|
||||
else
|
||||
object = this;
|
||||
|
||||
var resultSet = {};
|
||||
for (var o = object; o; o = o.__proto__) {
|
||||
try {
|
||||
var names = Object.getOwnPropertyNames(o);
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
resultSet[names[i]] = true;
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
if (result.subtype === "array")
|
||||
result.callFunctionJSON(inspectedPage_evalResult_getArrayCompletions, undefined, receivedArrayPropertyNames.bind(this));
|
||||
else if (result.type === "object" || result.type === "function")
|
||||
result.callFunctionJSON(inspectedPage_evalResult_getCompletions, undefined, receivedObjectPropertyNames.bind(this));
|
||||
else if (result.type === "string" || result.type === "number" || result.type === "boolean" || result.type === "symbol") {
|
||||
let options = {objectGroup: "completion", includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: true, generatePreview: false, saveResult: false};
|
||||
WI.runtimeManager.evaluateInInspectedWindow("(" + inspectedPage_evalResult_getCompletions + ")(\"" + result.type + "\")", options, receivedPropertyNamesFromEvaluate.bind(this));
|
||||
} else
|
||||
console.error("Unknown result type: " + result.type);
|
||||
}
|
||||
|
||||
function receivedPropertyNamesFromEvaluate(object, wasThrown, result)
|
||||
{
|
||||
receivedPropertyNames.call(this, result && !wasThrown ? Object.keys(result.value) : null);
|
||||
}
|
||||
|
||||
function receivedObjectPropertyNames(propertyNames)
|
||||
{
|
||||
receivedPropertyNames.call(this, Object.keys(propertyNames));
|
||||
}
|
||||
|
||||
function receivedArrayPropertyNames(propertyNames)
|
||||
{
|
||||
if (propertyNames && typeof propertyNames.length === "number") {
|
||||
// FIXME <https://webkit.org/b/201909> Web Inspector: autocompletion of array indexes can't handle large arrays in a performant way
|
||||
var max = Math.min(propertyNames.length, 1000);
|
||||
for (var i = 0; i < max; ++i)
|
||||
propertyNames[i] = true;
|
||||
}
|
||||
|
||||
receivedObjectPropertyNames.call(this, propertyNames);
|
||||
}
|
||||
|
||||
function receivedPropertyNames(propertyNames)
|
||||
{
|
||||
console.assert(!propertyNames || Array.isArray(propertyNames));
|
||||
propertyNames = propertyNames || [];
|
||||
|
||||
updateLastPropertyNames.call(this, propertyNames);
|
||||
|
||||
this._decrementOngoingCompletionRequests();
|
||||
|
||||
if (!base) {
|
||||
propertyNames.pushAll(JavaScriptRuntimeCompletionProvider._commandLineAPIKeys);
|
||||
|
||||
let savedResultAlias = WI.settings.consoleSavedResultAlias.value;
|
||||
if (savedResultAlias)
|
||||
propertyNames.push(savedResultAlias + "_");
|
||||
|
||||
let target = WI.runtimeManager.activeExecutionContext.target;
|
||||
let targetData = WI.debuggerManager.paused ? WI.debuggerManager.dataForTarget(target) : {};
|
||||
|
||||
function shouldExposeEvent() {
|
||||
switch (completionController.mode) {
|
||||
case WI.CodeMirrorCompletionController.Mode.FullConsoleCommandLineAPI:
|
||||
case WI.CodeMirrorCompletionController.Mode.EventBreakpoint:
|
||||
return true;
|
||||
case WI.CodeMirrorCompletionController.Mode.PausedConsoleCommandLineAPI:
|
||||
return targetData.pauseReason === WI.DebuggerManager.PauseReason.Listener || targetData.pauseReason === WI.DebuggerManager.PauseReason.EventListener;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (shouldExposeEvent()) {
|
||||
propertyNames.push("$event");
|
||||
if (savedResultAlias)
|
||||
propertyNames.push(savedResultAlias + "event");
|
||||
}
|
||||
|
||||
function shouldExposeException() {
|
||||
switch (completionController.mode) {
|
||||
case WI.CodeMirrorCompletionController.Mode.FullConsoleCommandLineAPI:
|
||||
case WI.CodeMirrorCompletionController.Mode.ExceptionBreakpoint:
|
||||
return true;
|
||||
case WI.CodeMirrorCompletionController.Mode.PausedConsoleCommandLineAPI:
|
||||
return targetData.pauseReason === WI.DebuggerManager.PauseReason.Exception;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (shouldExposeException()) {
|
||||
propertyNames.push("$exception");
|
||||
if (savedResultAlias)
|
||||
propertyNames.push(savedResultAlias + "exception");
|
||||
}
|
||||
|
||||
switch (target.type) {
|
||||
case WI.TargetType.Page:
|
||||
propertyNames.push("$");
|
||||
propertyNames.push("$$");
|
||||
propertyNames.push("$0");
|
||||
if (savedResultAlias)
|
||||
propertyNames.push(savedResultAlias + "0");
|
||||
propertyNames.push("$x");
|
||||
// fallthrough
|
||||
case WI.TargetType.ServiceWorker:
|
||||
case WI.TargetType.Worker:
|
||||
propertyNames.push("copy");
|
||||
propertyNames.push("getEventListeners");
|
||||
propertyNames.push("monitorEvents");
|
||||
propertyNames.push("unmonitorEvents");
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: Due to caching, sometimes old $n values show up as completion results even though they are not available. We should clear that proactively.
|
||||
for (var i = 1; i <= WI.ConsoleCommandResultMessage.maximumSavedResultIndex; ++i) {
|
||||
propertyNames.push("$" + i);
|
||||
if (savedResultAlias)
|
||||
propertyNames.push(savedResultAlias + i);
|
||||
}
|
||||
}
|
||||
|
||||
var implicitSuffix = "";
|
||||
if (bracketNotation) {
|
||||
var quoteUsed = prefix[0] === "'" ? "'" : "\"";
|
||||
if (suffix !== "]" && suffix !== quoteUsed)
|
||||
implicitSuffix = "]";
|
||||
}
|
||||
|
||||
var completions = defaultCompletions;
|
||||
let knownCompletions = new Set(completions);
|
||||
|
||||
for (var i = 0; i < propertyNames.length; ++i) {
|
||||
var property = propertyNames[i];
|
||||
|
||||
if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
|
||||
continue;
|
||||
|
||||
if (bracketNotation) {
|
||||
if (parseInt(property) != property)
|
||||
property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + (suffix !== quoteUsed ? quoteUsed : "");
|
||||
}
|
||||
|
||||
if (!property.startsWith(prefix) || knownCompletions.has(property))
|
||||
continue;
|
||||
|
||||
completions.push(property);
|
||||
knownCompletions.add(property);
|
||||
}
|
||||
|
||||
function compare(a, b)
|
||||
{
|
||||
// Try to sort in numerical order first.
|
||||
let numericCompareResult = a - b;
|
||||
if (!isNaN(numericCompareResult))
|
||||
return numericCompareResult;
|
||||
|
||||
// Sort __defineGetter__, __lookupGetter__, and friends last.
|
||||
let aRareProperty = a.startsWith("__") && a.endsWith("__");
|
||||
let bRareProperty = b.startsWith("__") && b.endsWith("__");
|
||||
if (aRareProperty && !bRareProperty)
|
||||
return 1;
|
||||
if (!aRareProperty && bRareProperty)
|
||||
return -1;
|
||||
|
||||
// Not numbers, sort as strings.
|
||||
return a.extendedLocaleCompare(b);
|
||||
}
|
||||
|
||||
completions.sort(compare);
|
||||
|
||||
completionController.updateCompletions(completions, implicitSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_incrementOngoingCompletionRequests()
|
||||
{
|
||||
this._ongoingCompletionRequests++;
|
||||
|
||||
console.assert(this._ongoingCompletionRequests <= 50, "Ongoing requests probably should not get this high. We may be missing a balancing decrement.");
|
||||
}
|
||||
|
||||
_decrementOngoingCompletionRequests()
|
||||
{
|
||||
this._ongoingCompletionRequests--;
|
||||
|
||||
console.assert(this._ongoingCompletionRequests >= 0, "Unbalanced increments / decrements.");
|
||||
|
||||
if (this._ongoingCompletionRequests <= 0)
|
||||
WI.runtimeManager.activeExecutionContext.target.RuntimeAgent.releaseObjectGroup("completion");
|
||||
}
|
||||
|
||||
_clearLastProperties()
|
||||
{
|
||||
if (this._clearLastPropertiesTimeout) {
|
||||
clearTimeout(this._clearLastPropertiesTimeout);
|
||||
delete this._clearLastPropertiesTimeout;
|
||||
}
|
||||
|
||||
// Clear the cache of property names so any changes while stepping or sitting idle get picked up if the same
|
||||
// expression is evaluated again.
|
||||
this._lastPropertyNames = null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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: LayerTreeManager lacks advanced multi-target support. (Layers per-target)
|
||||
|
||||
WI.LayerTreeManager = class LayerTreeManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._showPaintRects = false;
|
||||
this._compositingBordersVisible = false;
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (target.hasDomain("LayerTree"))
|
||||
target.LayerTreeAgent.enable();
|
||||
|
||||
if (target.hasDomain("Page")) {
|
||||
if (target.hasCommand("Page.setShowPaintRects") && this._showPaintRects)
|
||||
target.PageAgent.setShowPaintRects(this._showPaintRects);
|
||||
|
||||
if (this._compositingBordersVisible) {
|
||||
// COMPATIBILITY(iOS 13.1): Page.setCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter.
|
||||
if (target.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter) {
|
||||
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowDebugBorders, this._compositingBordersVisible);
|
||||
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowRepaintCounter, this._compositingBordersVisible);
|
||||
} else if (target.hasCommand("Page.setCompositingBordersVisible"))
|
||||
target.PageAgent.setCompositingBordersVisible(this._compositingBordersVisible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static supportsShowingPaintRects()
|
||||
{
|
||||
return InspectorBackend.hasCommand("Page.setShowPaintRects");
|
||||
}
|
||||
|
||||
static supportsVisibleCompositingBorders()
|
||||
{
|
||||
return InspectorBackend.hasCommand("Page.setCompositingBordersVisible")
|
||||
|| (InspectorBackend.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get supported()
|
||||
{
|
||||
return InspectorBackend.hasDomain("LayerTree");
|
||||
}
|
||||
|
||||
get showPaintRects()
|
||||
{
|
||||
return this._showPaintRects;
|
||||
}
|
||||
|
||||
set showPaintRects(showPaintRects)
|
||||
{
|
||||
if (this._showPaintRects === showPaintRects)
|
||||
return;
|
||||
|
||||
this._showPaintRects = showPaintRects;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasCommand("Page.setShowPaintRects"))
|
||||
target.PageAgent.setShowPaintRects(this._showPaintRects);
|
||||
}
|
||||
|
||||
this.dispatchEventToListeners(LayerTreeManager.Event.ShowPaintRectsChanged);
|
||||
}
|
||||
|
||||
get compositingBordersVisible()
|
||||
{
|
||||
return this._compositingBordersVisible;
|
||||
}
|
||||
|
||||
set compositingBordersVisible(compositingBordersVisible)
|
||||
{
|
||||
if (this._compositingBordersVisible === compositingBordersVisible)
|
||||
return;
|
||||
|
||||
this._compositingBordersVisible = compositingBordersVisible;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY(iOS 13.1): Page.setCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter.
|
||||
if (target.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter) {
|
||||
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowDebugBorders, this._compositingBordersVisible);
|
||||
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowRepaintCounter, this._compositingBordersVisible);
|
||||
} else if (target.hasCommand("Page.setCompositingBordersVisible"))
|
||||
target.PageAgent.setCompositingBordersVisible(this._compositingBordersVisible);
|
||||
}
|
||||
|
||||
this.dispatchEventToListeners(LayerTreeManager.Event.CompositingBordersVisibleChanged);
|
||||
}
|
||||
|
||||
updateCompositingBordersVisibleFromPageIfNeeded()
|
||||
{
|
||||
if (!WI.targetsAvailable()) {
|
||||
WI.whenTargetsAvailable().then(() => {
|
||||
this.updateCompositingBordersVisibleFromPageIfNeeded();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
|
||||
// COMPATIBILITY(iOS 13.1): Page.getCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter.
|
||||
if (!target.hasCommand("Page.getCompositingBordersVisible"))
|
||||
return;
|
||||
|
||||
target.PageAgent.getCompositingBordersVisible((error, compositingBordersVisible) => {
|
||||
if (error) {
|
||||
WI.reportInternalError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.compositingBordersVisible = compositingBordersVisible;
|
||||
});
|
||||
}
|
||||
|
||||
layerTreeMutations(previousLayers, newLayers)
|
||||
{
|
||||
console.assert(this.supported);
|
||||
|
||||
if (isEmptyObject(previousLayers))
|
||||
return {preserved: [], additions: newLayers, removals: []};
|
||||
|
||||
let previousLayerIds = new Set;
|
||||
let newLayerIds = new Set;
|
||||
|
||||
let preserved = [];
|
||||
let additions = [];
|
||||
|
||||
for (let layer of previousLayers)
|
||||
previousLayerIds.add(layer.layerId);
|
||||
|
||||
for (let layer of newLayers) {
|
||||
newLayerIds.add(layer.layerId);
|
||||
|
||||
if (previousLayerIds.has(layer.layerId))
|
||||
preserved.push(layer);
|
||||
else
|
||||
additions.push(layer);
|
||||
}
|
||||
|
||||
let removals = previousLayers.filter((layer) => !newLayerIds.has(layer.layerId));
|
||||
|
||||
return {preserved, additions, removals};
|
||||
}
|
||||
|
||||
layersForNode(node, callback)
|
||||
{
|
||||
console.assert(this.supported);
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.LayerTreeAgent.layersForNode(node.id, (error, layers) => {
|
||||
callback(error ? [] : layers.map(WI.Layer.fromPayload));
|
||||
});
|
||||
}
|
||||
|
||||
reasonsForCompositingLayer(layer, callback)
|
||||
{
|
||||
console.assert(this.supported);
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
target.LayerTreeAgent.reasonsForCompositingLayer(layer.layerId, function(error, reasons) {
|
||||
callback(error ? 0 : reasons);
|
||||
});
|
||||
}
|
||||
|
||||
// LayerTreeObserver
|
||||
|
||||
layerTreeDidChange()
|
||||
{
|
||||
this.dispatchEventToListeners(WI.LayerTreeManager.Event.LayerTreeDidChange);
|
||||
}
|
||||
};
|
||||
|
||||
WI.LayerTreeManager.Event = {
|
||||
ShowPaintRectsChanged: "show-paint-rects-changed",
|
||||
CompositingBordersVisibleChanged: "compositing-borders-visible-changed",
|
||||
LayerTreeDidChange: "layer-tree-did-change",
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.MemoryManager = class MemoryManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
// Agent
|
||||
|
||||
get domains() { return ["Memory"]; }
|
||||
|
||||
activateExtraDomain(domain)
|
||||
{
|
||||
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
|
||||
|
||||
console.assert(domain === "Memory");
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (target.hasDomain("Memory"))
|
||||
target.MemoryAgent.enable();
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
enable()
|
||||
{
|
||||
if (this._enabled)
|
||||
return;
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
for (let target of WI.targets)
|
||||
this.initializeTarget(target);
|
||||
}
|
||||
|
||||
disable()
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
for (let target of WI.targets) {
|
||||
if (target.hasDomain("Memory"))
|
||||
target.MemoryAgent.disable();
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
// MemoryObserver
|
||||
|
||||
memoryPressure(timestamp, protocolSeverity)
|
||||
{
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
let memoryPressureEvent = WI.MemoryPressureEvent.fromPayload(timestamp, protocolSeverity);
|
||||
this.dispatchEventToListeners(WI.MemoryManager.Event.MemoryPressure, {memoryPressureEvent});
|
||||
}
|
||||
};
|
||||
|
||||
WI.MemoryManager.Event = {
|
||||
MemoryPressure: "memory-manager-memory-pressure",
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
WI.QueryController = class QueryController
|
||||
{
|
||||
// Public
|
||||
|
||||
executeQuery(query)
|
||||
{
|
||||
throw WI.NotImplementedError.subclassMustOverride();
|
||||
}
|
||||
|
||||
findQueryMatches(query, searchString, specialCharacterIndices)
|
||||
{
|
||||
if (query.length > searchString.length)
|
||||
return [];
|
||||
|
||||
let matches = [];
|
||||
let queryIndex = 0;
|
||||
let searchIndex = 0;
|
||||
let specialIndex = 0;
|
||||
let deadBranches = (new Array(query.length)).fill(Infinity);
|
||||
let type = WI.QueryMatch.Type.Special;
|
||||
|
||||
function pushMatch(index) {
|
||||
matches.push(new WI.QueryMatch(type, index, queryIndex));
|
||||
searchIndex = index + 1;
|
||||
queryIndex++;
|
||||
}
|
||||
|
||||
function matchNextSpecialCharacter() {
|
||||
if (specialIndex >= specialCharacterIndices.length)
|
||||
return false;
|
||||
|
||||
let originalSpecialIndex = specialIndex;
|
||||
while (specialIndex < specialCharacterIndices.length) {
|
||||
// Normal character matching can move past special characters,
|
||||
// so advance the special character index if it's before the
|
||||
// current search string position.
|
||||
let index = specialCharacterIndices[specialIndex++];
|
||||
if (index < searchIndex)
|
||||
continue;
|
||||
|
||||
if (query[queryIndex] === searchString[index]) {
|
||||
pushMatch(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
specialIndex = originalSpecialIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
function backtrack() {
|
||||
while (matches.length) {
|
||||
queryIndex--;
|
||||
|
||||
let lastMatch = matches.pop();
|
||||
if (lastMatch.type !== WI.QueryMatch.Type.Special)
|
||||
continue;
|
||||
|
||||
deadBranches[lastMatch.queryIndex] = lastMatch.index;
|
||||
searchIndex = matches.lastValue ? matches.lastValue.index + 1 : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
while (queryIndex < query.length && searchIndex <= searchString.length) {
|
||||
if (type === WI.QueryMatch.Type.Special && !matchNextSpecialCharacter())
|
||||
type = WI.QueryMatch.Type.Normal;
|
||||
|
||||
if (type === WI.QueryMatch.Type.Normal) {
|
||||
let index = searchString.indexOf(query[queryIndex], searchIndex);
|
||||
if (index >= 0 && index < deadBranches[queryIndex]) {
|
||||
pushMatch(index);
|
||||
type = WI.QueryMatch.Type.Special;
|
||||
} else if (!backtrack())
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (queryIndex < query.length)
|
||||
return [];
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
findSpecialCharacterIndices(string, separators)
|
||||
{
|
||||
if (!string.length)
|
||||
return [];
|
||||
|
||||
// Special characters include the following:
|
||||
// - The first character.
|
||||
// - Uppercase characters that follow a lowercase letter.
|
||||
// - Separators and the first character following the separator.
|
||||
|
||||
let indices = [0];
|
||||
|
||||
for (let i = 1; i < string.length; ++i) {
|
||||
let character = string[i];
|
||||
let isSpecial = false;
|
||||
|
||||
if (separators.includes(character))
|
||||
isSpecial = true;
|
||||
else {
|
||||
let previousCharacter = string[i - 1];
|
||||
if (separators.includes(previousCharacter))
|
||||
isSpecial = true;
|
||||
else if (character.isUpperCase() && previousCharacter.isLowerCase())
|
||||
isSpecial = true;
|
||||
}
|
||||
|
||||
if (isSpecial)
|
||||
indices.push(i);
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.ResourceQueryController = class ResourceQueryController extends WI.QueryController
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._resourceDataMap = new Map;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
addResource(resource)
|
||||
{
|
||||
this._resourceDataMap.set(resource, {});
|
||||
}
|
||||
|
||||
removeResource(resource)
|
||||
{
|
||||
this._resourceDataMap.delete(resource);
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this._resourceDataMap.clear();
|
||||
}
|
||||
|
||||
executeQuery(query)
|
||||
{
|
||||
if (!query || !this._resourceDataMap.size)
|
||||
return [];
|
||||
|
||||
query = query.removeWhitespace().toLowerCase();
|
||||
|
||||
let cookie = null;
|
||||
if (query.includes(":")) {
|
||||
let [newQuery, lineNumber, columnNumber] = query.split(":");
|
||||
query = newQuery;
|
||||
lineNumber = lineNumber ? parseInt(lineNumber, 10) - 1 : 0;
|
||||
columnNumber = columnNumber ? parseInt(columnNumber, 10) - 1 : 0;
|
||||
cookie = {lineNumber, columnNumber};
|
||||
}
|
||||
|
||||
let results = [];
|
||||
for (let [resource, cachedData] of this._resourceDataMap) {
|
||||
if (isEmptyObject(cachedData)) {
|
||||
let displayName = resource.displayName;
|
||||
cachedData.displayName = {
|
||||
searchString: displayName.toLowerCase(),
|
||||
specialCharacterIndices: this._findSpecialCharacterIndicesInDisplayName(displayName),
|
||||
};
|
||||
|
||||
let url = resource.url;
|
||||
cachedData.url = {
|
||||
searchString: url.toLowerCase(),
|
||||
specialCharacterIndices: this._findSpecialCharacterIndicesInURL(url),
|
||||
};
|
||||
}
|
||||
|
||||
let resourceResult = null;
|
||||
|
||||
let findQueryMatches = ({searchString, specialCharacterIndices}) => {
|
||||
let matches = this.findQueryMatches(query, searchString, specialCharacterIndices);
|
||||
if (!matches.length)
|
||||
return;
|
||||
|
||||
let queryResult = new WI.ResourceQueryResult(resource, searchString, matches, cookie);
|
||||
if (!resourceResult || resourceResult.rank < queryResult.rank)
|
||||
resourceResult = queryResult;
|
||||
};
|
||||
findQueryMatches(cachedData.displayName);
|
||||
findQueryMatches(cachedData.url);
|
||||
|
||||
if (resourceResult)
|
||||
results.push(resourceResult);
|
||||
}
|
||||
|
||||
// Resources are sorted in descending order by rank. Resources of equal
|
||||
// rank are sorted by display name.
|
||||
return results.sort((a, b) => {
|
||||
if (a.rank === b.rank)
|
||||
return a.resource.displayName.extendedLocaleCompare(b.resource.displayName);
|
||||
return b.rank - a.rank;
|
||||
});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_findSpecialCharacterIndicesInDisplayName(displayName)
|
||||
{
|
||||
return this.findSpecialCharacterIndices(displayName, "_.-");
|
||||
}
|
||||
|
||||
_findSpecialCharacterIndicesInURL(url)
|
||||
{
|
||||
return this.findSpecialCharacterIndices(url, "_.-/");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.RuntimeManager = class RuntimeManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._activeExecutionContext = null;
|
||||
|
||||
WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, function(event) {
|
||||
for (let target of WI.targets) {
|
||||
// COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
|
||||
if (target.hasCommand("Runtime.setSavedResultAlias"))
|
||||
target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static supportsAwaitPromise()
|
||||
{
|
||||
// COMPATIBILITY (iOS 12): Runtime.awaitPromise did not exist.
|
||||
return InspectorBackend.hasCommand("Runtime.awaitPromise");
|
||||
}
|
||||
|
||||
static preferredSavedResultPrefix()
|
||||
{
|
||||
// COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
|
||||
if (!InspectorBackend.hasCommand("Runtime.setSavedResultAlias"))
|
||||
return "$";
|
||||
return WI.settings.consoleSavedResultAlias.value || "$";
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
target.RuntimeAgent.enable();
|
||||
|
||||
if (WI.settings.showJavaScriptTypeInformation.value)
|
||||
target.RuntimeAgent.enableTypeProfiler();
|
||||
|
||||
if (WI.settings.enableControlFlowProfiler.value)
|
||||
target.RuntimeAgent.enableControlFlowProfiler();
|
||||
|
||||
// COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
|
||||
if (target.hasCommand("Runtime.setSavedResultAlias") && WI.settings.consoleSavedResultAlias.value)
|
||||
target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get activeExecutionContext()
|
||||
{
|
||||
return this._activeExecutionContext;
|
||||
}
|
||||
|
||||
set activeExecutionContext(executionContext)
|
||||
{
|
||||
if (this._activeExecutionContext === executionContext)
|
||||
return;
|
||||
|
||||
this._activeExecutionContext = executionContext;
|
||||
|
||||
this.dispatchEventToListeners(WI.RuntimeManager.Event.ActiveExecutionContextChanged);
|
||||
}
|
||||
|
||||
evaluateInInspectedWindow(expression, options, callback)
|
||||
{
|
||||
if (!this._activeExecutionContext) {
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
let {objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, emulateUserGesture, sourceURLAppender} = options;
|
||||
|
||||
includeCommandLineAPI = includeCommandLineAPI || false;
|
||||
doNotPauseOnExceptionsAndMuteConsole = doNotPauseOnExceptionsAndMuteConsole || false;
|
||||
returnByValue = returnByValue || false;
|
||||
generatePreview = generatePreview || false;
|
||||
saveResult = saveResult || false;
|
||||
emulateUserGesture = emulateUserGesture || false;
|
||||
sourceURLAppender = sourceURLAppender || appendWebInspectorSourceURL;
|
||||
|
||||
console.assert(objectGroup, "RuntimeManager.evaluateInInspectedWindow should always be called with an objectGroup");
|
||||
console.assert(typeof sourceURLAppender === "function");
|
||||
|
||||
if (!expression) {
|
||||
// There is no expression, so the completion should happen against global properties.
|
||||
expression = "this";
|
||||
} else if (/^\s*\{/.test(expression) && /\}\s*$/.test(expression)) {
|
||||
// Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label.
|
||||
expression = "(" + expression + ")";
|
||||
} else if (/\bawait\b/.test(expression)) {
|
||||
// Transform `await <expr>` into an async function assignment.
|
||||
expression = this._tryApplyAwaitConvenience(expression);
|
||||
}
|
||||
|
||||
expression = sourceURLAppender(expression);
|
||||
|
||||
let target = this._activeExecutionContext.target;
|
||||
let executionContextId = this._activeExecutionContext.id;
|
||||
|
||||
if (WI.debuggerManager.activeCallFrame) {
|
||||
target = WI.debuggerManager.activeCallFrame.target;
|
||||
executionContextId = target.executionContext.id;
|
||||
}
|
||||
|
||||
function evalCallback(error, result, wasThrown, savedResultIndex)
|
||||
{
|
||||
this.dispatchEventToListeners(WI.RuntimeManager.Event.DidEvaluate, {objectGroup});
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (returnByValue)
|
||||
callback(null, wasThrown, wasThrown ? null : result, savedResultIndex);
|
||||
else
|
||||
callback(WI.RemoteObject.fromPayload(result, target), wasThrown, savedResultIndex);
|
||||
}
|
||||
|
||||
if (WI.debuggerManager.activeCallFrame) {
|
||||
target.DebuggerAgent.evaluateOnCallFrame.invoke({
|
||||
callFrameId: WI.debuggerManager.activeCallFrame.id,
|
||||
expression,
|
||||
objectGroup,
|
||||
includeCommandLineAPI,
|
||||
doNotPauseOnExceptionsAndMuteConsole,
|
||||
returnByValue,
|
||||
generatePreview,
|
||||
saveResult,
|
||||
emulateUserGesture, // COMPATIBILITY (iOS 13): "emulateUserGesture" did not exist yet.
|
||||
}, evalCallback.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
target.RuntimeAgent.evaluate.invoke({
|
||||
expression,
|
||||
objectGroup,
|
||||
includeCommandLineAPI,
|
||||
doNotPauseOnExceptionsAndMuteConsole,
|
||||
contextId: executionContextId,
|
||||
returnByValue,
|
||||
generatePreview,
|
||||
saveResult,
|
||||
emulateUserGesture, // COMPATIBILITY (iOS 12.2): "emulateUserGesture" did not exist yet.
|
||||
}, evalCallback.bind(this));
|
||||
}
|
||||
|
||||
saveResult(remoteObject, callback)
|
||||
{
|
||||
console.assert(remoteObject instanceof WI.RemoteObject);
|
||||
|
||||
let target = this._activeExecutionContext.target;
|
||||
let executionContextId = this._activeExecutionContext.id;
|
||||
|
||||
function mycallback(error, savedResultIndex)
|
||||
{
|
||||
callback(savedResultIndex);
|
||||
}
|
||||
|
||||
if (remoteObject.objectId)
|
||||
target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), mycallback);
|
||||
else
|
||||
target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), executionContextId, mycallback);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_tryApplyAwaitConvenience(originalExpression)
|
||||
{
|
||||
let esprimaSyntaxTree;
|
||||
|
||||
// Do not transform if the original code parses just fine.
|
||||
try {
|
||||
esprima.parse(originalExpression);
|
||||
return originalExpression;
|
||||
} catch { }
|
||||
|
||||
// Do not transform if the async function version does not parse.
|
||||
try {
|
||||
esprimaSyntaxTree = esprima.parse("(async function(){" + originalExpression + "})");
|
||||
} catch {
|
||||
return originalExpression;
|
||||
}
|
||||
|
||||
// Assert expected AST produced by our wrapping code.
|
||||
console.assert(esprimaSyntaxTree.type === "Program");
|
||||
console.assert(esprimaSyntaxTree.body.length === 1);
|
||||
console.assert(esprimaSyntaxTree.body[0].type === "ExpressionStatement");
|
||||
console.assert(esprimaSyntaxTree.body[0].expression.type === "FunctionExpression");
|
||||
console.assert(esprimaSyntaxTree.body[0].expression.async);
|
||||
console.assert(esprimaSyntaxTree.body[0].expression.body.type === "BlockStatement");
|
||||
|
||||
// Do not transform if there is more than one statement.
|
||||
let asyncFunctionBlock = esprimaSyntaxTree.body[0].expression.body;
|
||||
if (asyncFunctionBlock.body.length !== 1)
|
||||
return originalExpression;
|
||||
|
||||
// Extract the variable name for transformation.
|
||||
let variableName;
|
||||
let anonymous = false;
|
||||
let declarationKind = "var";
|
||||
let awaitPortion;
|
||||
let statement = asyncFunctionBlock.body[0];
|
||||
if (statement.type === "ExpressionStatement"
|
||||
&& statement.expression.type === "AwaitExpression") {
|
||||
// await <expr>
|
||||
anonymous = true;
|
||||
} else if (statement.type === "ExpressionStatement"
|
||||
&& statement.expression.type === "AssignmentExpression"
|
||||
&& statement.expression.right.type === "AwaitExpression"
|
||||
&& statement.expression.left.type === "Identifier") {
|
||||
// x = await <expr>
|
||||
variableName = statement.expression.left.name;
|
||||
awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
|
||||
} else if (statement.type === "VariableDeclaration"
|
||||
&& statement.declarations.length === 1
|
||||
&& statement.declarations[0].init.type === "AwaitExpression"
|
||||
&& statement.declarations[0].id.type === "Identifier") {
|
||||
// var x = await <expr>
|
||||
variableName = statement.declarations[0].id.name;
|
||||
declarationKind = statement.kind;
|
||||
awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
|
||||
} else {
|
||||
// Do not transform if this was not one of the simple supported syntaxes.
|
||||
return originalExpression;
|
||||
}
|
||||
|
||||
if (anonymous) {
|
||||
return `
|
||||
(async function() {
|
||||
try {
|
||||
let result = ${originalExpression};
|
||||
console.info("%o", result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
undefined`;
|
||||
}
|
||||
|
||||
return `${declarationKind} ${variableName};
|
||||
(async function() {
|
||||
try {
|
||||
${variableName} = ${awaitPortion};
|
||||
console.info("%o", ${variableName});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
undefined;`;
|
||||
}
|
||||
};
|
||||
|
||||
WI.RuntimeManager.ConsoleObjectGroup = "console";
|
||||
WI.RuntimeManager.TopLevelExecutionContextIdentifier = undefined;
|
||||
|
||||
WI.RuntimeManager.Event = {
|
||||
DidEvaluate: "runtime-manager-did-evaluate",
|
||||
DefaultExecutionContextChanged: "runtime-manager-default-execution-context-changed",
|
||||
ActiveExecutionContextChanged: "runtime-manager-active-execution-context-changed",
|
||||
};
|
||||
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright (C) 2018, 2019 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.
|
||||
*/
|
||||
|
||||
WI.SelectionController = class SelectionController extends WI.Object
|
||||
{
|
||||
constructor(delegate, comparator)
|
||||
{
|
||||
super();
|
||||
|
||||
console.assert(delegate);
|
||||
console.assert(typeof comparator === "function");
|
||||
|
||||
this._delegate = delegate;
|
||||
this._comparator = comparator;
|
||||
|
||||
this._allowsEmptySelection = true;
|
||||
this._allowsMultipleSelection = false;
|
||||
this._lastSelectedItem = null;
|
||||
this._shiftAnchorItem = null;
|
||||
this._selectedItems = new Set;
|
||||
this._suppressSelectionDidChange = false;
|
||||
|
||||
console.assert(this._delegate.selectionControllerFirstSelectableItem, "SelectionController delegate must implement selectionControllerFirstSelectableItem.");
|
||||
console.assert(this._delegate.selectionControllerLastSelectableItem, "SelectionController delegate must implement selectionControllerLastSelectableItem.");
|
||||
console.assert(this._delegate.selectionControllerNextSelectableItem, "SelectionController delegate must implement selectionControllerNextSelectableItem.");
|
||||
console.assert(this._delegate.selectionControllerPreviousSelectableItem, "SelectionController delegate must implement selectionControllerPreviousSelectableItem.");
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static createTreeComparator(itemForRepresentedObject)
|
||||
{
|
||||
return (a, b) => {
|
||||
a = itemForRepresentedObject(a);
|
||||
b = itemForRepresentedObject(b);
|
||||
if (!a || !b)
|
||||
return 0;
|
||||
|
||||
let getLevel = (item) => {
|
||||
let level = 0;
|
||||
while (item = item.parent)
|
||||
level++;
|
||||
return level;
|
||||
};
|
||||
|
||||
let compareSiblings = (s, t) => {
|
||||
return s.parent.children.indexOf(s) - s.parent.children.indexOf(t);
|
||||
};
|
||||
|
||||
if (a.parent === b.parent)
|
||||
return compareSiblings(a, b);
|
||||
|
||||
let aLevel = getLevel(a);
|
||||
let bLevel = getLevel(b);
|
||||
while (aLevel > bLevel) {
|
||||
if (a.parent === b)
|
||||
return 1;
|
||||
a = a.parent;
|
||||
aLevel--;
|
||||
}
|
||||
while (bLevel > aLevel) {
|
||||
if (b.parent === a)
|
||||
return -1;
|
||||
b = b.parent;
|
||||
bLevel--;
|
||||
}
|
||||
|
||||
while (a.parent !== b.parent) {
|
||||
a = a.parent;
|
||||
b = b.parent;
|
||||
}
|
||||
|
||||
console.assert(a.parent === b.parent, "Missing common ancestor.", a, b);
|
||||
return compareSiblings(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
static createListComparator(indexForRepresentedObject)
|
||||
{
|
||||
console.assert(indexForRepresentedObject);
|
||||
|
||||
return (a, b) => {
|
||||
return indexForRepresentedObject(a) - indexForRepresentedObject(b);
|
||||
};
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get delegate() { return this._delegate; }
|
||||
get lastSelectedItem() { return this._lastSelectedItem; }
|
||||
get selectedItems() { return this._selectedItems; }
|
||||
|
||||
get allowsEmptySelection() { return this._allowsEmptySelection; }
|
||||
set allowsEmptySelection(flag) { this._allowsEmptySelection = flag; }
|
||||
|
||||
get allowsMultipleSelection()
|
||||
{
|
||||
return this._allowsMultipleSelection;
|
||||
}
|
||||
|
||||
set allowsMultipleSelection(flag)
|
||||
{
|
||||
if (this._allowsMultipleSelection === flag)
|
||||
return;
|
||||
|
||||
this._allowsMultipleSelection = flag;
|
||||
if (this._allowsMultipleSelection)
|
||||
return;
|
||||
|
||||
if (this._selectedItems.size > 1)
|
||||
this._updateSelectedItems(new Set([this._lastSelectedItem]));
|
||||
}
|
||||
|
||||
hasSelectedItem(item)
|
||||
{
|
||||
return this._selectedItems.has(item);
|
||||
}
|
||||
|
||||
selectItem(item, extendSelection = false)
|
||||
{
|
||||
console.assert(item, "Invalid item for selection.");
|
||||
console.assert(!extendSelection || this._allowsMultipleSelection, "Cannot extend selection with multiple selection disabled.");
|
||||
|
||||
if (!this._allowsMultipleSelection)
|
||||
extendSelection = false;
|
||||
|
||||
this._lastSelectedItem = item;
|
||||
this._shiftAnchorItem = null;
|
||||
|
||||
let newItems = new Set(extendSelection ? this._selectedItems : null);
|
||||
newItems.add(item);
|
||||
|
||||
this._updateSelectedItems(newItems);
|
||||
}
|
||||
|
||||
selectItems(items)
|
||||
{
|
||||
console.assert(this._allowsMultipleSelection, "Cannot select multiple items with multiple selection disabled.");
|
||||
if (!this._allowsMultipleSelection)
|
||||
return;
|
||||
|
||||
if (!this._lastSelectedItem || !items.has(this._lastSelectedItem))
|
||||
this._lastSelectedItem = items.lastValue;
|
||||
|
||||
if (!this._shiftAnchorItem || !items.has(this._shiftAnchorItem))
|
||||
this._shiftAnchorItem = this._lastSelectedItem;
|
||||
|
||||
this._updateSelectedItems(items);
|
||||
}
|
||||
|
||||
deselectItem(item)
|
||||
{
|
||||
console.assert(item, "Invalid item for selection.");
|
||||
|
||||
if (!this.hasSelectedItem(item))
|
||||
return;
|
||||
|
||||
if (!this._allowsEmptySelection && this._selectedItems.size === 1)
|
||||
return;
|
||||
|
||||
let newItems = new Set(this._selectedItems);
|
||||
newItems.delete(item);
|
||||
|
||||
if (this._lastSelectedItem === item) {
|
||||
this._lastSelectedItem = null;
|
||||
|
||||
if (newItems.size) {
|
||||
console.assert(this._allowsMultipleSelection);
|
||||
|
||||
const operation = WI.SelectionController.Operation.Extend;
|
||||
|
||||
// Find selected item closest to deselected item.
|
||||
let previous = item;
|
||||
let next = item;
|
||||
while (!this._lastSelectedItem && previous && next) {
|
||||
previous = this._previousSelectableItem(previous, operation);
|
||||
if (this.hasSelectedItem(previous)) {
|
||||
this._lastSelectedItem = previous;
|
||||
break;
|
||||
}
|
||||
|
||||
next = this._nextSelectableItem(next, operation);
|
||||
if (this.hasSelectedItem(next)) {
|
||||
this._lastSelectedItem = next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._shiftAnchorItem === item)
|
||||
this._shiftAnchorItem = null;
|
||||
|
||||
this._updateSelectedItems(newItems);
|
||||
}
|
||||
|
||||
selectAll()
|
||||
{
|
||||
if (!this._allowsMultipleSelection)
|
||||
return;
|
||||
|
||||
const operation = WI.SelectionController.Operation.Extend;
|
||||
|
||||
let newItems = new Set;
|
||||
this._addRange(newItems, this._firstSelectableItem(operation), this._lastSelectableItem(operation));
|
||||
this.selectItems(newItems);
|
||||
}
|
||||
|
||||
deselectAll()
|
||||
{
|
||||
this._deselectAllAndSelect(null);
|
||||
}
|
||||
|
||||
removeSelectedItems()
|
||||
{
|
||||
if (!this._selectedItems.size)
|
||||
return;
|
||||
|
||||
let operation = this._allowsMultipleSelection ? WI.SelectionController.Operation.Extend : WI.SelectionController.Operation.Direct;
|
||||
|
||||
let orderedSelection = Array.from(this._selectedItems).sort(this._comparator);
|
||||
|
||||
// Try selecting the item preceding the selection.
|
||||
let firstSelectedItem = orderedSelection[0];
|
||||
let itemToSelect = this._previousSelectableItem(firstSelectedItem, operation);
|
||||
if (!itemToSelect) {
|
||||
// If no item exists before the first item in the selection, try selecting
|
||||
// a deselected item (hole) within the selection.
|
||||
itemToSelect = firstSelectedItem;
|
||||
while (itemToSelect && this.hasSelectedItem(itemToSelect))
|
||||
itemToSelect = this._nextSelectableItem(itemToSelect, operation);
|
||||
|
||||
if (!itemToSelect || this.hasSelectedItem(itemToSelect)) {
|
||||
// If the selection contains no holes, try selecting the item
|
||||
// following the selection.
|
||||
itemToSelect = this._nextSelectableItem(orderedSelection.lastValue, operation);
|
||||
}
|
||||
}
|
||||
|
||||
this._deselectAllAndSelect(itemToSelect);
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
this._lastSelectedItem = null;
|
||||
this._shiftAnchorItem = null;
|
||||
this._selectedItems.clear();
|
||||
}
|
||||
|
||||
didRemoveItems(items)
|
||||
{
|
||||
console.assert(items instanceof Set);
|
||||
|
||||
if (!items.size || !this._selectedItems.size)
|
||||
return;
|
||||
|
||||
this._updateSelectedItems(this._selectedItems.difference(items));
|
||||
}
|
||||
|
||||
handleKeyDown(event)
|
||||
{
|
||||
if (event.key === "a" && event.commandOrControlKey) {
|
||||
this.selectAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.metaKey || event.ctrlKey)
|
||||
return false;
|
||||
|
||||
if (event.keyIdentifier === "Up" || event.keyIdentifier === "Down") {
|
||||
this._selectItemsFromArrowKey(event.keyIdentifier === "Up", event.shiftKey);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
handleItemMouseDown(item, event)
|
||||
{
|
||||
console.assert(item, "Invalid item for selection.");
|
||||
|
||||
if (event.button !== 0 || event.ctrlKey)
|
||||
return;
|
||||
|
||||
// Command (macOS) or Control (Windows) key takes precedence over shift
|
||||
// whether or not multiple selection is enabled, so handle it first.
|
||||
if (event.commandOrControlKey) {
|
||||
if (this.hasSelectedItem(item))
|
||||
this.deselectItem(item);
|
||||
else
|
||||
this.selectItem(item, this._allowsMultipleSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
let shiftExtendSelection = this._allowsMultipleSelection && event.shiftKey;
|
||||
if (!shiftExtendSelection) {
|
||||
this.selectItem(item);
|
||||
return;
|
||||
}
|
||||
|
||||
let newItems = new Set(this._selectedItems);
|
||||
|
||||
// Shift-clicking when nothing is selected should cause the first item
|
||||
// through the clicked item to be selected.
|
||||
if (!newItems.size) {
|
||||
this._lastSelectedItem = item;
|
||||
this._shiftAnchorItem = this._firstSelectableItem(WI.SelectionController.Operation.Extend);
|
||||
|
||||
this._addRange(newItems, this._shiftAnchorItem, this._lastSelectedItem);
|
||||
this._updateSelectedItems(newItems);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._shiftAnchorItem)
|
||||
this._shiftAnchorItem = this._lastSelectedItem;
|
||||
|
||||
// Shift-clicking will add to or delete from the current selection, or
|
||||
// pivot the selection around the anchor (a delete followed by an add).
|
||||
// We could check for all three cases, and add or delete only those items
|
||||
// that are necessary, but it is simpler to throw out the previous shift-
|
||||
// selected range and add the new range between the anchor and clicked item.
|
||||
|
||||
let sortItemPair = (a, b) => {
|
||||
return [a, b].sort(this._comparator);
|
||||
};
|
||||
|
||||
if (this._shiftAnchorItem !== this._lastSelectedItem) {
|
||||
let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, this._lastSelectedItem);
|
||||
this._deleteRange(newItems, startItem, endItem);
|
||||
}
|
||||
|
||||
let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, item);
|
||||
this._addRange(newItems, startItem, endItem);
|
||||
|
||||
this._lastSelectedItem = item;
|
||||
|
||||
this._updateSelectedItems(newItems);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_deselectAllAndSelect(item)
|
||||
{
|
||||
if (!this._selectedItems.size && !item)
|
||||
return;
|
||||
|
||||
if (this._selectedItems.size === 1 && this.hasSelectedItem(item))
|
||||
return;
|
||||
|
||||
this._lastSelectedItem = item;
|
||||
this._shiftAnchorItem = null;
|
||||
|
||||
let newItems = new Set;
|
||||
if (item)
|
||||
newItems.add(item);
|
||||
|
||||
this._updateSelectedItems(newItems);
|
||||
}
|
||||
|
||||
_selectItemsFromArrowKey(goingUp, shiftKey)
|
||||
{
|
||||
let extendSelection = shiftKey && this._allowsMultipleSelection;
|
||||
let operation = extendSelection ? WI.SelectionController.Operation.Extend : WI.SelectionController.Operation.Direct;
|
||||
|
||||
if (!this._selectedItems.size) {
|
||||
this.selectItem(goingUp ? this._lastSelectableItem(operation) : this._firstSelectableItem(operation));
|
||||
return;
|
||||
}
|
||||
|
||||
let item = goingUp ? this._previousSelectableItem(this._lastSelectedItem, operation) : this._nextSelectableItem(this._lastSelectedItem, operation);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (!extendSelection || !this.hasSelectedItem(item)) {
|
||||
this.selectItem(item, extendSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Since the item in the direction of movement is selected, we are either
|
||||
// extending the selection into the item, or deselecting. Determine which
|
||||
// by checking whether the item opposite the anchor item is selected.
|
||||
let priorItem = goingUp ? this._nextSelectableItem(this._lastSelectedItem, operation) : this._previousSelectableItem(this._lastSelectedItem, operation);
|
||||
if (!priorItem || !this.hasSelectedItem(priorItem)) {
|
||||
this.deselectItem(this._lastSelectedItem);
|
||||
return;
|
||||
}
|
||||
|
||||
// The selection is being extended into the item; make it the new
|
||||
// anchor item then continue searching in the direction of movement
|
||||
// for an unselected item to select.
|
||||
while (item) {
|
||||
if (!this.hasSelectedItem(item)) {
|
||||
this.selectItem(item, extendSelection);
|
||||
break;
|
||||
}
|
||||
|
||||
this._lastSelectedItem = item;
|
||||
item = goingUp ? this._previousSelectableItem(item, operation) : this._nextSelectableItem(item, operation);
|
||||
}
|
||||
}
|
||||
|
||||
_firstSelectableItem(operation)
|
||||
{
|
||||
return this._delegate.selectionControllerFirstSelectableItem(this, operation);
|
||||
}
|
||||
|
||||
_lastSelectableItem(operation)
|
||||
{
|
||||
return this._delegate.selectionControllerLastSelectableItem(this, operation);
|
||||
}
|
||||
|
||||
_previousSelectableItem(item, operation)
|
||||
{
|
||||
return this._delegate.selectionControllerPreviousSelectableItem(this, item, operation);
|
||||
}
|
||||
|
||||
_nextSelectableItem(item, operation)
|
||||
{
|
||||
return this._delegate.selectionControllerNextSelectableItem(this, item, operation);
|
||||
}
|
||||
|
||||
_updateSelectedItems(items)
|
||||
{
|
||||
let oldSelectedItems = this._selectedItems;
|
||||
this._selectedItems = items;
|
||||
|
||||
if (this._suppressSelectionDidChange || !this._delegate.selectionControllerSelectionDidChange)
|
||||
return;
|
||||
|
||||
let deselectedItems = oldSelectedItems.difference(items);
|
||||
let selectedItems = items.difference(oldSelectedItems);
|
||||
if (deselectedItems.size || selectedItems.size)
|
||||
this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);
|
||||
}
|
||||
|
||||
_addRange(items, firstItem, lastItem)
|
||||
{
|
||||
console.assert(this._allowsMultipleSelection);
|
||||
|
||||
const operation = WI.SelectionController.Operation.Extend;
|
||||
|
||||
let current = firstItem;
|
||||
while (current) {
|
||||
items.add(current);
|
||||
if (current === lastItem)
|
||||
break;
|
||||
current = this._nextSelectableItem(current, operation);
|
||||
}
|
||||
|
||||
console.assert(!lastItem || items.has(lastItem), "End of range could not be reached.");
|
||||
}
|
||||
|
||||
_deleteRange(items, firstItem, lastItem)
|
||||
{
|
||||
console.assert(this._allowsMultipleSelection);
|
||||
|
||||
const operation = WI.SelectionController.Operation.Extend;
|
||||
|
||||
let current = firstItem;
|
||||
while (current) {
|
||||
items.delete(current);
|
||||
if (current === lastItem)
|
||||
break;
|
||||
current = this._nextSelectableItem(current, operation);
|
||||
}
|
||||
|
||||
console.assert(!lastItem || !items.has(lastItem), "End of range could not be reached.");
|
||||
}
|
||||
};
|
||||
|
||||
WI.SelectionController.Operation = {
|
||||
Direct: Symbol("selection-operation-direct"),
|
||||
Extend: Symbol("selection-operation-extend"),
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.StackTraceTreeController = class StackTraceTreeController extends WI.Object
|
||||
{
|
||||
constructor(treeOutline)
|
||||
{
|
||||
console.assert(treeOutline instanceof WI.TreeOutline);
|
||||
|
||||
super();
|
||||
|
||||
this._stackTrace = null;
|
||||
|
||||
this._treeOutline = treeOutline;
|
||||
|
||||
if (this._treeOutline.selectable)
|
||||
this._treeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
|
||||
else
|
||||
this._treeOutline.addEventListener(WI.TreeOutline.Event.ElementClicked, this._treeElementClicked, this);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static groupBlackboxedStackTrace(parent, stackTrace, {rememberBlackboxedCallFrameGroupToAutoExpand} = {})
|
||||
{
|
||||
let parentIsNode = parent instanceof Node;
|
||||
console.assert(parentIsNode || parent instanceof WI.TreeOutline || parent instanceof WI.TreeElement, parent);
|
||||
console.assert(stackTrace instanceof WI.StackTrace, stackTrace);
|
||||
|
||||
let CallFrameUIClass = parentIsNode ? WI.CallFrameView : WI.CallFrameTreeElement;
|
||||
|
||||
let activeCallFrameTreeElement = WI.StackTraceTreeController._groupBlackboxedCallFrames(parent, stackTrace.callFrames, {rememberBlackboxedCallFrameGroupToAutoExpand});
|
||||
|
||||
let parentStackTrace = stackTrace.parentStackTrace;
|
||||
while (parentStackTrace) {
|
||||
console.assert(parentStackTrace.callFrames.length, "StackTrace should have non-empty call frames array.");
|
||||
if (!parentStackTrace.callFrames.length)
|
||||
break;
|
||||
|
||||
let boundaryCallFrame;
|
||||
if (parentStackTrace.topCallFrameIsBoundary) {
|
||||
boundaryCallFrame = parentStackTrace.callFrames[0];
|
||||
console.assert(boundaryCallFrame.nativeCode && !boundaryCallFrame.sourceCodeLocation);
|
||||
} else {
|
||||
// Create a generic native CallFrame for the asynchronous boundary.
|
||||
boundaryCallFrame = new WI.CallFrame(parentStackTrace.callFrames[0].target, {
|
||||
functionName: WI.UIString("(async)"),
|
||||
nativeCode: true,
|
||||
});
|
||||
}
|
||||
|
||||
parent.appendChild(new CallFrameUIClass(boundaryCallFrame, {showFunctionName: true, isAsyncBoundaryCallFrame: true}));
|
||||
|
||||
let startIndex = parentStackTrace.topCallFrameIsBoundary ? 1 : 0;
|
||||
let parentCallFrames = startIndex ? parentStackTrace.callFrames.slice(startIndex) : parentStackTrace.callFrames;
|
||||
WI.StackTraceTreeController._groupBlackboxedCallFrames(parent, parentCallFrames, {rememberBlackboxedCallFrameGroupToAutoExpand});
|
||||
|
||||
if (parentStackTrace.truncated) {
|
||||
let truncatedCallFrame = new WI.CallFrame(parentStackTrace.callFrames[0].target, {
|
||||
functionName: WI.UIString("(call frames truncated)"),
|
||||
nativeCode: true,
|
||||
});
|
||||
parent.appendChild(new CallFrameUIClass(truncatedCallFrame, {showFunctionName: true, isTruncatedBoundaryCallFrame: true}));
|
||||
}
|
||||
|
||||
parentStackTrace = parentStackTrace.parentStackTrace;
|
||||
}
|
||||
|
||||
return activeCallFrameTreeElement;
|
||||
}
|
||||
|
||||
static _groupBlackboxedCallFrames(parent, callFrames, {rememberBlackboxedCallFrameGroupToAutoExpand} = {})
|
||||
{
|
||||
let parentIsNode = parent instanceof Node;
|
||||
console.assert(parentIsNode || parent instanceof WI.TreeOutline || parent instanceof WI.TreeElement, parent);
|
||||
console.assert(Array.isArray(callFrames) && callFrames.length && callFrames.every((callFrame) => callFrame instanceof WI.CallFrame), callFrames);
|
||||
|
||||
let BlackboxedGroupUIClass = parentIsNode ? WI.BlackboxedGroupView : WI.BlackboxedGroupTreeElement;
|
||||
let blackboxedGroupUIOptions = {rememberBlackboxedCallFrameGroupToAutoExpand};
|
||||
|
||||
let CallFrameUIClass = parentIsNode ? WI.CallFrameView : WI.CallFrameTreeElement;
|
||||
let callFrameUIOptions = {showFunctionName: true, indicateIfBlackboxed: true};
|
||||
|
||||
let activeCallFrame = WI.debuggerManager.activeCallFrame;
|
||||
let activeCallFrameTreeElement = null;
|
||||
|
||||
let blackboxedCallFrameGroupStartIndex = undefined;
|
||||
|
||||
function displayable(callFrame) {
|
||||
if (parentIsNode) {
|
||||
if (!callFrame.sourceCodeLocation && callFrame.functionName === null)
|
||||
return false;
|
||||
|
||||
if (callFrame.isConsoleEvaluation && !WI.settings.debugShowConsoleEvaluations.value)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add one extra iteration to handle call stacks that start blackboxed.
|
||||
for (let i = 0; i < callFrames.length + 1; ++i) {
|
||||
let callFrame = callFrames[i];
|
||||
|
||||
if (callFrame) {
|
||||
if (callFrames.length > 1 && !displayable(callFrame))
|
||||
continue;
|
||||
|
||||
if (callFrame.blackboxed) {
|
||||
blackboxedCallFrameGroupStartIndex ??= i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (blackboxedCallFrameGroupStartIndex !== undefined) {
|
||||
let blackboxedCallFrameGroup = callFrames.slice(blackboxedCallFrameGroupStartIndex, i).filter(displayable);
|
||||
blackboxedCallFrameGroupStartIndex = undefined;
|
||||
|
||||
if (!rememberBlackboxedCallFrameGroupToAutoExpand || !WI.debuggerManager.shouldAutoExpandBlackboxedCallFrameGroup(blackboxedCallFrameGroup))
|
||||
parent.appendChild(new BlackboxedGroupUIClass(blackboxedCallFrameGroup, blackboxedGroupUIOptions));
|
||||
else {
|
||||
for (let blackboxedCallFrame of blackboxedCallFrameGroup)
|
||||
parent.appendChild(new CallFrameUIClass(blackboxedCallFrame, callFrameUIOptions));
|
||||
}
|
||||
}
|
||||
|
||||
if (!callFrame)
|
||||
continue;
|
||||
|
||||
let callFrameTreeElement = new CallFrameUIClass(callFrame, callFrameUIOptions);
|
||||
if (callFrame === activeCallFrame && !parentIsNode)
|
||||
activeCallFrameTreeElement = callFrameTreeElement;
|
||||
parent.appendChild(callFrameTreeElement);
|
||||
}
|
||||
|
||||
return activeCallFrameTreeElement;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get treeOutline() { return this._treeOutline; }
|
||||
|
||||
get stackTrace()
|
||||
{
|
||||
return this._stackTrace;
|
||||
}
|
||||
|
||||
set stackTrace(stackTrace)
|
||||
{
|
||||
stackTrace = stackTrace || null;
|
||||
if (this._stackTrace === stackTrace)
|
||||
return;
|
||||
|
||||
this._stackTrace = stackTrace;
|
||||
|
||||
this._treeOutline.removeChildren();
|
||||
|
||||
if (this._stackTrace)
|
||||
WI.StackTraceTreeController.groupBlackboxedStackTrace(this._treeOutline, this._stackTrace);
|
||||
}
|
||||
|
||||
disconnect()
|
||||
{
|
||||
if (this._treeOutline.selectable)
|
||||
this._treeOutline.removeEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
|
||||
else
|
||||
this._treeOutline.removeEventListener(WI.TreeOutline.Event.ElementClicked, this._treeElementClicked, this);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_treeElementClicked(event)
|
||||
{
|
||||
this._showSourceCodeLocation(event.data.treeElement);
|
||||
}
|
||||
|
||||
_treeSelectionDidChange(event)
|
||||
{
|
||||
this._showSourceCodeLocation(this._treeOutline.selectedTreeElement);
|
||||
}
|
||||
|
||||
_showSourceCodeLocation(treeElement)
|
||||
{
|
||||
let callFrame = treeElement.callFrame;
|
||||
if (!callFrame.sourceCodeLocation)
|
||||
return;
|
||||
|
||||
WI.showSourceCodeLocation(callFrame.sourceCodeLocation, {
|
||||
ignoreNetworkTab: true,
|
||||
ignoreSearchTab: true,
|
||||
// Treat call frame clicks as link clicks since it jumps to a source location.
|
||||
initiatorHint: WI.TabBrowser.TabNavigationInitiator.LinkClick,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.TabActivityDiagnosticEventRecorder = class TabActivityDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
|
||||
{
|
||||
constructor(controller)
|
||||
{
|
||||
super("TabActivity", controller);
|
||||
|
||||
this._inspectorHasFocus = true;
|
||||
this._lastUserInteractionTimestamp = undefined;
|
||||
|
||||
this._eventSamplingTimerIdentifier = undefined;
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
// In milliseconds.
|
||||
static get eventSamplingInterval() { return 60 * 1000; }
|
||||
static get initialDelayBeforeSamplingInterval() { return 10 * 1000; }
|
||||
|
||||
// Protected
|
||||
|
||||
setup()
|
||||
{
|
||||
const options = {
|
||||
capture: true,
|
||||
};
|
||||
window.addEventListener("focus", this, options);
|
||||
window.addEventListener("blur", this, options);
|
||||
window.addEventListener("keydown", this, options);
|
||||
window.addEventListener("mousedown", this, options);
|
||||
|
||||
// If it's been less than 10 seconds since the frontend loaded, wait a bit.
|
||||
if (performance.now() - WI.frontendCompletedLoadTimestamp < TabActivityDiagnosticEventRecorder.initialDelayBeforeSamplingInterval)
|
||||
this._startInitialDelayBeforeSamplingTimer();
|
||||
else
|
||||
this._startEventSamplingTimer();
|
||||
|
||||
}
|
||||
|
||||
teardown()
|
||||
{
|
||||
const options = {
|
||||
capture: true,
|
||||
};
|
||||
window.removeEventListener("focus", this, options);
|
||||
window.removeEventListener("blur", this, options);
|
||||
window.removeEventListener("keydown", this, options);
|
||||
window.removeEventListener("mousedown", this, options);
|
||||
|
||||
this._stopInitialDelayBeforeSamplingTimer();
|
||||
this._stopEventSamplingTimer();
|
||||
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
handleEvent(event)
|
||||
{
|
||||
switch (event.type) {
|
||||
case "focus":
|
||||
this._handleWindowFocus(event);
|
||||
break;
|
||||
case "blur":
|
||||
this._handleWindowBlur(event);
|
||||
break;
|
||||
case "keydown":
|
||||
this._handleWindowKeyDown(event);
|
||||
break;
|
||||
case "mousedown":
|
||||
this._handleWindowMouseDown(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_startInitialDelayBeforeSamplingTimer()
|
||||
{
|
||||
if (this._initialDelayBeforeSamplingTimerIdentifier) {
|
||||
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
|
||||
// All intervals are in milliseconds.
|
||||
let maximumInitialDelay = TabActivityDiagnosticEventRecorder.initialDelayBeforeSamplingInterval;
|
||||
let elapsedTime = performance.now() - WI.frontendCompletedLoadTimestamp;
|
||||
let remainingTime = maximumInitialDelay - elapsedTime;
|
||||
let initialDelay = Number.constrain(remainingTime, 0, maximumInitialDelay);
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = setTimeout(this._sampleCurrentTabActivity.bind(this), initialDelay);
|
||||
}
|
||||
|
||||
_stopInitialDelayBeforeSamplingTimer()
|
||||
{
|
||||
if (this._initialDelayBeforeSamplingTimerIdentifier) {
|
||||
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
|
||||
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
_startEventSamplingTimer()
|
||||
{
|
||||
if (this._eventSamplingTimerIdentifier) {
|
||||
clearTimeout(this._eventSamplingTimerIdentifier);
|
||||
this._eventSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
|
||||
this._eventSamplingTimerIdentifier = setTimeout(this._sampleCurrentTabActivity.bind(this), TabActivityDiagnosticEventRecorder.eventSamplingInterval);
|
||||
}
|
||||
|
||||
_stopEventSamplingTimer()
|
||||
{
|
||||
if (this._eventSamplingTimerIdentifier) {
|
||||
clearTimeout(this._eventSamplingTimerIdentifier);
|
||||
this._eventSamplingTimerIdentifier = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
_sampleCurrentTabActivity()
|
||||
{
|
||||
// Set up the next timer first so later code can bail out if there's nothing to do.
|
||||
this._stopEventSamplingTimer();
|
||||
this._stopInitialDelayBeforeSamplingTimer();
|
||||
this._startEventSamplingTimer();
|
||||
|
||||
let intervalSinceLastUserInteraction = performance.now() - this._lastUserInteractionTimestamp;
|
||||
if (intervalSinceLastUserInteraction > TabActivityDiagnosticEventRecorder.eventSamplingInterval) {
|
||||
if (WI.settings.debugAutoLogDiagnosticEvents.valueRespectingDebugUIAvailability)
|
||||
console.log("TabActivity: sample not reported, last user interaction was %.1f seconds ago.".format(intervalSinceLastUserInteraction / 1000));
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedTabContentView = WI.tabBrowser.selectedTabContentView;
|
||||
console.assert(selectedTabContentView);
|
||||
if (!selectedTabContentView)
|
||||
return;
|
||||
|
||||
let tabType = selectedTabContentView.type;
|
||||
let interval = TabActivityDiagnosticEventRecorder.eventSamplingInterval / 1000;
|
||||
this.logDiagnosticEvent(this.name, {tabType, interval});
|
||||
}
|
||||
|
||||
_didObserveUserInteraction()
|
||||
{
|
||||
if (!this._inspectorHasFocus)
|
||||
return;
|
||||
|
||||
this._lastUserInteractionTimestamp = performance.now();
|
||||
}
|
||||
|
||||
_handleWindowFocus(event)
|
||||
{
|
||||
if (event.target !== window)
|
||||
return;
|
||||
|
||||
this._inspectorHasFocus = true;
|
||||
}
|
||||
|
||||
_handleWindowBlur(event)
|
||||
{
|
||||
if (event.target !== window)
|
||||
return;
|
||||
|
||||
this._inspectorHasFocus = false;
|
||||
}
|
||||
|
||||
_handleWindowKeyDown(event)
|
||||
{
|
||||
this._didObserveUserInteraction();
|
||||
}
|
||||
|
||||
_handleWindowMouseDown(event)
|
||||
{
|
||||
this._didObserveUserInteraction();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
WI.TabNavigationDiagnosticEventRecorder = class TabNavigationDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
|
||||
{
|
||||
constructor(controller)
|
||||
{
|
||||
super("TabNavigation", controller);
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
setup()
|
||||
{
|
||||
WI.tabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
|
||||
}
|
||||
|
||||
teardown()
|
||||
{
|
||||
WI.tabBrowser.removeEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_selectedTabContentViewDidChange(event)
|
||||
{
|
||||
let outgoingTabType = event.data.outgoingTab.identifier;
|
||||
let incomingTabType = event.data.incomingTab.identifier;
|
||||
let initiator = WI.TabNavigationDiagnosticEventRecorder.tabBrowserInitiatorToEventInitiator(event.data.initiator || WI.TabBrowser.TabNavigationInitiator.Unknown);
|
||||
if (!initiator) {
|
||||
// If initiator is null, then there is a missing value in the switch. This is a programming error.
|
||||
WI.reportInternalError("Value of 'initiator' could not be parsed: " + event.data.initiator);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logDiagnosticEvent(this.name, {outgoingTabType, incomingTabType, initiator});
|
||||
}
|
||||
|
||||
static tabBrowserInitiatorToEventInitiator(tabBrowserInitiator)
|
||||
{
|
||||
switch (tabBrowserInitiator) {
|
||||
case WI.TabBrowser.TabNavigationInitiator.TabClick:
|
||||
return "tab-click";
|
||||
case WI.TabBrowser.TabNavigationInitiator.LinkClick:
|
||||
return "link-click";
|
||||
case WI.TabBrowser.TabNavigationInitiator.ButtonClick:
|
||||
return "button-click";
|
||||
case WI.TabBrowser.TabNavigationInitiator.ContextMenu:
|
||||
return "context-menu";
|
||||
case WI.TabBrowser.TabNavigationInitiator.Dashboard:
|
||||
return "dashboard";
|
||||
case WI.TabBrowser.TabNavigationInitiator.Breakpoint:
|
||||
return "breakpoint";
|
||||
case WI.TabBrowser.TabNavigationInitiator.Inspect:
|
||||
return "inspect";
|
||||
case WI.TabBrowser.TabNavigationInitiator.KeyboardShortcut:
|
||||
return "keyboard-shortcut";
|
||||
case WI.TabBrowser.TabNavigationInitiator.FrontendAPI:
|
||||
return "frontend-api";
|
||||
case WI.TabBrowser.TabNavigationInitiator.Unknown:
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
console.error("Unhandled initiator type: " + tabBrowserInitiator);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2018 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.
|
||||
*/
|
||||
|
||||
WI.TargetManager = class TargetManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._targets = new Map;
|
||||
this._cachedTargetsList = null;
|
||||
this._seenPageTarget = false;
|
||||
this._transitionTimeoutIdentifier = undefined;
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
// COMPATIBILITY (iOS 13): Target.setPauseOnStart did not exist yet.
|
||||
if (target.hasCommand("Target.setPauseOnStart"))
|
||||
target.TargetAgent.setPauseOnStart(true);
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get targets()
|
||||
{
|
||||
if (!this._cachedTargetsList)
|
||||
this._cachedTargetsList = Array.from(this._targets.values()).filter((target) => !(target instanceof WI.MultiplexingBackendTarget));
|
||||
return this._cachedTargetsList;
|
||||
}
|
||||
|
||||
get workerTargets()
|
||||
{
|
||||
return this.targets.filter((target) => target.type === WI.TargetType.Worker);
|
||||
}
|
||||
|
||||
get allTargets()
|
||||
{
|
||||
return Array.from(this._targets.values());
|
||||
}
|
||||
|
||||
targetForIdentifier(targetId)
|
||||
{
|
||||
if (!targetId)
|
||||
return null;
|
||||
|
||||
for (let target of this._targets.values()) {
|
||||
if (target.identifier === targetId)
|
||||
return target;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
addTarget(target)
|
||||
{
|
||||
console.assert(target);
|
||||
console.assert(!this._targets.has(target.identifier));
|
||||
|
||||
this._cachedTargetsList = null;
|
||||
this._targets.set(target.identifier, target);
|
||||
|
||||
this.dispatchEventToListeners(WI.TargetManager.Event.TargetAdded, {target});
|
||||
}
|
||||
|
||||
removeTarget(target)
|
||||
{
|
||||
console.assert(target);
|
||||
console.assert(target !== WI.mainTarget);
|
||||
|
||||
this._cachedTargetsList = null;
|
||||
this._targets.delete(target.identifier);
|
||||
target.destroy();
|
||||
|
||||
this.dispatchEventToListeners(WI.TargetManager.Event.TargetRemoved, {target});
|
||||
}
|
||||
|
||||
createMultiplexingBackendTarget()
|
||||
{
|
||||
console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage);
|
||||
|
||||
let target = new WI.MultiplexingBackendTarget;
|
||||
target.initialize();
|
||||
|
||||
this._initializeBackendTarget(target);
|
||||
|
||||
// Add the target without dispatching an event.
|
||||
this._targets.set(target.identifier, target);
|
||||
}
|
||||
|
||||
createDirectBackendTarget()
|
||||
{
|
||||
console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.WebPage);
|
||||
|
||||
let target = new WI.DirectBackendTarget;
|
||||
target.initialize();
|
||||
|
||||
this._initializeBackendTarget(target);
|
||||
|
||||
if (WI.sharedApp.debuggableType === WI.DebuggableType.ITML || WI.sharedApp.debuggableType === WI.DebuggableType.Page)
|
||||
this._initializePageTarget(target);
|
||||
|
||||
this.addTarget(target);
|
||||
}
|
||||
|
||||
// TargetObserver
|
||||
|
||||
targetCreated(parentTarget, targetInfo)
|
||||
{
|
||||
let connection = new InspectorBackend.TargetConnection(parentTarget, targetInfo.targetId);
|
||||
let subTarget = this._createTarget(parentTarget, targetInfo, connection);
|
||||
this._checkAndHandlePageTargetTransition(subTarget);
|
||||
subTarget.initialize();
|
||||
this.addTarget(subTarget);
|
||||
}
|
||||
|
||||
didCommitProvisionalTarget(parentTarget, previousTargetId, newTargetId)
|
||||
{
|
||||
this.targetDestroyed(previousTargetId);
|
||||
let target = this._targets.get(newTargetId);
|
||||
console.assert(target);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
target.didCommitProvisionalTarget();
|
||||
this._checkAndHandlePageTargetTransition(target);
|
||||
target.connection.dispatchProvisionalMessages();
|
||||
|
||||
this.dispatchEventToListeners(WI.TargetManager.Event.DidCommitProvisionalTarget, {previousTargetId, target});
|
||||
}
|
||||
|
||||
targetDestroyed(targetId)
|
||||
{
|
||||
let target = this._targets.get(targetId);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
this._checkAndHandlePageTargetTermination(target);
|
||||
this.removeTarget(target);
|
||||
}
|
||||
|
||||
dispatchMessageFromTarget(targetId, message)
|
||||
{
|
||||
let target = this._targets.get(targetId);
|
||||
console.assert(target);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (target.isProvisional)
|
||||
target.connection.addProvisionalMessage(message);
|
||||
else
|
||||
target.connection.dispatch(message);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_createTarget(parentTarget, targetInfo, connection)
|
||||
{
|
||||
// COMPATIBILITY (iOS 13.0): `Target.TargetInfo.isProvisional` and `Target.TargetInfo.isPaused` did not exist yet.
|
||||
let {targetId, type, isProvisional, isPaused} = targetInfo;
|
||||
|
||||
switch (type) {
|
||||
case InspectorBackend.Enum.Target.TargetInfoType.Page:
|
||||
return new WI.PageTarget(parentTarget, targetId, WI.UIString("Page"), connection, {isProvisional, isPaused});
|
||||
case InspectorBackend.Enum.Target.TargetInfoType.Worker:
|
||||
return new WI.WorkerTarget(parentTarget, targetId, WI.UIString("Worker"), connection, {isPaused});
|
||||
case "serviceworker": // COMPATIBILITY (iOS 13): "serviceworker" was renamed to "service-worker".
|
||||
case InspectorBackend.Enum.Target.TargetInfoType.ServiceWorker:
|
||||
return new WI.WorkerTarget(parentTarget, targetId, WI.UIString("ServiceWorker"), connection, {isPaused});
|
||||
}
|
||||
|
||||
throw "Unknown Target type: " + type;
|
||||
}
|
||||
|
||||
_checkAndHandlePageTargetTransition(target)
|
||||
{
|
||||
if (target.type !== WI.TargetType.Page)
|
||||
return;
|
||||
|
||||
if (target.isProvisional)
|
||||
return;
|
||||
|
||||
// First page target.
|
||||
if (!WI.pageTarget && !this._seenPageTarget) {
|
||||
this._seenPageTarget = true;
|
||||
this._initializePageTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Transitioning page target.
|
||||
this._transitionPageTarget(target);
|
||||
}
|
||||
|
||||
_checkAndHandlePageTargetTermination(target)
|
||||
{
|
||||
if (target.type !== WI.TargetType.Page)
|
||||
return;
|
||||
|
||||
if (target.isProvisional)
|
||||
return;
|
||||
|
||||
console.assert(target === WI.pageTarget);
|
||||
console.assert(this._seenPageTarget);
|
||||
|
||||
// Terminating the page target.
|
||||
this._terminatePageTarget(target);
|
||||
|
||||
// Ensure we transition in a reasonable amount of time, otherwise close.
|
||||
const timeToTransition = 2000;
|
||||
clearTimeout(this._transitionTimeoutIdentifier);
|
||||
this._transitionTimeoutIdentifier = setTimeout(() => {
|
||||
this._transitionTimeoutIdentifier = undefined;
|
||||
if (WI.pageTarget)
|
||||
return;
|
||||
if (WI.isEngineeringBuild)
|
||||
throw new Error("Error: No new pageTarget some time after last page target was terminated. Failed transition?");
|
||||
WI.close();
|
||||
}, timeToTransition);
|
||||
}
|
||||
|
||||
_initializeBackendTarget(target)
|
||||
{
|
||||
console.assert(!WI.mainTarget);
|
||||
|
||||
WI.backendTarget = target;
|
||||
|
||||
this._resetMainExecutionContext();
|
||||
|
||||
WI._backendTargetAvailablePromise.resolve();
|
||||
}
|
||||
|
||||
_initializePageTarget(target)
|
||||
{
|
||||
console.assert(WI.sharedApp.isWebDebuggable() || WI.sharedApp.debuggableType === WI.DebuggableType.ITML);
|
||||
console.assert(target.type === WI.TargetType.Page || target instanceof WI.DirectBackendTarget);
|
||||
|
||||
WI.pageTarget = target;
|
||||
|
||||
this._resetMainExecutionContext();
|
||||
|
||||
WI._pageTargetAvailablePromise.resolve();
|
||||
}
|
||||
|
||||
_transitionPageTarget(target)
|
||||
{
|
||||
console.assert(!WI.pageTarget);
|
||||
console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage);
|
||||
console.assert(target.type === WI.TargetType.Page);
|
||||
|
||||
WI.pageTarget = target;
|
||||
|
||||
this._resetMainExecutionContext();
|
||||
|
||||
// Actions to transition the page target.
|
||||
WI.notifications.dispatchEventToListeners(WI.Notification.TransitionPageTarget);
|
||||
WI.domManager.transitionPageTarget();
|
||||
WI.networkManager.transitionPageTarget();
|
||||
WI.timelineManager.transitionPageTarget();
|
||||
}
|
||||
|
||||
_terminatePageTarget(target)
|
||||
{
|
||||
console.assert(WI.pageTarget);
|
||||
console.assert(WI.pageTarget === target);
|
||||
console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage);
|
||||
|
||||
// Remove any Worker targets associated with this page.
|
||||
for (let workerTarget of this.workerTargets)
|
||||
WI.workerManager.workerTerminated(workerTarget.identifier);
|
||||
|
||||
WI.pageTarget = null;
|
||||
}
|
||||
|
||||
_resetMainExecutionContext()
|
||||
{
|
||||
if (WI.mainTarget instanceof WI.MultiplexingBackendTarget)
|
||||
return;
|
||||
|
||||
if (WI.mainTarget.executionContext)
|
||||
WI.runtimeManager.activeExecutionContext = WI.mainTarget.executionContext;
|
||||
}
|
||||
};
|
||||
|
||||
WI.TargetManager.Event = {
|
||||
TargetAdded: "target-manager-target-added",
|
||||
TargetRemoved: "target-manager-target-removed",
|
||||
DidCommitProvisionalTarget: "target-manager-provisional-target-committed",
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2014, 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.
|
||||
*/
|
||||
|
||||
WI.TypeTokenAnnotator = class TypeTokenAnnotator extends WI.Annotator
|
||||
{
|
||||
constructor(sourceCodeTextEditor, script)
|
||||
{
|
||||
super(sourceCodeTextEditor);
|
||||
|
||||
this._script = script;
|
||||
this._typeTokenNodes = [];
|
||||
this._typeTokenBookmarks = [];
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
insertAnnotations()
|
||||
{
|
||||
if (!this.isActive())
|
||||
return;
|
||||
|
||||
var scriptSyntaxTree = this._script.scriptSyntaxTree;
|
||||
|
||||
if (!scriptSyntaxTree) {
|
||||
this._script.requestScriptSyntaxTree((syntaxTree) => {
|
||||
// After requesting the tree, we still might get a null tree from a parse error.
|
||||
if (syntaxTree)
|
||||
this.insertAnnotations();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scriptSyntaxTree.parsedSuccessfully)
|
||||
return;
|
||||
|
||||
let {startPosition, endPosition} = this.sourceCodeTextEditor.visibleRangePositions();
|
||||
|
||||
let startTime = Date.now();
|
||||
let allNodesInRange = scriptSyntaxTree.filterByRange(startPosition, endPosition);
|
||||
scriptSyntaxTree.updateTypes(allNodesInRange, (nodesWithUpdatedTypes) => {
|
||||
// Because this is an asynchronous call, we could have been deactivated before the callback function is called.
|
||||
if (!this.isActive())
|
||||
return;
|
||||
|
||||
nodesWithUpdatedTypes.forEach(this._insertTypeToken, this);
|
||||
|
||||
let totalTime = Date.now() - startTime;
|
||||
let timeoutTime = Number.constrain(8 * totalTime, 500, 2000);
|
||||
this._timeoutIdentifier = setTimeout(() => {
|
||||
this._timeoutIdentifier = null;
|
||||
this.insertAnnotations();
|
||||
}, timeoutTime);
|
||||
});
|
||||
}
|
||||
|
||||
clearAnnotations()
|
||||
{
|
||||
this._clearTypeTokens();
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_insertTypeToken(node)
|
||||
{
|
||||
if (node.type === WI.ScriptSyntaxTree.NodeType.Identifier) {
|
||||
if (!node.attachments.__typeToken && node.attachments.types && node.attachments.types.valid)
|
||||
this._insertToken(node, false, WI.TypeTokenView.TitleType.Variable, node.name);
|
||||
|
||||
if (node.attachments.__typeToken)
|
||||
node.attachments.__typeToken.update(node.attachments.types);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.assert(node.type === WI.ScriptSyntaxTree.NodeType.FunctionDeclaration || node.type === WI.ScriptSyntaxTree.NodeType.FunctionExpression || node.type === WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression);
|
||||
|
||||
var functionReturnType = node.attachments.returnTypes;
|
||||
if (!functionReturnType || !functionReturnType.valid)
|
||||
return;
|
||||
|
||||
// If a function does not have an explicit return statement with an argument (i.e, "return x;" instead of "return;")
|
||||
// then don't show a return type unless we think it's a constructor.
|
||||
var scriptSyntaxTree = this._script._scriptSyntaxTree;
|
||||
if (!node.attachments.__typeToken && (scriptSyntaxTree.containsNonEmptyReturnStatement(node.body) || !functionReturnType.typeSet.isContainedIn(WI.TypeSet.TypeBit.Undefined))) {
|
||||
var functionName = node.id ? node.id.name : null;
|
||||
this._insertToken(node, true, WI.TypeTokenView.TitleType.ReturnStatement, functionName);
|
||||
}
|
||||
|
||||
if (node.attachments.__typeToken)
|
||||
node.attachments.__typeToken.update(node.attachments.returnTypes);
|
||||
}
|
||||
|
||||
_insertToken(node, shouldTranslateOffsetToAfterParameterList, typeTokenTitleType, functionOrVariableName)
|
||||
{
|
||||
let tokenPosition = this.sourceCodeTextEditor.originalPositionToCurrentPosition(node.startPosition);
|
||||
let currentOffset = this.sourceCodeTextEditor.currentPositionToCurrentOffset(tokenPosition);
|
||||
let sourceString = this.sourceCodeTextEditor.string;
|
||||
|
||||
if (shouldTranslateOffsetToAfterParameterList) {
|
||||
// Translate the position to the closing parenthesis of the function arguments:
|
||||
// translate from: [type-token] function foo() {} => to: function foo() [type-token] {}
|
||||
currentOffset = this._translateToOffsetAfterFunctionParameterList(node, currentOffset, sourceString);
|
||||
tokenPosition = this.sourceCodeTextEditor.currentOffsetToCurrentPosition(currentOffset);
|
||||
}
|
||||
|
||||
// Note: bookmarks render to the left of the character they're being displayed next to.
|
||||
// This is why right margin checks the current offset. And this is okay to do because JavaScript can't be written right-to-left.
|
||||
var isSpaceRegexp = /\s/;
|
||||
var shouldHaveLeftMargin = currentOffset !== 0 && !isSpaceRegexp.test(sourceString[currentOffset - 1]);
|
||||
var shouldHaveRightMargin = !isSpaceRegexp.test(sourceString[currentOffset]);
|
||||
var typeToken = new WI.TypeTokenView(this, shouldHaveRightMargin, shouldHaveLeftMargin, typeTokenTitleType, functionOrVariableName);
|
||||
var bookmark = this.sourceCodeTextEditor.setInlineWidget(tokenPosition, typeToken.element);
|
||||
node.attachments.__typeToken = typeToken;
|
||||
this._typeTokenNodes.push(node);
|
||||
this._typeTokenBookmarks.push(bookmark);
|
||||
}
|
||||
|
||||
_translateToOffsetAfterFunctionParameterList(node, offset, sourceString)
|
||||
{
|
||||
// The assumption here is that we get the offset starting at the function keyword (or after the get/set keywords).
|
||||
// We will return the offset for the closing parenthesis in the function declaration.
|
||||
// All this code is just a way to find this parenthesis while ignoring comments.
|
||||
|
||||
var isMultiLineComment = false;
|
||||
var isSingleLineComment = false;
|
||||
var shouldIgnore = false;
|
||||
const isArrowFunction = node.type === WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression;
|
||||
|
||||
function isLineTerminator(char)
|
||||
{
|
||||
// Reference EcmaScript 5 grammar for single line comments and line terminators:
|
||||
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
|
||||
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.4
|
||||
return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
|
||||
}
|
||||
|
||||
while (((!isArrowFunction && sourceString[offset] !== ")")
|
||||
|| (isArrowFunction && sourceString[offset] !== ">")
|
||||
|| shouldIgnore)
|
||||
&& offset < sourceString.length) {
|
||||
if (isSingleLineComment && isLineTerminator(sourceString[offset])) {
|
||||
isSingleLineComment = false;
|
||||
shouldIgnore = false;
|
||||
} else if (isMultiLineComment && sourceString[offset] === "*" && sourceString[offset + 1] === "/") {
|
||||
isMultiLineComment = false;
|
||||
shouldIgnore = false;
|
||||
offset++;
|
||||
} else if (!shouldIgnore && sourceString[offset] === "/") {
|
||||
offset++;
|
||||
if (sourceString[offset] === "*")
|
||||
isMultiLineComment = true;
|
||||
else if (sourceString[offset] === "/")
|
||||
isSingleLineComment = true;
|
||||
else
|
||||
throw new Error("Bad parsing. Couldn't parse comment preamble.");
|
||||
shouldIgnore = true;
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
_clearTypeTokens()
|
||||
{
|
||||
this._typeTokenNodes.forEach(function(node) {
|
||||
node.attachments.__typeToken = null;
|
||||
});
|
||||
this._typeTokenBookmarks.forEach(function(bookmark) {
|
||||
bookmark.clear();
|
||||
});
|
||||
|
||||
this._typeTokenNodes = [];
|
||||
this._typeTokenBookmarks = [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 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.
|
||||
*/
|
||||
|
||||
WI.WebInspectorExtensionController = class WebInspectorExtensionController extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._extensionForExtensionIDMap = new Map;
|
||||
this._extensionTabContentViewForExtensionTabIDMap = new Map;
|
||||
this._tabIDsForExtensionIDMap = new Multimap;
|
||||
this._nextExtensionTabID = 1;
|
||||
|
||||
this._extensionTabPositions = null;
|
||||
this._saveTabPositionsDebouncer = null;
|
||||
|
||||
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static get extensionTabPositionsObjectStoreKey()
|
||||
{
|
||||
return "extension-tab-positions";
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get registeredExtensionIDs()
|
||||
{
|
||||
return new Set(this._extensionForExtensionIDMap.keys());
|
||||
}
|
||||
|
||||
registerExtension(extensionID, extensionBundleIdentifier, displayName)
|
||||
{
|
||||
if (this._extensionForExtensionIDMap.has(extensionID)) {
|
||||
WI.reportInternalError("Unable to register extension, it's already registered: " + extensionID);
|
||||
return WI.WebInspectorExtension.ErrorCode.RegistrationFailed;
|
||||
}
|
||||
|
||||
if (!this._extensionForExtensionIDMap.size) {
|
||||
WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this);
|
||||
WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this);
|
||||
WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this);
|
||||
}
|
||||
|
||||
let extension = new WI.WebInspectorExtension(extensionID, extensionBundleIdentifier, displayName);
|
||||
this._extensionForExtensionIDMap.set(extensionID, extension);
|
||||
|
||||
this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionAdded, {extension});
|
||||
}
|
||||
|
||||
unregisterExtension(extensionID)
|
||||
{
|
||||
let extension = this._extensionForExtensionIDMap.take(extensionID);
|
||||
if (!extension) {
|
||||
WI.reportInternalError("Unable to unregister extension with unknown ID: " + extensionID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
if (!this._extensionForExtensionIDMap.size) {
|
||||
WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this);
|
||||
WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this);
|
||||
WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this);
|
||||
}
|
||||
|
||||
let extensionTabIDsToRemove = this._tabIDsForExtensionIDMap.take(extensionID) || [];
|
||||
for (let extensionTabID of extensionTabIDsToRemove) {
|
||||
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
|
||||
|
||||
// Ensure that the iframe is actually detached and does not leak.
|
||||
WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true});
|
||||
tabContentView.dispose();
|
||||
}
|
||||
|
||||
this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionRemoved, {extension});
|
||||
}
|
||||
|
||||
async createTabForExtension(extensionID, tabName, tabIconURL, sourceURL)
|
||||
{
|
||||
let extension = this._extensionForExtensionIDMap.get(extensionID);
|
||||
if (!extension) {
|
||||
WI.reportInternalError("Unable to create tab for extension with unknown ID: " + extensionID + " sourceURL: " + sourceURL);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
let extensionTabID = `WebExtensionTab-${extensionID}-${this._nextExtensionTabID++}`;
|
||||
let tabContentView = new WI.WebInspectorExtensionTabContentView(extension, extensionTabID, tabName, tabIconURL, sourceURL);
|
||||
|
||||
this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
|
||||
this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
|
||||
|
||||
if (!this._extensionTabPositions)
|
||||
await this._loadExtensionTabPositions();
|
||||
|
||||
WI.tabBrowser.addTabForContentView(tabContentView, {
|
||||
suppressAnimations: true,
|
||||
insertionIndex: this._insertionIndexForExtensionTab(tabContentView),
|
||||
});
|
||||
|
||||
// The calling convention is to return an error string or a result object.
|
||||
return {"result": extensionTabID};
|
||||
}
|
||||
|
||||
evaluateScriptForExtension(extensionID, scriptSource, {frameURL, contextSecurityOrigin, useContentScriptContext} = {})
|
||||
{
|
||||
let extension = this._extensionForExtensionIDMap.get(extensionID);
|
||||
if (!extension) {
|
||||
WI.reportInternalError("Unable to evaluate script for extension with unknown ID: " + extensionID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
let frame = this._frameForFrameURL(frameURL);
|
||||
if (!frame) {
|
||||
WI.reportInternalError("evaluateScriptForExtension: No frame matched provided frameURL: " + frameURL);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
if (contextSecurityOrigin) {
|
||||
WI.reportInternalError("evaluateScriptForExtension: the 'contextSecurityOrigin' option is not yet implemented.");
|
||||
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
|
||||
}
|
||||
|
||||
if (useContentScriptContext) {
|
||||
WI.reportInternalError("evaluateScriptForExtension: the 'useContentScriptContext' option is not yet implemented.");
|
||||
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
|
||||
}
|
||||
|
||||
let evaluationContext = frame.pageExecutionContext;
|
||||
if (!evaluationContext) {
|
||||
WI.reportInternalError("evaluateScriptForExtension: No 'pageExecutionContext' was present for frame with URL: " + frame.url);
|
||||
return WI.WebInspectorExtension.ErrorCode.ContextDestroyed;
|
||||
}
|
||||
|
||||
return evaluationContext.target.RuntimeAgent.evaluate.invoke({
|
||||
expression: scriptSource,
|
||||
objectGroup: "extension-evaluation",
|
||||
includeCommandLineAPI: true,
|
||||
returnByValue: true,
|
||||
generatePreview: false,
|
||||
saveResult: false,
|
||||
contextId: evaluationContext.id,
|
||||
}).then((payload) => {
|
||||
let resultOrError = payload.result;
|
||||
let wasThrown = payload.wasThrown;
|
||||
let {type, value} = resultOrError;
|
||||
return wasThrown ? {"error": resultOrError.description} : {"result": value};
|
||||
}).catch((error) => error.description);
|
||||
}
|
||||
|
||||
reloadForExtension(extensionID, {ignoreCache, userAgent, injectedScript} = {})
|
||||
{
|
||||
let extension = this._extensionForExtensionIDMap.get(extensionID);
|
||||
if (!extension) {
|
||||
WI.reportInternalError("Unable to evaluate script for extension with unknown ID: " + extensionID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
// FIXME: <webkit.org/b/222328> Implement `userAgent` and `injectedScript` options for `devtools.inspectedWindow.reload` command
|
||||
if (userAgent) {
|
||||
WI.reportInternalError("reloadForExtension: the 'userAgent' option is not yet implemented.");
|
||||
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
|
||||
}
|
||||
|
||||
if (injectedScript) {
|
||||
WI.reportInternalError("reloadForExtension: the 'injectedScript' option is not yet implemented.");
|
||||
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
|
||||
}
|
||||
|
||||
let target = WI.assumingMainTarget();
|
||||
if (!target.hasCommand("Page.reload"))
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
|
||||
return target.PageAgent.reload.invoke({ignoreCache});
|
||||
}
|
||||
|
||||
navigateTabForExtension(extensionTabID, sourceURL)
|
||||
{
|
||||
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
|
||||
if (!tabContentView) {
|
||||
WI.reportInternalError("Unable to navigate extension tab with unknown extensionTabID: " + extensionTabID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
tabContentView.iframeURL = sourceURL;
|
||||
}
|
||||
|
||||
showExtensionTab(extensionTabID, options = {})
|
||||
{
|
||||
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
|
||||
if (!tabContentView) {
|
||||
WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
tabContentView.visible = true;
|
||||
let success = WI.tabBrowser.showTabForContentView(tabContentView, {
|
||||
...options,
|
||||
insertionIndex: this._insertionIndexForExtensionTab(tabContentView),
|
||||
initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
WI.reportInternalError("Unable to show extension tab with extensionTabID: " + extensionTabID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InternalError;
|
||||
}
|
||||
|
||||
tabContentView.visible = true;
|
||||
|
||||
// Clients expect to be able to use evaluateScriptInExtensionTab() when this method
|
||||
// returns, so wait for the extension tab to finish its loading sequence. Wrap the result.
|
||||
return tabContentView.whenPageAvailable().then((sourceURL) => { return {"result": sourceURL}; });
|
||||
}
|
||||
|
||||
hideExtensionTab(extensionTabID, options = {})
|
||||
{
|
||||
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
|
||||
if (!tabContentView) {
|
||||
WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
tabContentView.visible = false;
|
||||
WI.tabBrowser.closeTabForContentView(tabContentView, options);
|
||||
|
||||
console.assert(!tabContentView.visible);
|
||||
console.assert(!tabContentView.isClosed);
|
||||
}
|
||||
|
||||
addContextMenuItemsForClosedExtensionTabs(contextMenu)
|
||||
{
|
||||
contextMenu.appendSeparator();
|
||||
|
||||
for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
|
||||
// If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem
|
||||
// for the extension tab will not be connected to a parent TabBar.
|
||||
let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar;
|
||||
if (!shouldIncludeTab)
|
||||
continue;
|
||||
|
||||
contextMenu.appendItem(tabContentView.tabInfo().displayName, () => {
|
||||
this.showExtensionTab(tabContentView.extensionTabID);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addContextMenuItemsForAllExtensionTabs(contextMenu)
|
||||
{
|
||||
contextMenu.appendSeparator();
|
||||
|
||||
for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
|
||||
let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar;
|
||||
contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => {
|
||||
if (!checked)
|
||||
this.showExtensionTab(tabContentView.extensionTabID);
|
||||
else
|
||||
this.hideExtensionTab(tabContentView.extensionTabID);
|
||||
}, checked);
|
||||
}
|
||||
}
|
||||
|
||||
activeExtensionTabContentViews()
|
||||
{
|
||||
return Array.from(this._extensionTabContentViewForExtensionTabIDMap.values()).filter((tab) => tab.visible || tab.tabBarItem.parentTabBar);
|
||||
}
|
||||
|
||||
evaluateScriptInExtensionTab(extensionTabID, scriptSource)
|
||||
{
|
||||
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
|
||||
if (!tabContentView) {
|
||||
WI.reportInternalError("Unable to evaluate with unknown extensionTabID: " + extensionTabID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
let iframe = tabContentView.iframeElement;
|
||||
if (!(iframe instanceof HTMLIFrameElement)) {
|
||||
WI.reportInternalError("Unable to evaluate without an <iframe> for extensionTabID: " + extensionTabID);
|
||||
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// If `result` is a promise, then it came from a different frame and `instanceof Promise` won't work.
|
||||
let result = InspectorFrontendHost.evaluateScriptInExtensionTab(iframe, scriptSource);
|
||||
if (result?.then) {
|
||||
result.then((resolvedValue) => resolve({result: resolvedValue}), (errorValue) => reject({error: errorValue}));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve({result});
|
||||
} catch (error) {
|
||||
// Include more context in the stringification of the error.
|
||||
const stackIndent = " ";
|
||||
let stackLines = (error.stack?.split("\n") || []).map((line) => `${stackIndent}${line}`);
|
||||
let formattedMessage = [
|
||||
`Caught Exception: ${error.name}`,
|
||||
`at ${error.sourceURL || "(unknown)"}:${error.line || 0}:${error.column || 0}:`,
|
||||
error.message,
|
||||
"",
|
||||
"Backtrace:",
|
||||
...stackLines,
|
||||
].join("\n");
|
||||
reject({error: formattedMessage});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
async _loadExtensionTabPositions()
|
||||
{
|
||||
let savedTabPositions = await WI.objectStores.general.get(WebInspectorExtensionController.extensionTabPositionsObjectStoreKey);
|
||||
this._extensionTabPositions = savedTabPositions || {};
|
||||
}
|
||||
|
||||
_saveExtensionTabPositions()
|
||||
{
|
||||
if (!this._extensionTabPositions)
|
||||
return;
|
||||
|
||||
this._saveTabPositionsDebouncer ||= new Debouncer(() => {
|
||||
for (let tabBarItem of WI.tabBrowser.tabBar.visibleTabBarItemsFromLeftToRight) {
|
||||
if (!(tabBarItem.representedObject instanceof WI.WebInspectorExtensionTabContentView))
|
||||
continue;
|
||||
|
||||
let {anchorTabType, anchorTabIndex, distanceFromAnchorTab} = this._computeIndicesForExtensionTab(tabBarItem.representedObject, {recomputePositions: true});
|
||||
this._extensionTabPositions[tabBarItem.representedObject.savedTabPositionKey] = {anchorTabType, distanceFromAnchorTab};
|
||||
}
|
||||
|
||||
WI.objectStores.general.put(this._extensionTabPositions, WebInspectorExtensionController.extensionTabPositionsObjectStoreKey);
|
||||
});
|
||||
this._saveTabPositionsDebouncer.delayForTime(5000);
|
||||
}
|
||||
|
||||
_insertionIndexForExtensionTab(tabContentView, options = {})
|
||||
{
|
||||
let {anchorTabType, anchorTabIndex, distanceFromAnchorTab} = this._computeIndicesForExtensionTab(tabContentView, options);
|
||||
return anchorTabIndex + distanceFromAnchorTab + 1;
|
||||
}
|
||||
|
||||
_computeIndicesForExtensionTab(tabContentView, {recomputePositions} = {})
|
||||
{
|
||||
// Note: pinned tabs always appear on the trailing edge, so we can ignore them
|
||||
// for the purposes of computing an `insertionIndex`` for `tabContentView`.
|
||||
let anchorTabIndex = 0;
|
||||
let savedPositions = this._extensionTabPositions[tabContentView.savedTabPositionKey] || {};
|
||||
let anchorTabType = (recomputePositions && savedPositions.anchorTabType) || null;
|
||||
let distanceFromAnchorTab = (recomputePositions && savedPositions.distanceFromAnchorTab) || 0;
|
||||
|
||||
let visibleTabBarItems = WI.tabBrowser.tabBar.visibleTabBarItemsFromLeftToRight;
|
||||
for (let i = 0; i < visibleTabBarItems.length; ++i) {
|
||||
let visibleTab = visibleTabBarItems[i].representedObject;
|
||||
if (!visibleTab)
|
||||
continue;
|
||||
|
||||
if (visibleTab === tabContentView)
|
||||
break;
|
||||
|
||||
if (visibleTab instanceof WI.WebInspectorExtensionTabContentView)
|
||||
continue;
|
||||
|
||||
if (recomputePositions) {
|
||||
anchorTabType = visibleTab.type || null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visibleTab.type !== anchorTabType)
|
||||
continue;
|
||||
|
||||
anchorTabIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the count of extension tabs after the anchor tab to compute the real distanceFromAnchorTab.
|
||||
// Adding `distanceFromAnchorTab` to `anchorTabIndex` should not insert the tab after a different anchor tab.
|
||||
for (let i = 1; i < visibleTabBarItems.length - anchorTabIndex; ++i) {
|
||||
if (visibleTabBarItems[anchorTabIndex + i].representedObject?.constructor?.shouldSaveTab?.()) {
|
||||
distanceFromAnchorTab = Number.constrain(distanceFromAnchorTab, 0, Math.max(0, i - 1));
|
||||
return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
|
||||
}
|
||||
}
|
||||
|
||||
// If the anchor tab is now hidden upon restoring, place the extension at the end.
|
||||
// This could happen if a smaller set of tabs are enabled for the inspection target.
|
||||
anchorTabIndex = visibleTabBarItems.length - 1;
|
||||
return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
|
||||
}
|
||||
|
||||
_frameForFrameURL(frameURL)
|
||||
{
|
||||
if (!frameURL)
|
||||
return WI.networkManager.mainFrame;
|
||||
|
||||
function findFrame(frameURL, adjustKnownFrameURL) {
|
||||
return WI.networkManager.frames.find((knownFrame) => {
|
||||
let knownFrameURL = new URL(knownFrame.url);
|
||||
adjustKnownFrameURL?.(knownFrameURL);
|
||||
return knownFrameURL.toString() === frameURL;
|
||||
});
|
||||
}
|
||||
|
||||
let frame = findFrame(frameURL);
|
||||
if (frame)
|
||||
return frame;
|
||||
|
||||
let frameURLParts = new URL(frameURL);
|
||||
if (frameURLParts.hash.length)
|
||||
return null;
|
||||
|
||||
frame = findFrame(frameURL, (knownFrameURL) => {
|
||||
knownFrameURL.hash = "";
|
||||
});
|
||||
if (frame)
|
||||
return frame;
|
||||
|
||||
if (frameURLParts.search.length)
|
||||
return null;
|
||||
|
||||
return findFrame(frameURL, (knownFrameURL) => {
|
||||
knownFrameURL.hash = "";
|
||||
knownFrameURL.search = "";
|
||||
});
|
||||
}
|
||||
|
||||
_handleMainResourceDidChange(event)
|
||||
{
|
||||
if (!event.target.isMainFrame())
|
||||
return;
|
||||
|
||||
// Don't fire the event unless one or more extensions are registered.
|
||||
if (!this._extensionForExtensionIDMap.size)
|
||||
return;
|
||||
|
||||
InspectorFrontendHost.inspectedPageDidNavigate(WI.networkManager.mainFrame.url);
|
||||
}
|
||||
};
|
||||
|
||||
WI.WebInspectorExtensionController.Event = {
|
||||
ExtensionAdded: "extension-added",
|
||||
ExtensionRemoved: "extension-removed",
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.WorkerManager = class WorkerManager extends WI.Object
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._connections = new Map;
|
||||
}
|
||||
|
||||
// Target
|
||||
|
||||
initializeTarget(target)
|
||||
{
|
||||
if (target.hasDomain("Worker"))
|
||||
target.WorkerAgent.enable();
|
||||
}
|
||||
|
||||
// WorkerObserver
|
||||
|
||||
workerCreated(target, workerId, url, name)
|
||||
{
|
||||
console.assert(target.hasCommand("Worker.sendMessageToWorker"));
|
||||
let connection = new InspectorBackend.WorkerConnection;
|
||||
let workerTarget = new WI.WorkerTarget(target, workerId, url, name, connection);
|
||||
workerTarget.initialize();
|
||||
|
||||
WI.targetManager.addTarget(workerTarget);
|
||||
|
||||
this._connections.set(workerId, connection);
|
||||
|
||||
// Unpause the worker now that we have sent all initialization messages.
|
||||
// Ignore errors if a worker went away quickly.
|
||||
target.WorkerAgent.initialized(workerId).catch(function(){});
|
||||
}
|
||||
|
||||
workerTerminated(workerId)
|
||||
{
|
||||
let connection = this._connections.take(workerId);
|
||||
|
||||
WI.targetManager.removeTarget(connection.target);
|
||||
}
|
||||
|
||||
dispatchMessageFromWorker(workerId, message)
|
||||
{
|
||||
let connection = this._connections.get(workerId);
|
||||
|
||||
console.assert(connection);
|
||||
if (!connection)
|
||||
return;
|
||||
|
||||
connection.dispatch(message);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
body.focus-debug [tabindex="0"] {
|
||||
outline: 1px solid hsla(300, 100%, 50%, 0.4) !important;
|
||||
outline-offset: -2px !important;
|
||||
}
|
||||
|
||||
body.focus-debug *:focus {
|
||||
outline: 2px solid hsl(300, 100%, 50%) !important;
|
||||
outline-offset: -1px !important;
|
||||
}
|
||||
|
||||
.tab-bar > .navigation-bar .inspect-inspector {
|
||||
width: 1em !important;
|
||||
}
|
||||
|
||||
/* These rules are adapted from TabBar.css and are expected to match. */
|
||||
body.window-inactive .tab-bar > .navigation-bar .item.button.text-only {
|
||||
color: hsla(0, 0%, var(--foreground-lightness), 0.4);
|
||||
}
|
||||
.tab-bar > .navigation-bar .item.button.text-only {
|
||||
color: hsla(0, 0%, var(--foreground-lightness), 0.6);
|
||||
}
|
||||
|
||||
.tab-bar > .navigation-bar .item.button.text-only:not(.selected):hover {
|
||||
color: hsla(0, 0%, var(--foreground-lightness), 0.7);
|
||||
}
|
||||
|
||||
.tab-bar > .navigation-bar .item.button.text-only:not(.disabled).selected {
|
||||
color: hsla(0, 0%, var(--foreground-lightness), 0.8);
|
||||
}
|
||||
|
||||
body.window-inactive .tab-bar > .navigation-bar .item.button.text-only:not(.disabled).selected > {
|
||||
color: hsla(0, 0%, var(--foreground-lightness), 0.5);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2015 University of Washington.
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
WI.isEngineeringBuild = true;
|
||||
|
||||
// Disable Pause in Internal Scripts if Show Internal Scripts is dibbled.
|
||||
WI.settings.engineeringShowInternalScripts.addEventListener(WI.Setting.Event.Changed, function(event) {
|
||||
if (!WI.settings.engineeringShowInternalScripts.value)
|
||||
WI.settings.engineeringPauseForInternalScripts.value = false;
|
||||
}, WI.settings.engineeringPauseForInternalScripts);
|
||||
|
||||
// Enable Show Internal Scripts if Pause in Internal Scripts is enabled.
|
||||
WI.settings.engineeringPauseForInternalScripts.addEventListener(WI.Setting.Event.Changed, function(event) {
|
||||
if (WI.settings.engineeringPauseForInternalScripts.value)
|
||||
WI.settings.engineeringShowInternalScripts.value = true;
|
||||
}, WI.settings.engineeringShowInternalScripts);
|
||||
|
||||
WI.showDebugUISetting = new WI.Setting("show-debug-ui", false);
|
||||
|
||||
// This function is invoked after the inspector has loaded and has a backend target.
|
||||
WI.runBootstrapOperations = function() {
|
||||
// Toggle Debug UI setting.
|
||||
new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Option | WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, "D", () => {
|
||||
WI.showDebugUISetting.value = !WI.showDebugUISetting.value;
|
||||
});
|
||||
|
||||
// Reload the Web Inspector.
|
||||
new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Option | WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, "R", () => {
|
||||
InspectorFrontendHost.reopen();
|
||||
});
|
||||
|
||||
// Toggle Inspector Messages Filtering.
|
||||
let ignoreChangesToState = false;
|
||||
const DumpMessagesState = {Off: "off", Filtering: "filtering", Everything: "everything"};
|
||||
const dumpMessagesToolTip = WI.unlocalizedString("Enable dump inspector messages to console.\nShift-click to dump all inspector messages with no filtering.");
|
||||
const dumpMessagesActivatedToolTip = WI.unlocalizedString("Disable dump inspector messages to console");
|
||||
let dumpMessagesTabBarNavigationItem = new WI.ActivateButtonNavigationItem("dump-messages", dumpMessagesToolTip, dumpMessagesActivatedToolTip, "Images/Console.svg");
|
||||
|
||||
function dumpMessagesCurrentState() {
|
||||
if (!InspectorBackend.dumpInspectorProtocolMessages)
|
||||
return DumpMessagesState.Off;
|
||||
if (InspectorBackend.filterMultiplexingBackendInspectorProtocolMessages)
|
||||
return DumpMessagesState.Filtering;
|
||||
return DumpMessagesState.Everything;
|
||||
}
|
||||
|
||||
function applyDumpMessagesState(state) {
|
||||
ignoreChangesToState = true;
|
||||
switch (state) {
|
||||
case DumpMessagesState.Off:
|
||||
InspectorBackend.dumpInspectorProtocolMessages = false;
|
||||
InspectorBackend.filterMultiplexingBackendInspectorProtocolMessages = false;
|
||||
dumpMessagesTabBarNavigationItem.activated = false;
|
||||
dumpMessagesTabBarNavigationItem.element.style.removeProperty("color");
|
||||
break;
|
||||
case DumpMessagesState.Filtering:
|
||||
InspectorBackend.dumpInspectorProtocolMessages = true;
|
||||
InspectorBackend.filterMultiplexingBackendInspectorProtocolMessages = true;
|
||||
dumpMessagesTabBarNavigationItem.activated = true;
|
||||
dumpMessagesTabBarNavigationItem.element.style.removeProperty("color");
|
||||
break;
|
||||
case DumpMessagesState.Everything:
|
||||
InspectorBackend.dumpInspectorProtocolMessages = true;
|
||||
InspectorBackend.filterMultiplexingBackendInspectorProtocolMessages = false;
|
||||
dumpMessagesTabBarNavigationItem.activated = true;
|
||||
dumpMessagesTabBarNavigationItem.element.style.color = "rgb(164, 41, 154)";
|
||||
break;
|
||||
}
|
||||
ignoreChangesToState = false;
|
||||
}
|
||||
|
||||
dumpMessagesTabBarNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, function(event) {
|
||||
let nextState;
|
||||
switch (dumpMessagesCurrentState()) {
|
||||
case DumpMessagesState.Off:
|
||||
nextState = WI.modifierKeys.shiftKey ? DumpMessagesState.Everything : DumpMessagesState.Filtering;
|
||||
break;
|
||||
case DumpMessagesState.Filtering:
|
||||
nextState = WI.modifierKeys.shiftKey ? DumpMessagesState.Everything : DumpMessagesState.Off;
|
||||
break;
|
||||
case DumpMessagesState.Everything:
|
||||
nextState = DumpMessagesState.Off;
|
||||
break;
|
||||
}
|
||||
applyDumpMessagesState(nextState);
|
||||
}, dumpMessagesTabBarNavigationItem);
|
||||
WI.settings.protocolAutoLogMessages.addEventListener(WI.Setting.Event.Changed, function(event) {
|
||||
if (ignoreChangesToState)
|
||||
return;
|
||||
applyDumpMessagesState(dumpMessagesCurrentState());
|
||||
}, dumpMessagesTabBarNavigationItem);
|
||||
applyDumpMessagesState(dumpMessagesCurrentState());
|
||||
|
||||
// Next Level Inspector.
|
||||
let inspectionLevel = InspectorFrontendHost.inspectionLevel;
|
||||
const inspectInspectorToolTip = WI.unlocalizedString("Open Web Inspector [%d]").format(inspectionLevel + 1);
|
||||
let inspectInspectorTabBarNavigationItem = new WI.ButtonNavigationItem("inspect-inspector", inspectInspectorToolTip);
|
||||
inspectInspectorTabBarNavigationItem.element.textContent = inspectionLevel + 1;
|
||||
inspectInspectorTabBarNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, function(event) {
|
||||
InspectorFrontendHost.inspectInspector();
|
||||
}, inspectInspectorTabBarNavigationItem);
|
||||
|
||||
let groupNavigationItem = new WI.GroupNavigationItem([
|
||||
dumpMessagesTabBarNavigationItem,
|
||||
inspectInspectorTabBarNavigationItem,
|
||||
]);
|
||||
WI.tabBar.addNavigationItemAfter(groupNavigationItem);
|
||||
|
||||
function setFocusDebugOutline() {
|
||||
document.body.classList.toggle("focus-debug", WI.settings.debugOutlineFocusedElement.value);
|
||||
}
|
||||
WI.settings.debugOutlineFocusedElement.addEventListener(WI.Setting.Event.Changed, setFocusDebugOutline, WI.settings.debugOutlineFocusedElement);
|
||||
setFocusDebugOutline();
|
||||
|
||||
function updateDebugUI() {
|
||||
groupNavigationItem.hidden = !WI.showDebugUISetting.value;
|
||||
WI.tabBar.needsLayout();
|
||||
}
|
||||
|
||||
function updateMockWebExtensionTab() {
|
||||
let mockData = {
|
||||
extensionID: "1234567890ABCDEF",
|
||||
extensionBundleIdentifier: "org.webkit.WebInspector.MockExtension",
|
||||
displayName: WI.unlocalizedString("Mock Extension"),
|
||||
tabName: WI.unlocalizedString("Mock"),
|
||||
tabIconURL: "Images/Info.svg",
|
||||
sourceURL: "Debug/MockWebExtensionTab.html",
|
||||
};
|
||||
|
||||
// Simulates the steps taken by WebInspectorUIExtensionController to create an extension tab in WebInspectorUI.
|
||||
if (!WI.settings.debugShowMockWebExtensionTab.value) {
|
||||
if (WI.sharedApp.extensionController.registeredExtensionIDs.has(mockData.extensionID))
|
||||
InspectorFrontendAPI.unregisterExtension(mockData.extensionID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let error = InspectorFrontendAPI.registerExtension(mockData.extensionID, mockData.extensionBundleIdentifier, mockData.displayName);
|
||||
if (error) {
|
||||
WI.reportInternalError("Problem creating mock web extension: " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
let result = InspectorFrontendAPI.createTabForExtension(mockData.extensionID, mockData.tabName, mockData.tabIconURL, mockData.sourceURL);
|
||||
if (!result?.extensionTabID) {
|
||||
WI.reportInternalError("Problem creating mock web extension tab: " + result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
WI.settings.debugShowMockWebExtensionTab.addEventListener(WI.Setting.Event.Changed, updateMockWebExtensionTab, WI.settings.debugShowMockWebExtensionTab);
|
||||
updateMockWebExtensionTab();
|
||||
|
||||
WI.showDebugUISetting.addEventListener(WI.Setting.Event.Changed, function(event) {
|
||||
updateDebugUI();
|
||||
}, groupNavigationItem);
|
||||
|
||||
updateDebugUI();
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.CapturingProtocolTracer = class CapturingProtocolTracer extends WI.ProtocolTracer
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this._trace = new WI.ProtocolTrace;
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get trace()
|
||||
{
|
||||
return this._trace;
|
||||
}
|
||||
|
||||
logFrontendException(connection, message, exception)
|
||||
{
|
||||
this._processEntry({type: "exception", message: this._stringifyMessage(message), exception});
|
||||
}
|
||||
|
||||
logFrontendRequest(connection, message)
|
||||
{
|
||||
this._processEntry({type: "request", message: this._stringifyMessage(message)});
|
||||
}
|
||||
|
||||
logDidHandleResponse(connection, message, timings = null)
|
||||
{
|
||||
let entry = {type: "response", message: this._stringifyMessage(message)};
|
||||
if (timings)
|
||||
entry.timings = Object.shallowCopy(timings);
|
||||
|
||||
this._processEntry(entry);
|
||||
}
|
||||
|
||||
logDidHandleEvent(connection, message, timings = null)
|
||||
{
|
||||
let entry = {type: "event", message: this._stringifyMessage(message)};
|
||||
if (timings)
|
||||
entry.timings = Object.shallowCopy(timings);
|
||||
|
||||
this._processEntry(entry);
|
||||
}
|
||||
|
||||
_stringifyMessage(message)
|
||||
{
|
||||
try {
|
||||
return JSON.stringify(message);
|
||||
} catch (e) {
|
||||
console.error("couldn't stringify object:", message, e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
_processEntry(entry)
|
||||
{
|
||||
this._trace.addEntry(entry);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
Object.defineProperty(WI.DOMManager.prototype, "boundNodesDebugDescription", {
|
||||
get() {
|
||||
let debugDescription = "";
|
||||
function appendLine(line) {
|
||||
debugDescription += line + "\n";
|
||||
}
|
||||
|
||||
const singleLevelSpacePrefix = " ";
|
||||
function appendChildren(parent, level) {
|
||||
let spacePrefix = singleLevelSpacePrefix.repeat(level + 1);
|
||||
|
||||
for (let child of parent.children) {
|
||||
appendLine(`${spacePrefix}id(${child.id}) displayName(${child.displayName})`);
|
||||
|
||||
for (let pseudoElement of child.pseudoElements().values())
|
||||
appendLine(`${spacePrefix}${singleLevelSpacePrefix}id(${pseudoElement.id}) displayName(${pseudoElement.displayName})`);
|
||||
|
||||
if (child.children)
|
||||
appendChildren(child, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
let rootNodes = Object.values(this._idToDOMNode).filter((value) => !value.parentNode);
|
||||
for (let rootNode of rootNodes) {
|
||||
appendLine(`<${rootNode === this._document ? "Document" : "Detached"} Root> id(${rootNode.id}) displayName(${rootNode.displayName})`);
|
||||
if (rootNode.children)
|
||||
appendChildren(rootNode, 0);
|
||||
}
|
||||
|
||||
return debugDescription;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
.content-view.debug {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 15px;
|
||||
font-size: 64px;
|
||||
color: var(--text-color-gray-medium);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
WI.DebugContentView = class DebugContentView extends WI.ContentView
|
||||
{
|
||||
constructor(string)
|
||||
{
|
||||
super(new String(string));
|
||||
|
||||
this._string = string;
|
||||
|
||||
this.element.classList.add("debug");
|
||||
this.element.textContent = string;
|
||||
}
|
||||
|
||||
// Protected
|
||||
|
||||
attached()
|
||||
{
|
||||
super.attached();
|
||||
console.debug(`attached: ${this._string}`);
|
||||
}
|
||||
|
||||
detached()
|
||||
{
|
||||
console.debug(`detached: ${this._string}`);
|
||||
super.detached();
|
||||
}
|
||||
|
||||
closed()
|
||||
{
|
||||
console.debug(`closed: ${this._string}`);
|
||||
super.closed();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
window._secretValue = {answer:42};
|
||||
|
||||
window.getUniqueValue = function() {
|
||||
if (!window._cachedUniqueValue)
|
||||
window._cachedUniqueValue = Math.floor(Math.random() * 10e9);
|
||||
|
||||
return window._cachedUniqueValue;
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
document.getElementById("uniqueValueField").innerText = window.getUniqueValue();
|
||||
};
|
||||
</script>
|
||||
<body onload="initialize()">
|
||||
<h1>This is a test extension.</h1>
|
||||
<p>In a normal extension, this area would show the extension's user interface.</p>
|
||||
<p>The unique value for this iframe's execution context is:
|
||||
<span id="uniqueValueField">TBD</span>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
WI.ProtocolTrace = class ProtocolTrace
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this._entries = [];
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
addEntry(entry)
|
||||
{
|
||||
this._entries.push(entry);
|
||||
}
|
||||
|
||||
get saveMode()
|
||||
{
|
||||
return WI.FileUtilities.SaveMode.SingleFile;
|
||||
}
|
||||
|
||||
get saveData()
|
||||
{
|
||||
let now = new Date();
|
||||
let YYYY = now.getFullYear();
|
||||
let MM = now.getMonth() + 1;
|
||||
let DD = now.getDate();
|
||||
let hh = now.getHours();
|
||||
let mm = now.getMinutes();
|
||||
let ss = now.getSeconds();
|
||||
|
||||
// This follows the file name of screen shots on OS X (en-US):
|
||||
// "Protocol Trace 2015-12-31 at 12.43.04.json".
|
||||
// When the Intl API is implemented, we can do a better job.
|
||||
return {
|
||||
content: JSON.stringify(this._entries),
|
||||
suggestedName: WI.unlocalizedString(`Protocol Trace at ${YYYY}-${MM}-${DD} ${hh}.${mm}.${ss}.json`),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2020 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.
|
||||
*/
|
||||
|
||||
.sheet-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: var(--z-index-uncaught-exception-sheet);
|
||||
background-color: hsl(0, 0%, 96%);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet {
|
||||
min-width: 400px;
|
||||
margin-inline: 65px 55px;
|
||||
padding: 50px 0;
|
||||
font-family: -webkit-system-font, sans-serif;
|
||||
font-size: 11pt;
|
||||
color: hsl(0, 0%, 40%);
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet a {
|
||||
text-decoration: underline;
|
||||
color: hsl(240, 55%, 30%);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 97%;
|
||||
}
|
||||
.uncaught-exception-sheet a:hover,
|
||||
.uncaught-exception-sheet a:active {
|
||||
color: hsl(240, 55%, 25%);
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet h1,
|
||||
.uncaught-exception-sheet h2 {
|
||||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet h2 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet h1 > img {
|
||||
position: relative;
|
||||
height: 35px;
|
||||
margin-top: -5px;
|
||||
margin-inline-start: -50px;
|
||||
}
|
||||
|
||||
body[dir=ltr] .uncaught-exception-sheet h1 > img {
|
||||
float: left;
|
||||
}
|
||||
|
||||
body[dir=rtl] .uncaught-exception-sheet h1 > img {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet h2 > img {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
margin-top: 0;
|
||||
margin-inline-start: -45px;
|
||||
}
|
||||
|
||||
body[dir=ltr] .uncaught-exception-sheet h2 > img {
|
||||
float: left;
|
||||
}
|
||||
|
||||
body[dir=rtl] .uncaught-exception-sheet h2 > img {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet dl {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet dt {
|
||||
font-style: italic;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet dd {
|
||||
margin: 10px 0 20px;
|
||||
margin-inline-start: 10px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet ul {
|
||||
margin: 0;
|
||||
margin-inline-start: 2px;
|
||||
padding: 0;
|
||||
font-family: Menlo, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.uncaught-exception-sheet li {
|
||||
margin-bottom: 20px;
|
||||
word-break: break-word;
|
||||
-webkit-user-select: text;
|
||||
white-space: pre;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user