Bug 775463: Implement double-tap-to-zoom content r=cjones

This commit is contained in:
Doug Sherk 2012-08-08 21:39:02 -07:00
parent c3543b66ba
commit de15346fad
13 changed files with 567 additions and 91 deletions

View File

@ -9,6 +9,7 @@ const ContentPanning = {
});
addMessageListener("Viewport:Change", this._recvViewportChange.bind(this));
addMessageListener("Gesture:DoubleTap", this._recvDoubleTap.bind(this));
},
handleEvent: function cp_handleEvent(evt) {
@ -200,21 +201,34 @@ const ContentPanning = {
},
_recvViewportChange: function(data) {
let viewport = data.json;
let displayPort = viewport.displayPort;
let metrics = data.json;
let displayPort = metrics.displayPort;
let screenWidth = viewport.screenSize.width;
let screenHeight = viewport.screenSize.height;
let screenWidth = metrics.screenSize.width;
let screenHeight = metrics.screenSize.height;
let x = viewport.x;
let y = viewport.y;
let x = metrics.x;
let y = metrics.y;
this._zoom = metrics.zoom;
this._viewport = new Rect(x, y,
screenWidth / metrics.zoom,
screenHeight / metrics.zoom);
this._cssPageRect = new Rect(metrics.cssPageRect.x,
metrics.cssPageRect.y,
metrics.cssPageRect.width,
metrics.cssPageRect.height);
let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
cwu.setCSSViewport(screenWidth, screenHeight);
if (this._screenWidth != screenWidth || this._screenHeight != screenHeight) {
cwu.setCSSViewport(screenWidth, screenHeight);
this._screenWidth = screenWidth;
this._screenHeight = screenHeight;
}
// Set scroll position
cwu.setScrollPositionClampingScrollPortSize(
screenWidth / viewport.zoom, screenHeight / viewport.zoom);
screenWidth / metrics.zoom, screenHeight / metrics.zoom);
content.scrollTo(x, y);
cwu.setResolution(displayPort.resolution, displayPort.resolution);
@ -226,6 +240,109 @@ const ContentPanning = {
displayPort.height,
element);
}
},
_recvDoubleTap: function(data) {
let data = data.json;
// We haven't received a metrics update yet; don't do anything.
if (this._viewport == null) {
return;
}
let win = content;
let zoom = this._zoom;
let element = ElementTouchHelper.anyElementFromPoint(win, data.x, data.y);
if (!element) {
this._zoomOut();
return;
}
while (element && !this._shouldZoomToElement(element))
element = element.parentNode;
if (!element) {
this._zoomOut();
} else {
const margin = 15;
let rect = ElementTouchHelper.getBoundingContentRect(element);
let cssPageRect = this._cssPageRect;
let viewport = this._viewport;
let bRect = new Rect(Math.max(cssPageRect.left, rect.x - margin),
rect.y,
rect.w + 2 * margin,
rect.h);
// constrict the rect to the screen's right edge
bRect.width = Math.min(bRect.width, cssPageRect.right - bRect.x);
// if the rect is already taking up most of the visible area and is stretching the
// width of the page, then we want to zoom out instead.
if (this._isRectZoomedIn(bRect, viewport)) {
this._zoomOut();
return;
}
rect.x = Math.round(bRect.x);
rect.y = Math.round(bRect.y);
rect.w = Math.round(bRect.width);
rect.h = Math.round(Math.min(bRect.width * viewport.height / viewport.height, bRect.height));
// if the block we're zooming to is really tall, and the user double-tapped
// more than a screenful of height from the top of it, then adjust the y-coordinate
// so that we center the actual point the user double-tapped upon. this prevents
// flying to the top of a page when double-tapping to zoom in (bug 761721).
// the 1.2 multiplier is just a little fuzz to compensate for bRect including horizontal
// margins but not vertical ones.
let cssTapY = viewport.y + data.y;
if ((bRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) {
rect.y = cssTapY - (rect.h / 2);
}
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.notifyObservers(docShell, 'browser-zoom-to-rect', JSON.stringify(rect));
}
},
_shouldZoomToElement: function(aElement) {
let win = aElement.ownerDocument.defaultView;
if (win.getComputedStyle(aElement, null).display == "inline")
return false;
if (aElement instanceof Ci.nsIDOMHTMLLIElement)
return false;
if (aElement instanceof Ci.nsIDOMHTMLQuoteElement)
return false;
return true;
},
_zoomOut: function() {
let rect = new Rect(0, 0, 0, 0);
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.notifyObservers(docShell, 'browser-zoom-to-rect', JSON.stringify(rect));
},
_isRectZoomedIn: function(aRect, aViewport) {
// This function checks to see if the area of the rect visible in the
// viewport (i.e. the "overlapArea" variable below) is approximately
// the max area of the rect we can show. It also checks that the rect
// is actually on-screen by testing the left and right edges of the rect.
// In effect, this tells us whether or not zooming in to this rect
// will significantly change what the user is seeing.
const minDifference = -20;
const maxDifference = 20;
let vRect = new Rect(aViewport.x, aViewport.y, aViewport.width, aViewport.height);
let overlap = vRect.intersect(aRect);
let overlapArea = overlap.width * overlap.height;
let availHeight = Math.min(aRect.width * vRect.height / vRect.width, aRect.height);
let showing = overlapArea / (aRect.width * availHeight);
let dw = (aRect.width - vRect.width);
let dx = (aRect.x - vRect.x);
return (showing > 0.9 &&
dx > minDifference && dx < maxDifference &&
dw > minDifference && dw < maxDifference);
}
};
@ -399,3 +516,52 @@ const KineticPanning = {
content.mozRequestAnimationFrame(callback);
}
};
const ElementTouchHelper = {
anyElementFromPoint: function(aWindow, aX, aY) {
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
let elem = cwu.elementFromPoint(aX, aY, true, true);
let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
let HTMLFrameElement = Ci.nsIDOMHTMLFrameElement;
while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
let rect = elem.getBoundingClientRect();
aX -= rect.left;
aY -= rect.top;
cwu = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
elem = cwu.elementFromPoint(aX, aY, true, true);
}
return elem;
},
getBoundingContentRect: function(aElement) {
if (!aElement)
return {x: 0, y: 0, w: 0, h: 0};
let document = aElement.ownerDocument;
while (document.defaultView.frameElement)
document = document.defaultView.frameElement.ownerDocument;
let cwu = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
let scrollX = {}, scrollY = {};
cwu.getScrollXY(false, scrollX, scrollY);
let r = aElement.getBoundingClientRect();
// step out of iframes and frames, offsetting scroll values
for (let frame = aElement.ownerDocument.defaultView; frame.frameElement && frame != content; frame = frame.parent) {
// adjust client coordinates' origin to be top left of iframe viewport
let rect = frame.frameElement.getBoundingClientRect();
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
scrollX.value += rect.left + parseInt(left);
scrollY.value += rect.top + parseInt(top);
}
return {x: r.left + scrollX.value,
y: r.top + scrollY.value,
w: r.width,
h: r.height };
}
};

