mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-08 20:28:42 +02:00
Differential Revision: https://phabricator.services.mozilla.com/D65509 --HG-- extra : moz-landing-system : lando
370 lines
11 KiB
JavaScript
370 lines
11 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
"use strict";
|
|
|
|
const { UrlbarTestUtils } = ChromeUtils.import(
|
|
"resource://testing-common/UrlbarTestUtils.jsm"
|
|
);
|
|
|
|
add_task(async function() {
|
|
// This keyword needs to be unique to prevent history entries from unrelated
|
|
// tests from appearing in the suggestions list.
|
|
let keyword = "VeryUniqueKeywordThatDoesNeverMatchAnyTestUrl";
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
omnibox: {
|
|
keyword: keyword,
|
|
},
|
|
},
|
|
|
|
background: function() {
|
|
browser.omnibox.onInputStarted.addListener(() => {
|
|
browser.test.sendMessage("on-input-started-fired");
|
|
});
|
|
|
|
let synchronous = true;
|
|
let suggestions = null;
|
|
let suggestCallback = null;
|
|
|
|
browser.omnibox.onInputChanged.addListener((text, suggest) => {
|
|
if (synchronous && suggestions) {
|
|
suggest(suggestions);
|
|
} else {
|
|
suggestCallback = suggest;
|
|
}
|
|
browser.test.sendMessage("on-input-changed-fired", { text });
|
|
});
|
|
|
|
browser.omnibox.onInputCancelled.addListener(() => {
|
|
browser.test.sendMessage("on-input-cancelled-fired");
|
|
});
|
|
|
|
browser.omnibox.onInputEntered.addListener((text, disposition) => {
|
|
browser.test.sendMessage("on-input-entered-fired", {
|
|
text,
|
|
disposition,
|
|
});
|
|
});
|
|
|
|
browser.test.onMessage.addListener((msg, data) => {
|
|
switch (msg) {
|
|
case "set-suggestions":
|
|
suggestions = data.suggestions;
|
|
browser.test.sendMessage("suggestions-set");
|
|
break;
|
|
case "set-default-suggestion":
|
|
browser.omnibox.setDefaultSuggestion(data.suggestion);
|
|
browser.test.sendMessage("default-suggestion-set");
|
|
break;
|
|
case "set-synchronous":
|
|
synchronous = data.synchronous;
|
|
browser.test.sendMessage("set-synchronous-set");
|
|
break;
|
|
case "test-multiple-suggest-calls":
|
|
suggestions.forEach(suggestion => suggestCallback([suggestion]));
|
|
browser.test.sendMessage("test-ready");
|
|
break;
|
|
case "test-suggestions-after-delay":
|
|
Promise.resolve().then(() => {
|
|
suggestCallback(suggestions);
|
|
browser.test.sendMessage("test-ready");
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
});
|
|
|
|
async function expectEvent(event, expected = {}) {
|
|
let actual = await extension.awaitMessage(event);
|
|
if (expected.text) {
|
|
is(
|
|
actual.text,
|
|
expected.text,
|
|
`Expected "${event}" to have fired with text: "${expected.text}".`
|
|
);
|
|
}
|
|
if (expected.disposition) {
|
|
is(
|
|
actual.disposition,
|
|
expected.disposition,
|
|
`Expected "${event}" to have fired with disposition: "${expected.disposition}".`
|
|
);
|
|
}
|
|
}
|
|
|
|
async function waitForResult(index, searchString) {
|
|
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
|
|
// Ensure the addition is complete, for proper mouse events on the entries.
|
|
await new Promise(resolve =>
|
|
window.requestIdleCallback(resolve, { timeout: 1000 })
|
|
);
|
|
return result;
|
|
}
|
|
|
|
async function promiseClickOnItem(index, details) {
|
|
// The Address Bar panel is animated and updated on a timer, thus it may not
|
|
// yet be listening to events when we try to click on it. This uses a
|
|
// polling strategy to repeat the click, if it doesn't go through.
|
|
let clicked = false;
|
|
let element = await UrlbarTestUtils.waitForAutocompleteResultAt(
|
|
window,
|
|
index
|
|
);
|
|
element.addEventListener(
|
|
"mousedown",
|
|
() => {
|
|
clicked = true;
|
|
},
|
|
{ once: true }
|
|
);
|
|
while (!clicked) {
|
|
EventUtils.synthesizeMouseAtCenter(element, details);
|
|
await new Promise(r => window.requestIdleCallback(r, { timeout: 1000 }));
|
|
}
|
|
}
|
|
|
|
let inputSessionSerial = 0;
|
|
async function startInputSession() {
|
|
gURLBar.focus();
|
|
gURLBar.value = keyword;
|
|
EventUtils.sendString(" ");
|
|
await expectEvent("on-input-started-fired");
|
|
// Always use a different input at every invokation, so that
|
|
// waitForResult can distinguish different cases.
|
|
let char = (inputSessionSerial++ % 10).toString();
|
|
EventUtils.sendString(char);
|
|
|
|
await expectEvent("on-input-changed-fired", { text: char });
|
|
return char;
|
|
}
|
|
|
|
async function testInputEvents() {
|
|
gURLBar.focus();
|
|
|
|
// Start an input session by typing in <keyword><space>.
|
|
EventUtils.sendString(keyword + " ");
|
|
await expectEvent("on-input-started-fired");
|
|
|
|
// Test canceling the input before any changed events fire.
|
|
EventUtils.synthesizeKey("KEY_Backspace");
|
|
await expectEvent("on-input-cancelled-fired");
|
|
|
|
EventUtils.sendString(" ");
|
|
await expectEvent("on-input-started-fired");
|
|
|
|
// Test submitting the input before any changed events fire.
|
|
EventUtils.synthesizeKey("KEY_Enter");
|
|
await expectEvent("on-input-entered-fired");
|
|
|
|
gURLBar.focus();
|
|
|
|
// Start an input session by typing in <keyword><space>.
|
|
EventUtils.sendString(keyword + " ");
|
|
await expectEvent("on-input-started-fired");
|
|
|
|
// We should expect input changed events now that the keyword is active.
|
|
EventUtils.sendString("b");
|
|
await expectEvent("on-input-changed-fired", { text: "b" });
|
|
|
|
EventUtils.sendString("c");
|
|
await expectEvent("on-input-changed-fired", { text: "bc" });
|
|
|
|
EventUtils.synthesizeKey("KEY_Backspace");
|
|
await expectEvent("on-input-changed-fired", { text: "b" });
|
|
|
|
// Even though the input is <keyword><space> We should not expect an
|
|
// input started event to fire since the keyword is active.
|
|
EventUtils.synthesizeKey("KEY_Backspace");
|
|
await expectEvent("on-input-changed-fired", { text: "" });
|
|
|
|
// Make the keyword inactive by hitting backspace.
|
|
EventUtils.synthesizeKey("KEY_Backspace");
|
|
await expectEvent("on-input-cancelled-fired");
|
|
|
|
// Activate the keyword by typing a space.
|
|
// Expect onInputStarted to fire.
|
|
EventUtils.sendString(" ");
|
|
await expectEvent("on-input-started-fired");
|
|
|
|
// onInputChanged should fire even if a space is entered.
|
|
EventUtils.sendString(" ");
|
|
await expectEvent("on-input-changed-fired", { text: " " });
|
|
|
|
// The active session should cancel if the input blurs.
|
|
gURLBar.blur();
|
|
await expectEvent("on-input-cancelled-fired");
|
|
}
|
|
|
|
async function testHeuristicResult(expectedText, setDefaultSuggestion) {
|
|
if (setDefaultSuggestion) {
|
|
extension.sendMessage("set-default-suggestion", {
|
|
suggestion: {
|
|
description: expectedText,
|
|
},
|
|
});
|
|
await extension.awaitMessage("default-suggestion-set");
|
|
}
|
|
|
|
let text = await startInputSession();
|
|
let result = await waitForResult(0);
|
|
|
|
Assert.equal(
|
|
result.displayed.title,
|
|
expectedText,
|
|
`Expected heuristic result to have title: "${expectedText}".`
|
|
);
|
|
|
|
Assert.equal(
|
|
result.displayed.action,
|
|
`${keyword} ${text}`,
|
|
`Expected heuristic result to have displayurl: "${keyword} ${text}".`
|
|
);
|
|
|
|
let promiseEvent = expectEvent("on-input-entered-fired", {
|
|
text,
|
|
disposition: "currentTab",
|
|
});
|
|
await promiseClickOnItem(0, {});
|
|
await promiseEvent;
|
|
}
|
|
|
|
async function testDisposition(
|
|
suggestionIndex,
|
|
expectedDisposition,
|
|
expectedText
|
|
) {
|
|
await startInputSession();
|
|
await waitForResult(suggestionIndex);
|
|
|
|
// Select the suggestion.
|
|
EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: suggestionIndex });
|
|
|
|
let promiseEvent = expectEvent("on-input-entered-fired", {
|
|
text: expectedText,
|
|
disposition: expectedDisposition,
|
|
});
|
|
|
|
if (expectedDisposition == "currentTab") {
|
|
await promiseClickOnItem(suggestionIndex, {});
|
|
} else if (expectedDisposition == "newForegroundTab") {
|
|
await promiseClickOnItem(suggestionIndex, { accelKey: true });
|
|
} else if (expectedDisposition == "newBackgroundTab") {
|
|
await promiseClickOnItem(suggestionIndex, {
|
|
shiftKey: true,
|
|
accelKey: true,
|
|
});
|
|
}
|
|
await promiseEvent;
|
|
}
|
|
|
|
async function testSuggestions(info) {
|
|
extension.sendMessage("set-synchronous", { synchronous: false });
|
|
await extension.awaitMessage("set-synchronous-set");
|
|
|
|
let text = await startInputSession();
|
|
|
|
extension.sendMessage(info.test);
|
|
await extension.awaitMessage("test-ready");
|
|
|
|
await waitForResult(info.suggestions.length - 1);
|
|
// Skip the heuristic result.
|
|
let index = 1;
|
|
for (let { content, description } of info.suggestions) {
|
|
let item = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
|
|
Assert.equal(
|
|
item.displayed.title,
|
|
description,
|
|
`Expected suggestion to have title: "${description}".`
|
|
);
|
|
Assert.equal(
|
|
item.displayed.action,
|
|
`${keyword} ${content}`,
|
|
`Expected suggestion to have displayurl: "${keyword} ${content}".`
|
|
);
|
|
index++;
|
|
}
|
|
|
|
let promiseEvent = expectEvent("on-input-entered-fired", {
|
|
text,
|
|
disposition: "currentTab",
|
|
});
|
|
await promiseClickOnItem(0, {});
|
|
await promiseEvent;
|
|
}
|
|
|
|
await extension.startup();
|
|
|
|
await SimpleTest.promiseFocus(window);
|
|
|
|
await testInputEvents();
|
|
|
|
// Test the heuristic result with default suggestions.
|
|
await testHeuristicResult(
|
|
"Generated extension",
|
|
false /* setDefaultSuggestion */
|
|
);
|
|
await testHeuristicResult("hello world", true /* setDefaultSuggestion */);
|
|
await testHeuristicResult("foo bar", true /* setDefaultSuggestion */);
|
|
|
|
let suggestions = [
|
|
{ content: "a", description: "select a" },
|
|
{ content: "b", description: "select b" },
|
|
{ content: "c", description: "select c" },
|
|
];
|
|
|
|
extension.sendMessage("set-suggestions", { suggestions });
|
|
await extension.awaitMessage("suggestions-set");
|
|
|
|
// Test each suggestion and search disposition.
|
|
await testDisposition(1, "currentTab", suggestions[0].content);
|
|
await testDisposition(2, "newForegroundTab", suggestions[1].content);
|
|
await testDisposition(3, "newBackgroundTab", suggestions[2].content);
|
|
|
|
extension.sendMessage("set-suggestions", { suggestions });
|
|
await extension.awaitMessage("suggestions-set");
|
|
|
|
// Test adding suggestions asynchronously.
|
|
await testSuggestions({
|
|
test: "test-multiple-suggest-calls",
|
|
suggestions,
|
|
});
|
|
await testSuggestions({
|
|
test: "test-suggestions-after-delay",
|
|
suggestions,
|
|
});
|
|
|
|
// When we're the first task to be added, `waitForExplicitFinish()` may not have
|
|
// been called yet. Let's just do that, otherwise the `monitorConsole` will make
|
|
// the test fail with a failing assertion.
|
|
SimpleTest.waitForExplicitFinish();
|
|
// Start monitoring the console.
|
|
let waitForConsole = new Promise(resolve => {
|
|
SimpleTest.monitorConsole(resolve, [
|
|
{
|
|
message: new RegExp(
|
|
`The keyword provided is already registered: "${keyword}"`
|
|
),
|
|
},
|
|
]);
|
|
});
|
|
|
|
// Try registering another extension with the same keyword
|
|
let extension2 = ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
omnibox: {
|
|
keyword: keyword,
|
|
},
|
|
},
|
|
});
|
|
|
|
await extension2.startup();
|
|
|
|
// Stop monitoring the console and confirm the correct errors are logged.
|
|
SimpleTest.endMonitorConsole();
|
|
await waitForConsole;
|
|
|
|
await extension2.unload();
|
|
await extension.unload();
|
|
});
|