diff --git a/devtools/client/netmonitor/src/components/MdnLink.js b/devtools/client/netmonitor/src/components/MdnLink.js index 23f819127d5e..5dd1921e7c6c 100644 --- a/devtools/client/netmonitor/src/components/MdnLink.js +++ b/devtools/client/netmonitor/src/components/MdnLink.js @@ -35,7 +35,9 @@ function onLearnMoreClick(e, url) { e.preventDefault(); let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); - if (e.button === 1) { + let { button, ctrlKey, metaKey } = e; + let isOSX = Services.appinfo.OS == "Darwin"; + if (button === 1 || (button === 0 && (isOSX ? metaKey : ctrlKey))) { win.openUILinkIn(url, "tabshifted"); } else { win.openUILinkIn(url, "tab"); diff --git a/devtools/client/netmonitor/src/utils/mdn-utils.js b/devtools/client/netmonitor/src/utils/mdn-utils.js index 9297f875f23f..08389e04740e 100644 --- a/devtools/client/netmonitor/src/utils/mdn-utils.js +++ b/devtools/client/netmonitor/src/utils/mdn-utils.js @@ -141,8 +141,9 @@ const SUPPORTED_HTTP_CODES = [ ]; const MDN_URL = "https://developer.mozilla.org/docs/"; -const GA_PARAMS = - "?utm_source=mozilla&utm_medium=devtools-netmonitor&utm_campaign=default"; +const getGAParams = (panelId = "netmonitor") => { + return `?utm_source=mozilla&utm_medium=devtools-${panelId}&utm_campaign=default`; +}; /** * Get the MDN URL for the specified header. @@ -156,7 +157,7 @@ function getHeadersURL(header) { let idx = SUPPORTED_HEADERS.findIndex(item => item.toLowerCase() === lowerCaseHeader); return idx > -1 ? - `${MDN_URL}Web/HTTP/Headers/${SUPPORTED_HEADERS[idx] + GA_PARAMS}` : null; + `${MDN_URL}Web/HTTP/Headers/${SUPPORTED_HEADERS[idx] + getGAParams()}` : null; } /** @@ -166,10 +167,11 @@ function getHeadersURL(header) { * * @return {string} The MDN URL for the HTTP status code, or null if not available. */ -function getHTTPStatusCodeURL(statusCode) { +function getHTTPStatusCodeURL(statusCode, panelId) { let idx = SUPPORTED_HTTP_CODES.indexOf(statusCode); return idx > -1 ? - `${MDN_URL}Web/HTTP/Status/${SUPPORTED_HTTP_CODES[idx] + GA_PARAMS}` : null; + `${MDN_URL}Web/HTTP/Status/${SUPPORTED_HTTP_CODES[idx] + getGAParams(panelId)}` + : null; } /** @@ -178,7 +180,7 @@ function getHTTPStatusCodeURL(statusCode) { * @return {string} the MDN URL of the Timings tag for Network Monitor. */ function getNetMonitorTimingsURL() { - return `${MDN_URL}Tools/Network_Monitor${GA_PARAMS}#Timings`; + return `${MDN_URL}Tools/Network_Monitor${getGAParams()}#Timings`; } /** @@ -187,7 +189,7 @@ function getNetMonitorTimingsURL() { * @return {string} The MDN URL for the documentation of Performance Analysis. */ function getPerformanceAnalysisURL() { - return `${MDN_URL}Tools/Network_Monitor${GA_PARAMS}#Performance_analysis`; + return `${MDN_URL}Tools/Network_Monitor${getGAParams()}#Performance_analysis`; } module.exports = { diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css index 9fec54438ea1..010d696c6421 100644 --- a/devtools/client/themes/webconsole.css +++ b/devtools/client/themes/webconsole.css @@ -1012,6 +1012,8 @@ a.learn-more-link.webconsole-learn-more-link { .webconsole-output-wrapper .message.network .status .status-info .status-code { padding: 0 2px; border-radius: 3px; + text-decoration: none; + font-style: normal; } .webconsole-output-wrapper .message.network .status .status-info .status-code:not([data-code^="3"]) { diff --git a/devtools/client/webconsole/hudservice.js b/devtools/client/webconsole/hudservice.js index fd9531099fa8..4eb3f9613b1c 100644 --- a/devtools/client/webconsole/hudservice.js +++ b/devtools/client/webconsole/hudservice.js @@ -420,9 +420,14 @@ WebConsole.prototype = { * @param string aLink * The URL you want to open in a new tab. */ - openLink: function WC_openLink(aLink) + openLink: function WC_openLink(aLink, e) { - this.chromeUtilsWindow.openUILinkIn(aLink, "tab"); + let isOSX = Services.appinfo.OS == "Darwin"; + if (e != null && (e.button === 1 || (e.button === 0 && (isOSX ? e.metaKey : e.ctrlKey)))) { + this.chromeUtilsWindow.openUILinkIn(aLink, "tabshifted"); + } else { + this.chromeUtilsWindow.openUILinkIn(aLink, "tab"); + } }, /** diff --git a/devtools/client/webconsole/new-console-output/components/Message.js b/devtools/client/webconsole/new-console-output/components/Message.js index f8f538556c7a..5519d75e03cc 100644 --- a/devtools/client/webconsole/new-console-output/components/Message.js +++ b/devtools/client/webconsole/new-console-output/components/Message.js @@ -93,9 +93,9 @@ class Message extends Component { } } - onLearnMoreClick() { + onLearnMoreClick(e) { let {exceptionDocURL} = this.props; - this.props.serviceContainer.openLink(exceptionDocURL); + this.props.serviceContainer.openLink(exceptionDocURL, e); } onContextMenu(e) { diff --git a/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js b/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js index 2ec46d1c1f2f..17e676a32d0e 100644 --- a/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js +++ b/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js @@ -14,6 +14,8 @@ const Message = createFactory(require("devtools/client/webconsole/new-console-ou const actions = require("devtools/client/webconsole/new-console-output/actions/index"); const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages"); const TabboxPanel = createFactory(require("devtools/client/netmonitor/src/components/TabboxPanel")); +const { getHTTPStatusCodeURL } = require("devtools/client/netmonitor/src/utils/mdn-utils"); +const LEARN_MORE = l10n.getStr("webConsoleMoreInfoLabel"); NetworkEventMessage.displayName = "NetworkEventMessage"; @@ -74,7 +76,17 @@ function NetworkEventMessage({ let statusCode, statusInfo; if (httpVersion && status && statusText !== undefined && totalTime !== undefined) { - statusCode = dom.span({className: "status-code", "data-code": status}, status); + let statusCodeDocURL = getHTTPStatusCodeURL(status.toString(), "webconsole"); + statusCode = dom.a({ + className: "status-code", + "data-code": status, + title: LEARN_MORE, + onClick: (e) => { + e.stopPropagation(); + e.preventDefault(); + serviceContainer.openLink(statusCodeDocURL, e); + } + }, status); statusInfo = dom.span( {className: "status-info"}, `[${httpVersion} `, statusCode, ` ${statusText} ${totalTime}ms]` diff --git a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js index 337402cfa517..670e4bc13753 100644 --- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js +++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js @@ -88,8 +88,8 @@ NewConsoleOutputWrapper.prototype = { }])); }, hudProxy: hud.proxy, - openLink: url => { - hud.owner.openLink(url); + openLink: (url, e) => { + hud.owner.openLink(url, e); }, createElement: nodename => { return this.document.createElement(nodename); diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini index 7be0ab0433c0..6735aed3a36c 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini @@ -326,6 +326,7 @@ skip-if = true # Bug 1404886 [browser_webconsole_network_exceptions.js] [browser_webconsole_network_messages_expand.js] [browser_webconsole_network_messages_openinnet.js] +[browser_webconsole_network_messages_status_code.js] [browser_webconsole_network_requests_from_chrome.js] [browser_webconsole_network_reset_filter.js] [browser_webconsole_nodes_highlight.js] diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_allow_mixedcontent_securityerrors.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_allow_mixedcontent_securityerrors.js index c7312a407be3..64974b0ebbce 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_allow_mixedcontent_securityerrors.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_allow_mixedcontent_securityerrors.js @@ -43,8 +43,30 @@ add_task(async function () { const mixedActiveContentMessage = await onMixedActiveContent; ok(true, "Mixed active content warning message is visible"); + const checkLink = ({ link, where, expectedLink, expectedTab }) => { + is(link, expectedLink, `Clicking the provided link opens ${link}`); + is(where, expectedTab, `Clicking the provided link opens in expected tab`); + } + info("Clicking on the Learn More link"); const learnMoreLink = mixedActiveContentMessage.querySelector(".learn-more-link"); - const url = await simulateLinkClick(learnMoreLink); - is(url, LEARN_MORE_URI, `Clicking the provided link opens ${url}`); + let linkSimulation = await simulateLinkClick(learnMoreLink); + checkLink({ + ...linkSimulation, + expectedLink: LEARN_MORE_URI, + expectedTab: "tab" + }); + + let isOSX = Services.appinfo.OS == "Darwin"; + let ctrlOrCmdKeyMouseEvent = new MouseEvent("click", { + bubbles: true, + [isOSX ? "metaKey" : "ctrlKey"]: true, + view: window + }); + linkSimulation = await simulateLinkClick(learnMoreLink, ctrlOrCmdKeyMouseEvent); + checkLink({ + ...linkSimulation, + expectedLink: LEARN_MORE_URI, + expectedTab: "tabshifted" + }); }); diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_block_mixedcontent_securityerrors.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_block_mixedcontent_securityerrors.js index f8846f31f63a..7b0cdabdf038 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_block_mixedcontent_securityerrors.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_block_mixedcontent_securityerrors.js @@ -49,8 +49,8 @@ add_task(async function() { info("Clicking on the Learn More link"); let learnMoreLink = blockedMixedActiveContentMessage.querySelector(".learn-more-link"); - let url = await simulateLinkClick(learnMoreLink); - is(url, LEARN_MORE_URI, `Clicking the provided link opens ${url}`); + let response = await simulateLinkClick(learnMoreLink); + is(response.link, LEARN_MORE_URI, `Clicking the provided link opens ${response.link}`); info("Test disabling mixed content protection"); @@ -74,8 +74,8 @@ add_task(async function() { info("Clicking on the Learn More link"); learnMoreLink = mixedActiveContentMessage.querySelector(".learn-more-link"); - url = await simulateLinkClick(learnMoreLink); - is(url, LEARN_MORE_URI, `Clicking the provided link opens ${url}`); + response = await simulateLinkClick(learnMoreLink); + is(response.link, LEARN_MORE_URI, `Clicking the provided link opens ${response.link}`); }); function pushPrefEnv() { diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_status_code.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_status_code.js new file mode 100644 index 000000000000..63f700d48ea8 --- /dev/null +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_status_code.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_FILE = "test-network-request.html"; +const TEST_PATH = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/"; +const TEST_URI = TEST_PATH + TEST_FILE; + +const NET_PREF = "devtools.webconsole.filter.net"; +const XHR_PREF = "devtools.webconsole.filter.netxhr"; +let { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages"); +const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/HTTP/Status/200" + STATUS_CODES_GA_PARAMS; + +pushPref(NET_PREF, true); +pushPref(XHR_PREF, true); + +add_task(async function task() { + const hud = await openNewTabAndConsole(TEST_URI); + + const currentTab = gBrowser.selectedTab; + let target = TargetFactory.forTab(currentTab); + let toolbox = gDevTools.getToolbox(target); + let {ui} = toolbox.getCurrentPanel().hud; + const onNetworkMessageUpdate = ui.jsterm.hud.once("network-message-updated"); + + // Fire an XHR POST request. + await ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + content.wrappedJSObject.testXhrPost(); + }); + + info("XHR executed"); + await onNetworkMessageUpdate; + + let xhrUrl = TEST_PATH + "test-data.json"; + let messageNode = await waitFor(() => findMessage(hud, xhrUrl)); + let urlNode = messageNode.querySelector(".url"); + let statusCodeNode = messageNode.querySelector(".status-code"); + info("Network message found."); + + ok(statusCodeNode.title, l10n.getStr("webConsoleMoreInfoLabel")); + let { + middleMouseEvent, + ctrlOrCmdKeyMouseEvent, + rightClickMouseEvent, + rightClickCtrlOrCmdKeyMouseEvent, + } = getMouseEvents(); + + let testCases = [ + { clickEvent: middleMouseEvent, link: LEARN_MORE_URI, where: "tabshifted" }, + { clickEvent: null, link: LEARN_MORE_URI, where: "tab" }, + { clickEvent: ctrlOrCmdKeyMouseEvent, link: LEARN_MORE_URI, where: "tabshifted" }, + { clickEvent: rightClickMouseEvent, link: null, where: null }, + { clickEvent: rightClickCtrlOrCmdKeyMouseEvent, link: null, where: null } + ]; + + for (let testCase of testCases) { + const { clickEvent } = testCase; + let onConsoleMenuOpened = [rightClickMouseEvent, rightClickCtrlOrCmdKeyMouseEvent].includes(clickEvent) ? + hud.ui.newConsoleOutput.once("menu-open") : null; + + let { link, where } = await simulateLinkClick(statusCodeNode, testCase.clickEvent); + is(link, testCase.link, `Clicking the provided link opens ${link}`); + is(where, testCase.where, `Link opened in correct tab`); + + if (onConsoleMenuOpened) { + info("Check if context menu is opened on right clicking the status-code"); + await onConsoleMenuOpened; + } + } +}); + +function getMouseEvents() { + let isOSX = Services.appinfo.OS == "Darwin"; + + let middleMouseEvent = new MouseEvent("click", { + bubbles: true, + button: 1, + view: window + }); + let ctrlOrCmdKeyMouseEvent = new MouseEvent("click", { + bubbles: true, + [isOSX ? "metaKey" : "ctrlKey"]: true, + view: window + }); + let rightClickMouseEvent = new MouseEvent("contextmenu", { + bubbles: true, + button: 2, + view: window + }); + let rightClickCtrlOrCmdKeyMouseEvent = new MouseEvent("contextmenu", { + bubbles: true, + button: 2, + [isOSX ? "metaKey" : "ctrlKey"]: true, + view: window + }); + + return { + middleMouseEvent, + ctrlOrCmdKeyMouseEvent, + rightClickMouseEvent, + rightClickCtrlOrCmdKeyMouseEvent, + }; +} diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/head.js b/devtools/client/webconsole/new-console-output/test/mochitest/head.js index c767f9e241fd..f795e00c2ead 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js @@ -20,9 +20,16 @@ Services.scriptloader.loadSubScript( var {HUDService} = require("devtools/client/webconsole/hudservice"); var WCUL10n = require("devtools/client/webconsole/webconsole-l10n"); -const DOCS_GA_PARAMS = "?utm_source=mozilla" + - "&utm_medium=firefox-console-errors" + - "&utm_campaign=default"; +const DOCS_GA_PARAMS = `?${new URLSearchParams({ + "utm_source": "mozilla", + "utm_medium": "firefox-console-errors", + "utm_campaign": "default" +})}`; +const STATUS_CODES_GA_PARAMS = `?${new URLSearchParams({ + "utm_source": "mozilla", + "utm_medium": "devtools-webconsole", + "utm_campaign": "default" +})}`; Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true); registerCleanupFunction(function* () { @@ -413,24 +420,53 @@ async function closeConsole(tab = gBrowser.selectedTab) { * Fake clicking a link and return the URL we would have navigated to. * This function should be used to check external links since we can't access * network in tests. + * This can also be used to test that a click will not be fired. * * @param ElementNode element * The element we want to simulate click on. + * @param Object clickEventProps + * The custom properties which would be used to dispatch a click event * @returns Promise - * A Promise that resolved when the link clik simulation occured. + * A Promise that is resolved when the link click simulation occured or when the click is not dispatched. + * The promise resolves with an object that holds the following properties + * - link: url of the link or null(if event not fired) + * - where: "tab" if tab is active or "tabshifted" if tab is inactive or null(if event not fired) */ -function simulateLinkClick(element) { - return new Promise((resolve) => { - // Override openUILinkIn to prevent navigating. - let oldOpenUILinkIn = window.openUILinkIn; - window.openUILinkIn = function (link) { +function simulateLinkClick(element, clickEventProps) { + // Override openUILinkIn to prevent navigating. + let oldOpenUILinkIn = window.openUILinkIn; + + const onOpenLink = new Promise((resolve) => { + window.openUILinkIn = function (link, where) { window.openUILinkIn = oldOpenUILinkIn; - resolve(link); + resolve({link: link, where}); }; - // Click on the link. - element.click(); + if (clickEventProps) { + // Click on the link using the event properties. + element.dispatchEvent(clickEventProps); + } else { + // Click on the link. + element.click(); + } }); + + // Declare a timeout Promise that we can use to make sure openUILinkIn was not called. + let timeoutId; + const onTimeout = new Promise(function(resolve, reject) { + timeoutId = setTimeout(() => { + window.openUILinkIn = oldOpenUILinkIn; + timeoutId = null; + resolve({link: null, where: null}); + }, 1000); + }); + + onOpenLink.then(() => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }); + return Promise.race([onOpenLink, onTimeout]); } /**