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/Assertions.h"
14 #include "mozilla/CheckedInt.h"
15 #include "mozilla/EventForwards.h"
16 #include "mozilla/WritingModes.h"
17 #include "nsIWidget.h"
24 class ContentCacheInParent
;
31 * ContentCache stores various information of the child content.
32 * This class has members which are necessary both in parent process and
38 typedef CopyableTArray
<LayoutDeviceIntRect
> RectArray
;
39 typedef widget::IMENotification IMENotification
;
44 // Whole text in the target
47 // Start offset of the composition string.
48 uint32_t mCompositionStart
;
50 enum { ePrevCharRect
= 1, eNextCharRect
= 0 };
52 struct Selection final
{
53 // Following values are offset in "flat text".
57 WritingMode mWritingMode
;
59 // Character rects at previous and next character of mAnchor and mFocus.
60 // The reason why ContentCache needs to store each previous character of
61 // them is IME may query character rect of the last character of a line
62 // when caret is at the end of the line.
63 // Note that use ePrevCharRect and eNextCharRect for accessing each item.
64 LayoutDeviceIntRect mAnchorCharRects
[2];
65 LayoutDeviceIntRect mFocusCharRects
[2];
67 // Whole rect of selected text. This is empty if the selection is collapsed.
68 LayoutDeviceIntRect mRect
;
70 Selection() : mAnchor(UINT32_MAX
), mFocus(UINT32_MAX
) {}
73 mAnchor
= mFocus
= UINT32_MAX
;
74 mWritingMode
= WritingMode();
75 ClearAnchorCharRects();
76 ClearFocusCharRects();
80 void ClearAnchorCharRects() {
81 for (size_t i
= 0; i
< ArrayLength(mAnchorCharRects
); i
++) {
82 mAnchorCharRects
[i
].SetEmpty();
85 void ClearFocusCharRects() {
86 for (size_t i
= 0; i
< ArrayLength(mFocusCharRects
); i
++) {
87 mFocusCharRects
[i
].SetEmpty();
91 bool IsValid() const {
92 return mAnchor
!= UINT32_MAX
&& mFocus
!= UINT32_MAX
;
94 bool Collapsed() const {
95 NS_ASSERTION(IsValid(),
96 "The caller should check if the selection is valid");
97 return mFocus
== mAnchor
;
99 bool Reversed() const {
100 NS_ASSERTION(IsValid(),
101 "The caller should check if the selection is valid");
102 return mFocus
< mAnchor
;
104 uint32_t StartOffset() const {
105 NS_ASSERTION(IsValid(),
106 "The caller should check if the selection is valid");
107 return Reversed() ? mFocus
: mAnchor
;
109 uint32_t EndOffset() const {
110 NS_ASSERTION(IsValid(),
111 "The caller should check if the selection is valid");
112 return Reversed() ? mAnchor
: mFocus
;
114 uint32_t Length() const {
115 NS_ASSERTION(IsValid(),
116 "The caller should check if the selection is valid");
117 return Reversed() ? mAnchor
- mFocus
: mFocus
- mAnchor
;
119 LayoutDeviceIntRect
StartCharRect() const {
120 NS_ASSERTION(IsValid(),
121 "The caller should check if the selection is valid");
122 return Reversed() ? mFocusCharRects
[eNextCharRect
]
123 : mAnchorCharRects
[eNextCharRect
];
125 LayoutDeviceIntRect
EndCharRect() const {
126 NS_ASSERTION(IsValid(),
127 "The caller should check if the selection is valid");
128 return Reversed() ? mAnchorCharRects
[eNextCharRect
]
129 : mFocusCharRects
[eNextCharRect
];
133 bool IsSelectionValid() const {
134 return mSelection
.IsValid() && mSelection
.EndOffset() <= mText
.Length();
137 // Stores first char rect because Yosemite's Japanese IME sometimes tries
138 // to query it. If there is no text, this is caret rect.
139 LayoutDeviceIntRect mFirstCharRect
;
143 LayoutDeviceIntRect mRect
;
145 Caret() : mOffset(UINT32_MAX
) {}
148 mOffset
= UINT32_MAX
;
152 bool IsValid() const { return mOffset
!= UINT32_MAX
; }
154 uint32_t Offset() const {
155 NS_ASSERTION(IsValid(), "The caller should check if the caret is valid");
160 struct TextRectArray final
{
164 TextRectArray() : mStart(UINT32_MAX
) {}
171 bool IsValid() const {
172 if (mStart
== UINT32_MAX
) {
175 CheckedInt
<uint32_t> endOffset
=
176 CheckedInt
<uint32_t>(mStart
) + mRects
.Length();
177 return endOffset
.isValid();
179 bool HasRects() const { return IsValid() && !mRects
.IsEmpty(); }
180 uint32_t StartOffset() const {
181 NS_ASSERTION(IsValid(), "The caller should check if the caret is valid");
184 uint32_t EndOffset() const {
185 NS_ASSERTION(IsValid(), "The caller should check if the caret is valid");
189 return mStart
+ mRects
.Length();
191 bool InRange(uint32_t aOffset
) const {
192 return IsValid() && StartOffset() <= aOffset
&& aOffset
< EndOffset();
194 bool InRange(uint32_t aOffset
, uint32_t aLength
) const {
195 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
196 if (NS_WARN_IF(!endOffset
.isValid())) {
199 return InRange(aOffset
) && aOffset
+ aLength
<= EndOffset();
201 bool IsOverlappingWith(uint32_t aOffset
, uint32_t aLength
) const {
202 if (!HasRects() || aOffset
== UINT32_MAX
|| !aLength
) {
205 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
206 if (NS_WARN_IF(!endOffset
.isValid())) {
209 return aOffset
< EndOffset() && endOffset
.value() > mStart
;
211 LayoutDeviceIntRect
GetRect(uint32_t aOffset
) const;
212 LayoutDeviceIntRect
GetUnionRect(uint32_t aOffset
, uint32_t aLength
) const;
213 LayoutDeviceIntRect
GetUnionRectAsFarAsPossible(
214 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const;
217 LayoutDeviceIntRect mEditorRect
;
219 friend class ContentCacheInParent
;
220 friend struct IPC::ParamTraits
<ContentCache
>;
223 class ContentCacheInChild final
: public ContentCache
{
225 ContentCacheInChild();
228 * When IME loses focus, this should be called and making this forget the
229 * content for reducing footprint.
234 * Cache*() retrieves the latest content information and store them.
235 * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
236 * calls CacheSelection(). So, related data is also retrieved automatically.
238 bool CacheEditorRect(nsIWidget
* aWidget
,
239 const IMENotification
* aNotification
= nullptr);
240 bool CacheSelection(nsIWidget
* aWidget
,
241 const IMENotification
* aNotification
= nullptr);
242 bool CacheText(nsIWidget
* aWidget
,
243 const IMENotification
* aNotification
= nullptr);
245 bool CacheAll(nsIWidget
* aWidget
,
246 const IMENotification
* aNotification
= nullptr);
249 * SetSelection() modifies selection with specified raw data. And also this
250 * tries to retrieve text rects too.
252 void SetSelection(nsIWidget
* aWidget
, uint32_t aStartOffset
, uint32_t aLength
,
253 bool aReversed
, const WritingMode
& aWritingMode
);
256 bool QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
257 LayoutDeviceIntRect
& aCharRect
) const;
258 bool QueryCharRectArray(nsIWidget
* aWidget
, uint32_t aOffset
,
259 uint32_t aLength
, RectArray
& aCharRectArray
) const;
260 bool CacheCaret(nsIWidget
* aWidget
,
261 const IMENotification
* aNotification
= nullptr);
262 bool CacheTextRects(nsIWidget
* aWidget
,
263 const IMENotification
* aNotification
= nullptr);
266 class ContentCacheInParent final
: public ContentCache
{
268 explicit ContentCacheInParent(dom::BrowserParent
& aBrowserParent
);
271 * AssignContent() is called when BrowserParent receives ContentCache from
272 * the content process. This doesn't copy composition information because
273 * it's managed by BrowserParent itself.
275 void AssignContent(const ContentCache
& aOther
, nsIWidget
* aWidget
,
276 const IMENotification
* aNotification
= nullptr);
279 * HandleQueryContentEvent() sets content data to aEvent.mReply.
281 * For eQuerySelectedText, fail if the cache doesn't contain the whole
282 * selected range. (This shouldn't happen because PuppetWidget should have
283 * already sent the whole selection.)
285 * For eQueryTextContent, fail only if the cache doesn't overlap with
286 * the queried range. Note the difference from above. We use
287 * this behavior because a normal eQueryTextContent event is allowed to
288 * have out-of-bounds offsets, so that widget can request content without
289 * knowing the exact length of text. It's up to widget to handle cases when
290 * the returned offset/length are different from the queried offset/length.
292 * For eQueryTextRect, fail if cached offset/length aren't equals to input.
293 * Cocoa widget always queries selected offset, so it works on it.
295 * For eQueryCaretRect, fail if cached offset isn't equals to input
297 * For eQueryEditorRect, always success
299 bool HandleQueryContentEvent(WidgetQueryContentEvent
& aEvent
,
300 nsIWidget
* aWidget
) const;
303 * OnCompositionEvent() should be called before sending composition string.
304 * This returns true if the event should be sent. Otherwise, false.
306 bool OnCompositionEvent(const WidgetCompositionEvent
& aCompositionEvent
);
309 * OnSelectionEvent() should be called before sending selection event.
311 void OnSelectionEvent(const WidgetSelectionEvent
& aSelectionEvent
);
314 * OnEventNeedingAckHandled() should be called after the child process
315 * handles a sent event which needs acknowledging.
317 * WARNING: This may send notifications to IME. That might cause destroying
318 * BrowserParent or aWidget. Therefore, the caller must not destroy
319 * this instance during a call of this method.
321 void OnEventNeedingAckHandled(nsIWidget
* aWidget
, EventMessage aMessage
);
324 * RequestIMEToCommitComposition() requests aWidget to commit or cancel
325 * composition. If it's handled synchronously, this returns true.
327 * @param aWidget The widget to be requested to commit or cancel
329 * @param aCancel When the caller tries to cancel the composition, true.
330 * Otherwise, i.e., tries to commit the composition, false.
331 * @param aCommittedString The committed string (i.e., the last data of
332 * dispatched composition events during requesting
333 * IME to commit composition.
334 * @return Whether the composition is actually committed
337 bool RequestIMEToCommitComposition(nsIWidget
* aWidget
, bool aCancel
,
338 nsAString
& aCommittedString
);
341 * MaybeNotifyIME() may notify IME of the notification. If child process
342 * hasn't been handled all sending events yet, this stores the notification
343 * and flush it later.
345 void MaybeNotifyIME(nsIWidget
* aWidget
, const IMENotification
& aNotification
);
348 IMENotification mPendingSelectionChange
;
349 IMENotification mPendingTextChange
;
350 IMENotification mPendingLayoutChange
;
351 IMENotification mPendingCompositionUpdate
;
353 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
354 // Log of event messages to be output to crash report.
355 nsTArray
<EventMessage
> mDispatchedEventMessages
;
356 nsTArray
<EventMessage
> mReceivedEventMessages
;
357 // Log of RequestIMEToCommitComposition() in the last 2 compositions.
358 enum class RequestIMEToCommitCompositionResult
: uint8_t {
359 eToOldCompositionReceived
,
360 eToCommittedCompositionReceived
,
361 eReceivedAfterBrowserParentBlur
,
362 eReceivedButNoTextComposition
,
363 eHandledAsynchronously
,
364 eHandledSynchronously
,
366 const char* ToReadableText(
367 RequestIMEToCommitCompositionResult aResult
) const {
369 case RequestIMEToCommitCompositionResult::eToOldCompositionReceived
:
370 return "Commit request is not handled because it's for "
372 case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
:
373 return "Commit request is not handled because BrowserParent has "
375 "sent commit event for the composition";
376 case RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
:
377 return "Commit request is handled with stored composition string "
378 "because BrowserParent has already lost focus";
379 case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
:
380 return "Commit request is not handled because there is no "
381 "TextCompsition instance";
382 case RequestIMEToCommitCompositionResult::eHandledAsynchronously
:
383 return "Commit request is handled but IME doesn't commit current "
384 "composition synchronously";
385 case RequestIMEToCommitCompositionResult::eHandledSynchronously
:
386 return "Commit reqeust is handled synchronously";
388 return "Unknown reason";
391 nsTArray
<RequestIMEToCommitCompositionResult
>
392 mRequestIMEToCommitCompositionResults
;
393 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
395 // mBrowserParent is owner of the instance.
396 dom::BrowserParent
& MOZ_NON_OWNING_REF mBrowserParent
;
397 // mCompositionString is composition string which were sent to the remote
398 // process but not yet committed in the remote process.
399 nsString mCompositionString
;
400 // This is not nullptr only while the instance is requesting IME to
401 // composition. Then, data value of dispatched composition events should
402 // be stored into the instance.
403 nsAString
* mCommitStringByRequest
;
404 // mPendingEventsNeedingAck is increased before sending a composition event or
405 // a selection event and decreased after they are received in the child
407 uint32_t mPendingEventsNeedingAck
;
408 // mCompositionStartInChild stores current composition start offset in the
410 uint32_t mCompositionStartInChild
;
411 // mPendingCommitLength is commit string length of the first pending
412 // composition. This is used by relative offset query events when querying
413 // new composition start offset.
414 // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
415 // more pending compositions, this cache won't be used because in such case,
416 // anyway ContentCacheInParent cannot return proper character rect.
417 uint32_t mPendingCommitLength
;
418 // mPendingCompositionCount is number of compositions which started in widget
419 // but not yet handled in the child process.
420 uint8_t mPendingCompositionCount
;
421 // mPendingCommitCount is number of eCompositionCommit(AsIs) events which
422 // were sent to the child process but not yet handled in it.
423 uint8_t mPendingCommitCount
;
424 // mWidgetHasComposition is true when the widget in this process thinks that
425 // IME has composition. So, this is set to true when eCompositionStart is
426 // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
427 bool mWidgetHasComposition
;
428 // mIsChildIgnoringCompositionEvents is set to true if the child process
429 // requests commit composition whose commit has already been sent to it.
430 // Then, set to false when the child process ignores the commit event.
431 bool mIsChildIgnoringCompositionEvents
;
433 ContentCacheInParent() = delete;
436 * When following methods' aRoundToExistingOffset is true, even if specified
437 * offset or range is out of bounds, the result is computed with the existing
440 bool GetCaretRect(uint32_t aOffset
, bool aRoundToExistingOffset
,
441 LayoutDeviceIntRect
& aCaretRect
) const;
442 bool GetTextRect(uint32_t aOffset
, bool aRoundToExistingOffset
,
443 LayoutDeviceIntRect
& aTextRect
) const;
444 bool GetUnionTextRects(uint32_t aOffset
, uint32_t aLength
,
445 bool aRoundToExistingOffset
,
446 LayoutDeviceIntRect
& aUnionTextRect
) const;
448 void FlushPendingNotifications(nsIWidget
* aWidget
);
450 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
452 * Remove unnecessary messages from mDispatchedEventMessages and
453 * mReceivedEventMessages.
455 void RemoveUnnecessaryEventMessageLog();
458 * Append event message log to aLog.
460 void AppendEventMessageLog(nsACString
& aLog
) const;
461 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
464 } // namespace mozilla
466 #endif // mozilla_ContentCache_h