Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/base/AccessibleCaret.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 "AccessibleCaret.h"
8
9
#include "AccessibleCaretLogger.h"
10
#include "mozilla/FloatingPoint.h"
11
#include "mozilla/StaticPrefs.h"
12
#include "mozilla/ToString.h"
13
#include "nsCanvasFrame.h"
14
#include "nsCaret.h"
15
#include "nsCSSFrameConstructor.h"
16
#include "nsDOMTokenList.h"
17
#include "nsIFrame.h"
18
#include "nsPlaceholderFrame.h"
19
20
namespace mozilla {
21
using namespace dom;
22
23
#undef AC_LOG
24
#define AC_LOG(message, ...)                                                   \
25
0
  AC_LOG_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
26
27
#undef AC_LOGV
28
#define AC_LOGV(message, ...)                                                  \
29
  AC_LOGV_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
30
31
NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener)
32
33
NS_NAMED_LITERAL_STRING(AccessibleCaret::sTextOverlayElementId, "text-overlay");
34
NS_NAMED_LITERAL_STRING(AccessibleCaret::sCaretImageElementId, "image");
35
36
0
#define AC_PROCESS_ENUM_TO_STREAM(e) case(e): aStream << #e; break;
37
std::ostream&
38
operator<<(std::ostream& aStream, const AccessibleCaret::Appearance& aAppearance)
39
0
{
40
0
  using Appearance = AccessibleCaret::Appearance;
41
0
  switch (aAppearance) {
42
0
    AC_PROCESS_ENUM_TO_STREAM(Appearance::None);
43
0
    AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal);
44
0
    AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown);
45
0
    AC_PROCESS_ENUM_TO_STREAM(Appearance::Left);
46
0
    AC_PROCESS_ENUM_TO_STREAM(Appearance::Right);
47
0
  }
48
0
  return aStream;
49
0
}
50
51
std::ostream&
52
operator<<(std::ostream& aStream,
53
           const AccessibleCaret::PositionChangedResult& aResult)
54
0
{
55
0
  using PositionChangedResult = AccessibleCaret::PositionChangedResult;
56
0
  switch (aResult) {
57
0
    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged);
58
0
    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Changed);
59
0
    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
60
0
  }
61
0
  return aStream;
62
0
}
63
#undef AC_PROCESS_ENUM_TO_STREAM
64
65
// -----------------------------------------------------------------------------
66
// Implementation of AccessibleCaret methods
67
68
AccessibleCaret::AccessibleCaret(nsIPresShell* aPresShell)
69
  : mPresShell(aPresShell)
