Bug 1131359 - Port the double-tap-to-zoom functionality of BrowserElementPanning.js to C++. r=kats

This commit is contained in:
Botond Ballo 2015-07-27 14:07:58 -04:00
parent e4fa0cf8ea
commit 7c2c79296f
5 changed files with 229 additions and 63 deletions

View File

@ -27,6 +27,7 @@
#include "mozilla/layers/APZCTreeManager.h"
#include "mozilla/layers/APZEventState.h"
#include "mozilla/layers/CompositorChild.h"
#include "mozilla/layers/DoubleTapToZoom.h"
#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/ShadowLayers.h"
#include "mozilla/layout/RenderFrameChild.h"
@ -116,7 +117,6 @@ NS_IMPL_ISUPPORTS(ContentListener, nsIDOMEventListener)
static const CSSSize kDefaultViewportSize(980, 480);
static const char BROWSER_ZOOM_TO_RECT[] = "browser-zoom-to-rect";
static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
typedef nsDataHashtable<nsUint64HashKey, TabChild*> TabChildMap;
@ -283,39 +283,6 @@ TabChildBase::ProcessUpdateFrame(const FrameMetrics& aFrameMetrics)
FrameMetrics newMetrics = aFrameMetrics;
APZCCallbackHelper::UpdateRootFrame(newMetrics);
CSSSize cssCompositedSize = newMetrics.CalculateCompositedSizeInCssPixels();
// The BrowserElementScrolling helper must know about these updated metrics
// for other functions it performs, such as double tap handling.
// Note, %f must not be used because it is locale specific!
nsString data;
data.AppendPrintf("{ \"x\" : %d", NS_lround(newMetrics.GetScrollOffset().x));
data.AppendPrintf(", \"y\" : %d", NS_lround(newMetrics.GetScrollOffset().y));
data.AppendLiteral(", \"viewport\" : ");
data.AppendLiteral("{ \"width\" : ");
data.AppendFloat(newMetrics.GetViewport().width);
data.AppendLiteral(", \"height\" : ");
data.AppendFloat(newMetrics.GetViewport().height);
data.AppendLiteral(" }");
data.AppendLiteral(", \"cssPageRect\" : ");
data.AppendLiteral("{ \"x\" : ");
data.AppendFloat(newMetrics.GetScrollableRect().x);
data.AppendLiteral(", \"y\" : ");
data.AppendFloat(newMetrics.GetScrollableRect().y);
data.AppendLiteral(", \"width\" : ");
data.AppendFloat(newMetrics.GetScrollableRect().width);
data.AppendLiteral(", \"height\" : ");
data.AppendFloat(newMetrics.GetScrollableRect().height);
data.AppendLiteral(" }");
data.AppendLiteral(", \"cssCompositedRect\" : ");
data.AppendLiteral("{ \"width\" : ");
data.AppendFloat(cssCompositedSize.width);
data.AppendLiteral(", \"height\" : ");
data.AppendFloat(cssCompositedSize.height);
data.AppendLiteral(" }");
data.AppendLiteral(" }");
DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data);
}
NS_IMETHODIMP
@ -638,23 +605,7 @@ TabChild::Observe(nsISupports *aSubject,
const char *aTopic,
const char16_t *aData)
{
if (!strcmp(aTopic, BROWSER_ZOOM_TO_RECT)) {
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
nsCOMPtr<nsITabChild> tabChild(TabChild::GetFrom(docShell));
if (tabChild == this) {
nsCOMPtr<nsIDocument> doc(GetDocument());
uint32_t presShellId;
ViewID viewId;
if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(doc->GetDocumentElement(),
&presShellId, &viewId)) {
CSSRect rect;
sscanf(NS_ConvertUTF16toUTF8(aData).get(),
"{\"x\":%f,\"y\":%f,\"w\":%f,\"h\":%f}",
&rect.x, &rect.y, &rect.width, &rect.height);
SendZoomToRect(presShellId, viewId, rect);
}
}
} else if (!strcmp(aTopic, BEFORE_FIRST_PAINT)) {
if (!strcmp(aTopic, BEFORE_FIRST_PAINT)) {
if (AsyncPanZoomEnabled()) {
nsCOMPtr<nsIDocument> subject(do_QueryInterface(aSubject));
nsCOMPtr<nsIDocument> doc(GetDocument());
@ -1764,14 +1715,18 @@ TabChild::RecvHandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifier
// Note: there is nothing to do with the modifiers here, as we are not
// synthesizing any sort of mouse event.
CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
nsString data;
data.AppendLiteral("{ \"x\" : ");
data.AppendFloat(point.x);
data.AppendLiteral(", \"y\" : ");
data.AppendFloat(point.y);
data.AppendLiteral(" }");
DispatchMessageManagerMessage(NS_LITERAL_STRING("Gesture:DoubleTap"), data);
nsCOMPtr<nsIDocument> document = GetDocument();
CSSRect zoomToRect = CalculateRectToZoomTo(document, point);
// The double-tap can be dispatched by any scroll frame (so |aGuid| could be
// the guid of any scroll frame), but the zoom-to-rect operation must be
// performed by the root content scroll frame, so query its identifiers
// for the SendZoomToRect() call rather than using the ones from |aGuid|.
uint32_t presShellId;
ViewID viewId;
if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
document->GetDocumentElement(), &presShellId, &viewId)) {
SendZoomToRect(presShellId, viewId, zoomToRect);
}
return true;
}
@ -2454,7 +2409,6 @@ TabChild::RecvDestroy()
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
const nsAttrValue::EnumTable* table =
@ -2656,9 +2610,6 @@ TabChild::InitRenderingState(const TextureFactoryIdentifier& aTextureFactoryIden
mozilla::services::GetObserverService();
if (observerService) {
observerService->AddObserver(this,
BROWSER_ZOOM_TO_RECT,
false);
observerService->AddObserver(this,
BEFORE_FIRST_PAINT,
false);

View File

@ -0,0 +1,178 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DoubleTapToZoom.h"
#include <algorithm> // for std::min, std::max
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/dom/Element.h"
#include "nsCOMPtr.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDOMHTMLLIElement.h"
#include "nsIDOMHTMLQuoteElement.h"
#include "nsIDOMWindow.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "nsIPresShell.h"
#include "nsLayoutUtils.h"
#include "nsStyleConsts.h"
namespace mozilla {
namespace layers {
// Returns the DOM element found at |aPoint|, interpreted as being relative to
// the root frame of |aShell|. If the point is inside a subdocument, returns
// an element inside the subdocument, rather than the subdocument element
// (and does so recursively).
// The implementation was adapted from nsDocument::ElementFromPoint(), with
// the notable exception that we don't pass nsLayoutUtils::IGNORE_CROSS_DOC
// to GetFrameForPoint(), so as to get the behaviour described above in the
// presence of subdocuments.
static already_AddRefed<dom::Element>
ElementFromPoint(const nsCOMPtr<nsIPresShell>& aShell,
const CSSPoint& aPoint)
{
if (nsIFrame* rootFrame = aShell->GetRootFrame()) {
if (nsIFrame* frame = nsLayoutUtils::GetFrameForPoint(rootFrame,
CSSPoint::ToAppUnits(aPoint),
nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME)) {
while (frame && (!frame->GetContent() || frame->GetContent()->IsInAnonymousSubtree())) {
frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
}
nsIContent* content = frame->GetContent();
if (content && !content->IsElement()) {
content = content->GetParent();
}
if (content) {
nsCOMPtr<dom::Element> result = content->AsElement();
return result.forget();
}
}
}
return nullptr;
}
static bool
ShouldZoomToElement(const nsCOMPtr<dom::Element>& aElement) {
if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
if (frame->GetDisplay() == NS_STYLE_DISPLAY_INLINE) {
return false;
}
}
if (aElement->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::q)) {
return false;
}
return true;
}
// Calculate the bounding rect of |aElement|, relative to the origin
// of the document associated with |aShell|.
// |aRootScrollFrame| should be the root scroll frame of the document in
// question.
// The implementation is adapted from Element::GetBoundingClientRect().
static CSSRect
GetBoundingContentRect(const nsCOMPtr<nsIPresShell>& aShell,
const nsCOMPtr<dom::Element>& aElement,
const nsIScrollableFrame* aRootScrollFrame) {
if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
return CSSRect::FromAppUnits(
nsLayoutUtils::GetAllInFlowRectsUnion(
frame,
aShell->GetRootFrame(),
nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS)
+ aRootScrollFrame->GetScrollPosition());
}
return CSSRect();
}
static bool
IsRectZoomedIn(const CSSRect& aRect, const CSSRect& aCompositedArea)
{
// This functions checks to see if the area of the rect visible in the
// composition bounds (i.e. the overlapArea variable below) is approximately
// the max area of the rect we can show.
CSSRect overlap = aCompositedArea.Intersect(aRect);
float overlapArea = overlap.width * overlap.height;
float availHeight = std::min(aRect.width * aCompositedArea.height / aCompositedArea.width,
aRect.height);
float showing = overlapArea / (aRect.width * availHeight);
float ratioW = aRect.width / aCompositedArea.width;
float ratioH = aRect.height / aCompositedArea.height;
return showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9);
}
CSSRect
CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument,
const CSSPoint& aPoint)
{
// Ensure the layout information we get is up-to-date.
aRootContentDocument->FlushPendingNotifications(Flush_Layout);
// An empty rect as return value is interpreted as "zoom out".
const CSSRect zoomOut;
nsCOMPtr<nsIPresShell> shell = aRootContentDocument->GetShell();
if (!shell) {
return zoomOut;
}
nsIScrollableFrame* rootScrollFrame = shell->GetRootScrollFrameAsScrollable();
if (!rootScrollFrame) {
return zoomOut;
}
nsCOMPtr<dom::Element> element = ElementFromPoint(shell, aPoint);
if (!element) {
return zoomOut;
}
while (element && !ShouldZoomToElement(element)) {
element = element->GetParentElement();
}
if (!element) {
return zoomOut;
}
FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame);
CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels());
const CSSCoord margin = 15;
CSSRect rect = GetBoundingContentRect(shell, element, rootScrollFrame);
rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin),
rect.y,
rect.width + 2 * margin,
rect.height);
// Constrict the rect to the screen's right edge
rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.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 (IsRectZoomedIn(rect, compositedArea)) {
return zoomOut;
}
CSSRect rounded(rect);
rounded.Round();
// 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 the page when double-tapping
// to zoom in (bug 761721). The 1.2 multiplier is just a little fuzz to
// compensate for 'rect' including horizontal margins but not vertical ones.
CSSCoord cssTapY = metrics.GetScrollOffset().y + aPoint.y;
if ((rect.height > rounded.height) && (cssTapY > rounded.y + (rounded.height * 1.2))) {
rounded.y = cssTapY - (rounded.height / 2);
}
return rounded;
}
}
}