View File

@ -15,6 +15,7 @@ include protocol POfflineCacheUpdate;
include protocol PIndexedDB;
include "gfxMatrix.h";
include "FrameMetrics.h";
include "IPC/nsGUIEventIPC.h";
include "mozilla/dom/TabMessageUtils.h";
include "mozilla/dom/PermissionMessageUtils.h";
@ -26,8 +27,10 @@ include DOMTypes;
using IPC::URI;
using IPC::Principal;
using gfxMatrix;
using gfxRect;
using gfxSize;
using mozilla::layers::LayersBackend;
using mozilla::layers::FrameMetrics;
using mozilla::layout::ScrollingBehavior;
using mozilla::WindowsHandle;
using nscolor;
@ -249,6 +252,12 @@ parent:
NotifyDOMTouchListenerAdded();
/**
* Instructs the TabParent to forward a request to zoom to a rect given in
* CSS pixels. This rect is relative to the document.
*/
ZoomToRect(gfxRect aRect);
__delete__();
child:
@ -267,10 +276,14 @@ child:
UpdateDimensions(nsRect rect, nsIntSize size);
UpdateFrame(nsIntRect displayPort,
nsIntPoint scrollOffset,
gfxSize resolution,
nsIntRect screenSize);
UpdateFrame(FrameMetrics frame);
/**
* Requests handling of a double tap. |point| is in CSS pixels, relative to
* the scroll offset. This message is expected to round-trip back to
* ZoomToRect() with a rect indicating where we should zoom to.
*/
HandleDoubleTap(nsIntPoint point);
/**
* Sending an activate message moves focus to the child.

View File

@ -126,6 +126,16 @@ TabChild::Observe(nsISupports *aSubject,
if (tabChild == this) {
mRemoteFrame->CancelDefaultPanZoom();
}
} else if (!strcmp(aTopic, "browser-zoom-to-rect")) {
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
if (tabChild == this) {
gfxRect rect;
sscanf(NS_ConvertUTF16toUTF8(aData).get(),
"{\"x\":%lf,\"y\":%lf,\"w\":%lf,\"h\":%lf}",
&rect.x, &rect.y, &rect.width, &rect.height);
SendZoomToRect(rect);
}
}
return NS_OK;
@ -157,6 +167,9 @@ TabChild::Init()
observerService->AddObserver(this,
"cancel-default-pan-zoom",
false);
observerService->AddObserver(this,
"browser-zoom-to-rect",
false);
}
return NS_OK;
@ -682,41 +695,17 @@ TabChild::RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size)
return true;
}
bool
TabChild::RecvUpdateFrame(const nsIntRect& aDisplayPort,
const nsIntPoint& aScrollOffset,
const gfxSize& aResolution,
const nsIntRect& aScreenSize)
void
TabChild::DispatchMessageManagerMessage(const nsAString& aMessageName,
const nsACString& aJSONData)
{
if (!mCx || !mTabChildGlobal) {
return true;
}
nsCString data;
data += nsPrintfCString("{ \"x\" : %d", aScrollOffset.x);
data += nsPrintfCString(", \"y\" : %d", aScrollOffset.y);
// We don't treat the x and y scales any differently for this
// semi-platform-specific code.
data += nsPrintfCString(", \"zoom\" : %f", aResolution.width);
data += nsPrintfCString(", \"displayPort\" : ");
data += nsPrintfCString("{ \"left\" : %d", aDisplayPort.X());
data += nsPrintfCString(", \"top\" : %d", aDisplayPort.Y());
data += nsPrintfCString(", \"width\" : %d", aDisplayPort.Width());
data += nsPrintfCString(", \"height\" : %d", aDisplayPort.Height());
data += nsPrintfCString(", \"resolution\" : %f", aResolution.width);
data += nsPrintfCString(" }");
data += nsPrintfCString(", \"screenSize\" : ");
data += nsPrintfCString("{ \"width\" : %d", aScreenSize.width);
data += nsPrintfCString(", \"height\" : %d", aScreenSize.height);
data += nsPrintfCString(" }");
data += nsPrintfCString(" }");
JSAutoRequest ar(mCx);
jsval json = JSVAL_NULL;
StructuredCloneData cloneData;
JSAutoStructuredCloneBuffer buffer;
if (JS_ParseJSON(mCx,
static_cast<const jschar*>(NS_ConvertUTF8toUTF16(data).get()),
data.Length(),
static_cast<const jschar*>(NS_ConvertUTF8toUTF16(aJSONData).get()),
aJSONData.Length(),
&json)) {
WriteStructuredClone(mCx, json, buffer, cloneData.mClosure);
cloneData.mData = buffer.data();
@ -729,8 +718,60 @@ TabChild::RecvUpdateFrame(const nsIntRect& aDisplayPort,
nsRefPtr<nsFrameMessageManager> mm =
static_cast<nsFrameMessageManager*>(mTabChildGlobal->mMessageManager.get());
mm->ReceiveMessage(static_cast<nsIDOMEventTarget*>(mTabChildGlobal),
NS_LITERAL_STRING("Viewport:Change"), false,
&cloneData, nullptr, nullptr);
aMessageName, false, &cloneData, nullptr, nullptr);
}
bool
TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
{
if (!mCx || !mTabChildGlobal) {
return true;
}
nsCString data;
data += nsPrintfCString("{ \"x\" : %d", aFrameMetrics.mViewportScrollOffset.x);
data += nsPrintfCString(", \"y\" : %d", aFrameMetrics.mViewportScrollOffset.y);
// We don't treat the x and y scales any differently for this
// semi-platform-specific code.
data += nsPrintfCString(", \"zoom\" : %f", aFrameMetrics.mResolution.width);
data += nsPrintfCString(", \"displayPort\" : ");
data += nsPrintfCString("{ \"left\" : %d", aFrameMetrics.mDisplayPort.X());
data += nsPrintfCString(", \"top\" : %d", aFrameMetrics.mDisplayPort.Y());
data += nsPrintfCString(", \"width\" : %d", aFrameMetrics.mDisplayPort.Width());
data += nsPrintfCString(", \"height\" : %d", aFrameMetrics.mDisplayPort.Height());
data += nsPrintfCString(", \"resolution\" : %f", aFrameMetrics.mResolution.width);
data += nsPrintfCString(" }");
data += nsPrintfCString(", \"screenSize\" : ");
data += nsPrintfCString("{ \"width\" : %d", aFrameMetrics.mViewport.width);
data += nsPrintfCString(", \"height\" : %d", aFrameMetrics.mViewport.height);
data += nsPrintfCString(" }");
data += nsPrintfCString(", \"cssPageRect\" : ");
data += nsPrintfCString("{ \"x\" : %f", aFrameMetrics.mCSSContentRect.x);
data += nsPrintfCString(", \"y\" : %f", aFrameMetrics.mCSSContentRect.y);
data += nsPrintfCString(", \"width\" : %f", aFrameMetrics.mCSSContentRect.width);
data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mCSSContentRect.height);
data += nsPrintfCString(" }");
data += nsPrintfCString(" }");
DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data);
return true;
}
bool
TabChild::RecvHandleDoubleTap(const nsIntPoint& aPoint)
{
if (!mCx || !mTabChildGlobal) {
return true;
}
nsCString data;
data += nsPrintfCString("{ \"x\" : %d", aPoint.x);
data += nsPrintfCString(", \"y\" : %d", aPoint.y);
data += nsPrintfCString(" }");
DispatchMessageManagerMessage(NS_LITERAL_STRING("Gesture:DoubleTap"), data);
return true;
}

View File

@ -47,6 +47,7 @@
#include "nsWeakReference.h"
#include "nsITabChild.h"
#include "mozilla/Attributes.h"
#include "FrameMetrics.h"
struct gfxMatrix;
@ -172,10 +173,8 @@ public:
virtual bool RecvLoadURL(const nsCString& uri);
virtual bool RecvShow(const nsIntSize& size);
virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size);
virtual bool RecvUpdateFrame(const nsIntRect& aDisplayPort,
const nsIntPoint& aScrollOffset,
const gfxSize& aResolution,
const nsIntRect& aScreenSize);
virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
virtual bool RecvHandleDoubleTap(const nsIntPoint& aPoint);
virtual bool RecvActivate();
virtual bool RecvDeactivate();
virtual bool RecvMouseEvent(const nsString& aType,
@ -289,6 +288,14 @@ private:
// Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow().
void DoFakeShow();
// Wraps up a JSON object as a structured clone and sends it to the browser
// chrome script.
//
// XXX/bug 780335: Do the work the browser chrome script does in C++ instead
// so we don't need things like this.
void DispatchMessageManagerMessage(const nsAString& aMessageName,
const nsACString& aJSONData);
nsresult
BrowserFrameProvideWindow(nsIDOMWindow* aOpener,
nsIURI* aURI,

View File

@ -225,10 +225,12 @@ TabParent::UpdateDimensions(const nsRect& rect, const nsIntSize& size)
void
TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
{
unused << SendUpdateFrame(aFrameMetrics.mDisplayPort,
aFrameMetrics.mViewportScrollOffset,
aFrameMetrics.mResolution,
aFrameMetrics.mViewport);
unused << SendUpdateFrame(aFrameMetrics);
}
void TabParent::HandleDoubleTap(const nsIntPoint& aPoint)
{
unused << SendHandleDoubleTap(aPoint);
}
void
@ -1099,5 +1101,14 @@ TabParent::RecvNotifyDOMTouchListenerAdded()
return true;
}
bool
TabParent::RecvZoomToRect(const gfxRect& aRect)
{
if (RenderFrameParent* rfp = GetRenderFrame()) {
rfp->ZoomToRect(aRect);
}
return true;
}
} // namespace tabs
} // namespace mozilla

View File

@ -102,6 +102,7 @@ public:
virtual bool RecvGetDPI(float* aValue);
virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue);
virtual bool RecvNotifyDOMTouchListenerAdded();
virtual bool RecvZoomToRect(const gfxRect& aRect);
virtual PContentDialogParent* AllocPContentDialog(const PRUint32& aType,
const nsCString& aName,
const nsCString& aFeatures,
@ -121,6 +122,7 @@ public:
void Show(const nsIntSize& size);
void UpdateDimensions(const nsRect& rect, const nsIntSize& size);
void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
void HandleDoubleTap(const nsIntPoint& aPoint);
void Activate();
void Deactivate();

View File

@ -6,15 +6,20 @@
#include "CompositorParent.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Constants.h"
#include "mozilla/Util.h"
#include "mozilla/XPCOM.h"
#include "mozilla/Monitor.h"
#include "mozilla/StaticPtr.h"
#include "AsyncPanZoomController.h"
#include "GestureEventListener.h"
#include "nsIThreadManager.h"
#include "nsThreadUtils.h"
#include "Layers.h"
#include "AnimationCommon.h"
using namespace mozilla::css;
namespace mozilla {
namespace layers {
@ -44,6 +49,26 @@ static const float MIN_SKATE_SPEED = 0.5f;
*/
static const float AXIS_LOCK_ANGLE = M_PI / 6.0;
/**
* Duration of a zoom to animation.
*/
static const TimeDuration ZOOM_TO_DURATION = TimeDuration::FromSeconds(0.25);
/**
* Computed time function used for sampling frames of a zoom to animation.
*/
StaticAutoPtr<ComputedTimingFunction> gComputedTimingFunction;
/**
* Maximum zoom amount, always used, even if a page asks for higher.
*/
static const double MAX_ZOOM = 8.0;
/**
* Minimum zoom amount, always used, even if a page asks for lower.
*/
static const double MIN_ZOOM = 0.125;
AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoContentController,
GestureBehavior aGestures)
: mGeckoContentController(aGeckoContentController),
@ -62,6 +87,13 @@ AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoCon
}
SetDPI(mDPI);
if (!gComputedTimingFunction) {
gComputedTimingFunction = new ComputedTimingFunction();
gComputedTimingFunction->Init(
nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
ClearOnShutdown(&gComputedTimingFunction);
}
}
AsyncPanZoomController::~AsyncPanZoomController() {
@ -202,6 +234,12 @@ nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent
PRInt32 xPos = point.x, yPos = point.y;
switch (mState) {
case ANIMATING_ZOOM:
// We just interrupted a double-tap animation, so force a redraw in case
// this touchstart is just a tap that doesn't end up triggering a redraw.
RequestContentRepaint();
ScheduleComposite();
// Fall through.
case FLING:
CancelAnimation();
// Fall through.
@ -231,6 +269,7 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent)
switch (mState) {
case FLING:
case NOTHING:
case ANIMATING_ZOOM:
// May happen if the user double-taps and drags without lifting after the
// second tap. Ignore the move if this happens.
return nsEventStatus_eIgnore;
@ -272,6 +311,7 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent)
// Should never happen.
NS_WARNING("Received impossible touch end in OnTouchEnd.");
// Fall through.
case ANIMATING_ZOOM:
case NOTHING:
// May happen if the user double-taps and drags without lifting after the
// second tap. Ignore if this happens.
@ -343,14 +383,14 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
PRInt32 neededDisplacementX = 0, neededDisplacementY = 0;
// Only do the scaling if we won't go over 8x zoom in or out.
bool doScale = (scale < 8.0f && spanRatio > 1.0f) || (scale > 0.125f && spanRatio < 1.0f);
bool doScale = (scale < MAX_ZOOM && spanRatio > 1.0f) || (scale > MIN_ZOOM && spanRatio < 1.0f);
// If this zoom will take it over 8x zoom in either direction, but it's not
// already there, then normalize it.
if (scale * spanRatio > 8.0f) {
spanRatio = scale / 8.0f;
} else if (scale * spanRatio < 0.125f) {
spanRatio = scale / 0.125f;
if (scale * spanRatio > MAX_ZOOM) {
spanRatio = scale / MAX_ZOOM;
} else if (scale * spanRatio < MIN_ZOOM) {
spanRatio = scale / MIN_ZOOM;
}
if (doScale) {
@ -435,7 +475,15 @@ nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput
}
nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
// XXX: Implement this.
if (mGeckoContentController) {
MonitorAutoLock monitor(mMonitor);
gfx::Point point = WidgetSpaceToCompensatedViewportSpace(
gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y),
mFrameMetrics.mResolution.width);
mGeckoContentController->HandleDoubleTap(nsIntPoint(NS_lround(point.x), NS_lround(point.y)));
return nsEventStatus_eConsumeNoDefault;
}
return nsEventStatus_eIgnore;
}
@ -571,7 +619,7 @@ void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) {
float scale = mFrameMetrics.mResolution.width;
// The page rect is the css page rect scaled by the current zoom.
pageSize.ScaleRoundOut(scale);
pageSize.ScaleRoundOut(1 / scale);
// Round the page rect so we don't get any truncation, then get the nsIntRect
// from this.
@ -743,9 +791,43 @@ bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSa
{
MonitorAutoLock mon(mMonitor);
// If a fling is currently happening, apply it now. We can pull the updated
// metrics afterwards.
requestAnimationFrame = requestAnimationFrame || DoFling(aSampleTime - mLastSampleTime);
switch (mState)
{
case FLING:
// If a fling is currently happening, apply it now. We can pull the updated
// metrics afterwards.
requestAnimationFrame |= DoFling(aSampleTime - mLastSampleTime);
break;
case ANIMATING_ZOOM: {
double animPosition = (aSampleTime - mAnimationStartTime) / ZOOM_TO_DURATION;
if (animPosition > 1.0) {
animPosition = 1.0;
}
double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
mEndZoomToMetrics.mResolution.width * sampledPosition +
mStartZoomToMetrics.mResolution.width * (1 - sampledPosition);
mFrameMetrics.mViewportScrollOffset = nsIntPoint(
mEndZoomToMetrics.mViewportScrollOffset.x * sampledPosition +
mStartZoomToMetrics.mViewportScrollOffset.x * (1 - sampledPosition),
mEndZoomToMetrics.mViewportScrollOffset.y * sampledPosition +
mStartZoomToMetrics.mViewportScrollOffset.y * (1 - sampledPosition)
);
requestAnimationFrame = true;
if (aSampleTime - mAnimationStartTime >= ZOOM_TO_DURATION) {
mState = NOTHING;
RequestContentRepaint();
}
break;
}
default:
break;
}
// Current local transform; this is not what's painted but rather what PZC has
// transformed due to touches like panning or pinching. Eventually, the root
@ -815,7 +897,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr
// Assuming a first paint means a new page has been loaded, clear the flag
// indicating that we may have touch listeners.
mMayHaveTouchListeners = false;
} else if (!mFrameMetrics.mContentRect.IsEqualEdges(aViewportFrame.mContentRect)) {
} else if (!mFrameMetrics.mCSSContentRect.IsEqualEdges(aViewportFrame.mCSSContentRect)) {
mFrameMetrics.mCSSContentRect = aViewportFrame.mCSSContentRect;
SetPageRect(mFrameMetrics.mCSSContentRect);
}
@ -844,6 +926,80 @@ void AsyncPanZoomController::CancelDefaultPanZoom() {
}
}
void AsyncPanZoomController::ZoomToRect(const gfxRect& aRect) {
gfx::Rect zoomToRect(gfx::Rect(aRect.x, aRect.y, aRect.width, aRect.height));
gfx::Rect cssPageRect = mFrameMetrics.mCSSContentRect;
SetState(ANIMATING_ZOOM);
{
MonitorAutoLock mon(mMonitor);
nsIntRect viewport = mFrameMetrics.mViewport;
// If the rect is empty, treat it as a request to zoom out to the full page
// size.
if (zoomToRect.IsEmpty()) {
nsIntRect cssViewport = viewport;
cssViewport.ScaleRoundIn(1 / mFrameMetrics.mResolution.width);
cssViewport.MoveBy(mFrameMetrics.mViewportScrollOffset);
float y = mFrameMetrics.mViewportScrollOffset.y;
float newHeight = cssViewport.height * cssPageRect.width / cssViewport.width;
float dh = cssViewport.height - newHeight;
zoomToRect = gfx::Rect(0.0f,
y + dh/2,
cssPageRect.width,
y + dh/2 + newHeight);
} else {
float targetRatio = float(viewport.width) / float(viewport.height);
float rectRatio = zoomToRect.width / zoomToRect.height;
if (fabsf(targetRatio - rectRatio) < EPSILON) {
// All good, do nothing.
} else if (targetRatio < rectRatio) {
// Need to increase zoomToRect height.
float newHeight = zoomToRect.height / targetRatio;
zoomToRect.y -= (newHeight - zoomToRect.height) / 2;
zoomToRect.height = newHeight;
} else { // (targetRatio > rectRatio) {
// Need to increase zoomToRect width.
float newWidth = targetRatio * zoomToRect.width;
zoomToRect.x -= (newWidth - zoomToRect.width) / 2;
zoomToRect.width = newWidth;
}
zoomToRect = zoomToRect.Intersect(cssPageRect);
}
mEndZoomToMetrics.mResolution.width = mEndZoomToMetrics.mResolution.height =
NS_MIN(viewport.width / zoomToRect.width, viewport.height / zoomToRect.height);
mEndZoomToMetrics.mResolution.width = mEndZoomToMetrics.mResolution.height =
clamped(mEndZoomToMetrics.mResolution.width, MIN_ZOOM, MAX_ZOOM);
// Recalculate the zoom to rect using the new dimensions.
zoomToRect.width = viewport.width / mEndZoomToMetrics.mResolution.width;
zoomToRect.height = viewport.height / mEndZoomToMetrics.mResolution.height;
// Clamp the zoom to rect to the CSS rect to make sure it fits.
zoomToRect = zoomToRect.Intersect(cssPageRect);
// Do one final recalculation to get the resolution.
mEndZoomToMetrics.mResolution.width = mEndZoomToMetrics.mResolution.height =
NS_MAX(viewport.width / zoomToRect.width, viewport.height / zoomToRect.height);
mStartZoomToMetrics = mFrameMetrics;
mEndZoomToMetrics.mViewportScrollOffset =
nsIntPoint(NS_lround(zoomToRect.x), NS_lround(zoomToRect.y));
mAnimationStartTime = TimeStamp::Now();
ScheduleComposite();
}
}
void AsyncPanZoomController::SetState(PanZoomState aState) {
MonitorAutoLock monitor(mMonitor);
mState = aState;

View File

@ -119,6 +119,13 @@ public:
*/
void CancelDefaultPanZoom();
/**
* Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
* in. The actual animation is done on the compositor thread after being set
* up. |aRect| must be given in CSS pixels, relative to the document.
*/
void ZoomToRect(const gfxRect& aRect);
// --------------------------------------------------------------------------
// These methods must only be called on the compositor thread.
//
@ -368,6 +375,7 @@ private:
TOUCHING, /* one touch-start event received */
PANNING, /* panning without axis lock */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
ANIMATING_ZOOM /* animated zoom to a new rect */
};
enum ContentPainterStatus {
@ -411,6 +419,16 @@ private:
// If we don't do this check, we don't get a ShadowLayersUpdated back.
FrameMetrics mLastPaintRequestMetrics;
// Old metrics from before we started a zoom animation. This is only valid
// when we are in the "ANIMATED_ZOOM" state. This is used so that we can
// interpolate between the start and end frames. We only use the
// |mViewportScrollOffset| and |mResolution| fields on this.
FrameMetrics mStartZoomToMetrics;
// Target metrics for a zoom to animation. This is only valid when we are in
// the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
// |mResolution| fields on this.
FrameMetrics mEndZoomToMetrics;
AxisX mX;
AxisY mY;
@ -426,6 +444,10 @@ private:
// The last time a touch event came through on the UI thread.
PRInt32 mLastEventTime;
// Start time of an animation. This is used for a zoom to animation to mark
// the beginning.
TimeStamp mAnimationStartTime;
// Stores the previous focus point if there is a pinch gesture happening. Used
// to allow panning by moving multiple fingers (thus moving the focus point).
nsIntPoint mLastZoomFocus;

View File

@ -23,6 +23,14 @@ public:
*/
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
/**
* Requests handling of a double tap. |aPoint| is in CSS pixels, relative to
* the current scroll offset. This should eventually round-trip back to
* AsyncPanZoomController::ZoomToRect with the dimensions that we want to zoom
* to.
*/
virtual void HandleDoubleTap(const nsIntPoint& aPoint) = 0;
GeckoContentController() {}
virtual ~GeckoContentController() {}
};

