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/. */
5 
6 #include "nsComplexBreaker.h"
7 
8 #include <algorithm>
9 
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"
17 #include "nsString.h"
18 #include "nsTArray.h"
19 #include "nsThreadUtils.h"
20 
21 using namespace mozilla;
22 
23 using CacheMap = nsTHashMap<nsString, nsTArray<uint8_t>>;
24 
25 static UniquePtr<CacheMap> sBreakCache;
26 
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;
33 
34 static UniquePtr<CacheMap> sOldBreakCache;
35 
36 // Simple runnable to delete caches off the main thread.
37 class CacheDeleter final : public Runnable {
38  public:
CacheDeleter(UniquePtr<CacheMap> aCacheToDelete)39   explicit CacheDeleter(UniquePtr<CacheMap> aCacheToDelete)
40       : Runnable("ComplexBreaker CacheDeleter"),
41         mCacheToDelete(std::move(aCacheToDelete)) {}
42 
Run()43   NS_IMETHOD Run() override {
44     MOZ_ASSERT(!NS_IsMainThread());
45     mCacheToDelete = nullptr;
46     return NS_OK;
47   }
48 
49  private:
50   UniquePtr<CacheMap> mCacheToDelete;
51 };
52 
53 class ComplexBreakObserver final : public nsIObserver {
54   ~ComplexBreakObserver() = default;
55 
56  public:
57   NS_DECL_ISUPPORTS
58   NS_DECL_NSIOBSERVER
59 };
60 
NS_IMPL_ISUPPORTS(ComplexBreakObserver,nsIObserver)61 NS_IMPL_ISUPPORTS(ComplexBreakObserver, nsIObserver)
62 
63 NS_IMETHODIMP ComplexBreakObserver::Observe(nsISupports* aSubject,
64                                             const char* aTopic,
65                                             const char16_t* aData) {
66   MOZ_ASSERT(NS_IsMainThread());
67 
68   if (strcmp(aTopic, "memory-pressure") == 0) {
69     if (sOldBreakCache) {
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)));
76     }
77   }
78 
79   return NS_OK;
80 }
81 
Initialize()82 void ComplexBreaker::Initialize() {
83   MOZ_ASSERT(NS_IsMainThread());
84 
85   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
86   if (obs) {
87     obs->AddObserver(new ComplexBreakObserver(), "memory-pressure", false);
88   }
89 }
90 
Shutdown()91 void ComplexBreaker::Shutdown() {
92   MOZ_ASSERT(NS_IsMainThread());
93 
94   sBreakCache = nullptr;
95   sOldBreakCache = nullptr;
96 }
97 
AddToCache(const char16_t * aText,uint32_t aLength,nsTArray<uint8_t> aBreakBefore)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))) {
102     return;
103   }
104 
105   if (sBreakCache->Count() <= kCacheLimit) {
106     return;
107   }
108 
109   if (sOldBreakCache) {
110     NS_DispatchBackgroundTask(
111         MakeAndAddRef<CacheDeleter>(std::move(sOldBreakCache)));
112   }
113 
114   sOldBreakCache = std::move(sBreakCache);
115 }
116 
CopyAndFill(const nsTArray<uint8_t> & aCachedBreakBefore,uint8_t * aBreakBefore,uint8_t * aEndBreakBefore)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);
122 }
123 
GetBreaks(const char16_t * aText,uint32_t aLength,uint8_t * aBreakBefore)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());
130 
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");
134 
135   // If we have a cache then check it, if not then create it.
136   if (sBreakCache) {
137     if (auto entry =
138             sBreakCache->Lookup(nsDependentSubstring(aText, aLength))) {
139       auto& breakBefore = entry.Data();
140       CopyAndFill(breakBefore, aBreakBefore, aBreakBefore + aLength);
141       return;
142     }
143   } else {
144     sBreakCache = MakeUnique<CacheMap>();
145   }
146 
147   // We keep the previous cache when we hit our limit, so that the most recently
148   // used fragments remain fast.
149   if (sOldBreakCache) {
150     auto breakBefore =
151         sOldBreakCache->Extract(nsDependentSubstring(aText, aLength));
152     if (breakBefore) {
153       CopyAndFill(*breakBefore, aBreakBefore, aBreakBefore + aLength);
154       // Move the entry to the latest cache.
155       AddToCache(aText, aLength, std::move(*breakBefore));
156       return;
157     }
158   }
159 
160   NS_GetComplexLineBreaks(aText, aLength, aBreakBefore);
161 
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) {
167       break;
168     }
169   }
170 
171   AddToCache(aText, aLength,
172              nsTArray<uint8_t>(aBreakBefore, afterLastTrue - aBreakBefore));
173 }
174