forked from mirrors/gecko-dev
		
	This supports the fact that devtools, which is hosted in an "about:" page, does not use inline styles. They are prevented from working by the CSP defined at devtools/client/framework/toolbox.xhtml. This patch moves The styles that are added by the MozPanel.initialize method into the popup.css files for each theme, where the other .panel-arrowcontent class selectors live. Differential Revision: https://phabricator.services.mozilla.com/D78832
		
			
				
	
	
		
			310 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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 MozPanel extends MozElements.MozElementMixin(XULPopupElement) {
 | 
						|
    static get markup() {
 | 
						|
      return `
 | 
						|
      <html:link rel="stylesheet" href="chrome://global/skin/global.css"/>
 | 
						|
      <vbox class="panel-arrowcontainer" flex="1">
 | 
						|
        <box class="panel-arrowbox" part="arrowbox">
 | 
						|
          <image class="panel-arrow" part="arrow"/>
 | 
						|
        </box>
 | 
						|
        <box class="panel-arrowcontent" flex="1" part="arrowcontent"><html:slot/></box>
 | 
						|
      </vbox>
 | 
						|
      `;
 | 
						|
    }
 | 
						|
    constructor() {
 | 
						|
      super();
 | 
						|
 | 
						|
      this.attachShadow({ mode: "open" });
 | 
						|
 | 
						|
      this._prevFocus = 0;
 | 
						|
      this._fadeTimer = null;
 | 
						|
 | 
						|
      this.addEventListener("popupshowing", this);
 | 
						|
      this.addEventListener("popupshown", this);
 | 
						|
      this.addEventListener("popuphiding", this);
 | 
						|
      this.addEventListener("popuphidden", this);
 | 
						|
      this.addEventListener("popuppositioned", this);
 | 
						|
    }
 | 
						|
 | 
						|
    connectedCallback() {
 | 
						|
      // Create shadow DOM lazily if a panel is hidden. It helps to reduce
 | 
						|
      // cycles on startup.
 | 
						|
      if (!this.hidden) {
 | 
						|
        this.initialize();
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.isArrowPanel) {
 | 
						|
        if (!this.hasAttribute("flip")) {
 | 
						|
          this.setAttribute("flip", "both");
 | 
						|
        }
 | 
						|
        if (!this.hasAttribute("side")) {
 | 
						|
          this.setAttribute("side", "top");
 | 
						|
        }
 | 
						|
        if (!this.hasAttribute("position")) {
 | 
						|
          this.setAttribute("position", "bottomcenter topleft");
 | 
						|
        }
 | 
						|
        if (!this.hasAttribute("consumeoutsideclicks")) {
 | 
						|
          this.setAttribute("consumeoutsideclicks", "false");
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    initialize() {
 | 
						|
      // As an optimization, we don't slot contents if the panel is [hidden] in
 | 
						|
      // connecetedCallack this means we can avoid running this code at startup
 | 
						|
      // and only need to do it when a panel is about to be shown.
 | 
						|
      // We then override the `hidden` setter and `removeAttribute` and call this
 | 
						|
      // function if the node is about to be shown.
 | 
						|
      if (this.shadowRoot.firstChild) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!this.isArrowPanel) {
 | 
						|
        this.shadowRoot.appendChild(document.createElement("slot"));
 | 
						|
      } else {
 | 
						|
        this.shadowRoot.appendChild(this.constructor.fragment);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    get hidden() {
 | 
						|
      return super.hidden;
 | 
						|
    }
 | 
						|
    set hidden(v) {
 | 
						|
      if (!v) {
 | 
						|
        this.initialize();
 | 
						|
      }
 | 
						|
      return (super.hidden = v);
 | 
						|
    }
 | 
						|
 | 
						|
    removeAttribute(name) {
 | 
						|
      if (name == "hidden") {
 | 
						|
        this.initialize();
 | 
						|
      }
 | 
						|
      super.removeAttribute(name);
 | 
						|
    }
 | 
						|
 | 
						|
    get isArrowPanel() {
 | 
						|
      return this.getAttribute("type") == "arrow";
 | 
						|
    }
 | 
						|
 | 
						|
    adjustArrowPosition() {
 | 
						|
      if (!this.isArrowPanel) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      var anchor = this.anchorNode;
 | 
						|
      if (!anchor) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      var container = this.shadowRoot.querySelector(".panel-arrowcontainer");
 | 
						|
      var arrowbox = this.shadowRoot.querySelector(".panel-arrowbox");
 | 
						|
 | 
						|
      var position = this.alignmentPosition;
 | 
						|
      var offset = this.alignmentOffset;
 | 
						|
 | 
						|
      this.setAttribute("arrowposition", position);
 | 
						|
 | 
						|
      if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
 | 
						|
        container.setAttribute("orient", "horizontal");
 | 
						|
        arrowbox.setAttribute("orient", "vertical");
 | 
						|
        if (position.indexOf("_after") > 0) {
 | 
						|
          arrowbox.setAttribute("pack", "end");
 | 
						|
        } else {
 | 
						|
          arrowbox.setAttribute("pack", "start");
 | 
						|
        }
 | 
						|
        arrowbox.style.transform = "translate(0, " + -offset + "px)";
 | 
						|
 | 
						|
        // The assigned side stays the same regardless of direction.
 | 
						|
        var isRTL = window.getComputedStyle(this).direction == "rtl";
 | 
						|
 | 
						|
        if (position.indexOf("start_") == 0) {
 | 
						|
          container.style.MozBoxDirection = "reverse";
 | 
						|
          this.setAttribute("side", isRTL ? "left" : "right");
 | 
						|
        } else {
 | 
						|
          container.style.removeProperty("-moz-box-direction");
 | 
						|
          this.setAttribute("side", isRTL ? "right" : "left");
 | 
						|
        }
 | 
						|
      } else if (
 | 
						|
        position.indexOf("before_") == 0 ||
 | 
						|
        position.indexOf("after_") == 0
 | 
						|
      ) {
 | 
						|
        container.removeAttribute("orient");
 | 
						|
        arrowbox.removeAttribute("orient");
 | 
						|
        if (position.indexOf("_end") > 0) {
 | 
						|
          arrowbox.setAttribute("pack", "end");
 | 
						|
        } else {
 | 
						|
          arrowbox.setAttribute("pack", "start");
 | 
						|
        }
 | 
						|
        arrowbox.style.transform = "translate(" + -offset + "px, 0)";
 | 
						|
 | 
						|
        if (position.indexOf("before_") == 0) {
 | 
						|
          container.style.MozBoxDirection = "reverse";
 | 
						|
          this.setAttribute("side", "bottom");
 | 
						|
        } else {
 | 
						|
          container.style.removeProperty("-moz-box-direction");
 | 
						|
          this.setAttribute("side", "top");
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_popupshowing(event) {
 | 
						|
      if (this.isArrowPanel && event.target == this) {
 | 
						|
        var arrow = this.shadowRoot.querySelector(".panel-arrow");
 | 
						|
        arrow.hidden = this.anchorNode == null;
 | 
						|
        this.shadowRoot
 | 
						|
          .querySelector(".panel-arrowbox")
 | 
						|
          .style.removeProperty("transform");
 | 
						|
 | 
						|
        if (this.getAttribute("animate") != "false") {
 | 
						|
          this.setAttribute("animate", "open");
 | 
						|
          // the animating attribute prevents user interaction during transition
 | 
						|
          // it is removed when popupshown fires
 | 
						|
          this.setAttribute("animating", "true");
 | 
						|
        }
 | 
						|
 | 
						|
        // set fading
 | 
						|
        var fade = this.getAttribute("fade");
 | 
						|
        var fadeDelay = 0;
 | 
						|
        if (fade == "fast") {
 | 
						|
          fadeDelay = 1;
 | 
						|
        } else if (fade == "slow") {
 | 
						|
          fadeDelay = 4000;
 | 
						|
        }
 | 
						|
 | 
						|
        if (fadeDelay != 0) {
 | 
						|
          this._fadeTimer = setTimeout(
 | 
						|
            () => this.hidePopup(true),
 | 
						|
            fadeDelay,
 | 
						|
            this
 | 
						|
          );
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // Capture the previous focus before has a chance to get set inside the panel
 | 
						|
      try {
 | 
						|
        this._prevFocus = Cu.getWeakReference(
 | 
						|
          document.commandDispatcher.focusedElement
 | 
						|
        );
 | 
						|
        if (!this._prevFocus.get()) {
 | 
						|
          this._prevFocus = Cu.getWeakReference(document.activeElement);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      } catch (ex) {
 | 
						|
        this._prevFocus = Cu.getWeakReference(document.activeElement);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_popupshown(event) {
 | 
						|
      if (this.isArrowPanel && event.target == this) {
 | 
						|
        this.removeAttribute("animating");
 | 
						|
        this.setAttribute("panelopen", "true");
 | 
						|
      }
 | 
						|
 | 
						|
      // Fire event for accessibility APIs
 | 
						|
      let alertEvent = document.createEvent("Events");
 | 
						|
      alertEvent.initEvent("AlertActive", true, true);
 | 
						|
      this.dispatchEvent(alertEvent);
 | 
						|
    }
 | 
						|
 | 
						|
    on_popuphiding(event) {
 | 
						|
      if (this.isArrowPanel && event.target == this) {
 | 
						|
        let animate = this.getAttribute("animate") != "false";
 | 
						|
 | 
						|
        if (this._fadeTimer) {
 | 
						|
          clearTimeout(this._fadeTimer);
 | 
						|
          if (animate) {
 | 
						|
            this.setAttribute("animate", "fade");
 | 
						|
          }
 | 
						|
        } else if (animate) {
 | 
						|
          this.setAttribute("animate", "cancel");
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      try {
 | 
						|
        this._currentFocus = document.commandDispatcher.focusedElement;
 | 
						|
      } catch (e) {
 | 
						|
        this._currentFocus = document.activeElement;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_popuphidden(event) {
 | 
						|
      if (this.isArrowPanel && event.target == this) {
 | 
						|
        this.removeAttribute("panelopen");
 | 
						|
        if (this.getAttribute("animate") != "false") {
 | 
						|
          this.removeAttribute("animate");
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      function doFocus() {
 | 
						|
        // Focus was set on an element inside this panel,
 | 
						|
        // so we need to move it back to where it was previously.
 | 
						|
        // Elements can use refocused-by-panel to change their focus behaviour
 | 
						|
        // when re-focused by a panel hiding.
 | 
						|
        prevFocus.setAttribute("refocused-by-panel", true);
 | 
						|
        try {
 | 
						|
          let fm = Services.focus;
 | 
						|
          fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
 | 
						|
        } catch (e) {
 | 
						|
          prevFocus.focus();
 | 
						|
        }
 | 
						|
        prevFocus.removeAttribute("refocused-by-panel");
 | 
						|
      }
 | 
						|
      var currentFocus = this._currentFocus;
 | 
						|
      var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
 | 
						|
      this._currentFocus = null;
 | 
						|
      this._prevFocus = null;
 | 
						|
 | 
						|
      // Avoid changing focus if focus changed while we hide the popup
 | 
						|
      // (This can happen e.g. if the popup is hiding as a result of a
 | 
						|
      // click/keypress that focused something)
 | 
						|
      let nowFocus;
 | 
						|
      try {
 | 
						|
        nowFocus = document.commandDispatcher.focusedElement;
 | 
						|
      } catch (e) {
 | 
						|
        nowFocus = document.activeElement;
 | 
						|
      }
 | 
						|
      if (nowFocus && nowFocus != currentFocus) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (prevFocus && this.getAttribute("norestorefocus") != "true") {
 | 
						|
        // Try to restore focus
 | 
						|
        try {
 | 
						|
          if (document.commandDispatcher.focusedWindow != window) {
 | 
						|
            // Focus has already been set to a window outside of this panel
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        } catch (ex) {}
 | 
						|
 | 
						|
        if (!currentFocus) {
 | 
						|
          doFocus();
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        while (currentFocus) {
 | 
						|
          if (currentFocus == this) {
 | 
						|
            doFocus();
 | 
						|
            return;
 | 
						|
          }
 | 
						|
          currentFocus = currentFocus.parentNode;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_popuppositioned(event) {
 | 
						|
      if (event.target == this) {
 | 
						|
        this.adjustArrowPosition();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  customElements.define("panel", MozPanel);
 | 
						|
}
 |