mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
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:
parent
2c2409c49e
commit
5d8a3da397
@ -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]
|
||||
|
52
accessible/tests/browser/tree/browser_searchbar.js
Normal file
52
accessible/tests/browser/tree/browser_searchbar.js
Normal 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);
|
||||
});
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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"/>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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="©Cmd.label;" accesskey="©Cmd.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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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[
|
||||
|
@ -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[
|
||||
|
@ -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[
|
||||
|
@ -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[
|
||||
|
@ -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"
|
||||
|
@ -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");
|
||||
|
@ -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[
|
||||
|
@ -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>
|
@ -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>
|
683
toolkit/content/widgets/autocomplete-input.js
Normal file
683
toolkit/content/widgets/autocomplete-input.js
Normal 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",
|
||||
});
|
||||
}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user