Bug 1319629 - Add Narrate to Android (WIP). draft
authorEitan Isaacson <eitan@monotonous.org>
Tue, 22 Nov 2016 17:09:43 -0800
changeset 447135 4b7faf401053e396a4fbbbd23c4c0a2877467605
parent 447134 b8f069974ac024f254c13d7ed5093b0b9c89e5e9
child 538978 682f0f35118a083392c6b960db3fd773abecd075
push id37997
push userbmo:eitan@monotonous.org
push dateFri, 02 Dec 2016 18:58:57 +0000
bugs1319629
milestone53.0a1
Bug 1319629 - Add Narrate to Android (WIP). MozReview-Commit-ID: Ezh2YzJKiBK
mobile/android/themes/core/aboutReaderControls.css
mobile/android/themes/core/jar.mn
mobile/android/themes/core/narrate.css
mobile/android/themes/core/narrate/arrow.svg
mobile/android/themes/core/narrate/back.svg
mobile/android/themes/core/narrate/fast.svg
mobile/android/themes/core/narrate/forward.svg
mobile/android/themes/core/narrate/slow.svg
mobile/android/themes/core/narrate/start.svg
mobile/android/themes/core/narrate/stop.svg
mobile/android/themes/core/narrateControls.css
modules/libpref/init/all.js
toolkit/components/moz.build
toolkit/components/narrate/NarrateControls.jsm
toolkit/locales/jar.mn
--- a/mobile/android/themes/core/aboutReaderControls.css
+++ b/mobile/android/themes/core/aboutReaderControls.css
@@ -58,16 +58,17 @@
   position: fixed;
   width: 100%;
   left: 0;
   margin: 0;
   padding: 0;
   bottom: 0;
   list-style: none;
   pointer-events: none;
+  z-index: 1;
 }
 
 .toolbar > * {
   float: right;
 }
 
 .button {
   width: 56px;
@@ -93,17 +94,17 @@
   border: 0;
 }
 
 .button[hidden] {
   display: none;
 }
 
 .dropdown-toggle,
-#reader-popup {
+.dropdown-popup {
   pointer-events: auto;
 }
 
 .dropdown {
   left: 0;
   text-align: center;
   display: inline-block;
   list-style: none;
@@ -283,8 +284,12 @@
 
 @media screen and (min-width: 960px) {
   .dropdown-popup {
     width: 350px;
     left: auto;
     right: 0;
   }
 }
+
+#waveform > rect {
+  fill: #fff;
+}
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -91,8 +91,29 @@ chrome.jar:
   skin/images/accessiblecaret-normal-xhdpi.png      (images/accessiblecaret-normal-xhdpi.png)
   skin/images/accessiblecaret-normal-xxhdpi.png     (images/accessiblecaret-normal-xxhdpi.png)
   skin/images/accessiblecaret-tilt-left-hdpi.png    (images/accessiblecaret-tilt-left-hdpi.png)
   skin/images/accessiblecaret-tilt-left-xhdpi.png   (images/accessiblecaret-tilt-left-xhdpi.png)
   skin/images/accessiblecaret-tilt-left-xxhdpi.png  (images/accessiblecaret-tilt-left-xxhdpi.png)
   skin/images/accessiblecaret-tilt-right-hdpi.png   (images/accessiblecaret-tilt-right-hdpi.png)
   skin/images/accessiblecaret-tilt-right-xhdpi.png  (images/accessiblecaret-tilt-right-xhdpi.png)
   skin/images/accessiblecaret-tilt-right-xxhdpi.png (images/accessiblecaret-tilt-right-xxhdpi.png)
