Bug 1482454 - add learn more links across accessibility panel. r=gl, flod
authorYura Zenevich <yura.zenevich@gmail.com>
Mon, 20 Aug 2018 14:44:12 -0400
changeset 487939 5c44264ed1fec9b4fad27c0c18de7d383597d847
parent 487938 bcb838fd92f08e167ec87877904831a408fb0120
child 487940 d6e4d3e69d4c8331cfa35c318b616ed390d3538d
child 487977 94cfd6113ca7c7efef708d2b0af8bcd5703d7fe2
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl, flod
bugs1482454
milestone63.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 1482454 - add learn more links across accessibility panel. r=gl, flod MozReview-Commit-ID: GWrdjnzlS8b
devtools/client/accessibility/accessibility.css
devtools/client/accessibility/components/Description.js
devtools/client/accessibility/components/LearnMoreLink.js
devtools/client/accessibility/components/Toolbar.js
devtools/client/accessibility/components/moz.build
devtools/client/accessibility/constants.js
devtools/client/accessibility/test/mochitest/chrome.ini
devtools/client/accessibility/test/mochitest/test_accessible_learnMoreLink.html
devtools/client/locales/en-US/accessibility.properties
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -9,21 +9,25 @@
   --accessibility-toolbar-focus: var(--blue-50);
   --accessibility-toolbar-focus-alpha30: rgba(10, 132, 255, 0.3);
   --accessibility-full-length-minus-splitter: calc(100% - 1px);
   --accessibility-horizontal-padding: 5px;
   --accessibility-arrow-horizontal-padding: 4px;
   --accessibility-tree-row-height: 21px;
   --accessibility-unfocused-tree-focused-node-background: var(--grey-20);
   --accessibility-tree-focused-node-twisty-brightness: brightness(20%);
