browser/components/customizableui/content/panelUI.xml

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 <?xml version="1.0"?>
michael@0 2 <!-- This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 - License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
michael@0 5
michael@0 6 <bindings id="browserPanelUIBindings"
michael@0 7 xmlns="http://www.mozilla.org/xbl"
michael@0 8 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
michael@0 9 xmlns:xbl="http://www.mozilla.org/xbl">
michael@0 10
michael@0 11 <binding id="panelmultiview">
michael@0 12 <resources>
michael@0 13 <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
michael@0 14 </resources>
michael@0 15 <content>
michael@0 16 <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
michael@0 17 <xul:stack anonid="viewStack" xbl:inherits="viewtype" viewtype="main" class="panel-viewstack">
michael@0 18 <xul:vbox anonid="mainViewContainer" class="panel-mainview"/>
michael@0 19
michael@0 20 <!-- Used to capture click events over the PanelUI-mainView if we're in
michael@0 21 subview mode. That way, any click on the PanelUI-mainView causes us
michael@0 22 to revert to the mainView mode, whereupon PanelUI-click-capture then
michael@0 23 allows click events to go through it. -->
michael@0 24 <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
michael@0 25
michael@0 26 <!-- We manually set display: none (via a CSS attribute selector) on the
michael@0 27 subviews that are not being displayed. We're using this over a deck
michael@0 28 because a deck assumes the size of its largest child, regardless of
michael@0 29 whether or not it is shown. That's not good for our case, since we
michael@0 30 want to allow each subview to be uniquely sized. -->
michael@0 31 <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
michael@0 32 <children includes="panelview"/>
michael@0 33 </xul:vbox>
michael@0 34 </xul:stack>
michael@0 35 </xul:box>
michael@0 36 </content>
michael@0 37 <implementation implements="nsIDOMEventListener">
michael@0 38 <field name="_clickCapturer" readonly="true">
michael@0 39 document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
michael@0 40 </field>
michael@0 41 <field name="_viewContainer" readonly="true">
michael@0 42 document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
michael@0 43 </field>
michael@0 44 <field name="_mainViewContainer" readonly="true">
michael@0 45 document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
michael@0 46 </field>
michael@0 47 <field name="_subViews" readonly="true">
michael@0 48 document.getAnonymousElementByAttribute(this, "anonid", "subViews");
michael@0 49 </field>
michael@0 50 <field name="_viewStack" readonly="true">
michael@0 51 document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
michael@0 52 </field>
michael@0 53 <field name="_panel" readonly="true">
michael@0 54 this.parentNode;
michael@0 55 </field>
michael@0 56
michael@0 57 <field name="_currentSubView">null</field>
michael@0 58 <field name="_anchorElement">null</field>
michael@0 59 <field name="_mainViewHeight">0</field>
michael@0 60 <field name="_subViewObserver">null</field>
michael@0 61 <field name="__transitioning">false</field>
michael@0 62 <field name="_ignoreMutations">false</field>
michael@0 63
michael@0 64 <property name="showingSubView" readonly="true"
michael@0 65 onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
michael@0 66 <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
michael@0 67 <property name="_mainView" readonly="true"
michael@0 68 onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
michael@0 69 <property name="showingSubViewAsMainView" readonly="true"
michael@0 70 onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
michael@0 71
michael@0 72 <property name="ignoreMutations">
michael@0 73 <getter>
michael@0 74 return this._ignoreMutations;
michael@0 75 </getter>
michael@0 76 <setter><![CDATA[
michael@0 77 this._ignoreMutations = val;
michael@0 78 if (!val && this._panel.state == "open") {
michael@0 79 if (this.showingSubView) {
michael@0 80 this._syncContainerWithSubView();
michael@0 81 } else {
michael@0 82 this._syncContainerWithMainView();
michael@0 83 }
michael@0 84 }
michael@0 85 ]]></setter>
michael@0 86 </property>
michael@0 87
michael@0 88 <property name="_transitioning">
michael@0 89 <getter>
michael@0 90 return this.__transitioning;
michael@0 91 </getter>
michael@0 92 <setter><![CDATA[
michael@0 93 this.__transitioning = val;
michael@0 94 if (val) {
michael@0 95 this.setAttribute("transitioning", "true");
michael@0 96 } else {
michael@0 97 this.removeAttribute("transitioning");
michael@0 98 }
michael@0 99 ]]></setter>
michael@0 100 </property>
michael@0 101 <constructor><![CDATA[
michael@0 102 this._clickCapturer.addEventListener("click", this);
michael@0 103 this._panel.addEventListener("popupshowing", this);
michael@0 104 this._panel.addEventListener("popupshown", this);
michael@0 105 this._panel.addEventListener("popuphidden", this);
michael@0 106 this._subViews.addEventListener("overflow", this);
michael@0 107 this._mainViewContainer.addEventListener("overflow", this);
michael@0 108
michael@0 109 // Get a MutationObserver ready to react to subview size changes. We
michael@0 110 // only attach this MutationObserver when a subview is being displayed.
michael@0 111 this._subViewObserver =
michael@0 112 new MutationObserver(this._syncContainerWithSubView.bind(this));
michael@0 113 this._mainViewObserver =
michael@0 114 new MutationObserver(this._syncContainerWithMainView.bind(this));
michael@0 115
michael@0 116 this._mainViewContainer.setAttribute("panelid",
michael@0 117 this._panel.id);
michael@0 118
michael@0 119 if (this._mainView) {
michael@0 120 this.setMainView(this._mainView);
michael@0 121 }
michael@0 122 this.setAttribute("viewtype", "main");
michael@0 123 ]]></constructor>
michael@0 124
michael@0 125 <destructor><![CDATA[
michael@0 126 if (this._mainView) {
michael@0 127 this._mainView.removeAttribute("mainview");
michael@0 128 }
michael@0 129 this._mainViewObserver.disconnect();
michael@0 130 this._subViewObserver.disconnect();
michael@0 131 this._panel.removeEventListener("popupshowing", this);
michael@0 132 this._panel.removeEventListener("popupshown", this);
michael@0 133 this._panel.removeEventListener("popuphidden", this);
michael@0 134 this._subViews.removeEventListener("overflow", this);
michael@0 135 this._mainViewContainer.removeEventListener("overflow", this);
michael@0 136 this._clickCapturer.removeEventListener("click", this);
michael@0 137 ]]></destructor>
michael@0 138
michael@0 139 <method name="setMainView">
michael@0 140 <parameter name="aNewMainView"/>
michael@0 141 <body><![CDATA[
michael@0 142 if (this._mainView) {
michael@0 143 this._mainViewObserver.disconnect();
michael@0 144 this._subViews.appendChild(this._mainView);
michael@0 145 this._mainView.removeAttribute("mainview");
michael@0 146 }
michael@0 147 this._mainViewId = aNewMainView.id;
michael@0 148 aNewMainView.setAttribute("mainview", "true");
michael@0 149 this._mainViewContainer.appendChild(aNewMainView);
michael@0 150 ]]></body>
michael@0 151 </method>
michael@0 152
michael@0 153 <method name="showMainView">
michael@0 154 <body><![CDATA[
michael@0 155 if (this.showingSubView) {
michael@0 156 let viewNode = this._currentSubView;
michael@0 157 let evt = document.createEvent("CustomEvent");
michael@0 158 evt.initCustomEvent("ViewHiding", true, true, viewNode);
michael@0 159 viewNode.dispatchEvent(evt);
michael@0 160
michael@0 161 viewNode.removeAttribute("current");
michael@0 162 this._currentSubView = null;
michael@0 163
michael@0 164 this._subViewObserver.disconnect();
michael@0 165
michael@0 166 this._transitioning = true;
michael@0 167
michael@0 168 this._viewContainer.addEventListener("transitionend", function trans() {
michael@0 169 this._viewContainer.removeEventListener("transitionend", trans);
michael@0 170 this._transitioning = false;
michael@0 171 }.bind(this));
michael@0 172 this._viewContainer.style.height = this._mainViewHeight + "px";
michael@0 173
michael@0 174 this.setAttribute("viewtype", "main");
michael@0 175 }
michael@0 176
michael@0 177 this._shiftMainView();
michael@0 178 ]]></body>
michael@0 179 </method>
michael@0 180
michael@0 181 <method name="showSubView">
michael@0 182 <parameter name="aViewId"/>
michael@0 183 <parameter name="aAnchor"/>
michael@0 184 <body><![CDATA[
michael@0 185 let viewNode = this.querySelector("#" + aViewId);
michael@0 186 viewNode.setAttribute("current", true);
michael@0 187 // Emit the ViewShowing event so that the widget definition has a chance
michael@0 188 // to lazily populate the subview with things.
michael@0 189 let evt = document.createEvent("CustomEvent");
michael@0 190 evt.initCustomEvent("ViewShowing", true, true, viewNode);
michael@0 191 viewNode.dispatchEvent(evt);
michael@0 192 if (evt.defaultPrevented) {
michael@0 193 return;
michael@0 194 }
michael@0 195
michael@0 196 this._currentSubView = viewNode;
michael@0 197
michael@0 198 // Now we have to transition the panel. There are a few parts to this:
michael@0 199 //
michael@0 200 // 1) The main view content gets shifted so that the center of the anchor
michael@0 201 // node is at the left-most edge of the panel.
michael@0 202 // 2) The subview deck slides in so that it takes up almost all of the
michael@0 203 // panel.
michael@0 204 // 3) If the subview is taller then the main panel contents, then the panel
michael@0 205 // must grow to meet that new height. Otherwise, it must shrink.
michael@0 206 //
michael@0 207 // All three of these actions make use of CSS transformations, so they
michael@0 208 // should all occur simultaneously.
michael@0 209 this.setAttribute("viewtype", "subview");
michael@0 210 this._shiftMainView(aAnchor);
michael@0 211
michael@0 212 this._mainViewHeight = this._viewStack.clientHeight;
michael@0 213
michael@0 214 this._transitioning = true;
michael@0 215 this._viewContainer.addEventListener("transitionend", function trans() {
michael@0 216 this._viewContainer.removeEventListener("transitionend", trans);
michael@0 217 this._transitioning = false;
michael@0 218 }.bind(this));
michael@0 219
michael@0 220 let newHeight = this._heightOfSubview(viewNode, this._subViews);
michael@0 221 this._viewContainer.style.height = newHeight + "px";
michael@0 222
michael@0 223 this._subViewObserver.observe(viewNode, {
michael@0 224 attributes: true,
michael@0 225 characterData: true,
michael@0 226 childList: true,
michael@0 227 subtree: true
michael@0 228 });
michael@0 229 ]]></body>
michael@0 230 </method>
michael@0 231
michael@0 232 <method name="_shiftMainView">
michael@0 233 <parameter name="aAnchor"/>
michael@0 234 <body><![CDATA[
michael@0 235 if (aAnchor) {
michael@0 236 // We need to find the edge of the anchor, relative to the main panel.
michael@0 237 // Then we need to add half the width of the anchor. This is the target
michael@0 238 // that we need to transition to.
michael@0 239 let anchorRect = aAnchor.getBoundingClientRect();
michael@0 240 let mainViewRect = this._mainViewContainer.getBoundingClientRect();
michael@0 241 let center = aAnchor.clientWidth / 2;
michael@0 242 let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
michael@0 243 let edge, target;
michael@0 244 if (direction == "ltr") {
michael@0 245 edge = anchorRect.left - mainViewRect.left;
michael@0 246 target = "-" + (edge + center);
michael@0 247 } else {
michael@0 248 edge = mainViewRect.right - anchorRect.right;
michael@0 249 target = edge + center;
michael@0 250 }
michael@0 251 this._mainViewContainer.style.transform = "translateX(" + target + "px)";
michael@0 252 aAnchor.setAttribute("panel-multiview-anchor", true);
michael@0 253 } else {
michael@0 254 this._mainViewContainer.style.transform = "";
michael@0 255 if (this.anchorElement)
michael@0 256 this.anchorElement.removeAttribute("panel-multiview-anchor");
michael@0 257 }
michael@0 258 this.anchorElement = aAnchor;
michael@0 259 ]]></body>
michael@0 260 </method>
michael@0 261
michael@0 262 <method name="handleEvent">
michael@0 263 <parameter name="aEvent"/>
michael@0 264 <body><![CDATA[
michael@0 265 if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
michael@0 266 // Shouldn't act on e.g. context menus being shown from within the panel.
michael@0 267 return;
michael@0 268 }
michael@0 269 switch (aEvent.type) {
michael@0 270 case "click":
michael@0 271 if (aEvent.originalTarget == this._clickCapturer) {
michael@0 272 this.showMainView();
michael@0 273 }
michael@0 274 break;
michael@0 275 case "overflow":
michael@0 276 if (aEvent.target.localName == "vbox") {
michael@0 277 // Resize the right view on the next tick.
michael@0 278 if (this.showingSubView) {
michael@0 279 setTimeout(this._syncContainerWithSubView.bind(this), 0);
michael@0 280 } else if (!this.transitioning) {
michael@0 281 setTimeout(this._syncContainerWithMainView.bind(this), 0);
michael@0 282 }
michael@0 283 }
michael@0 284 break;
michael@0 285 case "popupshowing":
michael@0 286 this.setAttribute("panelopen", "true");
michael@0 287 // Bug 941196 - The panel can get taller when opening a subview. Disabling
michael@0 288 // autoPositioning means that the panel won't jump around if an opened
michael@0 289 // subview causes the panel to exceed the dimensions of the screen in the
michael@0 290 // direction that the panel originally opened in. This property resets
michael@0 291 // every time the popup closes, which is why we have to set it each time.
michael@0 292 this._panel.autoPosition = false;
michael@0 293 this._syncContainerWithMainView();
michael@0 294
michael@0 295 this._mainViewObserver.observe(this._mainView, {
michael@0 296 attributes: true,
michael@0 297 characterData: true,
michael@0 298 childList: true,
michael@0 299 subtree: true
michael@0 300 });
michael@0 301
michael@0 302 break;
michael@0 303 case "popupshown":
michael@0 304 this._setMaxHeight();
michael@0 305 break;
michael@0 306 case "popuphidden":
michael@0 307 this.removeAttribute("panelopen");
michael@0 308 this._mainView.style.removeProperty("height");
michael@0 309 this.showMainView();
michael@0 310 this._mainViewObserver.disconnect();
michael@0 311 break;
michael@0 312 }
michael@0 313 ]]></body>
michael@0 314 </method>
michael@0 315
michael@0 316 <method name="_shouldSetHeight">
michael@0 317 <body><![CDATA[
michael@0 318 return this.getAttribute("nosubviews") != "true";
michael@0 319 ]]></body>
michael@0 320 </method>
michael@0 321
michael@0 322 <method name="_setMaxHeight">
michael@0 323 <body><![CDATA[
michael@0 324 if (!this._shouldSetHeight())
michael@0 325 return;
michael@0 326
michael@0 327 // Ignore the mutation that'll fire when we set the height of
michael@0 328 // the main view.
michael@0 329 this.ignoreMutations = true;
michael@0 330 this._mainView.style.height =
michael@0 331 this.getBoundingClientRect().height + "px";
michael@0 332 this.ignoreMutations = false;
michael@0 333 ]]></body>
michael@0 334 </method>
michael@0 335 <method name="_syncContainerWithSubView">
michael@0 336 <body><![CDATA[
michael@0 337 // Check that this panel is still alive:
michael@0 338 if (!this._panel || !this._panel.parentNode) {
michael@0 339 return;
michael@0 340 }
michael@0 341
michael@0 342 if (!this.ignoreMutations && this.showingSubView) {
michael@0 343 let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
michael@0 344 this._viewContainer.style.height = newHeight + "px";
michael@0 345 }
michael@0 346 ]]></body>
michael@0 347 </method>
michael@0 348 <method name="_syncContainerWithMainView">
michael@0 349 <body><![CDATA[
michael@0 350 // Check that this panel is still alive:
michael@0 351 if (!this._panel || !this._panel.parentNode || !this._shouldSetHeight()) {
michael@0 352 return;
michael@0 353 }
michael@0 354
michael@0 355 if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
michael@0 356 let height;
michael@0 357 if (this.showingSubViewAsMainView) {
michael@0 358 height = this._heightOfSubview(this._mainView);
michael@0 359 } else {
michael@0 360 height = this._mainView.scrollHeight;
michael@0 361 }
michael@0 362 this._viewContainer.style.height = height + "px";
michael@0 363 }
michael@0 364 ]]></body>
michael@0 365 </method>
michael@0 366
michael@0 367 <method name="_heightOfSubview">
michael@0 368 <parameter name="aSubview"/>
michael@0 369 <parameter name="aContainerToCheck"/>
michael@0 370 <body><![CDATA[
michael@0 371 function getFullHeight(element) {
michael@0 372 //XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
michael@0 373 // that works with overflow: auto elements. Fortunately for us,
michael@0 374 // we have exactly 1 (potentially) scrolling element in here (the subview body),
michael@0 375 // and rounding 1 value is OK - rounding more than 1 and adding them means we get
michael@0 376 // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
michael@0 377 // So, use scrollHeight *only* if the element is vertically scrollable.
michael@0 378 let height;
michael@0 379 let elementCS;
michael@0 380 if (element.scrollTopMax) {
michael@0 381 height = element.scrollHeight;
michael@0 382 // Bounding client rects include borders, scrollHeight doesn't:
michael@0 383 elementCS = win.getComputedStyle(element);
michael@0 384 height += parseFloat(elementCS.borderTopWidth) +
michael@0 385 parseFloat(elementCS.borderBottomWidth);
michael@0 386 } else {
michael@0 387 height = element.getBoundingClientRect().height;
michael@0 388 if (height > 0) {
michael@0 389 elementCS = win.getComputedStyle(element);
michael@0 390 }
michael@0 391 }
michael@0 392 if (elementCS) {
michael@0 393 // Include margins - but not borders or paddings because they
michael@0 394 // were dealt with above.
michael@0 395 height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
michael@0 396 }
michael@0 397 return height;
michael@0 398 }
michael@0 399 let win = aSubview.ownerDocument.defaultView;
michael@0 400 let body = aSubview.querySelector(".panel-subview-body");
michael@0 401 let height = getFullHeight(body || aSubview);
michael@0 402 if (body) {
michael@0 403 let header = aSubview.querySelector(".panel-subview-header");
michael@0 404 let footer = aSubview.querySelector(".panel-subview-footer");
michael@0 405 height += (header ? getFullHeight(header) : 0) +
michael@0 406 (footer ? getFullHeight(footer) : 0);
michael@0 407 }
michael@0 408 if (aContainerToCheck) {
michael@0 409 let containerCS = win.getComputedStyle(aContainerToCheck);
michael@0 410 height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
michael@0 411 }
michael@0 412 return Math.round(height);
michael@0 413 ]]></body>
michael@0 414 </method>
michael@0 415
michael@0 416 </implementation>
michael@0 417 </binding>
michael@0 418
michael@0 419 <binding id="panelview">
michael@0 420 <implementation>
michael@0 421 <property name="panelMultiView" readonly="true">
michael@0 422 <getter><![CDATA[
michael@0 423 if (this.parentNode.localName != "panelmultiview") {
michael@0 424 return document.getBindingParent(this.parentNode);
michael@0 425 }
michael@0 426
michael@0 427 return this.parentNode;
michael@0 428 ]]></getter>
michael@0 429 </property>
michael@0 430 </implementation>
michael@0 431 </binding>
michael@0 432 </bindings>

mercurial