Line data Source code
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 : #include "mozilla/ContentCache.h"
9 :
10 : #include "mozilla/IMEStateManager.h"
11 : #include "mozilla/IntegerPrintfMacros.h"
12 : #include "mozilla/Logging.h"
13 : #include "mozilla/Move.h"
14 : #include "mozilla/RefPtr.h"
15 : #include "mozilla/SizePrintfMacros.h"
16 : #include "mozilla/TextComposition.h"
17 : #include "mozilla/TextEvents.h"
18 : #include "mozilla/dom/TabParent.h"
19 : #include "nsIWidget.h"
20 :
21 : namespace mozilla {
22 :
23 : using namespace dom;
24 : using namespace widget;
25 :
26 : static const char*
27 0 : GetBoolName(bool aBool)
28 : {
29 0 : return aBool ? "true" : "false";
30 : }
31 :
32 : static const char*
33 0 : GetNotificationName(const IMENotification* aNotification)
34 : {
35 0 : if (!aNotification) {
36 0 : return "Not notification";
37 : }
38 0 : return ToChar(aNotification->mMessage);
39 : }
40 :
41 : class GetRectText : public nsAutoCString
42 : {
43 : public:
44 0 : explicit GetRectText(const LayoutDeviceIntRect& aRect)
45 0 : {
46 0 : Assign("{ x=");
47 0 : AppendInt(aRect.x);
48 0 : Append(", y=");
49 0 : AppendInt(aRect.y);
50 0 : Append(", width=");
51 0 : AppendInt(aRect.width);
52 0 : Append(", height=");
53 0 : AppendInt(aRect.height);
54 0 : Append(" }");
55 0 : }
56 0 : virtual ~GetRectText() {}
57 : };
58 :
59 : class GetWritingModeName : public nsAutoCString
60 : {
61 : public:
62 0 : explicit GetWritingModeName(const WritingMode& aWritingMode)
63 0 : {
64 0 : if (!aWritingMode.IsVertical()) {
65 0 : Assign("Horizontal");
66 0 : return;
67 : }
68 0 : if (aWritingMode.IsVerticalLR()) {
69 0 : Assign("Vertical (LTR)");
70 0 : return;
71 : }
72 0 : Assign("Vertical (RTL)");
73 : }
74 0 : virtual ~GetWritingModeName() {}
75 : };
76 :
77 0 : class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8
78 : {
79 : public:
80 0 : explicit GetEscapedUTF8String(const nsAString& aString)
81 0 : : NS_ConvertUTF16toUTF8(aString)
82 : {
83 0 : Escape();
84 0 : }
85 : explicit GetEscapedUTF8String(const char16ptr_t aString)
86 : : NS_ConvertUTF16toUTF8(aString)
87 : {
88 : Escape();
89 : }
90 : GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
91 : : NS_ConvertUTF16toUTF8(aString, aLength)
92 : {
93 : Escape();
94 : }
95 :
96 : private:
97 0 : void Escape()
98 : {
99 0 : ReplaceSubstring("\r", "\\r");
100 0 : ReplaceSubstring("\n", "\\n");
101 0 : ReplaceSubstring("\t", "\\t");
102 0 : }
103 : };
104 :
105 : /*****************************************************************************
106 : * mozilla::ContentCache
107 : *****************************************************************************/
108 :
109 : LazyLogModule sContentCacheLog("ContentCacheWidgets");
110 :
111 3 : ContentCache::ContentCache()
112 3 : : mCompositionStart(UINT32_MAX)
113 : {
114 3 : }
115 :
116 : /*****************************************************************************
117 : * mozilla::ContentCacheInChild
118 : *****************************************************************************/
119 :
120 1 : ContentCacheInChild::ContentCacheInChild()
121 1 : : ContentCache()
122 : {
123 1 : }
124 :
125 : void
126 1 : ContentCacheInChild::Clear()
127 : {
128 1 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
129 : ("0x%p Clear()", this));
130 :
131 1 : mCompositionStart = UINT32_MAX;
132 1 : mText.Truncate();
133 1 : mSelection.Clear();
134 1 : mFirstCharRect.SetEmpty();
135 1 : mCaret.Clear();
136 1 : mTextRectArray.Clear();
137 1 : mEditorRect.SetEmpty();
138 1 : }
139 :
140 : bool
141 0 : ContentCacheInChild::CacheAll(nsIWidget* aWidget,
142 : const IMENotification* aNotification)
143 : {
144 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
145 : ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)",
146 : this, aWidget, GetNotificationName(aNotification)));
147 :
148 0 : if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
149 0 : NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
150 0 : return false;
151 : }
152 0 : return true;
153 : }
154 :
155 : bool
156 0 : ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
157 : const IMENotification* aNotification)
158 : {
159 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
160 : ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)",
161 : this, aWidget, GetNotificationName(aNotification)));
162 :
163 0 : mCaret.Clear();
164 0 : mSelection.Clear();
165 :
166 0 : nsEventStatus status = nsEventStatus_eIgnore;
167 0 : WidgetQueryContentEvent selection(true, eQuerySelectedText, aWidget);
168 0 : aWidget->DispatchEvent(&selection, status);
169 0 : if (NS_WARN_IF(!selection.mSucceeded)) {
170 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
171 : ("0x%p CacheSelection(), FAILED, "
172 : "couldn't retrieve the selected text", this));
173 0 : return false;
174 : }
175 0 : if (selection.mReply.mReversed) {
176 0 : mSelection.mAnchor =
177 0 : selection.mReply.mOffset + selection.mReply.mString.Length();
178 0 : mSelection.mFocus = selection.mReply.mOffset;
179 : } else {
180 0 : mSelection.mAnchor = selection.mReply.mOffset;
181 0 : mSelection.mFocus =
182 0 : selection.mReply.mOffset + selection.mReply.mString.Length();
183 : }
184 0 : mSelection.mWritingMode = selection.GetWritingMode();
185 :
186 0 : return CacheCaret(aWidget, aNotification) &&
187 0 : CacheTextRects(aWidget, aNotification);
188 : }
189 :
190 : bool
191 0 : ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
192 : const IMENotification* aNotification)
193 : {
194 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
195 : ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)",
196 : this, aWidget, GetNotificationName(aNotification)));
197 :
198 0 : mCaret.Clear();
199 :
200 0 : if (NS_WARN_IF(!mSelection.IsValid())) {
201 0 : return false;
202 : }
203 :
204 : // XXX Should be mSelection.mFocus?
205 0 : mCaret.mOffset = mSelection.StartOffset();
206 :
207 0 : nsEventStatus status = nsEventStatus_eIgnore;
208 0 : WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWidget);
209 0 : caretRect.InitForQueryCaretRect(mCaret.mOffset);
210 0 : aWidget->DispatchEvent(&caretRect, status);
211 0 : if (NS_WARN_IF(!caretRect.mSucceeded)) {
212 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
213 : ("0x%p CacheCaret(), FAILED, "
214 : "couldn't retrieve the caret rect at offset=%u",
215 : this, mCaret.mOffset));
216 0 : mCaret.Clear();
217 0 : return false;
218 : }
219 0 : mCaret.mRect = caretRect.mReply.mRect;
220 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
221 : ("0x%p CacheCaret(), Succeeded, "
222 : "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
223 : "mCaret={ mOffset=%u, mRect=%s }",
224 : this, mSelection.mAnchor, mSelection.mFocus,
225 : GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
226 : GetRectText(mCaret.mRect).get()));
227 0 : return true;
228 : }
229 :
230 : bool
231 0 : ContentCacheInChild::CacheEditorRect(nsIWidget* aWidget,
232 : const IMENotification* aNotification)
233 : {
234 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
235 : ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)",
236 : this, aWidget, GetNotificationName(aNotification)));
237 :
238 0 : nsEventStatus status = nsEventStatus_eIgnore;
239 0 : WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWidget);
240 0 : aWidget->DispatchEvent(&editorRectEvent, status);
241 0 : if (NS_WARN_IF(!editorRectEvent.mSucceeded)) {
242 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
243 : ("0x%p CacheEditorRect(), FAILED, "
244 : "couldn't retrieve the editor rect", this));
245 0 : return false;
246 : }
247 0 : mEditorRect = editorRectEvent.mReply.mRect;
248 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
249 : ("0x%p CacheEditorRect(), Succeeded, "
250 : "mEditorRect=%s", this, GetRectText(mEditorRect).get()));
251 0 : return true;
252 : }
253 :
254 : bool
255 0 : ContentCacheInChild::CacheText(nsIWidget* aWidget,
256 : const IMENotification* aNotification)
257 : {
258 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
259 : ("0x%p CacheText(aWidget=0x%p, aNotification=%s)",
260 : this, aWidget, GetNotificationName(aNotification)));
261 :
262 0 : nsEventStatus status = nsEventStatus_eIgnore;
263 0 : WidgetQueryContentEvent queryText(true, eQueryTextContent, aWidget);
264 0 : queryText.InitForQueryTextContent(0, UINT32_MAX);
265 0 : aWidget->DispatchEvent(&queryText, status);
266 0 : if (NS_WARN_IF(!queryText.mSucceeded)) {
267 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
268 : ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
269 0 : mText.Truncate();
270 0 : return false;
271 : }
272 0 : mText = queryText.mReply.mString;
273 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
274 : ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));
275 :
276 0 : return CacheSelection(aWidget, aNotification);
277 : }
278 :
279 : bool
280 0 : ContentCacheInChild::QueryCharRect(nsIWidget* aWidget,
281 : uint32_t aOffset,
282 : LayoutDeviceIntRect& aCharRect) const
283 : {
284 0 : aCharRect.SetEmpty();
285 :
286 0 : nsEventStatus status = nsEventStatus_eIgnore;
287 0 : WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
288 0 : textRect.InitForQueryTextRect(aOffset, 1);
289 0 : aWidget->DispatchEvent(&textRect, status);
290 0 : if (NS_WARN_IF(!textRect.mSucceeded)) {
291 0 : return false;
292 : }
293 0 : aCharRect = textRect.mReply.mRect;
294 :
295 : // Guarantee the rect is not empty.
296 0 : if (NS_WARN_IF(!aCharRect.height)) {
297 0 : aCharRect.height = 1;
298 : }
299 0 : if (NS_WARN_IF(!aCharRect.width)) {
300 0 : aCharRect.width = 1;
301 : }
302 0 : return true;
303 : }
304 :
305 : bool
306 0 : ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
307 : uint32_t aOffset,
308 : uint32_t aLength,
309 : RectArray& aCharRectArray) const
310 : {
311 0 : nsEventStatus status = nsEventStatus_eIgnore;
312 0 : WidgetQueryContentEvent textRects(true, eQueryTextRectArray, aWidget);
313 0 : textRects.InitForQueryTextRectArray(aOffset, aLength);
314 0 : aWidget->DispatchEvent(&textRects, status);
315 0 : if (NS_WARN_IF(!textRects.mSucceeded)) {
316 0 : aCharRectArray.Clear();
317 0 : return false;
318 : }
319 0 : aCharRectArray = Move(textRects.mReply.mRectArray);
320 0 : return true;
321 : }
322 :
323 : bool
324 0 : ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
325 : const IMENotification* aNotification)
326 : {
327 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
328 : ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), "
329 : "mCaret={ mOffset=%u, IsValid()=%s }",
330 : this, aWidget, GetNotificationName(aNotification), mCaret.mOffset,
331 : GetBoolName(mCaret.IsValid())));
332 :
333 0 : mCompositionStart = UINT32_MAX;
334 0 : mTextRectArray.Clear();
335 0 : mSelection.ClearAnchorCharRects();
336 0 : mSelection.ClearFocusCharRects();
337 0 : mSelection.mRect.SetEmpty();
338 0 : mFirstCharRect.SetEmpty();
339 :
340 0 : if (NS_WARN_IF(!mSelection.IsValid())) {
341 0 : return false;
342 : }
343 :
344 : // Retrieve text rects in composition string if there is.
345 : RefPtr<TextComposition> textComposition =
346 0 : IMEStateManager::GetTextCompositionFor(aWidget);
347 0 : if (textComposition) {
348 : // mCompositionStart may be updated by some composition event handlers.
349 : // So, let's update it with the latest information.
350 0 : mCompositionStart = textComposition->NativeOffsetOfStartComposition();
351 : // Note that TextComposition::String() may not be modified here because
352 : // it's modified after all edit action listeners are performed but this
353 : // is called while some of them are performed.
354 : // FYI: For supporting IME which commits composition and restart new
355 : // composition immediately, we should cache next character of current
356 : // composition too.
357 0 : uint32_t length = textComposition->LastData().Length() + 1;
358 0 : mTextRectArray.mStart = mCompositionStart;
359 0 : if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray.mStart, length,
360 : mTextRectArray.mRects))) {
361 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
362 : ("0x%p CacheTextRects(), FAILED, "
363 : "couldn't retrieve text rect array of the composition string", this));
364 : }
365 : }
366 :
367 0 : if (mTextRectArray.InRange(mSelection.mAnchor) &&
368 0 : (!mSelection.mAnchor || mTextRectArray.InRange(mSelection.mAnchor - 1))) {
369 : mSelection.mAnchorCharRects[eNextCharRect] =
370 0 : mTextRectArray.GetRect(mSelection.mAnchor);
371 0 : if (mSelection.mAnchor) {
372 : mSelection.mAnchorCharRects[ePrevCharRect] =
373 0 : mTextRectArray.GetRect(mSelection.mAnchor - 1);
374 : }
375 : } else {
376 0 : RectArray rects;
377 0 : uint32_t startOffset = mSelection.mAnchor ? mSelection.mAnchor - 1 : 0;
378 0 : uint32_t length = mSelection.mAnchor ? 2 : 1;
379 0 : if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
380 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
381 : ("0x%p CacheTextRects(), FAILED, "
382 : "couldn't retrieve text rect array around the selection anchor (%u)",
383 : this, mSelection.mAnchor));
384 0 : MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
385 0 : MOZ_ASSERT(mSelection.mAnchorCharRects[eNextCharRect].IsEmpty());
386 : } else {
387 0 : if (rects.Length() > 1) {
388 0 : mSelection.mAnchorCharRects[ePrevCharRect] = rects[0];
389 0 : mSelection.mAnchorCharRects[eNextCharRect] = rects[1];
390 0 : } else if (rects.Length()) {
391 0 : mSelection.mAnchorCharRects[eNextCharRect] = rects[0];
392 0 : MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
393 : }
394 : }
395 : }
396 :
397 0 : if (mSelection.Collapsed()) {
398 0 : mSelection.mFocusCharRects[0] = mSelection.mAnchorCharRects[0];
399 0 : mSelection.mFocusCharRects[1] = mSelection.mAnchorCharRects[1];
400 0 : } else if (mTextRectArray.InRange(mSelection.mFocus) &&
401 0 : (!mSelection.mFocus ||
402 0 : mTextRectArray.InRange(mSelection.mFocus - 1))) {
403 : mSelection.mFocusCharRects[eNextCharRect] =
404 0 : mTextRectArray.GetRect(mSelection.mFocus);
405 0 : if (mSelection.mFocus) {
406 : mSelection.mFocusCharRects[ePrevCharRect] =
407 0 : mTextRectArray.GetRect(mSelection.mFocus - 1);
408 : }
409 : } else {
410 0 : RectArray rects;
411 0 : uint32_t startOffset = mSelection.mFocus ? mSelection.mFocus - 1 : 0;
412 0 : uint32_t length = mSelection.mFocus ? 2 : 1;
413 0 : if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
414 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
415 : ("0x%p CacheTextRects(), FAILED, "
416 : "couldn't retrieve text rect array around the selection focus (%u)",
417 : this, mSelection.mFocus));
418 0 : MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
419 0 : MOZ_ASSERT(mSelection.mFocusCharRects[eNextCharRect].IsEmpty());
420 : } else {
421 0 : if (rects.Length() > 1) {
422 0 : mSelection.mFocusCharRects[ePrevCharRect] = rects[0];
423 0 : mSelection.mFocusCharRects[eNextCharRect] = rects[1];
424 0 : } else if (rects.Length()) {
425 0 : mSelection.mFocusCharRects[eNextCharRect] = rects[0];
426 0 : MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
427 : }
428 : }
429 : }
430 :
431 0 : if (!mSelection.Collapsed()) {
432 0 : nsEventStatus status = nsEventStatus_eIgnore;
433 0 : WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
434 0 : textRect.InitForQueryTextRect(mSelection.StartOffset(),
435 0 : mSelection.Length());
436 0 : aWidget->DispatchEvent(&textRect, status);
437 0 : if (NS_WARN_IF(!textRect.mSucceeded)) {
438 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
439 : ("0x%p CacheTextRects(), FAILED, "
440 : "couldn't retrieve text rect of whole selected text", this));
441 : } else {
442 0 : mSelection.mRect = textRect.mReply.mRect;
443 : }
444 : }
445 :
446 0 : if (!mSelection.mFocus) {
447 0 : mFirstCharRect = mSelection.mFocusCharRects[eNextCharRect];
448 0 : } else if (mSelection.mFocus == 1) {
449 0 : mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
450 0 : } else if (!mSelection.mAnchor) {
451 0 : mFirstCharRect = mSelection.mAnchorCharRects[eNextCharRect];
452 0 : } else if (mSelection.mAnchor == 1) {
453 0 : mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
454 0 : } else if (mTextRectArray.InRange(0)) {
455 0 : mFirstCharRect = mTextRectArray.GetRect(0);
456 : } else {
457 0 : LayoutDeviceIntRect charRect;
458 0 : if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
459 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
460 : ("0x%p CacheTextRects(), FAILED, "
461 : "couldn't retrieve first char rect", this));
462 : } else {
463 0 : mFirstCharRect = charRect;
464 : }
465 : }
466 :
467 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
468 : ("0x%p CacheTextRects(), Succeeded, "
469 : "mText.Length()=%x, mTextRectArray={ mStart=%u, mRects.Length()=%"
470 : PRIuSIZE " }, mSelection={ mAnchor=%u, mAnchorCharRects[eNextCharRect]=%s, "
471 : "mAnchorCharRects[ePrevCharRect]=%s, mFocus=%u, "
472 : "mFocusCharRects[eNextCharRect]=%s, mFocusCharRects[ePrevCharRect]=%s, "
473 : "mRect=%s }, mFirstCharRect=%s",
474 : this, mText.Length(), mTextRectArray.mStart,
475 : mTextRectArray.mRects.Length(), mSelection.mAnchor,
476 : GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
477 : GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
478 : mSelection.mFocus,
479 : GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
480 : GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
481 : GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get()));
482 0 : return true;
483 : }
484 :
485 : void
486 0 : ContentCacheInChild::SetSelection(nsIWidget* aWidget,
487 : uint32_t aStartOffset,
488 : uint32_t aLength,
489 : bool aReversed,
490 : const WritingMode& aWritingMode)
491 : {
492 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
493 : ("0x%p SetSelection(aStartOffset=%u, "
494 : "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
495 : this, aStartOffset, aLength, GetBoolName(aReversed),
496 : GetWritingModeName(aWritingMode).get(), mText.Length()));
497 :
498 0 : if (!aReversed) {
499 0 : mSelection.mAnchor = aStartOffset;
500 0 : mSelection.mFocus = aStartOffset + aLength;
501 : } else {
502 0 : mSelection.mAnchor = aStartOffset + aLength;
503 0 : mSelection.mFocus = aStartOffset;
504 : }
505 0 : mSelection.mWritingMode = aWritingMode;
506 :
507 0 : if (NS_WARN_IF(!CacheCaret(aWidget))) {
508 0 : return;
509 : }
510 0 : Unused << NS_WARN_IF(!CacheTextRects(aWidget));
511 : }
512 :
513 : /*****************************************************************************
514 : * mozilla::ContentCacheInParent
515 : *****************************************************************************/
516 :
517 1 : ContentCacheInParent::ContentCacheInParent(TabParent& aTabParent)
518 : : ContentCache()
519 : , mTabParent(aTabParent)
520 : , mCommitStringByRequest(nullptr)
521 : , mPendingEventsNeedingAck(0)
522 : , mCompositionStartInChild(UINT32_MAX)
523 : , mPendingCompositionCount(0)
524 : , mWidgetHasComposition(false)
525 1 : , mIsPendingLastCommitEvent(false)
526 : {
527 1 : }
528 :
529 : void
530 1 : ContentCacheInParent::AssignContent(const ContentCache& aOther,
531 : nsIWidget* aWidget,
532 : const IMENotification* aNotification)
533 : {
534 1 : mText = aOther.mText;
535 1 : mSelection = aOther.mSelection;
536 1 : mFirstCharRect = aOther.mFirstCharRect;
537 1 : mCaret = aOther.mCaret;
538 1 : mTextRectArray = aOther.mTextRectArray;
539 1 : mEditorRect = aOther.mEditorRect;
540 :
541 : // Only when there is one composition, the TextComposition instance in this
542 : // process is managing the composition in the remote process. Therefore,
543 : // we shouldn't update composition start offset of TextComposition with
544 : // old composition which is still being handled by the child process.
545 1 : if (mWidgetHasComposition && mPendingCompositionCount == 1) {
546 0 : IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget, mCompositionStart);
547 : }
548 :
549 : // When the widget has composition, we should set mCompositionStart to
550 : // *current* composition start offset. Note that, in strictly speaking,
551 : // widget should not use WidgetQueryContentEvent if there are some pending
552 : // compositions (i.e., when mPendingCompositionCount is 2 or more).
553 1 : mCompositionStartInChild = aOther.mCompositionStart;
554 1 : if (mWidgetHasComposition) {
555 0 : if (aOther.mCompositionStart != UINT32_MAX) {
556 0 : if (mCompositionStart != aOther.mCompositionStart) {
557 0 : mCompositionStart = aOther.mCompositionStart;
558 0 : mPendingCommitLength = 0;
559 : }
560 0 : } else if (mCompositionStart != mSelection.StartOffset()) {
561 0 : mCompositionStart = mSelection.StartOffset();
562 0 : mPendingCommitLength = 0;
563 0 : NS_WARNING_ASSERTION(mCompositionStart != UINT32_MAX,
564 : "mCompositionStart shouldn't be invalid offset when "
565 : "the widget has composition");
566 : }
567 1 : } else if (mCompositionStart != UINT32_MAX) {
568 0 : mCompositionStart = UINT32_MAX;
569 0 : mPendingCommitLength = 0;
570 : }
571 :
572 1 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
573 : ("0x%p AssignContent(aNotification=%s), "
574 : "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
575 : "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
576 : "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
577 : "mFocusCharRects[ePrevCharRect]=%s, mRect=%s }, "
578 : "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
579 : "mStart=%u, mRects.Length()=%" PRIuSIZE " }, mWidgetHasComposition=%s, "
580 : "mPendingCompositionCount=%u, mCompositionStart=%u, "
581 : "mPendingCommitLength=%u, mEditorRect=%s",
582 : this, GetNotificationName(aNotification),
583 : mText.Length(), mSelection.mAnchor, mSelection.mFocus,
584 : GetWritingModeName(mSelection.mWritingMode).get(),
585 : GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
586 : GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
587 : GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
588 : GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
589 : GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
590 : mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
591 : mTextRectArray.mRects.Length(), GetBoolName(mWidgetHasComposition),
592 : mPendingCompositionCount, mCompositionStart, mPendingCommitLength,
593 : GetRectText(mEditorRect).get()));
594 1 : }
595 :
596 : bool
597 0 : ContentCacheInParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
598 : nsIWidget* aWidget) const
599 : {
600 0 : MOZ_ASSERT(aWidget);
601 :
602 0 : aEvent.mSucceeded = false;
603 0 : aEvent.mReply.mFocusedWidget = aWidget;
604 :
605 : // ContentCache doesn't store offset of its start with XP linebreaks.
606 : // So, we don't support to query contents relative to composition start
607 : // offset with XP linebreaks.
608 0 : if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
609 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
610 : ("0x%p HandleQueryContentEvent(), FAILED due to query with XP linebreaks",
611 : this));
612 0 : return false;
613 : }
614 :
615 0 : if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
616 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
617 : ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset",
618 : this));
619 0 : return false;
620 : }
621 :
622 0 : if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
623 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
624 : ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
625 : this));
626 0 : return false;
627 : }
628 :
629 0 : bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
630 0 : if (isRelativeToInsertionPoint) {
631 0 : if (aWidget->PluginHasFocus()) {
632 0 : if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(0))) {
633 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
634 : ("0x%p HandleQueryContentEvent(), FAILED due to "
635 : "aEvent.mInput.MakeOffsetAbsolute(0) failure, aEvent={ mMessage=%s, "
636 : "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
637 : this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
638 : aEvent.mInput.mLength));
639 0 : return false;
640 : }
641 0 : } else if (mWidgetHasComposition) {
642 0 : if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
643 : mCompositionStart +
644 : mPendingCommitLength))) {
645 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
646 : ("0x%p HandleQueryContentEvent(), FAILED due to "
647 : "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
648 : "mPendingCommitLength) failure, "
649 : "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
650 : "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
651 : ", mLength=%" PRIu32 " } }",
652 : this, mCompositionStart, mPendingCommitLength,
653 : ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
654 : aEvent.mInput.mLength));
655 0 : return false;
656 : }
657 0 : } else if (NS_WARN_IF(!mSelection.IsValid())) {
658 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
659 : ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is invalid",
660 : this));
661 0 : return false;
662 0 : } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
663 : mSelection.StartOffset() +
664 : mPendingCommitLength))) {
665 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
666 : ("0x%p HandleQueryContentEvent(), FAILED due to "
667 : "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
668 : "mPendingCommitLength) failure, "
669 : "mSelection={ StartOffset()=%d, Length()=%d }, "
670 : "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
671 : "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
672 : this, mSelection.StartOffset(), mSelection.Length(),
673 : mPendingCommitLength, ToChar(aEvent.mMessage),
674 : aEvent.mInput.mOffset, aEvent.mInput.mLength));
675 0 : return false;
676 : }
677 : }
678 :
679 0 : switch (aEvent.mMessage) {
680 : case eQuerySelectedText:
681 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
682 : ("0x%p HandleQueryContentEvent("
683 : "aEvent={ mMessage=eQuerySelectedText }, aWidget=0x%p)",
684 : this, aWidget));
685 0 : if (aWidget->PluginHasFocus()) {
686 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
687 : ("0x%p HandleQueryContentEvent(), "
688 : "return emtpy selection becasue plugin has focus",
689 : this));
690 0 : aEvent.mSucceeded = true;
691 0 : aEvent.mReply.mOffset = 0;
692 0 : aEvent.mReply.mReversed = false;
693 0 : aEvent.mReply.mHasSelection = false;
694 0 : return true;
695 : }
696 0 : if (NS_WARN_IF(!IsSelectionValid())) {
697 : // If content cache hasn't been initialized properly, make the query
698 : // failed.
699 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
700 : ("0x%p HandleQueryContentEvent(), "
701 : "FAILED because mSelection is not valid", this));
702 0 : return true;
703 : }
704 0 : aEvent.mReply.mOffset = mSelection.StartOffset();
705 0 : if (mSelection.Collapsed()) {
706 0 : aEvent.mReply.mString.Truncate(0);
707 : } else {
708 0 : if (NS_WARN_IF(mSelection.EndOffset() > mText.Length())) {
709 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
710 : ("0x%p HandleQueryContentEvent(), "
711 : "FAILED because mSelection.EndOffset()=%u is larger than "
712 : "mText.Length()=%u",
713 : this, mSelection.EndOffset(), mText.Length()));
714 0 : return false;
715 : }
716 : aEvent.mReply.mString =
717 0 : Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
718 : }
719 0 : aEvent.mReply.mReversed = mSelection.Reversed();
720 0 : aEvent.mReply.mHasSelection = true;
721 0 : aEvent.mReply.mWritingMode = mSelection.mWritingMode;
722 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
723 : ("0x%p HandleQueryContentEvent(), "
724 : "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
725 : "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
726 : this, aEvent.mReply.mOffset,
727 : GetEscapedUTF8String(aEvent.mReply.mString).get(),
728 : GetBoolName(aEvent.mReply.mReversed),
729 : GetBoolName(aEvent.mReply.mHasSelection),
730 : GetWritingModeName(aEvent.mReply.mWritingMode).get()));
731 0 : break;
732 : case eQueryTextContent: {
733 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
734 : ("0x%p HandleQueryContentEvent("
735 : "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
736 : ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
737 : this, aEvent.mInput.mOffset,
738 : aEvent.mInput.mLength, aWidget, mText.Length()));
739 0 : uint32_t inputOffset = aEvent.mInput.mOffset;
740 : uint32_t inputEndOffset =
741 0 : std::min(aEvent.mInput.EndOffset(), mText.Length());
742 0 : if (NS_WARN_IF(inputEndOffset < inputOffset)) {
743 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
744 : ("0x%p HandleQueryContentEvent(), "
745 : "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
746 : this, inputOffset, inputEndOffset));
747 0 : return false;
748 : }
749 0 : aEvent.mReply.mOffset = inputOffset;
750 : aEvent.mReply.mString =
751 0 : Substring(mText, inputOffset, inputEndOffset - inputOffset);
752 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
753 : ("0x%p HandleQueryContentEvent(), "
754 : "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
755 : this, aEvent.mReply.mOffset, aEvent.mReply.mString.Length()));
756 0 : break;
757 : }
758 : case eQueryTextRect:
759 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
760 : ("0x%p HandleQueryContentEvent("
761 : "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
762 : ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
763 : this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
764 : mText.Length()));
765 0 : if (NS_WARN_IF(!IsSelectionValid())) {
766 : // If content cache hasn't been initialized properly, make the query
767 : // failed.
768 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
769 : ("0x%p HandleQueryContentEvent(), "
770 : "FAILED because mSelection is not valid", this));
771 0 : return true;
772 : }
773 : // Note that if the query is relative to insertion point, the query was
774 : // probably requested by native IME. In such case, we should return
775 : // non-empty rect since returning failure causes IME showing its window
776 : // at odd position.
777 0 : if (aEvent.mInput.mLength) {
778 0 : if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
779 : aEvent.mInput.mLength,
780 : isRelativeToInsertionPoint,
781 : aEvent.mReply.mRect))) {
782 : // XXX We don't have cache for this request.
783 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
784 : ("0x%p HandleQueryContentEvent(), "
785 : "FAILED to get union rect", this));
786 0 : return false;
787 : }
788 : } else {
789 : // If the length is 0, we should return caret rect instead.
790 0 : if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
791 : isRelativeToInsertionPoint,
792 : aEvent.mReply.mRect))) {
793 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
794 : ("0x%p HandleQueryContentEvent(), "
795 : "FAILED to get caret rect", this));
796 0 : return false;
797 : }
798 : }
799 0 : if (aEvent.mInput.mOffset < mText.Length()) {
800 : aEvent.mReply.mString =
801 0 : Substring(mText, aEvent.mInput.mOffset,
802 0 : mText.Length() >= aEvent.mInput.EndOffset() ?
803 0 : aEvent.mInput.mLength : UINT32_MAX);
804 : } else {
805 0 : aEvent.mReply.mString.Truncate(0);
806 : }
807 0 : aEvent.mReply.mOffset = aEvent.mInput.mOffset;
808 : // XXX This may be wrong if storing range isn't in the selection range.
809 0 : aEvent.mReply.mWritingMode = mSelection.mWritingMode;
810 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
811 : ("0x%p HandleQueryContentEvent(), "
812 : "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
813 : "mWritingMode=%s, mRect=%s } }",
814 : this, aEvent.mReply.mOffset,
815 : GetEscapedUTF8String(aEvent.mReply.mString).get(),
816 : GetWritingModeName(aEvent.mReply.mWritingMode).get(),
817 : GetRectText(aEvent.mReply.mRect).get()));
818 0 : break;
819 : case eQueryCaretRect:
820 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
821 : ("0x%p HandleQueryContentEvent("
822 : "aEvent={ mMessage=eQueryCaretRect, mInput={ mOffset=%" PRId64 " } }, "
823 : "aWidget=0x%p), mText.Length()=%u",
824 : this, aEvent.mInput.mOffset, aWidget, mText.Length()));
825 0 : if (NS_WARN_IF(!IsSelectionValid())) {
826 : // If content cache hasn't been initialized properly, make the query
827 : // failed.
828 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
829 : ("0x%p HandleQueryContentEvent(), "
830 : "FAILED because mSelection is not valid", this));
831 0 : return true;
832 : }
833 : // Note that if the query is relative to insertion point, the query was
834 : // probably requested by native IME. In such case, we should return
835 : // non-empty rect since returning failure causes IME showing its window
836 : // at odd position.
837 0 : if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
838 : isRelativeToInsertionPoint,
839 : aEvent.mReply.mRect))) {
840 0 : MOZ_LOG(sContentCacheLog, LogLevel::Error,
841 : ("0x%p HandleQueryContentEvent(), "
842 : "FAILED to get caret rect", this));
843 0 : return false;
844 : }
845 0 : aEvent.mReply.mOffset = aEvent.mInput.mOffset;
846 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
847 : ("0x%p HandleQueryContentEvent(), "
848 : "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
849 : this, aEvent.mReply.mOffset, GetRectText(aEvent.mReply.mRect).get()));
850 0 : break;
851 : case eQueryEditorRect:
852 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
853 : ("0x%p HandleQueryContentEvent("
854 : "aEvent={ mMessage=eQueryEditorRect }, aWidget=0x%p)",
855 : this, aWidget));
856 0 : aEvent.mReply.mRect = mEditorRect;
857 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
858 : ("0x%p HandleQueryContentEvent(), "
859 : "Succeeded, aEvent={ mReply={ mRect=%s } }",
860 : this, GetRectText(aEvent.mReply.mRect).get()));
861 0 : break;
862 : default:
863 0 : break;
864 : }
865 0 : aEvent.mSucceeded = true;
866 0 : return true;
867 : }
868 :
869 : bool
870 0 : ContentCacheInParent::GetTextRect(uint32_t aOffset,
871 : bool aRoundToExistingOffset,
872 : LayoutDeviceIntRect& aTextRect) const
873 : {
874 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
875 : ("0x%p GetTextRect(aOffset=%u, "
876 : "aRoundToExistingOffset=%s), "
877 : "mTextRectArray={ mStart=%u, mRects.Length()=%" PRIuSIZE " }, "
878 : "mSelection={ mAnchor=%u, mFocus=%u }",
879 : this, aOffset, GetBoolName(aRoundToExistingOffset),
880 : mTextRectArray.mStart, mTextRectArray.mRects.Length(),
881 : mSelection.mAnchor, mSelection.mFocus));
882 :
883 0 : if (!aOffset) {
884 0 : NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
885 0 : aTextRect = mFirstCharRect;
886 0 : return !aTextRect.IsEmpty();
887 : }
888 0 : if (aOffset == mSelection.mAnchor) {
889 0 : NS_WARNING_ASSERTION(!mSelection.mAnchorCharRects[eNextCharRect].IsEmpty(),
890 : "empty rect");
891 0 : aTextRect = mSelection.mAnchorCharRects[eNextCharRect];
892 0 : return !aTextRect.IsEmpty();
893 : }
894 0 : if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
895 0 : NS_WARNING_ASSERTION(!mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty(),
896 : "empty rect");
897 0 : aTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
898 0 : return !aTextRect.IsEmpty();
899 : }
900 0 : if (aOffset == mSelection.mFocus) {
901 0 : NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[eNextCharRect].IsEmpty(),
902 : "empty rect");
903 0 : aTextRect = mSelection.mFocusCharRects[eNextCharRect];
904 0 : return !aTextRect.IsEmpty();
905 : }
906 0 : if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
907 0 : NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[ePrevCharRect].IsEmpty(),
908 : "empty rect");
909 0 : aTextRect = mSelection.mFocusCharRects[ePrevCharRect];
910 0 : return !aTextRect.IsEmpty();
911 : }
912 :
913 0 : uint32_t offset = aOffset;
914 0 : if (!mTextRectArray.InRange(aOffset)) {
915 0 : if (!aRoundToExistingOffset) {
916 0 : aTextRect.SetEmpty();
917 0 : return false;
918 : }
919 0 : if (!mTextRectArray.IsValid()) {
920 : // If there are no rects in mTextRectArray, we should refer the start of
921 : // the selection because IME must query a char rect around it if there is
922 : // no composition.
923 0 : aTextRect = mSelection.StartCharRect();
924 0 : return !aTextRect.IsEmpty();
925 : }
926 0 : if (offset < mTextRectArray.StartOffset()) {
927 0 : offset = mTextRectArray.StartOffset();
928 : } else {
929 0 : offset = mTextRectArray.EndOffset() - 1;
930 : }
931 : }
932 0 : aTextRect = mTextRectArray.GetRect(offset);
933 0 : return !aTextRect.IsEmpty();
934 : }
935 :
936 : bool
937 0 : ContentCacheInParent::GetUnionTextRects(
938 : uint32_t aOffset,
939 : uint32_t aLength,
940 : bool aRoundToExistingOffset,
941 : LayoutDeviceIntRect& aUnionTextRect) const
942 : {
943 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
944 : ("0x%p GetUnionTextRects(aOffset=%u, "
945 : "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray={ "
946 : "mStart=%u, mRects.Length()=%" PRIuSIZE " }, "
947 : "mSelection={ mAnchor=%u, mFocus=%u }",
948 : this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
949 : mTextRectArray.mStart, mTextRectArray.mRects.Length(),
950 : mSelection.mAnchor, mSelection.mFocus));
951 :
952 : CheckedInt<uint32_t> endOffset =
953 0 : CheckedInt<uint32_t>(aOffset) + aLength;
954 0 : if (!endOffset.isValid()) {
955 0 : return false;
956 : }
957 :
958 0 : if (!mSelection.Collapsed() &&
959 0 : aOffset == mSelection.StartOffset() && aLength == mSelection.Length()) {
960 0 : NS_WARNING_ASSERTION(!mSelection.mRect.IsEmpty(), "empty rect");
961 0 : aUnionTextRect = mSelection.mRect;
962 0 : return !aUnionTextRect.IsEmpty();
963 : }
964 :
965 0 : if (aLength == 1) {
966 0 : if (!aOffset) {
967 0 : NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
968 0 : aUnionTextRect = mFirstCharRect;
969 0 : return !aUnionTextRect.IsEmpty();
970 : }
971 0 : if (aOffset == mSelection.mAnchor) {
972 0 : NS_WARNING_ASSERTION(
973 : !mSelection.mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
974 0 : aUnionTextRect = mSelection.mAnchorCharRects[eNextCharRect];
975 0 : return !aUnionTextRect.IsEmpty();
976 : }
977 0 : if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
978 0 : NS_WARNING_ASSERTION(
979 : !mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
980 0 : aUnionTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
981 0 : return !aUnionTextRect.IsEmpty();
982 : }
983 0 : if (aOffset == mSelection.mFocus) {
984 0 : NS_WARNING_ASSERTION(
985 : !mSelection.mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
986 0 : aUnionTextRect = mSelection.mFocusCharRects[eNextCharRect];
987 0 : return !aUnionTextRect.IsEmpty();
988 : }
989 0 : if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
990 0 : NS_WARNING_ASSERTION(
991 : !mSelection.mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
992 0 : aUnionTextRect = mSelection.mFocusCharRects[ePrevCharRect];
993 0 : return !aUnionTextRect.IsEmpty();
994 : }
995 : }
996 :
997 : // Even if some text rects are not cached of the queried range,
998 : // we should return union rect when the first character's rect is cached
999 : // since the first character rect is important and the others are not so
1000 : // in most cases.
1001 :
1002 0 : if (!aOffset && aOffset != mSelection.mAnchor &&
1003 0 : aOffset != mSelection.mFocus && !mTextRectArray.InRange(aOffset)) {
1004 : // The first character rect isn't cached.
1005 0 : return false;
1006 : }
1007 :
1008 0 : if ((aRoundToExistingOffset && mTextRectArray.HasRects()) ||
1009 0 : mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
1010 : aUnionTextRect =
1011 : mTextRectArray.GetUnionRectAsFarAsPossible(aOffset, aLength,
1012 0 : aRoundToExistingOffset);
1013 : } else {
1014 0 : aUnionTextRect.SetEmpty();
1015 : }
1016 :
1017 0 : if (!aOffset) {
1018 0 : aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1019 : }
1020 0 : if (aOffset <= mSelection.mAnchor && mSelection.mAnchor < endOffset.value()) {
1021 : aUnionTextRect =
1022 0 : aUnionTextRect.Union(mSelection.mAnchorCharRects[eNextCharRect]);
1023 : }
1024 0 : if (mSelection.mAnchor && aOffset <= mSelection.mAnchor - 1 &&
1025 0 : mSelection.mAnchor - 1 < endOffset.value()) {
1026 : aUnionTextRect =
1027 0 : aUnionTextRect.Union(mSelection.mAnchorCharRects[ePrevCharRect]);
1028 : }
1029 0 : if (aOffset <= mSelection.mFocus && mSelection.mFocus < endOffset.value()) {
1030 : aUnionTextRect =
1031 0 : aUnionTextRect.Union(mSelection.mFocusCharRects[eNextCharRect]);
1032 : }
1033 0 : if (mSelection.mFocus && aOffset <= mSelection.mFocus - 1 &&
1034 0 : mSelection.mFocus - 1 < endOffset.value()) {
1035 : aUnionTextRect =
1036 0 : aUnionTextRect.Union(mSelection.mFocusCharRects[ePrevCharRect]);
1037 : }
1038 :
1039 0 : return !aUnionTextRect.IsEmpty();
1040 : }
1041 :
1042 : bool
1043 0 : ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1044 : bool aRoundToExistingOffset,
1045 : LayoutDeviceIntRect& aCaretRect) const
1046 : {
1047 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
1048 : ("0x%p GetCaretRect(aOffset=%u, "
1049 : "aRoundToExistingOffset=%s), "
1050 : "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
1051 : "mStart=%u, mRects.Length()=%" PRIuSIZE " }, mSelection={ mAnchor=%u, mFocus=%u, "
1052 : "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
1053 : "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
1054 : "mFocusCharRects[ePrevCharRect]=%s }, mFirstCharRect=%s",
1055 : this, aOffset, GetBoolName(aRoundToExistingOffset),
1056 : mCaret.mOffset, GetRectText(mCaret.mRect).get(),
1057 : GetBoolName(mCaret.IsValid()), mTextRectArray.mStart,
1058 : mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus,
1059 : GetWritingModeName(mSelection.mWritingMode).get(),
1060 : GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
1061 : GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
1062 : GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
1063 : GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
1064 : GetRectText(mFirstCharRect).get()));
1065 :
1066 0 : if (mCaret.IsValid() && mCaret.mOffset == aOffset) {
1067 0 : aCaretRect = mCaret.mRect;
1068 0 : return true;
1069 : }
1070 :
1071 : // Guess caret rect from the text rect if it's stored.
1072 0 : if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1073 : // There might be previous character rect in the cache. If so, we can
1074 : // guess the caret rect with it.
1075 0 : if (!aOffset ||
1076 0 : !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1077 0 : aCaretRect.SetEmpty();
1078 0 : return false;
1079 : }
1080 :
1081 0 : if (mSelection.mWritingMode.IsVertical()) {
1082 0 : aCaretRect.y = aCaretRect.YMost();
1083 : } else {
1084 : // XXX bidi-unaware.
1085 0 : aCaretRect.x = aCaretRect.XMost();
1086 : }
1087 : }
1088 :
1089 : // XXX This is not bidi aware because we don't cache each character's
1090 : // direction. However, this is usually used by IME, so, assuming the
1091 : // character is in LRT context must not cause any problem.
1092 0 : if (mSelection.mWritingMode.IsVertical()) {
1093 0 : aCaretRect.height = mCaret.IsValid() ? mCaret.mRect.height : 1;
1094 : } else {
1095 0 : aCaretRect.width = mCaret.IsValid() ? mCaret.mRect.width : 1;
1096 : }
1097 0 : return true;
1098 : }
1099 :
1100 : bool
1101 0 : ContentCacheInParent::OnCompositionEvent(const WidgetCompositionEvent& aEvent)
1102 : {
1103 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
1104 : ("0x%p OnCompositionEvent(aEvent={ "
1105 : "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%" PRIuSIZE " }), "
1106 : "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1107 : "mPendingCompositionCount=%u, mCommitStringByRequest=0x%p",
1108 : this, ToChar(aEvent.mMessage),
1109 : GetEscapedUTF8String(aEvent.mData).get(), aEvent.mData.Length(),
1110 : aEvent.mRanges ? aEvent.mRanges->Length() : 0, mPendingEventsNeedingAck,
1111 : GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1112 : mCommitStringByRequest));
1113 :
1114 : // We must be able to simulate the selection because
1115 : // we might not receive selection updates in time
1116 0 : if (!mWidgetHasComposition) {
1117 0 : if (aEvent.mWidget && aEvent.mWidget->PluginHasFocus()) {
1118 : // If focus is on plugin, we cannot get selection range
1119 0 : mCompositionStart = 0;
1120 0 : } else if (mCompositionStartInChild != UINT32_MAX) {
1121 : // If there is pending composition in the remote process, let's use
1122 : // its start offset temporarily because this stores a lot of information
1123 : // around it and the user must look around there, so, showing some UI
1124 : // around it must make sense.
1125 0 : mCompositionStart = mCompositionStartInChild;
1126 : } else {
1127 0 : mCompositionStart = mSelection.StartOffset();
1128 : }
1129 0 : MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1130 0 : MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1131 0 : mPendingCompositionCount++;
1132 : }
1133 :
1134 0 : mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1135 :
1136 0 : if (!mWidgetHasComposition) {
1137 0 : mCompositionStart = UINT32_MAX;
1138 0 : if (mPendingCompositionCount == 1) {
1139 0 : mPendingCommitLength = aEvent.mData.Length();
1140 : }
1141 0 : mIsPendingLastCommitEvent = true;
1142 0 : } else if (aEvent.mMessage != eCompositionStart) {
1143 0 : mCompositionString = aEvent.mData;
1144 : }
1145 :
1146 : // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1147 : // widget usually sends a eCompositionChange and/or eCompositionCommit event
1148 : // to finalize or clear the composition, respectively. In this time,
1149 : // we need to intercept all composition events here and pass the commit
1150 : // string for returning to the remote process as a result of
1151 : // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1152 : // be dispatched with the committed string in the remote process internally.
1153 0 : if (mCommitStringByRequest) {
1154 0 : MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1155 : aEvent.mMessage == eCompositionCommit);
1156 0 : *mCommitStringByRequest = aEvent.mData;
1157 : // We need to wait eCompositionCommitRequestHandled from the remote process
1158 : // in this case. Therefore, mPendingEventsNeedingAck needs to be
1159 : // incremented here.
1160 0 : if (!mWidgetHasComposition) {
1161 0 : mPendingEventsNeedingAck++;
1162 : }
1163 : // Cancel mIsPendingLastCommitEvent because we won't send the commit event
1164 : // to the remote process.
1165 0 : mIsPendingLastCommitEvent = false;
1166 0 : return false;
1167 : }
1168 :
1169 0 : mPendingEventsNeedingAck++;
1170 0 : return true;
1171 : }
1172 :
1173 : void
1174 0 : ContentCacheInParent::OnSelectionEvent(
1175 : const WidgetSelectionEvent& aSelectionEvent)
1176 : {
1177 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
1178 : ("0x%p OnSelectionEvent(aEvent={ "
1179 : "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1180 : "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1181 : "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1182 : "mPendingCompositionCount=%u",
1183 : this, ToChar(aSelectionEvent.mMessage),
1184 : aSelectionEvent.mOffset, aSelectionEvent.mLength,
1185 : GetBoolName(aSelectionEvent.mReversed),
1186 : GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1187 : GetBoolName(aSelectionEvent.mUseNativeLineBreak), mPendingEventsNeedingAck,
1188 : GetBoolName(mWidgetHasComposition), mPendingCompositionCount));
1189 :
1190 0 : mPendingEventsNeedingAck++;
1191 0 : }
1192 :
1193 : void
1194 0 : ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1195 : EventMessage aMessage)
1196 : {
1197 : // This is called when the child process receives WidgetCompositionEvent or
1198 : // WidgetSelectionEvent.
1199 :
1200 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
1201 : ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1202 : "aMessage=%s), mPendingEventsNeedingAck=%u, mPendingCompositionCount=%" PRIu8,
1203 : this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck, mPendingCompositionCount));
1204 :
1205 0 : if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage) ||
1206 : aMessage == eCompositionCommitRequestHandled) {
1207 0 : MOZ_RELEASE_ASSERT(mPendingCompositionCount > 0);
1208 0 : mPendingCompositionCount--;
1209 : // Forget composition string only when the latest composition string is
1210 : // handled in the remote process because if there is 2 or more pending
1211 : // composition, this value shouldn't be referred.
1212 0 : if (!mPendingCompositionCount) {
1213 0 : mCompositionString.Truncate();
1214 0 : mIsPendingLastCommitEvent = false;
1215 : }
1216 : // Forget pending commit string length if it's handled in the remote
1217 : // process. Note that this doesn't care too old composition's commit
1218 : // string because in such case, we cannot return proper information
1219 : // to IME synchornously.
1220 0 : mPendingCommitLength = 0;
1221 : }
1222 :
1223 0 : MOZ_RELEASE_ASSERT(mPendingEventsNeedingAck > 0);
1224 0 : if (--mPendingEventsNeedingAck) {
1225 0 : return;
1226 : }
1227 :
1228 0 : FlushPendingNotifications(aWidget);
1229 : }
1230 :
1231 : bool
1232 0 : ContentCacheInParent::RequestIMEToCommitComposition(nsIWidget* aWidget,
1233 : bool aCancel,
1234 : nsAString& aCommittedString)
1235 : {
1236 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
1237 : ("0x%p RequestToCommitComposition(aWidget=%p, "
1238 : "aCancel=%s), mPendingCompositionCount=%u, "
1239 : "IMEStateManager::DoesTabParentHaveIMEFocus(&mTabParent)=%s, "
1240 : "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1241 : this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1242 : GetBoolName(IMEStateManager::DoesTabParentHaveIMEFocus(&mTabParent)),
1243 : GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1244 :
1245 0 : MOZ_ASSERT(!mCommitStringByRequest);
1246 :
1247 : // If there are 2 or more pending compositions, we already sent
1248 : // eCompositionCommit(AsIs) to the remote process. So, this request is
1249 : // too late for IME. The remote process should wait following
1250 : // composition events for cleaning up TextComposition and handle the
1251 : // request as it's handled asynchronously.
1252 0 : if (mPendingCompositionCount > 1) {
1253 0 : return false;
1254 : }
1255 :
1256 : // If TabParent which has IME focus was already changed to different one, the
1257 : // request shouldn't be sent to IME because it's too late.
1258 0 : if (!IMEStateManager::DoesTabParentHaveIMEFocus(&mTabParent)) {
1259 : // Use the latest composition string which may not be handled in the
1260 : // remote process for avoiding data loss.
1261 0 : aCommittedString = mCompositionString;
1262 0 : return true;
1263 : }
1264 :
1265 : // Even if the remote process has IME focus and there is no pending
1266 : // composition, we may have already sent eCompositionCommit(AsIs) event
1267 : // to it. If so, the remote process will receive composition events
1268 : // which causes cleaning up TextComposition. So, this shouldn't do nothing
1269 : // and TextComposition should handle the request as it's handled
1270 : // asynchronously.
1271 0 : if (mIsPendingLastCommitEvent) {
1272 0 : return false;
1273 : }
1274 :
1275 : RefPtr<TextComposition> composition =
1276 0 : IMEStateManager::GetTextCompositionFor(aWidget);
1277 0 : if (NS_WARN_IF(!composition)) {
1278 0 : MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1279 : (" 0x%p RequestToCommitComposition(), "
1280 : "does nothing due to no composition", this));
1281 0 : return false;
1282 : }
1283 :
1284 0 : mCommitStringByRequest = &aCommittedString;
1285 :
1286 0 : aWidget->NotifyIME(IMENotification(aCancel ? REQUEST_TO_CANCEL_COMPOSITION :
1287 0 : REQUEST_TO_COMMIT_COMPOSITION));
1288 :
1289 0 : mCommitStringByRequest = nullptr;
1290 :
1291 0 : MOZ_LOG(sContentCacheLog, LogLevel::Info,
1292 : (" 0x%p RequestToCommitComposition(), "
1293 : "mWidgetHasComposition=%s, the composition %s committed synchronously",
1294 : this, GetBoolName(mWidgetHasComposition),
1295 : composition->Destroyed() ? "WAS" : "has NOT been"));
1296 :
1297 0 : if (!composition->Destroyed()) {
1298 : // When the composition isn't committed synchronously, the remote process's
1299 : // TextComposition instance will synthesize commit events and wait to
1300 : // receive delayed composition events. When TextComposition instances both
1301 : // in this process and the remote process will be destroyed when delayed
1302 : // composition events received. TextComposition instance in the parent
1303 : // process will dispatch following composition events and be destroyed
1304 : // normally. On the other hand, TextComposition instance in the remote
1305 : // process won't dispatch following composition events and will be
1306 : // destroyed by IMEStateManager::DispatchCompositionEvent().
1307 0 : return false;
1308 : }
1309 :
1310 : // When the composition is committed synchronously, the commit string will be
1311 : // returned to the remote process. Then, PuppetWidget will dispatch
1312 : // eCompositionCommit event with the returned commit string (i.e., the value
1313 : // is aCommittedString of this method). Finally, TextComposition instance in
1314 : // the remote process will be destroyed by
1315 : // IMEStateManager::DispatchCompositionEvent() at receiving the
1316 : // eCompositionCommit event (Note that TextComposition instance in this
1317 : // process was already destroyed).
1318 0 : return true;
1319 : }
1320 :
1321 : void
1322 0 : ContentCacheInParent::MaybeNotifyIME(nsIWidget* aWidget,
1323 : const IMENotification& aNotification)
1324 : {
1325 0 : if (!mPendingEventsNeedingAck) {
1326 0 : IMEStateManager::NotifyIME(aNotification, aWidget, &mTabParent);
1327 0 : return;
1328 : }
1329 :
1330 0 : switch (aNotification.mMessage) {
1331 : case NOTIFY_IME_OF_SELECTION_CHANGE:
1332 0 : mPendingSelectionChange.MergeWith(aNotification);
1333 0 : break;
1334 : case NOTIFY_IME_OF_TEXT_CHANGE:
1335 0 : mPendingTextChange.MergeWith(aNotification);
1336 0 : break;
1337 : case NOTIFY_IME_OF_POSITION_CHANGE:
1338 0 : mPendingLayoutChange.MergeWith(aNotification);
1339 0 : break;
1340 : case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1341 0 : mPendingCompositionUpdate.MergeWith(aNotification);
1342 0 : break;
1343 : default:
1344 0 : MOZ_CRASH("Unsupported notification");
1345 : break;
1346 : }
1347 : }
1348 :
1349 : void
1350 0 : ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget)
1351 : {
1352 0 : MOZ_ASSERT(!mPendingEventsNeedingAck);
1353 :
1354 : // If the TabParent's widget has already gone, this can do nothing since
1355 : // widget is necessary to notify IME of something.
1356 0 : if (!aWidget) {
1357 0 : return;
1358 : }
1359 :
1360 : // New notifications which are notified during flushing pending notifications
1361 : // should be merged again.
1362 0 : mPendingEventsNeedingAck++;
1363 :
1364 0 : nsCOMPtr<nsIWidget> widget = aWidget;
1365 :
1366 : // First, text change notification should be sent because selection change
1367 : // notification notifies IME of current selection range in the latest content.
1368 : // So, IME may need the latest content before that.
1369 0 : if (mPendingTextChange.HasNotification()) {
1370 0 : IMENotification notification(mPendingTextChange);
1371 0 : if (!widget->Destroyed()) {
1372 0 : mPendingTextChange.Clear();
1373 0 : IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1374 : }
1375 : }
1376 :
1377 0 : if (mPendingSelectionChange.HasNotification()) {
1378 0 : IMENotification notification(mPendingSelectionChange);
1379 0 : if (!widget->Destroyed()) {
1380 0 : mPendingSelectionChange.Clear();
1381 0 : IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1382 : }
1383 : }
1384 :
1385 : // Layout change notification should be notified after selection change
1386 : // notification because IME may want to query position of new caret position.
1387 0 : if (mPendingLayoutChange.HasNotification()) {
1388 0 : IMENotification notification(mPendingLayoutChange);
1389 0 : if (!widget->Destroyed()) {
1390 0 : mPendingLayoutChange.Clear();
1391 0 : IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1392 : }
1393 : }
1394 :
1395 : // Finally, send composition update notification because it notifies IME of
1396 : // finishing handling whole sending events.
1397 0 : if (mPendingCompositionUpdate.HasNotification()) {
1398 0 : IMENotification notification(mPendingCompositionUpdate);
1399 0 : if (!widget->Destroyed()) {
1400 0 : mPendingCompositionUpdate.Clear();
1401 0 : IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1402 : }
1403 : }
1404 :
1405 0 : if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1406 0 : (mPendingTextChange.HasNotification() ||
1407 0 : mPendingSelectionChange.HasNotification() ||
1408 0 : mPendingLayoutChange.HasNotification() ||
1409 0 : mPendingCompositionUpdate.HasNotification())) {
1410 0 : FlushPendingNotifications(widget);
1411 : }
1412 : }
1413 :
1414 : /*****************************************************************************
1415 : * mozilla::ContentCache::TextRectArray
1416 : *****************************************************************************/
1417 :
1418 : LayoutDeviceIntRect
1419 0 : ContentCache::TextRectArray::GetRect(uint32_t aOffset) const
1420 : {
1421 0 : LayoutDeviceIntRect rect;
1422 0 : if (InRange(aOffset)) {
1423 0 : rect = mRects[aOffset - mStart];
1424 : }
1425 0 : return rect;
1426 : }
1427 :
1428 : LayoutDeviceIntRect
1429 0 : ContentCache::TextRectArray::GetUnionRect(uint32_t aOffset,
1430 : uint32_t aLength) const
1431 : {
1432 0 : LayoutDeviceIntRect rect;
1433 0 : if (!InRange(aOffset, aLength)) {
1434 0 : return rect;
1435 : }
1436 0 : for (uint32_t i = 0; i < aLength; i++) {
1437 0 : rect = rect.Union(mRects[aOffset - mStart + i]);
1438 : }
1439 0 : return rect;
1440 : }
1441 :
1442 : LayoutDeviceIntRect
1443 0 : ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1444 : uint32_t aOffset,
1445 : uint32_t aLength,
1446 : bool aRoundToExistingOffset) const
1447 : {
1448 0 : LayoutDeviceIntRect rect;
1449 0 : if (!HasRects() ||
1450 0 : (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1451 0 : return rect;
1452 : }
1453 0 : uint32_t startOffset = std::max(aOffset, mStart);
1454 0 : if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1455 0 : startOffset = EndOffset() - 1;
1456 : }
1457 0 : uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1458 0 : if (aRoundToExistingOffset && endOffset < mStart + 1) {
1459 0 : endOffset = mStart + 1;
1460 : }
1461 0 : if (NS_WARN_IF(endOffset < startOffset)) {
1462 0 : return rect;
1463 : }
1464 0 : for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1465 0 : rect = rect.Union(mRects[startOffset - mStart + i]);
1466 : }
1467 0 : return rect;
1468 : }
1469 :
1470 : } // namespace mozilla
|