From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- toolkit/content/widgets/autocomplete-popup.js | 71 ++- .../content/widgets/autocomplete-richlistitem.js | 152 ++++++ toolkit/content/widgets/button.js | 4 +- toolkit/content/widgets/infobar.css | 5 +- toolkit/content/widgets/mach_commands.py | 13 + toolkit/content/widgets/marquee.css | 22 +- toolkit/content/widgets/marquee.js | 560 +++++++++------------ toolkit/content/widgets/menu.js | 2 +- toolkit/content/widgets/menulist.js | 14 +- toolkit/content/widgets/moz-button/moz-button.css | 3 + toolkit/content/widgets/moz-button/moz-button.mjs | 5 + toolkit/content/widgets/moz-card/moz-card.css | 56 ++- toolkit/content/widgets/moz-card/moz-card.mjs | 20 +- .../content/widgets/moz-card/moz-card.stories.mjs | 32 +- .../widgets/moz-message-bar/moz-message-bar.css | 52 +- .../widgets/moz-message-bar/moz-message-bar.mjs | 16 +- .../content/widgets/moz-page-nav/README.stories.md | 98 ++++ .../widgets/moz-page-nav/moz-page-nav-button.css | 35 +- .../content/widgets/moz-page-nav/moz-page-nav.css | 3 +- toolkit/content/widgets/moz-toggle/moz-toggle.css | 24 +- toolkit/content/widgets/notificationbox.js | 4 + toolkit/content/widgets/radio.js | 4 +- toolkit/content/widgets/richlistbox.js | 24 +- toolkit/content/widgets/tabbox.js | 4 +- toolkit/content/widgets/tree.js | 2 +- toolkit/content/widgets/wizard.js | 4 +- 26 files changed, 720 insertions(+), 509 deletions(-) create mode 100644 toolkit/content/widgets/moz-page-nav/README.stories.md (limited to 'toolkit/content/widgets') diff --git a/toolkit/content/widgets/autocomplete-popup.js b/toolkit/content/widgets/autocomplete-popup.js index a13ba1bc62..8bbd012f31 100644 --- a/toolkit/content/widgets/autocomplete-popup.js +++ b/toolkit/content/widgets/autocomplete-popup.js @@ -211,24 +211,32 @@ return -1; } - var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount; - if ( - (aReverse && aIndex == -1) || - (newIdx > aMaxRow && aIndex != aMaxRow) - ) { - newIdx = aMaxRow; - } else if ((!aReverse && aIndex == -1) || (newIdx < 0 && aIndex != 0)) { - newIdx = 0; - } + do { + var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount; + if ( + (aReverse && aIndex == -1) || + (newIdx > aMaxRow && aIndex != aMaxRow) + ) { + newIdx = aMaxRow; + } else if ((!aReverse && aIndex == -1) || (newIdx < 0 && aIndex != 0)) { + newIdx = 0; + } - if ( - (newIdx < 0 && aIndex == 0) || - (newIdx > aMaxRow && aIndex == aMaxRow) - ) { - aIndex = -1; - } else { - aIndex = newIdx; - } + if ( + (newIdx < 0 && aIndex == 0) || + (newIdx > aMaxRow && aIndex == aMaxRow) + ) { + aIndex = -1; + } else { + aIndex = newIdx; + } + + if (aIndex == -1) { + return -1; + } + } while ( + !this.richlistbox.canUserSelect(this.richlistbox.getItemAtIndex(aIndex)) + ); return aIndex; } @@ -313,12 +321,7 @@ _collapseUnusedItems() { let existingItemsCount = this.richlistbox.children.length; for (let i = this.matchCount; i < existingItemsCount; ++i) { - let item = this.richlistbox.children[i]; - - item.collapsed = true; - if (typeof item._onCollapse == "function") { - item._onCollapse(); - } + this.richlistbox.children[i].collapsed = true; } } @@ -408,10 +411,9 @@ // The styles on the list which have different structure and overrided // _adjustAcItem() are unreusable. const UNREUSEABLE_STYLES = [ - "autofill-profile", - "autofill-footer", - "autofill-clear-button", - "autofill-insecureWarning", + "autofill", + "action", + "status", "generatedPassword", "generic", "importableLearnMore", @@ -434,17 +436,14 @@ if (!reusable) { let options = null; switch (style) { - case "autofill-profile": - options = { is: "autocomplete-profile-listitem" }; - break; - case "autofill-footer": - options = { is: "autocomplete-profile-listitem-footer" }; + case "autofill": + options = { is: "autocomplete-autofill-richlistitem" }; break; - case "autofill-clear-button": - options = { is: "autocomplete-profile-listitem-clear-button" }; + case "action": + options = { is: "autocomplete-action-richlistitem" }; break; - case "autofill-insecureWarning": - options = { is: "autocomplete-creditcard-insecure-field" }; + case "status": + options = { is: "autocomplete-status-richlistitem" }; break; case "generic": options = { is: "autocomplete-two-line-richlistitem" }; diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js index fddd5b4029..b339ab1e27 100644 --- a/toolkit/content/widgets/autocomplete-richlistitem.js +++ b/toolkit/content/widgets/autocomplete-richlistitem.js @@ -522,6 +522,11 @@ return; } + let label = this.getAttribute("ac-label"); + if (label && JSON.parse(label)?.noLearnMore) { + return; + } + let baseURL = Services.urlFormatter.formatURLPref( "app.support.baseURL" ); @@ -715,6 +720,129 @@ } } + // This type has an action that is triggered when activated. The comment + // for that result should contain a fillMessageName -- the message to send -- + // and, optionally a secondary label, for example: + // { "fillMessageName": "Fill:Clear", secondary: "Second Label" } + class MozAutocompleteActionRichlistitem extends MozAutocompleteTwoLineRichlistitem { + constructor() { + super(); + this.selectedByMouseOver = true; + } + + _adjustAcItem() { + super._adjustAcItem(); + + let comment = JSON.parse(this.getAttribute("ac-label")); + this.querySelector(".line2-label").textContent = comment?.secondary || ""; + this.querySelector(".ac-site-icon").collapsed = + this.getAttribute("ac-image") == ""; + } + } + + // A row that conveys status information assigned from the status field + // within the comment associated with the selected item in the list. + class MozAutocompleteStatusRichlistitem extends MozAutocompleteTwoLineRichlistitem { + static get markup() { + return `
`; + } + + connectedCallback() { + super.connectedCallback(); + this.parentNode.addEventListener("select", this); + this.eventListenerParentNode = this.parentNode; + } + + disconnectedCallback() { + this.eventListenerParentNode?.removeEventListener("select", this); + this.eventListenerParentNode = null; + } + + handleEvent(event) { + if (event.type == "select") { + let selectedItem = event.target.selectedItem; + if (selectedItem) { + this.#setStatus(selectedItem); + } + } + } + + #setStatus(item) { + // For normal rows, use that row's comment, otherwise use the status's + // comment which serves as the default label. + let target = + !item || item instanceof MozAutocompleteActionRichlistitem + ? this + : item; + + let comment = JSON.parse(target.getAttribute("ac-comment")); + let statusBox = this.querySelector(".ac-status"); + statusBox.textContent = comment?.status || ""; + } + + _adjustAcItem() { + super._adjustAcItem(); + this.#setStatus(this); + this.setAttribute("disabled", "true"); + } + } + + class MozAutocompleteAutoFillRichlistitem extends MozAutocompleteTwoLineRichlistitem { + constructor() { + super(); + this.selectedByMouseOver = true; + } + + _adjustAcItem() { + let { primary, secondary, ariaLabel } = JSON.parse( + this.getAttribute("ac-value") + ); + + let line1Label = this.querySelector(".line1-label"); + line1Label.textContent = primary.toString(); + + let line2Label = this.querySelector(".line2-label"); + line2Label.textContent = secondary.toString(); + + if (ariaLabel) { + this.setAttribute("aria-label", ariaLabel); + } + + this.querySelector(".ac-site-icon").collapsed = + this.getAttribute("ac-image") == ""; + } + + set selected(val) { + if (val) { + this.setAttribute("selected", "true"); + } else { + this.removeAttribute("selected"); + } + + let { AutoCompleteParent } = ChromeUtils.importESModule( + "resource://gre/actors/AutoCompleteParent.sys.mjs" + ); + + let actor = AutoCompleteParent.getCurrentActor(); + if (!actor) { + return; + } + + let popup = actor.openedPopup; + + setTimeout(() => { + let selectedIndex = popup ? popup.selectedIndex : -1; + actor.manager + .getActor("FormAutofill") + .sendAsyncMessage("FormAutofill:PreviewProfile", { selectedIndex }); + }, 0); + } + + get selected() { + return this.getAttribute("selected") == "true"; + } + } + class MozAutocompleteGeneratedPasswordRichlistitem extends MozAutocompleteTwoLineRichlistitem { constructor() { super(); @@ -839,6 +967,14 @@ } ); + customElements.define( + "autocomplete-autofill-richlistitem", + MozAutocompleteAutoFillRichlistitem, + { + extends: "richlistitem", + } + ); + customElements.define( "autocomplete-login-richlistitem", MozAutocompleteLoginRichlistitem, @@ -847,6 +983,22 @@ } ); + customElements.define( + "autocomplete-action-richlistitem", + MozAutocompleteActionRichlistitem, + { + extends: "richlistitem", + } + ); + + customElements.define( + "autocomplete-status-richlistitem", + MozAutocompleteStatusRichlistitem, + { + extends: "richlistitem", + } + ); + customElements.define( "autocomplete-generated-password-richlistitem", MozAutocompleteGeneratedPasswordRichlistitem, diff --git a/toolkit/content/widgets/button.js b/toolkit/content/widgets/button.js index ce48fac1e9..7ca2eddbed 100644 --- a/toolkit/content/widgets/button.js +++ b/toolkit/content/widgets/button.js @@ -84,7 +84,7 @@ ).toLowerCase(); // If the accesskey of the current button is pressed, just activate it - if (this.accessKey.toLowerCase() == charPressedLower) { + if (this.accessKey?.toLowerCase() == charPressedLower) { this.click(); return; } @@ -201,7 +201,7 @@ while (iterator.nextNode()) { var test = iterator.currentNode; if ( - test.accessKey.toLowerCase() == aAccessKeyLower && + test.accessKey?.toLowerCase() == aAccessKeyLower && !test.disabled && !test.collapsed && !test.hidden diff --git a/toolkit/content/widgets/infobar.css b/toolkit/content/widgets/infobar.css index ee811818b5..7818f1ef1d 100644 --- a/toolkit/content/widgets/infobar.css +++ b/toolkit/content/widgets/infobar.css @@ -32,11 +32,8 @@ } .close { - margin-block: 4px; + margin-block: 2px; margin-inline-start: 8px; - background-size: 12px; - height: 24px; - width: 24px; align-self: flex-start; } } diff --git a/toolkit/content/widgets/mach_commands.py b/toolkit/content/widgets/mach_commands.py index 58a8b8fcda..79398ab11a 100644 --- a/toolkit/content/widgets/mach_commands.py +++ b/toolkit/content/widgets/mach_commands.py @@ -206,3 +206,16 @@ def addstory(command_context, name, project_name, path): html_lit_import=html_lit_import, ) ) + + +@Command( + "buildtokens", + category="misc", + description="Build the design tokens CSS files", +) +def buildtokens(command_context): + run_mach( + command_context, + "npm", + args=["run", "build", "--prefix=toolkit/themes/shared/design-system"], + ) diff --git a/toolkit/content/widgets/marquee.css b/toolkit/content/widgets/marquee.css index b898cd0dce..6cdb52ca02 100644 --- a/toolkit/content/widgets/marquee.css +++ b/toolkit/content/widgets/marquee.css @@ -2,25 +2,19 @@ * 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/. */ -.outerDiv { - overflow: hidden; - width: -moz-available; +slot { + display: block; + will-change: translate; } -.horizontal > .innerDiv { - width: max-content; - /* We want to create overflow of twice our available space. */ - padding: 0 100%; -} - -/* disable scrolling in contenteditable */ -:host(:read-write) .innerDiv { - padding: 0 !important; +/* Disable the animation on contenteditable */ +:host(:read-write) > slot { + translate: none !important; } /* When printing or when the user doesn't want movement, we disable scrolling */ @media print, (prefers-reduced-motion) { - .innerDiv { - padding: 0 !important; + slot { + translate: none !important; } } diff --git a/toolkit/content/widgets/marquee.js b/toolkit/content/widgets/marquee.js index 8b18703b92..a694ffdca1 100644 --- a/toolkit/content/widgets/marquee.js +++ b/toolkit/content/widgets/marquee.js @@ -4,87 +4,25 @@ "use strict"; -/* - * This is the class of entry. It will construct the actual implementation - * according to the value of the "direction" property. - */ this.MarqueeWidget = class { - constructor(shadowRoot) { - this.shadowRoot = shadowRoot; - this.element = shadowRoot.host; - } - - /* - * Callback called by UAWidgets right after constructor. - */ - onsetup() { - this.switchImpl(); - } - - /* - * Callback called by UAWidgetsChild wheen the direction property - * changes. - */ - onchange() { - this.switchImpl(); - } - - switchImpl() { - let newImpl; - switch (this.element.direction) { - case "up": - case "down": - newImpl = MarqueeVerticalImplWidget; - break; - case "left": - case "right": - newImpl = MarqueeHorizontalImplWidget; - break; - } - - // Skip if we are asked to load the same implementation. - // This can happen if the property is set again w/o value change. - if (this.impl && this.impl.constructor == newImpl) { - return; - } - this.teardown(); - if (newImpl) { - this.impl = new newImpl(this.shadowRoot); - this.impl.onsetup(); - } - } - - teardown() { - if (!this.impl) { - return; - } - this.impl.teardown(); - this.shadowRoot.firstChild.remove(); - delete this.impl; - } -}; - -this.MarqueeBaseImplWidget = class { constructor(shadowRoot) { this.shadowRoot = shadowRoot; this.element = shadowRoot.host; this.document = this.element.ownerDocument; this.window = this.document.defaultView; + // This needed for behavior=alternate, in order to know in which of the two + // directions we're going. + this.dirsign = 1; + this._currentLoop = this.element.loop; + this.animation = null; + this._restartScheduled = null; } onsetup() { - this.generateContent(); - - // Set up state. - this._currentDirection = this.element.direction || "left"; - this._currentLoop = this.element.loop; - this.dirsign = 1; - this.startAt = 0; - this.stopAt = 0; - this.newPosition = 0; - this.runId = 0; - this.originalHeight = 0; - this.invalidateCache = true; + // White-space isn't allowed because a marquee could be + // inside 'white-space: pre' + this.shadowRoot.innerHTML = ``; this._mutationObserver = new this.window.MutationObserver(aMutations => this._mutationActor(aMutations) @@ -92,7 +30,7 @@ this.MarqueeBaseImplWidget = class { this._mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true, - attributeFilter: ["loop", "", "behavior", "direction", "width", "height"], + attributeFilter: ["loop", "direction", "behavior"], }); // init needs to be run after the page has loaded in order to calculate @@ -108,12 +46,13 @@ this.MarqueeBaseImplWidget = class { } teardown() { + this.doStop(); this._mutationObserver.disconnect(); - this.window.clearTimeout(this.runId); this.window.removeEventListener("load", this); this.shadowRoot.removeEventListener("marquee-start", this); this.shadowRoot.removeEventListener("marquee-stop", this); + this.shadowRoot.replaceChildren(); } handleEvent(aEvent) { @@ -131,15 +70,26 @@ this.MarqueeBaseImplWidget = class { case "marquee-stop": this.doStop(); break; + case "finish": + this._animationFinished(); + break; } } - get outerDiv() { - return this.shadowRoot.firstChild; - } - - get innerDiv() { - return this.shadowRoot.getElementById("innerDiv"); + _animationFinished() { + let behavior = this.element.behavior; + let shouldLoop = + this._currentLoop > 1 || (this._currentLoop == -1 && behavior != "slide"); + if (shouldLoop) { + if (this._currentLoop > 0) { + this._currentLoop--; + } + if (behavior == "alternate") { + this.dirsign = -this.dirsign; + } + this.doStop(); + this.doStart(); + } } get scrollDelayWithTruespeed() { @@ -149,269 +99,249 @@ this.MarqueeBaseImplWidget = class { return this.element.scrollDelay; } - doStart() { - if (this.runId == 0) { - var lambda = () => this._doMove(false); - this.runId = this.window.setTimeout( - lambda, - this.scrollDelayWithTruespeed - this._deltaStartStop - ); - this._deltaStartStop = 0; - } - } - - doStop() { - if (this.runId != 0) { - this._deltaStartStop = Date.now() - this._lastMoveDate; - this.window.clearTimeout(this.runId); - } - - this.runId = 0; + get slot() { + return this.shadowRoot.lastChild; } - _fireEvent(aName, aBubbles, aCancelable) { - var e = this.document.createEvent("Events"); - e.initEvent(aName, aBubbles, aCancelable); - this.element.dispatchEvent(e); + /** + * Computes CSS-derived values needed to compute the transform of the + * contents. + * + * In particular, it measures the auto width and height of the contents, + * and the effective width and height of the marquee itself, along with its + * css directionality (which affects the effective direction). + */ + getMetrics() { + let slot = this.slot; + slot.style.width = "max-content"; + let slotCS = this.window.getComputedStyle(slot); + let marqueeCS = this.window.getComputedStyle(this.element); + let contentWidth = parseFloat(slotCS.width) || 0; + let contentHeight = parseFloat(slotCS.height) || 0; + let marqueeWidth = parseFloat(marqueeCS.width) || 0; + let marqueeHeight = parseFloat(marqueeCS.height) || 0; + slot.style.width = ""; + return { + contentWidth, + contentHeight, + marqueeWidth, + marqueeHeight, + cssDirection: marqueeCS.direction, + }; } - _doMove(aResetPosition) { - this._lastMoveDate = Date.now(); - - // invalidateCache is true at first load and whenever an attribute - // is changed - if (this.invalidateCache) { - this.invalidateCache = false; // we only want this to run once every scroll direction change - - var corrvalue = 0; - - switch (this._currentDirection) { - case "up": - case "down": { - let height = this.window.getComputedStyle(this.element).height; - this.outerDiv.style.height = height; - if (this.originalHeight > this.outerDiv.offsetHeight) { - corrvalue = this.originalHeight - this.outerDiv.offsetHeight; + /** + * Gets the layout metrics from getMetrics(), and returns an object + * describing the start, end, and axis of the animation for the given marquee + * behavior and direction. + */ + getTransformParameters({ + contentWidth, + contentHeight, + marqueeWidth, + marqueeHeight, + cssDirection, + }) { + const innerWidth = marqueeWidth - contentWidth; + const innerHeight = marqueeHeight - contentHeight; + const dir = this.element.direction; + + let start = 0; + let end = 0; + const axis = dir == "up" || dir == "down" ? "y" : "x"; + switch (this.element.behavior) { + case "alternate": + switch (dir) { + case "up": + case "down": { + if (innerHeight >= 0) { + start = innerHeight; + end = 0; + } else { + start = 0; + end = innerHeight; + } + if (dir == "down") { + [start, end] = [end, start]; + } + if (this.dirsign == -1) { + [start, end] = [end, start]; + } + break; } - this.innerDiv.style.padding = height + " 0"; - let isUp = this._currentDirection == "up"; - if (isUp) { - this.dirsign = 1; - this.startAt = - this.element.behavior == "alternate" - ? this.originalHeight - corrvalue - : 0; - this.stopAt = - this.element.behavior == "alternate" || - this.element.behavior == "slide" - ? parseInt(height) + corrvalue - : this.originalHeight + parseInt(height); - } else { - this.dirsign = -1; - this.startAt = - this.element.behavior == "alternate" - ? parseInt(height) + corrvalue - : this.originalHeight + parseInt(height); - this.stopAt = - this.element.behavior == "alternate" || - this.element.behavior == "slide" - ? this.originalHeight - corrvalue - : 0; + case "right": + case "left": + default: { + if (innerWidth >= 0) { + start = innerWidth; + end = 0; + } else { + start = 0; + end = innerWidth; + } + if (dir == "right") { + [start, end] = [end, start]; + } + if (cssDirection == "rtl") { + [start, end] = [end, start]; + } + if (this.dirsign == -1) { + [start, end] = [end, start]; + } + break; } - break; } - case "left": - case "right": - default: { - let isRight = this._currentDirection == "right"; - // NOTE: It's important to use getComputedStyle() to not account for the padding. - let innerWidth = parseInt( - this.window.getComputedStyle(this.innerDiv).width - ); - if (innerWidth > this.outerDiv.offsetWidth) { - corrvalue = innerWidth - this.outerDiv.offsetWidth; + break; + case "slide": + switch (dir) { + case "up": { + start = marqueeHeight; + end = 0; + break; } - let rtl = - this.window.getComputedStyle(this.element).direction == "rtl"; - if (isRight != rtl) { - this.dirsign = -1; - this.stopAt = - this.element.behavior == "alternate" || - this.element.behavior == "slide" - ? innerWidth - corrvalue - : 0; - this.startAt = - this.outerDiv.offsetWidth + - (this.element.behavior == "alternate" - ? corrvalue - : innerWidth + this.stopAt); - } else { - this.dirsign = 1; - this.startAt = - this.element.behavior == "alternate" ? innerWidth - corrvalue : 0; - this.stopAt = - this.outerDiv.offsetWidth + - (this.element.behavior == "alternate" || - this.element.behavior == "slide" - ? corrvalue - : innerWidth + this.startAt); + case "down": { + start = -contentHeight; + end = innerHeight; + break; } - if (rtl) { - this.startAt = -this.startAt; - this.stopAt = -this.stopAt; - this.dirsign = -this.dirsign; + case "right": + default: { + let isRight = dir == "right"; + if (cssDirection == "rtl") { + isRight = !isRight; + } + if (isRight) { + start = -contentWidth; + end = innerWidth; + } else { + start = marqueeWidth; + end = 0; + } + break; } - break; } - } - - if (aResetPosition) { - this.newPosition = this.startAt; - this._fireEvent("start", false, false); - } - } // end if - - this.newPosition = - this.newPosition + this.dirsign * this.element.scrollAmount; - - if ( - (this.dirsign == 1 && this.newPosition > this.stopAt) || - (this.dirsign == -1 && this.newPosition < this.stopAt) - ) { - switch (this.element.behavior) { - case "alternate": - // lets start afresh - this.invalidateCache = true; - - // swap direction - const swap = { left: "right", down: "up", up: "down", right: "left" }; - this._currentDirection = swap[this._currentDirection] || "left"; - this.newPosition = this.stopAt; - - if ( - this._currentDirection == "up" || - this._currentDirection == "down" - ) { - this.outerDiv.scrollTop = this.newPosition; - } else { - this.outerDiv.scrollLeft = this.newPosition; - } - - if (this._currentLoop != 1) { - this._fireEvent("bounce", false, true); - } - break; - - case "slide": - if (this._currentLoop > 1) { - this.newPosition = this.startAt; + break; + case "scroll": + default: + switch (dir) { + case "up": + case "down": { + start = marqueeHeight; + end = -contentHeight; + if (dir == "down") { + [start, end] = [end, start]; + } + break; } - break; - - default: - this.newPosition = this.startAt; - - if ( - this._currentDirection == "up" || - this._currentDirection == "down" - ) { - this.outerDiv.scrollTop = this.newPosition; - } else { - this.outerDiv.scrollLeft = this.newPosition; + case "right": + case "left": + default: { + start = marqueeWidth; + end = -contentWidth; + if (dir == "right") { + [start, end] = [end, start]; + } + if (cssDirection == "rtl") { + [start, end] = [end, start]; + } + break; } + } + break; + } + return { start, end, axis }; + } - // dispatch start event, even when this._currentLoop == 1, comp. with IE6 - this._fireEvent("start", false, false); + /** + * Measures the marquee contents, and starts the marquee animation if needed. + * The translate animation is applied to the element. + * Bouncing and looping is implemented in the finish event handler for the + * given animation (see _animationFinished()). + */ + doStart() { + if (this.animation) { + return; + } + let scrollAmount = this.element.scrollAmount; + if (!scrollAmount) { + return; + } + let metrics = this.getMetrics(); + let { axis, start, end } = this.getTransformParameters(metrics); + let duration = + (Math.abs(end - start) * this.scrollDelayWithTruespeed) / scrollAmount; + let startValue = start + "px"; + let endValue = end + "px"; + if (axis == "y") { + startValue = "0 " + startValue; + endValue = "0 " + endValue; + } + // NOTE(emilio): It seems tempting to use `iterations` here, but doing so + // wouldn't be great because this uses current layout values (via + // getMetrics()), so sizes wouldn't update. This way we update once per + // animation iteration. + // + // fill: forwards is needed so that behavior=slide doesn't jump back to the + // start after the animation finishes. + this.animation = this.slot.animate( + { + translate: [startValue, endValue], + }, + { + duration, + easing: "linear", + fill: "forwards", } + ); + this.animation.addEventListener("finish", this, { once: true }); + } - if (this._currentLoop > 1) { - this._currentLoop--; - } else if (this._currentLoop == 1) { - if ( - this._currentDirection == "up" || - this._currentDirection == "down" - ) { - this.outerDiv.scrollTop = this.stopAt; - } else { - this.outerDiv.scrollLeft = this.stopAt; - } - this.element.stop(); - this._fireEvent("finish", false, true); - return; - } - } else if ( - this._currentDirection == "up" || - this._currentDirection == "down" - ) { - this.outerDiv.scrollTop = this.newPosition; - } else { - this.outerDiv.scrollLeft = this.newPosition; + doStop() { + if (!this.animation) { + return; } - - var myThis = this; - var lambda = function myTimeOutFunction() { - myThis._doMove(false); - }; - this.runId = this.window.setTimeout(lambda, this.scrollDelayWithTruespeed); + if (this._restartScheduled) { + this.window.cancelAnimationFrame(this._restartScheduled); + this._restartScheduled = null; + } + this.animation.removeEventListener("finish", this); + this.animation.cancel(); + this.animation = null; } init() { this.element.stop(); - - if (this._currentDirection == "up" || this._currentDirection == "down") { - // store the original height before we add padding - this.innerDiv.style.padding = 0; - this.originalHeight = this.innerDiv.offsetHeight; - } - - this._doMove(true); + this.doStart(); } _mutationActor(aMutations) { while (aMutations.length) { - var mutation = aMutations.shift(); - var attrName = mutation.attributeName.toLowerCase(); - var oldValue = mutation.oldValue; - var target = mutation.target; - var newValue = target.getAttribute(attrName); - - if (oldValue != newValue) { - this.invalidateCache = true; - switch (attrName) { - case "loop": - this._currentLoop = target.loop; - break; - case "direction": - this._currentDirection = target.direction; - break; - } + let mutation = aMutations.shift(); + let attrName = mutation.attributeName.toLowerCase(); + let oldValue = mutation.oldValue; + let newValue = this.element.getAttribute(attrName); + if (oldValue == newValue) { + continue; + } + if (attrName == "loop") { + this._currentLoop = this.element.loop; + } + if (attrName == "direction" || attrName == "behavior") { + this._scheduleRestartIfNeeded(); } } } -}; - -this.MarqueeHorizontalImplWidget = class extends MarqueeBaseImplWidget { - generateContent() { - // White-space isn't allowed because a marquee could be - // inside 'white-space: pre' - this.shadowRoot.innerHTML = `
`; - } -}; -this.MarqueeVerticalImplWidget = class extends MarqueeBaseImplWidget { - generateContent() { - // White-space isn't allowed because a marquee could be - // inside 'white-space: pre' - this.shadowRoot.innerHTML = `
`; + // Schedule a restart with the new parameters if we're running. + _scheduleRestartIfNeeded() { + if (!this.animation || this._restartScheduled != null) { + return; + } + this._restartScheduled = this.window.requestAnimationFrame(() => { + if (this.animation) { + this.doStop(); + this.doStart(); + } + }); } }; diff --git a/toolkit/content/widgets/menu.js b/toolkit/content/widgets/menu.js index 1a55d799b6..70b9521a27 100644 --- a/toolkit/content/widgets/menu.js +++ b/toolkit/content/widgets/menu.js @@ -19,7 +19,7 @@ this.setAttribute("value", val); } get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } // nsIDOMXULSelectControlItemElement diff --git a/toolkit/content/widgets/menulist.js b/toolkit/content/widgets/menulist.js index 3672d4ccf1..c7bff9c9d0 100644 --- a/toolkit/content/widgets/menulist.js +++ b/toolkit/content/widgets/menulist.js @@ -124,8 +124,6 @@ this.shadowRoot.appendChild(document.createElement("slot")); } - this.mSelectedInternal = null; - this.mAttributeObserver = null; this.setInitialSelection(); } @@ -153,7 +151,7 @@ // nsIDOMXULSelectControlElement get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } // nsIDOMXULMenuListElement @@ -163,12 +161,12 @@ // nsIDOMXULMenuListElement get image() { - return this.getAttribute("image"); + return this.getAttribute("image") || ""; } // nsIDOMXULMenuListElement get label() { - return this.getAttribute("label"); + return this.getAttribute("label") || ""; } set description(val) { @@ -176,7 +174,7 @@ } get description() { - return this.getAttribute("description"); + return this.getAttribute("description") || ""; } // nsIDOMXULMenuListElement @@ -292,6 +290,10 @@ if (this.getAttribute("noinitialselection") === "true") { return; } + + this.mSelectedInternal = null; + this.mAttributeObserver = null; + var popup = this.menupopup; if (popup) { var arr = popup.getElementsByAttribute("selected", "true"); diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css index 47567df41d..71d57ea93a 100644 --- a/toolkit/content/widgets/moz-button/moz-button.css +++ b/toolkit/content/widgets/moz-button/moz-button.css @@ -4,6 +4,8 @@ :host { display: inline-block; + height: fit-content; + width: fit-content; } button { @@ -133,6 +135,7 @@ button { width: var(--button-size-icon); height: var(--button-size-icon); padding: var(--button-padding-icon); + color: var(--icon-color); &[size=small] { width: var(--button-size-icon-small); diff --git a/toolkit/content/widgets/moz-button/moz-button.mjs b/toolkit/content/widgets/moz-button/moz-button.mjs index 3e7c151e61..a951dbf5d8 100644 --- a/toolkit/content/widgets/moz-button/moz-button.mjs +++ b/toolkit/content/widgets/moz-button/moz-button.mjs @@ -68,6 +68,11 @@ export default class MozButton extends MozLitElement { } } + // Delegate clicks on host to the button element. + click() { + this.buttonEl.click(); + } + render() { return html` - ${this.type == "accordion" - ? html`
` - : ""} + ${when( + this.type == "accordion", + () => html`
` + )} + ${when( + this.icon, + () => + html`` + )} ${this.heading} `; diff --git a/toolkit/content/widgets/moz-card/moz-card.stories.mjs b/toolkit/content/widgets/moz-card/moz-card.stories.mjs index da3279b2a4..0430956a52 100644 --- a/toolkit/content/widgets/moz-card/moz-card.stories.mjs +++ b/toolkit/content/widgets/moz-card/moz-card.stories.mjs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // eslint-disable-next-line import/no-unresolved -import { html, ifDefined } from "lit.all.mjs"; +import { classMap, html, ifDefined } from "lit.all.mjs"; // eslint-disable-next-line import/no-unassigned-import import "./moz-card.mjs"; @@ -15,6 +15,8 @@ export default { fluent: ` moz-card-heading = .heading = This is the label +moz-card-heading-with-icon = + .heading = This is a card with a heading icon `, }, argTypes: { @@ -22,13 +24,27 @@ moz-card-heading = options: ["default", "accordion"], control: { type: "select" }, }, + hasHeadingIcon: { + options: [true, false], + control: { type: "select" }, + }, }, }; -const Template = ({ l10nId, content, type }) => html` -
+const Template = ({ l10nId, content, type, hasHeadingIcon }) => html` + +
@@ -86,3 +102,13 @@ CardTypeAccordion.parameters = { }, }, }; + +export const CardWithHeadingIcon = Template.bind({}); +CardWithHeadingIcon.args = { + l10nId: "moz-card-heading-with-icon", + content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Nunc velit turpis, mollis a ultricies vitae, accumsan ut augue. + In a eros ac dolor hendrerit varius et at mauris.`, + type: "default", + hasHeadingIcon: true, +}; diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.css b/toolkit/content/widgets/moz-message-bar/moz-message-bar.css index 6d35009982..fa7b08612d 100644 --- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.css +++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.css @@ -6,14 +6,7 @@ /* Icon */ --message-bar-icon-color: var(--icon-color-information); --message-bar-icon-size: var(--size-item-small); - --message-bar-icon-close-color: var(--icon-color); - --message-bar-icon-close-url: url("chrome://global/skin/icons/close-12.svg"); - - /* Button */ - --message-bar-button-size-ghost: var(--button-min-height); - --message-bar-button-border-radius-ghost: var(--button-border-radius); - --message-bar-button-background-color-ghost-hover: var(--button-background-color-hover); - --message-bar-button-background-color-ghost-active: var(--button-background-color-active); + --message-bar-icon-close-url: url("chrome://global/skin/icons/close.svg"); /* Container */ --message-bar-container-min-height: var(--size-item-large); @@ -28,12 +21,13 @@ --message-bar-text-line-height: 1.5em; /* Background */ - --message-bar-background-color: var(--color-background-information); + --message-bar-background-color: var(--background-color-information); background-color: var(--message-bar-background-color); border: var(--message-bar-border-width) solid var(--message-bar-border-color); border-radius: var(--message-bar-border-radius); color: var(--message-bar-text-color); + text-align: start; } @media (prefers-contrast) { @@ -144,31 +138,8 @@ /* Close icon styles */ -.close { +moz-button::part(button) { background-image: var(--message-bar-icon-close-url); - background-repeat: no-repeat; - background-position: center center; - -moz-context-properties: fill; - fill: currentColor; - min-width: auto; - min-height: auto; - width: var(--message-bar-button-size-ghost); - height: var(--message-bar-button-size-ghost); - margin: 0; - padding: 0; - flex-shrink: 0; -} - -.ghost-button { - border-radius: var(--message-bar-button-border-radius-ghost); -} - -.ghost-button:enabled:hover { - background-color: var(--message-bar-button-background-color-ghost-hover); -} - -.ghost-button:enabled:hover:active { - background-color: var(--message-bar-button-background-color-ghost-active); } @media not (prefers-contrast) { @@ -176,7 +147,7 @@ /* Colors from: https://www.figma.com/file/zd3B9UyknB2XNZNdrYLm2W/Outreachy?type=design&node-id=59-1921&mode=design&t=ZYS4e6pAbAlXGvun-4 */ :host([type=warning]) { - --message-bar-background-color: var(--color-background-warning); + --message-bar-background-color: var(--background-color-warning); .icon { --message-bar-icon-color: var(--icon-color-warning); @@ -184,7 +155,7 @@ } :host([type=success]) { - --message-bar-background-color: var(--color-background-success); + --message-bar-background-color: var(--background-color-success); .icon { --message-bar-icon-color: var(--icon-color-success); @@ -193,19 +164,10 @@ :host([type=error]), :host([type=critical]) { - --message-bar-background-color: var(--color-background-critical); + --message-bar-background-color: var(--background-color-critical); .icon { --message-bar-icon-color: var(--icon-color-critical); } } - - .close { - fill: var(--message-bar-icon-close-color); - } - - .ghost-button { - border: none; - background-color: transparent; - } } diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs index d83de5d29f..8f0c997149 100644 --- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs +++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs @@ -4,6 +4,8 @@ import { html, ifDefined } from "../vendor/lit.all.mjs"; import { MozLitElement } from "../lit-utils.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "chrome://global/content/elements/moz-button.mjs"; const messageTypeToIconData = { info: { @@ -41,7 +43,7 @@ const messageTypeToIconData = { * @property {string} messageL10nArgs - Any args needed for the message l10n ID. * @fires message-bar:close * Custom event indicating that message bar was closed. - * @fires message-bar:user-dismissed + * @fires message-bar:user-dismissed * Custom event indicating that message bar was dismissed by the user. */ @@ -49,7 +51,7 @@ export default class MozMessageBar extends MozLitElement { static queries = { actionsSlotEl: "slot[name=actions]", actionsEl: ".actions", - closeButtonEl: "button.close", + closeButtonEl: "moz-button.close", supportLinkSlotEl: "slot[name=support-link]", }; @@ -113,14 +115,16 @@ export default class MozMessageBar extends MozLitElement { return ""; } - closeButtonTemplate() { + closeButtonTemplate({ size } = {}) { if (this.dismissable) { return html` - + > `; } return ""; diff --git a/toolkit/content/widgets/moz-page-nav/README.stories.md b/toolkit/content/widgets/moz-page-nav/README.stories.md new file mode 100644 index 0000000000..800d446478 --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/README.stories.md @@ -0,0 +1,98 @@ +# MozPageNav + +`moz-page-nav` is a grouping of navigation buttons that is displayed at the page level, +intended to change the selected view, provide a heading, and have links to external resources. + +```html story + + +

Test 1

+
+ +

Test 2

+
+ +

Test 3

+
+
+``` + +## When to use + +* Use moz-page-nav for single-page navigation to switch between different views. +* moz-page-nav will also support footer buttons for external support links in the future (See [bug 1877826](https://bugzilla.mozilla.org/show_bug.cgi?id=1877826)) +* This component will be used in about: pages such as about:firefoxview, about:preferences, about:addons, about:debugging, etc. + +## When not to use + +* If you need a navigation menu that does not switch between views within a single page + +## Code + +The source for `moz-page-nav` and `moz-page-nav-button` can be found under +[toolkit/content/widgets/moz-page-nav](https://searchfox.org/mozilla-central/source/toolkit/content/widgets/moz-page-nav). +You can find an examples of `moz-page-nav` in use in the Firefox codebase in +[about:firefoxview](https://searchfox.org/mozilla-central/source/browser/components/firefoxview/firefoxview.html#52-87). + +`moz-page-nav` can be imported into `.html`/`.xhtml` files: + +```html + +``` + +And used as follows: + +```html + + + + + + + + +``` + +### Fluent usage + +Generally the `heading` property of +`moz-page-nav` will be provided via [Fluent attributes](https://mozilla-l10n.github.io/localizer-documentation/tools/fluent/basic_syntax.html#attributes). +To get this working you will need to specify a `data-l10n-id` as well as +`data-l10n-attrs` if you're providing a heading: + +```html + +``` + +In which case your Fluent messages will look something like this: + +``` +with-heading = + .heading = Heading text goes here +``` + +You also need to specify a `data-l10n-id` for each `moz-page-nav-button`: + +```html + +``` + +``` +with-button-text = button text goes here +``` diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css index 2975bb1a7c..5d00198d65 100644 --- a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css @@ -13,7 +13,7 @@ button { background-color: var(--page-nav-button-background-color); border: 1px solid var(--page-nav-border-color); -moz-context-properties: fill, fill-opacity; - fill: currentColor; + fill: var(--icon-color); display: grid; grid-template-columns: min-content 1fr; gap: 12px; @@ -34,34 +34,35 @@ button:hover { @media not (prefers-contrast) { button { - border-inline-start: 2px solid transparent; - border-inline-end: none; - border-block: none; + position: relative; } - button:hover, - button[selected]:hover { - background-color: var(--page-nav-button-background-color-hover); + button::before { + content: ""; + display: block; + position: absolute; + inset-block: 0; + inset-inline-start: 0; + width: 2px; + background-color: transparent; } - button[selected]:hover { - border-inline-start-color: inherit; + button[selected]::before { + background-color: var(--color-accent-primary); } - button[selected], - button[selected]:hover { - border-inline-start: 2px solid; + button[selected]:hover::before { + background-color: var(--icon-color); } - button[selected]:not(:focus-visible) { - border-start-start-radius: 0; - border-end-start-radius: 0; + button:hover, + button[selected]:hover { + background-color: var(--page-nav-button-background-color-hover); } button[selected]:not(:hover) { color: var(--color-accent-primary); background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent); - border-inline-start-color: var(--color-accent-primary); } } @@ -74,7 +75,7 @@ button:hover { button:focus-visible, button[selected]:focus-visible { outline: var(--focus-outline); - outline-offset: var(--focus-outline-offset); + outline-offset: 0; border-radius: var(--border-radius-small); } diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css index 49000f622d..8e6724b2f5 100644 --- a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css @@ -20,6 +20,7 @@ position: sticky; top: 0; height: 100vh; + display: block; width: var(--page-nav-width); @media (prefers-reduced-motion) { @@ -38,7 +39,7 @@ nav { grid-template-rows: min-content 1fr auto; gap: var(--page-nav-gap); margin-block: var(--page-nav-margin-top) var(--page-nav-margin-bottom); - height: calc(100vh - var(--page-nav-margin-top) - var(--page-nav-margin-bottom)); + height: calc(100% - var(--page-nav-margin-top) - var(--page-nav-margin-bottom)); @media (max-width: 52rem) { grid-template-rows: 1fr auto; } diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.css b/toolkit/content/widgets/moz-toggle/moz-toggle.css index 2005544181..ba98e80021 100644 --- a/toolkit/content/widgets/moz-toggle/moz-toggle.css +++ b/toolkit/content/widgets/moz-toggle/moz-toggle.css @@ -37,7 +37,7 @@ --toggle-background-color-pressed: var(--color-accent-primary); --toggle-background-color-pressed-hover: var(--color-accent-primary-hover); --toggle-background-color-pressed-active: var(--color-accent-primary-active); - --toggle-border-color: var(--border-interactive-color); + --toggle-border-color: var(--border-color-interactive); --toggle-border-color-hover: var(--toggle-border-color); --toggle-border-color-active: var(--toggle-border-color); --toggle-border-radius: var(--border-radius-circle); @@ -47,7 +47,7 @@ --toggle-dot-background-color: var(--toggle-border-color); --toggle-dot-background-color-hover: var(--toggle-dot-background-color); --toggle-dot-background-color-active: var(--toggle-dot-background-color); - --toggle-dot-background-color-on-pressed: var(--color-canvas); + --toggle-dot-background-color-on-pressed: var(--background-color-canvas); --toggle-dot-margin: 1px; --toggle-dot-height: calc(var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * var(--toggle-border-width)); --toggle-dot-width: var(--toggle-dot-height); @@ -77,7 +77,7 @@ border-color: var(--toggle-border-color); } -.toggle-button:enabled:active { +.toggle-button:enabled:hover:active { background: var(--toggle-background-color-active); border-color: var(--toggle-border-color); } @@ -92,7 +92,7 @@ border-color: transparent; } -.toggle-button[aria-pressed="true"]:enabled:active { +.toggle-button[aria-pressed="true"]:enabled:hover:active { background: var(--toggle-background-color-pressed-active); border-color: transparent; } @@ -114,7 +114,7 @@ } .toggle-button[aria-pressed="true"]:enabled:hover::before, -.toggle-button[aria-pressed="true"]:enabled:active::before { +.toggle-button[aria-pressed="true"]:enabled:hover:active::before { background-color: var(--toggle-dot-background-color-on-pressed); } @@ -149,7 +149,7 @@ border-color: var(--toggle-border-color-hover); } - .toggle-button:enabled:active { + .toggle-button:enabled:hover:active { border-color: var(--toggle-border-color-active); } @@ -163,13 +163,13 @@ border-color: var(--toggle-border-color-hover); } - .toggle-button[aria-pressed="true"]:enabled:active { + .toggle-button[aria-pressed="true"]:enabled:hover:active { background-color: var(--toggle-dot-background-color-active); border-color: var(--toggle-dot-background-color-hover); } .toggle-button:hover::before, - .toggle-button:active::before { + .toggle-button:hover:active::before { background-color: var(--toggle-dot-background-color-hover); } } @@ -181,9 +181,9 @@ --toggle-dot-background-color-active: var(--color-accent-primary-active); --toggle-dot-background-color-on-pressed: var(--button-background-color); --toggle-background-color-disabled: var(--button-background-color-disabled); - --toggle-border-color-hover: var(--border-interactive-color-hover); - --toggle-border-color-active: var(--border-interactive-color-active); - --toggle-border-color-disabled: var(--border-interactive-color-disabled); + --toggle-border-color-hover: var(--border-color-interactive-hover); + --toggle-border-color-active: var(--border-color-interactive-active); + --toggle-border-color-disabled: var(--border-color-interactive-disabled); } .toggle-button[aria-pressed="true"]:enabled::after { @@ -197,7 +197,7 @@ inset: -2px; } - .toggle-button[aria-pressed="true"]:enabled:active::after { + .toggle-button[aria-pressed="true"]:enabled:hover:active::after { border-color: var(--toggle-border-color-active); } } diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js index 0161588853..8cfc7b865c 100644 --- a/toolkit/content/widgets/notificationbox.js +++ b/toolkit/content/widgets/notificationbox.js @@ -663,6 +663,10 @@ } } + closeButtonTemplate() { + return super.closeButtonTemplate({ size: "small" }); + } + #setStyles() { let style = document.createElement("link"); style.rel = "stylesheet"; diff --git a/toolkit/content/widgets/radio.js b/toolkit/content/widgets/radio.js index 41e8a945ba..52ace9bf6b 100644 --- a/toolkit/content/widgets/radio.js +++ b/toolkit/content/widgets/radio.js @@ -214,7 +214,7 @@ } get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } set disabled(val) { @@ -526,7 +526,7 @@ } get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } get selected() { diff --git a/toolkit/content/widgets/richlistbox.js b/toolkit/content/widgets/richlistbox.js index 01d970e6ed..0d669c4c5e 100644 --- a/toolkit/content/widgets/richlistbox.js +++ b/toolkit/content/widgets/richlistbox.js @@ -109,14 +109,14 @@ for (var i = 0; i < rowCount; i++) { var k = (start + i) % rowCount; var listitem = this.getItemAtIndex(k); - if (!this._canUserSelect(listitem)) { + if (!this.canUserSelect(listitem)) { continue; } // allow richlistitems to specify the string being searched for var searchText = "searchLabel" in listitem ? listitem.searchLabel - : listitem.getAttribute("label"); // (see also bug 250123) + : listitem.getAttribute("label") || ""; // (see also bug 250123) searchText = searchText.substring(0, length).toLowerCase(); if (searchText == incrementalString) { this.ensureIndexIsVisible(k); @@ -227,7 +227,7 @@ this.setAttribute("seltype", val); } get selType() { - return this.getAttribute("seltype"); + return this.getAttribute("seltype") || ""; } // nsIDOMXULSelectControlElement @@ -337,7 +337,7 @@ if ( aStartItem && aStartItem.localName == "richlistitem" && - (!this._userSelecting || this._canUserSelect(aStartItem)) + (!this._userSelecting || this.canUserSelect(aStartItem)) ) { --aDelta; if (aDelta == 0) { @@ -354,7 +354,7 @@ if ( aStartItem && aStartItem.localName == "richlistitem" && - (!this._userSelecting || this._canUserSelect(aStartItem)) + (!this._userSelecting || this.canUserSelect(aStartItem)) ) { --aDelta; if (aDelta == 0) { @@ -771,7 +771,7 @@ var newItem = this.getItemAtIndex(newIndex); // make sure that the item is actually visible/selectable - if (this._userSelecting && newItem && !this._canUserSelect(newItem)) { + if (this._userSelecting && newItem && !this.canUserSelect(newItem)) { newItem = aOffset > 0 ? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1) @@ -811,7 +811,11 @@ } } - _canUserSelect(aItem) { + canUserSelect(aItem) { + if (aItem.disabled) { + return false; + } + var style = document.defaultView.getComputedStyle(aItem); return ( style.display != "none" && @@ -886,7 +890,7 @@ */ this.addEventListener("mousedown", event => { var control = this.control; - if (!control || control.disabled) { + if (!control || this.disabled || control.disabled) { return; } if ( @@ -912,7 +916,7 @@ } var control = this.control; - if (!control || control.disabled) { + if (!control || this.disabled || control.disabled) { return; } control._userSelecting = true; @@ -977,7 +981,7 @@ } get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } /** diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js index b1b2ddecce..f4003d002c 100644 --- a/toolkit/content/widgets/tabbox.js +++ b/toolkit/content/widgets/tabbox.js @@ -459,7 +459,7 @@ } get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } get control() { @@ -563,7 +563,7 @@ } get value() { - return this.getAttribute("value"); + return this.getAttribute("value") || ""; } get tabbox() { diff --git a/toolkit/content/widgets/tree.js b/toolkit/content/widgets/tree.js index 4993bef0c2..a871d3396f 100644 --- a/toolkit/content/widgets/tree.js +++ b/toolkit/content/widgets/tree.js @@ -1078,7 +1078,7 @@ } get selType() { - return this.getAttribute("seltype"); + return this.getAttribute("seltype") || ""; } set currentIndex(val) { diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js index c4285fada5..8a3b9c164d 100644 --- a/toolkit/content/widgets/wizard.js +++ b/toolkit/content/widgets/wizard.js @@ -222,7 +222,7 @@ cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount - 1) || - (this._accessMethod == "random" && cp.next == "")) + (this._accessMethod == "random" && !cp.next)) ); } @@ -381,7 +381,7 @@ aPage.pageIndex = this.pageCount; this.pageCount += 1; if (!this._accessMethod) { - this._accessMethod = aPage.next == "" ? "sequential" : "random"; + this._accessMethod = aPage.next ? "random" : "sequential"; } if (!this._maybeStartWizard() && this._hasStarted) { // If the wizard has already started, adding a page might require -- cgit v1.2.3