Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /*
6 * ContentAreaObserver manages tracking the viewable area within the browser.
7 * It also handles certain tasks like positioning of input elements within
8 * content when the viewable area changes.
9 *
10 * ContentAreaObserver creates styles that content can apply and also fires
11 * events when things change. The 'width' and 'height' properties of the
12 * styles are updated whenever various parts of the UI are resized.
13 *
14 * styles: window-width, window-height
15 * events: SizeChanged
16 *
17 * The innerWidth/innerHeight of the main chrome window.
18 *
19 * styles: content-width, content-height
20 * events: ContentSizeChanged
21 *
22 * The area of the window dedicated to web content; this is equal to the
23 * innerWidth/Height minus any toolbars or similar chrome.
24 *
25 * styles: viewable-width, viewable-height
26 * events: ViewableSizeChanged
27 *
28 * The portion of the content area that is not obscured by the on-screen
29 * keyboard.
30 *
31 * ContentAreaObserver also manages soft keyboard related events. Events
32 * fired include:
33 *
34 * KeyboardChanged - fired after the visibility of the soft keyboard changes
35 * and after any view related changes are made to accomidate view dim
36 * changes as a result.
37 *
38 * MozDeckOffsetChanging, MozDeckOffsetChanged - interim events fired when
39 * the browser deck is being repositioned to move focused elements out of
40 * the way of the keyboard.
41 */
43 var ContentAreaObserver = {
44 styles: {},
45 _shiftAmount: 0,
46 _deckTransitioning: false,
48 /*
49 * Properties
50 */
52 get width() {
53 return window.innerWidth || 1366;
54 },
56 get height() {
57 return window.innerHeight || 768;
58 },
60 get contentHeight() {
61 return this._getContentHeightForWindow(this.height);
62 },
64 get viewableHeight() {
65 return this._getViewableHeightForContent(this.contentHeight);
66 },
68 get isKeyboardOpened() {
69 return Services.metro.keyboardVisible;
70 },
72 get isKeyboardTransitioning() {
73 return this._deckTransitioning;
74 },
76 get viewstate() {
77 if (this.width < Services.prefs.getIntPref("browser.ui.snapped.maxWidth")) {
78 return "snapped";
79 }
80 return (this.height > this.width) ? "portrait" : "landscape";
81 },
83 /*
84 * Public apis
85 */
87 init: function init() {
88 window.addEventListener("resize", this, false);
90 // Message manager msgs we listen for
91 messageManager.addMessageListener("Content:RepositionInfoResponse", this);
93 // Observer msgs we listen for
94 Services.obs.addObserver(this, "metro_softkeyboard_shown", false);
95 Services.obs.addObserver(this, "metro_softkeyboard_hidden", false);
97 // setup initial values for browser form repositioning
98 this.shiftBrowserDeck(0);
100 // initialize our custom width and height styles
101 this._initStyles();
103 // apply default styling
104 this.update();
105 },
107 shutdown: function shutdown() {
108 messageManager.removeMessageListener("Content:RepositionInfoResponse", this);
109 Services.obs.removeObserver(this, "metro_softkeyboard_shown");
110 Services.obs.removeObserver(this, "metro_softkeyboard_hidden");
111 },
113 update: function cao_update (width, height) {
114 let oldWidth = parseInt(this.styles["window-width"].width);
115 let oldHeight = parseInt(this.styles["window-height"].height);
117 let newWidth = width || this.width;
118 let newHeight = height || this.height;
120 if (newHeight == oldHeight && newWidth == oldWidth) {
121 return;
122 }
124 this.styles["window-width"].width = newWidth + "px";
125 this.styles["window-width"].maxWidth = newWidth + "px";
126 this.styles["window-height"].height = newHeight + "px";
127 this.styles["window-height"].maxHeight = newHeight + "px";
129 this._updateViewState();
131 this.updateContentArea(newWidth, this._getContentHeightForWindow(newHeight));
132 this._dispatchBrowserEvent("SizeChanged");
133 },
135 updateContentArea: function cao_updateContentArea (width, height) {
136 let oldHeight = parseInt(this.styles["content-height"].height);
137 let oldWidth = parseInt(this.styles["content-width"].width);
139 let newWidth = width || this.width;
140 let newHeight = height || this.contentHeight;
142 if (newHeight == oldHeight && newWidth == oldWidth) {
143 return;
144 }
146 this.styles["content-height"].height = newHeight + "px";
147 this.styles["content-height"].maxHeight = newHeight + "px";
148 this.styles["content-width"].width = newWidth + "px";
149 this.styles["content-width"].maxWidth = newWidth + "px";
151 this.updateViewableArea(newWidth, this._getViewableHeightForContent(newHeight));
152 this._dispatchBrowserEvent("ContentSizeChanged");
153 },
155 updateViewableArea: function cao_updateViewableArea (width, height) {
156 let oldHeight = parseInt(this.styles["viewable-height"].height);
157 let oldWidth = parseInt(this.styles["viewable-width"].width);
159 let newWidth = width || this.width;
160 let newHeight = height || this.viewableHeight;
162 if (newHeight == oldHeight && newWidth == oldWidth) {
163 return;
164 }
166 this.styles["viewable-height"].height = newHeight + "px";
167 this.styles["viewable-height"].maxHeight = newHeight + "px";
168 this.styles["viewable-width"].width = newWidth + "px";
169 this.styles["viewable-width"].maxWidth = newWidth + "px";
171 this.updateAppBarPosition();
173 // Update the back/tab button states. If the keyboard is up
174 // these are hidden.
175 BrowserUI._updateButtons();
177 this._dispatchBrowserEvent("ViewableSizeChanged");
178 },
180 updateAppBarPosition: function updateAppBarPosition(aForceDown) {
181 // Adjust the app and find bar position above the soft keyboard
182 let keyboardHeight = aForceDown ? 0 : Services.metro.keyboardHeight;
183 Elements.navbar.style.bottom = keyboardHeight + "px";
184 Elements.contextappbar.style.bottom = keyboardHeight + "px";
185 Elements.findbar.style.bottom = keyboardHeight + "px";
186 },
188 /*
189 * Called by BrowserUI right before we blur the nav bar edit. We use
190 * this to get a head start on shuffling app bars around before the
191 * soft keyboard transitions down.
192 */
193 navBarWillBlur: function navBarWillBlur() {
194 this.updateAppBarPosition(true);
195 },
197 onBrowserCreated: function onBrowserCreated(aBrowser) {
198 let notificationBox = aBrowser.parentNode.parentNode;
199 notificationBox.classList.add("content-width");
200 notificationBox.classList.add("content-height");
201 },
203 /*
204 * Event handling
205 */
207 _onKeyboardDisplayChanging: function _onKeyboardDisplayChanging(aNewState) {
208 if (aNewState) {
209 Elements.stack.setAttribute("keyboardVisible", true);
210 } else {
211 Elements.stack.removeAttribute("keyboardVisible");
212 }
214 this.updateViewableArea();
216 if (!aNewState) {
217 this.shiftBrowserDeck(0);
218 return;
219 }
221 // Request info about the target form element to see if we
222 // need to reposition the browser above the keyboard.
223 if (SelectionHelperUI.layerMode === 2 /*kContentLayer*/) {
224 Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:RepositionInfoRequest", {
225 viewHeight: this.viewableHeight,
226 });
227 }
228 },
230 _onRepositionResponse: function _onRepositionResponse(aJsonMsg) {
231 if (!aJsonMsg.reposition || !this.isKeyboardOpened) {
232 this.shiftBrowserDeck(0);
233 return;
234 }
235 this.shiftBrowserDeck(aJsonMsg.raiseContent);
236 },
238 observe: function cao_observe(aSubject, aTopic, aData) {
239 // Note these are fired before the transition starts. Also per MS specs
240 // we should not do anything "heavy" here. We have about 100ms before
241 // windows just ignores the event and starts the animation.
242 switch (aTopic) {
243 case "metro_softkeyboard_hidden":
244 case "metro_softkeyboard_shown":
245 // Mark that we are in a transition state. Auto-complete and other
246 // popups will wait for an event before displaying anything.
247 this._deckTransitioning = true;
248 // This also insures tap events are delivered before we fire
249 // this event. Form repositioning requires the selection handler
250 // be running before we send this.
251 let self = this;
252 let state = aTopic == "metro_softkeyboard_shown";
253 Util.executeSoon(function () {
254 self._onKeyboardDisplayChanging(state);
255 });
256 break;
257 }
258 },
260 handleEvent: function cao_handleEvent(aEvent) {
261 switch (aEvent.type) {
262 case 'resize':
263 if (aEvent.target != window)
264 return;
265 ContentAreaObserver.update();
266 break;
267 }
268 },
270 receiveMessage: function sh_receiveMessage(aMessage) {
271 switch (aMessage.name) {
272 case "Content:RepositionInfoResponse":
273 this._onRepositionResponse(aMessage.json);
274 break;
275 }
276 },
278 /*
279 * Internal helpers
280 */
282 _updateViewState: function (aState) {
283 let oldViewstate = Elements.windowState.getAttribute("viewstate");
284 let viewstate = aState || this.viewstate;
285 if (viewstate != oldViewstate) {
286 Elements.windowState.setAttribute("viewstate", viewstate);
287 Services.obs.notifyObservers(null, "metro_viewstate_changed", viewstate);
288 }
289 },
291 shiftBrowserDeck: function (aAmount) {
292 if (aAmount == 0) {
293 this._deckTransitioning = false;
294 this._dispatchWindowEvent("KeyboardChanged", this.isKeyboardOpened);
295 }
297 if (this._shiftAmount == aAmount)
298 return;
300 this._shiftAmount = aAmount;
301 this._dispatchWindowEvent("MozDeckOffsetChanging", aAmount);
303 // Elements.browsers is the deck all browsers sit in
304 let self = this;
305 Elements.browsers.addEventListener("transitionend", function () {
306 Elements.browsers.removeEventListener("transitionend", arguments.callee, true);
307 self._deckTransitioning = false;
308 self._dispatchWindowEvent("MozDeckOffsetChanged", aAmount);
309 self._dispatchWindowEvent("KeyboardChanged", self.isKeyboardOpened);
310 }, true);
312 // selectAtPoint bug 858471
313 //Elements.browsers.style.transform = "translateY(" + (-1 * aAmount) + "px)";
314 Elements.browsers.style.marginTop = "" + (-1 * aAmount) + "px";
315 },
317 _dispatchWindowEvent: function _dispatchWindowEvent(aEventName, aDetail) {
318 let event = document.createEvent("UIEvents");
319 event.initUIEvent(aEventName, true, false, window, aDetail);
320 window.dispatchEvent(event);
321 },
323 _dispatchBrowserEvent: function (aName, aDetail) {
324 setTimeout(function() {
325 let event = document.createEvent("Events");
326 event.initEvent(aName, true, false);
327 Elements.browsers.dispatchEvent(event);
328 }, 0);
329 },
331 _initStyles: function _initStyles() {
332 let stylesheet = document.styleSheets[0];
333 for (let style of ["window-width", "window-height",
334 "content-height", "content-width",
335 "viewable-height", "viewable-width"]) {
336 let index = stylesheet.insertRule("." + style + " {}", stylesheet.cssRules.length);
337 this.styles[style] = stylesheet.cssRules[index].style;
338 }
339 },
341 _getContentHeightForWindow: function (windowHeight) {
342 return windowHeight;
343 },
345 _getViewableHeightForContent: function (contentHeight) {
346 let keyboardHeight = Services.metro.keyboardHeight;
347 return contentHeight - keyboardHeight;
348 },
350 _debugDumpDims: function _debugDumpDims() {
351 let width = parseInt(this.styles["window-width"].width);
352 let height = parseInt(this.styles["window-height"].height);
353 Util.dumpLn("window:", width, height);
354 width = parseInt(this.styles["content-width"].width);
355 height = parseInt(this.styles["content-height"].height);
356 Util.dumpLn("content:", width, height);
357 width = parseInt(this.styles["viewable-width"].width);
358 height = parseInt(this.styles["viewable-height"].height);
359 Util.dumpLn("viewable:", width, height);
360 }
361 };