Bug 1180944 - Implement one-off searches from Awesomebar. r=mak,florian

MozReview-Commit-ID: A9YXB32L7MN
This commit is contained in:
Drew Willcoxon 2016-08-02 18:00:26 -07:00
parent b5ff483219
commit 55ab8f47c3
34 changed files with 1834 additions and 767 deletions

View File

@ -308,6 +308,12 @@ pref("browser.urlbar.suggest.history.onlyTyped", false);
pref("browser.urlbar.formatting.enabled", true);
pref("browser.urlbar.trimURLs", true);
#if defined(NIGHTLY_BUILD)
pref("browser.urlbar.oneOffSearches", true);
#else
pref("browser.urlbar.oneOffSearches", false);
#endif
pref("browser.altClickSave", false);
// Enable logging downloads operations to the Console.

View File

@ -152,14 +152,7 @@
noautofocus="true"
hidden="true"
flip="none"
level="parent">
#ifdef NIGHTLY_BUILD
<hbox id="urlbar-search-footer" flex="1" align="stretch" pack="end">
<button id="urlbar-search-settings" label="&changeSearchSettings.button;"
oncommand="BrowserUITelemetry.countSearchSettingsEvent('urlbar'); openPreferences('paneSearch')"/>
</hbox>
#endif
</panel>
level="parent"/>
<!-- for select dropdowns. The menupopup is what shows the list of options,
and the popuponly menulist makes things like the menuactive attributes

View File

@ -56,6 +56,7 @@ support-files =
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_urlbarHashChangeProxyState.js]
[browser_urlbarKeepStateAcrossTabSwitches.js]
[browser_urlbarOneOffs.js]
[browser_urlbarPrivateBrowsingWindowChange.js]
[browser_urlbarRevert.js]
[browser_urlbarSearchSingleWordNotification.js]

View File

