/* 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"; // React & Redux const { createFactory, createElement, } = require("resource://devtools/client/shared/vendor/react.js"); const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); const Message = createFactory( require("resource://devtools/client/webconsole/components/Output/Message.js") ); const actions = require("resource://devtools/client/webconsole/actions/index.js"); const { isMessageNetworkError, l10n, } = require("resource://devtools/client/webconsole/utils/messages.js"); loader.lazyRequireGetter( this, "TabboxPanel", "resource://devtools/client/netmonitor/src/components/TabboxPanel.js" ); const { getHTTPStatusCodeURL, } = require("resource://devtools/client/netmonitor/src/utils/doc-utils.js"); const { getUnicodeUrl, } = require("resource://devtools/client/shared/unicode-url.js"); loader.lazyRequireGetter( this, "BLOCKED_REASON_MESSAGES", "resource://devtools/client/netmonitor/src/constants.js", true ); const LEARN_MORE = l10n.getStr("webConsoleMoreInfoLabel"); const isMacOS = Services.appinfo.OS === "Darwin"; NetworkEventMessage.displayName = "NetworkEventMessage"; NetworkEventMessage.propTypes = { message: PropTypes.object.isRequired, serviceContainer: PropTypes.shape({ openNetworkPanel: PropTypes.func.isRequired, resendNetworkRequest: PropTypes.func.isRequired, }), timestampsVisible: PropTypes.bool.isRequired, networkMessageUpdate: PropTypes.object.isRequired, }; /** * This component is responsible for rendering network messages * in the Console panel. * * Network logs are expandable and the user can inspect it inline * within the Console panel (no need to switch to the Network panel). * * HTTP details are rendered using `TabboxPanel` component used to * render contents of the side bar in the Network panel. * * All HTTP details data are fetched from the backend on-demand * when the user is expanding network log for the first time. */ function NetworkEventMessage({ message = {}, serviceContainer, timestampsVisible, networkMessageUpdate = {}, networkMessageActiveTabId, dispatch, open, disabled, }) { const { id, indent, source, type, level, url, method, isXHR, timeStamp, blockedReason, httpVersion, status, statusText, totalTime, } = message; const topLevelClasses = ["cm-s-mozilla"]; if (isMessageNetworkError(message)) { topLevelClasses.push("error"); } let statusCode, statusInfo; if ( httpVersion && status && statusText !== undefined && totalTime !== undefined ) { const statusCodeDocURL = getHTTPStatusCodeURL( status.toString(), "webconsole" ); statusCode = dom.span( { 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]` ); } if (blockedReason) { statusInfo = dom.span( { className: "status-info" }, BLOCKED_REASON_MESSAGES[blockedReason] ); topLevelClasses.push("network-message-blocked"); } // Message body components. const requestMethod = dom.span({ className: "method" }, method); const xhr = isXHR ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator")) : null; const unicodeURL = getUnicodeUrl(url); const requestUrl = dom.a( { className: "url", title: unicodeURL, href: url, onClick: e => { // The href of the is the actual URL, so we need to prevent the navigation // within the console panel. // We only want to handle Ctrl/Cmd + click to open the link in a new tab. e.preventDefault(); const shouldOpenLink = (isMacOS && e.metaKey) || (!isMacOS && e.ctrlKey); if (shouldOpenLink) { e.stopPropagation(); serviceContainer.openLink(url, e); } }, }, unicodeURL ); const statusBody = statusInfo ? dom.a({ className: "status" }, statusInfo) : null; const messageBody = [xhr, requestMethod, requestUrl, statusBody]; // API consumed by Net monitor UI components. Most of the method // are not needed in context of the Console panel (atm) and thus // let's just provide empty implementation. // Individual methods might be implemented step by step as needed. const connector = { viewSourceInDebugger: (srcUrl, line, column) => { serviceContainer.onViewSourceInDebugger({ url: srcUrl, line, column }); }, getLongString: grip => { return serviceContainer.getLongString(grip); }, triggerActivity: () => {}, requestData: (requestId, dataType) => { return serviceContainer.requestData(requestId, dataType); }, }; // Only render the attachment if the network-event is // actually opened (performance optimization) and its not disabled. const attachment = open && !disabled && dom.div( { className: "network-info network-monitor", }, createElement(TabboxPanel, { connector, activeTabId: networkMessageActiveTabId, request: networkMessageUpdate, sourceMapURLService: serviceContainer.sourceMapURLService, openLink: serviceContainer.openLink, selectTab: tabId => { dispatch(actions.selectNetworkMessageTab(tabId)); }, openNetworkDetails: enabled => { if (!enabled) { dispatch(actions.messageClose(id)); } }, hideToggleButton: true, showMessagesView: false, }) ); const request = { url, method }; return Message({ dispatch, messageId: id, source, type, level, indent, collapsible: true, open, disabled, attachment, topLevelClasses, timeStamp, messageBody, serviceContainer, request, timestampsVisible, isBlockedNetworkMessage: !!blockedReason, message, }); } module.exports = NetworkEventMessage;