Bug 1257953 - Update narrate to use new animated icon when speaking. r=Gijs
MozReview-Commit-ID: 4OnlJBmzu5T
--- 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;