+
+  skin/narrate.css                          (narrate.css)
+  skin/narrateControls.css                  (narrateControls.css)
+  skin/narrate/arrow.svg                    (narrate/arrow.svg)
+  skin/narrate/back.svg                     (narrate/back.svg)
+  skin/narrate/fast.svg                     (narrate/fast.svg)
+  skin/narrate/forward.svg                  (narrate/forward.svg)
+  skin/narrate/slow.svg                     (narrate/slow.svg)
+  skin/narrate/start.svg                    (narrate/start.svg)
+  skin/narrate/stop.svg                     (narrate/stop.svg)
+% override chrome://global/skin/narrate.css chrome://browser/skin/narrate.css
+% override chrome://global/skin/narrateControls.css chrome://browser/skin/narrateControls.css
+% override chrome://global/skin/narrate/arrow.svg chrome://browser/skin/narrate/arrow.svg
+% override chrome://global/skin/narrate/back.svg#enabled chrome://browser/skin/narrate/back.svg#enabled
+% override chrome://global/skin/narrate/back.svg#disabled chrome://browser/skin/narrate/back.svg#disabled
+% override chrome://global/skin/narrate/fast.svg chrome://browser/skin/narrate/fast.svg
+% override chrome://global/skin/narrate/forward.svg#enabled chrome://browser/skin/narrate/forward.svg#enabled
+% override chrome://global/skin/narrate/forward.svg#disabled chrome://browser/skin/narrate/forward.svg#disabled
+% override chrome://global/skin/narrate/slow.svg chrome://browser/skin/narrate/slow.svg
+% override chrome://global/skin/narrate/start.svg chrome://browser/skin/narrate/start.svg
+% override chrome://global/skin/narrate/stop.svg chrome://browser/skin/narrate/stop.svg
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate.css
@@ -0,0 +1,46 @@
+.narrating {
+  position: relative;
+  z-index: 1;
+}
+
+body.light .narrating {
+  background-color: #ffc;
+}
+
+body.sepia .narrating {
+  background-color: #e0d7c5;
+}
+
+body.dark .narrating {
+  background-color: #242424;
+}
+
+.narrate-word-highlight {
+  position: absolute;
+  display: none;
+  transform: translate(-50%, calc(-50% - 2px));
+  z-index: -1;
+  border-bottom-style: solid;
+  border-bottom-width: 7px;
+  transition: left 0.1s ease;
+}
+
+.narrating > .narrate-word-highlight {
+  display: inline-block;
+}
+
+.narrate-word-highlight.newline {
+  transition: none;
+}
+
+body.light .narrate-word-highlight {
+  border-bottom-color: #ffe087;
+}
+
+body.sepia .narrate-word-highlight {
+  border-bottom-color: #bdb5a5;
+}
+
+body.dark .narrate-word-highlight {
+  border-bottom-color: #6f6f6f;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/arrow.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12">
+  <path d="M6 9L1 4l1-1 4 4 4-4 1 1z" fill="#4C4C4C"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/back.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+  <defs>
+    <style>
+      use:not(:target) {
+        display: none;
+      }
+      #disabled {
+        opacity: 0.5;
+      }
+    </style>
+    <path id="shape" d="M 5 0 C 4.446 0 4 0.446 4 1 L 4 23 C 4 23.554 4.446 24 5 24 L 7 24 C 7.554 24 8 23.554 8 23 L 8 12.404297 C 8.04108 12.509297 8.109944 12.610125 8.203125 12.703125 L 19.296875 23.775391 C 19.495259 23.972391 19.661613 24.039562 19.796875 23.976562 C 19.932137 23.915564 20 23.748516 20 23.478516 L 20 0.52148438 C 20 0.25248437 19.93214 0.084484365 19.796875 0.021484375 C 19.661613 -0.040515625 19.495259 0.02856248 19.296875 0.2265625 L 8.203125 11.298828 C 8.1099445 11.381828 8.04108 11.481703 8 11.595703 L 8 1 C 8 0.446 7.554 0 7 0 L 5 0 z " fill="gray"/>
+  </defs>
+  <use id="enabled" xlink:href="#shape"/>
+  <use id="disabled" xlink:href="#shape"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/fast.svg
@@ -0,0 +1,3 @@
+<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20.4">
+    <path fill="gray" d="M14.42 16.68a.77.77 0 0 0 .54.7l2.51.68a1.58 1.58 0 0 1 1.06 1.22l.05.39-3.89-.53a4.34 4.34 0 0 1-1.74-.72L7.2 14.03a5.79 5.79 0 0 1-5.34-4.88h-.82a1 1 0 0 1-1-1l2.9-3.24a6.16 6.16 0 0 1 4.7-2.39 5.88 5.88 0 0 1 .77.05 5 5 0 0 1 .87.15c3.75 1 6.5 5.84 6.5 5.84a2.27 2.27 0 0 0 1.14.85h.17a1.27 1.27 0 0 0 1.22-.4l.78-1-2.47-1.2c-3.38-1.46-2.46-5.71-2.46-5.71 0-.26.23-.32.42-.14l5.32 5-4.31-4.81a1.39 1.39 0 0 1 .81-1.22l4.17 6.65.33.31 2.19 1.54a2.44 2.44 0 0 1 .92 1.75v2.77l-.16.13a1.66 1.66 0 0 1-1.63.19l-.75-.36a2.57 2.57 0 0 0-2.55.32l-2.18 1.82a4.28 4.28 0 0 1-.89.55 10.18 10.18 0 0 0-4.62-8.46c-.27-.16-.66.31-.47.48a10.52 10.52 0 0 1 3.68 8.5v.48zm8.38-5.42a.49.49 0 1 0-.49-.49.49.49 0 0 0 .49.49zm-18 9.14v-.52a1.39 1.39 0 0 1 .93-1.25s2.7-.66 3.43-1.84l2.06 1.63a25.62 25.62 0 0 1-6.43 2z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/forward.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+  <defs>
+    <style>
+      use:not(:target) {
+        display: none;
+      }
+      #disabled {
+        opacity: 0.5;
+      }
+    </style>
+    <path id="shape" d="m 19,0 c 0.554,0 1,0.446 1,1 l 0,22 c 0,0.554 -0.446,1 -1,1 l -2,0 c -0.554,0 -1,-0.446 -1,-1 l 0,-10.595703 c -0.04108,0.105 -0.109944,0.205828 -0.203125,0.298828 L 4.703125,23.775391 c -0.198384,0.197 -0.364738,0.264171 -0.5,0.201171 C 4.067863,23.915564 4,23.748516 4,23.478516 L 4,0.52148438 c 0,-0.26900001 0.06786,-0.43700001 0.203125,-0.5 0.135262,-0.062 0.301616,0.0070781 0.5,0.20507812 l 11.09375,11.0722655 c 0.09318,0.083 0.162045,0.182875 0.203125,0.296875 L 16,1 c 0,-0.554 0.446,-1 1,-1 l 2,0 z" fill="gray"/>
+  </defs>
+  <use id="enabled" xlink:href="#shape"/>
+  <use id="disabled" xlink:href="#shape"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/slow.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <g fill="gray">
+        <path d="M1.684,13.486c-0.209,0-0.404-0.132-0.474-0.341c-0.528-1.58-0.23-5.767,4.097-7.921 c1.315-0.656,2.589-0.988,3.787-0.988c3.237,0,5.096,2.341,5.99,3.465c0.158,0.199,0.181,0.533,0,0.713 c-0.793,0.794-1.852,1.542-3.231,2.286c-2.46,1.327-5.045,1.775-7.121,2.134c-1.123,0.194-2.093,0.361-2.89,0.627 C1.789,13.479,1.735,13.486,1.684,13.486L1.684,13.486z"/>
+        <path d="M23.185,5.465c-0.86-1.121-2.074-1.819-3.168-1.819c-0.641,0-1.556,0.23-2.273,1.328 c-0.374,0.571-0.577,1.161-0.773,1.73c-0.512,1.482-1.041,3.016-4.662,4.969c-2.316,1.249-4.707,1.664-6.815,2.03 c-2.524,0.438-4.704,0.814-5.455,2.622c-0.069,0.165-0.045,0.354,0.062,0.495c0.107,0.143,0.281,0.217,0.46,0.193 c0.667-0.081,1.533,0.041,2.434,0.217c-0.122,0.146-0.261,0.286-0.391,0.418c-0.38,0.385-0.774,0.783-0.657,1.292 c0.108,0.474,0.604,0.699,0.966,0.828c0.399,0.142,0.843,0.217,1.283,0.217c1.241,0,2.216-0.579,2.649-1.539 c1.704,0.287,3.487,0.313,5.043,0.313l1.639-0.006c0.066,0.056,0.178,0.166,0.264,0.25c0.504,0.506,1.348,1.351,2.721,1.351 c0.129,0,0.264-0.008,0.416-0.026c0.687-0.102,1.351-0.267,1.574-0.787c0.227-0.528-0.123-1.023-0.526-1.597 c-0.481-0.685-1.08-1.532-0.998-2.652c0.196-0.397,0.368-0.824,0.546-1.267c0.479-1.19,0.975-2.421,2.12-3.513 c0.431,0.343,1.022,0.549,1.63,0.549l0,0c0.439,0,0.876-0.102,1.295-0.3c0.624-0.293,1.104-0.967,1.316-1.847 C24.175,7.707,23.914,6.418,23.185,5.465L23.185,5.465z M20.397,7.757c-0.276,0-0.5-0.224-0.5-0.5s0.224-0.5,0.5-0.5 c0.275,0,0.5,0.224,0.5,0.5S20.674,7.757,20.397,7.757z"/>
+    </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/start.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <path d="M21.64 12.44L2.827 22.895c-.217.123-.403.137-.56.042-.155-.094-.233-.264-.233-.51V1.572c0-.244.08-.414.233-.51.157-.093.343-.08.56.044L21.642 11.56c.217.124.326.27.326.44 0 .17-.11.316-.327.44z" fill="gray"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrate/stop.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <rect ry="1" rx="1" y="2" x="2" height="20" width="20" fill="gray"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/narrateControls.css
@@ -0,0 +1,188 @@
+:scope {
+  --border-color: #e5e5e5;
+}
+
+#narrate-toggle > svg {
+  display: block;
+  margin: 0 8px;
+}
+
+.dropdown-popup {
+  max-height: calc(100vh - 48px);
+}
+
+.dropdown-popup button {
+  background-color: transparent;
+}
+
+.narrate-row {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 40px;
+  box-sizing: border-box;
+}
+
+.narrate-row:not(:first-child) {
+  border-top: 1px solid var(--border-color);
+}
+
+/* Control buttons */
+
+#narrate-control > button {
+  background-size: 24px 24px;
+  background-repeat: no-repeat;
+  background-position: center center;
+  height: 64px;
+  width: 33%;
+  border: none;
+  color: #666;
+  box-sizing: border-box;
+}
+
+#narrate-control > button:not(:first-child) {
+  border-left: 1px solid var(--border-color);
+}
+
+#narrate-skip-previous {
+  border-top-left-radius: 3px;
+  background-image: url("chrome://global/skin/narrate/back.svg#enabled");
+}
+
+#narrate-skip-next {
+  border-top-right-radius: 3px;
+  background-image: url("chrome://global/skin/narrate/forward.svg#enabled");
+}
+
+#narrate-skip-previous:disabled {
+  background-image: url("chrome://global/skin/narrate/back.svg#disabled");
+}
+
+#narrate-skip-next:disabled {
+  background-image: url("chrome://global/skin/narrate/forward.svg#disabled");
+}
+
+#narrate-start-stop {
+  background-image: url("chrome://global/skin/narrate/start.svg");
+}
+
+#narrate-dropdown.speaking #narrate-start-stop {
+  background-image: url("chrome://global/skin/narrate/stop.svg");
+}
+
+/* Rate control */
+
+#narrate-rate::before, #narrate-rate::after {
+  content: '';
+  width: 48px;
+  height: 40px;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 24px auto;
+}
+
+#narrate-rate::before {
+  background-image: url("chrome://global/skin/narrate/slow.svg");
+}
+
+#narrate-rate::after {
+  background-image: url("chrome://global/skin/narrate/fast.svg");
+}
+
+#narrate-rate-input {
+  margin: 0 1px;
+  flex-grow: 1;
+  background-color: transparent;
+  border: none;
+}
+
+#narrate-rate-input::-moz-range-track {
+  background-color: #979797;
+  height: 2px;
+}
+
+#narrate-rate-input::-moz-range-progress {
+  background-color: #2EA3FF;
+  height: 2px;
+}
+
+#narrate-rate-input::-moz-range-thumb {
+  background-color: #808080;
+  height: 16px;
+  width: 16px;
+  border-radius: 8px;
+  border-width: 0;
+}
+
+#narrate-rate-input:active::-moz-range-thumb {
+  background-color: #2EA3FF;
+}
+
+/* Voice selection */
+
+.voiceselect {
+  width: 100%;
+}
+
+.voiceselect > button.select-toggle,
+.voiceselect > .options > button.option {
+  -moz-appearance: none;
+  border: none;
+  width: 100%;
+  min-height: 40px;
+}
+
+.voiceselect.open > button.select-toggle {
+  border-bottom: 1px solid var(--border-color);
+}
+
+.voiceselect > button.select-toggle::after {
+  content: '';
+  background-image: url("chrome://global/skin/narrate/arrow.svg");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 12px 12px;
+  display: inline-block;
+  width: 1.5em;
+  height: 1em;
+  vertical-align: middle;
+}
+
+.voiceselect > .options > button.option:not(:first-child) {
+  border-top: 1px solid var(--border-color);
+}
+
+.voiceselect > .options > button.option  {
+  box-sizing: border-box;
+}
+
+.voiceselect > .options:not(.hovering) > button.option:focus {
+  background-color: #eaeaea;
+}
+
+.voiceselect > .options:not(.hovering) > button.option:hover:not(:focus) {
+  background-color: transparent;
+}
+
+.voiceselect > .options > button.option::-moz-focus-inner {
+  outline: none;
+  border: 0;
+}
+
+.voiceselect > .options {
+  display: none;
+  overflow-y: auto;
+}
+
+.voiceselect.open > .options {
+  display: block;
+}
+
+.current-voice {
+  color: #7f7f7f;
+}
+
+.voiceselect:not(.open) > button,
+.option:last-child {
+  border-radius: 0 0 3px 3px;
+}
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5382,22 +5382,18 @@ pref("reader.font_type", "sans-serif");
 
 // Whether or not the user has interacted with the reader mode toolbar.
 // This is used to show a first-launch tip in reader mode.
 pref("reader.has_used_toolbar", false);
 
 // Whether to use a vertical or horizontal toolbar.
 pref("reader.toolbar.vertical", true);
 
