browser/metro/base/content/ContentAreaObserver.js

changeset 0
6474c204b198
     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 +};

mercurial