summaryrefslogtreecommitdiffstats
path: root/intl/lwbrk/nsComplexBreaker.cpp
blob: 0c0a5e45b6a705ea779058fd978a8bdbd53bc007 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsComplexBreaker.h"

#include <algorithm>

#include "MainThreadUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Services.h"
#include "mozilla/UniquePtr.h"
#include "nsTHashMap.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

using namespace mozilla;

using CacheMap = nsTHashMap<nsString, nsTArray<uint8_t>>;

static UniquePtr<CacheMap> sBreakCache;

// The underlying hash table extends capacity, when it hits .75 full and uses
// powers of 2 for sizing. This cache limit will hopefully mean most pages fit
// within the cache, while keeping it to a reasonable size. Also by holding the
// previous cache even if pages are bigger than the cache the most commonly used
// should still remain fast.
static const int kCacheLimit = 3072;

static UniquePtr<CacheMap> sOldBreakCache;

// Simple runnable to delete caches off the main thread.
class CacheDeleter final : public Runnable {
 public:
  explicit CacheDeleter(UniquePtr<CacheMap> aCacheToDelete)
      : Runnable("ComplexBreaker CacheDeleter"),
        mCacheToDelete(std::move(aCacheToDelete)) {}

  NS_IMETHOD Run() override {
    MOZ_ASSERT(!NS_IsMainThread());
    mCacheToDelete = nullptr;
    return NS_OK;
  }

 private:
  UniquePtr<CacheMap> mCacheToDelete;
};

class ComplexBreakObserver final : public nsIObserver {
  ~ComplexBreakObserver() = default;

 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
};

NS_IMPL_ISUPPORTS(ComplexBreakObserver, nsIObserver)

NS_IMETHODIMP ComplexBreakObserver::Observe(nsISupports* aSubject,
                                            const char* aTopic,
                                            const char16_t* aData) {
  MOZ_ASSERT(NS_IsMainThread());

  if (strcmp(aTopic, "memory-pressure") == 0) {
    if (sOldBreakCache) {
      // We have an old cache, so delete that one first.
      NS_DispatchBackgroundTask(
          MakeAndAddRef<CacheDeleter>(std::move(sOldBreakCache)));
    } else if (sBreakCache) {
      NS_DispatchBackgroundTask(
          MakeAndAddRef<CacheDeleter>(std::move(sBreakCache)));
    }
  }

  return NS_OK;
}

void ComplexBreaker::Initialize() {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    obs->AddObserver(new ComplexBreakObserver(), "memory-pressure", false);
  }
}

void ComplexBreaker::Shutdown() {
  MOZ_ASSERT(NS_IsMainThread());

  sBreakCache = nullptr;
  sOldBreakCache = nullptr;
}

static void AddToCache(const char16_t* aText, uint32_t aLength,
                       nsTArray<uint8_t> aBreakBefore) {
  if (NS_WARN_IF(!sBreakCache->InsertOrUpdate(
          nsString(aText, aLength), std::move(aBreakBefore), fallible))) {
    return;
  }

  if (sBreakCache->Count() <= kCacheLimit) {
    return;
  }

  if (sOldBreakCache) {
    NS_DispatchBackgroundTask(
        MakeAndAddRef<CacheDeleter>(std::move(sOldBreakCache)));
  }

  sOldBreakCache = std::move(sBreakCache);
}

static void CopyAndFill(const nsTArray<uint8_t>& aCachedBreakBefore,
                        uint8_t* aBreakBefore, uint8_t* aEndBreakBefore) {
  auto* startFill = std::copy(aCachedBreakBefore.begin(),
                              aCachedBreakBefore.end(), aBreakBefore);
  std::fill(startFill, aEndBreakBefore, false);
}

void ComplexBreaker::GetBreaks(const char16_t* aText, uint32_t aLength,
                               uint8_t* aBreakBefore) {
  // It is believed that this is only called on the main thread, so we don't
  // need to lock the caching structures. A diagnostic assert is used in case
  // our tests don't exercise all code paths.
  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(aText, "aText shouldn't be null");
  MOZ_ASSERT(aLength, "aLength shouldn't be zero");
  MOZ_ASSERT(aBreakBefore, "aBreakBefore shouldn't be null");

  // If we have a cache then check it, if not then create it.
  if (sBreakCache) {
    if (auto entry =
            sBreakCache->Lookup(nsDependentSubstring(aText, aLength))) {
      auto& breakBefore = entry.Data();
      CopyAndFill(breakBefore, aBreakBefore, aBreakBefore + aLength);
      return;
    }
  } else {
    sBreakCache = MakeUnique<CacheMap>();
  }

  // We keep the previous cache when we hit our limit, so that the most recently
  // used fragments remain fast.
  if (sOldBreakCache) {
    auto breakBefore =
        sOldBreakCache->Extract(nsDependentSubstring(aText, aLength));
    if (breakBefore) {
      CopyAndFill(*breakBefore, aBreakBefore, aBreakBefore + aLength);
      // Move the entry to the latest cache.
      AddToCache(aText, aLength, std::move(*breakBefore));
      return;
    }
  }

  NS_GetComplexLineBreaks(aText, aLength, aBreakBefore);

  // As a very simple memory saving measure we trim off trailing elements that
  // are false before caching.
  auto* afterLastTrue = aBreakBefore + aLength;
  while (!*(afterLastTrue - 1)) {
    if (--afterLastTrue == aBreakBefore) {
      break;
    }
  }

  AddToCache(aText, aLength,
             nsTArray<uint8_t>(aBreakBefore, afterLastTrue - aBreakBefore));
}