+  --accessibility-link-color: var(--blue-60);
+  --accessibility-link-color-active: var(--blue-70);
 }
 
 :root.theme-dark {
   --accessibility-unfocused-tree-focused-node-background: var(--grey-70);
   --accessibility-tree-focused-node-twisty-brightness: unset;
+  --accessibility-link-color: var(--theme-highlight-blue);
+  --accessibility-link-color-active: var(--blue-40);
 }
 
 /* General */
 html,
 body {
   height: 100%;
   margin: 0;
   padding: 0;
@@ -87,46 +91,109 @@ body {
 .devtools-button:focus > .btn-content:not(.devtools-throbber) {
   outline: 2px solid var(--accessibility-toolbar-focus);
   outline-offset: -2px;
   box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus-alpha30);
   border-radius: 2px;
   -moz-outline-radius: 2px;
 }
 
+.devtools-toolbar {
+  display: flex;
+  align-items: center;
+}
+
+.devtools-toolbar .help {
+  cursor: pointer;
+  width: 18px;
+  margin-inline-start: auto;
+  margin-inline-end: 3px;
+  background: transparent;
+}
+
+.devtools-toolbar .help .btn-content {
+  display: block;
+  padding: 0;
+  background-color: var(--theme-body-color);
+  width: 16px;
+  height: 16px;
+  mask: url("chrome://devtools/skin/images/help.svg") no-repeat;
+}
+
+.devtools-toolbar .help:focus {
+  outline: 2px solid var(--accessibility-toolbar-focus);
+  box-shadow: 0 0 0 3px var(--accessibility-toolbar-focus-alpha30);
+  border-radius: 2px;
+  outline-offset: -1px;
+  -moz-outline-radius: 2px;
+}
+
+.devtools-toolbar .help:focus > .btn-content {
+  outline: none;
+  box-shadow: none;
+}
+
 /* Description */
 .description {
   color: var(--theme-toolbar-color);
   font: message-box;
   font-size: calc(var(--accessibility-font-size) + 1px);
   margin: auto;
   padding-top: 15vh;
   width: 50vw;
 }
 
+/* To ensure that the message does not look squished in vertical mode, increase its width
+   when the toolbox is narrow */
+@media (max-width: 700px) {
+  .description {
+    width: 80vw;
+  }
+}
+
 .description .general {
   display: flex;
   align-items: center;
-  margin-bottom: 1.7em;
+  margin-bottom: 1em;
 }
 
 .description img {
   margin-right: 12px;
   flex-basis: 42px;
   flex-shrink: 0;
   -moz-context-properties: fill;
   fill: var(--grey-40);
 }
 
 .description .devtools-button {
   display: flex;
   align-items: center;
   margin: auto;
 }
 
+.description .link {
+  color: var(--accessibility-link-color);
+  cursor: pointer;
+  outline: 0;
+}
+
+.description .link:hover:not(:focus) {
+  text-decoration: underline;
+}
+
+.description .link:focus:not(:active) {
+  box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus), 0 0 0 4px var(--accessibility-toolbar-focus-alpha30);
+  border-radius: 2px;
+}
+
+.description .link:active {
+  color: var(--accessibility-link-color-active);
+  text-decoration: underline;
+}
+
 /* TreeView Customization */
 .split-box:not(.horz) .main-panel {
   height: calc(100vh - var(--accessibility-toolbar-height));
 }
 
 .treeTable > thead {
   position: sticky;
   top: 0;
--- a/devtools/client/accessibility/components/Description.js
+++ b/devtools/client/accessibility/components/Description.js
@@ -7,22 +7,23 @@
 
 // React & Redux
 const { createFactory, Component } = require("devtools/client/shared/vendor/react");
 const { div, p, img } = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const Button = createFactory(require("./Button"));
+const LearnMoreLink = createFactory(require("./LearnMoreLink"));
 const { enable, updateCanBeEnabled } = require("../actions/ui");
 
 // Localization
 const { L10N } = require("../utils/l10n");
 
-const { A11Y_SERVICE_ENABLED_COUNT } = require("../constants");
+const { A11Y_LEARN_MORE_LINK, A11Y_SERVICE_ENABLED_COUNT } = require("../constants");
 
 class OldVersionDescription extends Component {
   render() {
     return (
       div({ className: "description" },
         p({ className: "general" },
           img({
             src: "chrome://devtools/skin/images/accessibility.svg",
@@ -94,23 +95,33 @@ class Description extends Component {
     if (canBeEnabled) {
       title = L10N.getStr("accessibility.enable.enabledTitle");
     } else {
       disableButton = true;
       title = L10N.getStr("accessibility.enable.disabledTitle");
     }
 
     return (
-      div({ className: "description" },
-        p({ className: "general" },
+      div({ className: "description", role: "presentation" },
+        div({ className: "general", role: "presentation" },
           img({
             src: "chrome://devtools/skin/images/accessibility.svg",
             alt: L10N.getStr("accessibility.logo")
           }),
-          L10N.getStr("accessibility.description.general")),
+          div({ role: "presentation" },
+            LearnMoreLink({
+              href: A11Y_LEARN_MORE_LINK +
+                    "?utm_source=devtools&utm_medium=a11y-panel-description",
+              learnMoreStringKey: "accessibility.learnMore",
+              l10n: L10N,
+              messageStringKey: "accessibility.description.general.p1"
+            }),
+            p({}, L10N.getStr("accessibility.description.general.p2"))
+          )
+        ),
         Button({
           id: "accessibility-enable-button",
           onClick: this.onEnable,
           disabled: enabling || disableButton,
           busy: enabling,
           "data-standalone": true,
           title
         }, L10N.getStr(enableButtonStr))
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/LearnMoreLink.js
@@ -0,0 +1,60 @@
+/* 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 { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { p, a } = require("devtools/client/shared/vendor/react-dom-factories");
+
+const { openDocLink } = require("devtools/client/shared/link");
+
+/**
+ * Localization friendly component for rendering a block of text with a "Learn more" link
+ * as a part of it.
+ */
+class LearnMoreLink extends Component {
+  static get propTypes() {
+    return {
+      href: PropTypes.string,
+      learnMoreStringKey: PropTypes.string.isRequired,
+      l10n: PropTypes.object.isRequired,
+      messageStringKey: PropTypes.string.isRequired,
+      onClick: PropTypes.func
+    };
+  }
+
+  static get defaultProps() {
+    return {
+      href: "#",
+      learnMoreStringKey: null,
+      l10n: null,
+      messageStringKey: null,
+      onClick: LearnMoreLink.openDocOnClick
+    };
+  }
+
+  static openDocOnClick(event) {
+    event.preventDefault();
+    openDocLink(event.target.href);
+  }
+
+  render() {
+    const { href, learnMoreStringKey, l10n, messageStringKey, onClick } = this.props;
+    const learnMoreString = l10n.getStr(learnMoreStringKey);
+    const messageString = l10n.getFormatStr(messageStringKey, learnMoreString);
+
+    // Split the paragraph string with the link as a separator, and include the link into
+    // results.
+    const re = new RegExp(`(\\b${learnMoreString}\\b)`);
+    const contents = messageString.split(re);
+    contents[1] = a({ className: "link", href, onClick }, contents[1]);
+
+    return (
+      p({}, ...contents)
+    );
+  }
+}
+
+module.exports = LearnMoreLink;
--- a/devtools/client/accessibility/components/Toolbar.js
+++ b/devtools/client/accessibility/components/Toolbar.js
@@ -8,16 +8,19 @@ const { createFactory, Component } = req
 const { div } = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { L10N } = require("../utils/l10n");
 const Button = createFactory(require("./Button"));
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { disable, updateCanBeDisabled } = require("../actions/ui");
 
+const { A11Y_LEARN_MORE_LINK } = require("../constants");
+const { openDocLink } = require("devtools/client/shared/link");
+
 class Toolbar extends Component {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       accessibility: PropTypes.object.isRequired,
       canBeDisabled: PropTypes.bool.isRequired
     };
   }
@@ -51,16 +54,21 @@ class Toolbar extends Component {
     const { accessibility, dispatch } = this.props;
     this.setState({ disabling: true });
 
     dispatch(disable(accessibility))
       .then(() => this.setState({ disabling: false }))
       .catch(() => this.setState({ disabling: false }));
   }
 
+  onLearnMoreClick() {
+    openDocLink(A11Y_LEARN_MORE_LINK +
+      "?utm_source=devtools&utm_medium=a11y-panel-toolbar");
+  }
+
   render() {
     const { canBeDisabled } = this.props;
     const { disabling } = this.state;
     const disableButtonStr = disabling ?
       "accessibility.disabling" : "accessibility.disable";
     let title;
     let isDisabled = false;
 
@@ -77,17 +85,22 @@ class Toolbar extends Component {
         role: "toolbar"
       }, Button({
         className: "disable",
         id: "accessibility-disable-button",
         onClick: this.onDisable,
         disabled: disabling || isDisabled,
         busy: disabling,
         title
-      }, L10N.getStr(disableButtonStr)))
+      }, L10N.getStr(disableButtonStr)),
+      Button({
+        className: "help",
+        title: L10N.getStr("accessibility.learnMore"),
+        onClick: this.onLearnMoreClick
+      }))
     );
   }
 }
 
 const mapStateToProps = ({ ui }) => ({
   canBeDisabled: ui.canBeDisabled
 });
 
--- a/devtools/client/accessibility/components/moz.build
+++ b/devtools/client/accessibility/components/moz.build
@@ -3,12 +3,13 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'AccessibilityRow.js',
     'AccessibilityTree.js',
     'Accessible.js',
     'Button.js',
     'Description.js',
+    'LearnMoreLink.js',
     'MainFrame.js',
     'RightSidebar.js',
     'Toolbar.js'
 )
--- a/devtools/client/accessibility/constants.js
+++ b/devtools/client/accessibility/constants.js
@@ -58,8 +58,12 @@ exports.ACCESSIBLE_EVENTS = [
   "text-change",
   "value-change",
   "index-in-parent-change"
 ];
 
 // Telemetry name constants.
 exports.A11Y_SERVICE_DURATION = "DEVTOOLS_ACCESSIBILITY_SERVICE_TIME_ACTIVE_SECONDS";
 exports.A11Y_SERVICE_ENABLED_COUNT = "devtools.accessibility.service_enabled_count";
+
+// URL constants
+exports.A11Y_LEARN_MORE_LINK =
+  "https://developer.mozilla.org/docs/Tools/Accessibility_inspector";
--- a/devtools/client/accessibility/test/mochitest/chrome.ini
+++ b/devtools/client/accessibility/test/mochitest/chrome.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 support-files =
   head.js
 
+[test_accessible_learnMoreLink.html]
 [test_accessible_openLink.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/mochitest/test_accessible_learnMoreLink.html
@@ -0,0 +1,95 @@
+<!-- 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/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that LearnMoreLink parses and renders correctly text with learn more links.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>LearnMoreLink component test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
+
+"use strict";
+
+window.onload = async function() {
+  try {
+    const { gDevTools } = require("devtools/client/framework/devtools");
+    const Services = browserRequire("Services");
+    const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
+    const { createFactory } = browserRequire("devtools/client/shared/vendor/react");
+    const { Simulate } =
+      browserRequire("devtools/client/shared/vendor/react-dom-test-utils");
+    const LearnMoreLink = createFactory(
+      browserRequire("devtools/client/accessibility/components/LearnMoreLink"));
+
+    class MockL10N {
+      constructor(bundle) {
+        this.bundle = bundle;
+      }
+
+      getStr(name) {
+        return this.bundle[name];
+      }
+
+      getFormatStr(name, ...args) {
+        let index = 0;
+        return this.bundle[name].replace("%S", () => args[index++]);
+      }
+    }
+
+    function testLinkClicked(link, expectedUrl) {
+      const browserWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+      const defaultOpenWebLinkIn = browserWindow.openWebLinkIn;
+
+      const checker = Symbol();
+      let onClickUrl = checker;
+      browserWindow.openWebLinkIn = url => {
+        onClickUrl = url;
+      };
+
+      Simulate.click(link);
+
+      ok(onClickUrl !== checker, "Link was clicked");
+      is(onClickUrl, expectedUrl, "Correct URL is opened");
+
+      browserWindow.openWebLinkIn = defaultOpenWebLinkIn;
+    }
+
+    const href = "http://example.com/";
+    const l10n = new MockL10N({
+      message: "This is a message that contains a link. %S",
+      link: "Learn more"
+    });
+    const learnMoreLink = LearnMoreLink(
+      { href, l10n, learnMoreStringKey: "link", messageStringKey: "message" });
+    ok(LearnMoreLink, "Should be able to create LearnMoreLink instances");
+
+    ReactDOM.render(learnMoreLink, document.body);
+    const p = document.querySelector("p");
+    is(p.textContent, "This is a message that contains a link. Learn more",
+      "Text content for the whole paragraph is correct");
+
+    const link = p.querySelector(".link");
+    ok(link, "Link was rendered");
+    is(link.textContent, "Learn more", "Text content for link is correct");
+
+    testLinkClicked(link, href);
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+};
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -72,17 +72,32 @@ accessibility.disable.enabledTitle=Acces
 # be enabled.
 accessibility.enable.disabledTitle=Accessibility service can not be turned on. It is turned off via accessibility services privacy preference.
 
 # LOCALIZATION NOTE (accessibility.enable.enabledTitle): A title text used for
 # a tooltip for Enabled accessibility button when accessibility service can be
 # enabled.
 accessibility.enable.enabledTitle=Accessibility service will be turned on for all tabs and windows.
 
+# LOCALIZATION NOTE (accessibility.learnMore): A text that is used as is or as textual
+# description in places that link to accessibility inspector documentation.
+accessibility.learnMore=Learn more
+
 # LOCALIZATION NOTE (accessibility.description.general): A title text used when
 # accessibility service description is provided before accessibility inspector
 # is enabled.
 accessibility.description.general=Accessibility features are deactivated by default because they negatively impact performance. Consider turning off accessibility features before using other Developer Tools panels.
 
+# LOCALIZATION NOTE (accessibility.description.general.p1): A title text for the first
+# paragraph, used when accessibility service description is provided before accessibility
+# inspector is enabled. %S in the content will be replaced by a link at run time
+# with the accessibility.learnMore string.
+accessibility.description.general.p1=Accessibility Inspector lets you examine the current page’s accessibility tree, which is used by screen readers and other assistive technologies. %S
+
+# LOCALIZATION NOTE (accessibility.description.general.p2): A title text for the second
+# paragraph, used when accessibility service description is provided before accessibility
+# inspector is enabled.
+accessibility.description.general.p2=Accessibility features may affect the performance of other developer tools panels and should be turned off when not in use.
+
 # LOCALIZATION NOTE (accessibility.description.oldVersion): A title text used
 # when accessibility service description is provided when a client is connected
 # to an older version of accessibility actor.
 accessibility.description.oldVersion=You are connected to a debugger server that is too old. To use Accessibility panel, please connect to the latest debugger server version.