Bug 1171344 - Update in-content search UI tests. r=adw

This commit is contained in:
Nihanth Subramanya 2015-06-29 13:52:22 -07:00
parent a063f90543
commit a3423dfcbc
14 changed files with 802 additions and 649 deletions

View File

@ -80,6 +80,7 @@ support-files =
redirect_bug623155.sjs
searchSuggestionEngine.sjs
searchSuggestionEngine.xml
searchSuggestionEngine2.xml
subtst_contextmenu.html
test-mixedcontent-securityerrors.html
test_bug435035.html
@ -369,10 +370,10 @@ skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synt
skip-if = buildapp == 'mulet'
[browser_save_video_frame.js]
[browser_scope.js]
[browser_searchSuggestionUI.js]
[browser_contentSearchUI.js]
support-files =
searchSuggestionUI.html
searchSuggestionUI.js
contentSearchUI.html
contentSearchUI.js
[browser_selectpopup.js]
run-if = e10s
[browser_selectTabAtIndex.js]

View File

@ -101,28 +101,15 @@ let gTests = [
// Make this actually work in healthreport by giving it an ID:
engine.wrappedJSObject._identifier = 'org.mozilla.testsearchsuggestions';
let promise = promiseBrowserAttributes(gBrowser.selectedTab);
let p = promiseContentSearchChange(engine.name);
Services.search.currentEngine = engine;
yield promise;
yield p;
let numSearchesBefore = 0;
let searchEventDeferred = Promise.defer();
let doc = gBrowser.contentDocument;
let engineName = doc.documentElement.getAttribute("searchEngineName");
let engineName = gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name;
is(engine.name, engineName, "Engine name in DOM should match engine we just added");
let mm = gBrowser.selectedBrowser.messageManager;
mm.loadFrameScript(TEST_CONTENT_HELPER, false);
mm.addMessageListener("AboutHomeTest:CheckRecordedSearch", function (msg) {
let data = JSON.parse(msg.data);
is(data.engineName, engineName, "Detail is search engine name");
getNumberOfSearches(engineName).then(num => {
is(num, numSearchesBefore + 1, "One more search recorded.");
searchEventDeferred.resolve();
});
});
// Get the current number of recorded searches.
let searchStr = "a search";
@ -137,7 +124,12 @@ let gTests = [
let expectedURL = Services.search.currentEngine.
getSubmission(searchStr, null, "homepage").
uri.spec;
let loadPromise = waitForDocLoadAndStopIt(expectedURL);
let loadPromise = waitForDocLoadAndStopIt(expectedURL).then(() => {
getNumberOfSearches(engineName).then(num => {
is(num, numSearchesBefore + 1, "One more search recorded.");
searchEventDeferred.resolve();
});
});
try {
yield Promise.all([searchEventDeferred.promise, loadPromise]);
@ -236,11 +228,11 @@ let gTests = [
{
desc: "Check POST search engine support",
setup: function() {},
run: function()
run: function* ()
{
let deferred = Promise.defer();
let currEngine = Services.search.defaultEngine;
let searchObserver = function search_observer(aSubject, aTopic, aData) {
let searchObserver = Task.async(function* search_observer(aSubject, aTopic, aData) {
let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
info("Observer: " + aData + " for " + engine.name);
@ -255,24 +247,15 @@ let gTests = [
let document = gBrowser.selectedBrowser.contentDocument;
let searchText = document.getElementById("searchText");
// We're about to change the search engine. Once the change has
// propagated to the about:home content, we want to perform a search.
let mutationObserver = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
if (mutation.attributeName == "searchEngineName") {
searchText.value = needle;
searchText.focus();
EventUtils.synthesizeKey("VK_RETURN", {});
}
}
});
mutationObserver.observe(document.documentElement, { attributes: true });
// Change the search engine, triggering the observer above.
let p = promiseContentSearchChange(engine.name);
Services.search.defaultEngine = engine;
yield p;
searchText.value = needle;
searchText.focus();
EventUtils.synthesizeKey("VK_RETURN", {});
registerCleanupFunction(function() {
mutationObserver.disconnect();
Services.search.removeEngine(engine);
Services.search.defaultEngine = currEngine;
});
@ -286,7 +269,7 @@ let gTests = [
"Search text should arrive correctly");
deferred.resolve();
});
};
});
Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
registerCleanupFunction(function () {
Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
@ -335,8 +318,7 @@ let gTests = [
},
{
// See browser_searchSuggestionUI.js for comprehensive content search
// suggestion UI tests.
// See browser_contentSearchUI.js for comprehensive content search UI tests.
desc: "Search suggestion smoke test",
setup: function() {},
run: function()
@ -344,12 +326,12 @@ let gTests = [
return Task.spawn(function* () {
// Add a test engine that provides suggestions and switch to it.
let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
let promise = promiseBrowserAttributes(gBrowser.selectedTab);
let p = promiseContentSearchChange(engine.name);
Services.search.currentEngine = engine;
yield promise;
yield p;
// Avoid intermittent failures.
gBrowser.contentWindow.wrappedJSObject.gSearchSuggestionController.remoteTimeout = 5000;
gBrowser.contentWindow.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
// Type an X in the search input.
let input = gBrowser.contentDocument.getElementById("searchText");
@ -401,9 +383,11 @@ let gTests = [
caret: { start: 1, length: 0 }
}, gBrowser.contentWindow);
let searchController =
gBrowser.contentWindow.wrappedJSObject.gContentSearchController;
// Wait for the search suggestions to become visible.
let table =
gBrowser.contentDocument.getElementById("searchSuggestionTable");
let table = searchController._suggestionsList;
let deferred = Promise.defer();
let observer = new MutationObserver(() => {
if (input.getAttribute("aria-expanded") == "true") {
@ -424,9 +408,14 @@ let gTests = [
uri.spec;
let loadPromise = waitForDocLoadAndStopIt(expectedURL);
let row = table.children[1];
EventUtils.sendMouseEvent({ type: "mousedown" }, row, gBrowser.contentWindow);
// ContentSearchUIController looks at the current selectedIndex when
// performing a search. Synthesizing the mouse event on the suggestion
// doesn't actually mouseover the suggestion and trigger it to be flagged
// as selected, so we manually select it first.
searchController.selectedIndex = 1;
EventUtils.synthesizeMouseAtCenter(row, {button: 0}, gBrowser.contentWindow);
yield loadPromise;
ok(input.value == "xbar", "Suggestion is selected");
ok(input.value == "x", "Input value did not change");
});
}
},
@ -477,26 +466,6 @@ let gTests = [
is(gBrowser.currentURI.spec, "about:accounts?entrypoint=abouthome",
"Entry point should be `abouthome`.");
})
},
{
desc: "Clicking the icon should open the popup",
setup: function () {},
run: Task.async(function* () {
let doc = gBrowser.selectedBrowser.contentDocument;
let searchIcon = doc.getElementById("searchIcon");
let panel = window.document.getElementById("abouthome-search-panel");
info("Waiting for popup to open");
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, gBrowser.selectedBrowser.contentWindow);
yield promiseWaitForEvent(panel, "popupshown");
info("Saw popup open");
let promise = promisePrefsOpen();
let item = window.document.getElementById("abouthome-search-panel-manage");
EventUtils.synthesizeMouseAtCenter(item, {});
yield promise;
})
}
];
@ -576,38 +545,6 @@ function promiseSetupSnippetsMap(aTab, aSetupFn)
return deferred.promise;
}
/**
* Waits for the attributes being set by browser.js.
*
* @param aTab
* The tab containing about:home.
* @return {Promise} resolved when the attributes are ready.
*/
function promiseBrowserAttributes(aTab)
{
let deferred = Promise.defer();
let docElt = aTab.linkedBrowser.contentDocument.documentElement;
let observer = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
info("Got attribute mutation: " + mutation.attributeName +
" from " + mutation.oldValue);
// Now we just have to wait for the last attribute.
if (mutation.attributeName == "searchEngineName") {
info("Remove attributes observer");
observer.disconnect();
// Must be sure to continue after the page mutation observer.
executeSoon(function() deferred.resolve());
break;
}
}
});
info("Add attributes observer");
observer.observe(docElt, { attributes: true });
return deferred.promise;
}
/**
* Retrieves the number of about:home searches recorded for the current day.
*
@ -711,6 +648,18 @@ let promisePrefsOpen = Task.async(function*() {
}
});
function promiseContentSearchChange(newEngineName) {
return new Promise(resolve => {
content.addEventListener("ContentSearchService", function listener(aEvent) {
if (aEvent.detail.type == "CurrentState" &&
gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name == newEngineName) {
content.removeEventListener("ContentSearchService", listener);
resolve();
}
});
});
}
function promiseNewEngine(basename) {
info("Waiting for engine to be added: " + basename);
let addDeferred = Promise.defer();

View File

@ -0,0 +1,529 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_PAGE_BASENAME = "contentSearchUI.html";
const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js";
const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
const TEST_MSG = "ContentSearchUIControllerTest";
add_task(function* emptyInput() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_BACK_SPACE");
checkState(state, "", [], -1);
yield msg("reset");
});
add_task(function* blur() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("blur");
checkState(state, "x", [], -1);
yield msg("reset");
});
add_task(function* upDownKeys() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle down the suggestions starting from no selection.
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], 2);
state = yield msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], 3);
state = yield msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle up starting from no selection.
state = yield msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], 3);
state = yield msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], 2);
state = yield msg("key", "VK_UP");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = yield msg("key", "VK_UP");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = yield msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
add_task(function* rightLeftKeys() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_LEFT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_LEFT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_RIGHT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_RIGHT");
checkState(state, "x", [], -1);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
// This should make the xfoo suggestion sticky. To make sure it sticks,
// trigger suggestions again and cycle through them by pressing Down until
// nothing is selected again.
state = yield msg("key", "VK_RIGHT");
checkState(state, "xfoo", [], 0);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
yield msg("reset");
});
add_task(function* mouse() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
for (let i = 0; i < 4; ++i) {
state = yield msg("mousemove", i);
checkState(state, "x", ["xfoo", "xbar"], i);
}
state = yield msg("mousemove", -1);
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
add_task(function* formHistory() {
yield setUp();
// Type an X and add it to form history.
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Wait for Satchel to say it's been added to form history.
let deferred = Promise.defer();
Services.obs.addObserver(function onAdd(subj, topic, data) {
if (data == "formhistory-add") {
Services.obs.removeObserver(onAdd, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
yield Promise.all([msg("addInputValueToFormHistory"), deferred.promise]);
// Reset the input.
state = yield msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should appear.
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
-1);
// Select the form history entry and delete it.
state = yield msg("key", "VK_DOWN");
checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
0);
// Wait for Satchel.
deferred = Promise.defer();
Services.obs.addObserver(function onRemove(subj, topic, data) {
if (data == "formhistory-remove") {
Services.obs.removeObserver(onRemove, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
state = yield msg("key", "VK_DELETE");
checkState(state, "x", ["xfoo", "xbar"], -1);
yield deferred.promise;
// Reset the input.
state = yield msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should still be gone.
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
add_task(function* search() {
yield setUp();
let modifiers = {};
["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true);
// Test typing a query and pressing enter.
let p = msg("waitForSearch");
yield msg("key", { key: "x", waitForSuggestions: true });
yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
let mesg = yield p;
let eventData = {
engineName: TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME,
searchString: "x",
healthReportKey: "test",
searchPurpose: "test",
originalEvent: modifiers,
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Test typing a query, then selecting a suggestion and pressing enter.
p = msg("waitForSearch");
yield msg("key", { key: "x", waitForSuggestions: true });
yield msg("key", "VK_DOWN");
yield msg("key", "VK_DOWN");
yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
mesg = yield p;
eventData.searchString = "xfoo";
eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
eventData.selection = {
index: 1,
kind: "key",
}
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Test typing a query, then selecting a one-off button and pressing enter.
p = msg("waitForSearch");
yield msg("key", { key: "x", waitForSuggestions: true });
yield msg("key", "VK_UP");
yield msg("key", "VK_UP");
yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
mesg = yield p;
delete eventData.selection;
eventData.searchString = "x";
eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Test typing a query and clicking the search engine header.
p = msg("waitForSearch");
modifiers.button = 0;
yield msg("key", { key: "x", waitForSuggestions: true });
yield msg("mousemove", -1);
yield msg("click", { eltIdx: -1, modifiers: modifiers });
mesg = yield p;
eventData.originalEvent = modifiers;
eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Test typing a query and then clicking a suggestion.
yield msg("key", { key: "x", waitForSuggestions: true });
p = msg("waitForSearch");
yield msg("mousemove", 1);
yield msg("click", { eltIdx: 1, modifiers: modifiers });
mesg = yield p;
eventData.searchString = "xfoo";
eventData.selection = {
index: 1,
kind: "mouse",
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Test typing a query and then clicking a one-off button.
yield msg("key", { key: "x", waitForSuggestions: true });
p = msg("waitForSearch");
yield msg("mousemove", 3);
yield msg("click", { eltIdx: 3, modifiers: modifiers });
mesg = yield p;
eventData.searchString = "x";
eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
delete eventData.selection;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Test searching when using IME composition.
let state = yield msg("startComposition", { data: "" });
checkState(state, "", [], -1);
state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
checkState(state, "x", [{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" }, "xbar"], -1);
yield msg("commitComposition");
delete modifiers.button;
p = msg("waitForSearch");
yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
mesg = yield p;
eventData.originalEvent = modifiers;
eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
state = yield msg("startComposition", { data: "" });
checkState(state, "", [], -1);
state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
checkState(state, "x", [{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" }, "xbar"], -1);
// Mouse over the first suggestion.
state = yield msg("mousemove", 0);
checkState(state, "x", [{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" }, "xbar"], 0);
// Mouse over the second suggestion.
state = yield msg("mousemove", 1);
checkState(state, "x", [{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" }, "xbar"], 1);
modifiers.button = 0;
let currentTab = gBrowser.selectedTab;
p = msg("waitForSearch");
yield msg("click", { eltIdx: 1, modifiers: modifiers });
mesg = yield p;
eventData.searchString = "xfoo";
eventData.originalEvent = modifiers;
eventData.selection = {
index: 1,
kind: "mouse",
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
yield promiseTab();
yield setUp();
// Remove form history entries.
// Wait for Satchel.
let deferred = Promise.defer();
let historyCount = 2;
Services.obs.addObserver(function onRemove(subj, topic, data) {
if (data == "formhistory-remove") {
if (--historyCount) {
return;
}
Services.obs.removeObserver(onRemove, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
yield msg("key", { key: "x", waitForSuggestions: true });
yield msg("key", "VK_DOWN");
yield msg("key", "VK_DOWN");
yield msg("key", "VK_DELETE");
yield msg("key", "VK_DOWN");
yield msg("key", "VK_DELETE");
yield deferred.promise;
yield msg("reset");
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
yield promiseTab();
yield setUp();
yield msg("reset");
});
add_task(function* settings() {
yield setUp();
yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
yield msg("key", "VK_UP");
let p = msg("waitForSearchSettings");
yield msg("key", "VK_RETURN");
yield p;
yield msg("reset");
});
let gDidInitialSetUp = false;
function setUp(aNoEngine) {
return Task.spawn(function* () {
if (!gDidInitialSetUp) {
Cu.import("resource:///modules/ContentSearch.jsm");
let originalOnMessageSearch = ContentSearch._onMessageSearch;
let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines;
ContentSearch._onMessageSearch = () => {};
ContentSearch._onMessageManageEngines = () => {};
registerCleanupFunction(() => {
ContentSearch._onMessageSearch = originalOnMessageSearch;
ContentSearch._onMessageManageEngines = originalOnMessageManageEngines;
});
yield setUpEngines();
yield promiseTab();
gDidInitialSetUp = true;
}
yield msg("focus");
});
}
function msg(type, data=null) {
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: type,
data: data,
});
let deferred = Promise.defer();
gMsgMan.addMessageListener(TEST_MSG, function onMsg(msg) {
if (msg.data.type != type) {
return;
}
gMsgMan.removeMessageListener(TEST_MSG, onMsg);
deferred.resolve(msg.data.data);
});
return deferred.promise;
}
function checkState(actualState, expectedInputVal, expectedSuggestions,
expectedSelectedIdx) {
expectedSuggestions = expectedSuggestions.map(sugg => {
return typeof(sugg) == "object" ? sugg : {
str: sugg,
type: "remote",
};
});
let expectedState = {
selectedIndex: expectedSelectedIdx,
numSuggestions: expectedSuggestions.length,
suggestionAtIndex: expectedSuggestions.map(s => s.str),
isFormHistorySuggestionAtIndex:
expectedSuggestions.map(s => s.type == "formHistory"),
tableHidden: expectedSuggestions.length == 0,
inputValue: expectedInputVal,
ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
};
SimpleTest.isDeeply(actualState, expectedState, "State");
}
var gMsgMan;
function promiseTab() {
let deferred = Promise.defer();
let tab = gBrowser.addTab();
registerCleanupFunction(() => gBrowser.removeTab(tab));
gBrowser.selectedTab = tab;
let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
gMsgMan = tab.linkedBrowser.messageManager;
gMsgMan.sendAsyncMessage("ContentSearch", {
type: "AddToWhitelist",
data: [pageURL],
});
promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => {
let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
gMsgMan.loadFrameScript(jsURL, false);
deferred.resolve(msg("init"));
});
}, true, true);
openUILinkIn(pageURL, "current");
return deferred.promise;
}
function promiseMsg(name, type, msgMan) {
let deferred = Promise.defer();
info("Waiting for " + name + " message " + type + "...");
msgMan.addMessageListener(name, function onMsg(msg) {
info("Received " + name + " message " + msg.data.type + "\n");
if (msg.data.type == type) {
msgMan.removeMessageListener(name, onMsg);
deferred.resolve(msg);
}
});
return deferred.promise;
}
function setUpEngines() {
return Task.spawn(function* () {
info("Removing default search engines");
let currentEngineName = Services.search.currentEngine.name;
let currentEngines = Services.search.getVisibleEngines();
info("Adding test search engines");
let engine1 = yield promiseNewEngine(TEST_ENGINE_BASENAME);
let engine2 = yield promiseNewEngine(TEST_ENGINE_2_BASENAME);
Services.search.currentEngine = engine1;
for (let engine of currentEngines) {
Services.search.removeEngine(engine);
}
registerCleanupFunction(() => {
Services.search.restoreDefaultEngines();
Services.search.removeEngine(engine1);
Services.search.removeEngine(engine2);
Services.search.currentEngine = Services.search.getEngineByName(currentEngineName);
});
});
}
function promiseNewEngine(basename) {
info("Waiting for engine to be added: " + basename);
let addDeferred = Promise.defer();
let url = getRootDirectory(gTestPath) + basename;
Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
onSuccess: function (engine) {
info("Search engine added: " + basename);
addDeferred.resolve(engine);
},
onError: function (errCode) {
ok(false, "addEngine failed with error code " + errCode);
addDeferred.reject();
},
});
return addDeferred.promise;
}

View File

@ -1,345 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_PAGE_BASENAME = "searchSuggestionUI.html";
const TEST_CONTENT_SCRIPT_BASENAME = "searchSuggestionUI.js";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
const TEST_MSG = "SearchSuggestionUIControllerTest";
add_task(function* emptyInput() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_BACK_SPACE");
checkState(state, "", [], -1);
yield msg("reset");
});
add_task(function* blur() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("blur");
checkState(state, "x", [], -1);
yield msg("reset");
});
add_task(function* arrowKeys() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle down the suggestions starting from no selection.
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle up starting from no selection.
state = yield msg("key", "VK_UP");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = yield msg("key", "VK_UP");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = yield msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
// The right arrow and return key function the same.
function rightArrowOrReturn(keyName) {
return function* rightArrowOrReturnTest() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
// This should make the xfoo suggestion sticky. To make sure it sticks,
// trigger suggestions again and cycle through them by pressing Down until
// nothing is selected again.
state = yield msg("key", keyName);
checkState(state, "xfoo", [], -1);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
yield msg("reset");
};
}
add_task(rightArrowOrReturn("VK_RIGHT"));
add_task(rightArrowOrReturn("VK_RETURN"));
add_task(function* mouse() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Mouse over the first suggestion.
state = yield msg("mousemove", 0);
checkState(state, "x", ["xfoo", "xbar"], 0);
// Mouse over the second suggestion.
state = yield msg("mousemove", 1);
checkState(state, "x", ["xfoo", "xbar"], 1);
// Click the second suggestion. This should make it sticky. To make sure it
// sticks, trigger suggestions again and cycle through them by pressing Down
// until nothing is selected again.
state = yield msg("mousedown", 1);
checkState(state, "xbar", [], -1);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
yield msg("reset");
});
add_task(function* formHistory() {
yield setUp();
// Type an X and add it to form history.
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("addInputValueToFormHistory");
// Wait for Satchel to say it's been added to form history.
let deferred = Promise.defer();
Services.obs.addObserver(function onAdd(subj, topic, data) {
if (data == "formhistory-add") {
Services.obs.removeObserver(onAdd, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
yield deferred.promise;
// Reset the input.
state = yield msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should appear.
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
-1);
// Select the form history entry and delete it.
state = yield msg("key", "VK_DOWN");
checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
0);
state = yield msg("key", "VK_DELETE");
checkState(state, "x", ["xfoo", "xbar"], -1);
// Wait for Satchel.
deferred = Promise.defer();
Services.obs.addObserver(function onRemove(subj, topic, data) {
if (data == "formhistory-remove") {
Services.obs.removeObserver(onRemove, "satchel-storage-changed");
executeSoon(() => deferred.resolve());
}
}, "satchel-storage-changed", false);
yield deferred.promise;
// Reset the input.
state = yield msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should still be gone.
state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
yield msg("reset");
});
add_task(function* composition() {
yield setUp();
let state = yield msg("startComposition", { data: "" });
checkState(state, "", [], -1);
state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Mouse over the first suggestion.
state = yield msg("mousemove", 0);
checkState(state, "x", ["xfoo", "xbar"], 0);
// Mouse over the second suggestion.
state = yield msg("mousemove", 1);
checkState(state, "x", ["xfoo", "xbar"], 1);
// Click the second suggestion. This should make it sticky. To make sure it
// sticks, trigger suggestions again and cycle through them by pressing Down
// until nothing is selected again.
state = yield msg("mousedown", 1);
checkState(state, "xbar", [], -1);
state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1);
state = yield msg("key", "VK_DOWN");
checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
yield msg("reset");
});
let gDidInitialSetUp = false;
function setUp() {
return Task.spawn(function* () {
if (!gDidInitialSetUp) {
yield promiseNewEngine(TEST_ENGINE_BASENAME);
yield promiseTab();
gDidInitialSetUp = true;
}
yield msg("focus");
});
}
function msg(type, data=null) {
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: type,
data: data,
});
let deferred = Promise.defer();
gMsgMan.addMessageListener(TEST_MSG, function onMsg(msg) {
gMsgMan.removeMessageListener(TEST_MSG, onMsg);
deferred.resolve(msg.data);
});
return deferred.promise;
}
function checkState(actualState, expectedInputVal, expectedSuggestions,
expectedSelectedIdx) {
expectedSuggestions = expectedSuggestions.map(sugg => {
return typeof(sugg) == "object" ? sugg : {
str: sugg,
type: "remote",
};
});
let expectedState = {
selectedIndex: expectedSelectedIdx,
numSuggestions: expectedSuggestions.length,
suggestionAtIndex: expectedSuggestions.map(s => s.str),
isFormHistorySuggestionAtIndex:
expectedSuggestions.map(s => s.type == "formHistory"),
tableHidden: expectedSuggestions.length == 0,
tableChildrenLength: expectedSuggestions.length,
tableChildren: expectedSuggestions.map((s, i) => {
let expectedClasses = new Set([s.type]);
if (i == expectedSelectedIdx) {
expectedClasses.add("selected");
}
return {
textContent: s.str,
classes: expectedClasses,
};
}),
inputValue: expectedInputVal,
ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
};
SimpleTest.isDeeply(actualState, expectedState, "State");
}
var gMsgMan;
function promiseTab() {
let deferred = Promise.defer();
let tab = gBrowser.addTab();
registerCleanupFunction(() => gBrowser.removeTab(tab));
gBrowser.selectedTab = tab;
let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
gMsgMan = tab.linkedBrowser.messageManager;
gMsgMan.sendAsyncMessage("ContentSearch", {
type: "AddToWhitelist",
data: [pageURL],
});
promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => {
let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
gMsgMan.loadFrameScript(jsURL, false);
deferred.resolve();
});
}, true, true);
openUILinkIn(pageURL, "current");
return deferred.promise;
}
function promiseMsg(name, type, msgMan) {
let deferred = Promise.defer();
info("Waiting for " + name + " message " + type + "...");
msgMan.addMessageListener(name, function onMsg(msg) {
info("Received " + name + " message " + msg.data.type + "\n");
if (msg.data.type == type) {
msgMan.removeMessageListener(name, onMsg);
deferred.resolve(msg);
}
});
return deferred.promise;
}
function promiseNewEngine(basename) {
info("Waiting for engine to be added: " + basename);
let addDeferred = Promise.defer();
let url = getRootDirectory(gTestPath) + basename;
Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
onSuccess: function (engine) {
info("Search engine added: " + basename);
registerCleanupFunction(() => Services.search.removeEngine(engine));
addDeferred.resolve(engine);
},
onError: function (errCode) {
ok(false, "addEngine failed with error code " + errCode);
addDeferred.reject();
},
});
return addDeferred.promise;
}

View File

@ -9,12 +9,13 @@
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js">
</script>
<script type="application/javascript;version=1.8"
src="chrome://browser/content/searchSuggestionUI.js">
src="chrome://browser/content/contentSearchUI.js">
</script>
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/>
</head>
<body>
<input>
<div id="container" style="position: relative;"><input type="text" value=""/></div>
</body>
</html>

View File

@ -0,0 +1,189 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
(function () {
const TEST_MSG = "ContentSearchUIControllerTest";
const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
var gController;
addMessageListener(TEST_MSG, msg => {
messageHandlers[msg.data.type](msg.data.data);
});
let messageHandlers = {
init: function() {
Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME);
let input = content.document.querySelector("input");
gController =
new content.ContentSearchUIController(input, input.parentNode, "test", "test");
content.addEventListener("ContentSearchService", function listener(aEvent) {
if (aEvent.detail.type == "State" &&
gController.defaultEngine.name == ENGINE_NAME) {
content.removeEventListener("ContentSearchService", listener);
ack("init");
}
});
gController.remoteTimeout = 5000;
},
key: function (arg) {
let keyName = typeof(arg) == "string" ? arg : arg.key;
content.synthesizeKey(keyName, arg.modifiers || {});
let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
wait(ack.bind(null, "key"));
},
startComposition: function (arg) {
content.synthesizeComposition({ type: "compositionstart", data: "" });
ack("startComposition");
},
changeComposition: function (arg) {
let data = typeof(arg) == "string" ? arg : arg.data;
content.synthesizeCompositionChange({
composition: {
string: data,
clauses: [
{ length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
]
},
caret: { start: data.length, length: 0 }
});
let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
wait(ack.bind(null, "changeComposition"));
},
commitComposition: function () {
content.synthesizeComposition({ type: "compositioncommitasis" });
ack("commitComposition");
},
focus: function () {
gController.input.focus();
ack("focus");
},
blur: function () {
gController.input.blur();
ack("blur");
},
waitForSearch: function () {
waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
},
waitForSearchSettings: function () {
waitForContentSearchEvent("ManageEngines",
aData => ack("waitForSearchSettings", aData));
},
mousemove: function (itemIndex) {
let row;
if (itemIndex == -1) {
row = gController._table.firstChild;
}
else {
let allElts = [...gController._suggestionsList.children,
...gController._oneOffButtons,
content.document.getElementById("contentSearchSettingsButton")];
row = allElts[itemIndex];
}
let event = {
type: "mousemove",
clickcount: 0,
}
content.synthesizeMouseAtCenter(row, event);
ack("mousemove");
},
click: function (arg) {
let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg;
let row;
if (eltIdx == -1) {
row = gController._table.firstChild;
}
else {
let allElts = [...gController._suggestionsList.children,
...gController._oneOffButtons,
content.document.getElementById("contentSearchSettingsButton")];
row = allElts[eltIdx];
}
let event = arg.modifiers || {};
// synthesizeMouseAtCenter defaults to sending a mousedown followed by a
// mouseup if the event type is not specified.
content.synthesizeMouseAtCenter(row, event);
ack("click");
},
addInputValueToFormHistory: function () {
gController.addInputValueToFormHistory();
ack("addInputValueToFormHistory");
},
reset: function () {
// Reset both the input and suggestions by select all + delete.
gController.input.focus();
content.synthesizeKey("a", { accelKey: true });
content.synthesizeKey("VK_DELETE", {});
ack("reset");
},
};
function ack(aType, aData) {
sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
}
function waitForSuggestions(cb) {
let observer = new content.MutationObserver(() => {
if (gController.input.getAttribute("aria-expanded") == "true") {
observer.disconnect();
cb();
}
});
observer.observe(gController.input, {
attributes: true,
attributeFilter: ["aria-expanded"],
});
}
function waitForContentSearchEvent(messageType, cb) {
let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"].
getService(content.SpecialPowers.Ci.nsIMessageListenerManager);
mm.addMessageListener("ContentSearch", function listener(aMsg) {
if (aMsg.data.type != messageType) {
return;
}
mm.removeMessageListener("ContentSearch", listener);
cb(aMsg.data.data);
});
}
function currentState() {
let state = {
selectedIndex: gController.selectedIndex,
numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
suggestionAtIndex: [],
isFormHistorySuggestionAtIndex: [],
tableHidden: gController._table.hidden,
inputValue: gController.input.value,
ariaExpanded: gController.input.getAttribute("aria-expanded"),
};
if (state.numSuggestions) {
for (let i = 0; i < gController.numSuggestions; i++) {
state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
state.isFormHistorySuggestionAtIndex.push(
gController.isFormHistorySuggestionAtIndex(i));
}
}
return state;
}
})();

View File

@ -5,5 +5,5 @@
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine" rel="searchform"/>
<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine&amp;terms={searchTerms}" rel="searchform"/>
</SearchPlugin>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>browser_searchSuggestionEngine searchSuggestionEngine2.xml</ShortName>
<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine2&amp;terms={searchTerms}" rel="searchform"/>
</SearchPlugin>

View File

@ -1,158 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
(function () {
const TEST_MSG = "SearchSuggestionUIControllerTest";
const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
let input = content.document.querySelector("input");
let gController =
new content.SearchSuggestionUIController(input, input.parentNode);
gController.engineName = ENGINE_NAME;
gController.remoteTimeout = 5000;
addMessageListener(TEST_MSG, msg => {
messageHandlers[msg.data.type](msg.data.data);
});
let messageHandlers = {
key: function (arg) {
let keyName = typeof(arg) == "string" ? arg : arg.key;
content.synthesizeKey(keyName, {});
let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
wait(ack);
},
startComposition: function (arg) {
content.synthesizeComposition({ type: "compositionstart", data: "" });
ack();
},
changeComposition: function (arg) {
let data = typeof(arg) == "string" ? arg : arg.data;
content.synthesizeCompositionChange({
composition: {
string: data,
clauses: [
{ length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
]
},
caret: { start: data.length, length: 0 }
});
let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
wait(ack);
},
focus: function () {
gController.input.focus();
ack();
},
blur: function () {
gController.input.blur();
ack();
},
mousemove: function (suggestionIdx) {
// Copied from widget/tests/test_panel_mouse_coords.xul and
// browser/base/content/test/newtab/head.js
let row = gController._table.children[suggestionIdx];
let rect = row.getBoundingClientRect();
let left = content.mozInnerScreenX + rect.left;
let x = left + rect.width / 2;
let y = content.mozInnerScreenY + rect.top + rect.height / 2;
let utils = content.SpecialPowers.getDOMWindowUtils(content);
let scale = utils.screenPixelsPerCSSPixel;
let widgetToolkit = content.SpecialPowers.
Cc["@mozilla.org/xre/app-info;1"].
getService(content.SpecialPowers.Ci.nsIXULRuntime).
widgetToolkit;
let nativeMsg = widgetToolkit == "cocoa" ? 5 : // NSMouseMoved
widgetToolkit == "windows" ? 1 : // MOUSEEVENTF_MOVE
3; // GDK_MOTION_NOTIFY
row.addEventListener("mousemove", function onMove() {
row.removeEventListener("mousemove", onMove);
ack();
});
utils.sendNativeMouseEvent(x * scale, y * scale, nativeMsg, 0, null);
},
mousedown: function (suggestionIdx) {
gController.onClick = () => {
gController.onClick = null;
ack();
};
let row = gController._table.children[suggestionIdx];
content.sendMouseEvent({ type: "mousedown" }, row);
},
addInputValueToFormHistory: function () {
gController.addInputValueToFormHistory();
ack();
},
reset: function () {
// Reset both the input and suggestions by select all + delete.
gController.input.focus();
content.synthesizeKey("a", { accelKey: true });
content.synthesizeKey("VK_DELETE", {});
ack();
},
};
function ack() {
sendAsyncMessage(TEST_MSG, currentState());
}
function waitForSuggestions(cb) {
let observer = new content.MutationObserver(() => {
if (gController.input.getAttribute("aria-expanded") == "true") {
observer.disconnect();
cb();
}
});
observer.observe(gController.input, {
attributes: true,
attributeFilter: ["aria-expanded"],
});
}
function currentState() {
let state = {
selectedIndex: gController.selectedIndex,
numSuggestions: gController.numSuggestions,
suggestionAtIndex: [],
isFormHistorySuggestionAtIndex: [],
tableHidden: gController._table.hidden,
tableChildrenLength: gController._table.children.length,
tableChildren: [],
inputValue: gController.input.value,
ariaExpanded: gController.input.getAttribute("aria-expanded"),
};
for (let i = 0; i < gController.numSuggestions; i++) {
state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
state.isFormHistorySuggestionAtIndex.push(
gController.isFormHistorySuggestionAtIndex(i));
}
for (let child of gController._table.children) {
state.tableChildren.push({
textContent: child.textContent,
classes: new Set(child.className.split(/\s+/)),
});
}
return state;
}
})();

View File

@ -76,13 +76,6 @@ let runTaskifiedTests = Task.async(function* () {
info("Adding search event listener");
getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener);
let panel = searchPanel();
is(panel.state, "closed", "Search panel should be closed initially");
// The panel's animation often is not finished when the test clicks on panel
// children, which makes the test click the wrong children, so disable it.
panel.setAttribute("animate", "false");
// Add the engine without any logos and switch to it.
let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
Services.search.currentEngine = noLogoEngine;
@ -113,19 +106,6 @@ let runTaskifiedTests = Task.async(function* () {
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
// Click the logo to open the search panel.
yield Promise.all([
promisePanelShown(panel),
promiseClick(logoImg()),
]);
let manageBox = $("manage");
ok(!!manageBox, "The Manage Engines box should be present in the document");
is(panel.childNodes.length, 1, "Search panel should only contain the Manage Engines entry");
is(panel.childNodes[0], manageBox, "Search panel should contain the Manage Engines entry");
panel.hidePopup();
// Add the engine that provides search suggestions and switch to it.
let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
Services.search.currentEngine = suggestionEngine;
@ -133,7 +113,7 @@ let runTaskifiedTests = Task.async(function* () {
yield checkCurrentEngine(ENGINE_SUGGESTIONS);
// Avoid intermittent failures.
gSearch()._suggestionController.remoteTimeout = 5000;
gSearch().remoteTimeout = 5000;
// Type an X in the search input. This is only a smoke test. See
// browser_searchSuggestionUI.js for comprehensive content search suggestion
@ -309,21 +289,10 @@ let checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, lo
" basename=" + basename);
// gSearch.currentEngineName
is(gSearch().currentEngineName, engine.name,
is(gSearch().defaultEngine.name, engine.name,
"currentEngineName: " + engine.name);
});
function promisePanelShown(panel) {
let deferred = Promise.defer();
info("Waiting for popupshown");
panel.addEventListener("popupshown", function onEvent() {
panel.removeEventListener("popupshown", onEvent);
is(panel.state, "open", "Panel state");
deferred.resolve();
});
return deferred.promise;
}
function promiseClick(node) {
let deferred = Promise.defer();
let win = getContentWindow();
@ -334,16 +303,12 @@ function promiseClick(node) {
return deferred.promise;
}
function searchPanel() {
return $("panel");
}
function logoImg() {
return $("logo");
}
function gSearch() {
return getContentWindow().gSearch;
return getContentWindow().gSearch._contentSearchController;
}
/**

View File

@ -722,14 +722,23 @@ function whenPagesUpdated(aCallback = TestRunner.next) {
*/
function whenSearchInitDone() {
let deferred = Promise.defer();
if (getContentWindow().gSearch._initialStateReceived) {
let searchController = getContentWindow().gSearch._contentSearchController;
if (searchController.defaultEngine) {
return Promise.resolve();
}
let eventName = "ContentSearchService";
getContentWindow().addEventListener(eventName, function onEvent(event) {
if (event.detail.type == "State") {
getContentWindow().removeEventListener(eventName, onEvent);
deferred.resolve();
// Wait for the search controller to receive the event, then resolve.
let resolver = function() {
if (searchController.defaultEngine) {
deferred.resolve();
return;
}
executeSoon(resolver);
}
executeSoon(resolver);
}
});
return deferred.promise;

View File

@ -30,7 +30,9 @@ add_task(function* test_mozLoop_getSelectedTabMetadata() {
Assert.strictEqual(metadata.url, null, "URL should be empty for about:home");
Assert.strictEqual(metadata.favicon, null, "Favicon should be empty for about:home");
Assert.ok(metadata.title, "Title should be set for about:home");
Assert.deepEqual(metadata.previews, [], "No previews available for about:home");
// Filter out null elements in the previews - contentSearchUI adds some img
// elements with chrome:// srcs, which show up as null in metadata.previews.
Assert.deepEqual(metadata.previews.filter(e => e), [], "No previews available for about:home");
gBrowser.removeTab(tab);
});

View File

@ -673,7 +673,7 @@ let DirectoryLinksProvider = {
let pastImpressions;
// Check if the suggested tile was shown
if (action == "view") {
sites.slice(0, triggeringSiteIndex + 1).forEach(site => {
sites.slice(0, triggeringSiteIndex + 1).filter(s => s).forEach(site => {
let {targetedSite, url} = site.link;
if (targetedSite) {
this._addFrequencyCapView(url);

View File

@ -94,7 +94,8 @@ add_task(function* search() {
let data = {
engineName: engine.name,
searchString: "ContentSearchTest",
whence: "ContentSearchTest",
healthReportKey: "ContentSearchTest",
searchPurpose: "ContentSearchTest",
};
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: "Search",
@ -116,7 +117,8 @@ add_task(function* searchInBackgroundTab() {
let data = {
engineName: engine.name,
searchString: "ContentSearchTest",
whence: "ContentSearchTest",
healthReportKey: "ContentSearchTest",
searchPurpose: "ContentSearchTest",
};
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: "Search",