Bug 1308441 - Support image preview r?honza
MozReview-Commit-ID: 6rzKFaZZvnk
--- a/devtools/client/netmonitor/components/request-list-column-file.js
+++ b/devtools/client/netmonitor/components/request-list-column-file.js
@@ -11,17 +11,17 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { propertiesEqual } = require("../utils/request-utils");
// Components
const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
-const { div } = DOM;
+const { div, img } = DOM;
const UPDATED_PROPS = [
"responseContentDataUri",
"urlDetails",
];
/**
* Request list file column component
* Describes the header and cell contents of a table column
@@ -52,22 +52,28 @@ const FileColumnCell = createFactory(cre
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
},
render() {
let { rowData } = this.props;
- let { urlDetails } = rowData;
+ let { urlDetails, responseContentDataUri } = rowData;
return (
- div({
- className: "requests-list-url subitem-label",
- title: urlDetails.unicodeUrl,
- },
- urlDetails.baseNameWithQuery,
+ div({},
+ img({
+ className: "requests-list-file-icon",
+ src: responseContentDataUri || "chrome://devtools/skin/images/item-toggle.svg",
+ }),
+ div({
+ className: "subitem-label requests-list-url",
+ title: urlDetails.unicodeUrl,
+ },
+ urlDetails.baseNameWithQuery,
+ ),
)
);
}
}));
module.exports = RequestListColumnFile;
--- a/devtools/client/netmonitor/components/request-list.js
+++ b/devtools/client/netmonitor/components/request-list.js
@@ -11,17 +11,20 @@ const {
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const SortDirection = require("devtools/client/shared/vendor/react-virtualized").SortDirection;
const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
const Actions = require("../actions/index");
const { ACTIVITY_TYPE } = require("../constants");
const RequestListContextMenu = require("../request-list-context-menu");
-const { setTooltipStackTraceContent } = require("../request-list-tooltip");
+const {
+ setTooltipImageContent,
+ setTooltipStackTraceContent,
+} = require("../request-list-tooltip");
const {
getDisplayedRequests,
getSortedRequests,
getWaterfallScale,
} = require("../selectors/index");
// Components
const AutoSizer = createFactory(require("devtools/client/shared/vendor/react-virtualized").AutoSizer);
@@ -145,17 +148,19 @@ const RequestList = createClass({
return false;
}
let requestItem = this.props.displayedRequests.find(r => r.id === itemId);
if (!requestItem) {
return false;
}
- if (requestItem.cause && target.closest(".requests-list-cause-stack")) {
+ if (requestItem.responseContent && target.closest(".requests-list-file-icon")) {
+ return setTooltipImageContent(tooltip, itemEl, requestItem);
+ } else if (requestItem.cause && target.closest(".requests-list-cause-stack")) {
return setTooltipStackTraceContent(tooltip, requestItem);
}
return false;
},
onKeyDown(evt) {
let { displayedRequests } = this.props;
--- a/devtools/client/netmonitor/request-list-tooltip.js
+++ b/devtools/client/netmonitor/request-list-tooltip.js
@@ -1,21 +1,45 @@
/* 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 {
+ setImageTooltip,
+ getImageDimensions,
+} = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
const { WEBCONSOLE_L10N } = require("./utils/l10n");
+const { formDataURI } = require("./utils/request-utils");
// px
+const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
+// px
const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
const HTML_NS = "http://www.w3.org/1999/xhtml";
+async function setTooltipImageContent(tooltip, itemEl, requestItem) {
+ let { mimeType, text, encoding } = requestItem.responseContent.content;
+
+ if (!mimeType || !mimeType.includes("image/")) {
+ return false;
+ }
+
+ let string = await window.gNetwork.getString(text);
+ let src = formDataURI(mimeType, encoding, string);
+ let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
+ let { naturalWidth, naturalHeight } = await getImageDimensions(tooltip.doc, src);
+ let options = { maxDim, naturalWidth, naturalHeight };
+ setImageTooltip(tooltip, tooltip.doc, src, options);
+
+ return itemEl.querySelector(".requests-list-file-icon");
+}
+
async function setTooltipStackTraceContent(tooltip, requestItem) {
let {stacktrace} = requestItem.cause;
if (!stacktrace || stacktrace.length == 0) {
return false;
}
let doc = tooltip.doc;
@@ -72,10 +96,11 @@ async function setTooltipStackTraceConte
}
tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH});
return true;
}
module.exports = {
+ setTooltipImageContent,
setTooltipStackTraceContent,
};
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -3,16 +3,17 @@ tags = devtools
subsuite = devtools
support-files =
dropmarker.svg
head.js
html_cause-test-page.html
html_content-type-test-page.html
html_content-type-without-cache-test-page.html
html_brotli-test-page.html
+ html_image-tooltip-test-page.html
html_cors-test-page.html
html_custom-get-page.html
html_cyrillic-test-page.html
html_frame-test-page.html
html_frame-subdocument.html
html_filter-test-page.html
html_infinite-get-page.html
html_json-b64.html
@@ -101,16 +102,18 @@ skip-if = (os == 'linux' && bits == 32 &
skip-if = (os == 'linux' && debug && bits == 32) # Bug 1321434
[browser_net_filter-01.js]
skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
[browser_net_filter-02.js]
[browser_net_filter-03.js]
[browser_net_filter-04.js]
[browser_net_footer-summary.js]
[browser_net_html-preview.js]
+[browser_net_icon-preview.js]
+[browser_net_image-tooltip.js]
[browser_net_json-b64.js]
[browser_net_json-null.js]
[browser_net_json-long.js]
[browser_net_json-malformed.js]
[browser_net_json_custom_mime.js]
[browser_net_json_text_mime.js]
[browser_net_jsonp.js]
[browser_net_large-response.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if image responses show a thumbnail in the requests menu.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+ info("Starting test... ");
+
+ let { document, gStore, windowRequire, NetMonitorController } =
+ monitor.panelWin;
+ let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+ let { ACTIVITY_TYPE, EVENTS } = windowRequire("devtools/client/netmonitor/constants");
+
+ gStore.dispatch(Actions.batchEnable(false));
+
+ let wait = waitForEvents();
+ yield performRequests();
+ yield wait;
+
+ info("Checking the image thumbnail when all items are shown.");
+ checkImageThumbnail(5);
+
+ gStore.dispatch(Actions.toggleRequestFilterType("images"));
+ info("Checking the image thumbnail when only images are shown.");
+ checkImageThumbnail(0);
+
+ info("Reloading the debuggee and performing all requests again...");
+ wait = waitForEvents();
+ yield reloadAndPerformRequests();
+ yield wait;
+
+ info("Checking the image thumbnail after a reload.");
+ checkImageThumbnail(0);
+
+ yield teardown(monitor);
+
+ function waitForEvents() {
+ return promise.all([
+ waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS),
+ monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
+ ]);
+ }
+
+ function performRequests() {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ }
+
+ function* reloadAndPerformRequests() {
+ yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
+ yield performRequests();
+ }
+
+ function checkImageThumbnail(requestIndex) {
+ is(document.querySelectorAll(".requests-list-file-icon")[requestIndex].src,
+ TEST_IMAGE_DATA_URI,
+ "The image requests-list-icon thumbnail is displayed correctly.");
+ }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const IMAGE_TOOLTIP_URL = EXAMPLE_URL + "html_image-tooltip-test-page.html";
+const IMAGE_TOOLTIP_REQUESTS = 1;
+
+/**
+ * Tests if image responses show a popup in the requests menu when hovered.
+ */
+add_task(function* test() {
+ let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
+ info("Starting test... ");
+
+ let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+ let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+ let { ACTIVITY_TYPE, EVENTS } = windowRequire("devtools/client/netmonitor/constants");
+
+ gStore.dispatch(Actions.batchEnable(false));
+
+ let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
+ let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+ yield performRequests();
+ yield onEvents;
+ yield onThumbnail;
+
+ info("Checking the image thumbnail after a few requests were made...");
+ yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
+
+ // Hide tooltip before next test, to avoid the situation that tooltip covers
+ // the icon for the request of the next test.
+ info("Checking the image thumbnail gets hidden...");
+ yield hideTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
+
+ // +1 extra document reload
+ onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
+ onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+
+ info("Reloading the debuggee and performing all requests again...");
+ yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
+ yield performRequests();
+ yield onEvents;
+ yield onThumbnail;
+
+ info("Checking the image thumbnail after a reload.");
+ yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[1]);
+
+ info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
+ EventUtils.synthesizeMouse(document.body, 0, 0, { type: "mouseout" }, monitor.panelWin);
+ yield waitUntil(() => !document.querySelector(".tooltip-container.tooltip-visible"));
+
+ yield teardown(monitor);
+
+ function performRequests() {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ }
+
+ /**
+ * Show a tooltip on the {target} and verify that it was displayed
+ * with the expected content.
+ */
+ function* showTooltipAndVerify(target) {
+ let anchor = target.querySelector(".requests-list-file-icon");
+ yield showTooltipOn(anchor);
+
+ info("Tooltip was successfully opened for the image request.");
+ is(document.querySelector(".tooltip-panel img").src, TEST_IMAGE_DATA_URI,
+ "The tooltip's image content is displayed correctly.");
+ }
+
+ /**
+ * Trigger a tooltip over an element by sending mousemove event.
+ * @return a promise that resolves when the tooltip is shown
+ */
+ function* showTooltipOn(element) {
+ let win = element.ownerDocument.defaultView;
+ EventUtils.synthesizeMouseAtCenter(element, { type: "mousemove" }, win);
+ yield waitUntil(() => document.querySelector(".tooltip-panel img"));
+ }
+
+ /**
+ * Hide a tooltip on the {target} and verify that it was closed.
+ */
+ function* hideTooltipAndVerify(target) {
+ // Hovering over the "method" column hides the tooltip.
+ let anchor = target.querySelector(".requests-list-method");
+ let win = anchor.ownerDocument.defaultView;
+ EventUtils.synthesizeMouseAtCenter(anchor, { type: "mousemove" }, win);
+
+ yield waitUntil(
+ () => !document.querySelector(".tooltip-container.tooltip-visible"));
+ info("Tooltip was successfully closed.");
+ }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_image-tooltip-test-page.html
@@ -0,0 +1,29 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>tooltip test</p>
+
+ <script type="text/javascript">
+ /* exported performRequests */
+ "use strict";
+
+ function performRequests() {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "test-image.png?v=" + Math.random(), true);
+ xhr.send(null);
+ }
+ </script>
+ </body>
+
+</html>
\ No newline at end of file
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -300,16 +300,25 @@
}
/* File column */
.requests-list-file {
text-align: left;
}
+.requests-list-file-icon {
+ display: inline-block;
+ background: transparent;
+ width: 15px;
+ height: 15px;
+ margin-inline-end: 4px;
+ vertical-align: middle;
+}
+
/* Domain column */
.requests-list-domain {
text-align: left;
}
.requests-list-domain-url {
display: inline-block;