diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index 1f70d597331e..f583fa5b17ff 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -43,6 +43,8 @@ import java.util.Arrays; import java.util.List; import java.util.TimerTask; +import org.mozilla.gecko.gfx.ImmutableViewportMetrics; + public class BrowserToolbar implements ViewSwitcher.ViewFactory, Tabs.OnTabsChangedListener, GeckoMenu.ActionItemBarPresenter, @@ -480,6 +482,23 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, } } + private boolean canToolbarHide() { + // Forbid the toolbar from hiding if hiding the toolbar would cause + // the page to go into overscroll. + ImmutableViewportMetrics metrics = GeckoApp.mAppContext.getLayerView(). + getLayerClient().getViewportMetrics(); + return (metrics.getPageHeight() >= metrics.getHeight()); + } + + private void startVisibilityAnimation() { + // Only start the animation if we're showing the toolbar, or it's ok + // to hide it. + if (mVisibility == ToolbarVisibility.VISIBLE || + canToolbarHide()) { + mVisibilityAnimator.start(); + } + } + public void animateVisibility(boolean show, long delay) { // Do nothing if there's a delayed animation pending that does the // same thing and this request also has a delay. @@ -498,13 +517,13 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, if (delay > 0) { mDelayedVisibilityTask = new TimerTask() { public void run() { - mVisibilityAnimator.start(); + startVisibilityAnimation(); mDelayedVisibilityTask = null; } }; mLayout.postDelayed(mDelayedVisibilityTask, delay); } else { - mVisibilityAnimator.start(); + startVisibilityAnimation(); } } diff --git a/mobile/android/base/BrowserToolbarLayout.java b/mobile/android/base/BrowserToolbarLayout.java index 421e247705a9..5e3afa6235c0 100644 --- a/mobile/android/base/BrowserToolbarLayout.java +++ b/mobile/android/base/BrowserToolbarLayout.java @@ -45,6 +45,13 @@ public class BrowserToolbarLayout extends LinearLayout { super.onSizeChanged(w, h, oldw, oldh); if (h != oldh) { + // In the current UI, this is the only place we have need of + // viewport margins (to stop the toolbar from obscuring fixed-pos + // content). + GeckoAppShell.sendEventToGecko( + GeckoEvent.createBroadcastEvent("Viewport:FixedMarginsChanged", + "{ \"top\" : " + h + ", \"right\" : 0, \"bottom\" : 0, \"left\" : 0 }")); + refreshMargins(); } } diff --git a/mobile/android/base/gfx/JavaPanZoomController.java b/mobile/android/base/gfx/JavaPanZoomController.java index 695754fec64e..b65ad8e8eca1 100644 --- a/mobile/android/base/gfx/JavaPanZoomController.java +++ b/mobile/android/base/gfx/JavaPanZoomController.java @@ -829,15 +829,21 @@ class JavaPanZoomController // Ensure minZoomFactor keeps the page at least as big as the viewport. if (pageRect.width() > 0) { - float scaleFactor = viewport.width() / pageRect.width(); + float pageWidth = pageRect.width() + + viewportMetrics.fixedLayerMarginLeft + + viewportMetrics.fixedLayerMarginRight; + float scaleFactor = viewport.width() / pageWidth; minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); - if (viewport.width() > pageRect.width()) + if (viewport.width() > pageWidth) focusX = 0.0f; } if (pageRect.height() > 0) { - float scaleFactor = viewport.height() / pageRect.height(); + float pageHeight = pageRect.height() + + viewportMetrics.fixedLayerMarginTop + + viewportMetrics.fixedLayerMarginBottom; + float scaleFactor = viewport.height() / pageHeight; minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); - if (viewport.height() > pageRect.height()) + if (viewport.height() > pageHeight) focusY = 0.0f; } diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index e99c3470cb84..814333872bd3 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -205,6 +205,7 @@ var BrowserApp = { Services.obs.addObserver(this, "FullScreen:Exit", false); Services.obs.addObserver(this, "Viewport:Change", false); Services.obs.addObserver(this, "Viewport:Flush", false); + Services.obs.addObserver(this, "Viewport:FixedMarginsChanged", false); Services.obs.addObserver(this, "Passwords:Init", false); Services.obs.addObserver(this, "FormHistory:Init", false); Services.obs.addObserver(this, "ToggleProfiling", false); @@ -1252,6 +1253,10 @@ var BrowserApp = { sendMessageToJava({ type: "Telemetry:Gather" }); break; + case "Viewport:FixedMarginsChanged": + gViewportMargins = JSON.parse(aData); + break; + default: dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n'); break; @@ -2781,6 +2786,12 @@ nsBrowserAccess.prototype = { let gScreenWidth = 1; let gScreenHeight = 1; +// The margins that should be applied to the viewport for fixed position +// children. This is used to avoid browser chrome permanently obscuring +// fixed position content, and also to make sure window-sized pages take +// into account said browser chrome. +let gViewportMargins = { top: 0, right: 0, bottom: 0, left: 0}; + function Tab(aURL, aParams) { this.browser = null; this.id = 0; @@ -2789,6 +2800,9 @@ function Tab(aURL, aParams) { this._zoom = 1.0; this._drawZoom = 1.0; this.userScrollPos = { x: 0, y: 0 }; + this.viewportExcludesHorizontalMargins = true; + this.viewportExcludesVerticalMargins = true; + this.updatingViewportForPageSizeChange = false; this.contentDocumentIsDisplayed = true; this.pluginDoorhangerTimeout = null; this.shouldShowPluginDoorhanger = true; @@ -3257,15 +3271,34 @@ Tab.prototype = { setScrollClampingSize: function(zoom) { let viewportWidth = gScreenWidth / zoom; let viewportHeight = gScreenHeight / zoom; + let screenWidth = gScreenWidth; + let screenHeight = gScreenHeight; + let [pageWidth, pageHeight] = this.getPageSize(this.browser.contentDocument, viewportWidth, viewportHeight); + // Check if the page would fit into either of the viewport dimensions minus + // the margins and shrink the screen size accordingly so that the aspect + // ratio calculation below works correctly in these situations. + // We take away the margin size over two to account for rounding errors, + // as the browser size set in updateViewportSize doesn't allow for any + // size between these two values (and thus anything between them is + // attributable to rounding error). + if ((pageHeight * zoom) < gScreenHeight - (gViewportMargins.top + gViewportMargins.bottom) / 2) { + screenHeight = gScreenHeight - gViewportMargins.top - gViewportMargins.bottom; + viewportHeight = screenHeight / zoom; + } + if ((pageWidth * zoom) < gScreenWidth - (gViewportMargins.left + gViewportMargins.right) / 2) { + screenWidth = gScreenWidth - gViewportMargins.left - gViewportMargins.right; + viewportWidth = screenWidth / zoom; + } + // Make sure the aspect ratio of the screen is maintained when setting // the clamping scroll-port size. - let factor = Math.min(viewportWidth / gScreenWidth, pageWidth / gScreenWidth, - viewportHeight / gScreenHeight, pageHeight / gScreenHeight); - let scrollPortWidth = gScreenWidth * factor; - let scrollPortHeight = gScreenHeight * factor; + let factor = Math.min(viewportWidth / screenWidth, pageWidth / screenWidth, + viewportHeight / screenHeight, pageHeight / screenHeight); + let scrollPortWidth = Math.min(screenWidth * factor, pageWidth * zoom); + let scrollPortHeight = Math.min(screenHeight * factor, pageHeight * zoom); let win = this.browser.contentWindow; win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils). @@ -3325,20 +3358,23 @@ Tab.prototype = { }, getViewport: function() { + let screenW = gScreenWidth - gViewportMargins.left - gViewportMargins.right; + let screenH = gScreenHeight - gViewportMargins.top - gViewportMargins.bottom; + let viewport = { - width: gScreenWidth, - height: gScreenHeight, - cssWidth: gScreenWidth / this._zoom, - cssHeight: gScreenHeight / this._zoom, + width: screenW, + height: screenH, + cssWidth: screenW / this._zoom, + cssHeight: screenH / this._zoom, pageLeft: 0, pageTop: 0, - pageRight: gScreenWidth, - pageBottom: gScreenHeight, + pageRight: screenW, + pageBottom: screenH, // We make up matching css page dimensions cssPageLeft: 0, cssPageTop: 0, - cssPageRight: gScreenWidth / this._zoom, - cssPageBottom: gScreenHeight / this._zoom, + cssPageRight: screenW / this._zoom, + cssPageBottom: screenH / this._zoom, zoom: this._zoom, }; @@ -3388,6 +3424,20 @@ Tab.prototype = { let displayPort = getBridge().getDisplayPort(aPageSizeUpdate, BrowserApp.isBrowserContentDocumentDisplayed(), this.id, viewport); if (displayPort != null) this.setDisplayPort(displayPort); + + // If the page size has changed so that it might or might not fit on the + // screen with the margins included, run updateViewportSize to resize the + // browser accordingly. The -1 is to account for rounding errors. + if (!this.updatingViewportForPageSizeChange) { + this.updatingViewportForPageSizeChange = true; + if (((viewport.pageBottom - viewport.pageTop <= gScreenHeight - 1) != + this.viewportExcludesVerticalMargins) || + ((viewport.pageRight - viewport.pageLeft <= gScreenWidth - 1) != + this.viewportExcludesHorizontalMargins)) { + this.updateViewportSize(gScreenWidth); + } + this.updatingViewportForPageSizeChange = false; + } }, handleEvent: function(aEvent) { @@ -3814,9 +3864,11 @@ Tab.prototype = { if (!browser) return; - let screenW = gScreenWidth; - let screenH = gScreenHeight; + let screenW = gScreenWidth - gViewportMargins.left - gViewportMargins.right; + let screenH = gScreenHeight - gViewportMargins.top - gViewportMargins.bottom; let viewportW, viewportH; + this.viewportExcludesHorizontalMargins = true; + this.viewportExcludesVerticalMargins = true; let metadata = this.metadata; if (metadata.autoSize) { @@ -3871,7 +3923,21 @@ Tab.prototype = { // this may get run during a Viewport:Change message while the document // has not yet loaded, so need to guard against a null document. let [pageWidth, pageHeight] = this.getPageSize(this.browser.contentDocument, viewportW, viewportH); - minScale = gScreenWidth / pageWidth; + + minScale = screenW / pageWidth; + + // In the situation the page size exceeds the screen size minus the + // viewport margins on either axis, lengthen the viewport on the + // corresponding axis to include the margins. + // The +1 is to account for rounding errors. + if (pageWidth * this._zoom >= screenW + 1) { + screenW = gScreenWidth; + this.viewportExcludesHorizontalMargins = false; + } + if (pageHeight * this._zoom >= screenH + 1) { + screenH = gScreenHeight; + this.viewportExcludesVerticalMargins = false; + } } minScale = this.clampZoom(minScale); viewportH = Math.max(viewportH, screenH / minScale);