Bug 1534455 - Convert autocomplete binding to a customized input element. r=dao,bgrins

Differential Revision: https://phabricator.services.mozilla.com/D33250

--HG--
rename : toolkit/content/widgets/autocomplete.xml => toolkit/content/widgets/autocomplete-input.js
extra : moz-landing-system : lando
This commit is contained in:
Tim Nguyen 2019-09-17 06:22:42 +00:00
parent bbcb8b3a63
commit e043df8d78
38 changed files with 1027 additions and 1098 deletions

View File

@ -6,4 +6,5 @@ support-files =
[browser_aria_owns.js]
skip-if = true || (verify && !debug && (os == 'linux')) #Bug 1445513
[browser_searchbar.js]
[browser_shadowdom.js]

View File

@ -0,0 +1,52 @@
"use strict";
/* import-globals-from ../../mochitest/role.js */
loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
// eslint-disable-next-line camelcase
add_task(async function test_searchbar_a11y_tree() {
await SpecialPowers.pushPrefEnv({
set: [["browser.search.widget.inNavBar", true]],
});
let searchbar = await TestUtils.waitForCondition(
() => document.getElementById("searchbar"),
"wait for search bar to appear"
);
// Make sure the popup has been rendered so it shows up in the a11y tree.
let popup = document.getElementById("PopupSearchAutoComplete");
let promise = BrowserTestUtils.waitForEvent(popup, "popupshown", false);
searchbar.textbox.openPopup();
await promise;
promise = BrowserTestUtils.waitForEvent(popup, "popuphidden", false);
searchbar.textbox.closePopup();
await promise;
const TREE = {
role: ROLE_EDITCOMBOBOX,
children: [
// input element
{
role: ROLE_ENTRY,
children: [],
},
// context menu
{
role: ROLE_COMBOBOX_LIST,
children: [],
},
// result list
{
role: ROLE_GROUPING,
// not testing the structure inside the result list
},
],
};
testAccessibleTree(searchbar, TREE);
});

View File

@ -89,47 +89,7 @@
testAccessibleTree("txc_password", accTree);
//////////////////////////////////////////////////////////////////////////
// autocomplete textbox
accTree = {
// textbox
role: ROLE_AUTOCOMPLETE,
children: [
{
// html:input
role: ROLE_ENTRY,
children: [
{
// #text
role: ROLE_TEXT_LEAF,
children: []
}
]
},
{
// xul:menupopup
role: ROLE_COMBOBOX_LIST,
children: []
},
{
// xul:richlistbox
role: ROLE_COMBOBOX_LIST,
children: []
}
]
};
var txc = document.getElementById("txc_autocomplete");
SimpleTest.ok(txc, "Testing (New) Toolkit autocomplete widget.");
// Dumb access to trigger popup lazy creation.
dump("Trigget popup lazy creation");
waitForEvent(EVENT_REORDER, txc, () => {
testAccessibleTree("txc_autocomplete", accTree);
SimpleTest.finish();
});
txc.popup.initialize();
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
@ -157,7 +117,6 @@
<textbox id="txc_search_searchbutton" searchbutton="true" is="search-textbox" value="hello"/>
<textbox id="txc_number" type="number" value="44"/>
<textbox id="txc_password" type="password" value="hello"/>
<textbox id="txc_autocomplete" type="autocomplete" value="hello"/>
</vbox>
</hbox>

View File

