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