| /* |
| * 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.addMouseDownContextMenuHandlers = function(element, populateContextMenuCallback) |
| { |
| let ignoreMouseDown = false; |
| element.addEventListener("mousedown", (event) => { |
| if (event.button !== 0) |
| return; |
| |
| event.stop(); |
| |
| if (ignoreMouseDown) |
| return; |
| |
| let contextMenu = WI.ContextMenu.createFromEvent(event); |
| contextMenu.addBeforeShowCallback(() => { |
| ignoreMouseDown = false; |
| }); |
| |
| populateContextMenuCallback(contextMenu, event); |
| |
| ignoreMouseDown = !contextMenu.isEmpty(); |
| contextMenu.show(); |
| }); |
| |
| element.addEventListener("contextmenu", (event) => { |
| let contextMenu = WI.ContextMenu.createFromEvent(event); |
| populateContextMenuCallback(contextMenu, event); |
| }); |
| }; |
| |
| WI.appendContextMenuItemsForSourceCode = function(contextMenu, sourceCodeOrLocation) |
| { |
| console.assert(contextMenu instanceof WI.ContextMenu); |
| if (!(contextMenu instanceof WI.ContextMenu)) |
| return; |
| |
| let sourceCode = sourceCodeOrLocation; |
| let location = null; |
| if (sourceCodeOrLocation instanceof WI.SourceCodeLocation) { |
| sourceCode = sourceCodeOrLocation.sourceCode; |
| location = sourceCodeOrLocation; |
| } |
| |
| console.assert(sourceCode instanceof WI.SourceCode); |
| if (!(sourceCode instanceof WI.SourceCode)) |
| return; |
| |
| if (contextMenu.__domBreakpointItemsAdded) |
| return; |
| |
| if (!contextMenu.__localOverrideItemsAdded && WI.NetworkManager.supportsOverridingResponses()) { |
| contextMenu.__localOverrideItemsAdded = true; |
| |
| if (WI.networkManager.canBeOverridden(sourceCode)) { |
| contextMenu.appendSeparator(); |
| |
| if (WI.NetworkManager.supportsOverridingRequests()) { |
| contextMenu.appendItem(WI.UIString("Create Request Local Override"), async () => { |
| let localResourceOverride = await sourceCode.createLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Request); |
| WI.networkManager.addLocalResourceOverride(localResourceOverride); |
| WI.showLocalResourceOverride(localResourceOverride, { |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu, |
| }); |
| }); |
| } |
| |
| contextMenu.appendItem(WI.UIString("Create Response Local Override"), async () => { |
| let localResourceOverride = await sourceCode.createLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Response); |
| WI.networkManager.addLocalResourceOverride(localResourceOverride); |
| WI.showLocalResourceOverride(localResourceOverride, { |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu, |
| }); |
| }); |
| } else { |
| let localResourceOverride = WI.networkManager.localResourceOverridesForURL(sourceCode.url)[0]; |
| if (localResourceOverride) { |
| contextMenu.appendSeparator(); |
| |
| contextMenu.appendItem(WI.UIString("Reveal Local Override"), () => { |
| WI.showLocalResourceOverride(localResourceOverride, { |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu, |
| }); |
| }); |
| |
| contextMenu.appendItem(localResourceOverride.disabled ? WI.UIString("Enable Local Override") : WI.UIString("Disable Local Override"), () => { |
| localResourceOverride.disabled = !localResourceOverride.disabled; |
| }); |
| |
| contextMenu.appendItem(WI.UIString("Delete Local Override"), () => { |
| WI.networkManager.removeLocalResourceOverride(localResourceOverride); |
| }); |
| } |
| } |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| if (location && (sourceCode instanceof WI.Script || (sourceCode instanceof WI.Resource && sourceCode.type === WI.Resource.Type.Script && !sourceCode.localResourceOverride))) { |
| let existingBreakpoint = WI.debuggerManager.breakpointForSourceCodeLocation(location); |
| if (existingBreakpoint) { |
| contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => { |
| WI.debuggerManager.removeBreakpoint(existingBreakpoint); |
| }); |
| } else { |
| contextMenu.appendItem(WI.UIString("Add Breakpoint"), () => { |
| WI.debuggerManager.addBreakpoint(new WI.JavaScriptBreakpoint(location)); |
| }); |
| } |
| } |
| |
| if (sourceCode.supportsScriptBlackboxing) { |
| let blackboxData = WI.debuggerManager.blackboxDataForSourceCode(sourceCode); |
| if (blackboxData && blackboxData.type === WI.DebuggerManager.BlackboxType.Pattern) { |
| contextMenu.appendItem(WI.UIString("Reveal Blackbox Pattern"), () => { |
| WI.showSettingsTab({ |
| blackboxPatternToSelect: blackboxData.regex, |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu, |
| }); |
| }); |
| } else { |
| contextMenu.appendItem(blackboxData ? WI.UIString("Unblackbox Script") : WI.UIString("Blackbox Script"), () => { |
| WI.debuggerManager.setShouldBlackboxScript(sourceCode, !blackboxData); |
| }); |
| } |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| WI.appendContextMenuItemsForURL(contextMenu, sourceCode.url, {sourceCode, location}); |
| |
| if (sourceCode instanceof WI.Resource && !sourceCode.localResourceOverride) { |
| if (sourceCode.urlComponents.scheme !== "data") { |
| contextMenu.appendItem(WI.UIString("Copy as cURL"), () => { |
| InspectorFrontendHost.copyText(sourceCode.generateCURLCommand()); |
| }); |
| |
| contextMenu.appendSeparator(); |
| |
| contextMenu.appendItem(WI.UIString("Copy HTTP Request"), () => { |
| InspectorFrontendHost.copyText(sourceCode.stringifyHTTPRequest()); |
| }); |
| |
| if (sourceCode.hasResponse()) { |
| contextMenu.appendItem(WI.UIString("Copy HTTP Response"), () => { |
| InspectorFrontendHost.copyText(sourceCode.stringifyHTTPResponse()); |
| }); |
| } |
| } |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| contextMenu.appendItem(WI.UIString("Save File"), () => { |
| sourceCode.requestContent().then(() => { |
| let saveData = { |
| url: sourceCode.url, |
| content: sourceCode.content, |
| base64Encoded: sourceCode.base64Encoded, |
| }; |
| |
| if (sourceCode.urlComponents.path === "/") { |
| let extension = WI.fileExtensionForMIMEType(sourceCode.mimeType); |
| if (extension) |
| saveData.suggestedName = `index.${extension}`; |
| } |
| |
| const forceSaveAs = true; |
| WI.FileUtilities.save(saveData, forceSaveAs); |
| }); |
| }); |
| |
| contextMenu.appendSeparator(); |
| }; |
| |
| WI.appendContextMenuItemsForURL = function(contextMenu, url, options = {}) |
| { |
| if (!url) |
| return; |
| |
| function showResourceWithOptions(options) { |
| options.initiatorHint = WI.TabBrowser.TabNavigationInitiator.ContextMenu; |
| if (options.location) |
| WI.showSourceCodeLocation(options.location, options); |
| else if (options.sourceCode) |
| WI.showSourceCode(options.sourceCode, options); |
| else |
| WI.openURL(url, options.frame, options); |
| } |
| |
| if (!url.startsWith("javascript:") && !url.startsWith("data:")) { |
| contextMenu.appendItem(WI.UIString("Open in New Window", "Open in New Window @ Context Menu Item", "Context menu item for opening the target item in a new window."), () => { |
| const frame = null; |
| WI.openURL(url, frame, {alwaysOpenExternally: true}); |
| }); |
| } |
| |
| if (WI.networkManager.resourcesForURL(url).size) { |
| if (!WI.isShowingSourcesTab()) { |
| contextMenu.appendItem(WI.UIString("Reveal in Sources Tab"), () => { |
| showResourceWithOptions({preferredTabType: WI.SourcesTabContentView.Type}); |
| }); |
| } |
| |
| if (!WI.isShowingNetworkTab()) { |
| contextMenu.appendItem(WI.UIString("Reveal in Network Tab"), () => { |
| showResourceWithOptions({preferredTabType: WI.NetworkTabContentView.Type}); |
| }); |
| } |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| contextMenu.appendItem(WI.UIString("Copy Link"), () => { |
| InspectorFrontendHost.copyText(url); |
| }); |
| }; |
| |
| WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {}) |
| { |
| console.assert(contextMenu instanceof WI.ContextMenu); |
| if (!(contextMenu instanceof WI.ContextMenu)) |
| return; |
| |
| console.assert(domNode instanceof WI.DOMNode); |
| if (!(domNode instanceof WI.DOMNode)) |
| return; |
| |
| let copySubMenu = options.copySubMenu || contextMenu.appendSubMenuItem(WI.UIString("Copy")); |
| |
| let isElement = domNode.nodeType() === Node.ELEMENT_NODE; |
| let attached = domNode.attached; |
| |
| if (isElement && attached) { |
| copySubMenu.appendItem(WI.UIString("Selector Path"), () => { |
| let cssPath = WI.cssPath(domNode); |
| InspectorFrontendHost.copyText(cssPath); |
| }); |
| } |
| |
| if (!domNode.isPseudoElement() && attached) { |
| copySubMenu.appendItem(WI.UIString("XPath"), () => { |
| let xpath = WI.xpath(domNode); |
| InspectorFrontendHost.copyText(xpath); |
| }); |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| if (!options.usingLocalDOMNode) { |
| if (domNode.isCustomElement()) { |
| contextMenu.appendItem(WI.UIString("Jump to Definition"), () => { |
| function didGetFunctionDetails(error, response) { |
| if (error) |
| return; |
| |
| let location = response.location; |
| let sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, WI.mainTarget); |
| if (!sourceCode) |
| return; |
| |
| let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0); |
| WI.showSourceCodeLocation(sourceCodeLocation, { |
| ignoreNetworkTab: true, |
| ignoreSearchTab: true, |
| }); |
| } |
| |
| WI.RemoteObject.resolveNode(domNode).then((remoteObject) => { |
| remoteObject.getProperty("constructor", (error, result, wasThrown) => { |
| if (error) |
| return; |
| if (result.type === "function") |
| remoteObject.target.DebuggerAgent.getFunctionDetails(result.objectId, didGetFunctionDetails); |
| result.release(); |
| }); |
| remoteObject.release(); |
| }); |
| }); |
| |
| contextMenu.appendSeparator(); |
| } |
| |
| if (!options.disallowEditing && WI.cssManager.canForcePseudoClasses() && domNode.attached) { |
| contextMenu.appendSeparator(); |
| |
| let pseudoSubMenu = contextMenu.appendSubMenuItem(WI.UIString("Forced Pseudo-Classes", "A context menu item to force (override) a DOM node's pseudo-classes")); |
| |
| let enabledPseudoClasses = domNode.enabledPseudoClasses; |
| WI.CSSManager.ForceablePseudoClasses.forEach((pseudoClass) => { |
| let enabled = enabledPseudoClasses.includes(pseudoClass); |
| pseudoSubMenu.appendCheckboxItem(pseudoClass.capitalize(), () => { |
| domNode.setPseudoClassEnabled(pseudoClass, !enabled); |
| }, enabled); |
| }); |
| } |
| |
| if (WI.domDebuggerManager.supported && isElement && !domNode.isPseudoElement() && attached) { |
| contextMenu.appendSeparator(); |
| |
| WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, domNode, options); |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| let canLogShadowTree = !domNode.isInUserAgentShadowTree() || WI.DOMManager.supportsEditingUserAgentShadowTrees({frontendOnly: true}); |
| if (!options.excludeLogElement && canLogShadowTree && !domNode.destroyed && !domNode.isPseudoElement()) { |
| let label = isElement ? WI.UIString("Log Element", "Log (print) DOM element to Console") : WI.UIString("Log Node", "Log (print) DOM node to Console"); |
| contextMenu.appendItem(label, () => { |
| WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup).then((remoteObject) => { |
| let text = isElement ? WI.UIString("Selected Element", "Selected DOM element") : WI.UIString("Selected Node", "Selected DOM node"); |
| const addSpecialUserLogClass = true; |
| WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass); |
| }); |
| }); |
| } |
| |
| if (!options.excludeRevealElement && InspectorBackend.hasDomain("DOM") && attached) { |
| contextMenu.appendItem(WI.repeatedUIString.revealInDOMTree(), () => { |
| WI.domManager.inspectElement(domNode.id, { |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu, |
| }); |
| }); |
| } |
| |
| if (InspectorBackend.hasDomain("LayerTree") && attached) { |
| contextMenu.appendItem(WI.UIString("Reveal in Layers Tab", "Open Layers tab and select the layer corresponding to this node"), () => { |
| WI.showLayersTab({ |
| nodeToSelect: domNode, |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu, |
| }); |
| }); |
| } |
| |
| if (InspectorBackend.hasCommand("Page.snapshotNode") && attached) { |
| contextMenu.appendItem(WI.UIString("Capture Screenshot", "Capture screenshot of the selected DOM node"), () => { |
| let target = WI.assumingMainTarget(); |
| target.PageAgent.snapshotNode(domNode.id, (error, dataURL) => { |
| if (error) { |
| const target = WI.mainTarget; |
| const source = WI.ConsoleMessage.MessageSource.Other; |
| const level = WI.ConsoleMessage.MessageLevel.Error; |
| let consoleMessage = new WI.ConsoleMessage(target, source, level, error); |
| consoleMessage.shouldRevealConsole = true; |
| |
| WI.consoleLogViewController.appendConsoleMessage(consoleMessage); |
| return; |
| } |
| |
| WI.FileUtilities.save({ |
| content: parseDataURL(dataURL).data, |
| base64Encoded: true, |
| suggestedName: WI.FileUtilities.screenshotString() + ".png", |
| }); |
| }); |
| }); |
| } |
| |
| if (isElement && attached) { |
| contextMenu.appendItem(WI.UIString("Scroll into View", "Scroll selected DOM node into view on the inspected web page"), () => { |
| domNode.scrollIntoView(); |
| }); |
| } |
| |
| contextMenu.appendSeparator(); |
| } |
| }; |
| |
| WI.appendContextMenuItemsForDOMNodeBreakpoints = function(contextMenu, domNode, options = {}) |
| { |
| if (contextMenu.__domBreakpointItemsAdded) |
| return; |
| |
| contextMenu.__domBreakpointItemsAdded = true; |
| |
| let breakpoints = WI.domDebuggerManager.domBreakpointsForNode(domNode); |
| |
| contextMenu.appendSeparator(); |
| |
| let subMenu = contextMenu.appendSubMenuItem(WI.UIString("Break on")); |
| |
| for (let type of Object.values(WI.DOMBreakpoint.Type)) { |
| let label = WI.DOMBreakpoint.displayNameForType(type); |
| let breakpoint = breakpoints.find((breakpoint) => breakpoint.type === type); |
| |
| subMenu.appendCheckboxItem(label, function() { |
| if (breakpoint) |
| WI.domDebuggerManager.removeDOMBreakpoint(breakpoint); |
| else |
| WI.domDebuggerManager.addDOMBreakpoint(new WI.DOMBreakpoint(domNode, type)); |
| }, !!breakpoint); |
| } |
| |
| if (breakpoints.length && !WI.isShowingSourcesTab()) { |
| contextMenu.appendItem(breakpoints.length === 1 ? WI.UIString("Reveal Breakpoint in Sources Tab") : WI.UIString("Reveal Breakpoints in Sources Tab"), () => { |
| WI.showSourcesTab({ |
| representedObjectToSelect: breakpoints.length === 1 ? breakpoints[0] : domNode, |
| }); |
| }); |
| } |
| |
| contextMenu.appendSeparator(); |
| |
| if (breakpoints.length === 1) |
| WI.BreakpointPopover.appendContextMenuItems(contextMenu, breakpoints[0], options.popoverTargetElement); |
| else if (breakpoints.length) { |
| let shouldEnable = breakpoints.some((breakpoint) => breakpoint.disabled); |
| contextMenu.appendItem(shouldEnable ? WI.UIString("Enable Breakpoints") : WI.UIString("Disable Breakpoints"), () => { |
| for (let breakpoint of breakpoints) |
| breakpoint.disabled = !shouldEnable; |
| }); |
| |
| contextMenu.appendItem(WI.UIString("Delete Breakpoints"), () => { |
| for (let breakpoint of breakpoints) |
| WI.domDebuggerManager.removeDOMBreakpoint(breakpoint); |
| }); |
| |
| contextMenu.appendSeparator(); |
| } |
| |
| let subtreeBreakpoints = WI.domDebuggerManager.domBreakpointsInSubtree(domNode); |
| if (subtreeBreakpoints.length) { |
| if (options.revealDescendantBreakpointsMenuItemHandler) |
| contextMenu.appendItem(WI.UIString("Reveal Descendant Breakpoints"), options.revealDescendantBreakpointsMenuItemHandler); |
| |
| let subtreeShouldEnable = subtreeBreakpoints.some((breakpoint) => breakpoint.disabled); |
| contextMenu.appendItem(subtreeShouldEnable ? WI.UIString("Enable Descendant Breakpoints") : WI.UIString("Disable Descendant Breakpoints"), () => { |
| for (let subtreeBreakpoint of subtreeBreakpoints) |
| subtreeBreakpoint.disabled = !subtreeShouldEnable; |
| }); |
| |
| contextMenu.appendItem(WI.UIString("Delete Descendant Breakpoints"), () => { |
| for (let subtreeBreakpoint of subtreeBreakpoints) |
| WI.domDebuggerManager.removeDOMBreakpoint(subtreeBreakpoint); |
| }); |
| |
| contextMenu.appendSeparator(); |
| } |
| }; |