From 51881bc9e7f10f9167f35c0b630c7617e3b3bd28 Mon Sep 17 00:00:00 2001 From: Oana Pop Rus Date: Thu, 12 Mar 2020 15:25:39 +0200 Subject: [PATCH] Backed out changeset 8c7e3a9b7b6b (bug 1613885) for dt failures in browser_net_content-type.js on a CLOSED TREE --HG-- rename : devtools/client/netmonitor/src/components/previews/HtmlPreview.js => devtools/client/netmonitor/src/components/HtmlPreview.js rename : devtools/client/netmonitor/src/components/request-details/ResponsePanel.js => devtools/client/netmonitor/src/components/ResponsePanel.js --- .../src/assets/styles/NetworkDetailsBar.css | 35 +-- .../components/{previews => }/HtmlPreview.js | 0 .../src/components/PropertiesView.js | 2 +- .../{request-details => }/ResponsePanel.js | 227 +++++++----------- .../netmonitor/src/components/TabboxPanel.js | 2 +- .../netmonitor/src/components/moz.build | 2 + .../src/components/previews/ImagePreview.js | 87 ------- .../src/components/previews/SourcePreview.js | 174 ++++---------- .../src/components/previews/moz.build | 4 +- .../components/request-details/ParamsPanel.js | 9 +- .../request-details/PropertiesView.js | 5 +- .../src/components/request-details/moz.build | 2 - .../test/browser_net_cyrillic-02.js | 13 +- .../netmonitor/test/browser_net_json-b64.js | 67 +++--- .../netmonitor/test/browser_net_json-empty.js | 24 +- .../netmonitor/test/browser_net_json-long.js | 125 ++++------ .../test/browser_net_json-nogrip.js | 20 +- .../netmonitor/test/browser_net_json-null.js | 44 ++-- .../test/browser_net_json_custom_mime.js | 71 +++--- .../test/browser_net_json_text_mime.js | 70 +++--- .../netmonitor/test/browser_net_jsonp.js | 100 +++----- .../test/browser_net_large-response.js | 9 +- .../test/browser_net_propertiesview-copy.js | 10 +- .../test/browser_net_response-ui-limit.js | 11 +- .../browser_net_simple-request-details.js | 8 +- .../test/browser_net_truncate-post-data.js | 2 +- devtools/client/netmonitor/test/head.js | 13 - .../client/shared/components/Accordion.js | 132 ++++------ .../test/chrome/accordion.snapshots.js | 14 +- .../test/chrome/test_accordion.html | 7 +- 30 files changed, 413 insertions(+), 876 deletions(-) rename devtools/client/netmonitor/src/components/{previews => }/HtmlPreview.js (100%) rename devtools/client/netmonitor/src/components/{request-details => }/ResponsePanel.js (58%) delete mode 100644 devtools/client/netmonitor/src/components/previews/ImagePreview.js diff --git a/devtools/client/netmonitor/src/assets/styles/NetworkDetailsBar.css b/devtools/client/netmonitor/src/assets/styles/NetworkDetailsBar.css index c84618a11032..ca1931e93566 100644 --- a/devtools/client/netmonitor/src/assets/styles/NetworkDetailsBar.css +++ b/devtools/client/netmonitor/src/assets/styles/NetworkDetailsBar.css @@ -12,6 +12,9 @@ .network-monitor .panel-container { height: 100%; +} + +.network-monitor .panel-container { display: flex; flex-direction: column; overflow-x: hidden; @@ -80,7 +83,6 @@ right: 0; } - /* Text inputs in tab panels */ .network-monitor .textbox-input { @@ -180,7 +182,7 @@ /* If there is a source editor shows up in the last row of TreeView, * it should occupy the available vertical space. */ -.network-monitor .accordion .editor-row-container, +.network-monitor .tree-container .treeTable .editor-row-container, .network-monitor .tree-container .treeTable tr:last-child td[colspan="2"] { display: block; height: 100%; @@ -188,7 +190,7 @@ overflow-x: auto; } -.network-monitor .accordion .responseTextContainer { +.network-monitor .tree-container .treeTable .editor-row-container .responseTextContainer { overflow-x: auto; width: 100%; height: 100%; @@ -377,33 +379,6 @@ } /* Response tabpanel */ -/* Supports the single expand mode used for the accordion in the response panel */ -.network-monitor #response-panel .panel-container { - overflow-y: hidden; -} -.network-monitor #response-panel .accordion { - position: relative; - overflow: hidden; -} - -.network-monitor #response-panel .accordion-item .accordion-content { - position: absolute; - top: calc(var(--theme-toolbar-height) + 1px); - bottom: 0; - left: 0; - right: 0; - overflow: hidden; -} - - -.network-monitor #response-panel .accordion-item { - position: relative; - height: auto; -} - -.network-monitor #response-panel .accordion-item.accordion-open { - height: 100%; -} .network-monitor .response-image-box { display: flex; diff --git a/devtools/client/netmonitor/src/components/previews/HtmlPreview.js b/devtools/client/netmonitor/src/components/HtmlPreview.js similarity index 100% rename from devtools/client/netmonitor/src/components/previews/HtmlPreview.js rename to devtools/client/netmonitor/src/components/HtmlPreview.js diff --git a/devtools/client/netmonitor/src/components/PropertiesView.js b/devtools/client/netmonitor/src/components/PropertiesView.js index 9603a22a14d7..aaccfd8a30ca 100644 --- a/devtools/client/netmonitor/src/components/PropertiesView.js +++ b/devtools/client/netmonitor/src/components/PropertiesView.js @@ -45,7 +45,7 @@ loader.lazyGetter(this, "SourceEditor", function() { }); loader.lazyGetter(this, "HTMLPreview", function() { return createFactory( - require("devtools/client/netmonitor/src/components/previews/HtmlPreview") + require("devtools/client/netmonitor/src/components/HtmlPreview") ); }); diff --git a/devtools/client/netmonitor/src/components/request-details/ResponsePanel.js b/devtools/client/netmonitor/src/components/ResponsePanel.js similarity index 58% rename from devtools/client/netmonitor/src/components/request-details/ResponsePanel.js rename to devtools/client/netmonitor/src/components/ResponsePanel.js index 188e8db29752..a299c9b81e80 100644 --- a/devtools/client/netmonitor/src/components/request-details/ResponsePanel.js +++ b/devtools/client/netmonitor/src/components/ResponsePanel.js @@ -3,53 +3,43 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; + const { Component, createFactory, } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const { + connect, +} = require("devtools/client/shared/redux/visibility-handler-connect"); const Services = require("Services"); const { L10N } = require("devtools/client/netmonitor/src/utils/l10n"); const { decodeUnicodeBase64, fetchNetworkUpdatePacket, + formDataURI, + getUrlBaseName, isJSON, } = require("devtools/client/netmonitor/src/utils/request-utils"); const { Filters, } = require("devtools/client/netmonitor/src/utils/filter-predicates"); const { - FILTER_SEARCH_DELAY, -} = require("devtools/client/netmonitor/src/constants"); + setTargetSearchResult, +} = require("devtools/client/netmonitor/src/actions/search"); // Components const PropertiesView = createFactory( - require("devtools/client/netmonitor/src/components/request-details/PropertiesView") -); -const ImagePreview = createFactory( - require("devtools/client/netmonitor/src/components/previews/ImagePreview") -); -const SourcePreview = createFactory( - require("devtools/client/netmonitor/src/components/previews/SourcePreview") -); -const HtmlPreview = createFactory( - require("devtools/client/netmonitor/src/components/previews/HtmlPreview") -); -const Accordion = createFactory( - require("devtools/client/shared/components/Accordion") -); -const SearchBox = createFactory( - require("devtools/client/shared/components/SearchBox") + require("devtools/client/netmonitor/src/components/PropertiesView") ); -loader.lazyGetter(this, "MODE", function() { - return require("devtools/client/shared/components/reps/reps").MODE; -}); - -const { div } = dom; +const { div, img } = dom; const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName"); const JSON_FILTER_TEXT = L10N.getStr("jsonFilterText"); +const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name"); +const RESPONSE_IMG_DIMENSIONS = L10N.getStr("netmonitor.response.dimensions"); +const RESPONSE_IMG_MIMETYPE = L10N.getStr("netmonitor.response.mime"); const RESPONSE_PAYLOAD = L10N.getStr("responsePayload"); const RESPONSE_PREVIEW = L10N.getStr("responsePreview"); const RESPONSE_EMPTY_TEXT = L10N.getStr("responseEmptyText"); @@ -67,6 +57,7 @@ class ResponsePanel extends Component { request: PropTypes.object.isRequired, openLink: PropTypes.func, targetSearchResult: PropTypes.object, + resetTargetSearchResult: PropTypes.func, connector: PropTypes.object.isRequired, }; } @@ -75,9 +66,13 @@ class ResponsePanel extends Component { super(props); this.state = { - filterText: "", - currentOpen: undefined, + imageDimensions: { + width: 0, + height: 0, + }, }; + + this.updateImageDimensions = this.updateImageDimensions.bind(this); } componentDidMount() { @@ -109,6 +104,15 @@ class ResponsePanel extends Component { ); } + updateImageDimensions({ target }) { + this.setState({ + imageDimensions: { + width: target.naturalWidth, + height: target.naturalHeight, + }, + }); + } + /** * Handle json, which we tentatively identify by checking the * MIME type for "json" after any word boundary. This works @@ -173,9 +177,8 @@ class ResponsePanel extends Component { } render() { - const { request, targetSearchResult } = this.props; + const { openLink, request, targetSearchResult } = this.props; const { responseContent, url } = request; - const { filterText } = this.state; if ( !responseContent || @@ -188,7 +191,31 @@ class ResponsePanel extends Component { let { encoding, mimeType, text } = responseContent.content; if (mimeType.includes("image/")) { - return ImagePreview({ encoding, mimeType, text, url }); + const { width, height } = this.state.imageDimensions; + + return div( + { className: "panel-container response-image-box devtools-monospace" }, + img({ + className: "response-image", + src: formDataURI(mimeType, encoding, text), + onLoad: this.updateImageDimensions, + }), + div( + { className: "response-summary" }, + div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME), + div({ className: "tabpanel-summary-value" }, getUrlBaseName(url)) + ), + div( + { className: "response-summary" }, + div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_DIMENSIONS), + div({ className: "tabpanel-summary-value" }, `${width} × ${height}`) + ), + div( + { className: "response-summary" }, + div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_MIMETYPE), + div({ className: "tabpanel-summary-value" }, mimeType) + ) + ); } // Decode response if it's coming from JSONView. @@ -199,120 +226,41 @@ class ResponsePanel extends Component { // Display Properties View const { json, jsonpCallback, error } = this.handleJSONResponse(mimeType, text) || {}; - - const items = []; + const object = {}; let sectionName; - const onToggle = (open, item) => { - this.setState({ currentOpen: open ? item : null }); - }; - if (json) { if (jsonpCallback) { sectionName = L10N.getFormatStr("jsonpScopeName", jsonpCallback); } else { sectionName = JSON_SCOPE_NAME; } - - items.push({ - component: PropertiesView, - componentProps: { - object: json, - useQuotes: true, - filterText, - targetSearchResult, - mode: MODE.LONG, - }, - header: sectionName, - id: "jsonpScopeName", - opened: !!targetSearchResult, - shouldOpen: item => { - const { currentOpen } = this.state; - if (typeof currentOpen == "undefined" && item.id === items[0].id) { - // if this the first and panel just displayed, open this item - // by default; - return true; - } else if (!currentOpen) { - if (!targetSearchResult) { - return false; - } - return true; - } - // Open the item is toggled open or there is a serch result to show - if (item.id == currentOpen.id || targetSearchResult) { - return true; - } - return false; - }, - onToggle, - }); + object[sectionName] = json; } - // Display HTML + // Display HTML under Properties View if (Filters.html(this.props.request)) { - items.push({ - component: HtmlPreview, - componentProps: { responseContent }, - header: RESPONSE_PREVIEW, - id: "responsePreview", - opened: false, - shouldOpen: item => { - const { currentOpen } = this.state; - if (typeof currentOpen == "undefined" && item.id === items[0].id) { - // if this the first and panel just displayed, open this item - // by default; - if (targetSearchResult) { - // collapse when we do a search - return false; - } - return true; - } else if (!currentOpen) { - return false; - } - // close this if there is a search result since - // it does not apply search - if (targetSearchResult) { - return false; - } - if (item.id == currentOpen.id) { - return true; - } - return false; - }, - onToggle, - }); + object[RESPONSE_PREVIEW] = { + HTML_PREVIEW: { responseContent }, + }; } - items.push({ - component: SourcePreview, - componentProps: { + let scrollToLine; + let expandedNodes; + + if (targetSearchResult && targetSearchResult.line) { + scrollToLine = targetSearchResult.line; + expandedNodes = new Set(["/" + RESPONSE_PAYLOAD]); + } + + // Others like text/html, text/plain, application/javascript + object[RESPONSE_PAYLOAD] = { + EDITOR_CONFIG: { text, mode: json ? "application/json" : mimeType.replace(/;.+/, ""), - targetSearchResult, - limit: Services.prefs.getIntPref( - "devtools.netmonitor.response.ui.limit" - ), + scrollToLine, }, - header: RESPONSE_PAYLOAD, - id: "paramsPostPayload", - opened: !!targetSearchResult, - shouldOpen: item => { - const { currentOpen } = this.state; - if (typeof currentOpen == "undefined" && item.id === items[0].id) { - return true; - } else if (!currentOpen) { - if (targetSearchResult) { - return true; - } - return false; - } - if (item.id == currentOpen.id || targetSearchResult) { - return true; - } - return false; - }, - onToggle, - }); + }; const classList = ["panel-container"]; if (Filters.html(this.props.request)) { @@ -322,19 +270,22 @@ class ResponsePanel extends Component { return div( { className: classList.join(" ") }, error && div({ className: "response-error-header", title: error }, error), - json && - div( - { className: "devtools-toolbar devtools-input-toolbar" }, - SearchBox({ - delay: FILTER_SEARCH_DELAY, - type: "filter", - onChange: filter => this.setState({ filterText: filter }), - placeholder: JSON_FILTER_TEXT, - }) - ), - Accordion({ items }) + PropertiesView({ + object, + expandedNodes, + useQuotes: true, + filterPlaceHolder: JSON_FILTER_TEXT, + sectionNames: Object.keys(object), + openLink, + targetSearchResult, + }) ); } } -module.exports = ResponsePanel; +module.exports = connect( + null, + dispatch => ({ + resetTargetSearchResult: () => dispatch(setTargetSearchResult(null)), + }) +)(ResponsePanel); diff --git a/devtools/client/netmonitor/src/components/TabboxPanel.js b/devtools/client/netmonitor/src/components/TabboxPanel.js index 3bf87499b3dd..9736e1811396 100644 --- a/devtools/client/netmonitor/src/components/TabboxPanel.js +++ b/devtools/client/netmonitor/src/components/TabboxPanel.js @@ -36,7 +36,7 @@ const CachePanel = createFactory( require("devtools/client/netmonitor/src/components/request-details/CachePanel") ); const ResponsePanel = createFactory( - require("devtools/client/netmonitor/src/components/request-details/ResponsePanel") + require("devtools/client/netmonitor/src/components/ResponsePanel") ); const SecurityPanel = createFactory( require("devtools/client/netmonitor/src/components/request-details/SecurityPanel") diff --git a/devtools/client/netmonitor/src/components/moz.build b/devtools/client/netmonitor/src/components/moz.build index f827860402d5..4ed5cc213b1d 100644 --- a/devtools/client/netmonitor/src/components/moz.build +++ b/devtools/client/netmonitor/src/components/moz.build @@ -16,10 +16,12 @@ DevToolsModules( 'CustomRequestPanel.js', 'DropHarHandler.js', 'HeadersPanel.js', + 'HtmlPreview.js', 'JSONPreview.js', 'MonitorPanel.js', 'NetworkActionBar.js', 'PropertiesView.js', + 'ResponsePanel.js', 'SecurityState.js', 'SourceEditor.js', 'StatisticsPanel.js', diff --git a/devtools/client/netmonitor/src/components/previews/ImagePreview.js b/devtools/client/netmonitor/src/components/previews/ImagePreview.js deleted file mode 100644 index 02153edda6c3..000000000000 --- a/devtools/client/netmonitor/src/components/previews/ImagePreview.js +++ /dev/null @@ -1,87 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const { Component } = require("devtools/client/shared/vendor/react"); -const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { L10N } = require("devtools/client/netmonitor/src/utils/l10n"); - -const { - div, - img, -} = require("devtools/client/shared/vendor/react-dom-factories"); - -const { - formDataURI, - getUrlBaseName, -} = require("devtools/client/netmonitor/src/utils/request-utils"); - -const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name"); -const RESPONSE_IMG_DIMENSIONS = L10N.getStr("netmonitor.response.dimensions"); -const RESPONSE_IMG_MIMETYPE = L10N.getStr("netmonitor.response.mime"); - -class ImagePreview extends Component { - static get propTypes() { - return { - mimeType: PropTypes.string, - encoding: PropTypes.string, - text: PropTypes.string, - url: PropTypes.string, - }; - } - - constructor(props) { - super(props); - - this.state = { - dimensions: { - width: 0, - height: 0, - }, - }; - - this.updateDimensions = this.updateDimensions.bind(this); - } - - updateDimensions({ target }) { - this.setState({ - dimensions: { - width: target.naturalWidth, - height: target.naturalHeight, - }, - }); - } - - render() { - const { mimeType, encoding, text, url } = this.props; - const { width, height } = this.state.dimensions; - - return div( - { className: "panel-container response-image-box devtools-monospace" }, - img({ - className: "response-image", - src: formDataURI(mimeType, encoding, text), - onLoad: this.updateDimensions, - }), - div( - { className: "response-summary" }, - div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME), - div({ className: "tabpanel-summary-value" }, getUrlBaseName(url)) - ), - div( - { className: "response-summary" }, - div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_DIMENSIONS), - div({ className: "tabpanel-summary-value" }, `${width} × ${height}`) - ), - div( - { className: "response-summary" }, - div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_MIMETYPE), - div({ className: "tabpanel-summary-value" }, mimeType) - ) - ); - } -} - -module.exports = ImagePreview; diff --git a/devtools/client/netmonitor/src/components/previews/SourcePreview.js b/devtools/client/netmonitor/src/components/previews/SourcePreview.js index d3df484d6c33..f17c9a7d2078 100644 --- a/devtools/client/netmonitor/src/components/previews/SourcePreview.js +++ b/devtools/client/netmonitor/src/components/previews/SourcePreview.js @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; + const { Component } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const { @@ -13,7 +14,8 @@ const Editor = require("devtools/client/shared/sourceeditor/editor"); const { setTargetSearchResult, } = require("devtools/client/netmonitor/src/actions/search"); -const { div, pre } = dom; +const { div } = dom; + /** * CodeMirror editor as a React component */ @@ -25,58 +27,16 @@ class SourcePreview extends Component { // Source editor content text: PropTypes.string, // Search result text to select - targetSearchResult: PropTypes.object, + targetSearchResult: PropTypes.string, // Reset target search result that has been used for navigation in this panel. // This is done to avoid second navigation the next time. resetTargetSearchResult: PropTypes.func, - // Limit for determine how to render large content - limit: PropTypes.number, }; } componentDidMount() { const { mode, text } = this.props; - if (!this.isOverSizeLimit(text)) { - this.loadEditor(mode, text); - } - } - shouldComponentUpdate(nextProps) { - return ( - nextProps.mode !== this.props.mode || - nextProps.text !== this.props.text || - nextProps.targetSearchResult !== this.props.targetSearchResult - ); - } - - componentDidUpdate(prevProps) { - const { mode, targetSearchResult, text } = this.props; - - if (prevProps.text !== text) { - // if the text size is over the limit we have switched to - // rendering with a PRE, lets clean up any previous editor object. - if (this.isOverSizeLimit(text)) { - this.unloadEditor(); - return; - } - - if (!this.editor || this.editor.isDestroyed()) { - // When updating from a PRE to an editor - this.loadEditor(mode, text); - } else { - // When updating from editor to editor - this.updateEditor(mode, text); - } - } else if (prevProps.targetSearchResult !== targetSearchResult) { - this.findSearchResult(); - } - } - - componentWillUnmount() { - this.unloadEditor(); - } - - loadEditor(mode, text) { this.editor = new Editor({ lineNumbers: true, lineWrapping: false, @@ -102,33 +62,49 @@ class SourcePreview extends Component { }); } - updateEditor(mode, text) { - // Reset the existed 'mode' attribute in order to make setText() process faster - // to prevent drawing unnecessary syntax highlight. - this.editor.setMode(null); - this.editor.setText(text); - - if (this.editorSetModeTimeout) { - clearTimeout(this.editorSetModeTimeout); - } - - // CodeMirror's setMode() (syntax highlight) is the performance bottleneck when - // processing large content, so we enable it asynchronously within the setTimeout - // to avoid UI blocking. (rendering source code -> drawing syntax highlight) - this.editorSetModeTimeout = setTimeout(() => { - this.editorSetModeTimeout = null; - this.editor.setMode(mode); - this.findSearchResult(); - }); + shouldComponentUpdate(nextProps) { + return ( + nextProps.mode !== this.props.mode || + nextProps.text !== this.props.text || + nextProps.targetSearchResult !== this.props.targetSearchResult + ); } - unloadEditor() { + componentDidUpdate(prevProps) { + const { mode, targetSearchResult, text } = this.props; + + // Bail out if the editor has been destroyed in the meantime. + if (this.editor.isDestroyed()) { + return; + } + + if (prevProps.text !== text) { + // Reset the existed 'mode' attribute in order to make setText() process faster + // to prevent drawing unnecessary syntax highlight. + this.editor.setMode(null); + this.editor.setText(text); + + if (this.editorSetModeTimeout) { + clearTimeout(this.editorSetModeTimeout); + } + + // CodeMirror's setMode() (syntax highlight) is the performance bottleneck when + // processing large content, so we enable it asynchronously within the setTimeout + // to avoid UI blocking. (rendering source code -> drawing syntax highlight) + this.editorSetModeTimeout = setTimeout(() => { + this.editorSetModeTimeout = null; + this.editor.setMode(mode); + this.findSearchResult(); + }); + } else if (prevProps.targetSearchResult !== targetSearchResult) { + this.findSearchResult(); + } + } + + componentWillUnmount() { clearTimeout(this.editorTimeout); clearTimeout(this.editorSetModeTimeout); - if (this.editor) { - this.editor.destroy(); - this.editor = null; - } + this.editor.destroy(); } findSearchResult() { @@ -138,71 +114,17 @@ class SourcePreview extends Component { const { line } = targetSearchResult; // scroll the editor to center the line // with the target search result - if (this.editor) { - this.editor.setCursor({ line: line - 1 }, "center"); - } + this.editor.setCursor({ line: line - 1 }, "center"); } resetTargetSearchResult(); } - // Scroll to specified line if the user clicks on search results. - scrollToLine(element) { - const { targetSearchResult, resetTargetSearchResult } = this.props; - - // The following code is responsible for scrolling given line - // to visible view-port. - // It gets the
child element representing the target - // line (by index) and uses `scrollIntoView` API to make sure - // it's visible to the user. - if (element && targetSearchResult && targetSearchResult.line) { - const child = element.children[targetSearchResult.line - 1]; - if (child) { - const range = document.createRange(); - range.selectNode(child); - document.getSelection().addRange(range); - child.scrollIntoView({ block: "center" }); - } - resetTargetSearchResult(); - } - } - - isOverSizeLimit(text) { - const { limit } = this.props; - return text && text.length > limit; - } - - renderPre(text) { - return div( - { className: "responseTextContainer" }, - pre( - { ref: element => this.scrollToLine(element) }, - text.split(/\r\n|\r|\n/).map((line, index) => { - return div({ key: index }, line); - }) - ) - ); - } - - renderEditor() { - return div( - { className: "editor-row-container" }, - div({ - ref: "editorElement", - className: "source-editor-mount devtools-monospace", - }) - ); - } - render() { - const { text } = this.props; - // To prevent performance issues, switch from editor to pre() - // if response size is greater than specified limit. - const isOverSize = this.isOverSizeLimit(text); - return div( - { key: "EDITOR_CONFIG", className: "editor-row-container" }, - isOverSize ? this.renderPre(text) : this.renderEditor() - ); + return div({ + ref: "editorElement", + className: "source-editor-mount devtools-monospace", + }); } } diff --git a/devtools/client/netmonitor/src/components/previews/moz.build b/devtools/client/netmonitor/src/components/previews/moz.build index 7ee7c2b894eb..694e9700e749 100644 --- a/devtools/client/netmonitor/src/components/previews/moz.build +++ b/devtools/client/netmonitor/src/components/previews/moz.build @@ -3,7 +3,5 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( - 'HtmlPreview.js', - 'ImagePreview.js', - 'SourcePreview.js', + 'SourcePreview.js', ) diff --git a/devtools/client/netmonitor/src/components/request-details/ParamsPanel.js b/devtools/client/netmonitor/src/components/request-details/ParamsPanel.js index b007658bcd4b..41e652c2d4bc 100644 --- a/devtools/client/netmonitor/src/components/request-details/ParamsPanel.js +++ b/devtools/client/netmonitor/src/components/request-details/ParamsPanel.js @@ -198,12 +198,13 @@ class ParamsPanel extends Component { } // Request payload section + const limit = Services.prefs.getIntPref( "devtools.netmonitor.requestBodyLimit" ); - // Check if the request post data has been truncated from the backend, - // in which case no parse should be attempted. + // Check if the request post data has been truncated, in which case no parse should + // be attempted. if (postData && limit <= postData.length) { error = REQUEST_TRUNCATED; } @@ -235,10 +236,6 @@ class ParamsPanel extends Component { text: postData, mode: mimeType.replace(/;.+/, ""), targetSearchResult, - // Using the response limit to improve perf when rendering - limit: Services.prefs.getIntPref( - "devtools.netmonitor.response.ui.limit" - ), }, header: PARAMS_POST_PAYLOAD, id: "paramsPostPayload", diff --git a/devtools/client/netmonitor/src/components/request-details/PropertiesView.js b/devtools/client/netmonitor/src/components/request-details/PropertiesView.js index cff4b4897c82..318501426fe0 100644 --- a/devtools/client/netmonitor/src/components/request-details/PropertiesView.js +++ b/devtools/client/netmonitor/src/components/request-details/PropertiesView.js @@ -50,7 +50,7 @@ const { div } = dom; class PropertiesView extends Component { static get propTypes() { return { - object: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + object: PropTypes.object, provider: PropTypes.object, enableInput: PropTypes.bool, expandableStrings: PropTypes.bool, @@ -59,7 +59,6 @@ class PropertiesView extends Component { cropLimit: PropTypes.number, targetSearchResult: PropTypes.object, resetTargetSearchResult: PropTypes.func, - mode: PropTypes.symbol, }; } @@ -170,7 +169,7 @@ class PropertiesView extends Component { // FIXME: A workaround for the issue in StringRep // Force StringRep to crop the text every time member: Object.assign({}, member, { open: false }), - mode: this.props.mode || MODE.TINY, + mode: MODE.TINY, cropLimit: this.props.cropLimit, noGrip: true, }) diff --git a/devtools/client/netmonitor/src/components/request-details/moz.build b/devtools/client/netmonitor/src/components/request-details/moz.build index 7132b079ce1e..62244eb0aa02 100644 --- a/devtools/client/netmonitor/src/components/request-details/moz.build +++ b/devtools/client/netmonitor/src/components/request-details/moz.build @@ -8,9 +8,7 @@ DevToolsModules( 'NetworkDetailsBar.js', 'ParamsPanel.js', 'PropertiesView.js', - 'ResponsePanel.js', 'SecurityPanel.js', 'StackTracePanel.js', 'TimingsPanel.js' - ) diff --git a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js index 69da66ad0a01..21530140228b 100644 --- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js +++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js @@ -13,13 +13,10 @@ add_task(async function() { info("Starting test... "); const { document, store, windowRequire } = monitor.panelWin; - const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); const { getDisplayedRequests, getSortedRequests } = windowRequire( "devtools/client/netmonitor/src/selectors/index" ); - store.dispatch(Actions.batchEnable(false)); - let wait = waitForNetworkEvents(monitor, 1); tab.linkedBrowser.reload(); await wait; @@ -49,21 +46,13 @@ add_task(async function() { document.querySelectorAll(".request-list-item")[0] ); await wait; - - wait = waitForDOM(document, "#response-panel .accordion-item", 2); + wait = waitForDOM(document, "#response-panel .CodeMirror-code"); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); await wait; - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - const header = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(header, monitor); - await wait; - // CodeMirror will only load lines currently in view to the DOM. getValue() // retrieves all lines pending render after a user begins scrolling. const text = document.querySelector(".CodeMirror").CodeMirror.getValue(); diff --git a/devtools/client/netmonitor/test/browser_net_json-b64.js b/devtools/client/netmonitor/test/browser_net_json-b64.js index 118264ffc7a7..5b9a8a02c93a 100644 --- a/devtools/client/netmonitor/test/browser_net_json-b64.js +++ b/devtools/client/netmonitor/test/browser_net_json-b64.js @@ -20,58 +20,22 @@ add_task(async function() { // Execute requests. await performRequests(monitor, tab, 1); - let wait = waitForDOM(document, "#response-panel .accordion-item", 2); - const waitForPropsView = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - + const wait = waitForDOM(document, "#response-panel .CodeMirror-code"); store.dispatch(Actions.toggleNetworkDetails()); - EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - - await Promise.all([wait, waitForPropsView]); + await wait; const tabpanel = document.querySelector("#response-panel"); - is( - tabpanel.querySelectorAll(".treeRow").length, - 1, - "There should be 1 json properties displayed in this tabpanel." - ); - - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); - - is( - labels[0].textContent, - "greeting", - "The first json property name was incorrect." - ); - is( - values[0].textContent, - `"This is a base 64 string."`, - "The first json property value was incorrect." - ); - - // Open the response payload section, it should hide the json section - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - const header = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(header, monitor); - await wait; is( tabpanel.querySelector(".response-error-header") === null, true, "The response error header doesn't have the intended visibility." ); - const jsonView = - tabpanel.querySelector(".accordion-item .accordion-header-label") || {}; + const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {}; is( jsonView.textContent === L10N.getStr("jsonScopeName"), true, @@ -89,15 +53,38 @@ add_task(async function() { ); is( - tabpanel.querySelectorAll(".accordion-item").length, + tabpanel.querySelectorAll(".tree-section").length, 2, "There should be 2 tree sections displayed in this tabpanel." ); + is( + tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, + 1, + "There should be 1 json properties displayed in this tabpanel." + ); is( tabpanel.querySelectorAll(".empty-notice").length, 0, "The empty notice should not be displayed in this tabpanel." ); + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); + + is( + labels[0].textContent, + "greeting", + "The first json property name was incorrect." + ); + is( + values[0].textContent, + `"This is a base 64 string."`, + "The first json property value was incorrect." + ); + await teardown(monitor); }); diff --git a/devtools/client/netmonitor/test/browser_net_json-empty.js b/devtools/client/netmonitor/test/browser_net_json-empty.js index c90e840b97d8..58e387e97305 100644 --- a/devtools/client/netmonitor/test/browser_net_json-empty.js +++ b/devtools/client/netmonitor/test/browser_net_json-empty.js @@ -22,35 +22,20 @@ add_task(async function() { const onResponsePanelReady = waitForDOM( document, - "#response-panel .accordion-item", - 2 + "#response-panel .CodeMirror-code" ); - store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - await onResponsePanelReady; - const codeMirrorReady = waitForDOM( - document, - "#response-panel .CodeMirror-code" - ); - - const header = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(header, monitor); - - await codeMirrorReady; - const tabpanel = document.querySelector("#response-panel"); is( - tabpanel.querySelectorAll(".accordion-item").length, + tabpanel.querySelectorAll(".tree-section").length, 2, - "There should be 2 accordion items displayed in this tabpanel." + "There should be 2 tree sections displayed in this tabpanel." ); is( tabpanel.querySelectorAll(".empty-notice").length, @@ -74,8 +59,7 @@ add_task(async function() { "The response image box doesn't have the intended visibility." ); - const jsonView = - tabpanel.querySelector(".accordion-item .accordion-header-label") || {}; + const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {}; is( jsonView.textContent === L10N.getStr("jsonScopeName"), true, diff --git a/devtools/client/netmonitor/test/browser_net_json-long.js b/devtools/client/netmonitor/test/browser_net_json-long.js index 9fb548f7865e..342038ca2afc 100644 --- a/devtools/client/netmonitor/test/browser_net_json-long.js +++ b/devtools/client/netmonitor/test/browser_net_json-long.js @@ -53,45 +53,76 @@ add_task(async function() { } ); - let wait = waitForDOM(document, "#response-panel .accordion-item", 2); - const waitForPropsView = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - + const wait = waitForDOM(document, "#response-panel .CodeMirror-code"); store.dispatch(Actions.toggleNetworkDetails()); - EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - - await Promise.all([wait, waitForPropsView]); - - testJsonAccordionInResposeTab(); - - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - const payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); await wait; testResponseTab(); await teardown(monitor); - function testJsonAccordionInResposeTab() { + function testResponseTab() { const tabpanel = document.querySelector("#response-panel"); + is( - tabpanel.querySelectorAll(".treeRow").length, + tabpanel.querySelector(".response-error-header") === null, + true, + "The response error header doesn't have the intended visibility." + ); + const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {}; + is( + jsonView.textContent === L10N.getStr("jsonScopeName"), + true, + "The response json view has the intended visibility." + ); + is( + tabpanel.querySelector(".editor-row-container").clientHeight !== 0, + true, + "The source editor container has visible height." + ); + is( + tabpanel.querySelector(".CodeMirror-code") === null, + false, + "The response editor has the intended visibility." + ); + is( + tabpanel.querySelector(".response-image-box") === null, + true, + "The response image box doesn't have the intended visibility." + ); + + is( + tabpanel.querySelectorAll(".tree-section").length, + 2, + "There should be 2 tree sections displayed in this tabpanel." + ); + is( + tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 2047, "There should be 2047 json properties displayed in this tabpanel." ); + is( + tabpanel.querySelectorAll(".empty-notice").length, + 0, + "The empty notice should not be displayed in this tabpanel." + ); - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); + is( + tabpanel.querySelector(".tree-section .treeLabel").textContent, + L10N.getStr("jsonScopeName"), + "The json view section doesn't have the correct title." + ); + + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); is( labels[0].textContent, @@ -115,54 +146,4 @@ add_task(async function() { "The second json property value was incorrect." ); } - - function testResponseTab() { - const tabpanel = document.querySelector("#response-panel"); - - is( - tabpanel.querySelector(".response-error-header") === null, - true, - "The response error header doesn't have the intended visibility." - ); - const jsonView = - tabpanel.querySelector(".accordion-item .accordion-header-label") || {}; - is( - jsonView.textContent === L10N.getStr("jsonScopeName"), - true, - "The response json view has the intended visibility." - ); - is( - tabpanel.querySelector(".source-editor-mount").clientHeight !== 0, - true, - "The source editor container has visible height." - ); - is( - tabpanel.querySelector(".CodeMirror-code") === null, - false, - "The response editor has the intended visibility." - ); - is( - tabpanel.querySelector(".response-image-box") === null, - true, - "The response image box doesn't have the intended visibility." - ); - - is( - tabpanel.querySelectorAll(".accordion-item").length, - 2, - "There should be 2 accordion items displayed in this tabpanel." - ); - is( - tabpanel.querySelectorAll(".empty-notice").length, - 0, - "The empty notice should not be displayed in this tabpanel." - ); - - is( - tabpanel.querySelector(".accordion-item .accordion-header-label") - .textContent, - L10N.getStr("jsonScopeName"), - "The json view section doesn't have the correct title." - ); - } }); diff --git a/devtools/client/netmonitor/test/browser_net_json-nogrip.js b/devtools/client/netmonitor/test/browser_net_json-nogrip.js index dcd08a158618..065ea720744b 100644 --- a/devtools/client/netmonitor/test/browser_net_json-nogrip.js +++ b/devtools/client/netmonitor/test/browser_net_json-nogrip.js @@ -22,26 +22,22 @@ add_task(async function() { const onResponsePanelReady = waitForDOM( document, - "#response-panel .accordion-item", - 2 + "#response-panel .CodeMirror-code" ); - - const onPropsViewReady = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - await Promise.all([onResponsePanelReady, onPropsViewReady]); + await onResponsePanelReady; const tabpanel = document.querySelector("#response-panel"); - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); is(labels[0].textContent, "obj", "The first json property name is correct."); is( diff --git a/devtools/client/netmonitor/test/browser_net_json-null.js b/devtools/client/netmonitor/test/browser_net_json-null.js index f6c33383962a..d26244ae3bf0 100644 --- a/devtools/client/netmonitor/test/browser_net_json-null.js +++ b/devtools/client/netmonitor/test/browser_net_json-null.js @@ -22,30 +22,25 @@ add_task(async function() { const onResponsePanelReady = waitForDOM( document, - "#response-panel .accordion-item", - 2 - ); - - const onPropsViewReady = waitForDOM( - document, - "#response-panel .properties-view", - 1 + "#response-panel .CodeMirror-code" ); store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - await Promise.all([onResponsePanelReady, onPropsViewReady]); + await onResponsePanelReady; + + checkResponsePanelDisplaysJSON(); const tabpanel = document.querySelector("#response-panel"); is( - tabpanel.querySelectorAll(".accordion-item").length, + tabpanel.querySelectorAll(".tree-section").length, 2, - "There should be 2 accordion items displayed in this tabpanel." + "There should be 2 tree sections displayed in this tabpanel." ); is( - tabpanel.querySelectorAll(".treeRow").length, + tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 1, "There should be 1 json properties displayed in this tabpanel." ); @@ -55,8 +50,12 @@ add_task(async function() { "The empty notice should not be displayed in this tabpanel." ); - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); is( labels[0].textContent, @@ -69,20 +68,6 @@ add_task(async function() { "The first json property value was incorrect." ); - const onCodeMirrorReady = waitForDOM( - document, - "#response-panel .CodeMirror-code" - ); - - const payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); - - await onCodeMirrorReady; - - checkResponsePanelDisplaysJSON(); - await teardown(monitor); /** @@ -96,8 +81,7 @@ add_task(async function() { true, "The response error header doesn't have the intended visibility." ); - const jsonView = - panel.querySelector(".accordion-item .accordion-header-label") || {}; + const jsonView = panel.querySelector(".tree-section .treeLabel") || {}; is( jsonView.textContent === L10N.getStr("jsonScopeName"), true, diff --git a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js index 150ccdd1904c..c6c3fbb52b48 100644 --- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js +++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js @@ -45,56 +45,18 @@ add_task(async function() { } ); - let wait = waitForDOM(document, "#response-panel .accordion-item", 2); - const waitForPropsView = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - + const wait = waitForDOM(document, "#response-panel .CodeMirror-code"); store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - await Promise.all([wait, waitForPropsView]); - - testJsonSectionInResponseTab(); - - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - const payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); await wait; testResponseTab(); await teardown(monitor); - function testJsonSectionInResponseTab() { - const tabpanel = document.querySelector("#response-panel"); - is( - tabpanel.querySelectorAll(".treeRow").length, - 1, - "There should be 1 json properties displayed in this tabpanel." - ); - - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); - - is( - labels[0].textContent, - "greeting", - "The first json property name was incorrect." - ); - is( - values[0].textContent, - `"Hello oddly-named JSON!"`, - "The first json property value was incorrect." - ); - } - function testResponseTab() { const tabpanel = document.querySelector("#response-panel"); @@ -103,8 +65,7 @@ add_task(async function() { true, "The response error header doesn't have the intended visibility." ); - const jsonView = - tabpanel.querySelector(".accordion-item .accordion-header-label") || {}; + const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {}; is( jsonView.textContent === L10N.getStr("jsonScopeName"), true, @@ -122,15 +83,37 @@ add_task(async function() { ); is( - tabpanel.querySelectorAll(".accordion-item").length, + tabpanel.querySelectorAll(".tree-section").length, 2, - "There should be 2 accordion items displayed in this tabpanel." + "There should be 2 tree sections displayed in this tabpanel." + ); + is( + tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, + 1, + "There should be 1 json properties displayed in this tabpanel." ); - is( tabpanel.querySelectorAll(".empty-notice").length, 0, "The empty notice should not be displayed in this tabpanel." ); + + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); + + is( + labels[0].textContent, + "greeting", + "The first json property name was incorrect." + ); + is( + values[0].textContent, + `"Hello oddly-named JSON!"`, + "The first json property value was incorrect." + ); } }); diff --git a/devtools/client/netmonitor/test/browser_net_json_text_mime.js b/devtools/client/netmonitor/test/browser_net_json_text_mime.js index 49bd8d3b557c..2aa42233c054 100644 --- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js +++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js @@ -46,56 +46,18 @@ add_task(async function() { } ); - let wait = waitForDOM(document, "#response-panel .accordion-item", 2); - const waitForPropsView = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - + const wait = waitForDOM(document, "#response-panel .CodeMirror-code"); store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - await Promise.all([wait, waitForPropsView]); - - testJsonSectionInResponseTab(); - - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - const payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); await wait; testResponseTab(); await teardown(monitor); - function testJsonSectionInResponseTab() { - const tabpanel = document.querySelector("#response-panel"); - is( - tabpanel.querySelectorAll(".treeRow").length, - 1, - "There should be 1 json properties displayed in this tabpanel." - ); - - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); - - is( - labels[0].textContent, - "greeting", - "The first json property name was incorrect." - ); - is( - values[0].textContent, - `"Hello third-party JSON!"`, - "The first json property value was incorrect." - ); - } - function testResponseTab() { const tabpanel = document.querySelector("#response-panel"); @@ -104,8 +66,7 @@ add_task(async function() { true, "The response error header doesn't have the intended visibility." ); - const jsonView = - tabpanel.querySelector(".accordion-item .accordion-header-label") || {}; + const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {}; is( jsonView.textContent === L10N.getStr("jsonScopeName"), true, @@ -123,14 +84,37 @@ add_task(async function() { ); is( - tabpanel.querySelectorAll(".accordion-item").length, + tabpanel.querySelectorAll(".tree-section").length, 2, - "There should be 2 accordion items displayed in this tabpanel." + "There should be 2 tree sections displayed in this tabpanel." + ); + is( + tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, + 1, + "There should be 1 json properties displayed in this tabpanel." ); is( tabpanel.querySelectorAll(".empty-notice").length, 0, "The empty notice should not be displayed in this tabpanel." ); + + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); + + is( + labels[0].textContent, + "greeting", + "The first json property name was incorrect." + ); + is( + values[0].textContent, + `"Hello third-party JSON!"`, + "The first json property value was incorrect." + ); } }); diff --git a/devtools/client/netmonitor/test/browser_net_jsonp.js b/devtools/client/netmonitor/test/browser_net_jsonp.js index 899be2439707..a4e135e8d679 100644 --- a/devtools/client/netmonitor/test/browser_net_jsonp.js +++ b/devtools/client/netmonitor/test/browser_net_jsonp.js @@ -65,90 +65,30 @@ add_task(async function() { ); info("Testing first request"); - let wait = waitForDOM(document, "#response-panel .accordion-item", 2); - let waitForPropsView = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - + let wait = waitForDOM(document, "#response-panel .CodeMirror-code"); store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#response-tab") ); - await Promise.all([wait, waitForPropsView]); - - testJsonSectionInResponseTab(`"Hello JSONP!"`); - - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - let payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); await wait; - testResponseTab("$_0123Fun"); + testResponseTab("$_0123Fun", `"Hello JSONP!"`); info("Testing second request"); + wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - wait = waitForDOM(document, "#response-panel .accordion-item", 2); EventUtils.sendMouseEvent( { type: "mousedown" }, document.querySelectorAll(".request-list-item")[1] ); - await wait; - waitForPropsView = waitForDOM( - document, - "#response-panel .properties-view", - 1 - ); - payloadHeader = document.querySelector( - "#response-panel .accordion-item:first-child .accordion-header" - ); - clickElement(payloadHeader, monitor); - - await waitForPropsView; - - testJsonSectionInResponseTab(`"Hello weird JSONP!"`); - - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); - await wait; - - testResponseTab("$_4567Sad"); + testResponseTab("$_4567Sad", `"Hello weird JSONP!"`); await teardown(monitor); - function testJsonSectionInResponseTab(greeting) { - const tabpanel = document.querySelector("#response-panel"); - is( - tabpanel.querySelectorAll(".treeRow").length, - 1, - "There should be 1 json properties displayed in this tabpanel." - ); - - const labels = tabpanel.querySelectorAll("tr .treeLabelCell .treeLabel"); - const values = tabpanel.querySelectorAll("tr .treeValueCell .objectBox"); - - is( - labels[0].textContent, - "greeting", - "The first json property name was incorrect." - ); - is( - values[0].textContent, - greeting, - "The first json property value was incorrect." - ); - } - - function testResponseTab(func) { + function testResponseTab(func, greeting) { const tabpanel = document.querySelector("#response-panel"); is( @@ -157,8 +97,7 @@ add_task(async function() { "The response error header doesn't have the intended visibility." ); is( - tabpanel.querySelector(".accordion-item .accordion-header-label") - .textContent, + tabpanel.querySelector(".tree-section .treeLabel").textContent, L10N.getFormatStr("jsonpScopeName", func), "The response json view has the intened visibility and correct title." ); @@ -174,14 +113,37 @@ add_task(async function() { ); is( - tabpanel.querySelectorAll(".accordion-item").length, + tabpanel.querySelectorAll(".tree-section").length, 2, - "There should be 2 accordion items displayed in this tabpanel." + "There should be 2 tree sections displayed in this tabpanel." + ); + is( + tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, + 1, + "There should be 1 json properties displayed in this tabpanel." ); is( tabpanel.querySelectorAll(".empty-notice").length, 0, "The empty notice should not be displayed in this tabpanel." ); + + const labels = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeLabelCell .treeLabel" + ); + const values = tabpanel.querySelectorAll( + "tr:not(.tree-section) .treeValueCell .objectBox" + ); + + is( + labels[0].textContent, + "greeting", + "The first json property name was incorrect." + ); + is( + values[0].textContent, + greeting, + "The first json property value was incorrect." + ); } }); diff --git a/devtools/client/netmonitor/test/browser_net_large-response.js b/devtools/client/netmonitor/test/browser_net_large-response.js index ea6fcbcd6cd2..674a79404561 100644 --- a/devtools/client/netmonitor/test/browser_net_large-response.js +++ b/devtools/client/netmonitor/test/browser_net_large-response.js @@ -52,7 +52,7 @@ add_task(async function() { } ); - wait = waitForDOM(document, "#response-panel .accordion-item", 2); + wait = waitForDOM(document, "#response-panel .CodeMirror-code"); store.dispatch(Actions.toggleNetworkDetails()); EventUtils.sendMouseEvent( { type: "click" }, @@ -60,13 +60,6 @@ add_task(async function() { ); await wait; - wait = waitForDOM(document, "#response-panel .CodeMirror-code"); - const payloadHeader = document.querySelector( - "#response-panel .accordion-item:last-child .accordion-header" - ); - clickElement(payloadHeader, monitor); - await wait; - ok( getCodeMirrorValue(monitor).match(/^

/), "The text shown in the source editor is incorrect." diff --git a/devtools/client/netmonitor/test/browser_net_propertiesview-copy.js b/devtools/client/netmonitor/test/browser_net_propertiesview-copy.js index 2b52867ab74a..8f29aebda2f1 100644 --- a/devtools/client/netmonitor/test/browser_net_propertiesview-copy.js +++ b/devtools/client/netmonitor/test/browser_net_propertiesview-copy.js @@ -33,7 +33,7 @@ add_task(async function() { const responsePanel = document.querySelector("#response-panel"); - const objectRow = responsePanel.querySelectorAll(".objectRow")[0]; + const objectRow = responsePanel.querySelectorAll(".objectRow")[1]; const stringRow = responsePanel.querySelectorAll(".stringRow")[0]; /* Test for copy an object */ @@ -46,7 +46,9 @@ add_task(async function() { EventUtils.sendMouseEvent({ type: "contextmenu" }, objectRow); await waitForClipboardPromise(function setup() { getContextMenuItem(monitor, "properties-view-context-menu-copyall").click(); - }, `{"obj":{"type":"string"}}`); + }, `{"JSON":{"obj":{"type":"string"}},` + + `"Response Payload":{"EDITOR_CONFIG":{"text":` + + `"{\\"obj\\": {\\"type\\": \\"string\\" }}","mode":"application/json"}}}`); /* Test for copy a single row */ EventUtils.sendMouseEvent({ type: "contextmenu" }, stringRow); @@ -93,7 +95,9 @@ add_task(async function() { const cookiesPanel = document.querySelector("#cookies-panel"); - const objectRows = cookiesPanel.querySelectorAll(".objectRow"); + const objectRows = cookiesPanel.querySelectorAll( + ".objectRow:not(.tree-section)" + ); const stringRows = cookiesPanel.querySelectorAll(".stringRow"); const expectedResponseCookies = [ diff --git a/devtools/client/netmonitor/test/browser_net_response-ui-limit.js b/devtools/client/netmonitor/test/browser_net_response-ui-limit.js index 54f57bc3c020..e542932844ee 100644 --- a/devtools/client/netmonitor/test/browser_net_response-ui-limit.js +++ b/devtools/client/netmonitor/test/browser_net_response-ui-limit.js @@ -4,7 +4,7 @@ "use strict"; /** - * Tests if large response contents are displayed using

+ * Tests if large response contents are  displayed usin 
  * and not using syntax color highlighting (e.g. using CodeMirror)
  */
 const HTML_LONG_URL = CONTENT_TYPE_SJS + "?fmt=html-long";
@@ -32,7 +32,7 @@ add_task(async function() {
   });
   await wait;
 
-  wait = waitForDOM(document, "#response-panel .accordion-item", 2);
+  wait = waitForDOM(document, "#response-panel .responseTextContainer");
   store.dispatch(Actions.toggleNetworkDetails());
   EventUtils.sendMouseEvent(
     { type: "click" },
@@ -40,13 +40,6 @@ add_task(async function() {
   );
   await wait;
 
-  wait = waitForDOM(document, "#response-panel .responseTextContainer");
-  const payloadHeader = document.querySelector(
-    "#response-panel .accordion-item:last-child .accordion-header"
-  );
-  clickElement(payloadHeader, monitor);
-  await wait;
-
   await teardown(monitor);
 
   // This test uses a lot of memory, so force a GC to help fragmentation.
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-details.js b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
index 5c8b6f2684b6..c7e5cf745ecf 100644
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -299,16 +299,16 @@ add_task(async function() {
 
   async function testResponseTab() {
     const tabpanel = await selectTab(PANELS.RESPONSE, 3);
-    await waitForDOM(document, ".accordion .source-editor-mount");
+    await waitForDOM(document, ".treeTable tbody");
 
-    const responseAccordion = tabpanel.querySelector(".accordion");
+    const responseTable = tabpanel.querySelector(".treeTable tbody");
     is(
-      responseAccordion.querySelectorAll(".accordion-item").length,
+      responseTable.querySelectorAll(".tree-section").length,
       1,
       "There should be 1 response scope displayed in this tabpanel."
     );
     is(
-      responseAccordion.querySelectorAll(".source-editor-mount").length,
+      responseTable.querySelectorAll(".editor-row-container").length,
       1,
       "The response payload tab should be open initially."
     );
diff --git a/devtools/client/netmonitor/test/browser_net_truncate-post-data.js b/devtools/client/netmonitor/test/browser_net_truncate-post-data.js
index f7baa3e2b504..728dc1b7325c 100644
--- a/devtools/client/netmonitor/test/browser_net_truncate-post-data.js
+++ b/devtools/client/netmonitor/test/browser_net_truncate-post-data.js
@@ -35,7 +35,7 @@ add_task(async function() {
   );
   const waitSourceEditor = waitForDOM(
     document,
-    "#params-panel .responseTextContainer"
+    "#params-panel .CodeMirror-code"
   );
 
   store.dispatch(Actions.toggleNetworkDetails());
diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js
index cd30dd909298..77237508badf 100644
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -1196,16 +1196,3 @@ async function toggleBlockedUrl(element, monitor, store, action = "block") {
   await onRequestComplete;
   info(`Selected request is now ${action}ed`);
 }
-
-/**
- * Find and click an element
- *
- * @param {Element} element
- *        Target element to be clicked
- * @param {Object} monitor
- *        The netmonitor instance used for retrieving the window.
- */
-
-function clickElement(element, monitor) {
-  EventUtils.synthesizeMouseAtCenter(element, {}, monitor.panelWin);
-}
diff --git a/devtools/client/shared/components/Accordion.js b/devtools/client/shared/components/Accordion.js
index f238adcba9cd..88512b02fe5d 100644
--- a/devtools/client/shared/components/Accordion.js
+++ b/devtools/client/shared/components/Accordion.js
@@ -25,22 +25,40 @@ class Accordion extends Component {
         PropTypes.shape({
           buttons: PropTypes.arrayOf(PropTypes.object),
           className: PropTypes.string,
-          component: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
+          component: PropTypes.object,
           componentProps: PropTypes.object,
           contentClassName: PropTypes.string,
           header: PropTypes.string.isRequired,
           id: PropTypes.string.isRequired,
           onToggle: PropTypes.func,
-          // Determines the initial open state of the accordion item
           opened: PropTypes.bool.isRequired,
-          // Enables dynamically changing the open state of the accordion
-          // on update.
-          shouldOpen: PropTypes.func,
         })
       ).isRequired,
     };
   }
 
+  /**
+   * Add initial data to the state.opened map, and inject new data
+   * when receiving updated props.
+   */
+  static getDerivedStateFromProps(props, state) {
+    const newItems = props.items.filter(
+      ({ id }) => typeof state.opened[id] !== "boolean"
+    );
+
+    if (newItems.length) {
+      const everOpened = { ...state.everOpened };
+      const opened = { ...state.opened };
+      for (const item of newItems) {
+        everOpened[item.id] = item.opened;
+        opened[item.id] = item.opened;
+      }
+      return { everOpened, opened };
+    }
+
+    return null;
+  }
+
   constructor(props) {
     super(props);
 
@@ -50,115 +68,56 @@ class Accordion extends Component {
 
     this.onHeaderClick = this.onHeaderClick.bind(this);
     this.onHeaderKeyDown = this.onHeaderKeyDown.bind(this);
-    this.setInitialState = this.setInitialState.bind(this);
-    this.updateCurrentState = this.updateCurrentState.bind(this);
-  }
-
-  componentDidMount() {
-    this.setInitialState();
-  }
-
-  componentDidUpdate(prevProps) {
-    if (prevProps.items !== this.props.items) {
-      this.updateCurrentState();
-    }
-  }
-
-  setInitialState() {
-    /**
-     * Add initial data to the `state.opened` map.
-     * This happens only on initial mount of the accordion.
-     */
-    const newItems = this.props.items.filter(
-      ({ id }) => typeof this.state.opened[id] !== "boolean"
-    );
-
-    if (newItems.length) {
-      const everOpened = { ...this.state.everOpened };
-      const opened = { ...this.state.opened };
-      for (const item of newItems) {
-        everOpened[item.id] = item.opened;
-        opened[item.id] = item.opened;
-      }
-
-      this.setState({ everOpened, opened });
-    }
-  }
-
-  updateCurrentState() {
-    /**
-     * Updates the `state.opened` map based on the
-     * new items that have been added and those that
-     * `item.shouldOpen()` has changed. This happens
-     * on each update.
-     */
-    const updatedItems = this.props.items.filter(item => {
-      const notExist = typeof this.state.opened[item.id] !== "boolean";
-      if (typeof item.shouldOpen == "function") {
-        return notExist || this.state.opened[item.id] !== item.shouldOpen(item);
-      }
-      return notExist;
-    });
-
-    if (updatedItems.length) {
-      const everOpened = { ...this.state.everOpened };
-      const opened = { ...this.state.opened };
-      for (const item of updatedItems) {
-        let itemOpen = item.opened;
-        if (typeof item.shouldOpen == "function") {
-          itemOpen = item.shouldOpen(item);
-        }
-        everOpened[item.id] = itemOpen;
-        opened[item.id] = itemOpen;
-      }
-      this.setState({ everOpened, opened });
-    }
   }
 
   /**
    * @param {Event} event Click event.
-   * @param {Object} item The item to be collapsed/expanded.
    */
-  onHeaderClick(event, item) {
+  onHeaderClick(event) {
     event.preventDefault();
     // In the Browser Toolbox's Inspector/Layout view, handleHeaderClick is
     // called twice unless we call stopPropagation, making the accordion item
     // open-and-close or close-and-open
     event.stopPropagation();
-    this.toggleItem(item);
+    this.toggleItem(event.currentTarget.parentElement.id);
   }
 
   /**
    * @param {Event} event Keyboard event.
    * @param {Object} item The item to be collapsed/expanded.
    */
-  onHeaderKeyDown(event, item) {
+  onHeaderKeyDown(event) {
     if (event.key === " " || event.key === "Enter") {
       event.preventDefault();
-      this.toggleItem(item);
+      this.toggleItem(event.currentTarget.parentElement.id);
     }
   }
 
   /**
    * Expand or collapse an accordion list item.
-   * @param  {Object} item The item to be collapsed or expanded.
+   * @param  {String} id Id of the item to be collapsed or expanded.
    */
-  toggleItem(item) {
-    const opened = !this.state.opened[item.id];
+  toggleItem(id) {
+    const item = this.props.items.find(x => x.id === id);
+    const opened = !this.state.opened[id];
+    // We could have no item if props just changed
+    if (!item) {
+      return;
+    }
 
     this.setState({
       everOpened: {
         ...this.state.everOpened,
-        [item.id]: true,
+        [id]: true,
       },
       opened: {
         ...this.state.opened,
-        [item.id]: opened,
+        [id]: opened,
       },
     });
 
     if (typeof item.onToggle === "function") {
-      item.onToggle(opened, item);
+      item.onToggle(opened);
     }
   }
 
@@ -172,15 +131,14 @@ class Accordion extends Component {
       header,
       id,
     } = item;
-
     const headerId = `${id}-header`;
     const opened = this.state.opened[id];
     let itemContent;
 
-    // Only render content if the accordion item is open or has been opened once before.
-    // This saves us rendering complex components when users are keeping
+    // Only render content if the accordion item is open or has been opened once
+    // before. This saves us rendering complex components when users are keeping
     // them closed (e.g. in Inspector/Layout) or may not open them at all.
-    if (this.state.everOpened && this.state.everOpened[id]) {
+    if (this.state.everOpened[id]) {
       if (typeof component === "function") {
         itemContent = createElement(component, componentProps);
       } else if (typeof component === "object") {
@@ -192,9 +150,7 @@ class Accordion extends Component {
       {
         key: id,
         id,
-        className: `accordion-item ${
-          opened ? "accordion-open" : ""
-        } ${className} `.trim(),
+        className: `accordion-item ${className}`.trim(),
         "aria-labelledby": headerId,
       },
       h2(
@@ -206,8 +162,8 @@ class Accordion extends Component {
           // If the header contains buttons, make sure the heading name only
           // contains the "header" text and not the button text
           "aria-label": header,
-          onKeyDown: event => this.onHeaderKeyDown(event, item),
-          onClick: event => this.onHeaderClick(event, item),
+          onKeyDown: this.onHeaderKeyDown,
+          onClick: this.onHeaderClick,
         },
         span({
           className: `theme-twisty${opened ? " open" : ""}`,
diff --git a/devtools/client/shared/components/test/chrome/accordion.snapshots.js b/devtools/client/shared/components/test/chrome/accordion.snapshots.js
index 0f649b52d6b5..b01bcf1aa598 100644
--- a/devtools/client/shared/components/test/chrome/accordion.snapshots.js
+++ b/devtools/client/shared/components/test/chrome/accordion.snapshots.js
@@ -27,8 +27,8 @@ window._snapshots = {
               tabIndex: 0,
               "aria-expanded": false,
               "aria-label": "Test Accordion Item 1",
-              onKeyDown: "event => this.onHeaderKeyDown(event, item)",
-              onClick: "event => this.onHeaderClick(event, item)",
+              onKeyDown: "function() {\n    [native code]\n}",
+              onClick: "function() {\n    [native code]\n}",
             },
             children: [
               {
@@ -73,8 +73,8 @@ window._snapshots = {
               tabIndex: 0,
               "aria-expanded": false,
               "aria-label": "Test Accordion Item 2",
-              onKeyDown: "event => this.onHeaderKeyDown(event, item)",
-              onClick: "event => this.onHeaderClick(event, item)",
+              onKeyDown: "function() {\n    [native code]\n}",
+              onClick: "function() {\n    [native code]\n}",
             },
             children: [
               {
@@ -121,7 +121,7 @@ window._snapshots = {
         type: "li",
         props: {
           id: "accordion-item-3",
-          className: "accordion-item accordion-open",
+          className: "accordion-item",
           "aria-labelledby": "accordion-item-3-header",
         },
         children: [
@@ -133,8 +133,8 @@ window._snapshots = {
               tabIndex: 0,
               "aria-expanded": true,
               "aria-label": "Test Accordion Item 3",
-              onKeyDown: "event => this.onHeaderKeyDown(event, item)",
-              onClick: "event => this.onHeaderClick(event, item)",
+              onKeyDown: "function() {\n    [native code]\n}",
+              onClick: "function() {\n    [native code]\n}",
             },
             children: [
               {
diff --git a/devtools/client/shared/components/test/chrome/test_accordion.html b/devtools/client/shared/components/test/chrome/test_accordion.html
index 60d179be6fd9..689db51e7bb1 100644
--- a/devtools/client/shared/components/test/chrome/test_accordion.html
+++ b/devtools/client/shared/components/test/chrome/test_accordion.html
@@ -61,7 +61,6 @@ window.onload = async function() {
       },
     ];
 
-    // Accordion basic render
     const accordion = React.createElement(Accordion, { items: testItems });
 
     matchSnapshot("Accordion basic render.", accordion);
@@ -86,7 +85,7 @@ window.onload = async function() {
           "accordion-item-1": true,
           "accordion-item-2": false,
           "accordion-item-3": true,
-        },
+        }
       },
       "State updated correctly"
     );
@@ -105,7 +104,7 @@ window.onload = async function() {
           "accordion-item-1": false,
           "accordion-item-2": false,
           "accordion-item-3": true,
-        },
+        }
       },
       "State updated correctly"
     );
@@ -124,7 +123,7 @@ window.onload = async function() {
           "accordion-item-1": false,
           "accordion-item-2": true,
           "accordion-item-3": true,
-        },
+        }
       },
       "State updated correctly"
     );