@ -6,6 +6,21 @@ function repeat(limit, func) {
function is_selected(index) {
is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
// This is true because although both the listbox and the one-offs can have
// selections, the test doesn't check that.
is(gURLBar.popup.oneOffSearchButtons.selectedButton, null,
"A result is selected, so the one-offs should not have a selection");
}
function is_selected_one_off(index) {
is(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, index,
"Expected one-off button should be selected");
// This is true because although both the listbox and the one-offs can have
// selections, the test doesn't check that.
is(gURLBar.popup.richlistbox.selectedIndex, -1,
"A one-off is selected, so the listbox should not have a selection");
}
add_task(function*() {
@ -37,12 +52,27 @@ add_task(function*() {
EventUtils.synthesizeKey("VK_DOWN", {});
is_selected(1);
info("Key Down maxResults times should wrap around all the way around");
repeat(maxResults, () => EventUtils.synthesizeKey("VK_DOWN", {}));
info("Key Down maxResults-1 times should select the first one-off");
repeat(maxResults - 1, () => EventUtils.synthesizeKey("VK_DOWN", {}));
is_selected_one_off(0);
info("Key Down numButtons-1 should select the last one-off");
let numButtons =
gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
repeat(numButtons - 1, () => EventUtils.synthesizeKey("VK_DOWN", {}));
is_selected_one_off(numButtons - 1);
info("Key Down twice more should select the second result");
repeat(2, () => EventUtils.synthesizeKey("VK_DOWN", {}));
is_selected(1);
info("Key Up maxResults times should wrap around the other way");
repeat(maxResults, () => EventUtils.synthesizeKey("VK_UP", {}));
info("Key Down maxResults + numButtons times should wrap around");
repeat(maxResults + numButtons,
() => EventUtils.synthesizeKey("VK_DOWN", {}));
is_selected(1);
info("Key Up maxResults + numButtons times should wrap around the other way");
repeat(maxResults + numButtons, () => EventUtils.synthesizeKey("VK_UP", {}));
is_selected(1);
info("Page Up will go up the list, but not wrap");

View File

@ -31,15 +31,18 @@ add_task(function*() {
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
yield promiseSearchComplete();
let editedValue = gURLBar.value;
let editedValue = gURLBar.textValue;
is(list.selectedIndex, initialIndex, "The initial index is selected again.");
isnot(editedValue, nextValue, "The URL has changed.");
let docLoad = waitForDocLoadAndStopIt("http://" + editedValue);
info("Press return to load edited URL.");
EventUtils.synthesizeKey("VK_RETURN", {});
yield Promise.all([
promisePopupHidden(gURLBar.popup),
waitForDocLoadAndStopIt("http://" + editedValue)]);
docLoad,
]);
gBrowser.removeTab(gBrowser.selectedTab);
});

View File

@ -43,7 +43,7 @@ add_task(function*() {
EventUtils.synthesizeKey("b", {});
yield promiseSearchComplete();
is(gURLBar.value, "keyword ab", "urlbar should have expected input");
is(gURLBar.textValue, "keyword ab", "urlbar should have expected input");
let result = gURLBar.popup.richlistbox.firstChild;
isnot(result, null, "Should have first item");

View File

@ -44,8 +44,8 @@ function continue_test() {
EventUtils.synthesizeKey(aTyped.substr(-1), {});
waitForSearchComplete(function () {
info(`Got value: ${gURLBar.value}`);
is(gURLBar.value, aExpected, "Autofilled value is as expected");
info(`Got value: ${gURLBar.textValue}`);
is(gURLBar.textValue, aExpected, "Autofilled value is as expected");
aCallback();
});
}

View File

@ -0,0 +1,233 @@
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
let gMaxResults;
add_task(function* init() {
Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
// Add a search suggestion engine and move it to the front so that it appears
// as the first one-off.
let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
Services.search.moveEngine(engine, 0);
registerCleanupFunction(function* () {
yield hidePopup();
yield PlacesTestUtils.clearHistory();
});
yield PlacesTestUtils.clearHistory();
let visits = [];
for (let i = 0; i < gMaxResults; i++) {
visits.push({
uri: makeURI("http://example.com/browser_urlbarOneOffs.js/?" + i),
// TYPED so that the visit shows up when the urlbar's drop-down arrow is
// pressed.
transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
});
}
yield PlacesTestUtils.addVisits(visits);
});
// Keys up and down through the history panel, i.e., the panel that's shown when
// there's no text in the textbox.
add_task(function* history() {
gURLBar.focus();
EventUtils.synthesizeKey("VK_DOWN", {})
yield promisePopupShown(gURLBar.popup);
assertState(-1, -1, "");
// Key down through each result.
for (let i = 0; i < gMaxResults; i++) {
EventUtils.synthesizeKey("VK_DOWN", {})
assertState(i, -1,
"example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
}
// Key down through each one-off.
let numButtons =
gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
for (let i = 0; i < numButtons; i++) {
EventUtils.synthesizeKey("VK_DOWN", {})
assertState(-1, i, "");
}
// Key down once more. Nothing should be selected.
EventUtils.synthesizeKey("VK_DOWN", {})
assertState(-1, -1, "");
// Once more. The first result should be selected.
EventUtils.synthesizeKey("VK_DOWN", {})
assertState(0, -1,
"example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - 1));
// Now key up. Nothing should be selected again.
EventUtils.synthesizeKey("VK_UP", {})
assertState(-1, -1, "");
// Key up through each one-off.
for (let i = numButtons - 1; i >= 0; i--) {
EventUtils.synthesizeKey("VK_UP", {})
assertState(-1, i, "");
}
// Key up through each result.
for (let i = gMaxResults - 1; i >= 0; i--) {
EventUtils.synthesizeKey("VK_UP", {})
assertState(i, -1,
"example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
}
// Key up once more. Nothing should be selected.
EventUtils.synthesizeKey("VK_UP", {})
assertState(-1, -1, "");
yield hidePopup();
});
// Keys up and down through the non-history panel, i.e., the panel that's shown
// when you type something in the textbox.
add_task(function* typedValue() {
// Use a typed value that returns the visits added above but that doesn't
// trigger autofill since that would complicate the test.
let typedValue = "browser_urlbarOneOffs";
yield promiseAutocompleteResultPopup(typedValue, window, true);
assertState(0, -1, typedValue);
// Key down through each result. The first result is already selected, which
// is why gMaxResults - 1 is the correct number of times to do this.
for (let i = 0; i < gMaxResults - 1; i++) {
EventUtils.synthesizeKey("VK_DOWN", {})
// i starts at zero so that the textValue passed to assertState is correct.
// But that means that i + 1 is the expected selected index, since initially
// (when this loop starts) the first result is selected.
assertState(i + 1, -1,
"example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
}
// Key down through each one-off.
let numButtons =
gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
for (let i = 0; i < numButtons; i++) {
EventUtils.synthesizeKey("VK_DOWN", {})
assertState(-1, i, typedValue);
}
// Key down once more. The selection should wrap around to the first result.
EventUtils.synthesizeKey("VK_DOWN", {})
assertState(0, -1, typedValue);
// Now key up. The selection should wrap back around to the one-offs. Key
// up through all the one-offs.
for (let i = numButtons - 1; i >= 0; i--) {
EventUtils.synthesizeKey("VK_UP", {})
assertState(-1, i, typedValue);
}
// Key up through each non-heuristic result.
for (let i = gMaxResults - 2; i >= 0; i--) {
EventUtils.synthesizeKey("VK_UP", {})
assertState(i + 1, -1,
"example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
}
// Key up once more. The heuristic result should be selected.
EventUtils.synthesizeKey("VK_UP", {})
assertState(0, -1, typedValue);
yield hidePopup();
});
// Checks that "Search with Current Search Engine" items are updated to "Search
// with One-Off Engine" when a one-off is selected.
add_task(function* searchWith() {
let typedValue = "foo";
yield promiseAutocompleteResultPopup(typedValue);
assertState(0, -1, typedValue);
let item = gURLBar.popup.richlistbox.firstChild;
Assert.equal(item._actionText.textContent,
"Search with " + Services.search.currentEngine.name,
"Sanity check: first result's action text");
// Tab to the first one-off. Now the first result and the first one-off
// should both be selected.
EventUtils.synthesizeKey("VK_TAB", {})
assertState(0, 0, typedValue);
let engineName = gURLBar.popup.oneOffSearchButtons.selectedButton.engine.name;
Assert.notEqual(engineName, Services.search.currentEngine.name,
"Sanity check: First one-off engine should not be " +
"the current engine");
Assert.equal(item._actionText.textContent,
"Search with " + engineName,
"First result's action text should be updated");
yield hidePopup();
});
// Clicks a one-off.
add_task(function* oneOffClick() {
gBrowser.selectedTab = gBrowser.addTab();
let typedValue = "foo";
yield promiseAutocompleteResultPopup(typedValue);
assertState(0, -1, typedValue);
let oneOffs = gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true);
let resultsPromise = promiseSearchResultsLoaded();
EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
yield resultsPromise;
gBrowser.removeTab(gBrowser.selectedTab);
});
// Presses the Return key when a one-off is selected.
add_task(function* oneOffReturn() {
gBrowser.selectedTab = gBrowser.addTab();
let typedValue = "foo";
yield promiseAutocompleteResultPopup(typedValue, window, true);
assertState(0, -1, typedValue);
// Tab to select the first one-off.
EventUtils.synthesizeKey("VK_TAB", {})
assertState(0, 0, typedValue);
let resultsPromise = promiseSearchResultsLoaded();
EventUtils.synthesizeKey("VK_RETURN", {})
yield resultsPromise;
gBrowser.removeTab(gBrowser.selectedTab);
});
function assertState(result, oneOff, textValue = undefined) {
Assert.equal(gURLBar.popup.selectedIndex, result,
"Expected result should be selected");
Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, oneOff,
"Expected one-off should be selected");
if (textValue !== undefined) {
Assert.equal(gURLBar.textValue, textValue, "Expected textValue");
}
}
function* hidePopup() {
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield promisePopupHidden(gURLBar.popup);
}
function promiseSearchResultsLoaded() {
let tab = gBrowser.selectedTab;
return promiseTabLoadEvent(tab).then(() => {
Assert.equal(tab.linkedBrowser.currentURI.spec,
"http://mochi.test:8888/",
'Expected "search results" page loaded');
});
}

View File

@ -25,7 +25,10 @@ add_task(function* clickSuggestion() {
gBrowser.selectedTab = gBrowser.addTab();
gURLBar.focus();
yield promiseAutocompleteResultPopup("foo");
let [idx, suggestion] = yield promiseFirstSuggestion();
let [idx, suggestion, engineName] = yield promiseFirstSuggestion();
Assert.equal(engineName,
"browser_searchSuggestionEngine%20searchSuggestionEngine.xml",
"Expected suggestion engine");
let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
let loadPromise = promiseTabLoaded(gBrowser.selectedTab);
item.click();
@ -47,7 +50,7 @@ function getFirstSuggestion() {
let [, type, paramStr] = mozActionMatch;
let params = JSON.parse(paramStr);
if (type == "searchengine" && "searchSuggestion" in params) {
return [i, params.searchSuggestion];
return [i, params.searchSuggestion, params.engineName];
}
}
}
@ -56,10 +59,10 @@ function getFirstSuggestion() {
function promiseFirstSuggestion() {
return new Promise(resolve => {
let pair;
let tuple;
waitForCondition(() => {
pair = getFirstSuggestion();
return pair[0] >= 0;
}, () => resolve(pair));
tuple = getFirstSuggestion();
return tuple[0] >= 0;
}, () => resolve(tuple));
});
}

View File

@ -23,6 +23,12 @@ add_task(function* prepare() {
gURLBar.blur();
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
});
// Move the mouse away from the urlbar one-offs so that a one-off engine is
// not inadvertently selected.
yield new Promise(resolve => {
EventUtils.synthesizeNativeMouseMove(window.document.documentElement, 0, 0,
resolve);
});
});
add_task(function* heuristicResult() {

View File

@ -20,12 +20,11 @@ add_task(function* () {
gBrowser.removeCurrentTab();
});
function typeAndSubmitAndStop(url) {
gBrowser.userTypedValue = url;
URLBarSetURI();
function* typeAndSubmitAndStop(url) {
yield promiseAutocompleteResultPopup(url, window, true);
is(gURLBar.textValue, gURLBar.trimValue(url), "location bar reflects loading page");
let promise = waitForDocLoadAndStopIt(url, gBrowser.selectedBrowser, false);
gURLBar.handleCommand();
return promise;
yield promise;
}

View File

@ -7,13 +7,13 @@ function* test_autocomplete(data) {
info(desc);
yield promiseAutocompleteResultPopup(typed);
is(gURLBar.value, autofilled, "autofilled value is as expected");
is(gURLBar.textValue, autofilled, "autofilled value is as expected");
if (onAutoFill)
onAutoFill()
keys.forEach(key => EventUtils.synthesizeKey(key, {}));
is(gURLBar.value, modified, "backspaced value is as expected");
is(gURLBar.textValue, modified, "backspaced value is as expected");
yield promiseSearchComplete();

View File

@ -327,10 +327,18 @@ function promiseSearchComplete(win = window) {
});
}
function promiseAutocompleteResultPopup(inputText, win = window) {
function promiseAutocompleteResultPopup(inputText,
win = window,
fireInputEvent = false) {
waitForFocus(() => {
win.gURLBar.focus();
win.gURLBar.value = inputText;
if (fireInputEvent) {
// This is necessary to get the urlbar to set gBrowser.userTypedValue.
let event = document.createEvent("Events");
event.initEvent("input", true, true);
win.gURLBar.dispatchEvent(event);
}
win.gURLBar.controller.startSearch(inputText);
}, win);

View File

@ -108,6 +108,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
"gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
}
this._enableOrDisableOneOffSearches();
]]></constructor>
<destructor><![CDATA[
@ -197,7 +199,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.popup.selectedIndex = -1;
break;
}
if (this.popup.popupOpen &&
!this.popup.disableKeyNavigation &&
this.popup.handleKeyPress(aEvent)) {
return true;
}
return this.handleKeyPress(aEvent);
]]></body>
</method>
@ -331,135 +337,224 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
]]></body>
</method>
<!--
This is ultimately called by the autocomplete controller as the result
of handleEnter when the Return key is pressed in the textbox. Since
onPopupClick also calls handleEnter, this is also called as a result in
that case.
@param event
The event that triggered the command.
@param openUILinkWhere
Optional. The "where" to pass to openUILinkIn. This method
computes the appropriate "where" given the event, but you can
use this to override it.
@param openUILinkParams
Optional. The parameters to pass to openUILinkIn. As with
"where", this method computes the appropriate parameters, but
any parameters you supply here will override those.
-->
<method name="handleCommand">
<parameter name="aTriggeringEvent"/>
<parameter name="event"/>
<parameter name="openUILinkWhere"/>
<parameter name="openUILinkParams"/>
<body><![CDATA[
if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
return; // Do nothing for right clicks
var url = this.value;
var mayInheritPrincipal = false;
var postData = null;
let action = this._parseActionUrl(this._value);
let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
let matchLastLocationChange = true;
if (action) {
if (action.type == "switchtab") {
url = action.params.url;
if (this.hasAttribute("actiontype")) {
this.handleRevert();
let prevTab = gBrowser.selectedTab;
if (switchToTabHavingURI(url) && isTabEmpty(prevTab)) {
gBrowser.removeTab(prevTab);
}
return;
}
} else if (action.type == "remotetab") {
url = action.params.url;
} else if (action.type == "keyword") {
url = action.params.url;
} else if (action.type == "searchengine") {
[url, postData] = this._parseAndRecordSearchEngineAction(action);
} else if (action.type == "visiturl") {
url = action.params.url;
}
continueOperation.call(this);
}
else {
this._canonizeURL(aTriggeringEvent, response => {
[url, postData, mayInheritPrincipal] = response;
if (url) {
matchLastLocationChange = (lastLocationChange ==
gBrowser.selectedBrowser.lastLocationChange);
continueOperation.call(this);
}
});
let isMouseEvent = event instanceof MouseEvent;
if (isMouseEvent && event.button == 2) {
// Do nothing for right clicks.
return;
}
function continueOperation()
{
this.value = url;
gBrowser.userTypedValue = url;
if (gInitialPages.includes(url)) {
gBrowser.selectedBrowser.initialPageLoadedFromURLBar = url;
}
try {
addToUrlbarHistory(url);
} catch (ex) {
// Things may go wrong when adding url to session history,
// but don't let that interfere with the loading of the url.
Cu.reportError(ex);
}
// Do the command of the selected one-off if it's not an engine.
let selectedOneOff =
this.popup.oneOffSearchButtons.visuallySelectedButton;
if (selectedOneOff && !selectedOneOff.engine) {
selectedOneOff.doCommand();
return;
}
let loadCurrent = () => {
try {
openUILinkIn(url, "current", {
allowThirdPartyFixup: true,
indicateErrorPageLoad: true,
disallowInheritPrincipal: !mayInheritPrincipal,
allowPinnedTabHostChange: true,
postData: postData,
allowPopups: url.startsWith("javascript:"),
});
} catch (ex) {
// This load can throw an exception in certain cases, which means
// we'll want to replace the URL with the loaded URL:
if (ex.result != Cr.NS_ERROR_LOAD_SHOWED_ERRORPAGE) {
this.handleRevert();
}
}
// Ensure the start of the URL is visible for UX reasons:
this.selectionStart = this.selectionEnd = 0;
};
// Focus the content area before triggering loads, since if the load
// occurs in a new tab, we want focus to be restored to the content
// area when the current tab is re-selected.
gBrowser.selectedBrowser.focus();
let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
// If the current tab is empty, ignore Alt+Enter (just reuse this tab)
let altEnter = !isMouseEvent && aTriggeringEvent &&
aTriggeringEvent.altKey && !isTabEmpty(gBrowser.selectedTab);
if (isMouseEvent || altEnter) {
// Use the standard UI link behaviors for clicks or Alt+Enter
let where = "tab";
if (isMouseEvent)
where = whereToOpenLink(aTriggeringEvent, false, false);
if (where == "current") {
if (matchLastLocationChange) {
loadCurrent();
}
} else {
this.handleRevert();
let params = { allowThirdPartyFixup: true,
postData: postData,
initiatingDoc: document };
openUILinkIn(url, where, params);
}
let where = openUILinkWhere;
if (!where) {
if (isMouseEvent) {
where = whereToOpenLink(event, false, false);
} else {
if (matchLastLocationChange) {
loadCurrent();
}
// If the current tab is empty, ignore Alt+Enter (reuse this tab)
let altEnter = !isMouseEvent &&
event &&
event.altKey &&
!isTabEmpty(gBrowser.selectedTab);
where = altEnter ? "tab" : "current";
}
}
let url = this.value;
let mayInheritPrincipal = false;
let postData = null;
let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
let matchLastLocationChange = true;
let action = this._parseActionUrl(url);
if (action) {
switch (action.type) {
case "visiturl":
case "keyword":
case "remotetab":
url = action.params.url;
break;
case "switchtab":
url = action.params.url;
if (this.hasAttribute("actiontype")) {
this.handleRevert();
let prevTab = gBrowser.selectedTab;
if (switchToTabHavingURI(url) && isTabEmpty(prevTab)) {
gBrowser.removeTab(prevTab);
}
return;
}
break;
case "searchengine":
if (selectedOneOff && selectedOneOff.engine) {
// Replace the engine with the selected one-off engine.
action.params.engineName = engine.name;
}
[url, postData] = this._recordSearchEngineLoad(
action.params.engineName,
action.params.searchSuggestion || action.params.searchQuery,
event,
where,
openUILinkParams
);
break;
}
this._loadURL(url, postData, where, openUILinkParams,
matchLastLocationChange, mayInheritPrincipal);
return;
}
// If there's a selected one-off button and the input value is a
// search query (or "keyword" in URI-fixup terminology), then load a
// search using the one-off's engine.
if (selectedOneOff && selectedOneOff.engine) {
// `url` (which is this.value) may be an autofilled string. Search
// only with the portion that the user typed, if any, by preferring
// the autocomplete controller's searchString.
let value = this._searchStringOnHandleEnter ||
this.mController.searchString ||
url;
let fixup;
try {
fixup = Services.uriFixup.getFixupURIInfo(
value,
Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
);
} catch (ex) {}
if (fixup && fixup.keywordProviderName) {
[url, postData] =
this._recordSearchEngineLoad(selectedOneOff.engine, value,
event, where, openUILinkParams);
this._loadURL(url, postData, where, openUILinkParams,
matchLastLocationChange, mayInheritPrincipal);
return;
}
}
this._canonizeURL(event, response => {
[url, postData, mayInheritPrincipal] = response;
if (url) {
matchLastLocationChange =
lastLocationChange ==
gBrowser.selectedBrowser.lastLocationChange;
this._loadURL(url, postData, where, openUILinkParams,
matchLastLocationChange, mayInheritPrincipal);
}
});
]]></body>
</method>
<method name="_loadURL">
<parameter name="url"/>
<parameter name="postData"/>
<parameter name="openUILinkWhere"/>
<parameter name="openUILinkParams"/>
<parameter name="matchLastLocationChange"/>
<parameter name="mayInheritPrincipal"/>
<body><![CDATA[
this.value = url;
gBrowser.userTypedValue = url;
if (gInitialPages.includes(url)) {
gBrowser.selectedBrowser.initialPageLoadedFromURLBar = url;
}
try {
addToUrlbarHistory(url);
} catch (ex) {
// Things may go wrong when adding url to session history,
// but don't let that interfere with the loading of the url.
Cu.reportError(ex);
}
let params = {
postData: postData,
allowThirdPartyFixup: true,
};
if (openUILinkWhere == "current") {
params.indicateErrorPageLoad = true;
params.allowPinnedTabHostChange = true;
params.disallowInheritPrincipal = !mayInheritPrincipal;
params.allowPopups = url.startsWith("javascript:");
} else {
params.initiatingDoc = document;
}
if (openUILinkParams) {
for (let key in openUILinkParams) {
params[key] = openUILinkParams[key];
}
}
// Focus the content area before triggering loads, since if the load
// occurs in a new tab, we want focus to be restored to the content
// area when the current tab is re-selected.
gBrowser.selectedBrowser.focus();
if (openUILinkWhere == "current" && !matchLastLocationChange) {
return;
}
if (openUILinkWhere != "current") {
this.handleRevert();
}
try {
openUILinkIn(url, openUILinkWhere, params);
} catch (ex) {
// This load can throw an exception in certain cases, which means
// we'll want to replace the URL with the loaded URL:
if (ex.result != Cr.NS_ERROR_LOAD_SHOWED_ERRORPAGE) {
this.handleRevert();
}
}
if (openUILinkWhere == "current") {
// Ensure the start of the URL is visible for usability reasons.
this.selectionStart = this.selectionEnd = 0;
}
]]></body>
</method>
<method name="_parseAndRecordSearchEngineAction">
<parameter name="action"/>
<method name="_recordSearchEngineLoad">
<parameter name="engineOrEngineName"/>
<parameter name="query"/>
<parameter name="event"/>
<parameter name="openUILinkWhere"/>
<parameter name="openUILinkParams"/>
<body><![CDATA[
let engine =
Services.search.getEngineByName(action.params.engineName);
typeof(engineOrEngineName) == "string" ?
Services.search.getEngineByName(engineOrEngineName) :
engineOrEngineName;
BrowserSearch.recordSearchInTelemetry(engine, "urlbar");
let query = action.params.searchSuggestion ||
action.params.searchQuery;
this.popup.oneOffSearchButtons
.maybeRecordTelemetry(event, openUILinkWhere, openUILinkParams);
let submission = engine.getSubmission(query, null, "keyword");
return [submission.uri.spec, submission.postData];
]]></body>
@ -728,11 +823,21 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
case "trimURLs":
this._mayTrimURLs = this._prefs.getBoolPref(aData);
break;
case "oneOffSearches":
this._enableOrDisableOneOffSearches();
break;
}
}
]]></body>
</method>
<method name="_enableOrDisableOneOffSearches">
<body><![CDATA[
let enable = this._prefs.getBoolPref("oneOffSearches");
this.popup.enableOneOffSearches(enable);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
@ -903,6 +1008,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</method>
<method name="handleEnter">
<parameter name="event"/>
<body><![CDATA[
// We need to ensure we're using a selected autocomplete result.
// A result should automatically be selected by default,
@ -919,7 +1025,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// ensure that it corresponds to the current input.
if (this.popup.selectedIndex != 0 || this.gotResultForCurrentQuery) {
return this.mController.handleEnter(false);
// Store the current search string so it can be used in
// handleCommand, which will be called as a result of
// mController.handleEnter(). handleEnter will reset it.
this._searchStringOnHandleEnter = this.mController.searchString;
let rv = this.mController.handleEnter(false, event);
delete this._searchStringOnHandleEnter;
return rv;
}
this.handleEnterWhenGotResult = true;
@ -1100,7 +1212,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Check for unmodified left-click, and use default behavior
if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
!aEvent.altKey && !aEvent.metaKey) {
controller.handleEnter(true);
controller.handleEnter(true, aEvent);
return;
}
@ -1137,6 +1249,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
<resources>
<stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
<stylesheet src="chrome://browser/skin/searchbar.css"/>
</resources>
<content ignorekeys="true" level="top" consumeoutsideclicks="never"
aria-owns="richlistbox">
<xul:hbox anonid="search-suggestions-notification"
@ -1171,6 +1288,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
flex="1"/>
<xul:hbox anonid="footer">
<children/>
<xul:vbox anonid="one-off-search-buttons"
class="search-one-offs"
compact="true"
flex="1"/>
</xul:hbox>
</content>
@ -1193,6 +1314,31 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
document.getAnonymousElementByAttribute(this, "anonid", "footer");
</field>
<field name="oneOffSearchButtons" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid",
"one-off-search-buttons");
</field>
<field name="_oneOffSearchesEnabled">false</field>
<method name="enableOneOffSearches">
<parameter name="enable"/>
<body><![CDATA[
this._oneOffSearchesEnabled = enable;
if (enable) {
this.oneOffSearchButtons.style.display = "-moz-box";
this.oneOffSearchButtons.popup = this;
this.oneOffSearchButtons.textbox = this.input;
this.oneOffSearchButtons.telemetryOrigin = "urlbar";
} else {
this.oneOffSearchButtons.style.display = "none";
this.oneOffSearchButtons.popup = null;
this.oneOffSearchButtons.textbox = null;
this.oneOffSearchButtons.telemetryOrigin = null;
}
]]></body>
</method>
<method name="openSearchSuggestionsNotificationLearnMoreURL">
<body><![CDATA[
let url = Services.urlFormatter.formatURL(
@ -1491,62 +1637,68 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</body>
</method>
<method name="onPopupClick">
<parameter name="aEvent"/>
<body>
<![CDATA[
// Ignore right-clicks
if (aEvent.button == 2)
return;
var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
// Check for unmodified left-click, and use default behavior
if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
!aEvent.altKey && !aEvent.metaKey) {
controller.handleEnter(true);
return;
}
// Check for middle-click or modified clicks on the URL bar
if (gURLBar && this.mInput == gURLBar) {
var url = controller.getValueAt(this.selectedIndex);
var options = {};
// close the autocomplete popup and revert the entered address
this.closePopup();
controller.handleEscape();
// Check if this is meant to be an action
let action = this.mInput._parseActionUrl(url);
if (action) {
// TODO (bug 1054816): Centralise the implementation of actions
// into a JS module.
switch (action.type) {
case "switchtab": // Fall through.
case "keyword": // Fall through.
case "visiturl": {
url = action.params.url;
break;
}
case "searchengine": {
[url, options.postData] =
this.input._parseAndRecordSearchEngineAction(action);
break;
}
default: {
return;
}
<method name="_visuallySelectedOneOffChanged">
<body><![CDATA[
// Update all searchengine result items to use the newly selected
// engine.
for (let item of this.richlistbox.childNodes) {
if (item.collapsed) {
break;
}
let url = item.getAttribute("url");
if (url) {
let action = item._parseActionUrl(url);
if (action && action.type == "searchengine") {
item._adjustAcItem();
}
}
// respect the usual clicking subtleties
openUILink(url, aEvent, options);
}
]]>
</body>
]]></body>
</method>
<!-- This handles keypress changes to the selection among the one-off
search buttons and between the one-offs and the listbox. It returns
true if the keypress was consumed and false if not. -->
<method name="handleKeyPress">
<parameter name="aEvent"/>
<body><![CDATA[
this.oneOffSearchButtons.handleKeyPress(aEvent, this._matchCount,
!this._isFirstResultHeuristic,
gBrowser.userTypedValue);
return aEvent.defaultPrevented;
]]></body>
</method>
<!-- This is called when a one-off is clicked and when "search in new tab"
is selected from a one-off context menu. -->
<method name="handleOneOffSearch">
<parameter name="event"/>
<parameter name="engine"/>
<parameter name="where"/>
<parameter name="params"/>
<body><![CDATA[
this.input.handleCommand(event, where, params);
]]></body>
</method>
<!-- Result listitems call this to determine which search engine they
should show in their labels and include in their url attributes. -->
<property name="overrideSearchEngineName" readonly="true">
<getter><![CDATA[
// When building the popup, autocomplete reuses an item at index i if
// that item's url attribute matches the controller's value at index
// i, but only if overrideSearchEngineName matches the engine in the
// url attribute. To absolutely avoid reusing items that shouldn't be
// reused, always return a non-null name here by falling back to the
// current engine.
let engine =
(this.oneOffSearchButtons.visuallySelectedButton &&
this.oneOffSearchButtons.visuallySelectedButton.engine) ||
Services.search.currentEngine;
return engine ? engine.name : null;
]]></getter>
</property>
<method name="createResultLabel">
<parameter name="item"/>
<parameter name="proposedLabel"/>
@ -1616,6 +1768,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</implementation>
<handlers>
<handler event="OneOffsVisuallySelectedButtonChanged"><![CDATA[
this._visuallySelectedOneOffChanged();
]]></handler>
<handler event="mousedown"><![CDATA[
// Required to make the xul:label.text-link elements in the search
// suggestions notification work correctly when clicked on Linux.

File diff suppressed because it is too large Load Diff

View File

@ -7,3 +7,12 @@
.searchbar-textbox {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
}
.search-one-offs {
-moz-binding: url("chrome://browser/content/search/search.xml#search-one-offs");
}
.search-setting-button[compact=true],
.search-setting-button-compact:not([compact=true]) {
display: none;
}

View File

@ -14,21 +14,6 @@ const diacritic_engine = "Foo \u2661";
var Preferences =
Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
// Get an array of the one-off buttons.
function getOneOffs() {
let oneOffs = [];
let oneOff =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-panel-one-offs");
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
if (oneOff.classList.contains("dummy"))
break;
oneOffs.push(oneOff);
}
return oneOffs;
}
add_task(function* init() {
let currentEngine = Services.search.currentEngine;
yield promiseNewEngine("testEngine_diacritics.xml", {setAsCurrent: false});

View File

@ -10,11 +10,15 @@ const textbox = searchbar._textbox;
const searchPopup = document.getElementById("PopupSearchAutoComplete");
const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
"searchbar-search-button");
const searchSettings =
const oneOffsContainer =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-one-off-buttons");
const searchSettings =
document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
"search-settings");
var header =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
"search-panel-one-offs-header");
function getHeaderText() {
let headerChild = header.selectedPanel;
@ -28,21 +32,6 @@ function getHeaderText() {
return headerStrings.join("");
}
// Get an array of the one-off buttons.
function getOneOffs() {
let oneOffs = [];
let oneOff =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-panel-one-offs");
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
if (oneOff.classList.contains("dummy"))
break;
oneOffs.push(oneOff);
}
return oneOffs;
}
const msg = isMac ? 5 : 1;
const utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);

View File

@ -3,29 +3,18 @@
const searchbar = document.getElementById("searchbar");
const textbox = searchbar._textbox;
const searchPopup = document.getElementById("PopupSearchAutoComplete");
const oneOffsContainer =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-one-off-buttons");
const kValues = ["foo1", "foo2", "foo3"];
const kUserValue = "foo";
// Get an array of the one-off buttons.
function getOneOffs() {
let oneOffs = [];
let oneOff = document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-panel-one-offs");
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
if (oneOff.classList.contains("dummy"))
break;
oneOffs.push(oneOff);
}
return oneOffs;
}
function getOpenSearchItems() {
let os = [];
let addEngineList =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
"add-engines");
for (let item = addEngineList.firstChild; item; item = item.nextSibling)
os.push(item);

View File

@ -3,30 +3,19 @@
const searchbar = document.getElementById("searchbar");
const textbox = searchbar._textbox;
const searchPopup = document.getElementById("PopupSearchAutoComplete");
const oneOffsContainer =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-one-off-buttons");
const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
"searchbar-search-button");
const kValues = ["foo1", "foo2", "foo3"];
// Get an array of the one-off buttons.
function getOneOffs() {
let oneOffs = [];
let oneOff = document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-panel-one-offs");
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
if (oneOff.classList.contains("dummy"))
break;
oneOffs.push(oneOff);
}
return oneOffs;
}
function getOpenSearchItems() {
let os = [];
let addEngineList =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
"add-engines");
for (let item = addEngineList.firstChild; item; item = item.nextSibling)
os.push(item);

View File

@ -136,3 +136,24 @@ function promiseTabLoadEvent(tab, url)
// timeout promise as well, causing the all promise to resolve.
return Promise.all([deferred.promise, loaded]);
}
// Get an array of the one-off buttons.
function getOneOffs() {
let oneOffs = [];
let searchPopup = document.getElementById("PopupSearchAutoComplete");
let oneOffsContainer =
document.getAnonymousElementByAttribute(searchPopup, "anonid",
"search-one-off-buttons");
let oneOff =
document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
"search-panel-one-offs");
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
if (oneOff.nodeType == Node.ELEMENT_NODE) {
if (oneOff.classList.contains("dummy") ||
oneOff.classList.contains("search-setting-button-compact"))
break;
oneOffs.push(oneOff);
}
}
return oneOffs;
}

View File

@ -158,7 +158,7 @@ menuitem[cmd="cmd_clearhistory"][disabled] {
.search-panel-one-offs {
margin: 0 -1px !important;
border-top: 1px solid rgba(0, 0, 0, 0.2);
border-top: 1px solid #ccc;
}
.searchbar-engine-one-off-item {
@ -184,7 +184,15 @@ menuitem[cmd="cmd_clearhistory"][disabled] {
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
}
.searchbar-engine-one-off-item.last-of-row {
.search-setting-button-compact {
border-bottom: none !important;
}
.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
.search-setting-button-compact {
background-image: none;
}
@ -298,3 +306,11 @@ menuitem[cmd="cmd_clearhistory"][disabled] {
color: HighlightText;
border-top-color: #bdbebe;
}
.search-setting-button-compact {
list-style-image: url("chrome://browser/skin/gear.svg#gear");
}
.search-setting-button-compact[selected] {
list-style-image: url("chrome://browser/skin/gear.svg#gear-inverted");
}

View File

@ -173,7 +173,15 @@
border-bottom: 1px solid #ccc;
}
.searchbar-engine-one-off-item.last-of-row {
.search-setting-button-compact {
border-bottom: none !important;
}
.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
.search-setting-button-compact {
background-image: none;
}
@ -277,3 +285,11 @@
background-color: #d3d3d3;
border-top-color: #bdbebe;
}
.search-setting-button-compact {
list-style-image: url("chrome://browser/skin/gear.svg#gear");
}
.search-setting-button-compact[selected] {
list-style-image: url("chrome://browser/skin/gear.svg#gear-inverted");
}

View File

@ -98,6 +98,7 @@
skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg)
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
skin/classic/browser/gear.svg (../shared/search/gear.svg)
skin/classic/browser/social/chat-icons.svg (../shared/social/chat-icons.svg)
skin/classic/browser/social/gear_default.png (../shared/social/gear_default.png)
skin/classic/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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="16" height="16" viewBox="0 0 32 32">
<style>
use:not(:target) {
display: none;
}
use {
fill: GrayText;
}
use[id$="-inverted"] {
fill: highlighttext;
}
</style>
<defs>
<path id="glyphShape-gear" d="M28,16c0-1.7,0.9-3.1,2-3.3c-0.4-1.5-0.9-2.9-1.7-4.2c-0.9,0.7-2.6,0.3-3.8-0.9c-1.2-1.2-1.6-2.8-0.9-3.8 c-1.3-0.8-2.7-1.4-4.2-1.7c-0.2,1.1-1.6,2-3.3,2S13,3.1,12.8,2c-1.5,0.4-2.9,0.9-4.2,1.7c0.7,0.9,0.3,2.6-0.9,3.8 c-1.4,1.1-3,1.5-4,0.9C2.9,9.7,2.4,11.2,2,12.7c1.1,0.2,2,1.6,2,3.3s-0.9,3.1-2,3.3c0.4,1.5,0.9,2.9,1.7,4.2 c0.9-0.7,2.6-0.3,3.8,0.9c1.2,1.2,1.6,2.8,0.9,3.8c1.3,0.8,2.7,1.4,4.2,1.7c0.2-1.1,1.6-2,3.3-2s3.1,0.9,3.3,2 c1.5-0.4,2.9-0.9,4.2-1.7c-0.7-0.9-0.3-2.6,0.9-3.8c1.3-1.2,2.8-1.6,3.8-0.9c0.8-1.3,1.4-2.7,1.7-4.2C28.9,19.1,28,17.7,28,16z M16,24c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S20.4,24,16,24z"/>
</defs>
<use id="gear" xlink:href="#glyphShape-gear"/>
<use id="gear-inverted" xlink:href="#glyphShape-gear"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -181,7 +181,15 @@
border-bottom: 1px solid #ccc;
}
.searchbar-engine-one-off-item.last-of-row {
.search-setting-button-compact {
border-bottom: none !important;
}
.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
.search-setting-button-compact {
background-image: none;
}
@ -291,3 +299,11 @@
background-color: #d3d3d3;
border-top-color: #bdbebe;
}
.search-setting-button-compact {
list-style-image: url("chrome://browser/skin/gear.svg#gear");
}
.search-setting-button-compact[selected] {
list-style-image: url("chrome://browser/skin/gear.svg#gear-inverted");
}

View File

@ -289,7 +289,9 @@ nsAutoCompleteController::HandleText()
}
NS_IMETHODIMP
nsAutoCompleteController::HandleEnter(bool aIsPopupSelection, bool *_retval)
nsAutoCompleteController::HandleEnter(bool aIsPopupSelection,
nsIDOMEvent *aEvent,
bool *_retval)
{
*_retval = false;
if (!mInput)
@ -312,7 +314,7 @@ nsAutoCompleteController::HandleEnter(bool aIsPopupSelection, bool *_retval)
// Stop the search, and handle the enter.
StopSearch();
EnterMatch(aIsPopupSelection);
EnterMatch(aIsPopupSelection, aEvent);
return NS_OK;
}
@ -388,7 +390,7 @@ NS_IMETHODIMP
nsAutoCompleteController::HandleTab()
{
bool cancel;
return HandleEnter(false, &cancel);
return HandleEnter(false, nullptr, &cancel);
}
NS_IMETHODIMP
@ -1356,7 +1358,8 @@ nsAutoCompleteController::ClearSearchTimer()
}
nsresult
nsAutoCompleteController::EnterMatch(bool aIsPopupSelection)
nsAutoCompleteController::EnterMatch(bool aIsPopupSelection,
nsIDOMEvent *aEvent)
{
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
nsCOMPtr<nsIAutoCompletePopup> popup;
@ -1492,7 +1495,7 @@ nsAutoCompleteController::EnterMatch(bool aIsPopupSelection)
ClosePopup();
bool cancel;
input->OnTextEntered(&cancel);
input->OnTextEntered(aEvent, &cancel);
return NS_OK;
}

View File

@ -55,7 +55,8 @@ protected:
nsresult ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult);
nsresult PostSearchCleanup();
nsresult EnterMatch(bool aIsPopupSelection);
nsresult EnterMatch(bool aIsPopupSelection,
nsIDOMEvent *aEvent);
nsresult RevertTextValue();
nsresult CompleteDefaultIndex(int32_t aResultIndex);