70
0
{
71
0
  // Check all resources required.
72
0
  if (mPresShell) {
73
0
    MOZ_ASSERT(RootFrame());
74
0
    MOZ_ASSERT(mPresShell->GetDocument());
75
0
    InjectCaretElement(mPresShell->GetDocument());
76
0
  }
77
0
}
78
79
AccessibleCaret::~AccessibleCaret()
80
0
{
81
0
  if (mPresShell) {
82
0
    RemoveCaretElement(mPresShell->GetDocument());
83
0
  }
84
0
}
85
86
void
87
AccessibleCaret::SetAppearance(Appearance aAppearance)
88
0
{
89
0
  if (mAppearance == aAppearance) {
90
0
    return;
91
0
  }
92
0
93
0
  ErrorResult rv;
94
0
  CaretElement().ClassList()->Remove(AppearanceString(mAppearance), rv);
95
0
  MOZ_ASSERT(!rv.Failed(), "Remove old appearance failed!");
96
0
97
0
  CaretElement().ClassList()->Add(AppearanceString(aAppearance), rv);
98
0
  MOZ_ASSERT(!rv.Failed(), "Add new appearance failed!");
99
0
100
0
  AC_LOG("%s: %s -> %s", __FUNCTION__, ToString(mAppearance).c_str(),
101
0
         ToString(aAppearance).c_str());
102
0
103
0
  mAppearance = aAppearance;
104
0
105
0
  // Need to reset rect since the cached rect will be compared in SetPosition.
106
0
  if (mAppearance == Appearance::None) {
107
0
    mImaginaryCaretRect = nsRect();
108
0
    mZoomLevel = 0.0f;
109
0
  }
110
0
}
111
112
/* static */ nsAutoString
113
AccessibleCaret::AppearanceString(Appearance aAppearance)
114
0
{
115
0
  nsAutoString string;
116
0
  switch (aAppearance) {
117
0
  case Appearance::None:
118
0
  case Appearance::NormalNotShown:
119
0
    string = NS_LITERAL_STRING("none");
120
0
    break;
121
0
  case Appearance::Normal:
122
0
    string = NS_LITERAL_STRING("normal");
123
0
    break;
124
0
  case Appearance::Right:
125
0
    string = NS_LITERAL_STRING("right");
126
0
    break;
127
0
  case Appearance::Left:
128
0
    string = NS_LITERAL_STRING("left");
129
0
    break;
130
0
  }
131
0
  return string;
132
0
}
133
134
bool
135
AccessibleCaret::Intersects(const AccessibleCaret& aCaret) const
136
0
{
137
0
  MOZ_ASSERT(mPresShell == aCaret.mPresShell);
138
0
139
0
  if (!IsVisuallyVisible() || !aCaret.IsVisuallyVisible()) {
140
0
    return false;
141
0
  }
142
0
143
0
  nsRect rect = nsLayoutUtils::GetRectRelativeToFrame(&CaretElement(), RootFrame());
144
0
  nsRect rhsRect = nsLayoutUtils::GetRectRelativeToFrame(&aCaret.CaretElement(), RootFrame());
145
0
  return rect.Intersects(rhsRect);
146
0
}
147
148
bool
149
AccessibleCaret::Contains(const nsPoint& aPoint, TouchArea aTouchArea) const
150
0
{
151
0
  if (!IsVisuallyVisible()) {
152
0
    return false;
153
0
  }
154
0
155
0
  nsRect textOverlayRect =
156
0
    nsLayoutUtils::GetRectRelativeToFrame(TextOverlayElement(), RootFrame());
157
0
  nsRect caretImageRect =
158
0
    nsLayoutUtils::GetRectRelativeToFrame(CaretImageElement(), RootFrame());
159
0
160
0
  if (aTouchArea == TouchArea::CaretImage) {
161
0
    return caretImageRect.Contains(aPoint);
162
0
  }
163
0
164
0
  MOZ_ASSERT(aTouchArea == TouchArea::Full, "Unexpected TouchArea type!");
165
0
  return textOverlayRect.Contains(aPoint) || caretImageRect.Contains(aPoint);
166
0
}
167
168
void
169
AccessibleCaret::EnsureApzAware()
170
0
{
171
0
  // If the caret element was cloned, the listener might have been lost. So
172
0
  // if that's the case we register a dummy listener if there isn't one on
173
0
  // the element already.
174
0
  if (!CaretElement().IsApzAware()) {
175
0
    // FIXME(emilio): Is this needed anymore?
176
0
    CaretElement().AddEventListener(NS_LITERAL_STRING("touchstart"),
177
0
                                    mDummyTouchListener, false);
178
0
  }
179
0
}
180
181
void
182
AccessibleCaret::InjectCaretElement(nsIDocument* aDocument)
183
0
{
184
0
  ErrorResult rv;
185
0
  RefPtr<Element> element = CreateCaretElement(aDocument);
186
0
  mCaretElementHolder = aDocument->InsertAnonymousContent(*element, rv);
187
0
188
0
  MOZ_ASSERT(!rv.Failed(), "Insert anonymous content should not fail!");
189
0
  MOZ_ASSERT(mCaretElementHolder, "We must have anonymous content!");
190
0
191
0
  // InsertAnonymousContent will clone the element to make an AnonymousContent.
192
0
  // Since event listeners are not being cloned when cloning a node, we need to
193
0
  // add the listener here.
194
0
  EnsureApzAware();
195
0
}
196
197
already_AddRefed<Element>
198
AccessibleCaret::CreateCaretElement(nsIDocument* aDocument) const
199
0
{
200
0
  // Content structure of AccessibleCaret
201
0
  // <div class="moz-accessiblecaret">  <- CaretElement()
202
0
  //   <div id="text-overlay"           <- TextOverlayElement()
203
0
  //   <div id="image">                 <- CaretImageElement()
204
0
205
0
  ErrorResult rv;
206
0
  RefPtr<Element> parent = aDocument->CreateHTMLElement(nsGkAtoms::div);
207
0
  parent->ClassList()->Add(NS_LITERAL_STRING("moz-accessiblecaret"), rv);
208
0
  parent->ClassList()->Add(NS_LITERAL_STRING("none"), rv);
209
0
210
0
  auto CreateAndAppendChildElement = [aDocument, &parent](
211
0
    const nsLiteralString& aElementId)
212
0
  {
213
0
    RefPtr<Element> child = aDocument->CreateHTMLElement(nsGkAtoms::div);
214
0
    child->SetAttr(kNameSpaceID_None, nsGkAtoms::id, aElementId, true);
215
0
    parent->AppendChildTo(child, false);
216
0
  };
217
0
218
0
  CreateAndAppendChildElement(sTextOverlayElementId);
219
0
  CreateAndAppendChildElement(sCaretImageElementId);
220
0
221
0
  return parent.forget();
222
0
}
223
224
void
225
AccessibleCaret::RemoveCaretElement(nsIDocument* aDocument)
226
0
{
227
0
  CaretElement().RemoveEventListener(NS_LITERAL_STRING("touchstart"),
228
0
                                     mDummyTouchListener, false);
229
0
230
0
  // FIXME(emilio): This shouldn't be needed and should be done by
231
0
  // ContentRemoved via RemoveAnonymousContent, but the current setup tears down
232
0
  // the accessible caret manager after the shell has stopped observing the
233
0
  // document, but before the frame tree has gone away. This could clearly be
234
0
  // better...
235
0
  if (nsIFrame* frame = CaretElement().GetPrimaryFrame()) {
236
0
    if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
237
0
      frame = frame->GetPlaceholderFrame();
238
0
    }
239
0
    nsAutoScriptBlocker scriptBlocker;
240
0
    frame->GetParent()->RemoveFrame(nsIFrame::kPrincipalList, frame);
241
0
  }
