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