@ -557,7 +557,7 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
}
#urlbar,
.searchbar-textbox {
#searchbar {
/* Setting a width and min-width to let the location & search bars maintain
a constant width in case they haven't be resized manually. (bug 965772) */
width: 1px;

View File

@ -4735,10 +4735,7 @@ const BrowserSearch = {
}
let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
if (
!aSearchBar ||
document.activeElement != aSearchBar.textbox.inputField
) {
if (!aSearchBar || document.activeElement != aSearchBar.textbox) {
// Limit the results to search suggestions, like the search bar.
gURLBar.search(UrlbarTokenizer.RESTRICT.SEARCH);
}

View File

@ -26,7 +26,7 @@ add_task(async function() {
await BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
let doc = window.document;
let searchInput = BrowserSearch.searchBar.textbox.inputField;
let searchInput = BrowserSearch.searchBar.textbox;
isnot(
searchInput,
doc.activeElement,

View File

@ -10,7 +10,7 @@ async function waitForSearchBarFocus() {
let searchbar = document.getElementById("searchbar");
await TestUtils.waitForCondition(function() {
logActiveElement();
return document.activeElement === searchbar.textbox.inputField;
return document.activeElement === searchbar.textbox;
});
}

View File

@ -53,19 +53,23 @@ add_task(async function searchbar_in_panel() {
searchbar.value = "foo";
searchbar.focus();
// Reaching into this context menu is pretty evil, but hey... it's a test.
let textbox = document.getAnonymousElementByAttribute(
searchbar.textbox,
"anonid",
"moz-input-box"
);
let contextmenu = textbox.menupopup;
let contextMenuShown = promisePanelElementShown(window, contextmenu);
// Can't use promisePanelElementShown() here since the search bar
// creates its context menu lazily the first time it is opened.
let contextMenuShown = new Promise(resolve => {
let listener = event => {
if (searchbar._menupopup && event.target == searchbar._menupopup) {
window.removeEventListener("popupshown", listener);
resolve(searchbar._menupopup);
}
};
window.addEventListener("popupshown", listener);
});
EventUtils.synthesizeMouseAtCenter(searchbar, {
type: "contextmenu",
button: 2,
});
await contextMenuShown;
let contextmenu = await contextMenuShown;
ok(isOverflowOpen(), "Panel should still be open");

View File

@ -18,6 +18,7 @@
buttons="accept, cancel"
buttoniconaccept="save"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="BookmarkPropertiesPanel.onDialogLoad();"
onunload="BookmarkPropertiesPanel.onDialogUnload();"
style="min-width: 30em;"
@ -31,6 +32,7 @@
<script src="chrome://browser/content/places/editBookmark.js"/>
<script src="chrome://browser/content/places/bookmarkProperties.js"/>
<script src="chrome://global/content/globalOverlay.js"/>
<script src="chrome://global/content/editMenuOverlay.js"/>
<script src="chrome://browser/content/utilityOverlay.js"/>
<script src="chrome://browser/content/places/places-tree.js"/>

View File

@ -76,12 +76,12 @@
<label value="&editBookmarkOverlay.tags.label;"
accesskey="&editBookmarkOverlay.tags.accesskey;"
control="editBMPanel_tagsField"/>
<hbox flex="1" align="center">
<textbox id="editBMPanel_tagsField"
type="autocomplete"
flex="1"
<hbox flex="1" align="center" role="combobox">
<html:input id="editBMPanel_tagsField"
is="autocomplete-input"
style="-moz-box-flex: 1;"
autocompletesearch="places-tag-autocomplete"
autocompletepopup="PopupAutoComplete"
autocompletepopup="editBMPanel_tagsAutocomplete"
completedefaultindex="true"
completeselectedindex="true"
tabscrolling="true"
@ -93,6 +93,17 @@
tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
oncommand="gEditItemOverlay.toggleTagsSelector().catch(Cu.reportError);"/>
<popupset>
<panel is="autocomplete-richlistbox-popup"
type="autocomplete-richlistbox"
id="editBMPanel_tagsAutocomplete"
role="group"
noautofocus="true"
hidden="true"
overflowpadding="4"
norolluponanchor="true"
nomaxresults="true"/>
</popupset>
</hbox>
</vbox>

View File

@ -578,7 +578,7 @@ add_task(async function enter_during_autocomplete_should_prevent_autoclose() {
// Start autocomplete with the registered tag.
tagsField.value = "";
let popup = document.getElementById("PopupAutoComplete");
let popup = document.getElementById("editBMPanel_tagsAutocomplete");
let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
tagsField.focus();
EventUtils.sendString("a", window);
@ -622,7 +622,7 @@ add_task(async function escape_during_autocomplete_should_prevent_autoclose() {
// Start autocomplete with the registered tag.
tagsField.value = "";
let popup = document.getElementById("PopupAutoComplete");
let popup = document.getElementById("editBMPanel_tagsAutocomplete");
let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
tagsField.focus();
EventUtils.sendString("a", window);

View File

@ -44,12 +44,23 @@
</menulist>
<vbox id="customSettings" hidden="true">
<textbox id="homePageUrl"
class="uri-element check-home-page-controlled"
data-preference-related="browser.startup.homepage"
type="autocomplete"
data-l10n-id="home-homepage-custom-url"
autocompletesearch="unifiedcomplete" />
<box role="combobox">
<html:input id="homePageUrl"
type="text"
is="autocomplete-input"
class="uri-element check-home-page-controlled"
style="-moz-box-flex: 1;"
data-preference-related="browser.startup.homepage"
data-l10n-id="home-homepage-custom-url"
autocompletepopup="homePageUrlAutocomplete"
autocompletesearch="unifiedcomplete" />
<popupset>
<panel id="homePageUrlAutocomplete"
is="autocomplete-richlistbox-popup"
type="autocomplete-richlistbox"
noautofocus="true"/>
</popupset>
</box>
<hbox class="homepage-buttons">
<button id="useCurrentBtn"
is="highlightable-button"

View File

@ -51,7 +51,8 @@
<image class="searchbar-search-icon"></image>
<image class="searchbar-search-icon-overlay"></image>
</hbox>
<textbox class="searchbar-textbox" type="autocomplete" inputtype="search" placeholder="&searchInput.placeholder;" flex="1" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0"/>
<html:input class="searchbar-textbox" is="autocomplete-input" type="search" placeholder="&searchInput.placeholder;" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0"/>
<menupopup class="textbox-contextmenu"></menupopup>
<hbox class="search-go-container">
<image class="search-go-button urlbar-icon" hidden="true" onclick="handleSearchCommand(event);" tooltiptext="&contentSearchSubmit.tooltip;"></image>
</hbox>
@ -80,6 +81,10 @@
this._stringBundle = this.querySelector("stringbundle");
this._textbox = this.querySelector(".searchbar-textbox");
this._menupopup = null;
this._pasteAndSearchMenuItem = null;
this._suggestMenuItem = null;
this._setupTextboxEventListeners();
this._initTextbox();
@ -199,7 +204,9 @@
if (
this._textbox &&
this._textbox.mController &&
this._textbox.mController.input == this
this._textbox.mController.input &&
this._textbox.mController.input.wrappedJSObject ==
this.nsIAutocompleteInput
) {
this._textbox.mController.input = null;
}
@ -432,9 +439,8 @@
if (
!this._preventClickSelectsAll &&
UrlbarPrefs.get("clickSelectsAll") &&
document.activeElement == this._textbox.inputField &&
this._textbox.inputField.selectionStart ==
this._textbox.inputField.selectionEnd
document.activeElement == this._textbox &&
this._textbox.selectionStart == this._textbox.selectionEnd
) {
this._textbox.editor.selectAll();
}
@ -498,8 +504,7 @@
event => {
// If the input field is still focused then a different window has
// received focus, ignore the next focus event.
this._ignoreFocus =
document.activeElement == this._textbox.inputField;
this._ignoreFocus = document.activeElement == this._textbox;
},
true
);
@ -639,6 +644,18 @@
true
);
if (AppConstants.platform == "macosx") {
this.textbox.addEventListener(
"keypress",
event => {
if (event.keyCode == KeyEvent.DOM_VK_F4) {
this.textbox.openSearch();
}
},
true
);
}
this.textbox.addEventListener("dragover", event => {
let types = event.dataTransfer.types;
if (
@ -661,11 +678,40 @@
this.openSuggestionsPanel();
}
});
this.textbox.addEventListener("contextmenu", event => {
if (event.target != this.textbox) {
return;
}
if (!this._menupopup) {
this._buildContextMenu();
}
BrowserSearch.searchBar._textbox.closePopup();
let enabled = Services.prefs.getBoolPref(
"browser.search.suggest.enabled"
);
this._suggestMenuItem.setAttribute("checked", enabled);
let controller = document.commandDispatcher.getControllerForCommand(
"cmd_paste"
);
enabled = controller.isCommandEnabled("cmd_paste");
if (enabled) {
this._pasteAndSearchMenuItem.removeAttribute("disabled");
} else {
this._pasteAndSearchMenuItem.setAttribute("disabled", "true");
}
this._menupopup.openPopupAtScreen(event.screenX, event.screenY, true);
event.preventDefault();
});
}
_initTextbox() {
// nsIController
this.searchbarController = {
let searchbarController = {
textbox: this.textbox,
supportsCommand(command) {
return (
@ -699,29 +745,14 @@
}
},
};
this.textbox.controllers.appendController(searchbarController);
if (this.parentNode.parentNode.localName == "toolbarpaletteitem") {
return;
}
let inputBox = document.getAnonymousElementByAttribute(
this.textbox,
"anonid",
"moz-input-box"
);
// Force the Custom Element to upgrade until Bug 1470242 handles this:
window.customElements.upgrade(inputBox);
let cxmenu = inputBox.menupopup;
cxmenu.addEventListener(
"popupshowing",
() => {
this._initContextMenu(cxmenu);
},
{ capture: true, once: true }
);
this.textbox.setAttribute("aria-owns", this.textbox.popup.id);
this.setAttribute("role", "combobox");
this.setAttribute("aria-owns", this.textbox.popup.id);
// This overrides the searchParam property in autocomplete.xml. We're
// hijacking this property as a vehicle for delivering the privacy
@ -846,94 +877,75 @@
};
}
_initContextMenu(aMenu) {
let stringBundle = this._stringBundle;
_buildContextMenu() {
const raw = `
<menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
<menuseparator/>
<menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
<menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
<menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
<menuitem anonid="paste-and-search" oncommand="BrowserSearch.pasteAndSearch(event)"/>
<menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
<menuseparator/>
<menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
<menuseparator/>
<menuitem cmd="cmd_clearHistory"/>
<menuitem cmd="cmd_togglesuggest" type="checkbox" autocheck="false"/>
`;
let pasteAndSearch, suggestMenuItem;
let element, label, akey;
this._menupopup = this.querySelector(".textbox-contextmenu");
element = document.createXULElement("menuseparator");
aMenu.appendChild(element);
let frag = MozXULElement.parseXULToFragment(raw, [
"chrome://global/locale/textcontext.dtd",
]);
let insertLocation = aMenu.firstElementChild;
while (
insertLocation.nextElementSibling &&
insertLocation.getAttribute("cmd") != "cmd_paste"
) {
insertLocation = insertLocation.nextElementSibling;
}
if (insertLocation) {
element = document.createXULElement("menuitem");
label = stringBundle.getString("cmd_pasteAndSearch");
element.setAttribute("label", label);
element.setAttribute("anonid", "paste-and-search");
element.setAttribute(
"oncommand",
"BrowserSearch.pasteAndSearch(event)"
);
aMenu.insertBefore(element, insertLocation.nextElementSibling);
pasteAndSearch = element;
}
// Insert attributes that come from localized properties
let el = frag.querySelector('[anonid="paste-and-search"]');
el.setAttribute(
"label",
this._stringBundle.getString("cmd_pasteAndSearch")
);
element = document.createXULElement("menuitem");
label = stringBundle.getString("cmd_clearHistory");
akey = stringBundle.getString("cmd_clearHistory_accesskey");
element.setAttribute("label", label);
element.setAttribute("accesskey", akey);
element.setAttribute("cmd", "cmd_clearhistory");
aMenu.appendChild(element);
el = frag.querySelector('[cmd="cmd_clearHistory"]');
el.setAttribute(
"label",
this._stringBundle.getString("cmd_clearHistory")
);
el.setAttribute(
"accesskey",
this._stringBundle.getString("cmd_clearHistory_accesskey")
);
element = document.createXULElement("menuitem");
label = stringBundle.getString("cmd_showSuggestions");
akey = stringBundle.getString("cmd_showSuggestions_accesskey");
element.setAttribute("anonid", "toggle-suggest-item");
element.setAttribute("label", label);
element.setAttribute("accesskey", akey);
element.setAttribute("cmd", "cmd_togglesuggest");
element.setAttribute("type", "checkbox");
element.setAttribute("autocheck", "false");
suggestMenuItem = element;
aMenu.appendChild(element);
el = frag.querySelector('[cmd="cmd_togglesuggest"]');
el.setAttribute(
"label",
this._stringBundle.getString("cmd_showSuggestions")
);
el.setAttribute(
"accesskey",
this._stringBundle.getString("cmd_showSuggestions_accesskey")
);
if (AppConstants.platform == "macosx") {
this.textbox.addEventListener(
"keypress",
event => {
if (event.keyCode == KeyEvent.DOM_VK_F4) {
this.textbox.openSearch();
}
},
true
);
}
this._menupopup.appendChild(frag);
this.textbox.controllers.appendController(this.searchbarController);
this._pasteAndSearchMenuItem = this._menupopup.querySelector(
'[anonid="paste-and-search"]'
);
this._suggestMenuItem = this._menupopup.querySelector(
'[cmd="cmd_togglesuggest"]'
);
let onpopupshowing = function() {
BrowserSearch.searchBar._textbox.closePopup();
if (suggestMenuItem) {
let enabled = Services.prefs.getBoolPref(
"browser.search.suggest.enabled"
this._menupopup.addEventListener("command", cmdEvt => {
let cmd = cmdEvt.originalTarget.getAttribute("cmd");
if (cmd) {
let controller = document.commandDispatcher.getControllerForCommand(
cmd
);
suggestMenuItem.setAttribute("checked", enabled);
controller.doCommand(cmd);
cmdEvt.stopPropagation();
cmdEvt.preventDefault();
}
if (!pasteAndSearch) {
return;
}
let controller = document.commandDispatcher.getControllerForCommand(
"cmd_paste"
);
let enabled = controller.isCommandEnabled("cmd_paste");
if (enabled) {
pasteAndSearch.removeAttribute("disabled");
} else {
pasteAndSearch.setAttribute("disabled", "true");
}
};
aMenu.addEventListener("popupshowing", onpopupshowing);
onpopupshowing();
});
}
}

View File

@ -288,12 +288,18 @@ add_task(async function testAutocomplete() {
searchBar.textbox.showHistoryPopup();
await popupShownPromise;
checkMenuEntries(searchEntries);
searchBar.textbox.closePopup();
});
add_task(async function testClearHistory() {
// Open the textbox context menu to trigger controller attachment.
let textbox = searchBar.textbox;
let popupShownPromise = BrowserTestUtils.waitForEvent(textbox, "popupshown");
let popupShownPromise = BrowserTestUtils.waitForEvent(
window,
"popupshown",
false,
event => event.target.classList.contains("textbox-contextmenu")
);
EventUtils.synthesizeMouseAtCenter(textbox, {
type: "contextmenu",
button: 2,

View File

@ -174,7 +174,7 @@ add_task(async function test_arrows() {
add_task(async function test_typing_clears_button_selection() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"the search bar should be focused"
); // from the previous test.
ok(!textbox.selectedButton, "no button should be selected");
@ -196,7 +196,7 @@ add_task(async function test_typing_clears_button_selection() {
add_task(async function test_tab() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"the search bar should be focused"
); // from the previous test.

View File

@ -176,7 +176,7 @@ add_no_popup_task(function click_doesnt_open_popup() {
EventUtils.synthesizeMouseAtCenter(textbox, {});
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -199,7 +199,7 @@ add_task(async function click_opens_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -217,14 +217,22 @@ add_no_popup_task(async function right_click_doesnt_open_popup() {
gURLBar.focus();
textbox.value = "foo";
let contextPopup = textbox.inputField.parentNode.menupopup;
let promise = promiseEvent(contextPopup, "popupshown");
// Can't wait for an event on the actual menu since it is created
// lazily the first time it is displayed.
let promise = new Promise(resolve => {
let listener = event => {
if (searchbar._menupopup && event.target == searchbar._menupopup) {
resolve(searchbar._menupopup);
}
};
window.addEventListener("popupshown", listener);
});
context_click(textbox);
await promise;
let contextPopup = await promise;
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -253,7 +261,7 @@ add_task(async function focus_change_closes_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -286,7 +294,7 @@ add_task(async function focus_change_closes_small_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
@ -313,7 +321,7 @@ add_task(async function escape_closes_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -342,23 +350,17 @@ add_task(async function contextmenu_closes_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
is(textbox.selectionEnd, 3, "Should have selected all of the text");
promise = promiseEvent(searchPopup, "popuphidden");
// synthesizeKey does not work with VK_CONTEXT_MENU (bug 1127368)
EventUtils.synthesizeMouseAtCenter(textbox, {
type: "contextmenu",
button: null,
});
context_click(textbox);
await promise;
let contextPopup = textbox.inputField.parentNode.menupopup;
let contextPopup = searchbar._menupopup;
promise = promiseEvent(contextPopup, "popuphidden");
contextPopup.hidePopup();
await promise;
@ -384,7 +386,7 @@ add_task(async function tab_opens_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -408,7 +410,7 @@ add_no_popup_task(function tab_doesnt_open_popup() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -433,7 +435,7 @@ add_task(async function refocus_window_doesnt_open_popup_mouse() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -480,7 +482,7 @@ add_task(async function refocus_window_doesnt_open_popup_keyboard() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -541,7 +543,7 @@ add_task(async function dont_consume_clicks() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
is(textbox.selectionStart, 0, "Should have selected all of the text");
@ -579,7 +581,7 @@ add_task(async function drop_opens_popup() {
let homeButton = document.getElementById("home-button");
EventUtils.synthesizeDrop(
homeButton,
textbox.inputField,
textbox,
[[{ type: "text/plain", data: "foo" }]],
"move",
window
@ -593,7 +595,7 @@ add_task(async function drop_opens_popup() {
);
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"Should have focused the search bar"
);
promise = promiseEvent(searchPopup, "popuphidden");

View File

@ -136,7 +136,7 @@ add_task(async function test_arrows() {
add_task(async function test_tab() {
is(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"the search bar should be focused"
); // from the previous test.
@ -171,7 +171,7 @@ add_task(async function test_tab() {
// ... and move the focus out of the searchbox.
isnot(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"the search bar no longer be focused"
);
});
@ -225,7 +225,7 @@ add_task(async function test_shift_tab() {
// ... and move the focus out of the searchbox.
isnot(
Services.focus.focusedElement,
textbox.inputField,
textbox,
"the search bar no longer be focused"
);
});

View File

@ -314,7 +314,7 @@
%include ../shared/places/editBookmarkPanel.inc.css
#editBookmarkPanelRows > vbox > textbox,
#editBookmarkPanelRows > vbox > hbox > textbox {
#editBookmarkPanelRows > vbox > hbox > html|input {
-moz-appearance: none;
background-color: var(--arrowpanel-field-background);
color: inherit;
@ -325,7 +325,7 @@
}
#editBookmarkPanelRows > vbox > textbox[focused="true"],
#editBookmarkPanelRows > vbox > hbox > textbox[focused="true"] {
#editBookmarkPanelRows > vbox > hbox > html|input:focus {
border-color: -moz-mac-focusring !important;
box-shadow: var(--focus-ring-box-shadow);
}

View File

@ -47,9 +47,12 @@
.searchbar-textbox {
-moz-appearance: none;
-moz-box-flex: 1;
background: none;
color: inherit;
margin: 0;
border: none;
font: inherit;
margin: 0 !important; /* override autocomplete.css */
}
#urlbar:hover,

View File

@ -4,7 +4,7 @@
// This file defines these globals on the window object.
// Define them here so that ESLint can find them:
/* globals MozXULElement, MozElements */
/* globals MozXULElement, MozHTMLElement, MozElements */
"use strict";
@ -616,6 +616,7 @@
};
const MozXULElement = MozElements.MozElementMixin(XULElement);
const MozHTMLElement = MozElements.MozElementMixin(HTMLElement);
/**
* Given an object, add a proxy that reflects interface implementations
@ -737,6 +738,7 @@
// Attach the base class to the window so other scripts can use it:
window.MozXULElement = MozXULElement;
window.MozHTMLElement = MozHTMLElement;
customElements.setElementCreationCallback("browser", () => {
Services.scriptloader.loadSubScript(
@ -777,6 +779,10 @@
["findbar", "chrome://global/content/elements/findbar.js"],
["menulist", "chrome://global/content/elements/menulist.js"],
["search-textbox", "chrome://global/content/elements/search-textbox.js"],
[
"autocomplete-input",
"chrome://global/content/elements/autocomplete-input.js",
],
["stringbundle", "chrome://global/content/elements/stringbundle.js"],
[
"printpreview-toolbar",

View File

@ -61,7 +61,6 @@ toolkit.jar:
content/global/viewZoomOverlay.js
#endif
content/global/widgets.css
content/global/bindings/autocomplete.xml (widgets/autocomplete.xml)
content/global/bindings/calendar.js (widgets/calendar.js)
content/global/bindings/datekeeper.js (widgets/datekeeper.js)
content/global/bindings/datepicker.js (widgets/datepicker.js)
@ -73,6 +72,7 @@ toolkit.jar:
content/global/bindings/textbox.xml (widgets/textbox.xml)
content/global/bindings/timekeeper.js (widgets/timekeeper.js)
content/global/bindings/timepicker.js (widgets/timepicker.js)
content/global/elements/autocomplete-input.js (widgets/autocomplete-input.js)
content/global/elements/autocomplete-popup.js (widgets/autocomplete-popup.js)
content/global/elements/autocomplete-richlistitem.js (widgets/autocomplete-richlistitem.js)
content/global/elements/browser-custom-element.js (widgets/browser-custom-element.js)

View File

@ -64,7 +64,6 @@ skip-if = (verify && (os == 'win')) || (debug && (os == 'win') && (bits == 32))
[test_autocomplete5.xul]
[test_autocomplete_emphasis.xul]
[test_autocomplete_with_composition_on_input.html]
[test_autocomplete_with_composition_on_textbox.xul]
[test_autocomplete_placehold_last_complete.xul]
[test_browser_drop.xul]
[test_bug253481.xul]
@ -109,7 +108,6 @@ skip-if = toolkit == "cocoa"
[test_dialogfocus.xul]
[test_edit_contextmenu.html]
[test_editor_for_input_with_autocomplete.html]
[test_editor_for_textbox_with_autocomplete.xul]
[test_findbar.xul]
tags = clipboard
skip-if = os == 'mac' && os_version == '10.14' # macosx1014 due to 1550078

View File

@ -3,13 +3,15 @@
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Autocomplete Widget Test 2"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="autocomplete" type="autocomplete"
autocompletesearch="simple"/>
<html:input id="autocomplete"
is="autocomplete-input"
autocompletesearch="simple"/>
<script class="testbody" type="application/javascript">
<![CDATA[

View File

@ -3,13 +3,15 @@
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Autocomplete Widget Test 3"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="autocomplete" type="autocomplete"
autocompletesearch="simple"/>
<html:input id="autocomplete"
is="autocomplete-input"
autocompletesearch="simple"/>
<script class="testbody" type="application/javascript">
<![CDATA[

View File

@ -3,15 +3,16 @@
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Autocomplete Widget Test 4"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="autocomplete"
type="autocomplete"
completedefaultindex="true"
autocompletesearch="simple"/>
<html:input id="autocomplete"
is="autocomplete-input"
completedefaultindex="true"
autocompletesearch="simple"/>
<script class="testbody" type="application/javascript">
<![CDATA[

View File

@ -3,14 +3,16 @@
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Autocomplete Widget Test 5"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="autocomplete" type="autocomplete"
autocompletesearch="simple"
notifylegacyevents="true"/>
<html:input id="autocomplete"
is="autocomplete-input"
autocompletesearch="simple"
notifylegacyevents="true"/>
<script class="testbody" type="application/javascript">
<![CDATA[

View File

@ -3,14 +3,16 @@
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Autocomplete emphasis test"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="richautocomplete" type="autocomplete"
autocompletesearch="simple"
autocompletepopup="richpopup"/>
<html:input id="richautocomplete"
is="autocomplete-input"
autocompletesearch="simple"
autocompletepopup="richpopup"/>
<panel is="autocomplete-richlistbox-popup"
id="richpopup"
type="autocomplete-richlistbox"

View File

@ -4,12 +4,13 @@
<window title="Autocomplete Widget Test"
onload="setTimeout(keyCaretTest, 0);"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="autocomplete" type="autocomplete"/>
<html:input id="autocomplete" is="autocomplete-input"/>
<script class="testbody" type="application/javascript">
<![CDATA[
@ -51,10 +52,14 @@ function checkKeyCaretTest(key, expectedStart, expectedEnd, result, testid)
{
var autocomplete = $("autocomplete");
var keypressFired = false;
SpecialPowers.addSystemEventListener(autocomplete.inputField, "keypress", () => {
keypressFired = true;
}, {once: true});
function listener(event) {
if (event.target == autocomplete) {
keypressFired = true;
}
}
SpecialPowers.addSystemEventListener(window, "keypress", listener, false);
synthesizeKey(key, {});
SpecialPowers.removeSystemEventListener(window, "keypress", listener, false);
is(keypressFired, result, `${testid} keypress event should${result ? "" : " not"} be fired`);
is(autocomplete.selectionStart, expectedStart, testid + " selectionStart");
is(autocomplete.selectionEnd, expectedEnd, testid + " selectionEnd");

View File

@ -4,6 +4,7 @@
<window title="Autocomplete Widget Test"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="runTest();">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
@ -11,11 +12,11 @@
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<textbox id="autocomplete"
type="autocomplete"
completedefaultindex="true"
timeout="0"
autocompletesearch="simple"/>
<html:input id="autocomplete"
is="autocomplete-input"
completedefaultindex="true"
timeout="0"
autocompletesearch="simple"/>
<script class="testbody" type="application/javascript">
<![CDATA[

View File

@ -1,122 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Testing autocomplete with composition"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
<script type="text/javascript"
src="file_autocomplete_with_composition.js" />
<textbox id="textbox" type="autocomplete"
autocompletesearch="simpleForComposition"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
const nsIAutoCompleteResult = Ci.nsIAutoCompleteResult;
// This result can't be constructed in-line, because otherwise we leak memory.
function nsAutoCompleteSimpleResult(aString)
{
this.searchString = aString;
if (aString == "" || aString == "Mozilla".substr(0, aString.length)) {
this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
this.matchCount = 1;
this._value = "Mozilla";
} else {
this.searchResult = nsIAutoCompleteResult.RESULT_NOMATCH;
this.matchCount = 0;
this._value = "";
}
}
nsAutoCompleteSimpleResult.prototype = {
_value: "",
searchString: null,
searchResult: nsIAutoCompleteResult.RESULT_FAILURE,
defaultIndex: 0,
errorDescription: null,
matchCount: 0,
getValueAt: function(aIndex) { return aIndex == 0 ? this._value : null; },
getCommentAt: function() { return null; },
getStyleAt: function() { return null; },
getImageAt: function() { return null; },
getFinalCompleteValueAt: function(aIndex) { return this.getValueAt(aIndex); },
getLabelAt: function() { return null; },
removeValueAt: function() {}
};
// A basic autocomplete implementation that either returns one result or none
var autoCompleteSimpleID =
Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
var autoCompleteSimpleName =
"@mozilla.org/autocomplete/search;1?name=simpleForComposition"
var autoCompleteSimple = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsIAutoCompleteSearch))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
createInstance: function(outer, iid) {
return this.QueryInterface(iid);
},
startSearch: function(aString, aParam, aResult, aListener) {
var result = new nsAutoCompleteSimpleResult(aString);
aListener.onSearchResult(this, result);
},
stopSearch: function() {}
};
var componentManager =
Components.manager
.QueryInterface(Ci.nsIComponentRegistrar);
componentManager.registerFactory(autoCompleteSimpleID,
"Test Simple Autocomplete for composition",
autoCompleteSimpleName, autoCompleteSimple);
function runTests()
{
var target = document.getElementById("textbox");
target.setAttribute("timeout", 1);
var test1 = new nsDoTestsForAutoCompleteWithComposition(
"Testing on XUL textbox (asynchronously search)",
window, target, target.controller, is,
function () { return target.value; },
function () {
target.setAttribute("timeout", 0);
var test2 = new nsDoTestsForAutoCompleteWithComposition(
"Testing on XUL textbox (synchronously search)",
window, target, target.controller, is,
function () { return target.value; },
function () {
// Unregister the factory so that we don't get in the way of other
// tests
componentManager.unregisterFactory(autoCompleteSimpleID,
autoCompleteSimple);
SimpleTest.finish();
});
});
}
SimpleTest.waitForFocus(runTests);
]]>
</script>
</window>

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet
<?xml-stylesheet
href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
@ -10,19 +10,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=348233
<window title="Mozilla Bug 437844 and Bug 348233"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
<script type="application/javascript"
src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript"
src="RegisterUnregisterChrome.js"></script>
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=437844">
Mozilla Bug 437844
</a>
<a target="_blank"
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=348233">
Mozilla Bug 348233
</a>
@ -37,7 +37,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=348233
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.expectAssertions(14, 54);
SimpleTest.expectAssertions(14, 55);
/** Test for Bug 437844 and Bug 348233 **/
SimpleTest.waitForExplicitFinish();

View File

@ -1,124 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Basic editor behavior for XUL textbox element with autocomplete"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
<script type="text/javascript"
src="file_editor_with_autocomplete.js" />
<textbox id="textbox" type="autocomplete"
autocompletesearch="simpleForComposition"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
const nsIAutoCompleteResult = Ci.nsIAutoCompleteResult;
// This result can't be constructed in-line, because otherwise we leak memory.
function nsAutoCompleteSimpleResult(aString)
{
this.searchString = aString;
if (aString == "" ||
aString.toLowerCase() == "mozilla".substr(0, aString.length)) {
this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
this.matchCount = 1;
this._value = "Mozilla";
} else {
this.searchResult = nsIAutoCompleteResult.RESULT_NOMATCH;
this.matchCount = 0;
this._value = "";
}
}
nsAutoCompleteSimpleResult.prototype = {
_value: "",
searchString: null,
searchResult: nsIAutoCompleteResult.RESULT_FAILURE,
defaultIndex: 0,
errorDescription: null,
matchCount: 0,
getValueAt: function(aIndex) { return aIndex == 0 ? this._value : null; },
getCommentAt: function() { return null; },
getStyleAt: function() { return null; },
getImageAt: function() { return null; },
getFinalCompleteValueAt: function(aIndex) { return this.getValueAt(aIndex); },
getLabelAt: function() { return null; },
removeValueAt: function() {}
};
// A basic autocomplete implementation that either returns one result or none
var autoCompleteSimpleID =
Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
var autoCompleteSimpleName =
"@mozilla.org/autocomplete/search;1?name=simpleForComposition"
var autoCompleteSimple = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsIAutoCompleteSearch))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
createInstance: function(outer, iid) {
return this.QueryInterface(iid);
},
startSearch: function(aString, aParam, aResult, aListener) {
var result = new nsAutoCompleteSimpleResult(aString);
aListener.onSearchResult(this, result);
},
stopSearch: function() {}
};
var componentManager =
Components.manager
.QueryInterface(Ci.nsIComponentRegistrar);
componentManager.registerFactory(autoCompleteSimpleID,
"Test Simple Autocomplete for composition",
autoCompleteSimpleName, autoCompleteSimple);
async function runTests()
{
var target = document.getElementById("textbox");
target.setAttribute("timeout", 1);
let tests1 = new nsDoTestsForEditorWithAutoComplete(
"Testing on XUL textbox (asynchronously search)",
window, target, target.controller, is, todo_is,
function() { return target.value; });
await tests1.run();
target.setAttribute("timeout", 0);
let tests2 = new nsDoTestsForEditorWithAutoComplete(
"Testing on XUL textbox (synchronously search)",
window, target, target.controller, is, todo_is,
function() { return target.value; });
await tests2.run();
// Unregister the factory so that we don't get in the way of other
// tests
componentManager.unregisterFactory(autoCompleteSimpleID,
autoCompleteSimple);
SimpleTest.finish();
}
SimpleTest.waitForFocus(runTests);
]]>
</script>
</window>

View File

@ -0,0 +1,683 @@
/* 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/. */
"use strict";
// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
class AutocompleteInput extends HTMLInputElement {
constructor() {
super();
ChromeUtils.defineModuleGetter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
this.addEventListener("input", event => {
this.onInput(event);
});
this.addEventListener("keypress", event => this.handleKeyPress(event), {
capture: true,
mozSystemGroup: true,
});
this.addEventListener(
"compositionstart",
event => {
if (
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
this.mController.handleStartComposition();
}
},
true
);
this.addEventListener(
"compositionend",
event => {
if (
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
this.mController.handleEndComposition();
}
},
true
);
this.addEventListener(
"focus",
event => {
this.attachController();
if (
window.gBrowser &&
window.gBrowser.selectedBrowser.hasAttribute("usercontextid")
) {
this.userContextId = parseInt(
window.gBrowser.selectedBrowser.getAttribute("usercontextid")
);
} else {
this.userContextId = 0;
}
},
true
);
this.addEventListener(
"blur",
event => {
if (!this._dontBlur) {
if (this.forceComplete && this.mController.matchCount >= 1) {
// If forceComplete is requested, we need to call the enter processing
// on blur so the input will be forced to the closest match.
// Thunderbird is the only consumer of forceComplete and this is used
// to force an recipient's email to the exact address book entry.
this.mController.handleEnter(true);
}
if (!this.ignoreBlurWhileSearching) {
this.detachController();
}
}
},
true
);
}
connectedCallback() {
this.setAttribute("is", "autocomplete-input");
this.setAttribute("autocomplete", "off");
this.mController = Cc[
"@mozilla.org/autocomplete/controller;1"
].getService(Ci.nsIAutoCompleteController);
this.mSearchNames = null;
this.mIgnoreInput = false;
this.noRollupOnEmptySearch = false;
this._popup = null;
/**
* This is the maximum number of drop-down rows we get when we
* hit the drop marker beside fields that have it (like the URLbar).
*/
this.maxDropMarkerRows = 14;
this.nsIAutocompleteInput = this.getCustomInterfaceCallback(
Ci.nsIAutoCompleteInput
);
this.valueIsTyped = false;
this._textValueSetByCompleteDefault = false;
this._selectionDetails = null;
}
get popup() {
// Memoize the result in a field rather than replacing this property,
// so that it can be reset along with the binding.
if (this._popup) {
return this._popup;
}
let popup = null;
let popupId = this.getAttribute("autocompletepopup");
if (popupId) {
popup = document.getElementById(popupId);
}
/* This path is only used in tests, we have the <popupset> and <panel>
in document for other usages */
if (!popup) {
popup = document.createXULElement("panel", {
is: "autocomplete-richlistbox-popup",
});
popup.setAttribute("type", "autocomplete-richlistbox");
popup.setAttribute("noautofocus", "true");
if (!this._popupset) {
this._popupset = document.createXULElement("popupset");
document.documentElement.appendChild(this._popupset);
}
this._popupset.appendChild(popup);
}
popup.mInput = this;
return (this._popup = popup);
}
get popupElement() {
return this.popup;
}
get controller() {
return this.mController;
}
set popupOpen(val) {
if (val) {
this.openPopup();
} else {
this.closePopup();
}
}
get popupOpen() {
return this.popup.popupOpen;
}
set disableAutoComplete(val) {
this.setAttribute("disableautocomplete", val);
return val;
}
get disableAutoComplete() {
return this.getAttribute("disableautocomplete") == "true";
}
set completeDefaultIndex(val) {
this.setAttribute("completedefaultindex", val);
return val;
}
get completeDefaultIndex() {
return this.getAttribute("completedefaultindex") == "true";
}
set completeSelectedIndex(val) {
this.setAttribute("completeselectedindex", val);
return val;
}
get completeSelectedIndex() {
return this.getAttribute("completeselectedindex") == "true";
}
set forceComplete(val) {
this.setAttribute("forcecomplete", val);
return val;
}
get forceComplete() {
return this.getAttribute("forcecomplete") == "true";
}
set minResultsForPopup(val) {
this.setAttribute("minresultsforpopup", val);
return val;
}
get minResultsForPopup() {
var m = parseInt(this.getAttribute("minresultsforpopup"));
return isNaN(m) ? 1 : m;
}
set timeout(val) {
this.setAttribute("timeout", val);
return val;
}
get timeout() {
var t = parseInt(this.getAttribute("timeout"));
return isNaN(t) ? 50 : t;
}
set searchParam(val) {
this.setAttribute("autocompletesearchparam", val);
return val;
}
get searchParam() {
return this.getAttribute("autocompletesearchparam") || "";
}
get searchCount() {
this.initSearchNames();
return this.mSearchNames.length;
}
get inPrivateContext() {
return this.PrivateBrowsingUtils.isWindowPrivate(window);
}
get noRollupOnCaretMove() {
return this.popup.getAttribute("norolluponanchor") == "true";
}
set textValue(val) {
if (
typeof this.onBeforeTextValueSet == "function" &&
!this._textValueSetByCompleteDefault
) {
val = this.onBeforeTextValueSet(val);
}
// "input" event is automatically dispatched by the editor if
// necessary.
this._setValueInternal(val, true);
return this.value;
}
get textValue() {
if (typeof this.onBeforeTextValueGet == "function") {
let result = this.onBeforeTextValueGet();
if (result) {
return result.value;
}
}
return this.value;
}
/**
* =================== nsIDOMXULMenuListElement ===================
*/
get editable() {
return true;
}
set crop(val) {
return false;
}
get crop() {
return false;
}
set open(val) {
if (val) {
this.showHistoryPopup();
} else {
this.closePopup();
}
}
get open() {
return this.getAttribute("open") == "true";
}
set value(val) {
return this._setValueInternal(val, false);
}
get value() {
if (typeof this.onBeforeValueGet == "function") {
var result = this.onBeforeValueGet();
if (result) {
return result.value;
}
}
return super.value;
}
get focused() {
return this === document.activeElement;
}
/**
* maximum number of rows to display at a time
*/
set maxRows(val) {
this.setAttribute("maxrows", val);
return val;
}
get maxRows() {
return parseInt(this.getAttribute("maxrows")) || 0;
}
/**
* option to allow scrolling through the list via the tab key, rather than
* tab moving focus out of the textbox
*/
set tabScrolling(val) {
this.setAttribute("tabscrolling", val);
return val;
}
get tabScrolling() {
return this.getAttribute("tabscrolling") == "true";
}
/**
* option to completely ignore any blur events while searches are
* still going on.
*/
set ignoreBlurWhileSearching(val) {
this.setAttribute("ignoreblurwhilesearching", val);
return val;
}
get ignoreBlurWhileSearching() {
return this.getAttribute("ignoreblurwhilesearching") == "true";
}
/**
* option to highlight entries that don't have any matches
*/
set highlightNonMatches(val) {
this.setAttribute("highlightnonmatches", val);
return val;
}
get highlightNonMatches() {
return this.getAttribute("highlightnonmatches") == "true";
}
getSearchAt(aIndex) {
this.initSearchNames();
return this.mSearchNames[aIndex];
}
setTextValueWithReason(aValue, aReason) {
if (aReason == Ci.nsIAutoCompleteInput.TEXTVALUE_REASON_COMPLETEDEFAULT) {
this._textValueSetByCompleteDefault = true;
}
this.textValue = aValue;
this._textValueSetByCompleteDefault = false;
}
selectTextRange(aStartIndex, aEndIndex) {
super.setSelectionRange(aStartIndex, aEndIndex);
}
onSearchBegin() {
if (this.popup && typeof this.popup.onSearchBegin == "function") {
this.popup.onSearchBegin();
}
}
onSearchComplete() {
if (this.mController.matchCount == 0) {
this.setAttribute("nomatch", "true");
} else {
this.removeAttribute("nomatch");
}
if (this.ignoreBlurWhileSearching && !this.focused) {
this.handleEnter();
this.detachController();
}
}
onTextEntered(event) {
if (this.getAttribute("notifylegacyevents") === "true") {
let e = new CustomEvent("textEntered", {
bubbles: false,
cancelable: true,
detail: { rootEvent: event },
});
return !this.dispatchEvent(e);
}
return false;
}
onTextReverted(event) {
if (this.getAttribute("notifylegacyevents") === "true") {
let e = new CustomEvent("textReverted", {
bubbles: false,
cancelable: true,
detail: { rootEvent: event },
});
return !this.dispatchEvent(e);
}
return false;
}
/**
* =================== PRIVATE MEMBERS ===================
*/
/*
* ::::::::::::: autocomplete controller :::::::::::::
*/
attachController() {
this.mController.input = this.nsIAutocompleteInput;
}
detachController() {
if (this.mController.input.wrappedJSObject == this.nsIAutocompleteInput) {
this.mController.input = null;
}
}
/**
* ::::::::::::: popup opening :::::::::::::
*/
openPopup() {
if (this.focused) {
this.popup.openAutocompletePopup(this.nsIAutocompleteInput, this);
}
}
closePopup() {
this.popup.closePopup();
}
showHistoryPopup() {
// Store our "normal" maxRows on the popup, so that it can reset the
// value when the popup is hidden.
this.popup._normalMaxRows = this.maxRows;
// Increase our maxRows temporarily, since we want the dropdown to
// be bigger in this case. The popup's popupshowing/popuphiding
// handlers will take care of resetting this.
this.maxRows = this.maxDropMarkerRows;
// Ensure that we have focus.
if (!this.focused) {
this.focus();
}
this.attachController();
this.mController.startSearch("");
}
toggleHistoryPopup() {
if (!this.popup.popupOpen) {
this.showHistoryPopup();
} else {
this.closePopup();
}
}
handleKeyPress(aEvent) {
// Re: urlbarDeferred, see the comment in urlbarBindings.xml.
if (aEvent.defaultPrevented && !aEvent.urlbarDeferred) {
return false;
}
const isMac = AppConstants.platform == "macosx";
var cancel = false;
// Catch any keys that could potentially move the caret. Ctrl can be
// used in combination with these keys on Windows and Linux; and Alt
// can be used on OS X, so make sure the unused one isn't used.
let metaKey = isMac ? aEvent.ctrlKey : aEvent.altKey;
if (!metaKey) {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_LEFT:
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_HOME:
cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
break;
}
}
// Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
if (!aEvent.ctrlKey && !aEvent.altKey) {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_TAB:
if (this.tabScrolling && this.popup.popupOpen) {
cancel = this.mController.handleKeyNavigation(
aEvent.shiftKey ? KeyEvent.DOM_VK_UP : KeyEvent.DOM_VK_DOWN
);
} else if (this.forceComplete && this.mController.matchCount >= 1) {
this.mController.handleTab();
}
break;
case KeyEvent.DOM_VK_UP:
case KeyEvent.DOM_VK_DOWN:
case KeyEvent.DOM_VK_PAGE_UP:
case KeyEvent.DOM_VK_PAGE_DOWN:
cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
break;
}
}
// Handle readline/emacs-style navigation bindings on Mac.
if (
isMac &&
this.popup.popupOpen &&
aEvent.ctrlKey &&
(aEvent.key === "n" || aEvent.key === "p")
) {
const effectiveKey =
aEvent.key === "p" ? KeyEvent.DOM_VK_UP : KeyEvent.DOM_VK_DOWN;
cancel = this.mController.handleKeyNavigation(effectiveKey);
}
// Handle keys we know aren't part of a shortcut, even with Alt or
// Ctrl.
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_ESCAPE:
cancel = this.mController.handleEscape();
break;
case KeyEvent.DOM_VK_RETURN:
if (isMac) {
// Prevent the default action, since it will beep on Mac
if (aEvent.metaKey) {
aEvent.preventDefault();
}
}
if (this.popup.selectedIndex >= 0) {
this._selectionDetails = {
index: this.popup.selectedIndex,
kind: "key",
};
}
cancel = this.handleEnter(aEvent);
break;
case KeyEvent.DOM_VK_DELETE:
if (isMac && !aEvent.shiftKey) {
break;
}
cancel = this.handleDelete();
break;
case KeyEvent.DOM_VK_BACK_SPACE:
if (isMac && aEvent.shiftKey) {
cancel = this.handleDelete();
}
break;
case KeyEvent.DOM_VK_DOWN:
case KeyEvent.DOM_VK_UP:
if (aEvent.altKey) {
this.toggleHistoryPopup();
}
break;
case KeyEvent.DOM_VK_F4:
if (!isMac) {
this.toggleHistoryPopup();
}
break;
}
if (cancel) {
aEvent.stopPropagation();
aEvent.preventDefault();
}
return true;
}
handleEnter(event) {
return this.mController.handleEnter(false, event || null);
}
handleDelete() {
return this.mController.handleDelete();
}
/**
* ::::::::::::: miscellaneous :::::::::::::
*/
initSearchNames() {
if (!this.mSearchNames) {
var names = this.getAttribute("autocompletesearch");
if (!names) {
this.mSearchNames = [];
} else {
this.mSearchNames = names.split(" ");
}
}
}
_focus() {
this._dontBlur = true;
this.focus();
this._dontBlur = false;
}
resetActionType() {
if (this.mIgnoreInput) {
return;
}
this.removeAttribute("actiontype");
}
_setValueInternal(value, isUserInput) {
this.mIgnoreInput = true;
if (typeof this.onBeforeValueSet == "function") {
value = this.onBeforeValueSet(value);
}
if (
typeof this.trimValue == "function" &&
!this._textValueSetByCompleteDefault
) {
value = this.trimValue(value);
}
this.valueIsTyped = false;
if (isUserInput) {
super.setUserInput(value);
} else {
super.value = value;
}
if (typeof this.formatValue == "function") {
this.formatValue();
}
this.mIgnoreInput = false;
var event = document.createEvent("Events");
event.initEvent("ValueChange", true, true);
super.dispatchEvent(event);
return value;
}
onInput(aEvent) {
if (
!this.mIgnoreInput &&
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
this.valueIsTyped = true;
this.mController.handleText();
}
this.resetActionType();
}
}
MozHTMLElement.implementCustomInterface(AutocompleteInput, [
Ci.nsIAutoCompleteInput,
Ci.nsIDOMXULMenuListElement,
]);
customElements.define("autocomplete-input", AutocompleteInput, {
extends: "input",
});
}

View File

@ -1,595 +0,0 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="autocompleteBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="autocomplete"
extends="chrome://global/content/bindings/textbox.xml#textbox">
<content sizetopopup="pref">
<xul:moz-input-box anonid="moz-input-box" flex="1">
<html:input anonid="input" class="textbox-input"
allowevents="true"
autocomplete="off"
xbl:inherits="value,type=inputtype,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
</xul:moz-input-box>
<xul:popupset anonid="popupset" class="autocomplete-result-popupset"/>
</content>
<implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
<field name="mController">null</field>
<field name="mSearchNames">null</field>
<field name="mIgnoreInput">false</field>
<field name="noRollupOnEmptySearch">false</field>
<constructor><![CDATA[
this.mController = Cc["@mozilla.org/autocomplete/controller;1"].
getService(Ci.nsIAutoCompleteController);
]]></constructor>
<!-- =================== nsIAutoCompleteInput =================== -->
<field name="_popup">null</field>
<property name="popup" readonly="true">
<getter><![CDATA[
// Memoize the result in a field rather than replacing this property,
// so that it can be reset along with the binding.
if (this._popup) {
return this._popup;
}
let popup = null;
let popupId = this.getAttribute("autocompletepopup");
if (popupId) {
popup = document.getElementById(popupId);
}
if (!popup) {
popup = document.createXULElement("panel", { is: "autocomplete-richlistbox-popup" });
popup.setAttribute("type", "autocomplete-richlistbox");
popup.setAttribute("noautofocus", "true");
let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
popupset.appendChild(popup);
}
popup.mInput = this;
return this._popup = popup;
]]></getter>
</property>
<property name="popupElement" readonly="true"
onget="return this.popup;"/>
<property name="controller" onget="return this.mController;" readonly="true"/>
<property name="popupOpen"
onget="return this.popup.popupOpen;"
onset="if (val) this.openPopup(); else this.closePopup();"/>
<property name="disableAutoComplete"
onset="this.setAttribute('disableautocomplete', val); return val;"
onget="return this.getAttribute('disableautocomplete') == 'true';"/>
<property name="completeDefaultIndex"
onset="this.setAttribute('completedefaultindex', val); return val;"
onget="return this.getAttribute('completedefaultindex') == 'true';"/>
<property name="completeSelectedIndex"
onset="this.setAttribute('completeselectedindex', val); return val;"
onget="return this.getAttribute('completeselectedindex') == 'true';"/>
<property name="forceComplete"
onset="this.setAttribute('forcecomplete', val); return val;"
onget="return this.getAttribute('forcecomplete') == 'true';"/>
<property name="minResultsForPopup"
onset="this.setAttribute('minresultsforpopup', val); return val;"
onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
<property name="timeout"
onset="this.setAttribute('timeout', val); return val;"
onget="var t = parseInt(this.getAttribute('timeout')); return isNaN(t) ? 50 : t;"/>
<property name="searchParam"
onget="return this.getAttribute('autocompletesearchparam') || '';"
onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
<property name="searchCount" readonly="true"
onget="this.initSearchNames(); return this.mSearchNames.length;"/>
<property name="PrivateBrowsingUtils" readonly="true">
<getter><![CDATA[
let module = {};
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", module);
Object.defineProperty(this, "PrivateBrowsingUtils", {
configurable: true,
enumerable: true,
writable: true,
value: module.PrivateBrowsingUtils,
});
return module.PrivateBrowsingUtils;
]]></getter>
</property>
<property name="inPrivateContext" readonly="true"
onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>
<property name="noRollupOnCaretMove" readonly="true"
onget="return this.popup.getAttribute('norolluponanchor') == 'true'"/>
<!-- This is the maximum number of drop-down rows we get when we
hit the drop marker beside fields that have it (like the URLbar).-->
<field name="maxDropMarkerRows" readonly="true">14</field>
<method name="getSearchAt">
<parameter name="aIndex"/>
<body><![CDATA[
this.initSearchNames();
return this.mSearchNames[aIndex];
]]></body>
</method>
<method name="setTextValueWithReason">
<parameter name="aValue"/>
<parameter name="aReason"/>
<body><![CDATA[
if (aReason == Ci.nsIAutoCompleteInput
.TEXTVALUE_REASON_COMPLETEDEFAULT) {
this._textValueSetByCompleteDefault = true;
}
this.textValue = aValue;
this._textValueSetByCompleteDefault = false;
]]></body>
</method>
<property name="textValue">
<getter><![CDATA[
if (typeof this.onBeforeTextValueGet == "function") {
let result = this.onBeforeTextValueGet();
if (result) {
return result.value;
}
}
return this.value;
]]></getter>
<setter><![CDATA[
if (typeof this.onBeforeTextValueSet == "function" &&
!this._textValueSetByCompleteDefault) {
val = this.onBeforeTextValueSet(val);
}
// "input" event is automatically dispatched by the editor if
// necessary.
this._setValueInternal(val, true);
return this.value;
]]></setter>
</property>
<method name="selectTextRange">
<parameter name="aStartIndex"/>
<parameter name="aEndIndex"/>
<body><![CDATA[
this.inputField.setSelectionRange(aStartIndex, aEndIndex);
]]></body>
</method>
<method name="onSearchBegin">
<body><![CDATA[
if (this.popup && typeof this.popup.onSearchBegin == "function")
this.popup.onSearchBegin();
]]></body>
</method>
<method name="onSearchComplete">
<body><![CDATA[
if (this.mController.matchCount == 0)
this.setAttribute("nomatch", "true");
else
this.removeAttribute("nomatch");
if (this.ignoreBlurWhileSearching && !this.focused) {
this.handleEnter();
this.detachController();
}
]]></body>
</method>
<method name="onTextEntered">
<parameter name="event"/>
<body><![CDATA[
if (this.getAttribute("notifylegacyevents") === "true") {
let e = new CustomEvent("textEntered", {
bubbles: false,
cancelable: true,
detail: { rootEvent: event }
});
return !this.dispatchEvent(e);
}
]]></body>
</method>
<method name="onTextReverted">
<body><![CDATA[
if (this.getAttribute("notifylegacyevents") === "true") {
let e = new CustomEvent("textReverted", {
bubbles: false,
cancelable: true
});
return !this.dispatchEvent(e);
}
]]></body>
</method>
<!-- =================== nsIDOMXULMenuListElement =================== -->
<property name="editable" readonly="true"
onget="return true;" />
<property name="crop"
onset="this.setAttribute('crop',val); return val;"
onget="return this.getAttribute('crop');"/>
<property name="open"
onget="return this.getAttribute('open') == 'true';">
<setter><![CDATA[
if (val)
this.showHistoryPopup();
else
this.closePopup();
]]></setter>
</property>
<!-- =================== PUBLIC MEMBERS =================== -->
<field name="valueIsTyped">false</field>
<field name="_textValueSetByCompleteDefault">false</field>
<property name="value"
onset="return this._setValueInternal(val, false);">
<getter><![CDATA[
if (typeof this.onBeforeValueGet == "function") {
var result = this.onBeforeValueGet();
if (result)
return result.value;
}
return this.inputField.value;
]]></getter>
</property>
<property name="focused" readonly="true"
onget="return this.getAttribute('focused') == 'true';"/>
<!-- maximum number of rows to display at a time -->
<property name="maxRows"
onset="this.setAttribute('maxrows', val); return val;"
onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
<!-- option to allow scrolling through the list via the tab key, rather than
tab moving focus out of the textbox -->
<property name="tabScrolling"
onset="this.setAttribute('tabscrolling', val); return val;"
onget="return this.getAttribute('tabscrolling') == 'true';"/>
<!-- option to completely ignore any blur events while searches are
still going on. -->
<property name="ignoreBlurWhileSearching"
onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
<!-- option to highlight entries that don't have any matches -->
<property name="highlightNonMatches"
onset="this.setAttribute('highlightnonmatches', val); return val;"
onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
<!-- =================== PRIVATE MEMBERS =================== -->
<!-- ::::::::::::: autocomplete controller ::::::::::::: -->
<method name="attachController">
<body><![CDATA[
this.mController.input = this;
]]></body>
</method>
<method name="detachController">
<body><![CDATA[
if (this.mController.input == this)
this.mController.input = null;
]]></body>
</method>
<!-- ::::::::::::: popup opening ::::::::::::: -->
<method name="openPopup">
<body><![CDATA[
if (this.focused)
this.popup.openAutocompletePopup(this, this);
]]></body>
</method>
<method name="closePopup">
<body><![CDATA[
this.popup.closePopup();
]]></body>
</method>
<method name="showHistoryPopup">
<body><![CDATA[
// Store our "normal" maxRows on the popup, so that it can reset the
// value when the popup is hidden.
this.popup._normalMaxRows = this.maxRows;
// Increase our maxRows temporarily, since we want the dropdown to
// be bigger in this case. The popup's popupshowing/popuphiding
// handlers will take care of resetting this.
this.maxRows = this.maxDropMarkerRows;
// Ensure that we have focus.
if (!this.focused)
this.focus();
this.attachController();
this.mController.startSearch("");
]]></body>
</method>
<method name="toggleHistoryPopup">
<body><![CDATA[
if (!this.popup.popupOpen)
this.showHistoryPopup();
else
this.closePopup();
]]></body>
</method>
<!-- ::::::::::::: key handling ::::::::::::: -->
<field name="_selectionDetails">null</field>
<method name="onKeyPress">
<parameter name="aEvent"/>
<body><![CDATA[
return this.handleKeyPress(aEvent);
]]></body>
</method>
<method name="handleKeyPress">
<parameter name="aEvent"/>
<parameter name="aOptions"/>
<body><![CDATA[
if (aEvent.target.localName != "textbox")
return true; // Let child buttons of autocomplete take input
// Re: urlbarDeferred, see the comment in urlbarBindings.xml.
if (aEvent.defaultPrevented && !aEvent.urlbarDeferred) {
return false;
}
const isMac = /Mac/.test(navigator.platform);
var cancel = false;
// Catch any keys that could potentially move the caret. Ctrl can be
// used in combination with these keys on Windows and Linux; and Alt
// can be used on OS X, so make sure the unused one isn't used.
let metaKey = isMac ? aEvent.ctrlKey : aEvent.altKey;
if (!metaKey) {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_LEFT:
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_HOME:
cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
break;
}
}
// Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
if (!aEvent.ctrlKey && !aEvent.altKey) {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_TAB:
if (this.tabScrolling && this.popup.popupOpen)
cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
KeyEvent.DOM_VK_UP :
KeyEvent.DOM_VK_DOWN);
else if (this.forceComplete && this.mController.matchCount >= 1)
this.mController.handleTab();
break;
case KeyEvent.DOM_VK_UP:
case KeyEvent.DOM_VK_DOWN:
case KeyEvent.DOM_VK_PAGE_UP:
case KeyEvent.DOM_VK_PAGE_DOWN:
cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
break;
}
}
// Handle readline/emacs-style navigation bindings on Mac.
if (isMac &&
this.popup.popupOpen &&
aEvent.ctrlKey &&
(aEvent.key === "n" || aEvent.key === "p")) {
const effectiveKey = (aEvent.key === "p") ?
KeyEvent.DOM_VK_UP :
KeyEvent.DOM_VK_DOWN;
cancel = this.mController.handleKeyNavigation(effectiveKey);
}
// Handle keys we know aren't part of a shortcut, even with Alt or
// Ctrl.
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_ESCAPE:
cancel = this.mController.handleEscape();
break;
case KeyEvent.DOM_VK_RETURN:
if (isMac) {
// Prevent the default action, since it will beep on Mac
if (aEvent.metaKey)
aEvent.preventDefault();
}
if (this.popup.selectedIndex >= 0) {
this._selectionDetails = {
index: this.popup.selectedIndex,
kind: "key",
};
}
cancel = this.handleEnter(aEvent, aOptions);
break;
case KeyEvent.DOM_VK_DELETE:
if (isMac && !aEvent.shiftKey) {
break;
}
cancel = this.handleDelete();
break;
case KeyEvent.DOM_VK_BACK_SPACE:
if (isMac && aEvent.shiftKey) {
cancel = this.handleDelete();
}
break;
case KeyEvent.DOM_VK_DOWN:
case KeyEvent.DOM_VK_UP:
if (aEvent.altKey)
this.toggleHistoryPopup();
break;
case KeyEvent.DOM_VK_F4:
if (!isMac) {
this.toggleHistoryPopup();
}
break;
}
if (cancel) {
aEvent.stopPropagation();
aEvent.preventDefault();
}
return true;
]]></body>
</method>
<method name="handleEnter">
<parameter name="event"/>
<body><![CDATA[
return this.mController.handleEnter(false, event || null);
]]></body>
</method>
<method name="handleDelete">
<body><![CDATA[
return this.mController.handleDelete();
]]></body>
</method>
<!-- ::::::::::::: miscellaneous ::::::::::::: -->
<method name="initSearchNames">
<body><![CDATA[
if (!this.mSearchNames) {
var names = this.getAttribute("autocompletesearch");
if (!names)
this.mSearchNames = [];
else
this.mSearchNames = names.split(" ");
}
]]></body>
</method>
<method name="_focus">
<!-- doesn't reset this.mController -->
<body><![CDATA[
this._dontBlur = true;
this.focus();
this._dontBlur = false;
]]></body>
</method>
<method name="resetActionType">
<body><![CDATA[
if (this.mIgnoreInput)
return;
this.removeAttribute("actiontype");
]]></body>
</method>
<method name="_setValueInternal">
<parameter name="aValue"/>
<parameter name="aIsUserInput"/>
<body><![CDATA[
this.mIgnoreInput = true;
if (typeof this.onBeforeValueSet == "function")
aValue = this.onBeforeValueSet(aValue);
if (typeof this.trimValue == "function" &&
!this._textValueSetByCompleteDefault)
aValue = this.trimValue(aValue);
this.valueIsTyped = false;
if (aIsUserInput) {
this.inputField.setUserInput(aValue);
} else {
this.inputField.value = aValue;
}
if (typeof this.formatValue == "function")
this.formatValue();
this.mIgnoreInput = false;
var event = document.createEvent("Events");
event.initEvent("ValueChange", true, true);
this.inputField.dispatchEvent(event);
return aValue;
]]></body>
</method>
<method name="onInput">
<parameter name="aEvent"/>
<body><![CDATA[
if (!this.mIgnoreInput && this.mController.input == this) {
this.valueIsTyped = true;
this.mController.handleText();
}
this.resetActionType();
]]></body>
</method>
</implementation>
<handlers>
<handler event="input"><![CDATA[
this.onInput(event);
]]></handler>
<handler event="keypress" phase="capturing" group="system"
action="return this.onKeyPress(event);"/>
<handler event="compositionstart" phase="capturing"
action="if (this.mController.input == this) this.mController.handleStartComposition();"/>
<handler event="compositionend" phase="capturing"
action="if (this.mController.input == this) this.mController.handleEndComposition();"/>
<handler event="focus" phase="capturing"><![CDATA[
this.attachController();
if (window.gBrowser && window.gBrowser.selectedBrowser.hasAttribute("usercontextid")) {
this.userContextId = parseInt(window.gBrowser.selectedBrowser.getAttribute("usercontextid"));
} else {
this.userContextId = 0;
}
]]></handler>
<handler event="blur" phase="capturing"><![CDATA[
if (!this._dontBlur) {
if (this.forceComplete && this.mController.matchCount >= 1) {
// If forceComplete is requested, we need to call the enter processing
// on blur so the input will be forced to the closest match.
// Thunderbird is the only consumer of forceComplete and this is used
// to force an recipient's email to the exact address book entry.
this.mController.handleEnter(true);
}
if (!this.ignoreBlurWhileSearching)
this.detachController();
}
]]></handler>
</handlers>
</binding>
</bindings>

View File

@ -475,10 +475,6 @@ textbox[is="search-textbox"] {
/* SeaMonkey uses its own autocomplete and the toolkit's autocomplete widget */
%ifndef MOZ_SUITE
textbox[type="autocomplete"] {
-moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete");
}
panel[type="autocomplete-richlistbox"] {
-moz-binding: none;
}

View File

@ -11,7 +11,11 @@
/* ::::: autocomplete ::::: */
textbox[nomatch="true"][highlightnonmatches="true"] {
html|input[is="autocomplete-input"] {
margin: 2px 4px; /* matches xul <textbox> default margin */
}
html|input[nomatch="true"][highlightnonmatches="true"] {
color: red;
}

View File

@ -5,7 +5,11 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
textbox[nomatch="true"][highlightnonmatches="true"] {
html|input[is="autocomplete-input"] {
margin: 4px; /* matches xul <textbox> default margin */
}
html|input[nomatch="true"][highlightnonmatches="true"] {
color: red;
}

View File

@ -11,7 +11,11 @@
/* ::::: autocomplete ::::: */
textbox[nomatch="true"][highlightnonmatches="true"] {
html|input[is="autocomplete-input"] {
margin: 2px 4px; /* matches xul <textbox> default margin */
}
html|input[nomatch="true"][highlightnonmatches="true"] {
color: red;
}