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