From 740d80ed1b017386a8d285281acfbf6e265051a3 Mon Sep 17 00:00:00 2001 From: "drousso@apple.com" Date: Wed, 4 Sep 2019 02:57:26 +0000 Subject: [PATCH] Web Inspector: provide a way to view XML/HTML/SVG resource responses as a DOM tree https://bugs.webkit.org/show_bug.cgi?id=201046 Reviewed by Joseph Pecoraro. * UserInterface/Views/ResourceClusterContentView.js: (WI.ResourceClusterContentView): (WI.ResourceClusterContentView.prototype.showRequest): (WI.ResourceClusterContentView.prototype.showResponse): (WI.ResourceClusterContentView.prototype.get customRequestDOMContentView): Added. (WI.ResourceClusterContentView.prototype.get customRequestJSONContentView): Added. (WI.ResourceClusterContentView.prototype.get customResponseDOMContentView): Added. (WI.ResourceClusterContentView.prototype.get customResponseJSONContentView): Added. (WI.ResourceClusterContentView.prototype.get customResponseTextContentView): Added. (WI.ResourceClusterContentView.prototype._createPathComponent): Added. (WI.ResourceClusterContentView.prototype._canShowCustomRequestContentView): (WI.ResourceClusterContentView.prototype._canShowCustomResponseContentView): (WI.ResourceClusterContentView.prototype._contentViewForResourceType): (WI.ResourceClusterContentView.prototype._pathComponentForContentView): (WI.ResourceClusterContentView.prototype._identifierForContentView): (WI.ResourceClusterContentView.prototype._showContentViewForIdentifier): (WI.ResourceClusterContentView.prototype._resourceLoadingDidFinish): (WI.ResourceClusterContentView.prototype._canUseDOMContentViewForContent): Added. (WI.ResourceClusterContentView.prototype._normalizeMIMETypeForDOM): Added. (WI.ResourceClusterContentView.prototype._tryEnableCustomRequestContentViews): Added. (WI.ResourceClusterContentView.prototype._tryEnableCustomResponseContentViews): Added. (WI.ResourceClusterContentView.createPathComponent): Deleted. (WI.ResourceClusterContentView.prototype._tryEnableCustomRequestContentView): Deleted. (WI.ResourceClusterContentView.prototype._tryEnableCustomResponseContentView): Deleted. * UserInterface/Base/Main.js: (WI.showResourceRequest): * UserInterface/Views/PathComponentIcons.css: (.object-icon .icon): Added. * UserInterface/Main.html: * UserInterface/Views/SVGImageResourceClusterContentView.js: Removed. Reworked to allow more than one custom request/response content view at the same time. As such, merge the `WI.SVGImageResourceClusterContentView` into this class. * UserInterface/Views/LocalRemoteObjectContentView.js: Added. (WI.LocalRemoteObjectContentView): (WI.LocalRemoteObjectContentView.prototype.get expression): (WI.LocalRemoteObjectContentView.prototype.renderRemoteObject): (WI.LocalRemoteObjectContentView.prototype.initialLayout): (WI.LocalRemoteObjectContentView.prototype.attached): (WI.LocalRemoteObjectContentView.prototype.closed): * UserInterface/Views/LocalRemoteObjectContentView.css: Added. (.content-view.local-remote-object): * UserInterface/Views/LocalDOMContentView.js: Added. (WI.LocalDOMContentView): (WI.LocalDOMContentView.prototype.get expression): (WI.LocalDOMContentView.prototype.renderRemoteObject): * UserInterface/Views/LocalJSONContentView.js: Added. (WI.LocalJSONContentView): (WI.LocalJSONContentView.prototype.get expression): (WI.LocalJSONContentView.prototype.renderRemoteObject): * UserInterface/Views/JSONContentView.js: Removed. * UserInterface/Views/JSONContentView.css: Removed. Rework `WI.JSONContentView` into a more generic set of classes that render an object we send to the inspected page for instrumentation. * UserInterface/Views/DOMTreeOutline.js: (WI.DOMTreeOutline.prototype.populateContextMenu): (WI.DOMTreeOutline.prototype._onmousemove): (WI.DOMTreeOutline.prototype._onmouseout): (WI.DOMTreeOutline.prototype._ondragstart): (WI.DOMTreeOutline.prototype._ondragover): (WI.DOMTreeOutline.prototype._ondragleave): (WI.DOMTreeOutline.prototype._ondragend): (WI.DOMTreeOutline.prototype._hideElements): * UserInterface/Views/DOMTreeElement.js: (WI.DOMTreeElement.prototype.populateDOMNodeContextMenu): * UserInterface/Views/ContextMenuUtilities.js: (WI.appendContextMenuItemsForDOMNode): Ensure that interactions that would modify the DOM tree only happen when editable. If the `WI.DOMTreeOutline` represents a "local" `WI.DOMNode` (one that's been sent to the inspected page for instrumentation, and shouldn't be part of the main #document), don't allow any editing actions to be performed. * UserInterface/Views/TextContentView.js: (WI.TextContentView): Allow a `representedObject` object to be provided and used instead of the given `string`. * Localizations/en.lproj/localizedStrings.js: * UserInterface/Images/Object.svg: Added. git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249451 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- Source/WebInspectorUI/ChangeLog | 88 ++++ .../Localizations/en.lproj/localizedStrings.js | 9 +- Source/WebInspectorUI/UserInterface/Base/Main.js | 2 +- .../WebInspectorUI/UserInterface/Images/Object.svg | 10 + Source/WebInspectorUI/UserInterface/Main.html | 7 +- .../UserInterface/Views/ContextMenuUtilities.js | 176 ++++---- .../UserInterface/Views/DOMTreeElement.js | 24 +- .../UserInterface/Views/DOMTreeOutline.js | 32 ++ .../UserInterface/Views/LocalDOMContentView.js | 61 +++ .../UserInterface/Views/LocalJSONContentView.js | 52 +++ ...ntView.css => LocalRemoteObjectContentView.css} | 2 +- ...tentView.js => LocalRemoteObjectContentView.js} | 33 +- .../UserInterface/Views/PathComponentIcons.css | 4 + .../Views/ResourceClusterContentView.js | 441 ++++++++++++++------- .../Views/SVGImageResourceClusterContentView.js | 172 -------- .../UserInterface/Views/TextContentView.js | 4 +- 16 files changed, 686 insertions(+), 431 deletions(-) create mode 100644 Source/WebInspectorUI/UserInterface/Images/Object.svg create mode 100644 Source/WebInspectorUI/UserInterface/Views/LocalDOMContentView.js create mode 100644 Source/WebInspectorUI/UserInterface/Views/LocalJSONContentView.js rename Source/WebInspectorUI/UserInterface/Views/{JSONContentView.css => LocalRemoteObjectContentView.css} (97%) rename Source/WebInspectorUI/UserInterface/Views/{JSONContentView.js => LocalRemoteObjectContentView.js} (80%) delete mode 100644 Source/WebInspectorUI/UserInterface/Views/SVGImageResourceClusterContentView.js diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog index 661c34e..72a2228 100644 --- a/Source/WebInspectorUI/ChangeLog +++ b/Source/WebInspectorUI/ChangeLog @@ -1,5 +1,93 @@ 2019-09-03 Devin Rousso + Web Inspector: provide a way to view XML/HTML/SVG resource responses as a DOM tree + https://bugs.webkit.org/show_bug.cgi?id=201046 + + + Reviewed by Joseph Pecoraro. + + * UserInterface/Views/ResourceClusterContentView.js: + (WI.ResourceClusterContentView): + (WI.ResourceClusterContentView.prototype.showRequest): + (WI.ResourceClusterContentView.prototype.showResponse): + (WI.ResourceClusterContentView.prototype.get customRequestDOMContentView): Added. + (WI.ResourceClusterContentView.prototype.get customRequestJSONContentView): Added. + (WI.ResourceClusterContentView.prototype.get customResponseDOMContentView): Added. + (WI.ResourceClusterContentView.prototype.get customResponseJSONContentView): Added. + (WI.ResourceClusterContentView.prototype.get customResponseTextContentView): Added. + (WI.ResourceClusterContentView.prototype._createPathComponent): Added. + (WI.ResourceClusterContentView.prototype._canShowCustomRequestContentView): + (WI.ResourceClusterContentView.prototype._canShowCustomResponseContentView): + (WI.ResourceClusterContentView.prototype._contentViewForResourceType): + (WI.ResourceClusterContentView.prototype._pathComponentForContentView): + (WI.ResourceClusterContentView.prototype._identifierForContentView): + (WI.ResourceClusterContentView.prototype._showContentViewForIdentifier): + (WI.ResourceClusterContentView.prototype._resourceLoadingDidFinish): + (WI.ResourceClusterContentView.prototype._canUseDOMContentViewForContent): Added. + (WI.ResourceClusterContentView.prototype._normalizeMIMETypeForDOM): Added. + (WI.ResourceClusterContentView.prototype._tryEnableCustomRequestContentViews): Added. + (WI.ResourceClusterContentView.prototype._tryEnableCustomResponseContentViews): Added. + (WI.ResourceClusterContentView.createPathComponent): Deleted. + (WI.ResourceClusterContentView.prototype._tryEnableCustomRequestContentView): Deleted. + (WI.ResourceClusterContentView.prototype._tryEnableCustomResponseContentView): Deleted. + * UserInterface/Base/Main.js: + (WI.showResourceRequest): + * UserInterface/Views/PathComponentIcons.css: + (.object-icon .icon): Added. + * UserInterface/Main.html: + * UserInterface/Views/SVGImageResourceClusterContentView.js: Removed. + Reworked to allow more than one custom request/response content view at the same time. As + such, merge the `WI.SVGImageResourceClusterContentView` into this class. + + * UserInterface/Views/LocalRemoteObjectContentView.js: Added. + (WI.LocalRemoteObjectContentView): + (WI.LocalRemoteObjectContentView.prototype.get expression): + (WI.LocalRemoteObjectContentView.prototype.renderRemoteObject): + (WI.LocalRemoteObjectContentView.prototype.initialLayout): + (WI.LocalRemoteObjectContentView.prototype.attached): + (WI.LocalRemoteObjectContentView.prototype.closed): + * UserInterface/Views/LocalRemoteObjectContentView.css: Added. + (.content-view.local-remote-object): + * UserInterface/Views/LocalDOMContentView.js: Added. + (WI.LocalDOMContentView): + (WI.LocalDOMContentView.prototype.get expression): + (WI.LocalDOMContentView.prototype.renderRemoteObject): + * UserInterface/Views/LocalJSONContentView.js: Added. + (WI.LocalJSONContentView): + (WI.LocalJSONContentView.prototype.get expression): + (WI.LocalJSONContentView.prototype.renderRemoteObject): + * UserInterface/Views/JSONContentView.js: Removed. + * UserInterface/Views/JSONContentView.css: Removed. + Rework `WI.JSONContentView` into a more generic set of classes that render an object we send + to the inspected page for instrumentation. + + * UserInterface/Views/DOMTreeOutline.js: + (WI.DOMTreeOutline.prototype.populateContextMenu): + (WI.DOMTreeOutline.prototype._onmousemove): + (WI.DOMTreeOutline.prototype._onmouseout): + (WI.DOMTreeOutline.prototype._ondragstart): + (WI.DOMTreeOutline.prototype._ondragover): + (WI.DOMTreeOutline.prototype._ondragleave): + (WI.DOMTreeOutline.prototype._ondragend): + (WI.DOMTreeOutline.prototype._hideElements): + * UserInterface/Views/DOMTreeElement.js: + (WI.DOMTreeElement.prototype.populateDOMNodeContextMenu): + * UserInterface/Views/ContextMenuUtilities.js: + (WI.appendContextMenuItemsForDOMNode): + Ensure that interactions that would modify the DOM tree only happen when editable. If the + `WI.DOMTreeOutline` represents a "local" `WI.DOMNode` (one that's been sent to the inspected + page for instrumentation, and shouldn't be part of the main #document), don't allow any + editing actions to be performed. + + * UserInterface/Views/TextContentView.js: + (WI.TextContentView): + Allow a `representedObject` object to be provided and used instead of the given `string`. + + * Localizations/en.lproj/localizedStrings.js: + * UserInterface/Images/Object.svg: Added. + +2019-09-03 Devin Rousso + Web Inspector: implement blackboxing of script resources https://bugs.webkit.org/show_bug.cgi?id=17240 diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js index e43e2d6..aa527a8 100644 --- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js +++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js @@ -310,8 +310,6 @@ localizedStrings["Cross-Origin Restrictions"] = "Cross-Origin Restrictions"; localizedStrings["Current"] = "Current"; localizedStrings["Current State"] = "Current State"; localizedStrings["Custom"] = "Custom"; -localizedStrings["Custom Request"] = "Custom Request"; -localizedStrings["Custom Response"] = "Custom Response"; localizedStrings["DNS"] = "DNS"; localizedStrings["DOM"] = "DOM"; localizedStrings["DOM Content Loaded \u2014 %s"] = "DOM Content Loaded \u2014 %s"; @@ -876,7 +874,8 @@ localizedStrings["Repeating Linear Gradient"] = "Repeating Linear Gradient"; localizedStrings["Repeating Radial Gradient"] = "Repeating Radial Gradient"; localizedStrings["Request"] = "Request"; localizedStrings["Request & Response"] = "Request & Response"; -localizedStrings["Request (JSON)"] = "Request (JSON)"; +localizedStrings["Request (DOM Tree)"] = "Request (DOM Tree)"; +localizedStrings["Request (Object Tree)"] = "Request (Object Tree)"; localizedStrings["Request Cookies"] = "Request Cookies"; localizedStrings["Request Data"] = "Request Data"; localizedStrings["Request Headers"] = "Request Headers"; @@ -893,7 +892,9 @@ localizedStrings["Resource was loaded with the \u201Cdata\u201D scheme."] = "Res localizedStrings["Resource was served from the cache."] = "Resource was served from the cache."; localizedStrings["Resources"] = "Resources"; localizedStrings["Response"] = "Response"; -localizedStrings["Response (JSON)"] = "Response (JSON)"; +localizedStrings["Response (DOM Tree)"] = "Response (DOM Tree)"; +localizedStrings["Response (Object Tree)"] = "Response (Object Tree)"; +localizedStrings["Response (Text)"] = "Response (Text)"; localizedStrings["Response Cookies"] = "Response Cookies"; localizedStrings["Response Headers"] = "Response Headers"; localizedStrings["Response:"] = "Response:"; diff --git a/Source/WebInspectorUI/UserInterface/Base/Main.js b/Source/WebInspectorUI/UserInterface/Base/Main.js index 9853c0f..f907ea4 100644 --- a/Source/WebInspectorUI/UserInterface/Base/Main.js +++ b/Source/WebInspectorUI/UserInterface/Base/Main.js @@ -1405,7 +1405,7 @@ WI.showOriginalOrFormattedSourceCodeTextRange = function(sourceCodeTextRange, op WI.showResourceRequest = function(resource, options = {}) { - WI.showRepresentedObject(resource, {[WI.ResourceClusterContentView.ContentViewIdentifierCookieKey]: WI.ResourceClusterContentView.CustomRequestIdentifier}, options); + WI.showRepresentedObject(resource, {[WI.ResourceClusterContentView.ContentViewIdentifierCookieKey]: WI.ResourceClusterContentView.Identifier.Request}, options); }; WI.debuggerToggleBreakpoints = function(event) diff --git a/Source/WebInspectorUI/UserInterface/Images/Object.svg b/Source/WebInspectorUI/UserInterface/Images/Object.svg new file mode 100644 index 0000000..867bafd --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Images/Object.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html index d83754c..44f3c78 100644 --- a/Source/WebInspectorUI/UserInterface/Main.html +++ b/Source/WebInspectorUI/UserInterface/Main.html @@ -123,12 +123,12 @@ - + @@ -581,6 +581,7 @@ + @@ -706,7 +707,6 @@ - @@ -715,6 +715,8 @@ + + @@ -809,7 +811,6 @@ - diff --git a/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js b/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js index f8370f9..afbf301 100644 --- a/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js +++ b/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js @@ -204,114 +204,116 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = { contextMenu.appendSeparator(); - 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 (!options.disallowEditing) { + if (domNode.isCustomElement()) { + contextMenu.appendItem(WI.UIString("Jump to Definition"), () => { + function didGetFunctionDetails(error, response) { if (error) return; - if (result.type === "function") - remoteObject.target.DebuggerAgent.getFunctionDetails(result.objectId, didGetFunctionDetails); - result.release(); + + 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(); }); - remoteObject.release(); }); - }); - contextMenu.appendSeparator(); - } + contextMenu.appendSeparator(); + } - if (WI.cssManager.canForcePseudoClasses() && domNode.attached) { - contextMenu.appendSeparator(); + if (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 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); - }); - } + 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(); + if (WI.domDebuggerManager.supported && isElement && !domNode.isPseudoElement() && attached) { + contextMenu.appendSeparator(); - WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, domNode, options); - } + WI.appendContextMenuItemsForDOMNodeBreakpoints(contextMenu, domNode, options); + } - contextMenu.appendSeparator(); + contextMenu.appendSeparator(); - if (!options.excludeLogElement && !domNode.isInUserAgentShadowTree() && !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.excludeLogElement && !domNode.isInUserAgentShadowTree() && !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 && window.DOMAgent && attached) { - contextMenu.appendItem(WI.repeatedUIString.revealInDOMTree(), () => { - WI.domManager.inspectElement(domNode.id); - }); - } + if (!options.excludeRevealElement && window.DOMAgent && attached) { + contextMenu.appendItem(WI.repeatedUIString.revealInDOMTree(), () => { + WI.domManager.inspectElement(domNode.id); + }); + } - if (WI.settings.experimentalEnableLayersTab.value && window.LayerTreeAgent && attached) { - contextMenu.appendItem(WI.UIString("Reveal in Layers Tab", "Open Layers tab and select the layer corresponding to this node"), () => { - WI.showLayersTab({nodeToSelect: domNode}); - }); - } + if (WI.settings.experimentalEnableLayersTab.value && window.LayerTreeAgent && attached) { + contextMenu.appendItem(WI.UIString("Reveal in Layers Tab", "Open Layers tab and select the layer corresponding to this node"), () => { + WI.showLayersTab({nodeToSelect: domNode}); + }); + } - if (window.PageAgent && attached) { - contextMenu.appendItem(WI.UIString("Capture Screenshot", "Capture screenshot of the selected DOM node"), () => { - 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; - } + if (window.PageAgent && attached) { + contextMenu.appendItem(WI.UIString("Capture Screenshot", "Capture screenshot of the selected DOM node"), () => { + 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({ - url: WI.FileUtilities.inspectorURLForFilename(WI.FileUtilities.screenshotString() + ".png"), - content: parseDataURL(dataURL).data, - base64Encoded: true, + WI.FileUtilities.save({ + url: WI.FileUtilities.inspectorURLForFilename(WI.FileUtilities.screenshotString() + ".png"), + content: parseDataURL(dataURL).data, + base64Encoded: true, + }); }); }); - }); - } + } - if (isElement && attached) { - contextMenu.appendItem(WI.UIString("Scroll into View", "Scroll selected DOM node into view on the inspected web page"), () => { - domNode.scrollIntoView(); - }); - } + 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(); + contextMenu.appendSeparator(); + } }; WI.appendContextMenuItemsForDOMNodeBreakpoints = function(contextMenu, domNode, options = {}) diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js index dababa4..40200cc 100644 --- a/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js +++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js @@ -824,7 +824,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement }); } - if (attributeName && isNonShadowEditable) { + if (attributeName) { subMenus.copy.appendItem(WI.UIString("Attribute"), () => { let text = attributeName; let attributeValue = this.representedObject.getAttribute(attributeName); @@ -855,16 +855,18 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement for (let subMenu of Object.values(subMenus)) contextMenu.pushItem(subMenu); - if (this.selected && this.treeOutline && this.treeOutline.selectedTreeElements.length > 1) { - let forceHidden = !this.treeOutline.selectedTreeElements.every((treeElement) => treeElement.isNodeHidden); - let label = forceHidden ? WI.UIString("Hide Elements") : WI.UIString("Show Elements"); - contextMenu.appendItem(label, () => { - this.treeOutline.toggleSelectedElementsVisibility(forceHidden); - }); - } else { - contextMenu.appendItem(WI.UIString("Toggle Visibility"), () => { - this.toggleElementVisibility(); - }); + if (this.treeOutline.editable) { + if (this.selected && this.treeOutline && this.treeOutline.selectedTreeElements.length > 1) { + let forceHidden = !this.treeOutline.selectedTreeElements.every((treeElement) => treeElement.isNodeHidden); + let label = forceHidden ? WI.UIString("Hide Elements") : WI.UIString("Show Elements"); + contextMenu.appendItem(label, () => { + this.treeOutline.toggleSelectedElementsVisibility(forceHidden); + }); + } else if (isEditableNode) { + contextMenu.appendItem(WI.UIString("Toggle Visibility"), () => { + this.toggleElementVisibility(); + }); + } } } diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js index 85481d7..a932c6d 100644 --- a/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js +++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js @@ -58,6 +58,7 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline this._editable = false; this._editing = false; this._visible = false; + this._usingLocalDOMNode = false; this._hideElementsKeyboardShortcut = new WI.KeyboardShortcut(null, "H", this._hideElements.bind(this), this.element); this._hideElementsKeyboardShortcut.implicitlyPreventsDefault = false; @@ -118,6 +119,12 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline return this._isXMLMimeType; } + markAsUsingLocalDOMNode() + { + this._editable = false; + this._usingLocalDOMNode = true; + } + selectedDOMNode() { return this._selectedDOMNode; @@ -287,6 +294,7 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline treeElement.populateDOMNodeContextMenu(contextMenu, subMenus, event, subMenus); let options = { + disallowEditing: !this._editable, excludeRevealElement: this._excludeRevealElementContextMenu, copySubMenu: subMenus.copy, }; @@ -440,6 +448,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _onmousemove(event) { + if (this._usingLocalDOMNode) + return; + let element = this.treeElementFromEvent(event); if (element && this._previousHoveredElement === element) return; @@ -463,6 +474,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _onmouseout(event) { + if (this._usingLocalDOMNode) + return; + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); if (nodeUnderMouse && this.element.contains(nodeUnderMouse)) return; @@ -477,6 +491,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _ondragstart(event) { + if (!this._editable) + return false; + let treeElement = this.treeElementFromEvent(event); if (!treeElement) return false; @@ -500,6 +517,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _ondragover(event) { + if (!this._editable) + return false; + if (event.dataTransfer.types.includes(WI.GeneralStyleDetailsSidebarPanel.ToggledClassesDragType)) { event.preventDefault(); event.dataTransfer.dropEffect = "copy"; @@ -531,6 +551,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _ondragleave(event) { + if (!this._editable) + return false; + this._clearDragOverTreeElementMarker(); event.preventDefault(); return false; @@ -553,6 +576,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _ondrop(event) { + if (!this._editable) + return; + event.preventDefault(); function callback(error, newNodeId) @@ -592,6 +618,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _ondragend(event) { + if (!this._editable) + return; + event.preventDefault(); this._clearDragOverTreeElementMarker(); delete this._nodeBeingDragged; @@ -633,6 +662,9 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline _hideElements(event, keyboardShortcut) { + if (!this._editable) + return; + if (!this.selectedTreeElement || WI.isEditingAnyField()) return; diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalDOMContentView.js b/Source/WebInspectorUI/UserInterface/Views/LocalDOMContentView.js new file mode 100644 index 0000000..eee8dee --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/LocalDOMContentView.js @@ -0,0 +1,61 @@ +/* + * 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.LocalDOMContentView = class LocalDOMContentView extends WI.LocalRemoteObjectContentView +{ + constructor(content, mimeType, representedObject) + { + console.assert(typeof content === "string"); + console.assert(typeof mimeType === "string"); + + super(representedObject); + + this._content = content; + this._mimeType = mimeType; + } + + // Protected + + get expression() + { + return `(new DOMParser).parseFromString(${JSON.stringify(this._content)}, ${JSON.stringify(this._mimeType)})`; + } + + renderRemoteObject(remoteObject) + { + remoteObject.pushNodeToFrontend((nodeId) => { + let domNode = WI.domManager.nodeForId(nodeId); + console.assert(domNode); + + domNode.getSubtree(3, () => { + let treeOutline = new WI.DOMTreeOutline({omitRootDOMNode: true, selectable: false}); + treeOutline.markAsUsingLocalDOMNode(); + treeOutline.setVisible(true); + treeOutline.rootDOMNode = domNode; + this.element.appendChild(treeOutline.element); + }); + }); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalJSONContentView.js b/Source/WebInspectorUI/UserInterface/Views/LocalJSONContentView.js new file mode 100644 index 0000000..7253be7 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/LocalJSONContentView.js @@ -0,0 +1,52 @@ +/* + * 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.LocalJSONContentView = class LocalJSONContentView extends WI.LocalRemoteObjectContentView +{ + constructor(json, representedObject) + { + console.assert(typeof json === "string" && json.isJSON()); + + super(representedObject); + + this._json = json; + } + + // Protected + + get expression() + { + return this._json; + } + + renderRemoteObject(remoteObject) + { + let objectTree = new WI.ObjectTreeView(remoteObject); + objectTree.showOnlyJSON(); + objectTree.expand(); + + this.element.appendChild(objectTree.element); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Views/JSONContentView.css b/Source/WebInspectorUI/UserInterface/Views/LocalRemoteObjectContentView.css similarity index 97% rename from Source/WebInspectorUI/UserInterface/Views/JSONContentView.css rename to Source/WebInspectorUI/UserInterface/Views/LocalRemoteObjectContentView.css index bc14409..909bdd9 100644 --- a/Source/WebInspectorUI/UserInterface/Views/JSONContentView.css +++ b/Source/WebInspectorUI/UserInterface/Views/LocalRemoteObjectContentView.css @@ -23,7 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -.content-view.json { +.content-view.local-remote-object { padding: 10px; overflow: scroll; } diff --git a/Source/WebInspectorUI/UserInterface/Views/JSONContentView.js b/Source/WebInspectorUI/UserInterface/Views/LocalRemoteObjectContentView.js similarity index 80% rename from Source/WebInspectorUI/UserInterface/Views/JSONContentView.js rename to Source/WebInspectorUI/UserInterface/Views/LocalRemoteObjectContentView.js index 4a08ec9..6039b0e 100644 --- a/Source/WebInspectorUI/UserInterface/Views/JSONContentView.js +++ b/Source/WebInspectorUI/UserInterface/Views/LocalRemoteObjectContentView.js @@ -23,49 +23,54 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -WI.JSONContentView = class JSONContentView extends WI.ContentView +WI.LocalRemoteObjectContentView = class LocalRemoteObjectContentView extends WI.ContentView { - constructor(json, representedObject) + constructor(representedObject) { - console.assert(typeof json === "string" && json.isJSON()); - super(representedObject); - this._json = json; this._remoteObject = null; this._spinnerTimeout = undefined; - this.element.classList.add("json"); + this.element.classList.add("local-remote-object"); } // Protected + get expression() + { + throw WI.NotImplementedError.subclassMustOverride(); + } + + renderRemoteObject(remoteObject) + { + throw WI.NotImplementedError.subclassMustOverride(); + } + initialLayout() { super.initialLayout(); - const options = { - expression: "(" + this._json + ")", + let target = WI.assumingMainTarget(); + let options = { + expression: "(" + this.expression + ")", doNotPauseOnExceptionsAndMuteConsole: true, generatePreview: true, }; - RuntimeAgent.evaluate.invoke(options, (error, result, wasThrown) => { + target.RuntimeAgent.evaluate.invoke(options, (error, result, wasThrown) => { console.assert(!error); console.assert(!wasThrown); this._remoteObject = WI.RemoteObject.fromPayload(result); - let objectTree = new WI.ObjectTreeView(this._remoteObject); - objectTree.showOnlyJSON(); - objectTree.expand(); - if (this._spinnerTimeout) { clearTimeout(this._spinnerTimeout); this._spinnerTimeout = undefined; } this.element.removeChildren(); - this.element.appendChild(objectTree.element); + + this.renderRemoteObject(this._remoteObject); }); } diff --git a/Source/WebInspectorUI/UserInterface/Views/PathComponentIcons.css b/Source/WebInspectorUI/UserInterface/Views/PathComponentIcons.css index 12051b3..c74187a 100644 --- a/Source/WebInspectorUI/UserInterface/Views/PathComponentIcons.css +++ b/Source/WebInspectorUI/UserInterface/Views/PathComponentIcons.css @@ -75,6 +75,10 @@ content: url(../Images/DOMNode.svg); } +.object-icon .icon { + content: url(../Images/Object.svg); +} + .function-icon .icon { content: url(../Images/Function.svg); } diff --git a/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js b/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js index 282b945..f0bee23 100644 --- a/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js +++ b/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js @@ -33,102 +33,36 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus this._resource.addEventListener(WI.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); this._resource.addEventListener(WI.Resource.Event.LoadingDidFinish, this._resourceLoadingDidFinish, this); - function createPathComponent(displayName, className, identifier) - { - const textOnly = false; - const showSelectorArrows = true; - let pathComponent = new WI.HierarchicalPathComponent(displayName, className, identifier, textOnly, showSelectorArrows); - pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); - pathComponent.comparisonData = resource; - return pathComponent; - } - - this._requestContentView = null; - this._responseContentView = null; - this._customRequestContentView = null; - this._customRequestContentViewInitializer = null; - this._customResponseContentView = null; - this._customResponseContentViewInitializer = null; - - this._requestPathComponent = createPathComponent.call(this, WI.UIString("Request"), WI.ResourceClusterContentView.RequestIconStyleClassName, WI.ResourceClusterContentView.RequestIdentifier); - this._customRequestPathComponent = createPathComponent.call(this, WI.UIString("Custom Request"), WI.ResourceClusterContentView.RequestIconStyleClassName, WI.ResourceClusterContentView.CustomRequestIdentifier); - this._responsePathComponent = createPathComponent.call(this, WI.UIString("Response"), WI.ResourceClusterContentView.ResponseIconStyleClassName, WI.ResourceClusterContentView.ResponseIdentifier); - this._customResponsePathComponent = createPathComponent.call(this, WI.UIString("Custom Response"), WI.ResourceClusterContentView.ResponseIconStyleClassName, WI.ResourceClusterContentView.CustomResponseIdentifier); + this._responsePathComponent = this._createPathComponent({ + displayName: WI.UIString("Response"), + identifier: ResourceClusterContentView.Identifier.Response, + styleClassNames: ["response-icon"], + }); if (this._canShowRequestContentView()) { - this._requestPathComponent.nextSibling = this._responsePathComponent; - this._responsePathComponent.previousSibling = this._requestPathComponent; - - this._tryEnableCustomRequestContentView(); + this._requestPathComponent = this._createPathComponent({ + displayName: WI.UIString("Request"), + identifier: ResourceClusterContentView.Identifier.Request, + styleClassNames: ["request-icon"], + nextSibling: this._responsePathComponent, + }); + + this._tryEnableCustomRequestContentViews(); } // FIXME: Since a custom response content view may only become available after a response is received // we need to figure out a way to restore / prefer the custom content view. For example if users // always want to prefer the JSON view to the normal Response text view. - this._currentContentViewSetting = new WI.Setting("resource-current-view-" + this._resource.url.hash, WI.ResourceClusterContentView.CustomResponseIdentifier); + this._currentContentViewSetting = new WI.Setting("resource-current-view-" + this._resource.url.hash, ResourceClusterContentView.Identifier.Response); - this._tryEnableCustomResponseContentView(); + this._tryEnableCustomResponseContentViews(); } // Public get resource() { return this._resource; } - get requestContentView() - { - if (!this._canShowRequestContentView()) - return null; - - if (this._requestContentView) - return this._requestContentView; - - this._requestContentView = new WI.TextContentView(this._resource.requestData || "", this._resource.requestDataContentType); - - return this._requestContentView; - } - - get responseContentView() - { - if (this._responseContentView) - return this._responseContentView; - - this._responseContentView = this._contentViewForResourceType(this._resource.type); - if (this._responseContentView) - return this._responseContentView; - - let typeFromMIMEType = WI.Resource.typeFromMIMEType(this._resource.mimeType); - this._responseContentView = this._contentViewForResourceType(typeFromMIMEType); - if (this._responseContentView) - return this._responseContentView; - - if (WI.shouldTreatMIMETypeAsText(this._resource.mimeType)) { - this._responseContentView = new WI.TextResourceContentView(this._resource); - return this._responseContentView; - } - - this._responseContentView = new WI.GenericResourceContentView(this._resource); - return this._responseContentView; - } - - get customRequestContentView() - { - if (!this._customRequestContentView && this._customRequestContentViewInitializer) { - this._customRequestContentView = this._customRequestContentViewInitializer(); - this._customRequestContentViewInitializer = null; - } - return this._customRequestContentView; - } - - get customResponseContentView() - { - if (!this._customResponseContentView && this._customResponseContentViewInitializer) { - this._customResponseContentView = this._customResponseContentViewInitializer(); - this._customResponseContentViewInitializer = null; - } - return this._customResponseContentView; - } - get selectionPathComponents() { let currentContentView = this._contentViewContainer.currentContentView; @@ -171,7 +105,7 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus { this._shownInitialContent = true; - return this._showContentViewForIdentifier(WI.ResourceClusterContentView.CustomRequestIdentifier); + return this._showContentViewForIdentifier(ResourceClusterContentView.Identifier.Request); } showResponse(positionToReveal, textRangeToSelect, forceUnformatted) @@ -184,7 +118,7 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus this._forceUnformatted = forceUnformatted; } - let responseContentView = this._showContentViewForIdentifier(WI.ResourceClusterContentView.ResponseIdentifier); + let responseContentView = this._showContentViewForIdentifier(ResourceClusterContentView.Identifier.Response); if (typeof responseContentView.revealPosition === "function") responseContentView.revealPosition(positionToReveal, textRangeToSelect, forceUnformatted); return responseContentView; @@ -192,6 +126,99 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus // Private + get requestContentView() + { + if (!this._canShowRequestContentView()) + return null; + + if (this._requestContentView) + return this._requestContentView; + + this._requestContentView = new WI.TextContentView(this._resource.requestData || "", this._resource.requestDataContentType); + + return this._requestContentView; + } + + get responseContentView() + { + if (this._responseContentView) + return this._responseContentView; + + this._responseContentView = this._contentViewForResourceType(this._resource.type); + if (this._responseContentView) + return this._responseContentView; + + let typeFromMIMEType = WI.Resource.typeFromMIMEType(this._resource.mimeType); + this._responseContentView = this._contentViewForResourceType(typeFromMIMEType); + if (this._responseContentView) + return this._responseContentView; + + if (WI.shouldTreatMIMETypeAsText(this._resource.mimeType)) { + this._responseContentView = new WI.TextResourceContentView(this._resource); + return this._responseContentView; + } + + this._responseContentView = new WI.GenericResourceContentView(this._resource); + return this._responseContentView; + } + + get customRequestDOMContentView() + { + if (!this._customRequestDOMContentView && this._customRequestDOMContentViewInitializer) + this._customRequestDOMContentView = this._customRequestDOMContentViewInitializer(); + return this._customRequestDOMContentView; + } + + get customRequestJSONContentView() + { + if (!this._customRequestJSONContentView && this._customRequestJSONContentViewInitializer) + this._customRequestJSONContentView = this._customRequestJSONContentViewInitializer(); + return this._customRequestJSONContentView; + } + + get customResponseDOMContentView() + { + if (!this._customResponseDOMContentView && this._customResponseDOMContentViewInitializer) + this._customResponseDOMContentView = this._customResponseDOMContentViewInitializer(); + return this._customResponseDOMContentView; + } + + get customResponseJSONContentView() + { + if (!this._customResponseJSONContentView && this._customResponseJSONContentViewInitializer) + this._customResponseJSONContentView = this._customResponseJSONContentViewInitializer(); + return this._customResponseJSONContentView; + } + + get customResponseTextContentView() + { + if (!this._customResponseTextContentView && this._customResponseTextContentViewInitializer) + this._customResponseTextContentView = this._customResponseTextContentViewInitializer(); + return this._customResponseTextContentView; + } + + _createPathComponent({displayName, styleClassNames, identifier, previousSibling, nextSibling}) + { + const textOnly = false; + const showSelectorArrows = true; + let pathComponent = new WI.HierarchicalPathComponent(displayName, styleClassNames, identifier, textOnly, showSelectorArrows); + pathComponent.comparisonData = this._resource; + + if (previousSibling) { + previousSibling.nextSibling = pathComponent; + pathComponent.previousSibling = previousSibling; + } + + if (nextSibling) { + nextSibling.previousSibling = pathComponent; + pathComponent.nextSibling = nextSibling; + } + + pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); + + return pathComponent; + } + _canShowRequestContentView() { let requestData = this._resource.requestData; @@ -206,12 +233,12 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus _canShowCustomRequestContentView() { - return !!(this._customRequestContentView || this._customRequestContentViewInitializer); + return !!(this._customRequestDOMContentViewInitializer || this._customRequestJSONContentViewInitializer); } _canShowCustomResponseContentView() { - return !!(this._customResponseContentView || this._customResponseContentViewInitializer); + return !!(this._customResponseDOMContentViewInitializer || this._customResponseJSONContentViewInitializer || this._customResponseTextContentViewInitializer); } _contentViewForResourceType(type) @@ -223,8 +250,6 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus return new WI.TextResourceContentView(this._resource); case WI.Resource.Type.Image: - if (WI.fileExtensionForMIMEType(this._resource.mimeType) === "svg") - return new WI.SVGImageResourceClusterContentView(this._resource); return new WI.ImageResourceContentView(this._resource); case WI.Resource.Type.Font: @@ -240,35 +265,61 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus _pathComponentForContentView(contentView) { - console.assert(contentView); - if (!contentView) - return null; - if (contentView === this._requestContentView) + switch (contentView) { + case this._requestContentView: return this._requestPathComponent; - if (contentView === this._responseContentView) + + case this._customRequestDOMContentView: + return this._customRequestDOMPathComponent; + + case this._customRequestJSONContentView: + return this._customRequestJSONPathComponent; + + case this._responseContentView: return this._responsePathComponent; - if (contentView === this._customRequestContentView) - return this._customRequestPathComponent; - if (contentView === this._customResponseContentView) - return this._customResponsePathComponent; - console.error("Unknown contentView."); + + case this._customResponseDOMContentView: + return this._customResponseDOMPathComponent; + + case this._customResponseJSONContentView: + return this._customResponseJSONPathComponent; + + case this._customResponseTextContentView: + return this._customResponseTextPathComponent; + } + + console.error("Unknown contentView", contentView); return null; } _identifierForContentView(contentView) { console.assert(contentView); - if (!contentView) - return null; - if (contentView === this._requestContentView) - return WI.ResourceClusterContentView.RequestIdentifier; - if (contentView === this._responseContentView) - return WI.ResourceClusterContentView.ResponseIdentifier; - if (contentView === this._customRequestContentView) - return WI.ResourceClusterContentView.CustomRequestIdentifier; - if (contentView === this._customResponseContentView) - return WI.ResourceClusterContentView.CustomResponseIdentifier; - console.error("Unknown contentView."); + + switch (contentView) { + case this._requestContentView: + return ResourceClusterContentView.Identifier.Request; + + case this._customRequestDOMContentView: + return ResourceClusterContentView.Identifier.RequestDOM; + + case this._customRequestJSONContentView: + return ResourceClusterContentView.Identifier.RequestJSON; + + case this._responseContentView: + return ResourceClusterContentView.Identifier.Response; + + case this._customResponseDOMContentView: + return ResourceClusterContentView.Identifier.ResponseDOM; + + case this._customResponseJSONContentView: + return ResourceClusterContentView.Identifier.ResponseJSON; + + case this._customResponseTextContentView: + return ResourceClusterContentView.Identifier.ResponseText; + } + + console.error("Unknown contentView", contentView); return null; } @@ -278,22 +329,37 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus // This is expected to fall through all the way to the `default`. switch (identifier) { - case WI.ResourceClusterContentView.CustomRequestIdentifier: - contentViewToShow = this.customRequestContentView; + case ResourceClusterContentView.Identifier.RequestDOM: + contentViewToShow = this.customRequestDOMContentView; + if (contentViewToShow) + break; + // fallthrough + case ResourceClusterContentView.Identifier.RequestJSON: + contentViewToShow = this.customRequestJSONContentView; if (contentViewToShow) break; // fallthrough - case WI.ResourceClusterContentView.RequestIdentifier: + case ResourceClusterContentView.Identifier.Request: contentViewToShow = this.requestContentView; if (contentViewToShow) break; // fallthrough - case WI.ResourceClusterContentView.CustomResponseIdentifier: - contentViewToShow = this.customResponseContentView; + case ResourceClusterContentView.Identifier.ResponseDOM: + contentViewToShow = this.customResponseDOMContentView; if (contentViewToShow) break; // fallthrough - case WI.ResourceClusterContentView.ResponseIdentifier: + case ResourceClusterContentView.Identifier.ResponseJSON: + contentViewToShow = this.customResponseJSONContentView; + if (contentViewToShow) + break; + // fallthrough + case ResourceClusterContentView.Identifier.ResponseText: + contentViewToShow = this.customResponseTextContentView; + if (contentViewToShow) + break; + // fallthrough + case ResourceClusterContentView.Identifier.Response: default: contentViewToShow = this.responseContentView; break; @@ -328,7 +394,7 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus _resourceLoadingDidFinish(event) { - this._tryEnableCustomResponseContentView(); + this._tryEnableCustomResponseContentViews(); if ("_positionToReveal" in this) { if (this._contentViewContainer.currentContentView === this._responseContentView) @@ -345,21 +411,88 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus return typeof content === "string" && content.isJSON((json) => json && (typeof json === "object" || Array.isArray(json))); } - _tryEnableCustomRequestContentView() + _canUseDOMContentViewForContent(content, mimeType) + { + if (typeof content !== "string") + return false; + + switch (mimeType) { + case "text/html": + return true; + + case "text/xml": + case "application/xml": + case "application/xhtml+xml": + case "image/svg+xml": + try { + let dom = (new DOMParser).parseFromString(content, mimeType); + return !dom.querySelector("parsererror"); + } catch { } + return false; + } + + return false; + } + + _normalizeMIMETypeForDOM(mimeType) { - if (!this._canUseJSONContentViewForContent(this._resource.requestData)) + mimeType = parseMIMEType(mimeType).type; + + if (mimeType.endsWith("/html") || mimeType.endsWith("+html")) + return "text/html"; + + if (mimeType.endsWith("/xml") || mimeType.endsWith("+xml")) { + if (mimeType !== "application/xhtml+xml" && mimeType !== "image/svg+xml") + return "application/xml"; + } + + if (mimeType.endsWith("/xhtml") || mimeType.endsWith("+xhtml")) + return "application/xhtml+xml"; + + if (mimeType.endsWith("/svg") || mimeType.endsWith("+svg")) + return "image/svg+xml"; + + return mimeType; + } + + _tryEnableCustomRequestContentViews() + { + let content = this._resource.requestData; + + if (this._canUseJSONContentViewForContent(content)) { + this._customRequestJSONContentViewInitializer = () => new WI.LocalJSONContentView(content, this._resource); + + this._customRequestJSONPathComponent = this._createPathComponent({ + displayName: WI.UIString("Request (Object Tree)"), + styleClassNames: ["object-icon"], + identifier: ResourceClusterContentView.Identifier.RequestJSON, + previousSibling: this._requestPathComponent, + nextSibling: this._responsePathComponent, + }); + + this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); return; + } - this._customRequestContentViewInitializer = () => new WI.JSONContentView(this._resource.requestData, this._resource); + let mimeType = this._normalizeMIMETypeForDOM(this._resource.requestDataContentType); - this._customRequestPathComponent.displayName = WI.UIString("Request (JSON)"); - this._customRequestPathComponent.previousSibling = this._requestPathComponent; - this._customRequestPathComponent.nextSibling = this._responsePathComponent; - this._requestPathComponent.nextSibling = this._customRequestPathComponent; - this._responsePathComponent.previousSibling = this._customRequestPathComponent; + if (this._canUseDOMContentViewForContent(content, mimeType)) { + this._customRequestDOMContentViewInitializer = () => new WI.LocalDOMContentView(content, mimeType, this._resource); + + this._customRequestDOMPathComponent = this._createPathComponent({ + displayName: WI.UIString("Request (DOM Tree)"), + styleClassNames: ["dom-document-icon"], + identifier: ResourceClusterContentView.Identifier.RequestDOM, + previousSibling: this._requestPathComponent, + nextSibling: this._responsePathComponent, + }); + + this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); + return; + } } - _tryEnableCustomResponseContentView() + _tryEnableCustomResponseContentViews() { if (!this._resource.hasResponse()) return; @@ -370,25 +503,61 @@ WI.ResourceClusterContentView = class ResourceClusterContentView extends WI.Clus this._resource.requestContent() .then(({error, content}) => { - if (error || !content || !this._canUseJSONContentViewForContent(content)) + if (error || typeof content !== "string") return; - this._customResponseContentViewInitializer = () => new WI.JSONContentView(content, this._resource); + if (this._canUseJSONContentViewForContent(content)) { + this._customResponseJSONContentViewInitializer = () => new WI.LocalJSONContentView(content, this._resource); - this._customResponsePathComponent.displayName = WI.UIString("Response (JSON)"); - this._customResponsePathComponent.previousSibling = this._responsePathComponent; - this._responsePathComponent.nextSibling = this._customResponsePathComponent; + this._customResponseJSONPathComponent = this._createPathComponent({ + displayName: WI.UIString("Response (Object Tree)"), + styleClassNames: ["object-icon"], + identifier: ResourceClusterContentView.Identifier.ResponseJSON, + previousSibling: this._responsePathComponent, + }); - this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); + this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); + return; + } + + let mimeType = this._normalizeMIMETypeForDOM(this._resource.mimeType); + + if (this._canUseDOMContentViewForContent(content, mimeType)) { + if (mimeType === "image/svg+xml") { + this._customResponseTextContentViewInitializer = () => new WI.TextContentView(content, mimeType, this._resource); + + this._customResponseTextPathComponent = this._createPathComponent({ + displayName: WI.UIString("Response (Text)"), + styleClassNames: ["source-icon"], + identifier: ResourceClusterContentView.Identifier.ResponseText, + previousSibling: this._responsePathComponent, + }); + } + + this._customResponseDOMContentViewInitializer = () => new WI.LocalDOMContentView(content, mimeType, this._resource); + + this._customResponseDOMPathComponent = this._createPathComponent({ + displayName: WI.UIString("Response (DOM Tree)"), + styleClassNames: ["dom-document-icon"], + identifier: ResourceClusterContentView.Identifier.ResponseDOM, + previousSibling: this._customResponseTextPathComponent || this._responsePathComponent, + }); + + this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); + return; + } }); } }; WI.ResourceClusterContentView.ContentViewIdentifierCookieKey = "resource-cluster-content-view-identifier"; -WI.ResourceClusterContentView.RequestIconStyleClassName = "request-icon"; -WI.ResourceClusterContentView.ResponseIconStyleClassName = "response-icon"; -WI.ResourceClusterContentView.RequestIdentifier = "request"; -WI.ResourceClusterContentView.ResponseIdentifier = "response"; -WI.ResourceClusterContentView.CustomRequestIdentifier = "custom-request"; -WI.ResourceClusterContentView.CustomResponseIdentifier = "custom-response"; +WI.ResourceClusterContentView.Identifier = { + Request: "request", + RequestDOM: "request-dom", + RequestJSON: "request-json", + Response: "response", + ResponseDOM: "response-dom", + ResponseJSON: "response-json", + ResponseText: "response-text", +}; diff --git a/Source/WebInspectorUI/UserInterface/Views/SVGImageResourceClusterContentView.js b/Source/WebInspectorUI/UserInterface/Views/SVGImageResourceClusterContentView.js deleted file mode 100644 index 5a566a5..0000000 --- a/Source/WebInspectorUI/UserInterface/Views/SVGImageResourceClusterContentView.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2017 Devin Rousso . 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.SVGImageResourceClusterContentView = class SVGImageResourceClusterContentView extends WI.ClusterContentView -{ - constructor(resource) - { - super(resource); - - this._resource = resource; - - let createPathComponent = (displayName, className, identifier) => { - const textOnly = false; - const showSelectorArrows = true; - let pathComponent = new WI.HierarchicalPathComponent(displayName, className, identifier, textOnly, showSelectorArrows); - pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); - pathComponent.comparisonData = resource; - return pathComponent; - }; - - this._imagePathComponent = createPathComponent(WI.UIString("Image"), "image-icon", WI.SVGImageResourceClusterContentView.Identifier.Image); - this._sourcePathComponent = createPathComponent(WI.UIString("Source"), "source-icon", WI.SVGImageResourceClusterContentView.Identifier.Source); - - this._imagePathComponent.nextSibling = this._sourcePathComponent; - this._sourcePathComponent.previousSibling = this._imagePathComponent; - - this._currentContentViewSetting = new WI.Setting("svg-image-resource-cluster-current-view-" + this._resource.url.hash, WI.SVGImageResourceClusterContentView.Identifier.Image); - } - - // Public - - get resource() { return this._resource; } - - get selectionPathComponents() - { - let currentContentView = this._contentViewContainer.currentContentView; - if (!currentContentView) - return []; - - // Append the current view's path components to the path component representing the current view. - let components = [this._pathComponentForContentView(currentContentView)]; - return components.concat(currentContentView.selectionPathComponents); - } - - // Protected - - shown() - { - super.shown(); - - if (this._shownInitialContent) - return; - - this._showContentViewForIdentifier(this._currentContentViewSetting.value); - } - - closed() - { - super.closed(); - - this._shownInitialContent = false; - } - - saveToCookie(cookie) - { - cookie[WI.SVGImageResourceClusterContentView.ContentViewIdentifierCookieKey] = this._currentContentViewSetting.value; - } - - restoreFromCookie(cookie) - { - let contentView = this._showContentViewForIdentifier(cookie[WI.SVGImageResourceClusterContentView.ContentViewIdentifierCookieKey]); - if (typeof contentView.revealPosition === "function" && "lineNumber" in cookie && "columnNumber" in cookie) - contentView.revealPosition(new WI.SourceCodePosition(cookie.lineNumber, cookie.columnNumber)); - } - - // Private - - _pathComponentForContentView(contentView) - { - console.assert(contentView); - if (!contentView) - return null; - if (contentView instanceof WI.ImageResourceContentView) - return this._imagePathComponent; - if (contentView instanceof WI.TextContentView) - return this._sourcePathComponent; - console.error("Unknown contentView."); - return null; - } - - _identifierForContentView(contentView) - { - console.assert(contentView); - if (!contentView) - return null; - if (contentView instanceof WI.ImageResourceContentView) - return WI.SVGImageResourceClusterContentView.Identifier.Image; - if (contentView instanceof WI.TextContentView) - return WI.SVGImageResourceClusterContentView.Identifier.Source; - console.error("Unknown contentView."); - return null; - } - - _showContentViewForIdentifier(identifier) - { - let contentViewToShow = null; - - switch (identifier) { - case WI.SVGImageResourceClusterContentView.Identifier.Image: - contentViewToShow = new WI.ImageResourceContentView(this._resource); - break; - - case WI.SVGImageResourceClusterContentView.Identifier.Source: - contentViewToShow = new WI.TextContentView("", this._resource.mimeType); - - this._resource.requestContent().then((result) => { - if (typeof result.content === "string") { - contentViewToShow.textEditor.string = result.content; - return; - } - - blobAsText(result.content, (text) => { - contentViewToShow.textEditor.string = text; - }); - }); - break; - - default: - // Default to showing the image. - contentViewToShow = new WI.ImageResourceContentView(this._resource); - break; - } - - this._currentContentViewSetting.value = this._identifierForContentView(contentViewToShow); - - return this.contentViewContainer.showContentView(contentViewToShow); - } - - _pathComponentSelected(event) - { - this._showContentViewForIdentifier(event.data.pathComponent.representedObject); - } -}; - -WI.SVGImageResourceClusterContentView.ContentViewIdentifierCookieKey = "svg-image-resource-cluster-content-view-identifier"; - -WI.SVGImageResourceClusterContentView.Identifier = { - Image: "image", - Source: "source", -}; diff --git a/Source/WebInspectorUI/UserInterface/Views/TextContentView.js b/Source/WebInspectorUI/UserInterface/Views/TextContentView.js index 37bc503..1753219 100644 --- a/Source/WebInspectorUI/UserInterface/Views/TextContentView.js +++ b/Source/WebInspectorUI/UserInterface/Views/TextContentView.js @@ -25,9 +25,9 @@ WI.TextContentView = class TextContentView extends WI.ContentView { - constructor(string, mimeType) + constructor(string, mimeType, representedObject) { - super(string); + super(representedObject || string); this.element.classList.add("text"); -- 1.8.3.1