-#if !defined(ANDROID)
+// Enable Narrate
 pref("narrate.enabled", true);
-#else
-pref("narrate.enabled", false);
-#endif
-
 pref("narrate.test", false);
 pref("narrate.rate", 0);
 pref("narrate.voice", " { \"default\": \"automatic\" }");
 // Only make voices that match content language available.
 pref("narrate.filter-voices", true);
 
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
 // Whether to allow, on a Linux system that doesn't support the necessary sandboxing
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -30,16 +30,17 @@ DIRS += [
     'finalizationwitness',
     'formautofill',
     'find',
     'gfx',
     'jsdownloads',
     'lz4',
     'mediasniffer',
     'microformats',
+    'narrate',
     'osfile',
     'parentalcontrols',
     'passwordmgr',
     'perf',
     'perfmonitoring',
     'places',
     'privatebrowsing',
     'processsingleton',
@@ -66,17 +67,17 @@ DIRS += [
     'workerloader',
     'xulstore'
 ]
 
 if CONFIG['ENABLE_INTL_API']:
     DIRS += ['mozintl']
 
 if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
-    DIRS += ['narrate', 'viewsource'];
+    DIRS += ['viewsource'];
 
     if CONFIG['NS_PRINTING']:
         DIRS += ['printing']
 
 if CONFIG['MOZ_CRASHREPORTER']:
     DIRS += ['crashes']
 
 if CONFIG['BUILD_CTYPES']:
--- a/toolkit/components/narrate/NarrateControls.jsm
+++ b/toolkit/components/narrate/NarrateControls.jsm
@@ -281,17 +281,18 @@ NarrateControls.prototype = {
   },
 
   _createVoiceLabel: function(voice) {
     // This is a highly imperfect method of making human-readable labels
     // for system voices. Because each platform has a different naming scheme
     // for voices, we use a different method for each platform.
     switch (Services.appinfo.OS) {
       case "WINNT":
-        // On windows the language is included in the name, so just use the name
+      case "Android":
+        // On windows and Android the language is included in the name
         return voice.name;
       case "Linux":
         // On Linux, the name is usually the unlocalized language name.
         // Use a localized language name, and have the language tag in
         // parenthisis. This is to avoid six languages called "English".
         return gStrings.formatStringFromName("voiceLabel",
           [this._getLanguageName(voice.lang) || voice.name, voice.lang], 2);
       default:
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -58,19 +58,17 @@
 #endif
   locale/@AB_CD@/global/globalKeys.dtd                  (%chrome/global/globalKeys.dtd)
   locale/@AB_CD@/global/headsUpDisplay.properties       (%chrome/global/headsUpDisplay.properties)
   locale/@AB_CD@/global/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/global/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/global/keys.properties                 (%chrome/global/keys.properties)
   locale/@AB_CD@/global/languageNames.properties        (%chrome/global/languageNames.properties)
   locale/@AB_CD@/global/mozilla.dtd                     (%chrome/global/mozilla.dtd)
-#ifndef MOZ_FENNEC
   locale/@AB_CD@/global/narrate.properties              (%chrome/global/narrate.properties)
-#endif
   locale/@AB_CD@/global/notification.dtd                (%chrome/global/notification.dtd)
   locale/@AB_CD@/global/preferences.dtd                 (%chrome/global/preferences.dtd)
 #ifndef MOZ_FENNEC
   locale/@AB_CD@/global/printdialog.dtd                 (%chrome/global/printdialog.dtd)
   locale/@AB_CD@/global/printjoboptions.dtd             (%chrome/global/printjoboptions.dtd)
   locale/@AB_CD@/global/printPageSetup.dtd              (%chrome/global/printPageSetup.dtd)
   locale/@AB_CD@/global/printPreview.dtd                (%chrome/global/printPreview.dtd)
   locale/@AB_CD@/global/printPreviewProgress.dtd        (%chrome/global/printPreviewProgress.dtd)