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 : #ifndef mozilla_ContentCache_h
9 : #define mozilla_ContentCache_h
10 :
11 : #include <stdint.h>
12 :
13 : #include "mozilla/Assertions.h"
14 : #include "mozilla/CheckedInt.h"
15 : #include "mozilla/EventForwards.h"
16 : #include "mozilla/WritingModes.h"
17 : #include "nsIWidget.h"
18 : #include "nsString.h"
19 : #include "nsTArray.h"
20 : #include "Units.h"
21 :
22 : namespace mozilla {
23 :
24 : class ContentCacheInParent;
25 :
26 : namespace dom {
27 : class TabParent;
28 : } // namespace dom
29 :
30 : /**
31 : * ContentCache stores various information of the child content.
32 : * This class has members which are necessary both in parent process and
33 : * content process.
34 : */
35 :
36 1 : class ContentCache
37 : {
38 : public:
39 : typedef InfallibleTArray<LayoutDeviceIntRect> RectArray;
40 : typedef widget::IMENotification IMENotification;
41 :
42 : ContentCache();
43 :
44 : protected:
45 : // Whole text in the target
46 : nsString mText;
47 :
48 : // Start offset of the composition string.
49 : uint32_t mCompositionStart;
50 :
51 : enum
52 : {
53 : ePrevCharRect = 1,
54 : eNextCharRect = 0
55 : };
56 :
57 : struct Selection final
58 : {
59 : // Following values are offset in "flat text".
60 : uint32_t mAnchor;
61 : uint32_t mFocus;
62 :
63 : WritingMode mWritingMode;
64 :
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];
72 :
73 : // Whole rect of selected text. This is empty if the selection is collapsed.
74 : LayoutDeviceIntRect mRect;
75 :
76 3 : Selection()
77 3 : : mAnchor(UINT32_MAX)
78 3 : , mFocus(UINT32_MAX)
79 : {
80 3 : }
81 :
82 1 : void Clear()
83 : {
84 1 : mAnchor = mFocus = UINT32_MAX;
85 1 : mWritingMode = WritingMode();
86 1 : ClearAnchorCharRects();
87 1 : ClearFocusCharRects();
88 1 : mRect.SetEmpty();
89 1 : }
90 :
91 1 : void ClearAnchorCharRects()
92 : {
93 3 : for (size_t i = 0; i < ArrayLength(mAnchorCharRects); i++) {
94 2 : mAnchorCharRects[i].SetEmpty();
95 : }
96 1 : }
97 1 : void ClearFocusCharRects()
98 : {
99 3 : for (size_t i = 0; i < ArrayLength(mFocusCharRects); i++) {
100 2 : mFocusCharRects[i].SetEmpty();
101 : }
102 1 : }
103 :
104 0 : bool IsValid() const
105 : {
106 0 : return mAnchor != UINT32_MAX && mFocus != UINT32_MAX;
107 : }
108 0 : bool Collapsed() const
109 : {
110 0 : NS_ASSERTION(IsValid(),
111 : "The caller should check if the selection is valid");
112 0 : return mFocus == mAnchor;
113 : }
114 0 : bool Reversed() const
115 : {
116 0 : NS_ASSERTION(IsValid(),
117 : "The caller should check if the selection is valid");
118 0 : return mFocus < mAnchor;
119 : }
120 0 : uint32_t StartOffset() const
121 : {
122 0 : NS_ASSERTION(IsValid(),
123 : "The caller should check if the selection is valid");
124 0 : return Reversed() ? mFocus : mAnchor;
125 : }
126 0 : uint32_t EndOffset() const
127 : {
128 0 : NS_ASSERTION(IsValid(),
129 : "The caller should check if the selection is valid");
130 0 : return Reversed() ? mAnchor : mFocus;
131 : }
132 0 : uint32_t Length() const
133 : {
134 0 : NS_ASSERTION(IsValid(),
135 : "The caller should check if the selection is valid");
136 0 : return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
137 : }
138 0 : LayoutDeviceIntRect StartCharRect() const
139 : {
140 0 : NS_ASSERTION(IsValid(),
141 : "The caller should check if the selection is valid");
142 0 : return Reversed() ? mFocusCharRects[eNextCharRect] :
143 0 : mAnchorCharRects[eNextCharRect];
144 : }
145 : LayoutDeviceIntRect EndCharRect() const
146 : {
147 : NS_ASSERTION(IsValid(),
148 : "The caller should check if the selection is valid");
149 : return Reversed() ? mAnchorCharRects[eNextCharRect] :
150 : mFocusCharRects[eNextCharRect];
151 : }
152 : } mSelection;
153 :
154 0 : bool IsSelectionValid() const
155 : {
156 0 : return mSelection.IsValid() && mSelection.EndOffset() <= mText.Length();
157 : }
158 :
159 : // Stores first char rect because Yosemite's Japanese IME sometimes tries
160 : // to query it. If there is no text, this is caret rect.
161 : LayoutDeviceIntRect mFirstCharRect;
162 :
163 : struct Caret final
164 : {
165 : uint32_t mOffset;
166 : LayoutDeviceIntRect mRect;
167 :
168 3 : Caret()
169 3 : : mOffset(UINT32_MAX)
170 : {
171 3 : }
172 :
173 1 : void Clear()
174 : {
175 1 : mOffset = UINT32_MAX;
176 1 : mRect.SetEmpty();
177 1 : }
178 :
179 0 : bool IsValid() const { return mOffset != UINT32_MAX; }
180 :
181 : uint32_t Offset() const
182 : {
183 : NS_ASSERTION(IsValid(),
184 : "The caller should check if the caret is valid");
185 : return mOffset;
186 : }
187 : } mCaret;
188 :
189 2 : struct TextRectArray final
190 : {
191 : uint32_t mStart;
192 : RectArray mRects;
193 :
194 3 : TextRectArray()
195 3 : : mStart(UINT32_MAX)
196 : {
197 3 : }
198 :
199 1 : void Clear()
200 : {
201 1 : mStart = UINT32_MAX;
202 1 : mRects.Clear();
203 1 : }
204 :
205 0 : bool IsValid() const
206 : {
207 0 : if (mStart == UINT32_MAX) {
208 0 : return false;
209 : }
210 : CheckedInt<uint32_t> endOffset =
211 0 : CheckedInt<uint32_t>(mStart) + mRects.Length();
212 0 : return endOffset.isValid();
213 : }
214 0 : bool HasRects() const
215 : {
216 0 : return IsValid() && !mRects.IsEmpty();
217 : }
218 0 : uint32_t StartOffset() const
219 : {
220 0 : NS_ASSERTION(IsValid(),
221 : "The caller should check if the caret is valid");
222 0 : return mStart;
223 : }
224 0 : uint32_t EndOffset() const
225 : {
226 0 : NS_ASSERTION(IsValid(),
227 : "The caller should check if the caret is valid");
228 0 : if (!IsValid()) {
229 0 : return UINT32_MAX;
230 : }
231 0 : return mStart + mRects.Length();
232 : }
233 0 : bool InRange(uint32_t aOffset) const
234 : {
235 0 : return IsValid() &&
236 0 : StartOffset() <= aOffset && aOffset < EndOffset();
237 : }
238 0 : bool InRange(uint32_t aOffset, uint32_t aLength) const
239 : {
240 : CheckedInt<uint32_t> endOffset =
241 0 : CheckedInt<uint32_t>(aOffset) + aLength;
242 0 : if (NS_WARN_IF(!endOffset.isValid())) {
243 0 : return false;
244 : }
245 0 : return InRange(aOffset) && aOffset + aLength <= EndOffset();
246 : }
247 0 : bool IsOverlappingWith(uint32_t aOffset, uint32_t aLength) const
248 : {
249 0 : if (!HasRects() || aOffset == UINT32_MAX || !aLength) {
250 0 : return false;
251 : }
252 : CheckedInt<uint32_t> endOffset =
253 0 : CheckedInt<uint32_t>(aOffset) + aLength;
254 0 : if (NS_WARN_IF(!endOffset.isValid())) {
255 0 : return false;
256 : }
257 0 : return aOffset < EndOffset() && endOffset.value() > mStart;
258 : }
259 : LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
260 : LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
261 : LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
262 : uint32_t aOffset, uint32_t aLength,
263 : bool aRoundToExistingOffset) const;
264 : } mTextRectArray;
265 :
266 : LayoutDeviceIntRect mEditorRect;
267 :
268 : friend class ContentCacheInParent;
269 : friend struct IPC::ParamTraits<ContentCache>;
270 : };
271 :
272 0 : class ContentCacheInChild final : public ContentCache
273 : {
274 : public:
275 : ContentCacheInChild();
276 :
277 : /**
278 : * When IME loses focus, this should be called and making this forget the
279 : * content for reducing footprint.
280 : */
281 : void Clear();
282 :
283 : /**
284 : * Cache*() retrieves the latest content information and store them.
285 : * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
286 : * calls CacheSelection(). So, related data is also retrieved automatically.
287 : */
288 : bool CacheEditorRect(nsIWidget* aWidget,
289 : const IMENotification* aNotification = nullptr);
290 : bool CacheSelection(nsIWidget* aWidget,
291 : const IMENotification* aNotification = nullptr);
292 : bool CacheText(nsIWidget* aWidget,
293 : const IMENotification* aNotification = nullptr);
294 :
295 : bool CacheAll(nsIWidget* aWidget,
296 : const IMENotification* aNotification = nullptr);
297 :
298 : /**
299 : * SetSelection() modifies selection with specified raw data. And also this
300 : * tries to retrieve text rects too.
301 : */
302 : void SetSelection(nsIWidget* aWidget,
303 : uint32_t aStartOffset,
304 : uint32_t aLength,
305 : bool aReversed,
306 : const WritingMode& aWritingMode);
307 :
308 : private:
309 : bool QueryCharRect(nsIWidget* aWidget,
310 : uint32_t aOffset,
311 : LayoutDeviceIntRect& aCharRect) const;
312 : bool QueryCharRectArray(nsIWidget* aWidget,
313 : uint32_t aOffset,
314 : uint32_t aLength,
315 : RectArray& aCharRectArray) const;
316 : bool CacheCaret(nsIWidget* aWidget,
317 : const IMENotification* aNotification = nullptr);
318 : bool CacheTextRects(nsIWidget* aWidget,
319 : const IMENotification* aNotification = nullptr);
320 : };
321 :
322 0 : class ContentCacheInParent final : public ContentCache
323 : {
324 : public:
325 : explicit ContentCacheInParent(dom::TabParent& aTabParent);
326 :
327 : /**
328 : * AssignContent() is called when TabParent receives ContentCache from
329 : * the content process. This doesn't copy composition information because
330 : * it's managed by TabParent itself.
331 : */
332 : void AssignContent(const ContentCache& aOther,
333 : nsIWidget* aWidget,
334 : const IMENotification* aNotification = nullptr);
335 :
336 : /**
337 : * HandleQueryContentEvent() sets content data to aEvent.mReply.
338 : *
339 : * For eQuerySelectedText, fail if the cache doesn't contain the whole
340 : * selected range. (This shouldn't happen because PuppetWidget should have
341 : * already sent the whole selection.)
342 : *
343 : * For eQueryTextContent, fail only if the cache doesn't overlap with
344 : * the queried range. Note the difference from above. We use
345 : * this behavior because a normal eQueryTextContent event is allowed to
346 : * have out-of-bounds offsets, so that widget can request content without
347 : * knowing the exact length of text. It's up to widget to handle cases when
348 : * the returned offset/length are different from the queried offset/length.
349 : *
350 : * For eQueryTextRect, fail if cached offset/length aren't equals to input.
351 : * Cocoa widget always queries selected offset, so it works on it.
352 : *
353 : * For eQueryCaretRect, fail if cached offset isn't equals to input
354 : *
355 : * For eQueryEditorRect, always success
356 : */
357 : bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
358 : nsIWidget* aWidget) const;
359 :
360 : /**
361 : * OnCompositionEvent() should be called before sending composition string.
362 : * This returns true if the event should be sent. Otherwise, false.
363 : */
364 : bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
365 :
366 : /**
367 : * OnSelectionEvent() should be called before sending selection event.
368 : */
369 : void OnSelectionEvent(const WidgetSelectionEvent& aSelectionEvent);
370 :
371 : /**
372 : * OnEventNeedingAckHandled() should be called after the child process
373 : * handles a sent event which needs acknowledging.
374 : *
375 : * WARNING: This may send notifications to IME. That might cause destroying
376 : * TabParent or aWidget. Therefore, the caller must not destroy
377 : * this instance during a call of this method.
378 : */
379 : void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage);
380 :
381 : /**
382 : * RequestIMEToCommitComposition() requests aWidget to commit or cancel
383 : * composition. If it's handled synchronously, this returns true.
384 : *
385 : * @param aWidget The widget to be requested to commit or cancel
386 : * the composition.
387 : * @param aCancel When the caller tries to cancel the composition, true.
388 : * Otherwise, i.e., tries to commit the composition, false.
389 : * @param aCommittedString The committed string (i.e., the last data of
390 : * dispatched composition events during requesting
391 : * IME to commit composition.
392 : * @return Whether the composition is actually committed
393 : * synchronously.
394 : */
395 : bool RequestIMEToCommitComposition(nsIWidget* aWidget,
396 : bool aCancel,
397 : nsAString& aCommittedString);
398 :
399 : /**
400 : * MaybeNotifyIME() may notify IME of the notification. If child process
401 : * hasn't been handled all sending events yet, this stores the notification
402 : * and flush it later.
403 : */
404 : void MaybeNotifyIME(nsIWidget* aWidget,
405 : const IMENotification& aNotification);
406 :
407 : private:
408 : IMENotification mPendingSelectionChange;
409 : IMENotification mPendingTextChange;
410 : IMENotification mPendingLayoutChange;
411 : IMENotification mPendingCompositionUpdate;
412 :
413 : // mTabParent is owner of the instance.
414 : dom::TabParent& MOZ_NON_OWNING_REF mTabParent;
415 : // mCompositionString is composition string which were sent to the remote
416 : // process but not yet committed in the remote process.
417 : nsString mCompositionString;
418 : // This is not nullptr only while the instance is requesting IME to
419 : // composition. Then, data value of dispatched composition events should
420 : // be stored into the instance.
421 : nsAString* mCommitStringByRequest;
422 : // mPendingEventsNeedingAck is increased before sending a composition event or
423 : // a selection event and decreased after they are received in the child
424 : // process.
425 : uint32_t mPendingEventsNeedingAck;
426 : // mCompositionStartInChild stores current composition start offset in the
427 : // remote process.
428 : uint32_t mCompositionStartInChild;
429 : // mPendingCommitLength is commit string length of the first pending
430 : // composition. This is used by relative offset query events when querying
431 : // new composition start offset.
432 : // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
433 : // more pending compositions, this cache won't be used because in such case,
434 : // anyway ContentCacheInParent cannot return proper character rect.
435 : uint32_t mPendingCommitLength;
436 : // mPendingCompositionCount is number of compositions which started in widget
437 : // but not yet handled in the child process.
438 : uint8_t mPendingCompositionCount;
439 : // mWidgetHasComposition is true when the widget in this process thinks that
440 : // IME has composition. So, this is set to true when eCompositionStart is
441 : // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
442 : bool mWidgetHasComposition;
443 : // mIsPendingLastCommitEvent is true only when this sends
444 : // eCompositionCommit(AsIs) event to the remote process but it's not handled
445 : // in the remote process yet.
446 : bool mIsPendingLastCommitEvent;
447 :
448 : ContentCacheInParent() = delete;
449 :
450 : /**
451 : * When following methods' aRoundToExistingOffset is true, even if specified
452 : * offset or range is out of bounds, the result is computed with the existing
453 : * cache forcibly.
454 : */
455 : bool GetCaretRect(uint32_t aOffset,
456 : bool aRoundToExistingOffset,
457 : LayoutDeviceIntRect& aCaretRect) const;
458 : bool GetTextRect(uint32_t aOffset,
459 : bool aRoundToExistingOffset,
460 : LayoutDeviceIntRect& aTextRect) const;
461 : bool GetUnionTextRects(uint32_t aOffset,
462 : uint32_t aLength,
463 : bool aRoundToExistingOffset,
464 : LayoutDeviceIntRect& aUnionTextRect) const;
465 :
466 : void FlushPendingNotifications(nsIWidget* aWidget);
467 : };
468 :
469 : } // namespace mozilla
470 :
471 : #endif // mozilla_ContentCache_h
|