View File

@ -5,6 +5,7 @@
#include "nsISupports.idl"
interface nsIAutoCompleteInput;
interface nsIDOMEvent;
[scriptable, uuid(ff9f8465-204a-47a6-b3c9-0628b3856684)]
interface nsIAutoCompleteController : nsISupports
@ -63,9 +64,16 @@ interface nsIAutoCompleteController : nsISupports
* fill this value into the input field before continuing. If false, just
* use the current value of the input field.
*
* @param aIsPopupSelection
* Pass true if the selection was made from the popup.
* @param aEvent
* The event that triggered the enter, like a key event if the user
* pressed the Return key or a click event if the user clicked a popup
* item.
* @return True if the controller wishes to prevent event propagation and default event
*/
boolean handleEnter(in boolean aIsPopupSelection);
boolean handleEnter(in boolean aIsPopupSelection,
[optional] in nsIDOMEvent aEvent);
/*
* Notify the controller that the user wishes to revert autocomplete

View File

@ -124,9 +124,11 @@ interface nsIAutoCompleteInput : nsISupports
/*
* Notification that the user selected and entered a result item
*
* @param aEvent
* The event that triggered the enter.
* @return True if the user wishes to prevent the enter
*/
boolean onTextEntered();
boolean onTextEntered([optional] in nsIDOMEvent aEvent);
/*
* Notification that the user cancelled the autocomplete session

View File

@ -326,6 +326,23 @@ this.PlacesUtils = {
return bundle.GetStringFromName(key);
},
/**
* Makes a moz-action URI for the given action and set of parameters.
*
* @param type
* The action type.
* @param params
* A JS object of action params.
* @returns A moz-action URI as a string.
*/
mozActionURI(type, params) {
let encodedParams = {};
for (let key in params) {
encodedParams[key] = encodeURIComponent(params[key]);
}
return "moz-action:" + type + "," + JSON.stringify(encodedParams);
},
/**
* Determines whether or not a ResultNode is a Bookmark folder.
* @param aNode

View File

@ -575,24 +575,6 @@ function stripHttpAndTrim(spec) {
return spec;
}
/**
* Make a moz-action: URL for a given action and set of parameters.
*
* @param action
* Name of the action
* @param params
* Object, whose keys are parameter names and values are the
* corresponding parameter values.
* @return String representation of the built moz-action: URL
*/
function makeActionURL(action, params) {
let encodedParams = {};
for (let key in params) {
encodedParams[key] = encodeURIComponent(params[key]);
}
return "moz-action:" + action + "," + JSON.stringify(encodedParams);
}
/**
* Returns the key to be used for a URL in a map for the purposes of removing
* duplicate entries - any 2 URLs that should be considered the same should
@ -1132,8 +1114,10 @@ Search.prototype = {
let escapedURL = entry.url.href.replace("%s", queryString);
let style = (this._enableActions ? "action " : "") + "keyword";
let actionURL = makeActionURL("keyword", { url: escapedURL,
input: this._originalSearchString });
let actionURL = PlacesUtils.mozActionURI("keyword", {
url: escapedURL,
input: this._originalSearchString,
});
let value = this._enableActions ? actionURL : escapedURL;
// The title will end up being "host: queryString"
let comment = entry.url.host;
@ -1231,7 +1215,7 @@ Search.prototype = {
if (match.engineAlias) {
actionURLParams.alias = match.engineAlias;
}
let value = makeActionURL("searchengine", actionURLParams);
let value = PlacesUtils.mozActionURI("searchengine", actionURLParams);
this._addMatch({
value: value,
@ -1263,7 +1247,7 @@ Search.prototype = {
let match = {
// We include the deviceName in the action URL so we can render it in
// the URLBar.
value: makeActionURL("remotetab", { url, deviceName }),
value: PlacesUtils.mozActionURI("remotetab", { url, deviceName }),
comment: title || url,
style: "action remotetab",
// we want frecency > FRECENCY_DEFAULT so it doesn't get pushed out
@ -1323,7 +1307,7 @@ Search.prototype = {
let escapedURL = uri.spec;
let displayURL = textURIService.unEscapeURIForUI("UTF-8", uri.spec);
let value = makeActionURL("visiturl", {
let value = PlacesUtils.mozActionURI("visiturl", {
url: escapedURL,
input: this._originalSearchString,
});
@ -1392,7 +1376,7 @@ Search.prototype = {
}
// Turn the match into a searchengine action with a favicon.
match.value = makeActionURL("searchengine", {
match.value = PlacesUtils.mozActionURI("searchengine", {
engineName: parseResult.engineName,
input: parseResult.terms,
searchQuery: parseResult.terms,
@ -1564,7 +1548,7 @@ Search.prototype = {
let url = escapedURL;
let action = null;
if (this._enableActions && openPageCount > 0 && this.hasBehavior("openpage")) {
url = makeActionURL("switchtab", {url: escapedURL});
url = PlacesUtils.mozActionURI("switchtab", {url: escapedURL});
action = "switchtab";
}

View File

@ -557,7 +557,8 @@ nsFormFillController::OnSearchComplete()
}
NS_IMETHODIMP
nsFormFillController::OnTextEntered(bool* aPrevent)
nsFormFillController::OnTextEntered(nsIDOMEvent* aEvent,
bool* aPrevent)
{
NS_ENSURE_ARG(aPrevent);
NS_ENSURE_TRUE(mFocusedInput, NS_OK);
@ -1008,7 +1009,7 @@ nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
cancel = false;
break;
case nsIDOMKeyEvent::DOM_VK_RETURN:
mController->HandleEnter(false, &cancel);
mController->HandleEnter(false, aEvent, &cancel);
break;
}

View File

@ -44,7 +44,6 @@
<field name="mController">null</field>
<field name="mSearchNames">null</field>
<field name="mIgnoreInput">false</field>
<field name="mEnterEvent">null</field>
<field name="_searchBeginHandler">null</field>
<field name="_searchCompleteHandler">null</field>
@ -232,11 +231,12 @@
</method>
<method name="onTextEntered">
<parameter name="event"/>
<body><![CDATA[
let rv = false;
if (this._textEnteredHandler)
rv = this._textEnteredHandler(this.mEnterEvent);
this.mEnterEvent = null;
if (this._textEnteredHandler) {
rv = this._textEnteredHandler(event);
}
return rv;
]]></body>
</method>
@ -494,14 +494,13 @@
if (aEvent.metaKey)
aEvent.preventDefault();
}
this.mEnterEvent = aEvent;
if (this.mController.selection) {
this._selectionDetails = {
index: this.mController.selection.currentIndex,
kind: "key"
};
}
cancel = this.handleEnter();
cancel = this.handleEnter(aEvent);
break;
case KeyEvent.DOM_VK_DELETE:
if (AppConstants.platform == "macosx" && !aEvent.shiftKey) {
@ -536,8 +535,9 @@
</method>
<method name="handleEnter">
<parameter name="event"/>
<body><![CDATA[
return this.mController.handleEnter(false);
return this.mController.handleEnter(false, event || null);
]]></body>
</method>
@ -946,7 +946,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
<parameter name="aEvent"/>
<body><![CDATA[
var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
controller.handleEnter(true);
controller.handleEnter(true, aEvent);
]]></body>
</method>
</implementation>
@ -1253,15 +1253,24 @@ extends="chrome://global/content/bindings/popup.xml#popup">
invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
(item.getAttribute("url") == url ||
this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
item.collapsed = false;
// Call adjustSiteIconStart only after setting collapsed=false.
// The calculations it does may be wrong otherwise.
item.adjustSiteIconStart(this._siteIconStart);
// The popup may have changed size between now and the last time
// the item was shown, so always handle over/underflow.
item.handleOverUnderflow();
this._currentIndex++;
continue;
// Additionally, if the item is a searchengine action, then it
// should only be reused if the engine name is the same as the
// popup's override engine name, if any.
let action = item._parseActionUrl(url);
if (!action ||
action.type != "searchengine" ||
!this.overrideSearchEngineName ||
action.params.engineName == this.overrideSearchEngineName) {
item.collapsed = false;
// Call adjustSiteIconStart only after setting collapsed=
// false. The calculations it does may be wrong otherwise.
item.adjustSiteIconStart(this._siteIconStart);
// The popup may have changed size between now and the last
// time the item was shown, so always handle over/underflow.
item.handleOverUnderflow();
this._currentIndex++;
continue;
}
}
}
else {
@ -1276,7 +1285,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
item.setAttribute("image", iconURI);
item.setAttribute("url", url);
item.setAttribute("title", controller.getCommentAt(this._currentIndex));
item.setAttribute("type", controller.getStyleAt(this._currentIndex));
item.setAttribute("originaltype", controller.getStyleAt(this._currentIndex));
item.setAttribute("text", trimmedSearchString);
if (this._currentIndex < existingItemsCount) {
@ -1851,7 +1860,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
let originalUrl = this.getAttribute("url");
let emphasiseUrl = true;
let type = this.getAttribute("type");
let type = this.getAttribute("originaltype");
let types = new Set(type.split(/\s+/));
let initialTypes = new Set(types);
// Remove types that should ultimately not be in the `type` string.
@ -1898,6 +1907,17 @@ extends="chrome://global/content/bindings/popup.xml#popup">
// changed the order while it was possible, so doesn't look like
// there's a strong need for that.
let {engineName, searchSuggestion, searchQuery} = action.params;
// Override the engine name if the popup defines an override.
let override = popup.overrideSearchEngineName;
if (override && override != engineName) {
engineName = override;
action.params.engineName = override;
let newURL =
PlacesUtils.mozActionURI(action.type, action.params);
this.setAttribute("url", newURL);
}
let engineStr =
this._stringBundle.formatStringFromName("searchWithEngine",
[engineName], 1);