Bug 1257953 - Update narrate to use new animated icon when speaking. r=Gijs draft
authorEitan Isaacson <eitan@monotonous.org>
Wed, 20 Apr 2016 10:38:20 -0700
changeset 356941 fc1dbc87de9b99f590a8e8c144928b61da198faa
parent 356940 b42e46bc6b8cac5230f37aed1f02aa6ad805d170
child 519530 311e24679bdaf5a445a22871de90d13acb4b45f7
push id16652
push userbmo:eitan@monotonous.org
push dateWed, 27 Apr 2016 17:14:42 +0000
reviewersGijs
bugs1257953
milestone48.0a1
Bug 1257953 - Update narrate to use new animated icon when speaking. r=Gijs MozReview-Commit-ID: 4OnlJBmzu5T
toolkit/components/narrate/NarrateControls.jsm
toolkit/components/narrate/test/NarrateTestUtils.jsm
toolkit/components/narrate/test/browser_narrate.js
toolkit/components/reader/AboutReader.jsm
toolkit/themes/shared/jar.inc.mn
toolkit/themes/shared/narrate/narrate-active.svg
toolkit/themes/shared/narrate/narrate.svg
toolkit/themes/shared/narrateControls.css
--- a/toolkit/components/narrate/NarrateControls.jsm
+++ b/toolkit/components/narrate/NarrateControls.jsm
@@ -31,23 +31,69 @@ function NarrateControls(mm, win) {
       result += gStrings.GetStringFromName(substitutions[i]) + pieces[i + 1];
     }
     return result;
   }
 
   let dropdown = win.document.createElement("ul");
   dropdown.className = "dropdown";
   dropdown.id = "narrate-dropdown";
