Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion background_scripts/completion/completers.js
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,8 @@ export class MultiCompleter {
return [];
}

const queryMatchesUserSearchEngine = searchEngineCompleter?.getUserSearchEngineForQuery(query);
const queryMatchesUserSearchEngine = !request.isUserSearchEngineEscaped &&
searchEngineCompleter?.getUserSearchEngineForQuery(query);

// If the user's query matches one of their custom search engines, then use only that engine to
// provide completions for their query.
Expand Down
21 changes: 18 additions & 3 deletions pages/vomnibar_page.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class VomnibarUI {
// The user's custom search engine, if they have prefixed their query with the keyword for one
// of their search engines.
this.activeUserSearchEngine = null;
this.escapedUserSearchEngine = null;
// Used for synchronizing requests and responses to the background page.
this.lastRequestId = null;
}
Expand All @@ -72,6 +73,7 @@ class VomnibarUI {
}
setActiveUserSearchEngine(userSearchEngine) {
this.activeUserSearchEngine = userSearchEngine;
this.escapedUserSearchEngine = null;
}
setInitialSelectionValue(initialSelectionValue) {
this.initialSelectionValue = initialSelectionValue;
Expand Down Expand Up @@ -129,6 +131,7 @@ class VomnibarUI {
this.renderCompletions(this.completions);
this.previousInputValue = null;
this.activeUserSearchEngine = null;
this.escapedUserSearchEngine = null;
this.selection = this.initialSelectionValue;
this.seenTabToOpenCompletionList = false;
this.lastRequestId = null;
Expand Down Expand Up @@ -243,10 +246,17 @@ class VomnibarUI {
// Normally, with custom search engines, the keyword (e.g. the "w" of "w query terms") is
// suppressed. If the cursor is at the start of the input, then reinstate the keyword (the
// "w").
const keyword = this.activeUserSearchEngine.keyword;
this.escapedUserSearchEngine = this.activeUserSearchEngine;
const keyword = this.escapedUserSearchEngine.keyword + " ";
this.input.value = keyword + this.input.value.trimStart();
this.input.selectionStart = this.input.selectionEnd = keyword.length;
this.activeUserSearchEngine = null;
// The current completions belong to the custom search engine. Clear them synchronously so
// that pressing Enter before the replacement completions arrive uses the default search.
this.completions = [];
this.renderCompletions(this.completions);
this.previousInputValue = null;
this.selection = this.initialSelectionValue;
this.update();
} else if (this.seenTabToOpenCompletionList && (this.input.value.trim().length === 0)) {
this.seenTabToOpenCompletionList = false;
Expand Down Expand Up @@ -359,6 +369,7 @@ class VomnibarUI {
completerName: this.completerName,
queryTerms,
query,
isUserSearchEngineEscaped: this.escapedUserSearchEngine != null,
seenTabToOpenCompletionList: this.seenTabToOpenCompletionList,
});

Expand Down Expand Up @@ -402,8 +413,12 @@ class VomnibarUI {

// For custom search engines, we suppress the leading prefix (e.g. the "w" of "w query terms")
// within the vomnibar input.
if (!this.isUserSearchEngineActive() && this.getUserSearchEngineForQuery() != null) {
this.activeUserSearchEngine = this.getUserSearchEngineForQuery();
const userSearchEngine = this.getUserSearchEngineForQuery();
if (this.escapedUserSearchEngine != userSearchEngine) {
this.escapedUserSearchEngine = null;
}
if (!this.isUserSearchEngineActive() && !this.escapedUserSearchEngine && userSearchEngine) {
this.activeUserSearchEngine = userSearchEngine;
const queryTerms = this.input.value.trim().split(/\s+/);
this.input.value = queryTerms.slice(1).join(" ");
}
Expand Down
13 changes: 13 additions & 0 deletions tests/unit_tests/completion/completers_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,19 @@ context("multi completer", () => {
assert.equal("http://example.com", results[0].url);
assert.equal("http://other.com", results[1].url);
});

should("distinguish escaped and prefilled custom search engines", async () => {
userSearchEngines.set("e: https://example.com/?q=%s");
const fallback = new Suggestion({ url: "fallback", relevancy: 1, queryTerms: ["e"] });
const search = new SearchEngineCompleter();
const completer = new MultiCompleter([{ filter: () => [fallback] }, search]);
const request = { query: "e ", queryTerms: ["e"], isUserSearchEngineEscaped: true };
assert.equal([fallback], await completer.filter(request));
request.query = "e hello";
request.queryTerms.push("hello");
request.isUserSearchEngineEscaped = false;
assert.isTrue((await completer.filter(request))[0].isCustomSearch);
});
});

context("command completer", () => {
Expand Down
35 changes: 35 additions & 0 deletions tests/unit_tests/vomnibar_page_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,41 @@ context("vomnibar page", () => {
assert.equal("https://www.example.com/search?q=hello", capturedUrl);
});

should("preserve default completions after exiting a custom search engine", async () => {
userSearchEngines.set("e: https://www.example.com/search?q=%s Example");
let isUserSearchEngineEscaped;
let launchedSearchQuery;
stub(chrome.runtime, "sendMessage", async (message) => {
if (message.handler == "filterCompletions") {
isUserSearchEngineEscaped = message.isUserSearchEngineEscaped;
return new Promise(() => {});
}
if (message.handler == "launchSearchQuery") launchedSearchQuery = message.query;
});

ui.setQuery("e ");
ui.onInput();
assert.equal("", ui.input.value);
await ui.onKeyEvent(newKeyEvent({ key: "Backspace" }));
assert.equal("e ", ui.input.value);
assert.isTrue(isUserSearchEngineEscaped);
ui.input.value += "hello";
ui.onInput();
assert.equal("e hello", ui.input.value);
ui.setQuery("e");
ui.onInput();
ui.setQuery("e ");
ui.onInput();
assert.equal("", ui.input.value);
const engine = userSearchEngines.keywordToEngine.e;
ui.completions = [{ isCustomSearch: true, isPrimarySuggestion: true, searchUrl: engine.url }];
ui.selection = 0;
await ui.onKeyEvent(newKeyEvent({ key: "Backspace" }));
await ui.onKeyEvent(newKeyEvent({ type: "keypress", key: "Enter" }));
ui.onHidden();
assert.equal("e", launchedSearchQuery);
});

should("create command suggestions with correct HTML for key bindings", async () => {
await Commands.loadKeyMappings("");
const multiCompleter = new MultiCompleter([new CommandCompleter()]);
Expand Down