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

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

mercurial