+  // We need inline svg here for the animation to work (bug 908634 & 1190881).
+  // The style animation can't be scoped (bug 830056).
   dropdown.innerHTML =
     localize`<style scoped>
       @import url("chrome://global/skin/narrateControls.css");
     </style>
     <li>
-       <button class="dropdown-toggle button"
-               id="narrate-toggle" title="${"narrate"}"></button>
+       <button class="dropdown-toggle button" id="narrate-toggle" title="${"narrate"}">
+         <svg xmlns="http://www.w3.org/2000/svg"
+              xmlns:xlink="http://www.w3.org/1999/xlink"
+              width="24" height="24" viewBox="0 0 24 24">
+          <style>
+            @keyframes grow {
+              0%   { transform: scaleY(1);   }
+              15%  { transform: scaleY(1.5); }
+              15%  { transform: scaleY(1.5); }
+              30%  { transform: scaleY(1);   }
+              100% { transform: scaleY(1);   }
+            }
+
+            #waveform > rect {
+              fill: #808080;
+            }
+
+            .speaking #waveform > rect {
+              fill: #58bf43;
+              transform-box: fill-box;
+              transform-origin: 50% 50%;
+              animation-name: grow;
+              animation-duration: 1750ms;
+              animation-iteration-count: infinite;
+              animation-timing-function: linear;
+            }
+
+            #waveform > rect:nth-child(2) { animation-delay: 250ms; }
+            #waveform > rect:nth-child(3) { animation-delay: 500ms; }
+            #waveform > rect:nth-child(4) { animation-delay: 750ms; }
+            #waveform > rect:nth-child(5) { animation-delay: 1000ms; }
+            #waveform > rect:nth-child(6) { animation-delay: 1250ms; }
+            #waveform > rect:nth-child(7) { animation-delay: 1500ms; }
+
+          </style>
+          <g id="waveform">
+            <rect x="1"  y="8" width="2" height="8"  rx=".5" ry=".5" />
+            <rect x="4"  y="5" width="2" height="14" rx=".5" ry=".5" />
+            <rect x="7"  y="8" width="2" height="8"  rx=".5" ry=".5" />
+            <rect x="10" y="4" width="2" height="16" rx=".5" ry=".5" />
+            <rect x="13" y="2" width="2" height="20" rx=".5" ry=".5" />
+            <rect x="16" y="4" width="2" height="16" rx=".5" ry=".5" />
+            <rect x="19" y="7" width="2" height="10" rx=".5" ry=".5" />
+          </g>
+         </svg>
+        </button>
     </li>
     <li class="dropdown-popup">
       <div id="narrate-control" class="narrate-row">
         <button disabled id="narrate-skip-previous"
                 title="${"back"}"></button>
         <button id="narrate-start-stop" title="${"start"}"></button>
         <button disabled id="narrate-skip-next"
                 title="${"forward"}"></button>
@@ -161,39 +207,25 @@ NarrateControls.prototype = {
           this.narrator.start(options).then(() => {
             this._updateSpeechControls(false);
           }, err => {
             Cu.reportError(`Narrate failed: ${err}.`);
             this._updateSpeechControls(false);
           });
         }
         break;
-      case "narrate-toggle":
-        let dropdown = this._doc.getElementById("narrate-dropdown");
-        if (dropdown.classList.contains("open")) {
-          if (this.narrator.speaking) {
-            this.narrator.stop();
-          }
-
-          // We need to remove "keep-open" class here so that AboutReader
-          // closes this dropdown properly. This class is eventually removed in
-          // _updateSpeechControls which gets called after narration stops,
-          // but that happend asynchronously and is too late.
-          dropdown.classList.remove("keep-open");
-        }
-        break;
     }
   },
 
   _updateSpeechControls: function(speaking) {
     let dropdown = this._doc.getElementById("narrate-dropdown");
     dropdown.classList.toggle("keep-open", speaking);
+    dropdown.classList.toggle("speaking", speaking);
 
     let startStopButton = this._doc.getElementById("narrate-start-stop");
-    startStopButton.classList.toggle("speaking", speaking);
     startStopButton.title =
       gStrings.GetStringFromName(speaking ? "stop" : "start");
 
     this._doc.getElementById("narrate-skip-previous").disabled = !speaking;
     this._doc.getElementById("narrate-skip-next").disabled = !speaking;
   },
 
   _createVoiceLabel: function(voice) {
--- a/toolkit/components/narrate/test/NarrateTestUtils.jsm
+++ b/toolkit/components/narrate/test/NarrateTestUtils.jsm
@@ -13,18 +13,18 @@ this.EXPORTED_SYMBOLS = [ "NarrateTestUt
 this.NarrateTestUtils = {
   TOGGLE: "#narrate-toggle",
   POPUP: "#narrate-dropdown .dropdown-popup",
   VOICE_SELECT: "#narrate-voices .select-toggle",
   VOICE_OPTIONS: "#narrate-voices .options",
   VOICE_SELECTED: "#narrate-voices .options .option.selected",
   VOICE_SELECT_LABEL: "#narrate-voices .select-toggle .current-voice",
   RATE: "#narrate-rate-input",
-  START: "#narrate-start-stop:not(.speaking)",
-  STOP: "#narrate-start-stop.speaking",
+  START: "#narrate-dropdown:not(.speaking) #narrate-start-stop",
+  STOP: "#narrate-dropdown.speaking #narrate-start-stop",
   BACK: "#narrate-skip-previous",
   FORWARD: "#narrate-skip-next",
 
   isVisible: function(element) {
     let style = element.ownerDocument.defaultView.getComputedStyle(element, "");
     if (style.display == "none") {
       return false;
     } else if (style.visibility != "visible") {
--- a/toolkit/components/narrate/test/browser_narrate.js
+++ b/toolkit/components/narrate/test/browser_narrate.js
@@ -89,15 +89,20 @@ add_task(function* testNarrate() {
     yield promiseEvent;
     NarrateTestUtils.isStartedState(content, ok);
 
     promiseEvent = ContentTaskUtils.waitForEvent(content, "scroll");
     content.scrollBy(0, -10);
     yield promiseEvent;
     ok(NarrateTestUtils.isVisible(popup), "popup stays visible after scroll");
 
+    toggle.click();
+    ok(!NarrateTestUtils.isVisible(popup), "popup is dismissed while speaking");
+    NarrateTestUtils.isStartedState(content, ok);
+
     promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphend");
-    toggle.click();
+    $(NarrateTestUtils.STOP).click();
     yield promiseEvent;
-    ok(!NarrateTestUtils.isVisible(popup), "popup is dismissed while speaking");
-    ok(true, "speech stopped when popup is dismissed");
+    yield ContentTaskUtils.waitForCondition(
+      () => !$(NarrateTestUtils.STOP), "transitioned to stopped state");
+    NarrateTestUtils.isStoppedState(content, ok);
   });
 });
--- a/toolkit/components/reader/AboutReader.jsm
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -217,17 +217,17 @@ AboutReader.prototype = {
         let target = aEvent.target;
         if (target.classList.contains('dropdown-toggle')) {
           this._toggleDropdownClicked(aEvent);
         } else if (!target.closest('.dropdown-popup')) {
           this._closeDropdowns();
         }
         break;
       case "scroll":
-        this._closeDropdowns();
+        this._closeDropdowns(true);
         let isScrollingUp = this._scrollOffset > aEvent.pageY;
         this._setSystemUIVisibility(isScrollingUp);
         this._scrollOffset = aEvent.pageY;
         break;
       case "resize":
         this._updateImageMargins();
         if (this._isToolbarVertical) {
           this._win.setTimeout(() => {
@@ -766,20 +766,27 @@ AboutReader.prototype = {
     this._closeDropdowns();
 
     // Trigger BackPressListener initialization in Android.
     dropdown.classList.add("open");
     this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
   },
 
   /*
-   * If the ReaderView has open dropdowns, close them.
+   * If the ReaderView has open dropdowns, close them. If we are closing the
+   * dropdowns because the page is scrolling, allow popups to stay open with
+   * the keep-open class.
    */
-  _closeDropdowns: function() {
-    let openDropdowns = this._doc.querySelectorAll(".dropdown.open:not(.keep-open)");
+  _closeDropdowns: function(scrolling) {
+    let selector = ".dropdown.open";
+    if (scrolling) {
+      selector += ":not(.keep-open)";
+    }
+
+    let openDropdowns = this._doc.querySelectorAll(selector);
     for (let dropdown of openDropdowns) {
       dropdown.classList.remove("open");
     }
 
     // Trigger BackPressListener cleanup in Android.
     if (openDropdowns.length) {
       this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
     }
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -27,18 +27,16 @@ toolkit.jar:
   skin/classic/global/icons/warning.svg                    (../../shared/incontent-icons/warning.svg)
   skin/classic/global/alerts/alert-common.css              (../../shared/alert-common.css)
   skin/classic/global/narrate.css                          (../../shared/narrate.css)
   skin/classic/global/narrateControls.css                  (../../shared/narrateControls.css)
   skin/classic/global/narrate/arrow.svg                    (../../shared/narrate/arrow.svg)
   skin/classic/global/narrate/back.svg                     (../../shared/narrate/back.svg)
   skin/classic/global/narrate/fast.svg                     (../../shared/narrate/fast.svg)
   skin/classic/global/narrate/forward.svg                  (../../shared/narrate/forward.svg)
-  skin/classic/global/narrate/narrate.svg                  (../../shared/narrate/narrate.svg)
-  skin/classic/global/narrate/narrate-active.svg           (../../shared/narrate/narrate-active.svg)
   skin/classic/global/narrate/slow.svg                     (../../shared/narrate/slow.svg)
   skin/classic/global/narrate/start.svg                    (../../shared/narrate/start.svg)
   skin/classic/global/narrate/stop.svg                     (../../shared/narrate/stop.svg)
   skin/classic/global/menu/shared-menu-check@2x.png        (../../shared/menu-check@2x.png)
   skin/classic/global/menu/shared-menu-check.png           (../../shared/menu-check.png)
   skin/classic/global/menu/shared-menu-check-active.svg    (../../shared/menu-check-active.svg)
   skin/classic/global/menu/shared-menu-check-black.svg     (../../shared/menu-check-black.svg)
   skin/classic/global/menu/shared-menu-check-hover.svg     (../../shared/menu-check-hover.svg)
deleted file mode 100644
--- a/toolkit/themes/shared/narrate/narrate-active.svg
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
-  <style>
-    @keyframes grow {
-      0%   { transform: scaleY(1);   }
-      15%  { transform: scaleY(1.5); }
-      15%  { transform: scaleY(1.5); }
-      30%  { transform: scaleY(1);   }
-      100% { transform: scaleY(1);   }
-    }
-
-    @keyframes shrink {
-      0%   { transform: scaleY(1);   }
-      15%  { transform: scaleY(.5); }
-      15%  { transform: scaleY(.5); }
-      30%  { transform: scaleY(1);   }
-      100% { transform: scaleY(1);   }
-    }
-
-    .barAnimation {
-      transform-box: fill-box;
-      transform-origin: 50% 50%;
-      animation-name: grow;
-      animation-duration: 1750ms;
-      animation-iteration-count: infinite;
-      animation-timing-function: linear;
-    }
-
-    .barAnimation01 { animation-delay: 0; }
-    .barAnimation02 { animation-delay: 250ms; }
-    .barAnimation03 { animation-delay: 500ms; }
-    .barAnimation04 { animation-delay: 750ms; }
-    .barAnimation05 { animation-delay: 1000ms; }
-    .barAnimation06 { animation-delay: 1250ms; }
-    .barAnimation07 { animation-delay: 1500ms; }
-  </style>
-
-  <g id="glyph-waveform" fill="#58bf43">
-    <rect x="1"  y="8" width="2" height="8"  rx=".5" ry=".5" class="barAnimation barAnimation01" />
-    <rect x="4"  y="5" width="2" height="14" rx=".5" ry=".5" class="barAnimation barAnimation02" />
-    <rect x="7"  y="8" width="2" height="8"  rx=".5" ry=".5" class="barAnimation barAnimation03" />
-    <rect x="10" y="4" width="2" height="16" rx=".5" ry=".5" class="barAnimation barAnimation04" />
-    <rect x="13" y="2" width="2" height="20" rx=".5" ry=".5" class="barAnimation barAnimation05" />
-    <rect x="16" y="4" width="2" height="16" rx=".5" ry=".5" class="barAnimation barAnimation06" />
-    <rect x="19" y="7" width="2" height="10" rx=".5" ry=".5" class="barAnimation barAnimation07" />
-  </g>
-
-</svg>
deleted file mode 100644
--- a/toolkit/themes/shared/narrate/narrate.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
-  <g id="glyph-waveform" fill="#808080">
-    <rect x="1"  y="8" width="2" height="8"  rx=".5" ry=".5" />
-    <rect x="4"  y="5" width="2" height="14" rx=".5" ry=".5" />
-    <rect x="7"  y="8" width="2" height="8"  rx=".5" ry=".5" />
-    <rect x="10" y="4" width="2" height="16" rx=".5" ry=".5" />
-    <rect x="13" y="2" width="2" height="20" rx=".5" ry=".5" />
-    <rect x="16" y="4" width="2" height="16" rx=".5" ry=".5" />
-    <rect x="19" y="7" width="2" height="10" rx=".5" ry=".5" />
-  </g>
-
-</svg>
--- a/toolkit/themes/shared/narrateControls.css
+++ b/toolkit/themes/shared/narrateControls.css
@@ -1,14 +1,15 @@
 :scope {
   --border-color: #e5e5e5;
 }
 
-#narrate-toggle {
-  background-image: url("chrome://global/skin/narrate/narrate.svg");
+#narrate-toggle > svg {
+  display: block;
+  margin: 0 8px;
 }
 
 .dropdown-popup button {
   background-color: transparent;
 }
 
 .dropdown-popup button:hover:not(:disabled) {
   background-color: #eaeaea;
@@ -59,17 +60,17 @@
 #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-start-stop.speaking {
+#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;