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