☠☠ backed out by df43a7470ad3 ☠ ☠ | |
author | Farooq AR <farooqbckk@gmail.com> |
Fri, 13 Mar 2020 10:42:30 +0000 | |
changeset 518563 | 2e588c4bccf11a9c8c2d297e7791665ecc8f88fe |
parent 518562 | d5eff66da2cdaecb0867143340b992365ce95198 |
child 518564 | 25ad22509b7809e464ca6602d8b821841a17deb8 |
push id | 110048 |
push user | jodvarko@mozilla.com |
push date | Fri, 13 Mar 2020 10:43:13 +0000 |
treeherder | autoland@2e588c4bccf1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Harald, Honza, bomsy |
bugs | 1615102 |
milestone | 76.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/locales/en-US/netmonitor.properties +++ b/devtools/client/locales/en-US/netmonitor.properties @@ -948,16 +948,24 @@ netmonitor.toolbar.search=Search # LOCALIZATION NOTE (netmonitor.toolbar.resetColumns): This is the label # displayed in the network table header context menu. netmonitor.toolbar.resetColumns=Reset Columns # LOCALIZATION NOTE (netmonitor.toolbar.resetSorting): This is the label # displayed in the network table header context menu to reset sorting netmonitor.toolbar.resetSorting=Reset Sorting +# LOCALIZATION NOTE (netmonitor.toolbar.resizeColumnToFitContent): This is the label +# displayed in the network table header context menu to resize a column to fit its content +netmonitor.toolbar.resizeColumnToFitContent=Resize Column To Fit Content + +# LOCALIZATION NOTE (netmonitor.toolbar.resizeColumnToFitContent.title): This is the title +# tooltip displayed when draggable resizer in network table headers is hovered +netmonitor.toolbar.resizeColumnToFitContent.title=Double-click to fit column to content + # LOCALIZATION NOTE (netmonitor.toolbar.timings): This is the label # displayed in the network table header context menu for the timing submenu netmonitor.toolbar.timings=Timings # LOCALIZATION NOTE (netmonitor.toolbar.responseHeaders): This is the # label displayed in the network table header context menu for the # response headers submenu. netmonitor.toolbar.responseHeaders=Response Headers
--- a/devtools/client/netmonitor/src/components/request-list/RequestListHeader.js +++ b/devtools/client/netmonitor/src/components/request-list/RequestListHeader.js @@ -69,24 +69,26 @@ class RequestListHeader extends Componen this.requestListHeader = createRef(); this.onContextMenu = this.onContextMenu.bind(this); this.drawBackground = this.drawBackground.bind(this); this.resizeWaterfall = this.resizeWaterfall.bind(this); this.waterfallDivisionLabels = this.waterfallDivisionLabels.bind(this); this.waterfallLabel = this.waterfallLabel.bind(this); this.onHeaderClick = this.onHeaderClick.bind(this); + this.resizeColumnToFitContent = this.resizeColumnToFitContent.bind(this); } componentWillMount() { const { resetColumns, resetSorting, toggleColumn } = this.props; this.contextMenu = new RequestListHeaderContextMenu({ resetColumns, resetSorting, toggleColumn, + resizeColumnToFitContent: this.resizeColumnToFitContent, }); } componentDidMount() { // Create the object that takes care of drawing the waterfall canvas background this.background = new WaterfallBackground(document); this.drawBackground(); // When visible columns add up to less or more than 100% => update widths in prefs. @@ -110,16 +112,109 @@ class RequestListHeader extends Componen componentWillUnmount() { this.background.destroy(); this.background = null; window.removeEventListener("resize", this.resizeWaterfall); removeThemeObserver(this.drawBackground); } + /** + * Helper method to get the total width of cell's content. + * Used for resizing columns to fit their content. + */ + totalCellWidth(cellEl) { + return [...cellEl.childNodes] + .map(cNode => { + if (cNode.nodeType === 3) { + // if it's text node + return Math.ceil( + cNode.getBoxQuads()[0].p2.x - cNode.getBoxQuads()[0].p1.x + ); + } + return cNode.getBoundingClientRect().width; + }) + .reduce((a, b) => a + b, 0); + } + + /** + * Resize column to fit its content. + * Additionally, resize other columns (starting from last) to compensate. + */ + resizeColumnToFitContent(name) { + const headerRef = this.refs[`${name}Header`]; + const parentEl = headerRef.closest(".requests-list-table"); + const width = headerRef.getBoundingClientRect().width; + const parentWidth = parentEl.getBoundingClientRect().width; + const items = parentEl.querySelectorAll(".request-list-item"); + const columnIndex = headerRef.cellIndex; + const widths = [...items].map(item => + this.totalCellWidth(item.children[columnIndex]) + ); + + const minW = this.getMinWidth(name); + + // Add 11 to account for cell padding (padding-right + padding-left = 9px), not accurate. + let maxWidth = 11 + Math.max.apply(null, widths); + + if (maxWidth < minW) { + maxWidth = minW; + } + + // Pixel value which, if added to this column's width, will fit its content. + let change = maxWidth - width; + + // Max change we can do while taking other columns into account. + let maxAllowedChange = 0; + const visibleColumns = this.getVisibleColumns(); + const newWidths = []; + + // Calculate new widths for other columns to compensate. + // Start from the 2nd last column if last column is waterfall. + // This is done to comply with the existing resizing behavior. + const delta = + visibleColumns[visibleColumns.length - 1].name === "waterfall" ? 2 : 1; + + for (let i = visibleColumns.length - delta; i > 0; i--) { + if (i !== columnIndex) { + const columnName = visibleColumns[i].name; + const columnHeaderRef = this.refs[`${columnName}Header`]; + const columnWidth = columnHeaderRef.getBoundingClientRect().width; + const minWidth = this.getMinWidth(columnName); + const newWidth = columnWidth - change; + + // If this column can compensate for all the remaining change. + if (newWidth >= minWidth) { + maxAllowedChange += change; + change = 0; + newWidths.push({ + name: columnName, + width: this.px2percent(newWidth, parentWidth), + }); + break; + } else { + // Max change we can do in this column. + let maxColumnChange = columnWidth - minWidth; + maxColumnChange = maxColumnChange > change ? change : maxColumnChange; + maxAllowedChange += maxColumnChange; + change -= maxColumnChange; + newWidths.push({ + name: columnName, + width: this.px2percent(columnWidth - maxColumnChange, parentWidth), + }); + } + } + } + newWidths.push({ + name, + width: this.px2percent(width + maxAllowedChange, parentWidth), + }); + this.props.setColumnsWidth(newWidths); + } + onContextMenu(evt) { evt.preventDefault(); this.contextMenu.open(evt, this.props.columns); } onHeaderClick(evt, headerName) { const { sortBy, resetSorting } = this.props; if (evt.button == 1) { @@ -546,19 +641,21 @@ class RequestListHeader extends Componen } const columnStyle = { width: colWidth + "%", }; // Support for columns resizing is currently hidden behind a pref. const draggable = Draggable({ className: "column-resizer ", + title: L10N.getStr("netmonitor.toolbar.resizeColumnToFitContent.title"), onStart: () => this.onStartMove(), onStop: () => this.onStopMove(), onMove: x => this.onMove(name, x), + onDoubleClick: () => this.resizeColumnToFitContent(name), }); return dom.th( { id: `requests-list-${boxName}-header-box`, className: `requests-list-column requests-list-${boxName}`, scope: "col", style: columnStyle, @@ -567,16 +664,17 @@ class RequestListHeader extends Componen // Used to style the next column. "data-active": active, }, button( { id: `requests-list-${name}-button`, className: `requests-list-header-button`, "data-sorted": sorted, + "data-name": name, title: sortedTitle ? `${label} (${sortedTitle})` : label, onClick: evt => this.onHeaderClick(evt, name), }, name === "waterfall" ? this.waterfallLabel(waterfallWidth, scale, label) : div({ className: "button-text" }, label), div({ className: "button-icon" }) ),
--- a/devtools/client/netmonitor/src/widgets/RequestListHeaderContextMenu.js +++ b/devtools/client/netmonitor/src/widgets/RequestListHeaderContextMenu.js @@ -76,16 +76,24 @@ class RequestListHeaderContextMenu { }); menu.push({ id: "request-list-header-reset-sorting", label: L10N.getStr("netmonitor.toolbar.resetSorting"), click: () => this.props.resetSorting(), }); + const columnName = event.target.getAttribute("data-name"); + + menu.push({ + id: "request-list-header-resize-column-to-fit-content", + label: L10N.getStr("netmonitor.toolbar.resizeColumnToFitContent"), + click: () => this.props.resizeColumnToFitContent(columnName), + }); + showMenu(menu, { screenX: event.screenX, screenY: event.screenY, }); } } module.exports = RequestListHeaderContextMenu;
--- a/devtools/client/netmonitor/test/browser.ini +++ b/devtools/client/netmonitor/test/browser.ini @@ -99,16 +99,17 @@ skip-if = (verify && !debug && (os == 'l [browser_net_charts-02.js] [browser_net_charts-03.js] [browser_net_charts-04.js] [browser_net_charts-05.js] [browser_net_charts-06.js] [browser_net_charts-07.js] [browser_net_clear.js] [browser_net_column_headers_tooltips.js] +[browser_net_column-resize-fit.js] [browser_net_columns_last_column.js] [browser_net_columns_pref.js] [browser_net_columns_reset.js] [browser_net_columns_showhide.js] [browser_net_columns_time.js] [browser_net_complex-params.js] skip-if = (verify && !debug && (os == 'win')) [browser_net_content-type.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_column-resize-fit.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests resizing of columns in NetMonitor. + */ +add_task(async function() { + // Reset visibleColumns so we only get the default ones + // and not all that are set in head.js + Services.prefs.clearUserPref("devtools.netmonitor.visibleColumns"); + const visibleColumns = JSON.parse( + Services.prefs.getCharPref("devtools.netmonitor.visibleColumns") + ); + // Init network monitor + const { tab, monitor } = await initNetMonitor(SIMPLE_URL); + info("Starting test... "); + + const { document } = monitor.panelWin; + + // Wait for network events (to have some requests in the table) + const wait = waitForNetworkEvents(monitor, 1); + tab.linkedBrowser.reload(); + await wait; + + info("Testing column resize to fit using double-click on draggable resizer"); + const fileHeader = document.querySelector(`#requests-list-file-header-box`); + const fileColumnResizer = fileHeader.querySelector(".column-resizer"); + + EventUtils.sendMouseEvent({ type: "dblclick" }, fileColumnResizer); + + // After resize - get fresh prefs for tests. + let columnsData = JSON.parse( + Services.prefs.getCharPref("devtools.netmonitor.columnsData") + ); + + // `File` column before resize: 25%, after resize: 11.25% + // `Transferred` column before resize: 10%, after resize: 10% + checkColumnsData(columnsData, "file", 12); + checkSumOfVisibleColumns(columnsData, visibleColumns); + + info( + "Testing column resize to fit using context menu `Resize Column To Fit Content`" + ); + + // Resizing `transferred` column. + EventUtils.sendMouseEvent( + { type: "contextmenu" }, + document.querySelector("#requests-list-transferred-button") + ); + + getContextMenuItem( + monitor, + "request-list-header-resize-column-to-fit-content" + ).click(); + + columnsData = JSON.parse( + Services.prefs.getCharPref("devtools.netmonitor.columnsData") + ); + + // `Transferred` column before resize: 10%, after resize: 2.97% + checkColumnsData(columnsData, "transferred", 3); + checkSumOfVisibleColumns(columnsData, visibleColumns); + + // Done: clean up. + return teardown(monitor); +}); + +function checkColumnsData(columnsData, column, expectedWidth) { + const width = getWidthFromPref(columnsData, column); + const widthsDiff = Math.abs(width - expectedWidth); + ok( + widthsDiff < 2, + `Column ${column} has expected size. Got ${width}, Expected ${expectedWidth}` + ); +} + +function checkSumOfVisibleColumns(columnsData, visibleColumns) { + let sum = 0; + visibleColumns.forEach(column => { + sum += getWidthFromPref(columnsData, column); + }); + sum = Math.round(sum); + is(sum, 100, "All visible columns cover 100%."); +} + +function getWidthFromPref(columnsData, column) { + const widthInPref = columnsData.find(function(element) { + return element.name === column; + }).width; + return widthInPref; +}
--- a/devtools/client/shared/components/splitter/Draggable.js +++ b/devtools/client/shared/components/splitter/Draggable.js @@ -7,46 +7,65 @@ const { createRef, Component } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); class Draggable extends Component { static get propTypes() { return { onMove: PropTypes.func.isRequired, + onDoubleClick: PropTypes.func, onStart: PropTypes.func, onStop: PropTypes.func, style: PropTypes.object, + title: PropTypes.string, className: PropTypes.string, }; } constructor(props) { super(props); this.draggableEl = createRef(); this.startDragging = this.startDragging.bind(this); + this.onDoubleClick = this.onDoubleClick.bind(this); this.onMove = this.onMove.bind(this); this.onUp = this.onUp.bind(this); + this.mouseX = 0; + this.mouseY = 0; } + startDragging(ev) { + const xDiff = Math.abs(this.mouseX - ev.clientX); + const yDiff = Math.abs(this.mouseY - ev.clientY); - startDragging(ev) { + // This allows for double-click. + if (xDiff + yDiff <= 1) { + return; + } + this.mouseX = ev.clientX; + this.mouseY = ev.clientY; + if (this.isDragging) { return; } this.isDragging = true; - ev.preventDefault(); const doc = this.draggableEl.current.ownerDocument; doc.addEventListener("mousemove", this.onMove); doc.addEventListener("mouseup", this.onUp); this.props.onStart && this.props.onStart(); } + onDoubleClick() { + if (this.props.onDoubleClick) { + this.props.onDoubleClick(); + } + } + onMove(ev) { if (!this.isDragging) { return; } ev.preventDefault(); // Use viewport coordinates so, moving mouse over iframes // doesn't mangle (relative) coordinates. @@ -66,15 +85,17 @@ class Draggable extends Component { this.props.onStop && this.props.onStop(); } render() { return dom.div({ ref: this.draggableEl, role: "presentation", style: this.props.style, + title: this.props.title, className: this.props.className, onMouseDown: this.startDragging, + onDoubleClick: this.onDoubleClick, }); } } module.exports = Draggable;