Bug 1320233 - Add Learn More link to headers in Network panel. r=Honza
authorEduardo Boucas <mail@eduardoboucas.com>
Fri, 03 Feb 2017 17:19:20 +0000
changeset 340879 031087a1f3c6
parent 340845 be38817f54c5
child 340880 dee894afe7bb
push id31318
push usercbook@mozilla.com
push dateMon, 06 Feb 2017 11:56:59 +0000
treeherdermozilla-central@1cc159c7a044 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1320233
milestone54.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
Bug 1320233 - Add Learn More link to headers in Network panel. r=Honza
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/shared/components/headers-mdn.js
devtools/client/netmonitor/shared/components/headers-panel.js
devtools/client/netmonitor/shared/components/moz.build
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_header-docs.js
devtools/client/shared/components/tree/tree-view.css
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -744,8 +744,12 @@ netmonitor.custom.send=Send
 
 # LOCALIZATION NOTE (netmonitor.custom.cancel): This is the label displayed
 # on the button which cancels and closes the custom request form
 netmonitor.custom.cancel=Cancel
 
 # LOCALIZATION NOTE (netmonitor.backButton): This is the label displayed
 # on the button which exists the performance statistics view
 netmonitor.backButton=Back
+
+# LOCALIZATION NOTE (netmonitor.headers.learnMore): This is the label displayed
+# next to a header list item, with a link to external documentation
+netmonitor.headers.learnMore=Learn More
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/headers-mdn.js
@@ -0,0 +1,119 @@
+/* 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/. */
+
+/**
+ * A mapping of header names to external documentation. Any header included
+ * here will show a "Learn More" link alongside it.
+ */
+
+"use strict";
+
+var URL_DOMAIN = "https://developer.mozilla.org";
+const URL_PATH = "/en-US/docs/Web/HTTP/Headers/";
+const URL_PARAMS =
+  "?utm_source=mozilla&utm_medium=devtools-netmonitor&utm_campaign=default";
+
+var SUPPORTED_HEADERS = [
+  "Accept",
+  "Accept-Charset",
+  "Accept-Encoding",
+  "Accept-Language",
+  "Accept-Ranges",
+  "Access-Control-Allow-Credentials",
+  "Access-Control-Allow-Headers",
+  "Access-Control-Allow-Methods",
+  "Access-Control-Allow-Origin",
+  "Access-Control-Expose-Headers",
+  "Access-Control-Max-Age",
+  "Access-Control-Request-Headers",
+  "Access-Control-Request-Method",
+  "Age",
+  "Cache-Control",
+  "Connection",
+  "Content-Disposition",
+  "Content-Encoding",
+  "Content-Language",
+  "Content-Length",
+  "Content-Location",
+  "Content-Security-Policy",
+  "Content-Security-Policy-Report-Only",
+  "Content-Type",
+  "Cookie",
+  "Cookie2",
+  "DNT",
+  "Date",
+  "ETag",
+  "Expires",
+  "From",
+  "Host",
+  "If-Match",
+  "If-Modified-Since",
+  "If-None-Match",
+  "If-Range",
+  "If-Unmodified-Since",
+  "Keep-Alive",
+  "Last-Modified",
+  "Location",
+  "Origin",
+  "Pragma",
+  "Public-Key-Pins",
+  "Public-Key-Pins-Report-Only",
+  "Referer",
+  "Referrer-Policy",
+  "Retry-After",
+  "Server",
+  "Set-Cookie",
+  "Set-Cookie2",
+  "Strict-Transport-Security",
+  "TE",
+  "Tk",
+  "Trailer",
+  "Transfer-Encoding",
+  "Upgrade-Insecure-Requests",
+  "User-Agent",
+  "Vary",
+  "Via",
+  "Warning",
+  "X-Content-Type-Options",
+  "X-DNS-Prefetch-Control",
+  "X-Frame-Options",
+  "X-XSS-Protection"
+];
+
+/**
+ * Get the MDN URL for the specified header
+ *
+ * @param {string} Name of the header
+ * The baseURL to use.
+ *
+ * @return {string}
+ * The MDN URL for the header, or null if not available.
+ */
+exports.getURL = (header) => {
+  if (SUPPORTED_HEADERS.indexOf(header) === -1) {
+    return null;
+  }
+
+  return URL_DOMAIN + URL_PATH + header + URL_PARAMS;
+};
+
+/**
+ * Use a different domain for the URLs. Used only for testing.
+ *
+ * @param {string} domain
+ * The domain to use.
+ */
+exports.setDomain = (domain) => {
+  URL_DOMAIN = domain;
+};
+
+/**
+ * Use a different list of supported headers. Used only for testing.
+ *
+ * @param {array} headers
+ * The supported headers to use.
+ */
+exports.setSupportedHeaders = (headers) => {
+  SUPPORTED_HEADERS = headers;
+};
--- a/devtools/client/netmonitor/shared/components/headers-panel.js
+++ b/devtools/client/netmonitor/shared/components/headers-panel.js
@@ -10,21 +10,26 @@ const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../../l10n");
 const { writeHeaderText } = require("../../request-utils");
 const { getFormattedSize } = require("../../utils/format-utils");
+const Services = require("Services");
+const { gDevTools } = require("devtools/client/framework/devtools");
+const HeadersMDN = require("devtools/client/netmonitor/shared/components/headers-mdn");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const Rep = createFactory(REPS.Rep);
 
 // Components
 const PropertiesView = createFactory(require("./properties-view"));
 
