--- a/browser/extensions/narrate/.eslintrc
+++ b/browser/extensions/narrate/.eslintrc
@@ -11,16 +11,18 @@
"env": { "browser": true },
"rules": {
// Mozilla stuff
"mozilla/no-aArgs": 1,
"mozilla/reject-importGlobalProperties": 1,
"mozilla/var-only-at-top-level": 1,
+ "mozilla/no-cpows-in-tests": 0,
+ "mozilla/import-headjs-globals": 1,
"block-scoped-var": 2,
"brace-style": [1, "1tbs", {"allowSingleLine": false}],
"camelcase": 1,
"comma-dangle": 1,
"comma-spacing": [1, {"before": false, "after": true}],
"comma-style": [1, "last"],
"complexity": 1,
--- a/browser/extensions/narrate/content/Narrator.jsm
+++ b/browser/extensions/narrate/content/Narrator.jsm
@@ -5,21 +5,24 @@
"use strict";
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [ "Narrator" ];
function Narrator(win) {
this._winRef = Cu.getWeakReference(win);
+ this._inTest = Services.prefs.getBoolPref("extensions.narrate.test");
this._speechOptions = {};
this._startTime = 0;
this._stopped = false;
}
Narrator.prototype = {
get _doc() {
return this._winRef.get().document;
@@ -115,26 +118,38 @@ Narrator.prototype = {
return new Promise(resolve => {
utterance.addEventListener("start", () => {
paragraph.classList.add("narrating");
let bb = paragraph.getBoundingClientRect();
if (bb.top < 0 || bb.bottom > this._win.innerHeight) {
paragraph.scrollIntoView({ behavior: "smooth", block: "start"});
}
+ if (this._inTest) {
+ Services.cpmm.sendAsyncMessage("Narrate:StartParagraph",
+ { voice: utterance.chosenVoiceURI,
+ rate: utterance.rate,
+ paragraph: this._index });
+ }
});
utterance.addEventListener("end", () => {
if (!this._win) {
// page got unloaded, don't do anything.
return;
}
paragraph.classList.remove("narrating");
this._startTime = 0;
+ if (this._inTest) {
+ Services.cpmm.sendAsyncMessage("Narrate:EndParagraph",
+ { voice: utterance.chosenVoiceURI,
+ rate: utterance.rate,
+ paragraph: this._index });
+ }
if (this._index + 1 >= this._paragraphs.length || this._stopped) {
// We reached the end of the document, or the user pressed stopped.
resolve();
} else {
this._index++;
this._speakInner().then(resolve);
}
});
--- a/browser/extensions/narrate/moz.build
+++ b/browser/extensions/narrate/moz.build
@@ -9,9 +9,11 @@ DIRS += ['locales']
FINAL_TARGET_FILES.features['narrate@mozilla.org'] += [
'bootstrap.js'
]
FINAL_TARGET_PP_FILES.features['narrate@mozilla.org'] += [
'install.rdf.in'
]
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/narrate/test/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ head.js
+ content_narrate.js
+
+[browser_narrate.js]
+[browser_narrate_disable.js]
+[browser_voiceselect.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/narrate/test/browser_narrate.js
@@ -0,0 +1,83 @@
+/* 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/. */
+
+/* globals is, isnot, registerCleanupFunction, add_task */
+
+"use strict";
+
+const TEST_PATH = "http://example.com/browser/browser/base/content/test/general/";
+const TEST_VOICE = "urn:moz-tts:fake-indirect:teresa";
+
+add_task(function* testNarrate() {
+ registerCleanupFunction(teardown);
+
+ setup();
+
+ let tab = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ // Narrate button is showing
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is there");
+
+ // Popup is hidden by default
+ yield content.isHidden(tab, ELEMENT.POPUP, "popup is hidden");
+
+ // Click button to show popup
+ yield content.click(tab, ELEMENT.TOGGLE, "click narrate toggle");
+
+ // Popup is shown
+ yield content.isVisible(tab, ELEMENT.POPUP, "popup is showing");
+
+ // Select widget is showing, but options are not.
+ yield content.isVisible(tab, ELEMENT.VOICE_SELECT, "select button showing");
+ yield selectVoice(tab, TEST_VOICE);
+
+ yield isStoppedState(tab);
+ let speechinfo = yield content.narrateStart(tab, "start narrate", () => {
+ content.click(tab, ELEMENT.START, "start button pressed");
+ });
+ is(speechinfo.voice, TEST_VOICE, "correct voice is used");
+ is(speechinfo.paragraph, 0, "first paragraph is being spoken");
+ yield isNarratingState(tab);
+
+ speechinfo = yield content.narrateStart(tab,
+ "narrate next paragraph", () => {
+ content.click(tab, ELEMENT.FORWARD, "forward button pressed");
+ });
+ is(speechinfo.voice, TEST_VOICE, "correct voice is used");
+ is(speechinfo.paragraph, 1, "second paragraph is being spoken");
+
+ let newspeechinfo = yield content.narrateStart(tab,
+ "adjust rate and start paragraph again", () => {
+ content.sendkey(tab, ELEMENT.RATE, "PAGE_UP", "rate change");
+ });
+ is(newspeechinfo.paragraph, speechinfo.paragraph, "restart same paragraph");
+ isnot(newspeechinfo.rate, speechinfo.rate, "rate changed");
+
+ yield content.narrateStop(tab, "stop narrate", () => {
+ content.click(tab, ELEMENT.STOP, "start button pressed");
+ });
+ yield isStoppedState(tab);
+
+ newspeechinfo = yield content.narrateStart(tab,
+ "start narrate again", () => {
+ content.click(tab, ELEMENT.START, "start button pressed");
+ });
+ yield isNarratingState(tab);
+ is(newspeechinfo.paragraph, speechinfo.paragraph,
+ "start from last paragraph");
+
+ yield content.scroll(tab, 0, 10);
+ yield content.isVisible(tab, ELEMENT.POPUP, "popup is visible");
+
+ yield content.narrateStop(tab, "closing popup stops speech", () => {
+ content.click(tab, ELEMENT.TOGGLE, "narrate popu closed");
+ });
+ yield content.isHidden(tab, ELEMENT.POPUP, "popup is hidden");
+ yield content.click(tab, ELEMENT.TOGGLE, "click narrate toggle");
+ yield isStoppedState(tab);
+
+ yield content.isVisible(tab, ELEMENT.POPUP, "popup is visible");
+ yield content.scroll(tab, 0, -10);
+ yield content.isHidden(tab, ELEMENT.POPUP, "popup is hidden");
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/narrate/test/browser_narrate_disable.js
@@ -0,0 +1,69 @@
+/* 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/. */
+
+/* globals registerCleanupFunction, add_task */
+
+"use strict";
+
+const TEST_PATH = "http://example.com/browser/browser/base/content/test/general/";
+const ENABLE_PREF = "extensions.narrate.enabled";
+
+add_task(function* testNarratePref() {
+ registerCleanupFunction(() => {
+ clearUserPref(ENABLE_PREF);
+ teardown();
+ });
+
+ setup();
+
+ let tab = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is there");
+
+ setBoolPref(ENABLE_PREF, false);
+
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is not here");
+
+ let tab2 = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ yield content.isHidden(tab2, ELEMENT.TOGGLE, "narrate button is not here");
+
+ setBoolPref(ENABLE_PREF, true);
+
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is there");
+
+ yield content.isHidden(tab2, ELEMENT.TOGGLE, "narrate button is there");
+
+ let tab3 = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ yield content.isVisible(tab3, ELEMENT.TOGGLE, "narrate button is there");
+});
+
+add_task(function* testNarrateAddon() {
+ registerCleanupFunction(teardown);
+
+ setup();
+
+ let tab = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is there");
+
+ yield toggleExtension(false);
+
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is not here");
+
+ let tab2 = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ yield content.isHidden(tab2, ELEMENT.TOGGLE, "narrate button is not here");
+
+ yield toggleExtension(true);
+
+ yield content.isVisible(tab, ELEMENT.TOGGLE, "narrate button is there");
+
+ yield content.isHidden(tab2, ELEMENT.TOGGLE, "narrate button is there");
+
+ let tab3 = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ yield content.isVisible(tab3, ELEMENT.TOGGLE, "narrate button is there");
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/narrate/test/browser_voiceselect.js
@@ -0,0 +1,97 @@
+/* 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/. */
+
+/* globals registerCleanupFunction, add_task, is, isnot */
+
+"use strict";
+
+const TEST_PATH = "http://example.com/browser/browser/base/content/test/general/";
+const VOICE_1 = {
+ uri: "urn:moz-tts:fake-direct:lenny",
+ label: "Leonard Cohen (en-CA)"
+};
+
+const VOICE_SELECT_LABEL = "#narrate-voices .select-toggle .current-voice";
+
+add_task(function* testVoiceselectDropdownAutoclose() {
+ registerCleanupFunction(teardown);
+
+ setup();
+
+ let tab = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ // Click button to show popup
+ yield content.click(tab, ELEMENT.TOGGLE, "click narrate toggle");
+
+ yield content.isHidden(tab, ELEMENT.VOICE_OPTIONS, "options are hidden");
+
+ yield content.click(tab, ELEMENT.VOICE_SELECT, "click select widget");
+
+ yield content.isVisible(tab, ELEMENT.VOICE_OPTIONS, "options are showing");
+
+ yield content.click(tab, ELEMENT.TOGGLE, "close popup");
+
+ yield content.isHidden(tab, ELEMENT.POPUP, "popup is hidden");
+
+ yield content.click(tab, ELEMENT.TOGGLE, "open popup");
+ yield content.isVisible(tab, ELEMENT.POPUP, "popup is hidden");
+
+ // When popup re-opens voices dropdown should be closed.
+ yield content.isHidden(tab, ELEMENT.VOICE_OPTIONS, "options are hidden");
+});
+
+add_task(function* testVoiceselectLabelChange() {
+ registerCleanupFunction(teardown);
+
+ setup();
+
+ let tab = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ // Click button to show popup
+ yield content.click(tab, ELEMENT.TOGGLE, "click narrate toggle");
+
+ yield selectVoice(tab, VOICE_1.uri);
+
+ let label = yield content.getText(tab, VOICE_SELECT_LABEL);
+ is(label, VOICE_1.label);
+});
+
+add_task(function* testVoiceselectKeyboard() {
+ registerCleanupFunction(teardown);
+
+ setup();
+
+ let tab = yield enterReaderMode(TEST_PATH + "readerModeArticle.html");
+
+ // Click button to show popup
+ yield content.click(tab, ELEMENT.TOGGLE, "click narrate toggle");
+
+ let firstValue = yield content.getValue(tab, ELEMENT.VOICE_SELECTED);
+
+ yield content.sendkey(tab, ELEMENT.VOICE_SELECT, "DOWN", "change voice");
+
+ let secondValue = yield content.getValue(tab, ELEMENT.VOICE_SELECTED);
+
+ isnot(firstValue, secondValue, "value changed after arrowing in button");
+
+ yield content.isHidden(tab, ELEMENT.VOICE_OPTIONS, "option hidden 1");
+
+ yield content.sendkey(tab, ELEMENT.VOICE_SELECT, "RETURN", "open popup");
+
+ yield content.isVisible(tab, ELEMENT.VOICE_OPTIONS, "options visible");
+
+ yield content.sendkey(tab, null, "UP", "go up one item");
+
+ let thirdValue = yield content.getValue(tab, ELEMENT.VOICE_SELECTED);
+
+ is(thirdValue, secondValue, "arrowing in options shouldn't change selection");
+
+ yield content.sendkey(tab, null, "RETURN", "select and close options");
+
+ yield content.isHidden(tab, ELEMENT.VOICE_OPTIONS, "option hidden 2");
+
+ let fourthValue = yield content.getValue(tab, ELEMENT.VOICE_SELECTED);
+
+ is(fourthValue, firstValue, "selection changes back to first value");
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/narrate/test/content_narrate.js
@@ -0,0 +1,132 @@
+/* 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/. */
+
+/* globals content, sendAsyncMessage, addMessageListener */
+
+// This file is loaded as a "content script" for browser narrate tests
+
+"use strict";
+
+var {interfaces: Ci, utils: Cu, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+var EventUtils = {
+ "_EU_Ci": Ci,
+ "_EU_Cc": Cc,
+
+ get window() {
+ return content;
+ },
+
+ get parent() {
+ return content;
+ },
+
+ get KeyboardEvent() {
+ return content.KeyboardEvent;
+ },
+
+ get navigator() {
+ return content.navigator;
+ },
+
+ get KeyEvent() {
+ return content.KeyEvent;
+ }
+};
+Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+function observe(subject, topic) {
+ sendAsyncMessage("test:document:load");
+ Services.obs.removeObserver(observe, topic);
+}
+
+Services.obs.addObserver(observe, "AboutReader:Ready", false);
+
+function _msgHandler(msg, cb, failIfNotFound = true) {
+ // if no selector is provided, use the focused element
+ let element = msg.data.selector ?
+ content.document.querySelector(msg.data.selector) :
+ content.document.activeElement;
+ let responseName = msg.data.responseName || (msg.name + ":response");
+ if (!element && failIfNotFound) {
+ sendAsyncMessage(responseName, { msg: "No element found" });
+ } else {
+ sendAsyncMessage(responseName, { result: cb(element) });
+ }
+}
+
+addMessageListener("test:element-visible", msg => {
+ _msgHandler(msg, e => !!(e && isVisible(e)), false);
+});
+
+addMessageListener("test:element-selected", msg => {
+ _msgHandler(msg,
+ e => e.selected || JSON.parse(e.getAttribute("aria-selected") || "false"));
+});
+
+addMessageListener("test:element-enabled", msg => {
+ _msgHandler(msg, e => !e.disabled);
+});
+
+addMessageListener("test:element-click", msg => {
+ _msgHandler(msg, e => {
+ e.click();
+ return true;
+ });
+});
+
+addMessageListener("test:element-focus", msg => {
+ _msgHandler(msg, e => {
+ e.focus();
+ return true;
+ });
+});
+
+addMessageListener("test:element-sendkey", msg => {
+ _msgHandler(msg, e => {
+ e.focus();
+ EventUtils.sendKey(msg.data.key, content);
+ return true;
+ });
+});
+
+addMessageListener("test:element-get-text", msg => {
+ _msgHandler(msg, e => {
+ return e.textContent;
+ });
+});
+
+addMessageListener("test:element-get-value", msg => {
+ _msgHandler(msg, e => {
+ return e.value || e.dataset.value;
+ });
+});
+
+addMessageListener("test:scroll", msg => {
+ function sendResponse() {
+ sendAsyncMessage(msg.data.responseName, { });
+ content.removeEventListener("scroll", sendResponse);
+ }
+ content.addEventListener("scroll", sendResponse);
+ content.scrollBy(msg.data.x, msg.data.y);
+});
+
+function isVisible(element) {
+ let style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+ if (style.display == "none") {
+ return false;
+ } else if (style.visibility != "visible") {
+ return false;
+ } else if (style.display == "-moz-popup" && element.state != "open") {
+ return false;
+ }
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument) {
+ return isVisible(element.parentNode);
+ }
+
+ return true;
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/narrate/test/head.js
@@ -0,0 +1,237 @@
+/* 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/. */
+
+/* globals ok, isnot, gBrowser */
+/* exported ELEMENT, isStoppedState, isNarratingState, selectVoice,
+ enterReaderMode, content, teardown, setup, toggleExtension */
+
+"use strict";
+
+const ELEMENT = {
+ 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",
+ RATE: "#narrate-rate-input",
+ START: "#narrate-start-stop:not(.speaking)",
+ STOP: "#narrate-start-stop.speaking",
+ BACK: "#narrate-skip-previous",
+ FORWARD: "#narrate-skip-next"
+};
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+
+const TEST_PREFS = [
+ ["reader.parse-on-load.enabled", true],
+ ["media.webspeech.synth.test", true],
+ ["browser.reader.detectedFirstArticle", true],
+ ["extensions.narrate.test", true]
+];
+
+const CHROME_BASE =
+ "chrome://mochitests/content/browser/browser/extensions/narrate/test/";
+
+const ADDON_ID = "narrate@mozilla.org";
+
+function voiceOption(voiceUri) {
+ return '#narrate-voices .option[data-value="' + voiceUri + '"]';
+}
+
+function isStoppedState(tab) {
+ return Promise.all([
+ content.isDisabled(tab, ELEMENT.BACK, "back button is disabled"),
+ content.isDisabled(tab, ELEMENT.FORWARD, "forward button is disabled"),
+ content.isHidden(tab, ELEMENT.STOP, "stop button is hidden"),
+ content.isVisible(tab, ELEMENT.START, "start button is showing")]);
+}
+
+function isNarratingState(tab) {
+ return Promise.all([
+ content.isEnabled(tab, ELEMENT.BACK, "back button is enabled"),
+ content.isEnabled(tab, ELEMENT.FORWARD, "forward button is enabled"),
+ content.isVisible(tab, ELEMENT.STOP, "stop button is showing"),
+ content.isHidden(tab, ELEMENT.START, "start button is hidden")]);
+}
+
+function selectVoice(tab, voiceUri) {
+ return content.isHidden(tab, ELEMENT.VOICE_OPTIONS, "options hidden")
+ .then(
+ () => content.click(tab, ELEMENT.VOICE_SELECT, "click select widget"))
+ .then(
+ () => content.isVisible(tab, ELEMENT.VOICE_OPTIONS, "options visible"))
+ .then(
+ () => content.focus(tab, voiceOption(voiceUri), "focused option"))
+ .then(
+ () => content.click(tab, voiceOption(voiceUri), "select option"))
+ .then(
+ () => content.isHidden(tab, ELEMENT.VOICE_OPTIONS, "options hidden"))
+ .then(
+ () => content.isSelected(tab, voiceOption(voiceUri), "option selected"));
+}
+
+function setBoolPref(name, value) {
+ Services.prefs.setBoolPref(name, value);
+}
+
+function clearUserPref(name) {
+ Services.prefs.clearUserPref(name);
+}
+
+function setup() {
+ // Set required test prefs.
+ TEST_PREFS.forEach(([name, value]) => {
+ setBoolPref(name, value);
+ });
+}
+
+function enterReaderMode(url) {
+ return promiseNewTabLoadEvent(
+ "about:reader?url=" + encodeURIComponent(url),
+ CHROME_BASE + "content_narrate.js");
+}
+
+function promiseNewTabLoadEvent(url, frameScript) {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = tab.linkedBrowser;
+ let mm = browser.messageManager;
+
+ // give it an e10s-friendly content script to help with our tests.
+ mm.loadFrameScript(frameScript, true);
+ // and wait for it to tell us about the load.
+ return promiseOneMessage(tab, "test:document:load").then(
+ () => tab
+ );
+}
+
+function promiseOneMessage(tab, messageName) {
+ let mm = tab ? tab.linkedBrowser.messageManager : Services.ppmm;
+ let deferred = Promise.defer();
+ mm.addMessageListener(messageName, function onmessage(message) {
+ mm.removeMessageListener(messageName, onmessage);
+ deferred.resolve(message);
+ });
+ return deferred.promise;
+}
+
+function teardown() {
+ // Reset test prefs.
+ TEST_PREFS.forEach(pref => {
+ clearUserPref(pref[0]);
+ });
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+}
+
+function toggleExtension(enabled) {
+ let deferred = Promise.defer();
+ AddonManager.getAddonByID(ADDON_ID, addon => {
+ addon.userDisabled = !enabled;
+ deferred.resolve();
+ });
+ return deferred.promise;
+}
+
+function notOk(value, msg) {
+ ok(!value, msg);
+}
+
+var content = {
+ _generateResponseName: function(cmd) {
+ return "test:" + cmd + ":response:" + Math.random();
+ },
+
+ _do: function(tab, cmd, data, checkFunc, msg) {
+ let mm = tab.linkedBrowser.messageManager;
+ data.responseName = this._generateResponseName(cmd);
+ mm.sendAsyncMessage("test:" + cmd, data);
+ return promiseOneMessage(tab, data.responseName).then(
+ m => {
+ if (checkFunc) {
+ checkFunc(
+ m.data.result, msg + (m.data.msg ? " [" + m.data.msg + "]" : ""));
+ } else {
+ isnot(m.data.result, undefined, m.data.msg || cmd);
+ }
+ return m.data.result;
+ });
+ },
+
+ isVisible: function(tab, selector, msg) {
+ return this._do(tab, "element-visible", { selector: selector }, ok, msg);
+ },
+
+ isHidden: function(tab, selector, msg) {
+ return this._do(tab, "element-visible", { selector: selector }, notOk, msg);
+ },
+
+ click: function(tab, selector, msg) {
+ return this._do(tab, "element-click", { selector: selector }, ok, msg);
+ },
+
+ sendkey: function(tab, selector, key, msg) {
+ return this._do(tab, "element-sendkey",
+ { selector: selector, key: key }, ok, msg);
+ },
+
+ focus: function(tab, selector, msg) {
+ return this._do(tab, "element-focus", { selector: selector }, ok, msg);
+ },
+
+ isSelected: function(tab, selector, msg) {
+ return this._do(tab, "element-selected", { selector: selector }, ok, msg);
+ },
+
+ getValue: function(tab, selector, msg) {
+ return this._do(tab, "element-get-value", { selector: selector }, null,
+ msg);
+ },
+
+ isEnabled: function(tab, selector, msg) {
+ return this._do(tab, "element-enabled", { selector: selector }, ok, msg);
+ },
+
+ isDisabled: function(tab, selector, msg) {
+ return this._do(tab, "element-enabled", { selector: selector }, notOk, msg);
+ },
+
+ scroll: function(tab, x, y) {
+ let mm = tab.linkedBrowser.messageManager;
+ let responseName = this._generateResponseName("scroll");
+ mm.sendAsyncMessage("test:scroll",
+ { x: x, y: y, responseName: responseName });
+ return promiseOneMessage(tab, responseName);
+ },
+
+ getText: function(tab, selector, msg) {
+ return this._do(tab, "element-get-text", { selector: selector }, null,
+ msg);
+ },
+
+ narrateStart: function(tab, msg, func) {
+ let interact = func();
+ let startparagraph = promiseOneMessage(null, "Narrate:StartParagraph");
+ return Promise.all([interact, startparagraph]).then(args => {
+ ok(true, msg);
+ return args[1].data;
+ });
+ },
+
+ narrateStop: function(tab, msg, func) {
+ let interact = func();
+ let endparagraph = promiseOneMessage(null, "Narrate:EndParagraph");
+ return Promise.all([interact, endparagraph]).then(args => {
+ ok(true, msg);
+ return args[1].data;
+ });
+ }
+};