browser/components/customizableui/content/panelUI.xml

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/customizableui/content/panelUI.xml	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,432 @@
     1.4 +<?xml version="1.0"?>
     1.5 +<!-- This Source Code Form is subject to the terms of the Mozilla Public
     1.6 +   - License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 +   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
     1.8 +
     1.9 +<bindings id="browserPanelUIBindings"
    1.10 +          xmlns="http://www.mozilla.org/xbl"
    1.11 +          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    1.12 +          xmlns:xbl="http://www.mozilla.org/xbl">
    1.13 +
    1.14 +  <binding id="panelmultiview">
    1.15 +    <resources>
    1.16 +      <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
    1.17 +    </resources>
    1.18 +    <content>
    1.19 +      <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
    1.20 +        <xul:stack anonid="viewStack" xbl:inherits="viewtype" viewtype="main" class="panel-viewstack">
    1.21 +          <xul:vbox anonid="mainViewContainer" class="panel-mainview"/>
    1.22 +
    1.23 +          <!-- Used to capture click events over the PanelUI-mainView if we're in
    1.24 +               subview mode. That way, any click on the PanelUI-mainView causes us
    1.25 +               to revert to the mainView mode, whereupon PanelUI-click-capture then
    1.26 +               allows click events to go through it. -->
    1.27 +          <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
    1.28 +
    1.29 +          <!-- We manually set display: none (via a CSS attribute selector) on the
    1.30 +               subviews that are not being displayed. We're using this over a deck
    1.31 +               because a deck assumes the size of its largest child, regardless of
    1.32 +               whether or not it is shown. That's not good for our case, since we
    1.33 +               want to allow each subview to be uniquely sized. -->
    1.34 +          <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
    1.35 +            <children includes="panelview"/>
    1.36 +          </xul:vbox>
    1.37 +        </xul:stack>
    1.38 +      </xul:box>
    1.39 +    </content>
    1.40 +    <implementation implements="nsIDOMEventListener">
    1.41 +      <field name="_clickCapturer" readonly="true">
    1.42 +        document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
    1.43 +      </field>
    1.44 +      <field name="_viewContainer" readonly="true">
    1.45 +        document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
    1.46 +      </field>
    1.47 +      <field name="_mainViewContainer" readonly="true">
    1.48 +        document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
    1.49 +      </field>
    1.50 +      <field name="_subViews" readonly="true">
    1.51 +        document.getAnonymousElementByAttribute(this, "anonid", "subViews");
    1.52 +      </field>
    1.53 +      <field name="_viewStack" readonly="true">
    1.54 +        document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
    1.55 +      </field>
    1.56 +      <field name="_panel" readonly="true">
    1.57 +        this.parentNode;
    1.58 +      </field>
    1.59 +
    1.60 +      <field name="_currentSubView">null</field>
    1.61 +      <field name="_anchorElement">null</field>
    1.62 +      <field name="_mainViewHeight">0</field>
    1.63 +      <field name="_subViewObserver">null</field>
    1.64 +      <field name="__transitioning">false</field>
    1.65 +      <field name="_ignoreMutations">false</field>
    1.66 +
    1.67 +      <property name="showingSubView" readonly="true"
    1.68 +                onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
    1.69 +      <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
    1.70 +      <property name="_mainView" readonly="true"
    1.71 +                onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
    1.72 +      <property name="showingSubViewAsMainView" readonly="true"
    1.73 +                onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
    1.74 +
    1.75 +      <property name="ignoreMutations">
    1.76 +        <getter>
    1.77 +          return this._ignoreMutations;
    1.78 +        </getter>
    1.79 +        <setter><![CDATA[
    1.80 +          this._ignoreMutations = val;
    1.81 +          if (!val && this._panel.state == "open") {
    1.82 +            if (this.showingSubView) {
    1.83 +              this._syncContainerWithSubView();
    1.84 +            } else {
    1.85 +              this._syncContainerWithMainView();
    1.86 +            }
    1.87 +          }
    1.88 +        ]]></setter>
    1.89 +      </property>
    1.90 +
    1.91 +      <property name="_transitioning">
    1.92 +        <getter>
    1.93 +          return this.__transitioning;
    1.94 +        </getter>
    1.95 +        <setter><![CDATA[
    1.96 +          this.__transitioning = val;
    1.97 +          if (val) {
    1.98 +            this.setAttribute("transitioning", "true");
    1.99 +          } else {
   1.100 +            this.removeAttribute("transitioning");
   1.101 +          }
   1.102 +        ]]></setter>
   1.103 +      </property>
   1.104 +      <constructor><![CDATA[
   1.105 +        this._clickCapturer.addEventListener("click", this);
   1.106 +        this._panel.addEventListener("popupshowing", this);
   1.107 +        this._panel.addEventListener("popupshown", this);
   1.108 +        this._panel.addEventListener("popuphidden", this);
   1.109 +        this._subViews.addEventListener("overflow", this);
   1.110 +        this._mainViewContainer.addEventListener("overflow", this);
   1.111 +
   1.112 +        // Get a MutationObserver ready to react to subview size changes. We
   1.113 +        // only attach this MutationObserver when a subview is being displayed.
   1.114 +        this._subViewObserver =
   1.115 +          new MutationObserver(this._syncContainerWithSubView.bind(this));
   1.116 +        this._mainViewObserver =
   1.117 +          new MutationObserver(this._syncContainerWithMainView.bind(this));
   1.118 +
   1.119 +        this._mainViewContainer.setAttribute("panelid",
   1.120 +                                             this._panel.id);
   1.121 +
   1.122 +        if (this._mainView) {
   1.123 +          this.setMainView(this._mainView);
   1.124 +        }
   1.125 +        this.setAttribute("viewtype", "main");
   1.126 +      ]]></constructor>
   1.127 +
   1.128 +      <destructor><![CDATA[
   1.129 +        if (this._mainView) {
   1.130 +          this._mainView.removeAttribute("mainview");
   1.131 +        }
   1.132 +        this._mainViewObserver.disconnect();
   1.133 +        this._subViewObserver.disconnect();
   1.134 +        this._panel.removeEventListener("popupshowing", this);
   1.135 +        this._panel.removeEventListener("popupshown", this);
   1.136 +        this._panel.removeEventListener("popuphidden", this);
   1.137 +        this._subViews.removeEventListener("overflow", this);
   1.138 +        this._mainViewContainer.removeEventListener("overflow", this);
   1.139 +        this._clickCapturer.removeEventListener("click", this);
   1.140 +      ]]></destructor>
   1.141 +
   1.142 +      <method name="setMainView">
   1.143 +        <parameter name="aNewMainView"/>
   1.144 +        <body><![CDATA[
   1.145 +        if (this._mainView) {
   1.146 +          this._mainViewObserver.disconnect();
   1.147 +          this._subViews.appendChild(this._mainView);
   1.148 +          this._mainView.removeAttribute("mainview");
   1.149 +        }
   1.150 +        this._mainViewId = aNewMainView.id;
   1.151 +        aNewMainView.setAttribute("mainview", "true");
   1.152 +        this._mainViewContainer.appendChild(aNewMainView);
   1.153 +        ]]></body>
   1.154 +      </method>
   1.155 +
   1.156 +      <method name="showMainView">
   1.157 +        <body><![CDATA[
   1.158 +          if (this.showingSubView) {
   1.159 +            let viewNode = this._currentSubView;
   1.160 +            let evt = document.createEvent("CustomEvent");
   1.161 +            evt.initCustomEvent("ViewHiding", true, true, viewNode);
   1.162 +            viewNode.dispatchEvent(evt);
   1.163 +
   1.164 +            viewNode.removeAttribute("current");
   1.165 +            this._currentSubView = null;
   1.166 +
   1.167 +            this._subViewObserver.disconnect();
   1.168 +
   1.169 +            this._transitioning = true;
   1.170 +
   1.171 +            this._viewContainer.addEventListener("transitionend", function trans() {
   1.172 +              this._viewContainer.removeEventListener("transitionend", trans);
   1.173 +              this._transitioning = false;
   1.174 +            }.bind(this));
   1.175 +            this._viewContainer.style.height = this._mainViewHeight + "px";
   1.176 +
   1.177 +            this.setAttribute("viewtype", "main");
   1.178 +          }
   1.179 +
   1.180 +          this._shiftMainView();
   1.181 +        ]]></body>
   1.182 +      </method>
   1.183 +
   1.184 +      <method name="showSubView">
   1.185 +        <parameter name="aViewId"/>
   1.186 +        <parameter name="aAnchor"/>
   1.187 +        <body><![CDATA[
   1.188 +          let viewNode = this.querySelector("#" + aViewId);
   1.189 +          viewNode.setAttribute("current", true);
   1.190 +          // Emit the ViewShowing event so that the widget definition has a chance
   1.191 +          // to lazily populate the subview with things.
   1.192 +          let evt = document.createEvent("CustomEvent");
   1.193 +          evt.initCustomEvent("ViewShowing", true, true, viewNode);
   1.194 +          viewNode.dispatchEvent(evt);
   1.195 +          if (evt.defaultPrevented) {
   1.196 +            return;
   1.197 +          }
   1.198 +
   1.199 +          this._currentSubView = viewNode;
   1.200 +
   1.201 +          // Now we have to transition the panel. There are a few parts to this:
   1.202 +          //
   1.203 +          // 1) The main view content gets shifted so that the center of the anchor
   1.204 +          //    node is at the left-most edge of the panel.
   1.205 +          // 2) The subview deck slides in so that it takes up almost all of the
   1.206 +          //    panel.
   1.207 +          // 3) If the subview is taller then the main panel contents, then the panel
   1.208 +          //    must grow to meet that new height. Otherwise, it must shrink.
   1.209 +          //
   1.210 +          // All three of these actions make use of CSS transformations, so they
   1.211 +          // should all occur simultaneously.
   1.212 +          this.setAttribute("viewtype", "subview");
   1.213 +          this._shiftMainView(aAnchor);
   1.214 +
   1.215 +          this._mainViewHeight = this._viewStack.clientHeight;
   1.216 +
   1.217 +          this._transitioning = true;
   1.218 +          this._viewContainer.addEventListener("transitionend", function trans() {
   1.219 +            this._viewContainer.removeEventListener("transitionend", trans);
   1.220 +            this._transitioning = false;
   1.221 +          }.bind(this));
   1.222 +
   1.223 +          let newHeight = this._heightOfSubview(viewNode, this._subViews);
   1.224 +          this._viewContainer.style.height = newHeight + "px";
   1.225 +
   1.226 +          this._subViewObserver.observe(viewNode, {
   1.227 +            attributes: true,
   1.228 +            characterData: true,
   1.229 +            childList: true,
   1.230 +            subtree: true
   1.231 +          });
   1.232 +        ]]></body>
   1.233 +      </method>
   1.234 +
   1.235 +      <method name="_shiftMainView">
   1.236 +        <parameter name="aAnchor"/>
   1.237 +        <body><![CDATA[
   1.238 +          if (aAnchor) {
   1.239 +            // We need to find the edge of the anchor, relative to the main panel.
   1.240 +            // Then we need to add half the width of the anchor. This is the target
   1.241 +            // that we need to transition to.
   1.242 +            let anchorRect = aAnchor.getBoundingClientRect();
   1.243 +            let mainViewRect = this._mainViewContainer.getBoundingClientRect();
   1.244 +            let center = aAnchor.clientWidth / 2;
   1.245 +            let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
   1.246 +            let edge, target;
   1.247 +            if (direction == "ltr") {
   1.248 +              edge = anchorRect.left - mainViewRect.left;
   1.249 +              target = "-" + (edge + center);
   1.250 +            } else {
   1.251 +              edge = mainViewRect.right - anchorRect.right;
   1.252 +              target = edge + center;
   1.253 +            }
   1.254 +            this._mainViewContainer.style.transform = "translateX(" + target + "px)";
   1.255 +            aAnchor.setAttribute("panel-multiview-anchor", true);
   1.256 +          } else {
   1.257 +            this._mainViewContainer.style.transform = "";
   1.258 +            if (this.anchorElement)
   1.259 +              this.anchorElement.removeAttribute("panel-multiview-anchor");
   1.260 +          }
   1.261 +          this.anchorElement = aAnchor;
   1.262 +        ]]></body>
   1.263 +      </method>
   1.264 +
   1.265 +      <method name="handleEvent">
   1.266 +        <parameter name="aEvent"/>
   1.267 +        <body><![CDATA[
   1.268 +          if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
   1.269 +            // Shouldn't act on e.g. context menus being shown from within the panel.
   1.270 +            return;
   1.271 +          }
   1.272 +          switch (aEvent.type) {
   1.273 +            case "click":
   1.274 +              if (aEvent.originalTarget == this._clickCapturer) {
   1.275 +                this.showMainView();
   1.276 +              }
   1.277 +              break;
   1.278 +            case "overflow":
   1.279 +              if (aEvent.target.localName == "vbox") {
   1.280 +                // Resize the right view on the next tick.
   1.281 +                if (this.showingSubView) {
   1.282 +                  setTimeout(this._syncContainerWithSubView.bind(this), 0);
   1.283 +                } else if (!this.transitioning) {
   1.284 +                  setTimeout(this._syncContainerWithMainView.bind(this), 0);
   1.285 +                }
   1.286 +              }
   1.287 +              break;
   1.288 +            case "popupshowing":
   1.289 +              this.setAttribute("panelopen", "true");
   1.290 +              // Bug 941196 - The panel can get taller when opening a subview. Disabling
   1.291 +              // autoPositioning means that the panel won't jump around if an opened
   1.292 +              // subview causes the panel to exceed the dimensions of the screen in the
   1.293 +              // direction that the panel originally opened in. This property resets
   1.294 +              // every time the popup closes, which is why we have to set it each time.
   1.295 +              this._panel.autoPosition = false;
   1.296 +              this._syncContainerWithMainView();
   1.297 +
   1.298 +              this._mainViewObserver.observe(this._mainView, {
   1.299 +                attributes: true,
   1.300 +                characterData: true,
   1.301 +                childList: true,
   1.302 +                subtree: true
   1.303 +              });
   1.304 +
   1.305 +              break;
   1.306 +            case "popupshown":
   1.307 +              this._setMaxHeight();
   1.308 +              break;
   1.309 +            case "popuphidden":
   1.310 +              this.removeAttribute("panelopen");
   1.311 +              this._mainView.style.removeProperty("height");
   1.312 +              this.showMainView();
   1.313 +              this._mainViewObserver.disconnect();
   1.314 +              break;
   1.315 +          }
   1.316 +        ]]></body>
   1.317 +      </method>
   1.318 +
   1.319 +      <method name="_shouldSetHeight">
   1.320 +        <body><![CDATA[
   1.321 +          return this.getAttribute("nosubviews") != "true";
   1.322 +        ]]></body>
   1.323 +      </method>
   1.324 +
   1.325 +      <method name="_setMaxHeight">
   1.326 +        <body><![CDATA[
   1.327 +          if (!this._shouldSetHeight())
   1.328 +            return;
   1.329 +
   1.330 +          // Ignore the mutation that'll fire when we set the height of
   1.331 +          // the main view.
   1.332 +          this.ignoreMutations = true;
   1.333 +          this._mainView.style.height =
   1.334 +            this.getBoundingClientRect().height + "px";
   1.335 +          this.ignoreMutations = false;
   1.336 +        ]]></body>
   1.337 +      </method>
   1.338 +      <method name="_syncContainerWithSubView">
   1.339 +        <body><![CDATA[
   1.340 +          // Check that this panel is still alive:
   1.341 +          if (!this._panel || !this._panel.parentNode) {
   1.342 +            return;
   1.343 +          }
   1.344 +
   1.345 +          if (!this.ignoreMutations && this.showingSubView) {
   1.346 +            let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
   1.347 +            this._viewContainer.style.height = newHeight + "px";
   1.348 +          }
   1.349 +        ]]></body>
   1.350 +      </method>
   1.351 +      <method name="_syncContainerWithMainView">
   1.352 +        <body><![CDATA[
   1.353 +          // Check that this panel is still alive:
   1.354 +          if (!this._panel || !this._panel.parentNode || !this._shouldSetHeight()) {
   1.355 +            return;
   1.356 +          }
   1.357 +
   1.358 +          if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
   1.359 +            let height;
   1.360 +            if (this.showingSubViewAsMainView) {
   1.361 +              height = this._heightOfSubview(this._mainView);
   1.362 +            } else {
   1.363 +              height = this._mainView.scrollHeight;
   1.364 +            }
   1.365 +            this._viewContainer.style.height = height + "px";
   1.366 +          }
   1.367 +        ]]></body>
   1.368 +      </method>
   1.369 +
   1.370 +      <method name="_heightOfSubview">
   1.371 +        <parameter name="aSubview"/>
   1.372 +        <parameter name="aContainerToCheck"/>
   1.373 +        <body><![CDATA[
   1.374 +          function getFullHeight(element) {
   1.375 +            //XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
   1.376 +            // that works with overflow: auto elements. Fortunately for us,
   1.377 +            // we have exactly 1 (potentially) scrolling element in here (the subview body),
   1.378 +            // and rounding 1 value is OK - rounding more than 1 and adding them means we get
   1.379 +            // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
   1.380 +            // So, use scrollHeight *only* if the element is vertically scrollable.
   1.381 +            let height;
   1.382 +            let elementCS;
   1.383 +            if (element.scrollTopMax) {
   1.384 +              height = element.scrollHeight;
   1.385 +              // Bounding client rects include borders, scrollHeight doesn't:
   1.386 +              elementCS = win.getComputedStyle(element);
   1.387 +              height += parseFloat(elementCS.borderTopWidth) +
   1.388 +                        parseFloat(elementCS.borderBottomWidth);
   1.389 +            } else {
   1.390 +              height = element.getBoundingClientRect().height;
   1.391 +              if (height > 0) {
   1.392 +                elementCS = win.getComputedStyle(element);
   1.393 +              }
   1.394 +            }
   1.395 +            if (elementCS) {
   1.396 +              // Include margins - but not borders or paddings because they
   1.397 +              // were dealt with above.
   1.398 +              height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
   1.399 +            }
   1.400 +            return height;
   1.401 +          }
   1.402 +          let win = aSubview.ownerDocument.defaultView;
   1.403 +          let body = aSubview.querySelector(".panel-subview-body");
   1.404 +          let height = getFullHeight(body || aSubview);
   1.405 +          if (body) {
   1.406 +            let header = aSubview.querySelector(".panel-subview-header");
   1.407 +            let footer = aSubview.querySelector(".panel-subview-footer");
   1.408 +            height += (header ? getFullHeight(header) : 0) +
   1.409 +                      (footer ? getFullHeight(footer) : 0);
   1.410 +          }
   1.411 +          if (aContainerToCheck) {
   1.412 +            let containerCS = win.getComputedStyle(aContainerToCheck);
   1.413 +            height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
   1.414 +          }
   1.415 +          return Math.round(height);
   1.416 +        ]]></body>
   1.417 +      </method>
   1.418 +
   1.419 +    </implementation>
   1.420 +  </binding>
   1.421 +
   1.422 +  <binding id="panelview">
   1.423 +    <implementation>
   1.424 +      <property name="panelMultiView" readonly="true">
   1.425 +        <getter><![CDATA[
   1.426 +          if (this.parentNode.localName != "panelmultiview") {
   1.427 +            return document.getBindingParent(this.parentNode);
   1.428 +          }
   1.429 +
   1.430 +          return this.parentNode;
   1.431 +        ]]></getter>
   1.432 +      </property>
   1.433 +    </implementation>
   1.434 +  </binding>
   1.435 +</bindings>

mercurial