summaryrefslogtreecommitdiffstats
path: root/layout/style/SharedStyleSheetCache.cpp
blob: b0ec65522dab1834dbf55620f43d2fa9236d876c (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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "SharedStyleSheetCache.h"

#include "mozilla/MemoryReporting.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/css/SheetLoadData.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/ServoBindings.h"
#include "nsContentUtils.h"
#include "nsXULPrototypeCache.h"

extern mozilla::LazyLogModule sCssLoaderLog;

#define LOG(...) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))

namespace mozilla {

NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)

MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)

SharedStyleSheetCache::SharedStyleSheetCache() = default;

void SharedStyleSheetCache::Init() { RegisterWeakMemoryReporter(this); }

SharedStyleSheetCache::~SharedStyleSheetCache() {
  UnregisterWeakMemoryReporter(this);
}

void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
                                          StyleSheetLoadData& aData,
                                          nsresult aStatus) {
  // If aStatus is a failure we need to mark this data failed.  We also need to
  // mark any ancestors of a failing data as failed and any sibling of a
  // failing data as failed.  Note that SheetComplete is never called on a
  // SheetLoadData that is the mNext of some other SheetLoadData.
  nsresult cancelledStatus = aStatus;
  if (NS_FAILED(aStatus)) {
    css::Loader::MarkLoadTreeFailed(aData);
  } else {
    cancelledStatus = NS_BINDING_ABORTED;
    css::SheetLoadData* data = &aData;
    do {
      if (data->IsCancelled()) {
        // We only need to mark loads for this loader as cancelled, so as to not
        // fire error events in unrelated documents.
        css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
      }
    } while ((data = data->mNext));
  }

  // 8 is probably big enough for all our common cases.  It's not likely that
  // imports will nest more than 8 deep, and multiple sheets with the same URI
  // are rare.
  AutoTArray<RefPtr<css::SheetLoadData>, 8> datasToNotify;
  LoadCompletedInternal(aCache, aData, datasToNotify);

  // Now it's safe to go ahead and notify observers
  for (RefPtr<css::SheetLoadData>& data : datasToNotify) {
    auto status = data->IsCancelled() ? cancelledStatus : aStatus;
    data->mLoader->NotifyObservers(*data, status);
  }
}

void SharedStyleSheetCache::InsertIfNeeded(css::SheetLoadData& aData) {
  MOZ_ASSERT(aData.mLoader->IsDocumentAssociated(),
             "We only cache document-associated sheets");
  LOG("SharedStyleSheetCache::InsertIfNeeded");
  // If we ever start doing this for failed loads, we'll need to adjust the
  // PostLoadEvent code that thinks anything already complete must have loaded
  // succesfully.
  if (aData.mLoadFailed) {
    LOG("  Load failed, bailing");
    return;
  }

  // If this sheet came from the cache already, there's no need to override
  // anything.
  if (aData.mSheetAlreadyComplete) {
    LOG("  Sheet came from the cache, bailing");
    return;
  }

  if (!aData.mURI) {
    LOG("  Inline or constructable style sheet, bailing");
    // Inline sheet caching happens in Loader::mInlineSheets.
    // Constructable sheets are not worth caching, they're always unique.
    return;
  }

  LOG("  Putting style sheet in shared cache: %s",
      aData.mURI->GetSpecOrDefault().get());
  Insert(aData);
}

void SharedStyleSheetCache::LoadCompletedInternal(
    SharedStyleSheetCache* aCache, css::SheetLoadData& aData,
    nsTArray<RefPtr<css::SheetLoadData>>& aDatasToNotify) {
  if (aCache) {
    aCache->LoadCompleted(aData);
  }

  // Go through and deal with the whole linked list.
  auto* data = &aData;
  do {
    MOZ_RELEASE_ASSERT(!data->mSheetCompleteCalled);
    data->mSheetCompleteCalled = true;

    if (!data->mSheetAlreadyComplete) {
      // If mSheetAlreadyComplete, then the sheet could well be modified between
      // when we posted the async call to SheetComplete and now, since the sheet
      // was page-accessible during that whole time.

      // HasForcedUniqueInner() is okay if the sheet is constructed, because
      // constructed sheets are always unique and they may be set to complete
      // multiple times if their rules are replaced via Replace()
      MOZ_ASSERT(data->mSheet->IsConstructed() ||
                     !data->mSheet->HasForcedUniqueInner(),
                 "should not get a forced unique inner during parsing");
      // Insert the sheet into the tree now the sheet has loaded, but only if
      // the sheet is still relevant, and if this is a top-level sheet.
      const bool needInsertIntoTree = [&] {
        if (!data->mLoader->GetDocument()) {
          // Not a document load, nothing to do.
          return false;
        }
        if (data->IsPreload()) {
          // Preloads are not supposed to be observable.
          return false;
        }
        if (data->mSheet->IsConstructed()) {
          // Constructable sheets are not in the regular stylesheet tree.
          return false;
        }
        if (data->mIsChildSheet) {
          // A child sheet, those will get exposed from the parent, no need to
          // insert them into the tree.
          return false;
        }
        if (data->mOwningNodeBeforeLoadEvent != data->mSheet->GetOwnerNode()) {
          // The sheet was already removed from the tree and is no longer the
          // current sheet of the owning node, we can bail.
          return false;
        }
        return true;
      }();

      if (needInsertIntoTree) {
        data->mLoader->InsertSheetInTree(*data->mSheet);
      }
      data->mSheet->SetComplete();
      data->ScheduleLoadEventIfNeeded();
    } else if (data->mSheet->IsApplicable()) {
      if (dom::Document* doc = data->mLoader->GetDocument()) {
        // We post these events for devtools, even though the applicable state
        // has not actually changed, to make the cache not observable.
        doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
      }
    }

    aDatasToNotify.AppendElement(data);

    NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
                 "Broken pending child count on our parent");

    // If we have a parent, our parent is no longer being parsed, and
    // we are the last pending child, then our load completion
    // completes the parent too.  Note that the parent _can_ still be
    // being parsed (eg if the child (us) failed to open the channel
    // or some such).
    if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
        !data->mParentData->mIsBeingParsed) {
      LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
    }

    data = data->mNext;
  } while (data);

  if (aCache) {
    aCache->InsertIfNeeded(aData);
  }
}

NS_IMETHODIMP
SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
                                      nsISupports* aData, bool aAnonymize) {
  MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
                     KIND_HEAP, UNITS_BYTES,
                     SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
                     "Memory used for SharedStyleSheetCache to share style "
                     "sheets across documents (not to be confused with "
                     "GlobalStyleSheetCache)");
  return NS_OK;
}

void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal,
                                  const nsACString* aBaseDomain) {
  using ContentParent = dom::ContentParent;

  if (XRE_IsParentProcess()) {
    auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing();
    auto baseDomain = aBaseDomain ? Some(nsCString(*aBaseDomain)) : Nothing();

    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
      Unused << cp->SendClearStyleSheetCache(forPrincipal, baseDomain);
    }
  }

  if (sInstance) {
    sInstance->ClearInProcess(aForPrincipal, aBaseDomain);
  }
}

}  // namespace mozilla

#undef LOG