1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/base/content/ContentAreaObserver.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,361 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 + /* 1.9 + * ContentAreaObserver manages tracking the viewable area within the browser. 1.10 + * It also handles certain tasks like positioning of input elements within 1.11 + * content when the viewable area changes. 1.12 + * 1.13 + * ContentAreaObserver creates styles that content can apply and also fires 1.14 + * events when things change. The 'width' and 'height' properties of the 1.15 + * styles are updated whenever various parts of the UI are resized. 1.16 + * 1.17 + * styles: window-width, window-height 1.18 + * events: SizeChanged 1.19 + * 1.20 + * The innerWidth/innerHeight of the main chrome window. 1.21 + * 1.22 + * styles: content-width, content-height 1.23 + * events: ContentSizeChanged 1.24 + * 1.25 + * The area of the window dedicated to web content; this is equal to the 1.26 + * innerWidth/Height minus any toolbars or similar chrome. 1.27 + * 1.28 + * styles: viewable-width, viewable-height 1.29 + * events: ViewableSizeChanged 1.30 + * 1.31 + * The portion of the content area that is not obscured by the on-screen 1.32 + * keyboard. 1.33 + * 1.34 + * ContentAreaObserver also manages soft keyboard related events. Events 1.35 + * fired include: 1.36 + * 1.37 + * KeyboardChanged - fired after the visibility of the soft keyboard changes 1.38 + * and after any view related changes are made to accomidate view dim 1.39 + * changes as a result. 1.40 + * 1.41 + * MozDeckOffsetChanging, MozDeckOffsetChanged - interim events fired when 1.42 + * the browser deck is being repositioned to move focused elements out of 1.43 + * the way of the keyboard. 1.44 + */ 1.45 + 1.46 +var ContentAreaObserver = { 1.47 + styles: {}, 1.48 + _shiftAmount: 0, 1.49 + _deckTransitioning: false, 1.50 + 1.51 + /* 1.52 + * Properties 1.53 + */ 1.54 + 1.55 + get width() { 1.56 + return window.innerWidth || 1366; 1.57 + }, 1.58 + 1.59 + get height() { 1.60 + return window.innerHeight || 768; 1.61 + }, 1.62 + 1.63 + get contentHeight() { 1.64 + return this._getContentHeightForWindow(this.height); 1.65 + }, 1.66 + 1.67 + get viewableHeight() { 1.68 + return this._getViewableHeightForContent(this.contentHeight); 1.69 + }, 1.70 + 1.71 + get isKeyboardOpened() { 1.72 + return Services.metro.keyboardVisible; 1.73 + }, 1.74 + 1.75 + get isKeyboardTransitioning() { 1.76 + return this._deckTransitioning; 1.77 + }, 1.78 + 1.79 + get viewstate() { 1.80 + if (this.width < Services.prefs.getIntPref("browser.ui.snapped.maxWidth")) { 1.81 + return "snapped"; 1.82 + } 1.83 + return (this.height > this.width) ? "portrait" : "landscape"; 1.84 + }, 1.85 + 1.86 + /* 1.87 + * Public apis 1.88 + */ 1.89 + 1.90 + init: function init() { 1.91 + window.addEventListener("resize", this, false); 1.92 + 1.93 + // Message manager msgs we listen for 1.94 + messageManager.addMessageListener("Content:RepositionInfoResponse", this); 1.95 + 1.96 + // Observer msgs we listen for 1.97 + Services.obs.addObserver(this, "metro_softkeyboard_shown", false); 1.98 + Services.obs.addObserver(this, "metro_softkeyboard_hidden", false); 1.99 + 1.100 + // setup initial values for browser form repositioning 1.101 + this.shiftBrowserDeck(0); 1.102 + 1.103 + // initialize our custom width and height styles 1.104 + this._initStyles(); 1.105 + 1.106 + // apply default styling 1.107 + this.update(); 1.108 + }, 1.109 + 1.110 + shutdown: function shutdown() { 1.111 + messageManager.removeMessageListener("Content:RepositionInfoResponse", this); 1.112 + Services.obs.removeObserver(this, "metro_softkeyboard_shown"); 1.113 + Services.obs.removeObserver(this, "metro_softkeyboard_hidden"); 1.114 + }, 1.115 + 1.116 + update: function cao_update (width, height) { 1.117 + let oldWidth = parseInt(this.styles["window-width"].width); 1.118 + let oldHeight = parseInt(this.styles["window-height"].height); 1.119 + 1.120 + let newWidth = width || this.width; 1.121 + let newHeight = height || this.height; 1.122 + 1.123 + if (newHeight == oldHeight && newWidth == oldWidth) { 1.124 + return; 1.125 + } 1.126 + 1.127 + this.styles["window-width"].width = newWidth + "px"; 1.128 + this.styles["window-width"].maxWidth = newWidth + "px"; 1.129 + this.styles["window-height"].height = newHeight + "px"; 1.130 + this.styles["window-height"].maxHeight = newHeight + "px"; 1.131 + 1.132 + this._updateViewState(); 1.133 + 1.134 + this.updateContentArea(newWidth, this._getContentHeightForWindow(newHeight)); 1.135 + this._dispatchBrowserEvent("SizeChanged"); 1.136 + }, 1.137 + 1.138 + updateContentArea: function cao_updateContentArea (width, height) { 1.139 + let oldHeight = parseInt(this.styles["content-height"].height); 1.140 + let oldWidth = parseInt(this.styles["content-width"].width); 1.141 + 1.142 + let newWidth = width || this.width; 1.143 + let newHeight = height || this.contentHeight; 1.144 + 1.145 + if (newHeight == oldHeight && newWidth == oldWidth) { 1.146 + return; 1.147 + } 1.148 + 1.149 + this.styles["content-height"].height = newHeight + "px"; 1.150 + this.styles["content-height"].maxHeight = newHeight + "px"; 1.151 + this.styles["content-width"].width = newWidth + "px"; 1.152 + this.styles["content-width"].maxWidth = newWidth + "px"; 1.153 + 1.154 + this.updateViewableArea(newWidth, this._getViewableHeightForContent(newHeight)); 1.155 + this._dispatchBrowserEvent("ContentSizeChanged"); 1.156 + }, 1.157 + 1.158 + updateViewableArea: function cao_updateViewableArea (width, height) { 1.159 + let oldHeight = parseInt(this.styles["viewable-height"].height); 1.160 + let oldWidth = parseInt(this.styles["viewable-width"].width); 1.161 + 1.162 + let newWidth = width || this.width; 1.163 + let newHeight = height || this.viewableHeight; 1.164 + 1.165 + if (newHeight == oldHeight && newWidth == oldWidth) { 1.166 + return; 1.167 + } 1.168 + 1.169 + this.styles["viewable-height"].height = newHeight + "px"; 1.170 + this.styles["viewable-height"].maxHeight = newHeight + "px"; 1.171 + this.styles["viewable-width"].width = newWidth + "px"; 1.172 + this.styles["viewable-width"].maxWidth = newWidth + "px"; 1.173 + 1.174 + this.updateAppBarPosition(); 1.175 + 1.176 + // Update the back/tab button states. If the keyboard is up 1.177 + // these are hidden. 1.178 + BrowserUI._updateButtons(); 1.179 + 1.180 + this._dispatchBrowserEvent("ViewableSizeChanged"); 1.181 + }, 1.182 + 1.183 + updateAppBarPosition: function updateAppBarPosition(aForceDown) { 1.184 + // Adjust the app and find bar position above the soft keyboard 1.185 + let keyboardHeight = aForceDown ? 0 : Services.metro.keyboardHeight; 1.186 + Elements.navbar.style.bottom = keyboardHeight + "px"; 1.187 + Elements.contextappbar.style.bottom = keyboardHeight + "px"; 1.188 + Elements.findbar.style.bottom = keyboardHeight + "px"; 1.189 + }, 1.190 + 1.191 + /* 1.192 + * Called by BrowserUI right before we blur the nav bar edit. We use 1.193 + * this to get a head start on shuffling app bars around before the 1.194 + * soft keyboard transitions down. 1.195 + */ 1.196 + navBarWillBlur: function navBarWillBlur() { 1.197 + this.updateAppBarPosition(true); 1.198 + }, 1.199 + 1.200 + onBrowserCreated: function onBrowserCreated(aBrowser) { 1.201 + let notificationBox = aBrowser.parentNode.parentNode; 1.202 + notificationBox.classList.add("content-width"); 1.203 + notificationBox.classList.add("content-height"); 1.204 + }, 1.205 + 1.206 + /* 1.207 + * Event handling 1.208 + */ 1.209 + 1.210 + _onKeyboardDisplayChanging: function _onKeyboardDisplayChanging(aNewState) { 1.211 + if (aNewState) { 1.212 + Elements.stack.setAttribute("keyboardVisible", true); 1.213 + } else { 1.214 + Elements.stack.removeAttribute("keyboardVisible"); 1.215 + } 1.216 + 1.217 + this.updateViewableArea(); 1.218 + 1.219 + if (!aNewState) { 1.220 + this.shiftBrowserDeck(0); 1.221 + return; 1.222 + } 1.223 + 1.224 + // Request info about the target form element to see if we 1.225 + // need to reposition the browser above the keyboard. 1.226 + if (SelectionHelperUI.layerMode === 2 /*kContentLayer*/) { 1.227 + Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:RepositionInfoRequest", { 1.228 + viewHeight: this.viewableHeight, 1.229 + }); 1.230 + } 1.231 + }, 1.232 + 1.233 + _onRepositionResponse: function _onRepositionResponse(aJsonMsg) { 1.234 + if (!aJsonMsg.reposition || !this.isKeyboardOpened) { 1.235 + this.shiftBrowserDeck(0); 1.236 + return; 1.237 + } 1.238 + this.shiftBrowserDeck(aJsonMsg.raiseContent); 1.239 + }, 1.240 + 1.241 + observe: function cao_observe(aSubject, aTopic, aData) { 1.242 + // Note these are fired before the transition starts. Also per MS specs 1.243 + // we should not do anything "heavy" here. We have about 100ms before 1.244 + // windows just ignores the event and starts the animation. 1.245 + switch (aTopic) { 1.246 + case "metro_softkeyboard_hidden": 1.247 + case "metro_softkeyboard_shown": 1.248 + // Mark that we are in a transition state. Auto-complete and other 1.249 + // popups will wait for an event before displaying anything. 1.250 + this._deckTransitioning = true; 1.251 + // This also insures tap events are delivered before we fire 1.252 + // this event. Form repositioning requires the selection handler 1.253 + // be running before we send this. 1.254 + let self = this; 1.255 + let state = aTopic == "metro_softkeyboard_shown"; 1.256 + Util.executeSoon(function () { 1.257 + self._onKeyboardDisplayChanging(state); 1.258 + }); 1.259 + break; 1.260 + } 1.261 + }, 1.262 + 1.263 + handleEvent: function cao_handleEvent(aEvent) { 1.264 + switch (aEvent.type) { 1.265 + case 'resize': 1.266 + if (aEvent.target != window) 1.267 + return; 1.268 + ContentAreaObserver.update(); 1.269 + break; 1.270 + } 1.271 + }, 1.272 + 1.273 + receiveMessage: function sh_receiveMessage(aMessage) { 1.274 + switch (aMessage.name) { 1.275 + case "Content:RepositionInfoResponse": 1.276 + this._onRepositionResponse(aMessage.json); 1.277 + break; 1.278 + } 1.279 + }, 1.280 + 1.281 + /* 1.282 + * Internal helpers 1.283 + */ 1.284 + 1.285 + _updateViewState: function (aState) { 1.286 + let oldViewstate = Elements.windowState.getAttribute("viewstate"); 1.287 + let viewstate = aState || this.viewstate; 1.288 + if (viewstate != oldViewstate) { 1.289 + Elements.windowState.setAttribute("viewstate", viewstate); 1.290 + Services.obs.notifyObservers(null, "metro_viewstate_changed", viewstate); 1.291 + } 1.292 + }, 1.293 + 1.294 + shiftBrowserDeck: function (aAmount) { 1.295 + if (aAmount == 0) { 1.296 + this._deckTransitioning = false; 1.297 + this._dispatchWindowEvent("KeyboardChanged", this.isKeyboardOpened); 1.298 + } 1.299 + 1.300 + if (this._shiftAmount == aAmount) 1.301 + return; 1.302 + 1.303 + this._shiftAmount = aAmount; 1.304 + this._dispatchWindowEvent("MozDeckOffsetChanging", aAmount); 1.305 + 1.306 + // Elements.browsers is the deck all browsers sit in 1.307 + let self = this; 1.308 + Elements.browsers.addEventListener("transitionend", function () { 1.309 + Elements.browsers.removeEventListener("transitionend", arguments.callee, true); 1.310 + self._deckTransitioning = false; 1.311 + self._dispatchWindowEvent("MozDeckOffsetChanged", aAmount); 1.312 + self._dispatchWindowEvent("KeyboardChanged", self.isKeyboardOpened); 1.313 + }, true); 1.314 + 1.315 + // selectAtPoint bug 858471 1.316 + //Elements.browsers.style.transform = "translateY(" + (-1 * aAmount) + "px)"; 1.317 + Elements.browsers.style.marginTop = "" + (-1 * aAmount) + "px"; 1.318 + }, 1.319 + 1.320 + _dispatchWindowEvent: function _dispatchWindowEvent(aEventName, aDetail) { 1.321 + let event = document.createEvent("UIEvents"); 1.322 + event.initUIEvent(aEventName, true, false, window, aDetail); 1.323 + window.dispatchEvent(event); 1.324 + }, 1.325 + 1.326 + _dispatchBrowserEvent: function (aName, aDetail) { 1.327 + setTimeout(function() { 1.328 + let event = document.createEvent("Events"); 1.329 + event.initEvent(aName, true, false); 1.330 + Elements.browsers.dispatchEvent(event); 1.331 + }, 0); 1.332 + }, 1.333 + 1.334 + _initStyles: function _initStyles() { 1.335 + let stylesheet = document.styleSheets[0]; 1.336 + for (let style of ["window-width", "window-height", 1.337 + "content-height", "content-width", 1.338 + "viewable-height", "viewable-width"]) { 1.339 + let index = stylesheet.insertRule("." + style + " {}", stylesheet.cssRules.length); 1.340 + this.styles[style] = stylesheet.cssRules[index].style; 1.341 + } 1.342 + }, 1.343 + 1.344 + _getContentHeightForWindow: function (windowHeight) { 1.345 + return windowHeight; 1.346 + }, 1.347 + 1.348 + _getViewableHeightForContent: function (contentHeight) { 1.349 + let keyboardHeight = Services.metro.keyboardHeight; 1.350 + return contentHeight - keyboardHeight; 1.351 + }, 1.352 + 1.353 + _debugDumpDims: function _debugDumpDims() { 1.354 + let width = parseInt(this.styles["window-width"].width); 1.355 + let height = parseInt(this.styles["window-height"].height); 1.356 + Util.dumpLn("window:", width, height); 1.357 + width = parseInt(this.styles["content-width"].width); 1.358 + height = parseInt(this.styles["content-height"].height); 1.359 + Util.dumpLn("content:", width, height); 1.360 + width = parseInt(this.styles["viewable-width"].width); 1.361 + height = parseInt(this.styles["viewable-height"].height); 1.362 + Util.dumpLn("viewable:", width, height); 1.363 + } 1.364 +};