242
0
243
0
  aDocument->RemoveAnonymousContent(*mCaretElementHolder, IgnoreErrors());
244
0
}
245
246
AccessibleCaret::PositionChangedResult
247
AccessibleCaret::SetPosition(nsIFrame* aFrame, int32_t aOffset)
248
0
{
249
0
  if (!CustomContentContainerFrame()) {
250
0
    return PositionChangedResult::NotChanged;
251
0
  }
252
0
253
0
  nsRect imaginaryCaretRectInFrame =
254
0
    nsCaret::GetGeometryForFrame(aFrame, aOffset, nullptr);
255
0
256
0
  imaginaryCaretRectInFrame =
257
0
    nsLayoutUtils::ClampRectToScrollFrames(aFrame, imaginaryCaretRectInFrame);
258
0
259
0
  if (imaginaryCaretRectInFrame.IsEmpty()) {
260
0
    // Don't bother to set the caret position since it's invisible.
261
0
    mImaginaryCaretRect = nsRect();
262
0
    mZoomLevel = 0.0f;
263
0
    return PositionChangedResult::Invisible;
264
0
  }
265
0
266
0
  nsRect imaginaryCaretRect = imaginaryCaretRectInFrame;
267
0
  nsLayoutUtils::TransformRect(aFrame, RootFrame(), imaginaryCaretRect);
268
0
  float zoomLevel = GetZoomLevel();
269
0
270
0
  if (imaginaryCaretRect.IsEqualEdges(mImaginaryCaretRect) &&
271
0
      FuzzyEqualsMultiplicative(zoomLevel, mZoomLevel)) {
272
0
    return PositionChangedResult::NotChanged;
273
0
  }
274
0
275
0
  mImaginaryCaretRect = imaginaryCaretRect;
276
0
  mZoomLevel = zoomLevel;
277
0
278
0
  // SetCaretElementStyle() requires the input rect relative to container frame.
279
0
  nsRect imaginaryCaretRectInContainerFrame = imaginaryCaretRectInFrame;
280
0
  nsLayoutUtils::TransformRect(aFrame, CustomContentContainerFrame(),
281
0
                               imaginaryCaretRectInContainerFrame);
282
0
  SetCaretElementStyle(imaginaryCaretRectInContainerFrame, mZoomLevel);
283
0
284
0
  return PositionChangedResult::Changed;
285
0
}
286
287
nsIFrame*
288
AccessibleCaret::CustomContentContainerFrame() const
289
0
{
290
0
  nsCanvasFrame* canvasFrame = mPresShell->GetCanvasFrame();
291
0
  Element* container = canvasFrame->GetCustomContentContainer();
292
0
  nsIFrame* containerFrame = container->GetPrimaryFrame();
293
0
  return containerFrame;
294
0
}
295
296
void
297
AccessibleCaret::SetCaretElementStyle(const nsRect& aRect, float aZoomLevel)
298
0
{
299
0
  nsPoint position = CaretElementPosition(aRect);
300
0
  nsAutoString styleStr;
301
0
  styleStr.AppendPrintf("left: %dpx; top: %dpx; "
302
0
                        "width: ",
303
0
                        nsPresContext::AppUnitsToIntCSSPixels(position.x),
304
0
                        nsPresContext::AppUnitsToIntCSSPixels(position.y));
305
0
  // We can't use AppendPrintf here, because it does locale-specific
306
0
  // formatting of floating-point values.
307
0
  styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_width() / aZoomLevel);
