diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 4338cff6d207..e9ff2991adb5 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -849,11 +849,6 @@ toolbarspring { } } -#editBMPanel_tagsSelector { - /* override default listbox width from xul.css */ - width: auto; -} - menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result { display: none; } diff --git a/browser/base/content/webext-panels.xul b/browser/base/content/webext-panels.xul index 97bcd7c5d914..b821c6ce6510 100644 --- a/browser/base/content/webext-panels.xul +++ b/browser/base/content/webext-panels.xul @@ -5,6 +5,7 @@ # 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/. + diff --git a/browser/components/places/content/editBookmark.js b/browser/components/places/content/editBookmark.js index d21c90afe7c2..a72b733c9593 100644 --- a/browser/components/places/content/editBookmark.js +++ b/browser/components/places/content/editBookmark.js @@ -747,8 +747,6 @@ var gEditItemOverlay = { if (tagsSelectorRow.collapsed) return; - // Save the current scroll position and restore it after the rebuild. - let firstIndex = tagsSelector.getIndexOfFirstVisibleRow(); let selectedIndex = tagsSelector.selectedIndex; let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label : null; @@ -759,24 +757,22 @@ var gEditItemOverlay = { let tagsInField = this._getTagsArrayFromTagsInputField(); let allTags = PlacesUtils.tagging.allTags; - for (let tag of allTags) { - let elt = document.createElement("listitem"); - elt.setAttribute("type", "checkbox"); - elt.setAttribute("label", tag); + let fragment = document.createDocumentFragment(); + for (var i = 0; i < allTags.length; i++) { + let tag = allTags[i]; + let elt = document.createElement("richlistitem"); + elt.appendChild(document.createElement("image")); + let label = document.createElement("label"); + label.setAttribute("value", tag); + elt.appendChild(label); if (tagsInField.includes(tag)) elt.setAttribute("checked", "true"); - tagsSelector.appendChild(elt); + fragment.appendChild(elt); if (selectedTag === tag) - selectedIndex = tagsSelector.getIndexOfItem(elt); + selectedIndex = i; } + tagsSelector.appendChild(fragment); - // Restore position. - // The listbox allows to scroll only if the required offset doesn't - // overflow its capacity, thus need to adjust the index for removals. - firstIndex = - Math.min(firstIndex, - tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows()); - tagsSelector.scrollToIndex(firstIndex); if (selectedIndex >= 0 && tagsSelector.itemCount > 0) { selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1); tagsSelector.selectedIndex = selectedIndex; @@ -796,12 +792,17 @@ var gEditItemOverlay = { this._rebuildTagsSelectorList(); // This is a no-op if we've added the listener. - tagsSelector.addEventListener("CheckboxStateChange", this); + tagsSelector.addEventListener("mousedown", this); + tagsSelector.addEventListener("keypress", this); } else { expander.className = "expander-down"; expander.setAttribute("tooltiptext", expander.getAttribute("tooltiptextdown")); tagsSelectorRow.collapsed = true; + + // This is a no-op if we've removed the listener. + tagsSelector.removeEventListener("mousedown", this); + tagsSelector.removeEventListener("keypress", this); } }, @@ -845,25 +846,24 @@ var gEditItemOverlay = { }, // EventListener - handleEvent(aEvent) { - switch (aEvent.type) { - case "CheckboxStateChange": - // Update the tags field when items are checked/unchecked in the listbox - let tags = this._getTagsArrayFromTagsInputField(); - let tagCheckbox = aEvent.target; - - let curTagIndex = tags.indexOf(tagCheckbox.label); - let tagsSelector = this._element("tagsSelector"); - tagsSelector.selectedItem = tagCheckbox; - - if (tagCheckbox.checked) { - if (curTagIndex == -1) - tags.push(tagCheckbox.label); - } else if (curTagIndex != -1) { - tags.splice(curTagIndex, 1); + handleEvent(event) { + switch (event.type) { + case "mousedown": + if (event.button == 0) { + // Make sure the event is triggered on an item and not the empty space. + let item = event.target.closest("richlistbox,richlistitem"); + if (item.localName == "richlistitem") { + this.toggleItemCheckbox(item); + } + } + break; + case "keypress": + if (event.key == " ") { + let item = event.target.currentItem; + if (item) { + this.toggleItemCheckbox(item); + } } - this._element("tagsField").value = tags.join(", "); - this._updateTags(); break; case "unload": this.uninitPanel(false); @@ -871,6 +871,27 @@ var gEditItemOverlay = { } }, + toggleItemCheckbox(item) { + // Update the tags field when items are checked/unchecked in the listbox + let tags = this._getTagsArrayFromTagsInputField(); + + let curTagIndex = tags.indexOf(item.label); + let tagsSelector = this._element("tagsSelector"); + tagsSelector.selectedItem = item; + + if (!item.hasAttribute("checked")) { + item.setAttribute("checked", "true"); + if (curTagIndex == -1) + tags.push(item.label); + } else { + item.removeAttribute("checked"); + if (curTagIndex != -1) + tags.splice(curTagIndex, 1); + } + this._element("tagsField").value = tags.join(", "); + this._updateTags(); + }, + _initTagsField() { let tags; if (this._paneInfo.isURI) diff --git a/browser/components/places/content/editBookmarkPanel.inc.xul b/browser/components/places/content/editBookmarkPanel.inc.xul index 9de94dd896e3..5ae7df24e1ef 100644 --- a/browser/components/places/content/editBookmarkPanel.inc.xul +++ b/browser/components/places/content/editBookmarkPanel.inc.xul @@ -107,9 +107,10 @@ - + collapsed="true"> + parentItemRect.top < y && y < parentItemRect.bottom; + + // Partially visible items are also considered visible. + return pointInView(itemRect.top) || pointInView(itemRect.bottom); +} + add_task(async function() { await PlacesUtils.bookmarks.eraseEverything(); let tags = ["a", "b", "c", "d", "e", "f", "g", @@ -63,32 +72,30 @@ add_task(async function() { isnot(listItem, null, "Valid listItem found"); tagsSelector.ensureElementIsVisible(listItem); - let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow(); + let scrollTop = tagsSelector.scrollTop; - ok(listItem.checked, "Item is checked " + i); + ok(listItem.hasAttribute("checked"), "Item is checked " + i); let selectedTag = listItem.label; // Uncheck the tag. let promiseNotification = PlacesTestUtils.waitForNotification( "onItemChanged", (id, property) => property == "tags"); - listItem.checked = false; + EventUtils.synthesizeMouseAtCenter(listItem.firstChild, {}); await promiseNotification; - is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(), - "Scroll position did not change"); + is(scrollTop, tagsSelector.scrollTop, "Scroll position did not change"); // The listbox is rebuilt, so we have to get the new element. let newItem = tagsSelector.selectedItem; isnot(newItem, null, "Valid new listItem found"); - ok(!newItem.checked, "New listItem is unchecked " + i); + ok(!newItem.hasAttribute("checked"), "New listItem is unchecked " + i); is(newItem.label, selectedTag, "Correct tag is still selected"); // Check the tag. promiseNotification = PlacesTestUtils.waitForNotification( "onItemChanged", (id, property) => property == "tags"); - newItem.checked = true; + EventUtils.synthesizeMouseAtCenter(newItem.firstChild, {}); await promiseNotification; - is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(), - "Scroll position did not change"); + is(scrollTop, tagsSelector.scrollTop, "Scroll position did not change"); } // Remove the second bookmark, then nuke some of the tags. @@ -101,28 +108,24 @@ add_task(async function() { isnot(listItem, null, "Valid listItem found"); tagsSelector.ensureElementIsVisible(listItem); - let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()]; + let items = [...tagsSelector.children]; + let topTag = items.find(e => scrolledIntoView(e, tagsSelector)).label; - ok(listItem.checked, "Item is checked " + i); + ok(listItem.hasAttribute("checked"), "Item is checked " + i); // Uncheck the tag. let promiseNotification = PlacesTestUtils.waitForNotification( "onItemChanged", (id, property) => property == "tags"); - listItem.checked = false; + EventUtils.synthesizeMouseAtCenter(listItem.firstChild, {}); await promiseNotification; - // Ensure the first visible tag is still visible in the list. - let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow(); - let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() - 1; - let expectedTagIndex = tags.indexOf(firstVisibleTag); - ok(expectedTagIndex >= firstVisibleIndex && - expectedTagIndex <= lastVisibleIndex, - "Scroll position is correct"); - // The listbox is rebuilt, so we have to get the new element. + let topItem = [...tagsSelector.children].find(e => e.label == topTag); + ok(scrolledIntoView(topItem, tagsSelector), "Scroll position is correct"); + let newItem = tagsSelector.selectedItem; isnot(newItem, null, "Valid new listItem found"); - ok(newItem.checked, "New listItem is checked " + i); + ok(newItem.hasAttribute("checked"), "New listItem is checked " + i); is(tagsSelector.selectedItem.label, tags[Math.min(i + 1, tags.length - 2)], "The next tag is now selected"); diff --git a/browser/components/places/tests/browser/browser_editBookmark_tags_liveUpdate.js b/browser/components/places/tests/browser/browser_editBookmark_tags_liveUpdate.js index a29428cd3891..8c7a5f86ae5a 100644 --- a/browser/components/places/tests/browser/browser_editBookmark_tags_liveUpdate.js +++ b/browser/components/places/tests/browser/browser_editBookmark_tags_liveUpdate.js @@ -9,7 +9,7 @@ function checkTagsSelector(aAvailableTags, aCheckedTags) { "Found expected number of tags in the tags selector"); Array.prototype.forEach.call(children, function(aChild) { - let tag = aChild.getAttribute("label"); + let tag = aChild.querySelector("label").getAttribute("value"); ok(true, "Found tag '" + tag + "' in the selector"); ok(aAvailableTags.includes(tag), "Found expected tag"); let checked = aChild.getAttribute("checked") == "true"; diff --git a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js index e9b043e53964..b3d8e6aef00d 100644 --- a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js +++ b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js @@ -23,14 +23,6 @@ add_task(async function() { ok(checkbox.checked, "Checkbox is checked"); await checkPageScrolling(container, "checkbox"); - // Test listbox - let listbox = doc.getElementById("listbox"); - let listitem = doc.getElementById("listitem"); - listbox.focus(); - EventUtils.sendString(" "); - ok(listitem.selected, "Listitem is selected"); - await checkPageScrolling(container, "listbox"); - // Test radio let radiogroup = doc.getElementById("radiogroup"); radiogroup.focus(); diff --git a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul index 59b644c8fa24..4945102f598f 100644 --- a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul +++ b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul @@ -15,13 +15,6 @@ - - - - - - - diff --git a/browser/components/preferences/languages.js b/browser/components/preferences/languages.js index c8f417212627..2062630d3e57 100644 --- a/browser/components/preferences/languages.js +++ b/browser/components/preferences/languages.js @@ -22,18 +22,13 @@ var gLanguagesDialog = { _selectedItemID: null, - init() { - if (!this._availableLanguagesList.length) - this._loadAvailableLanguages(); - }, + onLoad() { + Preferences.get("intl.accept_languages").on("change", + () => this._readAcceptLanguages().catch(Cu.reportError)); - // Ugly hack used to trigger extra reflow in order to work around XUL bug 1194844; - // see bug 1194346. - forceReflow() { - this._activeLanguages.style.fontKerning = "none"; - setTimeout(() => { - this._activeLanguages.style.removeProperty("font-kerning"); - }, 0); + if (!this._availableLanguagesList.length) { + document.mozSubdialogReady = this._loadAvailableLanguages(); + } }, get _activeLanguages() { @@ -44,7 +39,7 @@ var gLanguagesDialog = { return document.getElementById("availableLanguages"); }, - _loadAvailableLanguages() { + async _loadAvailableLanguages() { // This is a parser for: resource://gre/res/language.properties // The file is formatted like so: // ab[-cd].accept=true|false @@ -85,7 +80,8 @@ var gLanguagesDialog = { this._availableLanguagesList.push(li); } - this._buildAvailableLanguageList(); + await this._buildAvailableLanguageList(); + await this._readAcceptLanguages(); }, async _buildAvailableLanguageList() { @@ -132,23 +128,25 @@ var gLanguagesDialog = { this._availableLanguages.setAttribute("label", this._availableLanguages.getAttribute("placeholder")); }, - async readAcceptLanguages() { + async _readAcceptLanguages() { while (this._activeLanguages.hasChildNodes()) this._activeLanguages.firstChild.remove(); var selectedIndex = 0; var preference = Preferences.get("intl.accept_languages"); if (preference.value == "") - return undefined; + return; var languages = preference.value.toLowerCase().split(/\s*,\s*/); for (var i = 0; i < languages.length; ++i) { - var listitem = document.createElement("listitem"); + var listitem = document.createElement("richlistitem"); + var label = document.createElement("label"); + listitem.appendChild(label); listitem.id = languages[i]; if (languages[i] == this._selectedItemID) selectedIndex = i; this._activeLanguages.appendChild(listitem); var localeName = this._getLocaleName(languages[i]); - document.l10n.setAttributes(listitem, "languages-code-format", { + document.l10n.setAttributes(label, "languages-active-code-format", { locale: localeName, code: languages[i], }); @@ -171,12 +169,6 @@ var gLanguagesDialog = { // Update states of accept-language list and buttons according to // privacy.resistFingerprinting and privacy.spoof_english. this.readSpoofEnglish(); - - return undefined; - }, - - writeAcceptLanguages() { - return undefined; }, onAvailableLanguageSelect() { @@ -210,7 +202,7 @@ var gLanguagesDialog = { this._availableLanguages.selectedItem = null; // Rebuild the available list with the added item removed... - this._buildAvailableLanguageList(); + this._buildAvailableLanguageList().catch(Cu.reportError); }, removeLanguage() { @@ -237,7 +229,7 @@ var gLanguagesDialog = { var preference = Preferences.get("intl.accept_languages"); preference.value = string; - this._buildAvailableLanguageList(); + this._buildAvailableLanguageList().catch(Cu.reportError); }, _getLocaleName(localeCode) { @@ -350,8 +342,3 @@ var gLanguagesDialog = { return document.getElementById("spoofEnglish").checked ? 2 : 1; } }; - -// These focus and resize handlers hack around XUL bug 1194844 -// by triggering extra reflow (see bug 1194346). -window.addEventListener("focus", () => gLanguagesDialog.forceReflow()); -window.addEventListener("resize", () => gLanguagesDialog.forceReflow()); diff --git a/browser/components/preferences/languages.xul b/browser/components/preferences/languages.xul index d33143ab2387..d3f98234f269 100644 --- a/browser/components/preferences/languages.xul +++ b/browser/components/preferences/languages.xul @@ -15,7 +15,7 @@ buttons="accept,cancel,help" persist="lastSelected screenX screenY" role="dialog" - onload="gLanguagesDialog.init();" + onload="gLanguagesDialog.onLoad();" helpTopic="prefs-languages" ondialoghelp="openPrefsHelp()"> @@ -49,11 +49,9 @@ - +