1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
8 #ifndef mozilla_ContentCache_h
9 #define mozilla_ContentCache_h
13 #include "mozilla/widget/IMEData.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/CheckedInt.h"
16 #include "mozilla/EventForwards.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/ToString.h"
19 #include "mozilla/WritingModes.h"
28 class ContentCacheInParent
;
35 * ContentCache stores various information of the child content.
36 * This class has members which are necessary both in parent process and
42 typedef CopyableTArray
<LayoutDeviceIntRect
> RectArray
;
43 typedef widget::IMENotification IMENotification
;
45 ContentCache() = default;
48 // Whole text in the target
51 // Start offset of the composition string.
52 Maybe
<uint32_t> mCompositionStart
;
54 enum { ePrevCharRect
= 1, eNextCharRect
= 0 };
56 struct Selection final
{
57 // Following values are offset in "flat text".
61 WritingMode mWritingMode
;
63 // Character rects at previous and next character of mAnchor and mFocus.
64 // The reason why ContentCache needs to store each previous character of
65 // them is IME may query character rect of the last character of a line
66 // when caret is at the end of the line.
67 // Note that use ePrevCharRect and eNextCharRect for accessing each item.
68 LayoutDeviceIntRect mAnchorCharRects
[2];
69 LayoutDeviceIntRect mFocusCharRects
[2];
71 // Whole rect of selected text. This is empty if the selection is collapsed.
72 LayoutDeviceIntRect mRect
;
74 explicit Selection(uint32_t aAnchorOffset
, uint32_t aFocusOffset
,
75 const WritingMode
& aWritingMode
)
76 : mAnchor(aAnchorOffset
),
78 mWritingMode(aWritingMode
) {}
81 for (auto& rect
: mAnchorCharRects
) {
84 for (auto& rect
: mFocusCharRects
) {
89 bool HasRects() const {
90 for (auto& rect
: mAnchorCharRects
) {
91 if (!rect
.IsEmpty()) {
95 for (auto& rect
: mFocusCharRects
) {
96 if (!rect
.IsEmpty()) {
100 return !mRect
.IsEmpty();
103 bool Collapsed() const { return mFocus
== mAnchor
; }
104 bool Reversed() const { return mFocus
< mAnchor
; }
105 uint32_t StartOffset() const { return Reversed() ? mFocus
: mAnchor
; }
106 uint32_t EndOffset() const { return Reversed() ? mAnchor
: mFocus
; }
107 uint32_t Length() const {
108 return Reversed() ? mAnchor
- mFocus
: mFocus
- mAnchor
;
110 LayoutDeviceIntRect
StartCharRect() const {
111 return Reversed() ? mFocusCharRects
[eNextCharRect
]
112 : mAnchorCharRects
[eNextCharRect
];
114 LayoutDeviceIntRect
EndCharRect() const {
115 return Reversed() ? mAnchorCharRects
[eNextCharRect
]
116 : mFocusCharRects
[eNextCharRect
];
119 friend std::ostream
& operator<<(std::ostream
& aStream
,
120 const Selection
& aSelection
) {
121 aStream
<< "{ mAnchor=" << aSelection
.mAnchor
122 << ", mFocus=" << aSelection
.mFocus
123 << ", mWritingMode=" << ToString(aSelection
.mWritingMode
).c_str();
124 if (aSelection
.HasRects()) {
125 if (aSelection
.mAnchor
> 0) {
126 aStream
<< ", mAnchorCharRects[ePrevCharRect]="
127 << aSelection
.mAnchorCharRects
[ContentCache::ePrevCharRect
];
129 aStream
<< ", mAnchorCharRects[eNextCharRect]="
130 << aSelection
.mAnchorCharRects
[ContentCache::eNextCharRect
];
131 if (aSelection
.mFocus
> 0) {
132 aStream
<< ", mFocusCharRects[ePrevCharRect]="
133 << aSelection
.mFocusCharRects
[ContentCache::ePrevCharRect
];
135 aStream
<< ", mFocusCharRects[eNextCharRect]="
136 << aSelection
.mFocusCharRects
[ContentCache::eNextCharRect
]
137 << ", mRect=" << aSelection
.mRect
;
139 aStream
<< ", Reversed()=" << (aSelection
.Reversed() ? "true" : "false")
140 << ", StartOffset()=" << aSelection
.StartOffset()
141 << ", EndOffset()=" << aSelection
.EndOffset()
142 << ", Collapsed()=" << (aSelection
.Collapsed() ? "true" : "false")
143 << ", Length()=" << aSelection
.Length() << " }";
148 Selection() = default;
150 friend struct IPC::ParamTraits
<ContentCache::Selection
>;
151 friend struct IPC::ParamTraits
<Maybe
<ContentCache::Selection
>>;
153 Maybe
<Selection
> mSelection
;
155 bool IsSelectionValid() const {
156 return mSelection
.isSome() && mSelection
->EndOffset() <= mText
.Length();
159 // Stores first char rect because Yosemite's Japanese IME sometimes tries
160 // to query it. If there is no text, this is caret rect.
161 LayoutDeviceIntRect mFirstCharRect
;
165 LayoutDeviceIntRect mRect
;
167 explicit Caret(uint32_t aOffset
, LayoutDeviceIntRect aCaretRect
)
168 : mOffset(aOffset
), mRect(aCaretRect
) {}
170 uint32_t Offset() const { return mOffset
; }
171 bool HasRect() const { return !mRect
.IsEmpty(); }
173 friend std::ostream
& operator<<(std::ostream
& aStream
,
174 const Caret
& aCaret
) {
175 aStream
<< "{ mOffset=" << aCaret
.mOffset
;
176 if (aCaret
.HasRect()) {
177 aStream
<< ", mRect=" << aCaret
.mRect
;
179 return aStream
<< " }";
185 friend struct IPC::ParamTraits
<ContentCache::Caret
>;
186 friend struct IPC::ParamTraits
<Maybe
<ContentCache::Caret
>>;
190 struct TextRectArray final
{
194 explicit TextRectArray(uint32_t aStartOffset
) : mStart(aStartOffset
) {}
196 bool HasRects() const { return Length() > 0; }
197 uint32_t StartOffset() const { return mStart
; }
198 uint32_t EndOffset() const {
199 CheckedInt
<uint32_t> endOffset
=
200 CheckedInt
<uint32_t>(mStart
) + mRects
.Length();
201 return endOffset
.isValid() ? endOffset
.value() : UINT32_MAX
;
203 uint32_t Length() const { return EndOffset() - mStart
; }
204 bool IsOffsetInRange(uint32_t aOffset
) const {
205 return StartOffset() <= aOffset
&& aOffset
< EndOffset();
207 bool IsRangeCompletelyInRange(uint32_t aOffset
, uint32_t aLength
) const {
208 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
209 if (NS_WARN_IF(!endOffset
.isValid())) {
212 return IsOffsetInRange(aOffset
) && aOffset
+ aLength
<= EndOffset();
214 bool IsOverlappingWith(uint32_t aOffset
, uint32_t aLength
) const {
215 if (!HasRects() || aOffset
== UINT32_MAX
|| !aLength
) {
218 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
219 if (NS_WARN_IF(!endOffset
.isValid())) {
222 return aOffset
< EndOffset() && endOffset
.value() > mStart
;
224 LayoutDeviceIntRect
GetRect(uint32_t aOffset
) const;
225 LayoutDeviceIntRect
GetUnionRect(uint32_t aOffset
, uint32_t aLength
) const;
226 LayoutDeviceIntRect
GetUnionRectAsFarAsPossible(
227 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const;
229 friend std::ostream
& operator<<(std::ostream
& aStream
,
230 const TextRectArray
& aTextRectArray
) {
231 aStream
<< "{ mStart=" << aTextRectArray
.mStart
232 << ", mRects={ Length()=" << aTextRectArray
.Length();
233 if (aTextRectArray
.HasRects()) {
234 aStream
<< ", Elements()=[ ";
235 static constexpr uint32_t kMaxPrintRects
= 4;
236 const uint32_t kFirstHalf
= aTextRectArray
.Length() <= kMaxPrintRects
238 : (kMaxPrintRects
+ 1) / 2;
239 const uint32_t kSecondHalf
=
240 aTextRectArray
.Length() <= kMaxPrintRects
? 0 : kMaxPrintRects
/ 2;
241 for (uint32_t i
= 0; i
< aTextRectArray
.Length(); i
++) {
245 aStream
<< ToString(aTextRectArray
.mRects
[i
]).c_str();
246 if (i
+ 1 == kFirstHalf
) {
248 i
= aTextRectArray
.Length() - kSecondHalf
- 1;
252 return aStream
<< " ] } }";
256 TextRectArray() = default;
258 friend struct IPC::ParamTraits
<ContentCache::TextRectArray
>;
259 friend struct IPC::ParamTraits
<Maybe
<ContentCache::TextRectArray
>>;
261 Maybe
<TextRectArray
> mTextRectArray
;
262 Maybe
<TextRectArray
> mLastCommitStringTextRectArray
;
264 LayoutDeviceIntRect mEditorRect
;
266 friend class ContentCacheInParent
;
267 friend struct IPC::ParamTraits
<ContentCache
>;
268 friend struct IPC::ParamTraits
<ContentCache::Selection
>;
269 friend struct IPC::ParamTraits
<ContentCache::Caret
>;
270 friend struct IPC::ParamTraits
<ContentCache::TextRectArray
>;
271 friend std::ostream
& operator<<(
272 std::ostream
& aStream
,
273 const Selection
& aSelection
); // For e(Prev|Next)CharRect
276 class ContentCacheInChild final
: public ContentCache
{
278 ContentCacheInChild() = default;
281 * Called when composition event will be dispatched in this process from
284 void OnCompositionEvent(const WidgetCompositionEvent
& aCompositionEvent
);
287 * When IME loses focus, this should be called and making this forget the
288 * content for reducing footprint.
293 * Cache*() retrieves the latest content information and store them.
294 * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
295 * calls CacheSelection(). So, related data is also retrieved automatically.
297 bool CacheEditorRect(nsIWidget
* aWidget
,
298 const IMENotification
* aNotification
= nullptr);
299 bool CacheSelection(nsIWidget
* aWidget
,
300 const IMENotification
* aNotification
= nullptr);
301 bool CacheText(nsIWidget
* aWidget
,
302 const IMENotification
* aNotification
= nullptr);
304 bool CacheAll(nsIWidget
* aWidget
,
305 const IMENotification
* aNotification
= nullptr);
308 * SetSelection() modifies selection with specified raw data. And also this
309 * tries to retrieve text rects too.
311 void SetSelection(nsIWidget
* aWidget
, uint32_t aStartOffset
, uint32_t aLength
,
312 bool aReversed
, const WritingMode
& aWritingMode
);
315 bool QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
316 LayoutDeviceIntRect
& aCharRect
) const;
317 bool QueryCharRectArray(nsIWidget
* aWidget
, uint32_t aOffset
,
318 uint32_t aLength
, RectArray
& aCharRectArray
) const;
319 bool CacheCaret(nsIWidget
* aWidget
,
320 const IMENotification
* aNotification
= nullptr);
321 bool CacheTextRects(nsIWidget
* aWidget
,
322 const IMENotification
* aNotification
= nullptr);
324 // Once composition is committed, all of the commit string may be composed
325 // again by Kakutei-Undo of Japanese IME. Therefore, we need to keep
326 // storing the last composition start to cache all character rects of the
327 // last commit string.
328 Maybe
<OffsetAndData
<uint32_t>> mLastCommit
;
331 class ContentCacheInParent final
: public ContentCache
{
333 explicit ContentCacheInParent(dom::BrowserParent
& aBrowserParent
);
336 * AssignContent() is called when BrowserParent receives ContentCache from
337 * the content process. This doesn't copy composition information because
338 * it's managed by BrowserParent itself.
340 void AssignContent(const ContentCache
& aOther
, nsIWidget
* aWidget
,
341 const IMENotification
* aNotification
= nullptr);
344 * HandleQueryContentEvent() sets content data to aEvent.mReply.
346 * For eQuerySelectedText, fail if the cache doesn't contain the whole
347 * selected range. (This shouldn't happen because PuppetWidget should have
348 * already sent the whole selection.)
350 * For eQueryTextContent, fail only if the cache doesn't overlap with
351 * the queried range. Note the difference from above. We use
352 * this behavior because a normal eQueryTextContent event is allowed to
353 * have out-of-bounds offsets, so that widget can request content without
354 * knowing the exact length of text. It's up to widget to handle cases when
355 * the returned offset/length are different from the queried offset/length.
357 * For eQueryTextRect, fail if cached offset/length aren't equals to input.
358 * Cocoa widget always queries selected offset, so it works on it.
360 * For eQueryCaretRect, fail if cached offset isn't equals to input
362 * For eQueryEditorRect, always success
364 bool HandleQueryContentEvent(WidgetQueryContentEvent
& aEvent
,
365 nsIWidget
* aWidget
) const;
368 * OnCompositionEvent() should be called before sending composition string.
369 * This returns true if the event should be sent. Otherwise, false.
371 bool OnCompositionEvent(const WidgetCompositionEvent
& aCompositionEvent
);
374 * OnSelectionEvent() should be called before sending selection event.
376 void OnSelectionEvent(const WidgetSelectionEvent
& aSelectionEvent
);
379 * OnEventNeedingAckHandled() should be called after the child process
380 * handles a sent event which needs acknowledging.
382 * WARNING: This may send notifications to IME. That might cause destroying
383 * BrowserParent or aWidget. Therefore, the caller must not destroy
384 * this instance during a call of this method.
386 void OnEventNeedingAckHandled(nsIWidget
* aWidget
, EventMessage aMessage
);
389 * RequestIMEToCommitComposition() requests aWidget to commit or cancel
390 * composition. If it's handled synchronously, this returns true.
392 * @param aWidget The widget to be requested to commit or cancel
394 * @param aCancel When the caller tries to cancel the composition, true.
395 * Otherwise, i.e., tries to commit the composition, false.
396 * @param aCommittedString The committed string (i.e., the last data of
397 * dispatched composition events during requesting
398 * IME to commit composition.
399 * @return Whether the composition is actually committed
402 bool RequestIMEToCommitComposition(nsIWidget
* aWidget
, bool aCancel
,
403 nsAString
& aCommittedString
);
406 * MaybeNotifyIME() may notify IME of the notification. If child process
407 * hasn't been handled all sending events yet, this stores the notification
408 * and flush it later.
410 void MaybeNotifyIME(nsIWidget
* aWidget
, const IMENotification
& aNotification
);
413 IMENotification mPendingSelectionChange
;
414 IMENotification mPendingTextChange
;
415 IMENotification mPendingLayoutChange
;
416 IMENotification mPendingCompositionUpdate
;
418 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
419 // Log of event messages to be output to crash report.
420 nsTArray
<EventMessage
> mDispatchedEventMessages
;
421 nsTArray
<EventMessage
> mReceivedEventMessages
;
422 // Log of RequestIMEToCommitComposition() in the last 2 compositions.
423 enum class RequestIMEToCommitCompositionResult
: uint8_t {
424 eToOldCompositionReceived
,
425 eToCommittedCompositionReceived
,
426 eReceivedAfterBrowserParentBlur
,
427 eReceivedButNoTextComposition
,
428 eHandledAsynchronously
,
429 eHandledSynchronously
,
431 const char* ToReadableText(
432 RequestIMEToCommitCompositionResult aResult
) const {
434 case RequestIMEToCommitCompositionResult::eToOldCompositionReceived
:
435 return "Commit request is not handled because it's for "
437 case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
:
438 return "Commit request is not handled because BrowserParent has "
440 "sent commit event for the composition";
441 case RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
:
442 return "Commit request is handled with stored composition string "
443 "because BrowserParent has already lost focus";
444 case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
:
445 return "Commit request is not handled because there is no "
446 "TextCompsition instance";
447 case RequestIMEToCommitCompositionResult::eHandledAsynchronously
:
448 return "Commit request is handled but IME doesn't commit current "
449 "composition synchronously";
450 case RequestIMEToCommitCompositionResult::eHandledSynchronously
:
451 return "Commit reqeust is handled synchronously";
453 return "Unknown reason";
456 nsTArray
<RequestIMEToCommitCompositionResult
>
457 mRequestIMEToCommitCompositionResults
;
458 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
460 // mBrowserParent is owner of the instance.
461 dom::BrowserParent
& MOZ_NON_OWNING_REF mBrowserParent
;
462 // mCompositionString is composition string which were sent to the remote
463 // process but not yet committed in the remote process.
464 nsString mCompositionString
;
465 // This is not nullptr only while the instance is requesting IME to
466 // composition. Then, data value of dispatched composition events should
467 // be stored into the instance.
468 nsAString
* mCommitStringByRequest
;
469 // mPendingEventsNeedingAck is increased before sending a composition event or
470 // a selection event and decreased after they are received in the child
472 uint32_t mPendingEventsNeedingAck
;
473 // mCompositionStartInChild stores current composition start offset in the
475 Maybe
<uint32_t> mCompositionStartInChild
;
476 // mPendingCommitLength is commit string length of the first pending
477 // composition. This is used by relative offset query events when querying
478 // new composition start offset.
479 // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
480 // more pending compositions, this cache won't be used because in such case,
481 // anyway ContentCacheInParent cannot return proper character rect.
482 uint32_t mPendingCommitLength
;
483 // mPendingCompositionCount is number of compositions which started in widget
484 // but not yet handled in the child process.
485 uint8_t mPendingCompositionCount
;
486 // mPendingCommitCount is number of eCompositionCommit(AsIs) events which
487 // were sent to the child process but not yet handled in it.
488 uint8_t mPendingCommitCount
;
489 // mWidgetHasComposition is true when the widget in this process thinks that
490 // IME has composition. So, this is set to true when eCompositionStart is
491 // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
492 bool mWidgetHasComposition
;
493 // mIsChildIgnoringCompositionEvents is set to true if the child process
494 // requests commit composition whose commit has already been sent to it.
495 // Then, set to false when the child process ignores the commit event.
496 bool mIsChildIgnoringCompositionEvents
;
498 ContentCacheInParent() = delete;
501 * When following methods' aRoundToExistingOffset is true, even if specified
502 * offset or range is out of bounds, the result is computed with the existing
505 bool GetCaretRect(uint32_t aOffset
, bool aRoundToExistingOffset
,
506 LayoutDeviceIntRect
& aCaretRect
) const;
507 bool GetTextRect(uint32_t aOffset
, bool aRoundToExistingOffset
,
508 LayoutDeviceIntRect
& aTextRect
) const;
509 bool GetUnionTextRects(uint32_t aOffset
, uint32_t aLength
,
510 bool aRoundToExistingOffset
,
511 LayoutDeviceIntRect
& aUnionTextRect
) const;
513 void FlushPendingNotifications(nsIWidget
* aWidget
);
515 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
517 * Remove unnecessary messages from mDispatchedEventMessages and
518 * mReceivedEventMessages.
520 void RemoveUnnecessaryEventMessageLog();
523 * Append event message log to aLog.
525 void AppendEventMessageLog(nsACString
& aLog
) const;
526 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
529 } // namespace mozilla
531 #endif // mozilla_ContentCache_h