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>