308
0
  styleStr.AppendLiteral("px; height: ");
309
0
  styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_height() / aZoomLevel);
310
0
  styleStr.AppendLiteral("px; margin-left: ");
311
0
  styleStr.AppendFloat(
312
0
    StaticPrefs::layout_accessiblecaret_margin_left() / aZoomLevel);
313
0
  styleStr.AppendLiteral("px");
314
0
315
0
  CaretElement().SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr, true);
316
0
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
317
0
318
0
  // Set style string for children.
319
0
  SetTextOverlayElementStyle(aRect, aZoomLevel);
320
0
  SetCaretImageElementStyle(aRect, aZoomLevel);
321
0
}
322
323
void
324
AccessibleCaret::SetTextOverlayElementStyle(const nsRect& aRect,
325
                                            float aZoomLevel)
326
0
{
327
0
  nsAutoString styleStr;
328
0
  styleStr.AppendPrintf("height: %dpx;",
329
0
                        nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
330
0
  TextOverlayElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
331
0
                                true);
332
0
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
333
0
}
334
335
void
336
AccessibleCaret::SetCaretImageElementStyle(const nsRect& aRect,
337
                                           float aZoomLevel)
338
0
{
339
0
  nsAutoString styleStr;
340
0
  styleStr.AppendPrintf("margin-top: %dpx;",
341
0
                        nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
342
0
  CaretImageElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
343
0
                               true);
344
0
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
345
0
}
346
347
float
348
AccessibleCaret::GetZoomLevel()
349
0
{
350
0
  // Full zoom on desktop.
351
0
  float fullZoom = mPresShell->GetPresContext()->GetFullZoom();
352
0
353
0
  // Pinch-zoom on fennec.
354
0
  float resolution = mPresShell->GetCumulativeResolution();
355
0
356
0
  return fullZoom * resolution;
357
0
}
358
359
} // namespace mozilla