browser/metro/base/content/ContentAreaObserver.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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 };

mercurial