Coverage Report

Created: 2018-09-25 14:53

/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
}