Bug 1889091 - Part 4: Remove extra stack pointer move. r=jandem
[gecko.git] / widget / ContentCache.h
blob3f8b48425ede9097d7137ea43fe255f1ce79481b
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/ipc/IPCForwards.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/CheckedInt.h"
17 #include "mozilla/EventForwards.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/ToString.h"
20 #include "mozilla/WritingModes.h"
21 #include "nsString.h"
22 #include "nsTArray.h"
23 #include "Units.h"
25 class nsIWidget;
27 namespace mozilla {
29 class ContentCacheInParent;
31 namespace dom {
32 class BrowserParent;
33 } // namespace dom
35 /**
36 * ContentCache stores various information of the child content.
37 * This class has members which are necessary both in parent process and
38 * content process.
41 class ContentCache {
42 public:
43 using RectArray = CopyableTArray<LayoutDeviceIntRect>;
44 using IMENotification = widget::IMENotification;
46 ContentCache() = default;
48 [[nodiscard]] bool IsValid() const;
50 protected:
51 void AssertIfInvalid() const;
53 // Whole text in the target
54 Maybe<nsString> mText;
56 // Start offset of the composition string.
57 Maybe<uint32_t> mCompositionStart;
59 enum { ePrevCharRect = 1, eNextCharRect = 0 };
61 struct Selection final {
62 // Following values are offset in "flat text".
63 uint32_t mAnchor;
64 uint32_t mFocus;
66 WritingMode mWritingMode;
68 bool mHasRange;
70 // Character rects at previous and next character of mAnchor and mFocus.
71 // The reason why ContentCache needs to store each previous character of
72 // them is IME may query character rect of the last character of a line
73 // when caret is at the end of the line.
74 // Note that use ePrevCharRect and eNextCharRect for accessing each item.
75 LayoutDeviceIntRect mAnchorCharRects[2];
76 LayoutDeviceIntRect mFocusCharRects[2];
78 // Whole rect of selected text. This is empty if the selection is collapsed.
79 LayoutDeviceIntRect mRect;
81 Selection() : mAnchor(UINT32_MAX), mFocus(UINT32_MAX), mHasRange(false) {
82 ClearRects();
85 explicit Selection(
86 const IMENotification::SelectionChangeDataBase& aSelectionChangeData)
87 : mAnchor(UINT32_MAX),
88 mFocus(UINT32_MAX),
89 mWritingMode(aSelectionChangeData.GetWritingMode()),
90 mHasRange(aSelectionChangeData.HasRange()) {
91 if (mHasRange) {
92 mAnchor = aSelectionChangeData.AnchorOffset();
93 mFocus = aSelectionChangeData.FocusOffset();
97 [[nodiscard]] bool IsValidIn(const nsAString& aText) const {
98 return !mHasRange ||
99 (mAnchor <= aText.Length() && mFocus <= aText.Length());
102 explicit Selection(const WidgetQueryContentEvent& aQuerySelectedTextEvent);
104 void ClearRects() {
105 for (auto& rect : mAnchorCharRects) {
106 rect.SetEmpty();
108 for (auto& rect : mFocusCharRects) {
109 rect.SetEmpty();
111 mRect.SetEmpty();
113 bool HasRects() const {
114 for (const auto& rect : mAnchorCharRects) {
115 if (!rect.IsEmpty()) {
116 return true;
119 for (const auto& rect : mFocusCharRects) {
120 if (!rect.IsEmpty()) {
121 return true;
124 return !mRect.IsEmpty();
127 bool IsCollapsed() const { return !mHasRange || mFocus == mAnchor; }
128 bool Reversed() const {
129 MOZ_ASSERT(mHasRange);
130 return mFocus < mAnchor;
132 uint32_t StartOffset() const {
133 MOZ_ASSERT(mHasRange);
134 return Reversed() ? mFocus : mAnchor;
136 uint32_t EndOffset() const {
137 MOZ_ASSERT(mHasRange);
138 return Reversed() ? mAnchor : mFocus;
140 uint32_t Length() const {
141 MOZ_ASSERT(mHasRange);
142 return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
144 LayoutDeviceIntRect StartCharRect() const {
145 return Reversed() ? mFocusCharRects[eNextCharRect]
146 : mAnchorCharRects[eNextCharRect];
148 LayoutDeviceIntRect EndCharRect() const {
149 return Reversed() ? mAnchorCharRects[eNextCharRect]
150 : mFocusCharRects[eNextCharRect];
153 friend std::ostream& operator<<(std::ostream& aStream,
154 const Selection& aSelection) {
155 aStream << "{ ";
156 if (!aSelection.mHasRange) {
157 aStream << "HasRange()=false";
158 } else {
159 aStream << "mAnchor=" << aSelection.mAnchor
160 << ", mFocus=" << aSelection.mFocus << ", mWritingMode="
161 << ToString(aSelection.mWritingMode).c_str();
163 if (aSelection.HasRects()) {
164 if (aSelection.mAnchor > 0) {
165 aStream << ", mAnchorCharRects[ePrevCharRect]="
166 << aSelection.mAnchorCharRects[ContentCache::ePrevCharRect];
168 aStream << ", mAnchorCharRects[eNextCharRect]="
169 << aSelection.mAnchorCharRects[ContentCache::eNextCharRect];
170 if (aSelection.mFocus > 0) {
171 aStream << ", mFocusCharRects[ePrevCharRect]="
172 << aSelection.mFocusCharRects[ContentCache::ePrevCharRect];
174 aStream << ", mFocusCharRects[eNextCharRect]="
175 << aSelection.mFocusCharRects[ContentCache::eNextCharRect]
176 << ", mRect=" << aSelection.mRect;
178 if (aSelection.mHasRange) {
179 aStream << ", Reversed()=" << (aSelection.Reversed() ? "true" : "false")
180 << ", StartOffset()=" << aSelection.StartOffset()
181 << ", EndOffset()=" << aSelection.EndOffset()
182 << ", IsCollapsed()="
183 << (aSelection.IsCollapsed() ? "true" : "false")
184 << ", Length()=" << aSelection.Length();
186 aStream << " }";
187 return aStream;
190 Maybe<Selection> mSelection;
192 // Stores first char rect because Yosemite's Japanese IME sometimes tries
193 // to query it. If there is no text, this is caret rect.
194 LayoutDeviceIntRect mFirstCharRect;
196 struct Caret final {
197 uint32_t mOffset = 0u;
198 LayoutDeviceIntRect mRect;
200 explicit Caret(uint32_t aOffset, LayoutDeviceIntRect aCaretRect)
201 : mOffset(aOffset), mRect(aCaretRect) {}
203 uint32_t Offset() const { return mOffset; }
204 bool HasRect() const { return !mRect.IsEmpty(); }
206 [[nodiscard]] bool IsValidIn(const nsAString& aText) const {
207 return mOffset <= aText.Length();
210 friend std::ostream& operator<<(std::ostream& aStream,
211 const Caret& aCaret) {
212 aStream << "{ mOffset=" << aCaret.mOffset;
213 if (aCaret.HasRect()) {
214 aStream << ", mRect=" << aCaret.mRect;
216 return aStream << " }";
219 private:
220 // For ParamTraits<Caret>
221 Caret() = default;
223 friend struct IPC::ParamTraits<ContentCache::Caret>;
224 ALLOW_DEPRECATED_READPARAM
226 Maybe<Caret> mCaret;
228 struct TextRectArray final {
229 uint32_t mStart = 0u;
230 RectArray mRects;
232 explicit TextRectArray(uint32_t aStartOffset) : mStart(aStartOffset) {}
234 bool HasRects() const { return Length() > 0; }
235 uint32_t StartOffset() const { return mStart; }
236 uint32_t EndOffset() const {
237 CheckedInt<uint32_t> endOffset =
238 CheckedInt<uint32_t>(mStart) + mRects.Length();
239 return endOffset.isValid() ? endOffset.value() : UINT32_MAX;
241 uint32_t Length() const { return EndOffset() - mStart; }
242 bool IsOffsetInRange(uint32_t aOffset) const {
243 return StartOffset() <= aOffset && aOffset < EndOffset();
245 bool IsRangeCompletelyInRange(uint32_t aOffset, uint32_t aLength) const {
246 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
247 if (NS_WARN_IF(!endOffset.isValid())) {
248 return false;
250 return IsOffsetInRange(aOffset) && aOffset + aLength <= EndOffset();
252 bool IsOverlappingWith(uint32_t aOffset, uint32_t aLength) const {
253 if (!HasRects() || aOffset == UINT32_MAX || !aLength) {
254 return false;
256 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
257 if (NS_WARN_IF(!endOffset.isValid())) {
258 return false;
260 return aOffset < EndOffset() && endOffset.value() > mStart;
262 LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
263 LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
264 LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
265 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const;
267 friend std::ostream& operator<<(std::ostream& aStream,
268 const TextRectArray& aTextRectArray) {
269 aStream << "{ mStart=" << aTextRectArray.mStart
270 << ", mRects={ Length()=" << aTextRectArray.Length();
271 if (aTextRectArray.HasRects()) {
272 aStream << ", Elements()=[ ";
273 static constexpr uint32_t kMaxPrintRects = 4;
274 const uint32_t kFirstHalf = aTextRectArray.Length() <= kMaxPrintRects
275 ? UINT32_MAX
276 : (kMaxPrintRects + 1) / 2;
277 const uint32_t kSecondHalf =
278 aTextRectArray.Length() <= kMaxPrintRects ? 0 : kMaxPrintRects / 2;
279 for (uint32_t i = 0; i < aTextRectArray.Length(); i++) {
280 if (i > 0) {
281 aStream << ", ";
283 aStream << ToString(aTextRectArray.mRects[i]).c_str();
284 if (i + 1 == kFirstHalf) {
285 aStream << " ...";
286 i = aTextRectArray.Length() - kSecondHalf - 1;
290 return aStream << " ] } }";
293 private:
294 // For ParamTraits<TextRectArray>
295 TextRectArray() = default;
297 friend struct IPC::ParamTraits<ContentCache::TextRectArray>;
298 ALLOW_DEPRECATED_READPARAM
300 Maybe<TextRectArray> mTextRectArray;
301 Maybe<TextRectArray> mLastCommitStringTextRectArray;
303 LayoutDeviceIntRect mEditorRect;
305 friend class ContentCacheInParent;
306 friend struct IPC::ParamTraits<ContentCache>;
307 friend struct IPC::ParamTraits<ContentCache::Selection>;
308 friend struct IPC::ParamTraits<ContentCache::Caret>;
309 friend struct IPC::ParamTraits<ContentCache::TextRectArray>;
310 friend std::ostream& operator<<(
311 std::ostream& aStream,
312 const Selection& aSelection); // For e(Prev|Next)CharRect
313 ALLOW_DEPRECATED_READPARAM
316 class ContentCacheInChild final : public ContentCache {
317 public:
318 ContentCacheInChild() = default;
321 * Called when composition event will be dispatched in this process from
322 * PuppetWidget.
324 void OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
327 * When IME loses focus, this should be called and making this forget the
328 * content for reducing footprint.
330 void Clear();
333 * Cache*() retrieves the latest content information and store them.
334 * Be aware, CacheSelection() calls CacheCaretAndTextRects(),
335 * CacheCaretAndTextRects() calls CacheCaret() and CacheTextRects(), and
336 * CacheText() calls CacheSelection(). So, related data is also retrieved
337 * automatically.
339 bool CacheEditorRect(nsIWidget* aWidget,
340 const IMENotification* aNotification = nullptr);
341 bool CacheCaretAndTextRects(nsIWidget* aWidget,
342 const IMENotification* aNotification = nullptr);
343 bool CacheText(nsIWidget* aWidget,
344 const IMENotification* aNotification = nullptr);
346 bool CacheAll(nsIWidget* aWidget,
347 const IMENotification* aNotification = nullptr);
350 * SetSelection() modifies selection with specified raw data. And also this
351 * tries to retrieve text rects too.
353 * @return true if the selection is cached. Otherwise, false.
355 [[nodiscard]] bool SetSelection(
356 nsIWidget* aWidget,
357 const IMENotification::SelectionChangeDataBase& aSelectionChangeData);
359 private:
360 bool QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
361 LayoutDeviceIntRect& aCharRect) const;
362 bool QueryCharRectArray(nsIWidget* aWidget, uint32_t aOffset,
363 uint32_t aLength, RectArray& aCharRectArray) const;
364 bool CacheSelection(nsIWidget* aWidget,
365 const IMENotification* aNotification = nullptr);
366 bool CacheCaret(nsIWidget* aWidget,
367 const IMENotification* aNotification = nullptr);
368 bool CacheTextRects(nsIWidget* aWidget,
369 const IMENotification* aNotification = nullptr);
371 // Once composition is committed, all of the commit string may be composed
372 // again by Kakutei-Undo of Japanese IME. Therefore, we need to keep
373 // storing the last composition start to cache all character rects of the
374 // last commit string.
375 Maybe<OffsetAndData<uint32_t>> mLastCommit;
378 class ContentCacheInParent final : public ContentCache {
379 public:
380 ContentCacheInParent() = delete;
381 explicit ContentCacheInParent(dom::BrowserParent& aBrowserParent);
384 * AssignContent() is called when BrowserParent receives ContentCache from
385 * the content process. This doesn't copy composition information because
386 * it's managed by BrowserParent itself.
388 void AssignContent(const ContentCache& aOther, nsIWidget* aWidget,
389 const IMENotification* aNotification = nullptr);
392 * HandleQueryContentEvent() sets content data to aEvent.mReply.
394 * For eQuerySelectedText, fail if the cache doesn't contain the whole
395 * selected range. (This shouldn't happen because PuppetWidget should have
396 * already sent the whole selection.)
398 * For eQueryTextContent, fail only if the cache doesn't overlap with
399 * the queried range. Note the difference from above. We use
400 * this behavior because a normal eQueryTextContent event is allowed to
401 * have out-of-bounds offsets, so that widget can request content without
402 * knowing the exact length of text. It's up to widget to handle cases when
403 * the returned offset/length are different from the queried offset/length.
405 * For eQueryTextRect, fail if cached offset/length aren't equals to input.
406 * Cocoa widget always queries selected offset, so it works on it.
408 * For eQueryCaretRect, fail if cached offset isn't equals to input
410 * For eQueryEditorRect, always success
412 bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
413 nsIWidget* aWidget) const;
416 * OnCompositionEvent() should be called before sending composition string.
417 * This returns true if the event should be sent. Otherwise, false.
419 bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
422 * OnSelectionEvent() should be called before sending selection event.
424 void OnSelectionEvent(const WidgetSelectionEvent& aSelectionEvent);
427 * OnEventNeedingAckHandled() should be called after the child process
428 * handles a sent event which needs acknowledging.
430 * WARNING: This may send notifications to IME. That might cause destroying
431 * BrowserParent or aWidget. Therefore, the caller must not destroy
432 * this instance during a call of this method.
434 void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage,
435 uint32_t aCompositionId);
438 * RequestIMEToCommitComposition() requests aWidget to commit or cancel
439 * composition. If it's handled synchronously, this returns true.
441 * @param aWidget The widget to be requested to commit or cancel
442 * the composition.
443 * @param aCancel When the caller tries to cancel the composition, true.
444 * Otherwise, i.e., tries to commit the composition, false.
445 * @param aCompositionId
446 * The composition ID which should be committed or
447 * canceled.
448 * @param aCommittedString The committed string (i.e., the last data of
449 * dispatched composition events during requesting
450 * IME to commit composition.
451 * @return Whether the composition is actually committed
452 * synchronously.
454 bool RequestIMEToCommitComposition(nsIWidget* aWidget, bool aCancel,
455 uint32_t aCompositionId,
456 nsAString& aCommittedString);
459 * MaybeNotifyIME() may notify IME of the notification. If child process
460 * hasn't been handled all sending events yet, this stores the notification
461 * and flush it later.
463 void MaybeNotifyIME(nsIWidget* aWidget, const IMENotification& aNotification);
465 private:
466 struct HandlingCompositionData;
468 // Return true when the widget in this process thinks that IME has
469 // composition. So, this returns true when there is at least one handling
470 // composition data and the last handling composition has not dispatched
471 // composition commit event to the remote process yet.
472 [[nodiscard]] bool WidgetHasComposition() const {
473 return !mHandlingCompositions.IsEmpty() &&
474 !mHandlingCompositions.LastElement().mSentCommitEvent;
477 // Return true if there is a pending composition which has already sent
478 // a commit event to the remote process, but not yet handled by it.
479 [[nodiscard]] bool HasPendingCommit() const {
480 for (const HandlingCompositionData& data : mHandlingCompositions) {
481 if (data.mSentCommitEvent) {
482 return true;
485 return false;
488 // Return the number of composition events and set selection events which were
489 // sent to the remote process, but we've not verified that the remote process
490 // finished handling it.
491 [[nodiscard]] uint32_t PendingEventsNeedingAck() const {
492 uint32_t ret = mPendingSetSelectionEventNeedingAck;
493 for (const HandlingCompositionData& data : mHandlingCompositions) {
494 ret += data.mPendingEventsNeedingAck;
496 return ret;
499 [[nodiscard]] HandlingCompositionData* GetHandlingCompositionData(
500 uint32_t aCompositionId) {
501 for (HandlingCompositionData& data : mHandlingCompositions) {
502 if (data.mCompositionId == aCompositionId) {
503 return &data;
506 return nullptr;
508 [[nodiscard]] const HandlingCompositionData* GetHandlingCompositionData(
509 uint32_t aCompositionId) const {
510 return const_cast<ContentCacheInParent*>(this)->GetHandlingCompositionData(
511 aCompositionId);
514 IMENotification mPendingSelectionChange;
515 IMENotification mPendingTextChange;
516 IMENotification mPendingLayoutChange;
517 IMENotification mPendingCompositionUpdate;
519 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
520 // Log of event messages to be output to crash report.
521 nsTArray<EventMessage> mDispatchedEventMessages;
522 nsTArray<EventMessage> mReceivedEventMessages;
523 // Log of RequestIMEToCommitComposition() in the last 2 compositions.
524 enum class RequestIMEToCommitCompositionResult : uint8_t {
525 eToOldCompositionReceived,
526 eToUnknownCompositionReceived,
527 eToCommittedCompositionReceived,
528 eReceivedAfterBrowserParentBlur,
529 eReceivedButNoTextComposition,
530 eReceivedButForDifferentTextComposition,
531 eHandledAsynchronously,
532 eHandledSynchronously,
534 const char* ToReadableText(
535 RequestIMEToCommitCompositionResult aResult) const {
536 switch (aResult) {
537 case RequestIMEToCommitCompositionResult::eToOldCompositionReceived:
538 return "Commit request is not handled because it's for "
539 "older composition";
540 case RequestIMEToCommitCompositionResult::eToUnknownCompositionReceived:
541 return "Commit request is not handled because it's for "
542 "unknown composition";
543 case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived:
544 return "Commit request is not handled because BrowserParent has "
545 "already "
546 "sent commit event for the composition";
547 case RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur:
548 return "Commit request is handled with stored composition string "
549 "because BrowserParent has already lost focus";
550 case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition:
551 return "Commit request is not handled because there is no "
552 "TextComposition instance";
553 case RequestIMEToCommitCompositionResult::
554 eReceivedButForDifferentTextComposition:
555 return "Commit request is handled with stored composition string "
556 "because new TextComposition is active";
557 case RequestIMEToCommitCompositionResult::eHandledAsynchronously:
558 return "Commit request is handled but IME doesn't commit current "
559 "composition synchronously";
560 case RequestIMEToCommitCompositionResult::eHandledSynchronously:
561 return "Commit request is handled synchronously";
562 default:
563 return "Unknown reason";
566 nsTArray<RequestIMEToCommitCompositionResult>
567 mRequestIMEToCommitCompositionResults;
568 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
570 // Stores pending compositions (meaning eCompositionStart was dispatched, but
571 // eCompositionCommit(AsIs) has not been handled by the remote process yet).
572 struct HandlingCompositionData {
573 // The lasted composition string which was sent to the remote process.
574 nsString mCompositionString;
575 // The composition ID of a handling composition with the instance.
576 uint32_t mCompositionId;
577 // Increased when sending composition events and decreased when the
578 // remote process finished handling the events.
579 uint32_t mPendingEventsNeedingAck = 0u;
580 // true if eCompositionCommit(AsIs) has already been sent to the remote
581 // process.
582 bool mSentCommitEvent = false;
584 explicit HandlingCompositionData(uint32_t aCompositionId)
585 : mCompositionId(aCompositionId) {}
587 AutoTArray<HandlingCompositionData, 2> mHandlingCompositions;
589 // mBrowserParent is owner of the instance.
590 dom::BrowserParent& MOZ_NON_OWNING_REF mBrowserParent;
591 // This is not nullptr only while the instance is requesting IME to
592 // composition. Then, data value of dispatched composition events should
593 // be stored into the instance.
594 nsAString* mCommitStringByRequest;
595 // mCompositionStartInChild stores current composition start offset in the
596 // remote process.
597 Maybe<uint32_t> mCompositionStartInChild;
598 // Increased when sending eSetSelection events and decreased when the remote
599 // process finished handling the events. Note that eSetSelection may be
600 // dispatched without composition. Therefore, we need to count it with this.
601 uint32_t mPendingSetSelectionEventNeedingAck = 0u;
602 // mPendingCommitLength is commit string length of the first pending
603 // composition. This is used by relative offset query events when querying
604 // new composition start offset.
605 // Note that when mHandlingCompositions has 2 or more elements, i.e., there
606 // are 2 or more pending compositions, this cache won't be used because in
607 // such case, anyway ContentCacheInParent cannot return proper character rect.
608 uint32_t mPendingCommitLength;
609 // mIsChildIgnoringCompositionEvents is set to true if the child process
610 // requests commit composition whose commit has already been sent to it.
611 // Then, set to false when the child process ignores the commit event.
612 bool mIsChildIgnoringCompositionEvents;
615 * When following methods' aRoundToExistingOffset is true, even if specified
616 * offset or range is out of bounds, the result is computed with the existing
617 * cache forcibly.
619 bool GetCaretRect(uint32_t aOffset, bool aRoundToExistingOffset,
620 LayoutDeviceIntRect& aCaretRect) const;
621 bool GetTextRect(uint32_t aOffset, bool aRoundToExistingOffset,
622 LayoutDeviceIntRect& aTextRect) const;
623 bool GetUnionTextRects(uint32_t aOffset, uint32_t aLength,
624 bool aRoundToExistingOffset,
625 LayoutDeviceIntRect& aUnionTextRect) const;
627 void FlushPendingNotifications(nsIWidget* aWidget);
629 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
631 * Remove unnecessary messages from mDispatchedEventMessages and
632 * mReceivedEventMessages.
634 void RemoveUnnecessaryEventMessageLog();
637 * Append event message log to aLog.
639 void AppendEventMessageLog(nsACString& aLog) const;
640 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
643 } // namespace mozilla
645 #endif // mozilla_ContentCache_h