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/ipc/IPCForwards.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/CheckedInt.h"
17 #include "mozilla/EventForwards.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/ToString.h"
20 #include "mozilla/WritingModes.h"
29 class ContentCacheInParent
;
36 * ContentCache stores various information of the child content.
37 * This class has members which are necessary both in parent process and
43 typedef CopyableTArray
<LayoutDeviceIntRect
> RectArray
;
44 typedef widget::IMENotification IMENotification
;
46 ContentCache() = default;
49 // Whole text in the target
50 Maybe
<nsString
> mText
;
52 // Start offset of the composition string.
53 Maybe
<uint32_t> mCompositionStart
;
55 enum { ePrevCharRect
= 1, eNextCharRect
= 0 };
57 struct Selection final
{
58 // Following values are offset in "flat text".
62 WritingMode mWritingMode
;
66 // Character rects at previous and next character of mAnchor and mFocus.
67 // The reason why ContentCache needs to store each previous character of
68 // them is IME may query character rect of the last character of a line
69 // when caret is at the end of the line.
70 // Note that use ePrevCharRect and eNextCharRect for accessing each item.
71 LayoutDeviceIntRect mAnchorCharRects
[2];
72 LayoutDeviceIntRect mFocusCharRects
[2];
74 // Whole rect of selected text. This is empty if the selection is collapsed.
75 LayoutDeviceIntRect mRect
;
77 Selection() : mAnchor(UINT32_MAX
), mFocus(UINT32_MAX
), mHasRange(false) {
82 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
)
83 : mAnchor(UINT32_MAX
),
85 mWritingMode(aSelectionChangeData
.GetWritingMode()),
86 mHasRange(aSelectionChangeData
.HasRange()) {
88 mAnchor
= aSelectionChangeData
.AnchorOffset();
89 mFocus
= aSelectionChangeData
.FocusOffset();
93 explicit Selection(const WidgetQueryContentEvent
& aQuerySelectedTextEvent
);
96 for (auto& rect
: mAnchorCharRects
) {
99 for (auto& rect
: mFocusCharRects
) {
104 bool HasRects() const {
105 for (auto& rect
: mAnchorCharRects
) {
106 if (!rect
.IsEmpty()) {
110 for (auto& rect
: mFocusCharRects
) {
111 if (!rect
.IsEmpty()) {
115 return !mRect
.IsEmpty();
118 bool IsCollapsed() const { return !mHasRange
|| mFocus
== mAnchor
; }
119 bool Reversed() const {
120 MOZ_ASSERT(mHasRange
);
121 return mFocus
< mAnchor
;
123 uint32_t StartOffset() const {
124 MOZ_ASSERT(mHasRange
);
125 return Reversed() ? mFocus
: mAnchor
;
127 uint32_t EndOffset() const {
128 MOZ_ASSERT(mHasRange
);
129 return Reversed() ? mAnchor
: mFocus
;
131 uint32_t Length() const {
132 MOZ_ASSERT(mHasRange
);
133 return Reversed() ? mAnchor
- mFocus
: mFocus
- mAnchor
;
135 LayoutDeviceIntRect
StartCharRect() const {
136 return Reversed() ? mFocusCharRects
[eNextCharRect
]
137 : mAnchorCharRects
[eNextCharRect
];
139 LayoutDeviceIntRect
EndCharRect() const {
140 return Reversed() ? mAnchorCharRects
[eNextCharRect
]
141 : mFocusCharRects
[eNextCharRect
];
144 friend std::ostream
& operator<<(std::ostream
& aStream
,
145 const Selection
& aSelection
) {
147 if (!aSelection
.mHasRange
) {
148 aStream
<< "HasRange()=false";
150 aStream
<< "mAnchor=" << aSelection
.mAnchor
151 << ", mFocus=" << aSelection
.mFocus
<< ", mWritingMode="
152 << ToString(aSelection
.mWritingMode
).c_str();
154 if (aSelection
.HasRects()) {
155 if (aSelection
.mAnchor
> 0) {
156 aStream
<< ", mAnchorCharRects[ePrevCharRect]="
157 << aSelection
.mAnchorCharRects
[ContentCache::ePrevCharRect
];
159 aStream
<< ", mAnchorCharRects[eNextCharRect]="
160 << aSelection
.mAnchorCharRects
[ContentCache::eNextCharRect
];
161 if (aSelection
.mFocus
> 0) {
162 aStream
<< ", mFocusCharRects[ePrevCharRect]="
163 << aSelection
.mFocusCharRects
[ContentCache::ePrevCharRect
];
165 aStream
<< ", mFocusCharRects[eNextCharRect]="
166 << aSelection
.mFocusCharRects
[ContentCache::eNextCharRect
]
167 << ", mRect=" << aSelection
.mRect
;
169 if (aSelection
.mHasRange
) {
170 aStream
<< ", Reversed()=" << (aSelection
.Reversed() ? "true" : "false")
171 << ", StartOffset()=" << aSelection
.StartOffset()
172 << ", EndOffset()=" << aSelection
.EndOffset()
173 << ", IsCollapsed()="
174 << (aSelection
.IsCollapsed() ? "true" : "false")
175 << ", Length()=" << aSelection
.Length();
181 Maybe
<Selection
> mSelection
;
183 // Stores first char rect because Yosemite's Japanese IME sometimes tries
184 // to query it. If there is no text, this is caret rect.
185 LayoutDeviceIntRect mFirstCharRect
;
189 LayoutDeviceIntRect mRect
;
191 explicit Caret(uint32_t aOffset
, LayoutDeviceIntRect aCaretRect
)
192 : mOffset(aOffset
), mRect(aCaretRect
) {}
194 uint32_t Offset() const { return mOffset
; }
195 bool HasRect() const { return !mRect
.IsEmpty(); }
197 friend std::ostream
& operator<<(std::ostream
& aStream
,
198 const Caret
& aCaret
) {
199 aStream
<< "{ mOffset=" << aCaret
.mOffset
;
200 if (aCaret
.HasRect()) {
201 aStream
<< ", mRect=" << aCaret
.mRect
;
203 return aStream
<< " }";
209 friend struct IPC::ParamTraits
<ContentCache::Caret
>;
210 ALLOW_DEPRECATED_READPARAM
214 struct TextRectArray final
{
218 explicit TextRectArray(uint32_t aStartOffset
) : mStart(aStartOffset
) {}
220 bool HasRects() const { return Length() > 0; }
221 uint32_t StartOffset() const { return mStart
; }
222 uint32_t EndOffset() const {
223 CheckedInt
<uint32_t> endOffset
=
224 CheckedInt
<uint32_t>(mStart
) + mRects
.Length();
225 return endOffset
.isValid() ? endOffset
.value() : UINT32_MAX
;
227 uint32_t Length() const { return EndOffset() - mStart
; }
228 bool IsOffsetInRange(uint32_t aOffset
) const {
229 return StartOffset() <= aOffset
&& aOffset
< EndOffset();
231 bool IsRangeCompletelyInRange(uint32_t aOffset
, uint32_t aLength
) const {
232 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
233 if (NS_WARN_IF(!endOffset
.isValid())) {
236 return IsOffsetInRange(aOffset
) && aOffset
+ aLength
<= EndOffset();
238 bool IsOverlappingWith(uint32_t aOffset
, uint32_t aLength
) const {
239 if (!HasRects() || aOffset
== UINT32_MAX
|| !aLength
) {
242 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
243 if (NS_WARN_IF(!endOffset
.isValid())) {
246 return aOffset
< EndOffset() && endOffset
.value() > mStart
;
248 LayoutDeviceIntRect
GetRect(uint32_t aOffset
) const;
249 LayoutDeviceIntRect
GetUnionRect(uint32_t aOffset
, uint32_t aLength
) const;
250 LayoutDeviceIntRect
GetUnionRectAsFarAsPossible(
251 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const;
253 friend std::ostream
& operator<<(std::ostream
& aStream
,
254 const TextRectArray
& aTextRectArray
) {
255 aStream
<< "{ mStart=" << aTextRectArray
.mStart
256 << ", mRects={ Length()=" << aTextRectArray
.Length();
257 if (aTextRectArray
.HasRects()) {
258 aStream
<< ", Elements()=[ ";
259 static constexpr uint32_t kMaxPrintRects
= 4;
260 const uint32_t kFirstHalf
= aTextRectArray
.Length() <= kMaxPrintRects
262 : (kMaxPrintRects
+ 1) / 2;
263 const uint32_t kSecondHalf
=
264 aTextRectArray
.Length() <= kMaxPrintRects
? 0 : kMaxPrintRects
/ 2;
265 for (uint32_t i
= 0; i
< aTextRectArray
.Length(); i
++) {
269 aStream
<< ToString(aTextRectArray
.mRects
[i
]).c_str();
270 if (i
+ 1 == kFirstHalf
) {
272 i
= aTextRectArray
.Length() - kSecondHalf
- 1;
276 return aStream
<< " ] } }";
280 TextRectArray() = default;
282 friend struct IPC::ParamTraits
<ContentCache::TextRectArray
>;
283 ALLOW_DEPRECATED_READPARAM
285 Maybe
<TextRectArray
> mTextRectArray
;
286 Maybe
<TextRectArray
> mLastCommitStringTextRectArray
;
288 LayoutDeviceIntRect mEditorRect
;
290 friend class ContentCacheInParent
;
291 friend struct IPC::ParamTraits
<ContentCache
>;
292 friend struct IPC::ParamTraits
<ContentCache::Selection
>;
293 friend struct IPC::ParamTraits
<ContentCache::Caret
>;
294 friend struct IPC::ParamTraits
<ContentCache::TextRectArray
>;
295 friend std::ostream
& operator<<(
296 std::ostream
& aStream
,
297 const Selection
& aSelection
); // For e(Prev|Next)CharRect
298 ALLOW_DEPRECATED_READPARAM
301 class ContentCacheInChild final
: public ContentCache
{
303 ContentCacheInChild() = default;
306 * Called when composition event will be dispatched in this process from
309 void OnCompositionEvent(const WidgetCompositionEvent
& aCompositionEvent
);
312 * When IME loses focus, this should be called and making this forget the
313 * content for reducing footprint.
318 * Cache*() retrieves the latest content information and store them.
319 * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
320 * calls CacheSelection(). So, related data is also retrieved automatically.
322 bool CacheEditorRect(nsIWidget
* aWidget
,
323 const IMENotification
* aNotification
= nullptr);
324 bool CacheSelection(nsIWidget
* aWidget
,
325 const IMENotification
* aNotification
= nullptr);
326 bool CacheText(nsIWidget
* aWidget
,
327 const IMENotification
* aNotification
= nullptr);
329 bool CacheAll(nsIWidget
* aWidget
,
330 const IMENotification
* aNotification
= nullptr);
333 * SetSelection() modifies selection with specified raw data. And also this
334 * tries to retrieve text rects too.
338 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
);
341 bool QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
342 LayoutDeviceIntRect
& aCharRect
) const;
343 bool QueryCharRectArray(nsIWidget
* aWidget
, uint32_t aOffset
,
344 uint32_t aLength
, RectArray
& aCharRectArray
) const;
345 bool CacheCaret(nsIWidget
* aWidget
,
346 const IMENotification
* aNotification
= nullptr);
347 bool CacheTextRects(nsIWidget
* aWidget
,
348 const IMENotification
* aNotification
= nullptr);
350 // Once composition is committed, all of the commit string may be composed
351 // again by Kakutei-Undo of Japanese IME. Therefore, we need to keep
352 // storing the last composition start to cache all character rects of the
353 // last commit string.
354 Maybe
<OffsetAndData
<uint32_t>> mLastCommit
;
357 class ContentCacheInParent final
: public ContentCache
{
359 explicit ContentCacheInParent(dom::BrowserParent
& aBrowserParent
);
362 * AssignContent() is called when BrowserParent receives ContentCache from
363 * the content process. This doesn't copy composition information because
364 * it's managed by BrowserParent itself.
366 void AssignContent(const ContentCache
& aOther
, nsIWidget
* aWidget
,
367 const IMENotification
* aNotification
= nullptr);
370 * HandleQueryContentEvent() sets content data to aEvent.mReply.
372 * For eQuerySelectedText, fail if the cache doesn't contain the whole
373 * selected range. (This shouldn't happen because PuppetWidget should have
374 * already sent the whole selection.)
376 * For eQueryTextContent, fail only if the cache doesn't overlap with
377 * the queried range. Note the difference from above. We use
378 * this behavior because a normal eQueryTextContent event is allowed to
379 * have out-of-bounds offsets, so that widget can request content without
380 * knowing the exact length of text. It's up to widget to handle cases when
381 * the returned offset/length are different from the queried offset/length.
383 * For eQueryTextRect, fail if cached offset/length aren't equals to input.
384 * Cocoa widget always queries selected offset, so it works on it.
386 * For eQueryCaretRect, fail if cached offset isn't equals to input
388 * For eQueryEditorRect, always success
390 bool HandleQueryContentEvent(WidgetQueryContentEvent
& aEvent
,
391 nsIWidget
* aWidget
) const;
394 * OnCompositionEvent() should be called before sending composition string.
395 * This returns true if the event should be sent. Otherwise, false.
397 bool OnCompositionEvent(const WidgetCompositionEvent
& aCompositionEvent
);
400 * OnSelectionEvent() should be called before sending selection event.
402 void OnSelectionEvent(const WidgetSelectionEvent
& aSelectionEvent
);
405 * OnEventNeedingAckHandled() should be called after the child process
406 * handles a sent event which needs acknowledging.
408 * WARNING: This may send notifications to IME. That might cause destroying
409 * BrowserParent or aWidget. Therefore, the caller must not destroy
410 * this instance during a call of this method.
412 void OnEventNeedingAckHandled(nsIWidget
* aWidget
, EventMessage aMessage
);
415 * RequestIMEToCommitComposition() requests aWidget to commit or cancel
416 * composition. If it's handled synchronously, this returns true.
418 * @param aWidget The widget to be requested to commit or cancel
420 * @param aCancel When the caller tries to cancel the composition, true.
421 * Otherwise, i.e., tries to commit the composition, false.
422 * @param aCommittedString The committed string (i.e., the last data of
423 * dispatched composition events during requesting
424 * IME to commit composition.
425 * @return Whether the composition is actually committed
428 bool RequestIMEToCommitComposition(nsIWidget
* aWidget
, bool aCancel
,
429 nsAString
& aCommittedString
);
432 * MaybeNotifyIME() may notify IME of the notification. If child process
433 * hasn't been handled all sending events yet, this stores the notification
434 * and flush it later.
436 void MaybeNotifyIME(nsIWidget
* aWidget
, const IMENotification
& aNotification
);
439 IMENotification mPendingSelectionChange
;
440 IMENotification mPendingTextChange
;
441 IMENotification mPendingLayoutChange
;
442 IMENotification mPendingCompositionUpdate
;
444 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
445 // Log of event messages to be output to crash report.
446 nsTArray
<EventMessage
> mDispatchedEventMessages
;
447 nsTArray
<EventMessage
> mReceivedEventMessages
;
448 // Log of RequestIMEToCommitComposition() in the last 2 compositions.
449 enum class RequestIMEToCommitCompositionResult
: uint8_t {
450 eToOldCompositionReceived
,
451 eToCommittedCompositionReceived
,
452 eReceivedAfterBrowserParentBlur
,
453 eReceivedButNoTextComposition
,
454 eHandledAsynchronously
,
455 eHandledSynchronously
,
457 const char* ToReadableText(
458 RequestIMEToCommitCompositionResult aResult
) const {
460 case RequestIMEToCommitCompositionResult::eToOldCompositionReceived
:
461 return "Commit request is not handled because it's for "
463 case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
:
464 return "Commit request is not handled because BrowserParent has "
466 "sent commit event for the composition";
467 case RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
:
468 return "Commit request is handled with stored composition string "
469 "because BrowserParent has already lost focus";
470 case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
:
471 return "Commit request is not handled because there is no "
472 "TextCompsition instance";
473 case RequestIMEToCommitCompositionResult::eHandledAsynchronously
:
474 return "Commit request is handled but IME doesn't commit current "
475 "composition synchronously";
476 case RequestIMEToCommitCompositionResult::eHandledSynchronously
:
477 return "Commit reqeust is handled synchronously";
479 return "Unknown reason";
482 nsTArray
<RequestIMEToCommitCompositionResult
>
483 mRequestIMEToCommitCompositionResults
;
484 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
486 // mBrowserParent is owner of the instance.
487 dom::BrowserParent
& MOZ_NON_OWNING_REF mBrowserParent
;
488 // mCompositionString is composition string which were sent to the remote
489 // process but not yet committed in the remote process.
490 nsString mCompositionString
;
491 // This is not nullptr only while the instance is requesting IME to
492 // composition. Then, data value of dispatched composition events should
493 // be stored into the instance.
494 nsAString
* mCommitStringByRequest
;
495 // mPendingEventsNeedingAck is increased before sending a composition event or
496 // a selection event and decreased after they are received in the child
498 uint32_t mPendingEventsNeedingAck
;
499 // mCompositionStartInChild stores current composition start offset in the
501 Maybe
<uint32_t> mCompositionStartInChild
;
502 // mPendingCommitLength is commit string length of the first pending
503 // composition. This is used by relative offset query events when querying
504 // new composition start offset.
505 // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
506 // more pending compositions, this cache won't be used because in such case,
507 // anyway ContentCacheInParent cannot return proper character rect.
508 uint32_t mPendingCommitLength
;
509 // mPendingCompositionCount is number of compositions which started in widget
510 // but not yet handled in the child process.
511 uint8_t mPendingCompositionCount
;
512 // mPendingCommitCount is number of eCompositionCommit(AsIs) events which
513 // were sent to the child process but not yet handled in it.
514 uint8_t mPendingCommitCount
;
515 // mWidgetHasComposition is true when the widget in this process thinks that
516 // IME has composition. So, this is set to true when eCompositionStart is
517 // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
518 bool mWidgetHasComposition
;
519 // mIsChildIgnoringCompositionEvents is set to true if the child process
520 // requests commit composition whose commit has already been sent to it.
521 // Then, set to false when the child process ignores the commit event.
522 bool mIsChildIgnoringCompositionEvents
;
524 ContentCacheInParent() = delete;
527 * When following methods' aRoundToExistingOffset is true, even if specified
528 * offset or range is out of bounds, the result is computed with the existing
531 bool GetCaretRect(uint32_t aOffset
, bool aRoundToExistingOffset
,
532 LayoutDeviceIntRect
& aCaretRect
) const;
533 bool GetTextRect(uint32_t aOffset
, bool aRoundToExistingOffset
,
534 LayoutDeviceIntRect
& aTextRect
) const;
535 bool GetUnionTextRects(uint32_t aOffset
, uint32_t aLength
,
536 bool aRoundToExistingOffset
,
537 LayoutDeviceIntRect
& aUnionTextRect
) const;
539 void FlushPendingNotifications(nsIWidget
* aWidget
);
541 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
543 * Remove unnecessary messages from mDispatchedEventMessages and
544 * mReceivedEventMessages.
546 void RemoveUnnecessaryEventMessageLog();
549 * Append event message log to aLog.
551 void AppendEventMessageLog(nsACString
& aLog
) const;
552 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
555 } // namespace mozilla
557 #endif // mozilla_ContentCache_h