View File

@ -9,7 +9,6 @@
#define IPC_ShadowLayerUtils_h
#include "IPC/IPCMessageUtils.h"
#include "FrameMetrics.h"
#include "GLContext.h"
#include "mozilla/WidgetUtils.h"
@ -39,34 +38,6 @@ struct MagicGrallocBufferHandle {
namespace IPC {
template <>
struct ParamTraits<mozilla::layers::FrameMetrics>
{
typedef mozilla::layers::FrameMetrics paramType;
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.mCSSContentRect);
WriteParam(aMsg, aParam.mViewport);
WriteParam(aMsg, aParam.mContentRect);
WriteParam(aMsg, aParam.mViewportScrollOffset);
WriteParam(aMsg, aParam.mDisplayPort);
WriteParam(aMsg, aParam.mScrollId);
WriteParam(aMsg, aParam.mResolution);
}
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
return (ReadParam(aMsg, aIter, &aResult->mCSSContentRect) &&
ReadParam(aMsg, aIter, &aResult->mViewport) &&
ReadParam(aMsg, aIter, &aResult->mContentRect) &&
ReadParam(aMsg, aIter, &aResult->mViewportScrollOffset) &&
ReadParam(aMsg, aIter, &aResult->mDisplayPort) &&
ReadParam(aMsg, aIter, &aResult->mScrollId) &&
ReadParam(aMsg, aIter, &aResult->mResolution));
}
};
#if !defined(MOZ_HAVE_SURFACEDESCRIPTORX11)
template <>
struct ParamTraits<mozilla::layers::SurfaceDescriptorX11> {

View File

@ -23,11 +23,13 @@
#include "gfxMatrix.h"
#include "gfxPattern.h"
#include "gfxPoint.h"
#include "gfxRect.h"
#include "nsRect.h"
#include "nsRegion.h"
#include "gfxASurface.h"
#include "jsapi.h"
#include "LayersTypes.h"
#include "FrameMetrics.h"
#ifdef _MSC_VER
#pragma warning( disable : 4800 )
@ -517,6 +519,28 @@ struct ParamTraits<gfxSize>
}
};
template<>
struct ParamTraits<gfxRect>
{
typedef gfxRect paramType;
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.x);
WriteParam(aMsg, aParam.y);
WriteParam(aMsg, aParam.width);
WriteParam(aMsg, aParam.height);
}
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
return ReadParam(aMsg, aIter, &aResult->x) &&
ReadParam(aMsg, aIter, &aResult->y) &&
ReadParam(aMsg, aIter, &aResult->width) &&
ReadParam(aMsg, aIter, &aResult->height);
}
};
template<>
struct ParamTraits<gfx3DMatrix>
{
@ -902,6 +926,34 @@ struct ParamTraits<mozilla::SerializedStructuredCloneBuffer>
}
};
template <>
struct ParamTraits<mozilla::layers::FrameMetrics>
{
typedef mozilla::layers::FrameMetrics paramType;
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.mCSSContentRect);
WriteParam(aMsg, aParam.mViewport);
WriteParam(aMsg, aParam.mContentRect);
WriteParam(aMsg, aParam.mViewportScrollOffset);
WriteParam(aMsg, aParam.mDisplayPort);
WriteParam(aMsg, aParam.mScrollId);
WriteParam(aMsg, aParam.mResolution);
}
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
return (ReadParam(aMsg, aIter, &aResult->mCSSContentRect) &&
ReadParam(aMsg, aIter, &aResult->mViewport) &&
ReadParam(aMsg, aIter, &aResult->mContentRect) &&
ReadParam(aMsg, aIter, &aResult->mViewportScrollOffset) &&
ReadParam(aMsg, aIter, &aResult->mDisplayPort) &&
ReadParam(aMsg, aIter, &aResult->mScrollId) &&
ReadParam(aMsg, aIter, &aResult->mResolution));
}
};
} /* namespace IPC */
#endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */

View File

@ -492,6 +492,23 @@ public:
}
}
virtual void HandleDoubleTap(const nsIntPoint& aPoint) MOZ_OVERRIDE
{
if (MessageLoop::current() != mUILoop) {
// We have to send this message from the "UI thread" (main
// thread).
mUILoop->PostTask(
FROM_HERE,
NewRunnableMethod(this, &RemoteContentController::HandleDoubleTap,
aPoint));
return;
}
if (mRenderFrame) {
TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
browser->HandleDoubleTap(aPoint);
}
}
void ClearRenderFrame() { mRenderFrame = nullptr; }
private:
@ -869,6 +886,14 @@ RenderFrameParent::NotifyDOMTouchListenerAdded()
}
}
void
RenderFrameParent::ZoomToRect(const gfxRect& aRect)
{
if (mPanZoomController) {
mPanZoomController->ZoomToRect(aRect);
}
}
} // namespace layout
} // namespace mozilla

View File

@ -95,6 +95,8 @@ public:
void NotifyDOMTouchListenerAdded();
void ZoomToRect(const gfxRect& aRect);
protected:
void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;