Bug 1755316 - Add audio tests with simultaneous processes r=alwu
[gecko.git] / widget / ContentCache.h
blob25eaf643ae44c3eb7f2131bfa0f66544ba07059c
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/. */
8 #ifndef mozilla_ContentCache_h
9 #define mozilla_ContentCache_h
11 #include <stdint.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"
20 #include "nsString.h"
21 #include "nsTArray.h"
22 #include "Units.h"
24 class nsIWidget;
26 namespace mozilla {
28 class ContentCacheInParent;
30 namespace dom {
31 class BrowserParent;
32 } // namespace dom
34 /**
35 * ContentCache stores various information of the child content.
36 * This class has members which are necessary both in parent process and
37 * content process.
40 class ContentCache {
41 public:
42 typedef CopyableTArray<LayoutDeviceIntRect> RectArray;
43 typedef widget::IMENotification IMENotification;
45 ContentCache() = default;
47 protected:
48 // Whole text in the target
49 Maybe<nsString> mText;
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".
58 uint32_t mAnchor;
59 uint32_t mFocus;
61 WritingMode mWritingMode;
63 bool mHasRange;
65 // Character rects at previous and next character of mAnchor and mFocus.
66 // The reason why ContentCache needs to store each previous character of
67 // them is IME may query character rect of the last character of a line
68 // when caret is at the end of the line.
69 // Note that use ePrevCharRect and eNextCharRect for accessing each item.
70 LayoutDeviceIntRect mAnchorCharRects[2];
71 LayoutDeviceIntRect mFocusCharRects[2];
73 // Whole rect of selected text. This is empty if the selection is collapsed.
74 LayoutDeviceIntRect mRect;
76 Selection() : mAnchor(UINT32_MAX), mFocus(UINT32_MAX), mHasRange(false) {
77 ClearRects();
80 explicit Selection(
81 const IMENotification::SelectionChangeDataBase& aSelectionChangeData)
82 : mAnchor(UINT32_MAX),
83 mFocus(UINT32_MAX),
84 mWritingMode(aSelectionChangeData.GetWritingMode()),
85 mHasRange(aSelectionChangeData.HasRange()) {
86 if (mHasRange) {
87 mAnchor = aSelectionChangeData.AnchorOffset();
88 mFocus = aSelectionChangeData.FocusOffset();
92 explicit Selection(const WidgetQueryContentEvent& aQuerySelectedTextEvent);
94 void ClearRects() {
95 for (auto& rect : mAnchorCharRects) {
96 rect.SetEmpty();
98 for (auto& rect : mFocusCharRects) {
99 rect.SetEmpty();
101 mRect.SetEmpty();
103 bool HasRects() const {
104 for (auto& rect : mAnchorCharRects) {
105 if (!rect.IsEmpty()) {
106 return true;
109 for (auto& rect : mFocusCharRects) {
110 if (!rect.IsEmpty()) {
111 return true;
114 return !mRect.IsEmpty();
117 bool IsCollapsed() const { return !mHasRange || mFocus == mAnchor; }
118 bool Reversed() const {
119 MOZ_ASSERT(mHasRange);
120 return mFocus < mAnchor;
122 uint32_t StartOffset() const {
123 MOZ_ASSERT(mHasRange);
124 return Reversed() ? mFocus : mAnchor;
126 uint32_t EndOffset() const {
127 MOZ_ASSERT(mHasRange);
128 return Reversed() ? mAnchor : mFocus;
130 uint32_t Length() const {
131 MOZ_ASSERT(mHasRange);
132 return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
134 LayoutDeviceIntRect StartCharRect() const {
135 return Reversed() ? mFocusCharRects[eNextCharRect]
136 : mAnchorCharRects[eNextCharRect];
138 LayoutDeviceIntRect EndCharRect() const {
139 return Reversed() ? mAnchorCharRects[eNextCharRect]
140 : mFocusCharRects[eNextCharRect];
143 friend std::ostream& operator<<(std::ostream& aStream,
144 const Selection& aSelection) {
145 aStream << "{ ";
146 if (!aSelection.mHasRange) {
147 aStream << "HasRange()=false";
148 } else {
149 aStream << "mAnchor=" << aSelection.mAnchor
150 << ", mFocus=" << aSelection.mFocus << ", mWritingMode="
151 << ToString(aSelection.mWritingMode).c_str();
153 if (aSelection.HasRects()) {
154 if (aSelection.mAnchor > 0) {
155 aStream << ", mAnchorCharRects[ePrevCharRect]="
156 << aSelection.mAnchorCharRects[ContentCache::ePrevCharRect];
158 aStream << ", mAnchorCharRects[eNextCharRect]="
159 << aSelection.mAnchorCharRects[ContentCache::eNextCharRect];
160 if (aSelection.mFocus > 0) {
161 aStream << ", mFocusCharRects[ePrevCharRect]="
162 << aSelection.mFocusCharRects[ContentCache::ePrevCharRect];
164 aStream << ", mFocusCharRects[eNextCharRect]="
165 << aSelection.mFocusCharRects[ContentCache::eNextCharRect]
166 << ", mRect=" << aSelection.mRect;
168 if (aSelection.mHasRange) {
169 aStream << ", Reversed()=" << (aSelection.Reversed() ? "true" : "false")
170 << ", StartOffset()=" << aSelection.StartOffset()
171 << ", EndOffset()=" << aSelection.EndOffset()
172 << ", IsCollapsed()="
173 << (aSelection.IsCollapsed() ? "true" : "false")
174 << ", Length()=" << aSelection.Length();
176 aStream << " }";
177 return aStream;
180 Maybe<Selection> mSelection;
182 // Stores first char rect because Yosemite's Japanese IME sometimes tries
183 // to query it. If there is no text, this is caret rect.
184 LayoutDeviceIntRect mFirstCharRect;
186 struct Caret final {
187 uint32_t mOffset;
188 LayoutDeviceIntRect mRect;
190 explicit Caret(uint32_t aOffset, LayoutDeviceIntRect aCaretRect)
191 : mOffset(aOffset), mRect(aCaretRect) {}
193 uint32_t Offset() const { return mOffset; }
194 bool HasRect() const { return !mRect.IsEmpty(); }
196 friend std::ostream& operator<<(std::ostream& aStream,
197 const Caret& aCaret) {
198 aStream << "{ mOffset=" << aCaret.mOffset;
199 if (aCaret.HasRect()) {
200 aStream << ", mRect=" << aCaret.mRect;
202 return aStream << " }";
205 private:
206 Caret() = default;
208 friend struct IPC::ParamTraits<ContentCache::Caret>;
209 friend struct IPC::ParamTraits<Maybe<ContentCache::Caret>>;
211 Maybe<Caret> mCaret;
213 struct TextRectArray final {
214 uint32_t mStart;
215 RectArray mRects;
217 explicit TextRectArray(uint32_t aStartOffset) : mStart(aStartOffset) {}
219 bool HasRects() const { return Length() > 0; }
220 uint32_t StartOffset() const { return mStart; }
221 uint32_t EndOffset() const {
222 CheckedInt<uint32_t> endOffset =
223 CheckedInt<uint32_t>(mStart) + mRects.Length();
224 return endOffset.isValid() ? endOffset.value() : UINT32_MAX;
226 uint32_t Length() const { return EndOffset() - mStart; }
227 bool IsOffsetInRange(uint32_t aOffset) const {
228 return StartOffset() <= aOffset && aOffset < EndOffset();
230 bool IsRangeCompletelyInRange(uint32_t aOffset, uint32_t aLength) const {
231 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
232 if (NS_WARN_IF(!endOffset.isValid())) {
233 return false;
235 return IsOffsetInRange(aOffset) && aOffset + aLength <= EndOffset();
237 bool IsOverlappingWith(uint32_t aOffset, uint32_t aLength) const {
238 if (!HasRects() || aOffset == UINT32_MAX || !aLength) {
239 return false;
241 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
242 if (NS_WARN_IF(!endOffset.isValid())) {
243 return false;
245 return aOffset < EndOffset() && endOffset.value() > mStart;
247 LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
248 LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
249 LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
250 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const;
252 friend std::ostream& operator<<(std::ostream& aStream,
253 const TextRectArray& aTextRectArray) {
254 aStream << "{ mStart=" << aTextRectArray.mStart
255 << ", mRects={ Length()=" << aTextRectArray.Length();
256 if (aTextRectArray.HasRects()) {
257 aStream << ", Elements()=[ ";
258 static constexpr uint32_t kMaxPrintRects = 4;
259 const uint32_t kFirstHalf = aTextRectArray.Length() <= kMaxPrintRects
260 ? UINT32_MAX
261 : (kMaxPrintRects + 1) / 2;
262 const uint32_t kSecondHalf =
263 aTextRectArray.Length() <= kMaxPrintRects ? 0 : kMaxPrintRects / 2;
264 for (uint32_t i = 0; i < aTextRectArray.Length(); i++) {
265 if (i > 0) {
266 aStream << ", ";
268 aStream << ToString(aTextRectArray.mRects[i]).c_str();
269 if (i + 1 == kFirstHalf) {
270 aStream << " ...";
271 i = aTextRectArray.Length() - kSecondHalf - 1;
275 return aStream << " ] } }";
278 private:
279 TextRectArray() = default;
281 friend struct IPC::ParamTraits<ContentCache::TextRectArray>;
282 friend struct IPC::ParamTraits<Maybe<ContentCache::TextRectArray>>;
284 Maybe<TextRectArray> mTextRectArray;
285 Maybe<TextRectArray> mLastCommitStringTextRectArray;
287 LayoutDeviceIntRect mEditorRect;
289 friend class ContentCacheInParent;
290 friend struct IPC::ParamTraits<ContentCache>;
291 friend struct IPC::ParamTraits<ContentCache::Selection>;
292 friend struct IPC::ParamTraits<ContentCache::Caret>;
293 friend struct IPC::ParamTraits<ContentCache::TextRectArray>;
294 friend std::ostream& operator<<(
295 std::ostream& aStream,
296 const Selection& aSelection); // For e(Prev|Next)CharRect
299 class ContentCacheInChild final : public ContentCache {
300 public:
301 ContentCacheInChild() = default;
304 * Called when composition event will be dispatched in this process from
305 * PuppetWidget.
307 void OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
310 * When IME loses focus, this should be called and making this forget the
311 * content for reducing footprint.
313 void Clear();
316 * Cache*() retrieves the latest content information and store them.
317 * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
318 * calls CacheSelection(). So, related data is also retrieved automatically.
320 bool CacheEditorRect(nsIWidget* aWidget,
321 const IMENotification* aNotification = nullptr);
322 bool CacheSelection(nsIWidget* aWidget,
323 const IMENotification* aNotification = nullptr);
324 bool CacheText(nsIWidget* aWidget,
325 const IMENotification* aNotification = nullptr);
327 bool CacheAll(nsIWidget* aWidget,
328 const IMENotification* aNotification = nullptr);
331 * SetSelection() modifies selection with specified raw data. And also this
332 * tries to retrieve text rects too.
334 void SetSelection(
335 nsIWidget* aWidget,
336 const IMENotification::SelectionChangeDataBase& aSelectionChangeData);
338 private:
339 bool QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
340 LayoutDeviceIntRect& aCharRect) const;
341 bool QueryCharRectArray(nsIWidget* aWidget, uint32_t aOffset,
342 uint32_t aLength, RectArray& aCharRectArray) const;
343 bool CacheCaret(nsIWidget* aWidget,
344 const IMENotification* aNotification = nullptr);
345 bool CacheTextRects(nsIWidget* aWidget,
346 const IMENotification* aNotification = nullptr);
348 // Once composition is committed, all of the commit string may be composed
349 // again by Kakutei-Undo of Japanese IME. Therefore, we need to keep
350 // storing the last composition start to cache all character rects of the
351 // last commit string.
352 Maybe<OffsetAndData<uint32_t>> mLastCommit;
355 class ContentCacheInParent final : public ContentCache {
356 public:
357 explicit ContentCacheInParent(dom::BrowserParent& aBrowserParent);
360 * AssignContent() is called when BrowserParent receives ContentCache from
361 * the content process. This doesn't copy composition information because
362 * it's managed by BrowserParent itself.
364 void AssignContent(const ContentCache& aOther, nsIWidget* aWidget,
365 const IMENotification* aNotification = nullptr);
368 * HandleQueryContentEvent() sets content data to aEvent.mReply.
370 * For eQuerySelectedText, fail if the cache doesn't contain the whole
371 * selected range. (This shouldn't happen because PuppetWidget should have
372 * already sent the whole selection.)
374 * For eQueryTextContent, fail only if the cache doesn't overlap with
375 * the queried range. Note the difference from above. We use
376 * this behavior because a normal eQueryTextContent event is allowed to
377 * have out-of-bounds offsets, so that widget can request content without
378 * knowing the exact length of text. It's up to widget to handle cases when
379 * the returned offset/length are different from the queried offset/length.
381 * For eQueryTextRect, fail if cached offset/length aren't equals to input.
382 * Cocoa widget always queries selected offset, so it works on it.
384 * For eQueryCaretRect, fail if cached offset isn't equals to input
386 * For eQueryEditorRect, always success
388 bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
389 nsIWidget* aWidget) const;
392 * OnCompositionEvent() should be called before sending composition string.
393 * This returns true if the event should be sent. Otherwise, false.
395 bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
398 * OnSelectionEvent() should be called before sending selection event.
400 void OnSelectionEvent(const WidgetSelectionEvent& aSelectionEvent);
403 * OnEventNeedingAckHandled() should be called after the child process
404 * handles a sent event which needs acknowledging.
406 * WARNING: This may send notifications to IME. That might cause destroying
407 * BrowserParent or aWidget. Therefore, the caller must not destroy
408 * this instance during a call of this method.
410 void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage);
413 * RequestIMEToCommitComposition() requests aWidget to commit or cancel
414 * composition. If it's handled synchronously, this returns true.
416 * @param aWidget The widget to be requested to commit or cancel
417 * the composition.
418 * @param aCancel When the caller tries to cancel the composition, true.
419 * Otherwise, i.e., tries to commit the composition, false.
420 * @param aCommittedString The committed string (i.e., the last data of
421 * dispatched composition events during requesting
422 * IME to commit composition.
423 * @return Whether the composition is actually committed
424 * synchronously.
426 bool RequestIMEToCommitComposition(nsIWidget* aWidget, bool aCancel,
427 nsAString& aCommittedString);
430 * MaybeNotifyIME() may notify IME of the notification. If child process
431 * hasn't been handled all sending events yet, this stores the notification
432 * and flush it later.
434 void MaybeNotifyIME(nsIWidget* aWidget, const IMENotification& aNotification);
436 private:
437 IMENotification mPendingSelectionChange;
438 IMENotification mPendingTextChange;
439 IMENotification mPendingLayoutChange;
440 IMENotification mPendingCompositionUpdate;
442 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
443 // Log of event messages to be output to crash report.
444 nsTArray<EventMessage> mDispatchedEventMessages;
445 nsTArray<EventMessage> mReceivedEventMessages;
446 // Log of RequestIMEToCommitComposition() in the last 2 compositions.
447 enum class RequestIMEToCommitCompositionResult : uint8_t {
448 eToOldCompositionReceived,
449 eToCommittedCompositionReceived,
450 eReceivedAfterBrowserParentBlur,
451 eReceivedButNoTextComposition,
452 eHandledAsynchronously,
453 eHandledSynchronously,
455 const char* ToReadableText(
456 RequestIMEToCommitCompositionResult aResult) const {
457 switch (aResult) {
458 case RequestIMEToCommitCompositionResult::eToOldCompositionReceived:
459 return "Commit request is not handled because it's for "
460 "older composition";
461 case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived:
462 return "Commit request is not handled because BrowserParent has "
463 "already "
464 "sent commit event for the composition";
465 case RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur:
466 return "Commit request is handled with stored composition string "
467 "because BrowserParent has already lost focus";
468 case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition:
469 return "Commit request is not handled because there is no "
470 "TextCompsition instance";
471 case RequestIMEToCommitCompositionResult::eHandledAsynchronously:
472 return "Commit request is handled but IME doesn't commit current "
473 "composition synchronously";
474 case RequestIMEToCommitCompositionResult::eHandledSynchronously:
475 return "Commit reqeust is handled synchronously";
476 default:
477 return "Unknown reason";
480 nsTArray<RequestIMEToCommitCompositionResult>
481 mRequestIMEToCommitCompositionResults;
482 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
484 // mBrowserParent is owner of the instance.
485 dom::BrowserParent& MOZ_NON_OWNING_REF mBrowserParent;
486 // mCompositionString is composition string which were sent to the remote
487 // process but not yet committed in the remote process.
488 nsString mCompositionString;
489 // This is not nullptr only while the instance is requesting IME to
490 // composition. Then, data value of dispatched composition events should
491 // be stored into the instance.
492 nsAString* mCommitStringByRequest;
493 // mPendingEventsNeedingAck is increased before sending a composition event or
494 // a selection event and decreased after they are received in the child
495 // process.
496 uint32_t mPendingEventsNeedingAck;
497 // mCompositionStartInChild stores current composition start offset in the
498 // remote process.
499 Maybe<uint32_t> mCompositionStartInChild;
500 // mPendingCommitLength is commit string length of the first pending
501 // composition. This is used by relative offset query events when querying
502 // new composition start offset.
503 // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
504 // more pending compositions, this cache won't be used because in such case,
505 // anyway ContentCacheInParent cannot return proper character rect.
506 uint32_t mPendingCommitLength;
507 // mPendingCompositionCount is number of compositions which started in widget
508 // but not yet handled in the child process.
509 uint8_t mPendingCompositionCount;
510 // mPendingCommitCount is number of eCompositionCommit(AsIs) events which
511 // were sent to the child process but not yet handled in it.
512 uint8_t mPendingCommitCount;
513 // mWidgetHasComposition is true when the widget in this process thinks that
514 // IME has composition. So, this is set to true when eCompositionStart is
515 // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
516 bool mWidgetHasComposition;
517 // mIsChildIgnoringCompositionEvents is set to true if the child process
518 // requests commit composition whose commit has already been sent to it.
519 // Then, set to false when the child process ignores the commit event.
520 bool mIsChildIgnoringCompositionEvents;
522 ContentCacheInParent() = delete;
525 * When following methods' aRoundToExistingOffset is true, even if specified
526 * offset or range is out of bounds, the result is computed with the existing
527 * cache forcibly.
529 bool GetCaretRect(uint32_t aOffset, bool aRoundToExistingOffset,
530 LayoutDeviceIntRect& aCaretRect) const;
531 bool GetTextRect(uint32_t aOffset, bool aRoundToExistingOffset,
532 LayoutDeviceIntRect& aTextRect) const;
533 bool GetUnionTextRects(uint32_t aOffset, uint32_t aLength,
534 bool aRoundToExistingOffset,
535 LayoutDeviceIntRect& aUnionTextRect) const;
537 void FlushPendingNotifications(nsIWidget* aWidget);
539 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
541 * Remove unnecessary messages from mDispatchedEventMessages and
542 * mReceivedEventMessages.
544 void RemoveUnnecessaryEventMessageLog();
547 * Append event message log to aLog.
549 void AppendEventMessageLog(nsACString& aLog) const;
550 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
553 } // namespace mozilla
555 #endif // mozilla_ContentCache_h