1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: sw=2 ts=8 et :
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #ifndef mozilla_ContentCache_h
9 #define mozilla_ContentCache_h
10 
11 #include <stdint.h>
12 
13 #include "mozilla/Assertions.h"
14 #include "mozilla/CheckedInt.h"
15 #include "mozilla/EventForwards.h"
16 #include "mozilla/WritingModes.h"
17 #include "nsIWidget.h"
18 #include "nsString.h"
19 #include "nsTArray.h"
20 #include "Units.h"
21 
22 namespace mozilla {
23 
24 class ContentCacheInParent;
25 
26 /**
27  * ContentCache stores various information of the child content.
28  * This class has members which are necessary both in parent process and
29  * content process.
30  */
31 
32 class ContentCache
33 {
34 public:
35   typedef InfallibleTArray<LayoutDeviceIntRect> RectArray;
36   typedef widget::IMENotification IMENotification;
37 
38   ContentCache();
39 
40 protected:
41   // Whole text in the target
42   nsString mText;
43 
44   // Start offset of the composition string.
45   uint32_t mCompositionStart;
46 
47   enum
48   {
49     ePrevCharRect = 1,
50     eNextCharRect = 0
51   };
52 
53   struct Selection final
54   {
55     // Following values are offset in "flat text".
56     uint32_t mAnchor;
57     uint32_t mFocus;
58 
59     WritingMode mWritingMode;
60 
61     // Character rects at previous and next character of mAnchor and mFocus.
62     // The reason why ContentCache needs to store each previous character of
63     // them is IME may query character rect of the last character of a line
64     // when caret is at the end of the line.
65     // Note that use ePrevCharRect and eNextCharRect for accessing each item.
66     LayoutDeviceIntRect mAnchorCharRects[2];
67     LayoutDeviceIntRect mFocusCharRects[2];
68 
69     // Whole rect of selected text. This is empty if the selection is collapsed.
70     LayoutDeviceIntRect mRect;
71 
Selectionfinal72     Selection()
73       : mAnchor(UINT32_MAX)
74       , mFocus(UINT32_MAX)
75     {
76     }
77 
Clearfinal78     void Clear()
79     {
80       mAnchor = mFocus = UINT32_MAX;
81       mWritingMode = WritingMode();
82       ClearAnchorCharRects();
83       ClearFocusCharRects();
84       mRect.SetEmpty();
85     }
86 
ClearAnchorCharRectsfinal87     void ClearAnchorCharRects()
88     {
89       for (size_t i = 0; i < ArrayLength(mAnchorCharRects); i++) {
90         mAnchorCharRects[i].SetEmpty();
91       }
92     }
ClearFocusCharRectsfinal93     void ClearFocusCharRects()
94     {
95       for (size_t i = 0; i < ArrayLength(mFocusCharRects); i++) {
96         mFocusCharRects[i].SetEmpty();
97       }
98     }
99 
IsValidfinal100     bool IsValid() const
101     {
102       return mAnchor != UINT32_MAX && mFocus != UINT32_MAX;
103     }
Collapsedfinal104     bool Collapsed() const
105     {
106       NS_ASSERTION(IsValid(),
107                    "The caller should check if the selection is valid");
108       return mFocus == mAnchor;
109     }
Reversedfinal110     bool Reversed() const
111     {
112       NS_ASSERTION(IsValid(),
113                    "The caller should check if the selection is valid");
114       return mFocus < mAnchor;
115     }
StartOffsetfinal116     uint32_t StartOffset() const
117     {
118       NS_ASSERTION(IsValid(),
119                    "The caller should check if the selection is valid");
120       return Reversed() ? mFocus : mAnchor;
121     }
EndOffsetfinal122     uint32_t EndOffset() const
123     {
124       NS_ASSERTION(IsValid(),
125                    "The caller should check if the selection is valid");
126       return Reversed() ? mAnchor : mFocus;
127     }
Lengthfinal128     uint32_t Length() const
129     {
130       NS_ASSERTION(IsValid(),
131                    "The caller should check if the selection is valid");
132       return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
133     }
StartCharRectfinal134     LayoutDeviceIntRect StartCharRect() const
135     {
136       NS_ASSERTION(IsValid(),
137                    "The caller should check if the selection is valid");
138       return Reversed() ? mFocusCharRects[eNextCharRect] :
139                           mAnchorCharRects[eNextCharRect];
140     }
EndCharRectfinal141     LayoutDeviceIntRect EndCharRect() const
142     {
143       NS_ASSERTION(IsValid(),
144                    "The caller should check if the selection is valid");
145       return Reversed() ? mAnchorCharRects[eNextCharRect] :
146                           mFocusCharRects[eNextCharRect];
147     }
148   } mSelection;
149 
IsSelectionValid()150   bool IsSelectionValid() const
151   {
152     return mSelection.IsValid() && mSelection.EndOffset() <= mText.Length();
153   }
154 
155   // Stores first char rect because Yosemite's Japanese IME sometimes tries
156   // to query it.  If there is no text, this is caret rect.
157   LayoutDeviceIntRect mFirstCharRect;
158 
159   struct Caret final
160   {
161     uint32_t mOffset;
162     LayoutDeviceIntRect mRect;
163 
Caretfinal164     Caret()
165       : mOffset(UINT32_MAX)
166     {
167     }
168 
Clearfinal169     void Clear()
170     {
171       mOffset = UINT32_MAX;
172       mRect.SetEmpty();
173     }
174 
IsValidfinal175     bool IsValid() const { return mOffset != UINT32_MAX; }
176 
Offsetfinal177     uint32_t Offset() const
178     {
179       NS_ASSERTION(IsValid(),
180                    "The caller should check if the caret is valid");
181       return mOffset;
182     }
183   } mCaret;
184 
185   struct TextRectArray final
186   {
187     uint32_t mStart;
188     RectArray mRects;
189 
TextRectArrayfinal190     TextRectArray()
191       : mStart(UINT32_MAX)
192     {
193     }
194 
Clearfinal195     void Clear()
196     {
197       mStart = UINT32_MAX;
198       mRects.Clear();
199     }
200 
IsValidfinal201     bool IsValid() const
202     {
203       if (mStart == UINT32_MAX) {
204         return false;
205       }
206       CheckedInt<uint32_t> endOffset =
207         CheckedInt<uint32_t>(mStart) + mRects.Length();
208       return endOffset.isValid();
209     }
HasRectsfinal210     bool HasRects() const
211     {
212       return IsValid() && !mRects.IsEmpty();
213     }
StartOffsetfinal214     uint32_t StartOffset() const
215     {
216       NS_ASSERTION(IsValid(),
217                    "The caller should check if the caret is valid");
218       return mStart;
219     }
EndOffsetfinal220     uint32_t EndOffset() const
221     {
222       NS_ASSERTION(IsValid(),
223                    "The caller should check if the caret is valid");
224       if (!IsValid()) {
225         return UINT32_MAX;
226       }
227       return mStart + mRects.Length();
228     }
InRangefinal229     bool InRange(uint32_t aOffset) const
230     {
231       return IsValid() &&
232              StartOffset() <= aOffset && aOffset < EndOffset();
233     }
InRangefinal234     bool InRange(uint32_t aOffset, uint32_t aLength) const
235     {
236       CheckedInt<uint32_t> endOffset =
237         CheckedInt<uint32_t>(aOffset) + aLength;
238       if (NS_WARN_IF(!endOffset.isValid())) {
239         return false;
240       }
241       return InRange(aOffset) && aOffset + aLength <= EndOffset();
242     }
IsOverlappingWithfinal243     bool IsOverlappingWith(uint32_t aOffset, uint32_t aLength) const
244     {
245       if (!HasRects() || aOffset == UINT32_MAX || !aLength) {
246         return false;
247       }
248       CheckedInt<uint32_t> endOffset =
249         CheckedInt<uint32_t>(aOffset) + aLength;
250       if (NS_WARN_IF(!endOffset.isValid())) {
251         return false;
252       }
253       return aOffset < EndOffset() && endOffset.value() > mStart;
254     }
255     LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
256     LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
257     LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
258                           uint32_t aOffset, uint32_t aLength,
259                           bool aRoundToExistingOffset) const;
260   } mTextRectArray;
261 
262   LayoutDeviceIntRect mEditorRect;
263 
264   friend class ContentCacheInParent;
265   friend struct IPC::ParamTraits<ContentCache>;
266 };
267 
268 class ContentCacheInChild final : public ContentCache
269 {
270 public:
271   ContentCacheInChild();
272 
273   /**
274    * When IME loses focus, this should be called and making this forget the
275    * content for reducing footprint.
276    */
277   void Clear();
278 
279   /**
280    * Cache*() retrieves the latest content information and store them.
281    * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
282    * calls CacheSelection().  So, related data is also retrieved automatically.
283    */
284   bool CacheEditorRect(nsIWidget* aWidget,
285                        const IMENotification* aNotification = nullptr);
286   bool CacheSelection(nsIWidget* aWidget,
287                       const IMENotification* aNotification = nullptr);
288   bool CacheText(nsIWidget* aWidget,
289                  const IMENotification* aNotification = nullptr);
290 
291   bool CacheAll(nsIWidget* aWidget,
292                 const IMENotification* aNotification = nullptr);
293 
294   /**
295    * SetSelection() modifies selection with specified raw data. And also this
296    * tries to retrieve text rects too.
297    */
298   void SetSelection(nsIWidget* aWidget,
299                     uint32_t aStartOffset,
300                     uint32_t aLength,
301                     bool aReversed,
302                     const WritingMode& aWritingMode);
303 
304 private:
305   bool QueryCharRect(nsIWidget* aWidget,
306                      uint32_t aOffset,
307                      LayoutDeviceIntRect& aCharRect) const;
308   bool QueryCharRectArray(nsIWidget* aWidget,
309                           uint32_t aOffset,
310                           uint32_t aLength,
311                           RectArray& aCharRectArray) const;
312   bool CacheCaret(nsIWidget* aWidget,
313                   const IMENotification* aNotification = nullptr);
314   bool CacheTextRects(nsIWidget* aWidget,
315                       const IMENotification* aNotification = nullptr);
316 };
317 
318 class ContentCacheInParent final : public ContentCache
319 {
320 public:
321   ContentCacheInParent();
322 
323   /**
324    * AssignContent() is called when TabParent receives ContentCache from
325    * the content process.  This doesn't copy composition information because
326    * it's managed by TabParent itself.
327    */
328   void AssignContent(const ContentCache& aOther,
329                      nsIWidget* aWidget,
330                      const IMENotification* aNotification = nullptr);
331 
332   /**
333    * HandleQueryContentEvent() sets content data to aEvent.mReply.
334    *
335    * For eQuerySelectedText, fail if the cache doesn't contain the whole
336    *  selected range. (This shouldn't happen because PuppetWidget should have
337    *  already sent the whole selection.)
338    *
339    * For eQueryTextContent, fail only if the cache doesn't overlap with
340    *  the queried range. Note the difference from above. We use
341    *  this behavior because a normal eQueryTextContent event is allowed to
342    *  have out-of-bounds offsets, so that widget can request content without
343    *  knowing the exact length of text. It's up to widget to handle cases when
344    *  the returned offset/length are different from the queried offset/length.
345    *
346    * For eQueryTextRect, fail if cached offset/length aren't equals to input.
347    *   Cocoa widget always queries selected offset, so it works on it.
348    *
349    * For eQueryCaretRect, fail if cached offset isn't equals to input
350    *
351    * For eQueryEditorRect, always success
352    */
353   bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
354                                nsIWidget* aWidget) const;
355 
356   /**
357    * OnCompositionEvent() should be called before sending composition string.
358    * This returns true if the event should be sent.  Otherwise, false.
359    */
360   bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
361 
362   /**
363    * OnSelectionEvent() should be called before sending selection event.
364    */
365   void OnSelectionEvent(const WidgetSelectionEvent& aSelectionEvent);
366 
367   /**
368    * OnEventNeedingAckHandled() should be called after the child process
369    * handles a sent event which needs acknowledging.
370    *
371    * WARNING: This may send notifications to IME.  That might cause destroying
372    *          TabParent or aWidget.  Therefore, the caller must not destroy
373    *          this instance during a call of this method.
374    */
375   void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage);
376 
377   /**
378    * RequestIMEToCommitComposition() requests aWidget to commit or cancel
379    * composition.  If it's handled synchronously, this returns true.
380    *
381    * @param aWidget     The widget to be requested to commit or cancel
382    *                    the composition.
383    * @param aCancel     When the caller tries to cancel the composition, true.
384    *                    Otherwise, i.e., tries to commit the composition, false.
385    * @param aCommittedString    The committed string (i.e., the last data of
386    *                            dispatched composition events during requesting
387    *                            IME to commit composition.
388    * @return            Whether the composition is actually committed
389    *                    synchronously.
390    */
391   bool RequestIMEToCommitComposition(nsIWidget* aWidget,
392                                      bool aCancel,
393                                      nsAString& aCommittedString);
394 
395   /**
396    * MaybeNotifyIME() may notify IME of the notification.  If child process
397    * hasn't been handled all sending events yet, this stores the notification
398    * and flush it later.
399    */
400   void MaybeNotifyIME(nsIWidget* aWidget,
401                       const IMENotification& aNotification);
402 
403 private:
404   IMENotification mPendingSelectionChange;
405   IMENotification mPendingTextChange;
406   IMENotification mPendingLayoutChange;
407   IMENotification mPendingCompositionUpdate;
408 
409   // This is not nullptr only while the instance is requesting IME to
410   // composition.  Then, data value of dispatched composition events should
411   // be stored into the instance.
412   nsAString* mCommitStringByRequest;
413   // mPendingEventsNeedingAck is increased before sending a composition event or
414   // a selection event and decreased after they are received in the child
415   // process.
416   uint32_t mPendingEventsNeedingAck;
417   // mCompositionStartInChild stores current composition start offset in the
418   // remote process.
419   uint32_t mCompositionStartInChild;
420   // mPendingCompositionCount is number of compositions which started in widget
421   // but not yet handled in the child process.
422   uint8_t mPendingCompositionCount;
423   // mWidgetHasComposition is true when the widget in this process thinks that
424   // IME has composition.  So, this is set to true when eCompositionStart is
425   // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
426   bool mWidgetHasComposition;
427 
428   /**
429    * When following methods' aRoundToExistingOffset is true, even if specified
430    * offset or range is out of bounds, the result is computed with the existing
431    * cache forcibly.
432    */
433   bool GetCaretRect(uint32_t aOffset,
434                     bool aRoundToExistingOffset,
435                     LayoutDeviceIntRect& aCaretRect) const;
436   bool GetTextRect(uint32_t aOffset,
437                    bool aRoundToExistingOffset,
438                    LayoutDeviceIntRect& aTextRect) const;
439   bool GetUnionTextRects(uint32_t aOffset,
440                          uint32_t aLength,
441                          bool aRoundToExistingOffset,
442                          LayoutDeviceIntRect& aUnionTextRect) const;
443 
444   void FlushPendingNotifications(nsIWidget* aWidget);
445 };
446 
447 } // namespace mozilla
448 
449 #endif // mozilla_ContentCache_h
450