-const { div, input, textarea } = DOM;
+const { a, div, input, textarea } = DOM;
 const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend");
 const RAW_HEADERS = L10N.getStr("netmonitor.summary.rawHeaders");
 const RAW_HEADERS_REQUEST = L10N.getStr("netmonitor.summary.rawHeaders.requestHeaders");
 const RAW_HEADERS_RESPONSE = L10N.getStr("netmonitor.summary.rawHeaders.responseHeaders");
 const HEADERS_EMPTY_TEXT = L10N.getStr("headersEmptyText");
 const HEADERS_FILTER_TEXT = L10N.getStr("headersFilterText");
 const REQUEST_HEADERS = L10N.getStr("requestHeaders");
 const REQUEST_HEADERS_FROM_UPLOAD = L10N.getStr("requestHeadersFromUpload");
@@ -40,16 +45,17 @@ const SUMMARY_VERSION = L10N.getStr("net
  * Lists basic information about the request
  */
 const HeadersPanel = createClass({
   displayName: "HeadersPanel",
 
   propTypes: {
     cloneSelectedRequest: PropTypes.func.isRequired,
     request: PropTypes.object.isRequired,
+    renderValue: PropTypes.func
   },
 
   getInitialState() {
     return {
       rawHeadersOpened: false,
     };
   },
 
@@ -208,15 +214,54 @@ const HeadersPanel = createClass({
           summaryStatus,
           summaryVersion,
           summaryRawHeaders,
         ),
         PropertiesView({
           object,
           filterPlaceHolder: HEADERS_FILTER_TEXT,
           sectionNames: Object.keys(object),
+          renderValue
         }),
       )
     );
   }
 });
 
+function onLearnMoreClick(e, headerDocURL) {
+  e.stopPropagation();
+  e.preventDefault();
+
+  let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+  win.openUILinkIn(headerDocURL, "tab");
+}
+
+function renderValue(props) {
+  const { member, value } = props;
+
+  if (typeof value !== "string") {
+    return null;
+  }
+
+  let headerDocURL = HeadersMDN.getURL(member.name);
+
+  return (
+    div({ className: "treeValueCellDivider" },
+      Rep(Object.assign(props, {
+        // FIXME: A workaround for the issue in StringRep
+        // Force StringRep to crop the text everytime
+        member: Object.assign({}, member, { open: false }),
+        mode: MODE.TINY,
+        cropLimit: 60,
+      })),
+      headerDocURL ?
+        a({
+          className: "learn-more-link",
+          title: headerDocURL,
+          onClick: (e) => onLearnMoreClick(e, headerDocURL),
+        }, `[${L10N.getStr("netmonitor.headers.learnMore")}]`)
+        :
+        null
+    )
+  );
+}
+
 module.exports = HeadersPanel;
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,16 +1,17 @@
 # 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/.
 
 DevToolsModules(
     'cookies-panel.js',
     'details-panel.js',
     'editor.js',
+    'headers-mdn.js',
     'headers-panel.js',
     'params-panel.js',
     'preview-panel.js',
     'properties-view.js',
     'response-panel.js',
     'security-panel.js',
     'timings-panel.js',
 )
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -93,16 +93,17 @@ subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_frame.js]
+[browser_net_header-docs.js]
 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]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_header-docs.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const HeadersMDN = require("devtools/client/netmonitor/shared/components/headers-mdn");
+
+/**
+ * Tests if "Learn More" links are correctly displayed
+ * next to headers.
+ */
+add_task(function* () {
+  let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
+  info("Starting test... ");
+
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
+
+  RequestsMenu.lazyUpdate = false;
+
+  let wait = waitForNetworkEvents(monitor, 0, 2);
+  yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+    content.wrappedJSObject.performRequests();
+  });
+  yield wait;
+
+  let origItem = RequestsMenu.getItemAtIndex(0);
+  RequestsMenu.selectedItem = origItem;
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelectorAll(".request-list-item")[0]);
+
+  testShowLearnMore(origItem);
+
+  return teardown(monitor);
+
+  /*
+   * Tests that a "Learn More" button is only shown if
+   * and only if a header is documented in MDN.
+   */
+  function testShowLearnMore(data) {
+    document.querySelectorAll(".properties-view .treeRow.stringRow").forEach((rowEl, index) => {
+      let headerName = rowEl.querySelectorAll(".treeLabelCell .treeLabel")[0].textContent;
+      let headerDocURL = HeadersMDN.getURL(headerName);
+      let learnMoreEl = rowEl.querySelectorAll(".treeValueCell .learn-more-link");
+
+      if (headerDocURL === null) {
+        ok(learnMoreEl.length === 0,
+          "undocumented header does not include a \"Learn More\" button");
+      } else {
+        ok(learnMoreEl[0].getAttribute("title") === headerDocURL,
+          "documented header includes a \"Learn More\" button with a link to MDN");
+      }
+    });
+  }
+});
--- a/devtools/client/shared/components/tree/tree-view.css
+++ b/devtools/client/shared/components/tree/tree-view.css
@@ -72,16 +72,33 @@
   text-decoration: underline;
 }
 
 /* Filtering */
 .treeTable .treeRow.hidden {
   display: none;
 }
 
+.treeTable .treeValueCellDivider {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+
+/* Learn More link */
+.treeTable .treeValueCell .learn-more-link {
+  color: var(--theme-highlight-blue);
+  cursor: pointer;
+  margin: 0 5px;
+}
+
+.treeTable .treeValueCell .learn-more-link:hover {
+  text-decoration: underline;
+}
+
 /******************************************************************************/
 /* Toggle Icon */
 
 .treeTable .treeRow .treeIcon {
   height: 14px;
   width: 14px;
   font-size: 10px; /* Set the size of loading spinner */
   display: inline-block;