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 #ifndef AccessibleCaret_h__
8 #define AccessibleCaret_h__
9 
10 #include "mozilla/Attributes.h"
11 #include "mozilla/dom/AnonymousContent.h"
12 #include "mozilla/dom/Element.h"
13 #include "nsCOMPtr.h"
14 #include "nsIDOMEventListener.h"
15 #include "nsISupportsBase.h"
16 #include "nsISupportsImpl.h"
17 #include "nsLiteralString.h"
18 #include "nsRect.h"
19 #include "mozilla/RefPtr.h"
20 #include "nsString.h"
21 
22 class nsIDocument;
23 class nsIFrame;
24 class nsIPresShell;
25 struct nsPoint;
26 
27 namespace mozilla {
28 
29 // -----------------------------------------------------------------------------
30 // Upon the creation of AccessibleCaret, it will insert DOM Element as an
31 // anonymous content containing the caret image. The caret appearance and
32 // position can be controlled by SetAppearance() and SetPosition().
33 //
34 // All the rect or point are relative to root frame except being specified
35 // explicitly.
36 //
37 // None of the methods in AccessibleCaret will flush layout or style. To ensure
38 // that SetPosition() works correctly, the caller must make sure the layout is
39 // up to date.
40 //
41 // Please see the wiki page for more information.
42 // https://wiki.mozilla.org/AccessibleCaret
43 //
44 class AccessibleCaret {
45  public:
46   explicit AccessibleCaret(nsIPresShell* aPresShell);
47   virtual ~AccessibleCaret();
48 
49   // This enumeration representing the visibility and visual style of an
50   // AccessibleCaret.
51   //
52   // Use SetAppearance() to change the appearance, and use GetAppearance() to
53   // get the current appearance.
54   enum class Appearance : uint8_t {
55     // Do not display the caret at all.
56     None,
57 
58     // Display the caret in default style.
59     Normal,
60 
61     // The caret should be displayed logically but it is kept invisible to the
62     // user. This enum is the only difference between "logically visible" and
63     // "visually visible". It can be used for reasons such as:
64     // 1. Out of scroll port.
65     // 2. For UX requirement such as hide a caret in an empty text area.
66     NormalNotShown,
67 
68     // Display the caret which is tilted to the left.
69     Left,
70 
71     // Display the caret which is tilted to the right.
72     Right
73   };
74 
75   friend std::ostream& operator<<(std::ostream& aStream,
76                                   const Appearance& aAppearance);
77 
GetAppearance()78   Appearance GetAppearance() const { return mAppearance; }
79 
80   virtual void SetAppearance(Appearance aAppearance);
81 
82   // Return true if current appearance is either Normal, NormalNotShown, Left,
83   // or Right.
IsLogicallyVisible()84   bool IsLogicallyVisible() const { return mAppearance != Appearance::None; }
85 
86   // Return true if current appearance is either Normal, Left, or Right.
IsVisuallyVisible()87   bool IsVisuallyVisible() const {
88     return (mAppearance != Appearance::None) &&
89            (mAppearance != Appearance::NormalNotShown);
90   }
91 
92   // Set true to enable the "Text Selection Bar" described in "Text Selection
93   // Visual Spec" in bug 921965.
94   virtual void SetSelectionBarEnabled(bool aEnabled);
95 
96   // This enumeration representing the result returned by SetPosition().
97   enum class PositionChangedResult : uint8_t {
98     // Position is not changed.
99     NotChanged,
100 
101     // Position or zoom level is changed.
102     Changed,
103 
104     // Position is out of scroll port.
105     Invisible
106   };
107 
108   friend std::ostream& operator<<(std::ostream& aStream,
109                                   const PositionChangedResult& aResult);
110 
111   virtual PositionChangedResult SetPosition(nsIFrame* aFrame, int32_t aOffset);
112 
113   // Does two AccessibleCarets overlap?
114   bool Intersects(const AccessibleCaret& aCaret) const;
115 
116   // Is the point within the caret's rect? The point should be relative to root
117   // frame.
118   enum class TouchArea {
119     Full,  // Contains both text overlay and caret image.
120     CaretImage
121   };
122   bool Contains(const nsPoint& aPoint, TouchArea aTouchArea) const;
123 
124   // The geometry center of the imaginary caret (nsCaret) to which this
125   // AccessibleCaret is attached. It is needed when dragging the caret.
LogicalPosition()126   nsPoint LogicalPosition() const { return mImaginaryCaretRect.Center(); }
127 
128   // Element for 'Intersects' test. Container of image and bar elements.
CaretElement()129   dom::Element* CaretElement() const {
130     return mCaretElementHolder->GetContentNode();
131   }
132 
133   // Ensures that the caret element is made "APZ aware" so that the APZ code
134   // doesn't scroll the page when the user is trying to drag the caret.
135   void EnsureApzAware();
136 
137  protected:
138   // Argument aRect should be relative to CustomContentContainerFrame().
139   void SetCaretElementStyle(const nsRect& aRect, float aZoomLevel);
140   void SetTextOverlayElementStyle(const nsRect& aRect, float aZoomLevel);
141   void SetCaretImageElementStyle(const nsRect& aRect, float aZoomLevel);
142   void SetSelectionBarElementStyle(const nsRect& aRect, float aZoomLevel);
143 
144   // Get current zoom level.
145   float GetZoomLevel();
146 
147   // Element which contains the text overly for the 'Contains' test.
TextOverlayElement()148   dom::Element* TextOverlayElement() const {
149     return mCaretElementHolder->GetElementById(sTextOverlayElementId);
150   }
151 
152   // Element which contains the caret image for 'Contains' test.
CaretImageElement()153   dom::Element* CaretImageElement() const {
154     return mCaretElementHolder->GetElementById(sCaretImageElementId);
155   }
156 
157   // Element which represents the text selection bar.
SelectionBarElement()158   dom::Element* SelectionBarElement() const {
159     return mCaretElementHolder->GetElementById(sSelectionBarElementId);
160   }
161 
RootFrame()162   nsIFrame* RootFrame() const { return mPresShell->GetRootFrame(); }
163 
164   nsIFrame* CustomContentContainerFrame() const;
165 
166   // Transform Appearance to CSS id used in ua.css.
167   static nsAutoString AppearanceString(Appearance aAppearance);
168 
169   already_AddRefed<dom::Element> CreateCaretElement(
170       nsIDocument* aDocument) const;
171 
172   // Inject caret element into custom content container.
173   void InjectCaretElement(nsIDocument* aDocument);
174 
175   // Remove caret element from custom content container.
176   void RemoveCaretElement(nsIDocument* aDocument);
177 
178   // The top-center of the imaginary caret to which this AccessibleCaret is
179   // attached.
CaretElementPosition(const nsRect & aRect)180   static nsPoint CaretElementPosition(const nsRect& aRect) {
181     return aRect.TopLeft() + nsPoint(aRect.width / 2, 0);
182   }
183 
184   class DummyTouchListener final : public nsIDOMEventListener {
185    public:
186     NS_DECL_ISUPPORTS
HandleEvent(nsIDOMEvent * aEvent)187     NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override { return NS_OK; }
188 
189    private:
~DummyTouchListener()190     virtual ~DummyTouchListener(){};
191   };
192 
193   // Member variables
194   Appearance mAppearance = Appearance::None;
195 
196   bool mSelectionBarEnabled = false;
197 
198   // AccessibleCaretManager owns us by a UniquePtr. When it's terminated by
199   // AccessibleCaretEventHub::Terminate() which is called in
200   // PresShell::Destroy(), it frees us automatically. No need to worry if we
201   // outlive mPresShell.
202   nsIPresShell* MOZ_NON_OWNING_REF const mPresShell = nullptr;
203 
204   RefPtr<dom::AnonymousContent> mCaretElementHolder;
205 
206   // mImaginaryCaretRect is relative to root frame.
207   nsRect mImaginaryCaretRect;
208 
209   // Cache current zoom level to determine whether position is changed.
210   float mZoomLevel = 0.0f;
211 
212   // A no-op touch-start listener which prevents APZ from panning when dragging
213   // the caret.
214   RefPtr<DummyTouchListener> mDummyTouchListener{new DummyTouchListener()};
215 
216   // Static class variables
217   static float sWidth;
218   static float sHeight;
219   static float sMarginLeft;
220   static float sBarWidth;
221   static const nsLiteralString sTextOverlayElementId;
222   static const nsLiteralString sCaretImageElementId;
223   static const nsLiteralString sSelectionBarElementId;
224 
225 };  // class AccessibleCaret
226 
227 std::ostream& operator<<(std::ostream& aStream,
228                          const AccessibleCaret::Appearance& aAppearance);
229 
230 std::ostream& operator<<(std::ostream& aStream,
231                          const AccessibleCaret::PositionChangedResult& aResult);
232 
233 }  // namespace mozilla
234 
235 #endif  // AccessibleCaret_h__
236