From 05c34bba510bc2802df57efec9ba600bfddec387 Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Thu, 7 Mar 2013 10:17:33 +0000 Subject: [PATCH] Bug 716403 - Resize viewport dynamically on Android. r=kats,mfinkle This causes the viewport size to differ, depending on the length of the page. This has the effect of pages that size themselves to the size of the window always having the toolbar visible, making sites like Google Maps more usable. --- mobile/android/base/BrowserToolbar.java | 23 ++++- mobile/android/base/BrowserToolbarLayout.java | 7 ++ .../base/gfx/JavaPanZoomController.java | 14 ++- mobile/android/chrome/content/browser.js | 96 ++++++++++++++++--- 4 files changed, 119 insertions(+), 21 deletions(-) diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index c5adda7826db..bcc2dbda6484 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, @@ -485,6 +487,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. @@ -503,13 +522,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 8ad97b4d9c1d..a76ab94e694b 100644 --- a/mobile/android/base/gfx/JavaPanZoomController.java +++ b/mobile/android/base/gfx/JavaPanZoomController.java @@ -909,15 +909,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 eac8f1f54e02..66dbc7364815 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; @@ -2785,6 +2790,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; @@ -2793,6 +2804,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; @@ -3261,15 +3275,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). @@ -3329,20 +3362,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, }; @@ -3392,6 +3428,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) { @@ -3818,9 +3868,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) { @@ -3875,7 +3927,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);