1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsComplexBreaker.h"
10 #include "MainThreadUtils.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Services.h"
13 #include "mozilla/UniquePtr.h"
14 #include "nsTHashMap.h"
15 #include "nsIObserver.h"
16 #include "nsIObserverService.h"
19 #include "nsThreadUtils.h"
21 using namespace mozilla
;
23 using CacheMap
= nsTHashMap
<nsString
, nsTArray
<uint8_t>>;
25 static UniquePtr
<CacheMap
> sBreakCache
;
27 // The underlying hash table extends capacity, when it hits .75 full and uses
28 // powers of 2 for sizing. This cache limit will hopefully mean most pages fit
29 // within the cache, while keeping it to a reasonable size. Also by holding the
30 // previous cache even if pages are bigger than the cache the most commonly used
31 // should still remain fast.
32 static const int kCacheLimit
= 3072;
34 static UniquePtr
<CacheMap
> sOldBreakCache
;
36 // Simple runnable to delete caches off the main thread.
37 class CacheDeleter final
: public Runnable
{
39 explicit CacheDeleter(UniquePtr
<CacheMap
> aCacheToDelete
)
40 : Runnable("ComplexBreaker CacheDeleter"),
41 mCacheToDelete(std::move(aCacheToDelete
)) {}
43 NS_IMETHOD
Run() override
{
44 MOZ_ASSERT(!NS_IsMainThread());
45 mCacheToDelete
= nullptr;
50 UniquePtr
<CacheMap
> mCacheToDelete
;
53 class ComplexBreakObserver final
: public nsIObserver
{
54 ~ComplexBreakObserver() = default;
61 NS_IMPL_ISUPPORTS(ComplexBreakObserver
, nsIObserver
)
63 NS_IMETHODIMP
ComplexBreakObserver::Observe(nsISupports
* aSubject
,
65 const char16_t
* aData
) {
66 MOZ_ASSERT(NS_IsMainThread());
68 if (strcmp(aTopic
, "memory-pressure") == 0) {
70 // We have an old cache, so delete that one first.
71 NS_DispatchBackgroundTask(
72 MakeAndAddRef
<CacheDeleter
>(std::move(sOldBreakCache
)));
73 } else if (sBreakCache
) {
74 NS_DispatchBackgroundTask(
75 MakeAndAddRef
<CacheDeleter
>(std::move(sBreakCache
)));
82 void ComplexBreaker::Initialize() {
83 MOZ_ASSERT(NS_IsMainThread());
85 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
87 obs
->AddObserver(new ComplexBreakObserver(), "memory-pressure", false);
91 void ComplexBreaker::Shutdown() {
92 MOZ_ASSERT(NS_IsMainThread());
94 sBreakCache
= nullptr;
95 sOldBreakCache
= nullptr;
98 static void AddToCache(const char16_t
* aText
, uint32_t aLength
,
99 nsTArray
<uint8_t> aBreakBefore
) {
100 if (NS_WARN_IF(!sBreakCache
->InsertOrUpdate(
101 nsString(aText
, aLength
), std::move(aBreakBefore
), fallible
))) {
105 if (sBreakCache
->Count() <= kCacheLimit
) {
109 if (sOldBreakCache
) {
110 NS_DispatchBackgroundTask(
111 MakeAndAddRef
<CacheDeleter
>(std::move(sOldBreakCache
)));
114 sOldBreakCache
= std::move(sBreakCache
);
117 static void CopyAndFill(const nsTArray
<uint8_t>& aCachedBreakBefore
,
118 uint8_t* aBreakBefore
, uint8_t* aEndBreakBefore
) {
119 auto* startFill
= std::copy(aCachedBreakBefore
.begin(),
120 aCachedBreakBefore
.end(), aBreakBefore
);
121 std::fill(startFill
, aEndBreakBefore
, false);
124 void ComplexBreaker::GetBreaks(const char16_t
* aText
, uint32_t aLength
,
125 uint8_t* aBreakBefore
) {
126 // It is believed that this is only called on the main thread, so we don't
127 // need to lock the caching structures. A diagnostic assert is used in case
128 // our tests don't exercise all code paths.
129 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
131 MOZ_ASSERT(aText
, "aText shouldn't be null");
132 MOZ_ASSERT(aLength
, "aLength shouldn't be zero");
133 MOZ_ASSERT(aBreakBefore
, "aBreakBefore shouldn't be null");
135 // If we have a cache then check it, if not then create it.
138 sBreakCache
->Lookup(nsDependentSubstring(aText
, aLength
))) {
139 auto& breakBefore
= entry
.Data();
140 CopyAndFill(breakBefore
, aBreakBefore
, aBreakBefore
+ aLength
);
144 sBreakCache
= MakeUnique
<CacheMap
>();
147 // We keep the previous cache when we hit our limit, so that the most recently
148 // used fragments remain fast.
149 if (sOldBreakCache
) {
151 sOldBreakCache
->Extract(nsDependentSubstring(aText
, aLength
));
153 CopyAndFill(*breakBefore
, aBreakBefore
, aBreakBefore
+ aLength
);
154 // Move the entry to the latest cache.
155 AddToCache(aText
, aLength
, std::move(*breakBefore
));
160 NS_GetComplexLineBreaks(aText
, aLength
, aBreakBefore
);
162 // As a very simple memory saving measure we trim off trailing elements that
163 // are false before caching.
164 auto* afterLastTrue
= aBreakBefore
+ aLength
;
165 while (!*(afterLastTrue
- 1)) {
166 if (--afterLastTrue
== aBreakBefore
) {
171 AddToCache(aText
, aLength
,
172 nsTArray
<uint8_t>(aBreakBefore
, afterLastTrue
- aBreakBefore
));