Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "DoubleTapToZoom.h"
7 :
8 : #include <algorithm> // for std::min, std::max
9 :
10 : #include "mozilla/AlreadyAddRefed.h"
11 : #include "mozilla/dom/Element.h"
12 : #include "nsCOMPtr.h"
13 : #include "nsIContent.h"
14 : #include "nsIDocument.h"
15 : #include "nsIDOMHTMLLIElement.h"
16 : #include "nsIDOMHTMLQuoteElement.h"
17 : #include "nsIDOMWindow.h"
18 : #include "nsIFrame.h"
19 : #include "nsIFrameInlines.h"
20 : #include "nsIPresShell.h"
21 : #include "nsLayoutUtils.h"
22 : #include "nsStyleConsts.h"
23 :
24 : namespace mozilla {
25 : namespace layers {
26 :
27 : // Returns the DOM element found at |aPoint|, interpreted as being relative to
28 : // the root frame of |aShell|. If the point is inside a subdocument, returns
29 : // an element inside the subdocument, rather than the subdocument element
30 : // (and does so recursively).
31 : // The implementation was adapted from nsDocument::ElementFromPoint(), with
32 : // the notable exception that we don't pass nsLayoutUtils::IGNORE_CROSS_DOC
33 : // to GetFrameForPoint(), so as to get the behaviour described above in the
34 : // presence of subdocuments.
35 : static already_AddRefed<dom::Element>
36 0 : ElementFromPoint(const nsCOMPtr<nsIPresShell>& aShell,
37 : const CSSPoint& aPoint)
38 : {
39 0 : if (nsIFrame* rootFrame = aShell->GetRootFrame()) {
40 0 : if (nsIFrame* frame = nsLayoutUtils::GetFrameForPoint(rootFrame,
41 0 : CSSPoint::ToAppUnits(aPoint),
42 : nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
43 0 : nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME)) {
44 0 : while (frame && (!frame->GetContent() || frame->GetContent()->IsInAnonymousSubtree())) {
45 0 : frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
46 : }
47 0 : nsIContent* content = frame->GetContent();
48 0 : if (content && !content->IsElement()) {
49 0 : content = content->GetParent();
50 : }
51 0 : if (content) {
52 0 : nsCOMPtr<dom::Element> result = content->AsElement();
53 0 : return result.forget();
54 : }
55 : }
56 : }
57 0 : return nullptr;
58 : }
59 :
60 : static bool
61 0 : ShouldZoomToElement(const nsCOMPtr<dom::Element>& aElement) {
62 0 : if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
63 0 : if (frame->GetDisplay() == StyleDisplay::Inline) {
64 0 : return false;
65 : }
66 : }
67 0 : if (aElement->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::q)) {
68 0 : return false;
69 : }
70 0 : return true;
71 : }
72 :
73 : static bool
74 0 : IsRectZoomedIn(const CSSRect& aRect, const CSSRect& aCompositedArea)
75 : {
76 : // This functions checks to see if the area of the rect visible in the
77 : // composition bounds (i.e. the overlapArea variable below) is approximately
78 : // the max area of the rect we can show.
79 0 : CSSRect overlap = aCompositedArea.Intersect(aRect);
80 0 : float overlapArea = overlap.width * overlap.height;
81 0 : float availHeight = std::min(aRect.width * aCompositedArea.height / aCompositedArea.width,
82 0 : aRect.height);
83 0 : float showing = overlapArea / (aRect.width * availHeight);
84 0 : float ratioW = aRect.width / aCompositedArea.width;
85 0 : float ratioH = aRect.height / aCompositedArea.height;
86 :
87 0 : return showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9);
88 : }
89 :
90 : CSSRect
91 0 : CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument,
92 : const CSSPoint& aPoint)
93 : {
94 : // Ensure the layout information we get is up-to-date.
95 0 : aRootContentDocument->FlushPendingNotifications(FlushType::Layout);
96 :
97 : // An empty rect as return value is interpreted as "zoom out".
98 0 : const CSSRect zoomOut;
99 :
100 0 : nsCOMPtr<nsIPresShell> shell = aRootContentDocument->GetShell();
101 0 : if (!shell) {
102 0 : return zoomOut;
103 : }
104 :
105 0 : nsIScrollableFrame* rootScrollFrame = shell->GetRootScrollFrameAsScrollable();
106 0 : if (!rootScrollFrame) {
107 0 : return zoomOut;
108 : }
109 :
110 0 : nsCOMPtr<dom::Element> element = ElementFromPoint(shell, aPoint);
111 0 : if (!element) {
112 0 : return zoomOut;
113 : }
114 :
115 0 : while (element && !ShouldZoomToElement(element)) {
116 0 : element = element->GetParentElement();
117 : }
118 :
119 0 : if (!element) {
120 0 : return zoomOut;
121 : }
122 :
123 0 : FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame);
124 0 : CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels());
125 0 : const CSSCoord margin = 15;
126 0 : CSSRect rect = nsLayoutUtils::GetBoundingContentRect(element, rootScrollFrame);
127 :
128 : // If the element is taller than the visible area of the page scale
129 : // the height of the |rect| so that it has the same aspect ratio as
130 : // the root frame. The clipped |rect| is centered on the y value of
131 : // the touch point. This allows tall narrow elements to be zoomed.
132 0 : if (!rect.IsEmpty() && compositedArea.width > 0.0f) {
133 0 : const float widthRatio = rect.width / compositedArea.width;
134 0 : float targetHeight = compositedArea.height * widthRatio;
135 0 : if (widthRatio < 0.9 && targetHeight < rect.height) {
136 0 : const CSSPoint scrollPoint = CSSPoint::FromAppUnits(rootScrollFrame->GetScrollPosition());
137 0 : float newY = aPoint.y + scrollPoint.y - (targetHeight * 0.5f);
138 0 : if ((newY + targetHeight) > (rect.y + rect.height)) {
139 0 : rect.y += rect.height - targetHeight;
140 0 : } else if (newY > rect.y) {
141 0 : rect.y = newY;
142 : }
143 0 : rect.height = targetHeight;
144 : }
145 : }
146 :
147 0 : rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin),
148 : rect.y,
149 0 : rect.width + 2 * margin,
150 : rect.height);
151 : // Constrict the rect to the screen's right edge
152 0 : rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.x);
153 :
154 : // If the rect is already taking up most of the visible area and is
155 : // stretching the width of the page, then we want to zoom out instead.
156 0 : if (IsRectZoomedIn(rect, compositedArea)) {
157 0 : return zoomOut;
158 : }
159 :
160 0 : CSSRect rounded(rect);
161 0 : rounded.Round();
162 :
163 : // If the block we're zooming to is really tall, and the user double-tapped
164 : // more than a screenful of height from the top of it, then adjust the
165 : // y-coordinate so that we center the actual point the user double-tapped
166 : // upon. This prevents flying to the top of the page when double-tapping
167 : // to zoom in (bug 761721). The 1.2 multiplier is just a little fuzz to
168 : // compensate for 'rect' including horizontal margins but not vertical ones.
169 0 : CSSCoord cssTapY = metrics.GetScrollOffset().y + aPoint.y;
170 0 : if ((rect.height > rounded.height) && (cssTapY > rounded.y + (rounded.height * 1.2))) {
171 0 : rounded.y = cssTapY - (rounded.height / 2);
172 : }
173 :
174 0 : return rounded;
175 : }
176 :
177 : }
178 : }
|