View File

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_layers_DoubleTapToZoom_h
#define mozilla_layers_DoubleTapToZoom_h
#include "Units.h"
class nsIDocument;
template<class T> class nsCOMPtr;
namespace mozilla {
namespace layers {
/**
* For a double tap at |aPoint|, return the rect to which the browser
* should zoom in response, or an empty rect if the browser should zoom out.
* |aDocument| should be the root content document for the content that was
* tapped.
*/
CSSRect CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument,
const CSSPoint& aPoint);
}
}
#endif /* mozilla_layers_DoubleTapToZoom_h */

View File

@ -104,6 +104,7 @@ EXPORTS.mozilla.layers += [
'apz/util/APZEventState.h',
'apz/util/APZThreadUtils.h',
'apz/util/ChromeProcessController.h',
'apz/util/DoubleTapToZoom.h',
'apz/util/InputAPZContext.h',
'AtomicRefCountedWithFinalize.h',
'AxisPhysicsModel.h',
@ -247,6 +248,7 @@ UNIFIED_SOURCES += [
'apz/util/APZEventState.cpp',
'apz/util/APZThreadUtils.cpp',
'apz/util/ChromeProcessController.cpp',
'apz/util/DoubleTapToZoom.cpp',
'apz/util/InputAPZContext.cpp',
'AxisPhysicsModel.cpp',
'AxisPhysicsMSDModel.cpp',
@ -384,6 +386,7 @@ include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/docshell/base', # for nsDocShell.h
'/layout/base', # for TouchManager.h
'/layout/generic', # for nsTextFrame.h
]
FINAL_LIBRARY = 'xul'

View File

@ -164,6 +164,11 @@ struct CSSPixel {
NSAppUnitsToFloatPixels(aPoint.y, float(AppUnitsPerCSSPixel())));
}
static CSSSize FromAppUnits(const nsSize& aSize) {
return CSSSize(NSAppUnitsToFloatPixels(aSize.width, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aSize.height, float(AppUnitsPerCSSPixel())));
}
static CSSRect FromAppUnits(const nsRect& aRect) {
return CSSRect(NSAppUnitsToFloatPixels(aRect.x, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aRect.y, float(AppUnitsPerCSSPixel())),