summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets/button.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets/button.js')
-rw-r--r--toolkit/content/widgets/button.js312
1 files changed, 312 insertions, 0 deletions
diff --git a/toolkit/content/widgets/button.js b/toolkit/content/widgets/button.js
new file mode 100644
index 0000000000..ce48fac1e9
--- /dev/null
+++ b/toolkit/content/widgets/button.js
@@ -0,0 +1,312 @@
+/* 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.
+{
+ class MozButtonBase extends MozElements.BaseText {
+ constructor() {
+ super();
+
+ /**
+ * While it would seem we could do this by handling oncommand, we can't
+ * because any external oncommand handlers might get called before ours,
+ * and then they would see the incorrect value of checked. Additionally
+ * a command attribute would redirect the command events anyway.
+ */
+ this.addEventListener("click", event => {
+ if (event.button != 0) {
+ return;
+ }
+ this._handleClick();
+ });
+
+ this.addEventListener("keypress", event => {
+ if (event.key != " ") {
+ return;
+ }
+ this._handleClick();
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ });
+
+ this.addEventListener("keypress", event => {
+ if (this.hasMenu()) {
+ if (this.open) {
+ return;
+ }
+ } else if (!this.inRichListItem) {
+ if (
+ event.keyCode == KeyEvent.DOM_VK_UP ||
+ (event.keyCode == KeyEvent.DOM_VK_LEFT &&
+ document.defaultView.getComputedStyle(this.parentNode)
+ .direction == "ltr") ||
+ (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+ document.defaultView.getComputedStyle(this.parentNode)
+ .direction == "rtl")
+ ) {
+ event.preventDefault();
+ window.document.commandDispatcher.rewindFocus();
+ return;
+ }
+
+ if (
+ event.keyCode == KeyEvent.DOM_VK_DOWN ||
+ (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+ document.defaultView.getComputedStyle(this.parentNode)
+ .direction == "ltr") ||
+ (event.keyCode == KeyEvent.DOM_VK_LEFT &&
+ document.defaultView.getComputedStyle(this.parentNode)
+ .direction == "rtl")
+ ) {
+ event.preventDefault();
+ window.document.commandDispatcher.advanceFocus();
+ return;
+ }
+ }
+
+ if (
+ event.keyCode ||
+ event.charCode <= 32 ||
+ event.altKey ||
+ event.ctrlKey ||
+ event.metaKey
+ ) {
+ return;
+ } // No printable char pressed, not a potential accesskey
+
+ // Possible accesskey pressed
+ var charPressedLower = String.fromCharCode(
+ event.charCode
+ ).toLowerCase();
+
+ // If the accesskey of the current button is pressed, just activate it
+ if (this.accessKey.toLowerCase() == charPressedLower) {
+ this.click();
+ return;
+ }
+
+ // Search for accesskey in the list of buttons for this doc and each subdoc
+ // Get the buttons for the main document and all sub-frames
+ for (
+ var frameCount = -1;
+ frameCount < window.top.frames.length;
+ frameCount++
+ ) {
+ var doc =
+ frameCount == -1
+ ? window.top.document
+ : window.top.frames[frameCount].document;
+ if (this.fireAccessKeyButton(doc.documentElement, charPressedLower)) {
+ return;
+ }
+ }
+
+ // Test dialog buttons
+ let buttonBox = window.top.document.querySelector("dialog")?.buttonBox;
+ if (buttonBox) {
+ this.fireAccessKeyButton(buttonBox, charPressedLower);
+ }
+ });
+ }
+
+ set type(val) {
+ this.setAttribute("type", val);
+ }
+
+ get type() {
+ return this.getAttribute("type");
+ }
+
+ set disabled(val) {
+ if (val) {
+ this.setAttribute("disabled", "true");
+ } else {
+ this.removeAttribute("disabled");
+ }
+ }
+
+ get disabled() {
+ return this.getAttribute("disabled") == "true";
+ }
+
+ set group(val) {
+ this.setAttribute("group", val);
+ }
+
+ get group() {
+ return this.getAttribute("group");
+ }
+
+ set open(val) {
+ if (this.hasMenu()) {
+ this.openMenu(val);
+ } else if (val) {
+ // Fall back to just setting the attribute
+ this.setAttribute("open", "true");
+ } else {
+ this.removeAttribute("open");
+ }
+ }
+
+ get open() {
+ return this.hasAttribute("open");
+ }
+
+ set checked(val) {
+ if (this.type == "radio" && val) {
+ var sibs = this.parentNode.getElementsByAttribute("group", this.group);
+ for (var i = 0; i < sibs.length; ++i) {
+ sibs[i].removeAttribute("checked");
+ }
+ }
+
+ if (val) {
+ this.setAttribute("checked", "true");
+ } else {
+ this.removeAttribute("checked");
+ }
+ }
+
+ get checked() {
+ return this.hasAttribute("checked");
+ }
+
+ filterButtons(node) {
+ // if the node isn't visible, don't descend into it.
+ var cs = node.ownerGlobal.getComputedStyle(node);
+ if (cs.visibility != "visible" || cs.display == "none") {
+ return NodeFilter.FILTER_REJECT;
+ }
+ // but it may be a popup element, in which case we look at "state"...
+ if (XULPopupElement.isInstance(node) && node.state != "open") {
+ return NodeFilter.FILTER_REJECT;
+ }
+ // OK - the node seems visible, so it is a candidate.
+ if (node.localName == "button" && node.accessKey && !node.disabled) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ fireAccessKeyButton(aSubtree, aAccessKeyLower) {
+ var iterator = aSubtree.ownerDocument.createTreeWalker(
+ aSubtree,
+ NodeFilter.SHOW_ELEMENT,
+ this.filterButtons
+ );
+ while (iterator.nextNode()) {
+ var test = iterator.currentNode;
+ if (
+ test.accessKey.toLowerCase() == aAccessKeyLower &&
+ !test.disabled &&
+ !test.collapsed &&
+ !test.hidden
+ ) {
+ test.focus();
+ test.click();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ _handleClick() {
+ if (!this.disabled) {
+ if (this.type == "checkbox") {
+ this.checked = !this.checked;
+ } else if (this.type == "radio") {
+ this.checked = true;
+ }
+ }
+ }
+ }
+
+ MozXULElement.implementCustomInterface(MozButtonBase, [
+ Ci.nsIDOMXULButtonElement,
+ ]);
+
+ MozElements.ButtonBase = MozButtonBase;
+
+ class MozButton extends MozButtonBase {
+ static get inheritedAttributes() {
+ return {
+ ".box-inherit": "align,dir,pack,orient",
+ ".button-icon": "src=image",
+ ".button-text": "value=label,accesskey,crop",
+ ".button-menu-dropmarker": "open,disabled,label",
+ };
+ }
+
+ get icon() {
+ return this.querySelector(".button-icon");
+ }
+
+ static get buttonFragment() {
+ let frag = document.importNode(
+ MozXULElement.parseXULToFragment(`
+ <hbox class="box-inherit button-box" align="center" pack="center" flex="1" anonid="button-box">
+ <image class="button-icon"/>
+ <label class="button-text"/>
+ </hbox>`),
+ true
+ );
+ Object.defineProperty(this, "buttonFragment", { value: frag });
+ return frag;
+ }
+
+ static get menuFragment() {
+ let frag = document.importNode(
+ MozXULElement.parseXULToFragment(`
+ <hbox class="box-inherit button-box" align="center" pack="center" flex="1">
+ <hbox class="box-inherit" align="center" pack="center" flex="1">
+ <image class="button-icon"/>
+ <label class="button-text"/>
+ </hbox>
+ <dropmarker class="button-menu-dropmarker"/>
+ </hbox>`),
+ true
+ );
+ Object.defineProperty(this, "menuFragment", { value: frag });
+ return frag;
+ }
+
+ get _hasConnected() {
+ return this.querySelector(":scope > .button-box") != null;
+ }
+
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this._hasConnected) {
+ return;
+ }
+
+ let fragment;
+ if (this.type === "menu") {
+ fragment = MozButton.menuFragment;
+
+ this.addEventListener("keypress", event => {
+ if (event.keyCode != KeyEvent.DOM_VK_RETURN && event.key != " ") {
+ return;
+ }
+
+ this.open = true;
+ // Prevent page from scrolling on the space key.
+ if (event.key == " ") {
+ event.preventDefault();
+ }
+ });
+ } else {
+ fragment = this.constructor.buttonFragment;
+ }
+
+ this.appendChild(fragment.cloneNode(true));
+ this.initializeAttributeInheritance();
+ this.inRichListItem = !!this.closest("richlistitem");
+ }
+ }
+
+ customElements.define("button", MozButton);
+}