tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SharedStyleSheetCache.cpp (10135B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SharedStyleSheetCache.h"
      8 
      9 #include "mozilla/MemoryReporting.h"
     10 #include "mozilla/ServoBindings.h"
     11 #include "mozilla/StoragePrincipalHelper.h"
     12 #include "mozilla/StyleSheet.h"
     13 #include "mozilla/css/SheetLoadData.h"
     14 #include "mozilla/dom/ContentParent.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "nsContentUtils.h"
     17 #include "nsXULPrototypeCache.h"
     18 
     19 extern mozilla::LazyLogModule sCssLoaderLog;
     20 
     21 #define LOG(...) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
     22 
     23 namespace mozilla {
     24 
     25 NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
     26 
     27 MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
     28 
     29 SharedStyleSheetCache::SharedStyleSheetCache() = default;
     30 
     31 void SharedStyleSheetCache::Init() {
     32  RegisterWeakMemoryReporter(this);
     33  auto ClearCache = [](const char*, void*) { Clear(); };
     34  Preferences::RegisterPrefixCallback(ClearCache, "layout.css.");
     35 }
     36 
     37 SharedStyleSheetCache::~SharedStyleSheetCache() {
     38  UnregisterWeakMemoryReporter(this);
     39 }
     40 
     41 void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
     42                                          StyleSheetLoadData& aData,
     43                                          nsresult aStatus) {
     44  // If aStatus is a failure we need to mark this data failed.  We also need to
     45  // mark any ancestors of a failing data as failed and any sibling of a
     46  // failing data as failed.  Note that SheetComplete is never called on a
     47  // SheetLoadData that is the mNext of some other SheetLoadData.
     48  nsresult cancelledStatus = aStatus;
     49  if (NS_FAILED(aStatus)) {
     50    css::Loader::MarkLoadTreeFailed(aData);
     51  } else {
     52    cancelledStatus = NS_BINDING_ABORTED;
     53    css::SheetLoadData* data = &aData;
     54    do {
     55      if (data->IsCancelled()) {
     56        // We only need to mark loads for this loader as cancelled, so as to not
     57        // fire error events in unrelated documents.
     58        css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
     59      }
     60    } while ((data = data->mNext));
     61  }
     62 
     63  // 8 is probably big enough for all our common cases.  It's not likely that
     64  // imports will nest more than 8 deep, and multiple sheets with the same URI
     65  // are rare.
     66  AutoTArray<RefPtr<css::SheetLoadData>, 8> datasToNotify;
     67  LoadCompletedInternal(aCache, aData, datasToNotify);
     68 
     69  // Now it's safe to go ahead and notify observers
     70  for (RefPtr<css::SheetLoadData>& data : datasToNotify) {
     71    auto status = data->IsCancelled() ? cancelledStatus : aStatus;
     72    data->mLoader->NotifyObservers(*data, status);
     73  }
     74 }
     75 
     76 void SharedStyleSheetCache::InsertIfNeeded(css::SheetLoadData& aData) {
     77  MOZ_ASSERT(aData.mLoader->IsDocumentAssociated(),
     78             "We only cache document-associated sheets");
     79  LOG("SharedStyleSheetCache::InsertIfNeeded");
     80  // If we ever start doing this for failed loads, we'll need to adjust the
     81  // PostLoadEvent code that thinks anything already complete must have loaded
     82  // succesfully.
     83  if (aData.mLoadFailed) {
     84    LOG("  Load failed, bailing");
     85    return;
     86  }
     87 
     88  // If this sheet came from the cache already, there's no need to override
     89  // anything.
     90  if (aData.mSheetAlreadyComplete) {
     91    LOG("  Sheet came from the cache, bailing");
     92    return;
     93  }
     94 
     95  if (!aData.mURI) {
     96    LOG("  Inline or constructable style sheet, bailing");
     97    // Inline sheet caching happens in Loader::mInlineSheets, where we still
     98    // have the input text available.
     99    // Constructable sheets are not worth caching, they're always unique.
    100    return;
    101  }
    102 
    103  LOG("  Putting style sheet in shared cache: %s",
    104      aData.mURI->GetSpecOrDefault().get());
    105  Insert(aData);
    106 }
    107 
    108 void SharedStyleSheetCache::LoadCompletedInternal(
    109    SharedStyleSheetCache* aCache, css::SheetLoadData& aData,
    110    nsTArray<RefPtr<css::SheetLoadData>>& aDatasToNotify) {
    111  if (aCache) {
    112    aCache->LoadCompleted(aData);
    113  }
    114 
    115  // Go through and deal with the whole linked list.
    116  auto* data = &aData;
    117  auto* networkMetadata = aData.GetNetworkMetadata();
    118  do {
    119    MOZ_RELEASE_ASSERT(!data->mSheetCompleteCalled);
    120    data->mSheetCompleteCalled = true;
    121 
    122    if (!data->mNetworkMetadata) {
    123      data->mNetworkMetadata = networkMetadata;
    124    }
    125 
    126    if (!data->mSheetAlreadyComplete) {
    127      // If mSheetAlreadyComplete, then the sheet could well be modified between
    128      // when we posted the async call to SheetComplete and now, since the sheet
    129      // was page-accessible during that whole time.
    130 
    131      // HasForcedUniqueInner() is okay if the sheet is constructed, because
    132      // constructed sheets are always unique and they may be set to complete
    133      // multiple times if their rules are replaced via Replace()
    134      MOZ_ASSERT(data->mSheet->IsConstructed() ||
    135                     !data->mSheet->HasForcedUniqueInner(),
    136                 "should not get a forced unique inner during parsing");
    137      // Insert the sheet into the tree now the sheet has loaded, but only if
    138      // the sheet is still relevant, and if this is a top-level sheet.
    139      const bool needInsertIntoTree = [&] {
    140        if (!data->mLoader->GetDocument()) {
    141          // Not a document load, nothing to do.
    142          return false;
    143        }
    144        if (data->IsPreload()) {
    145          // Preloads are not supposed to be observable.
    146          return false;
    147        }
    148        if (data->mSheet->IsConstructed()) {
    149          // Constructable sheets are not in the regular stylesheet tree.
    150          return false;
    151        }
    152        if (data->mIsChildSheet) {
    153          // A child sheet, those will get exposed from the parent, no need to
    154          // insert them into the tree.
    155          return false;
    156        }
    157        if (data->mHadOwnerNode != !!data->mSheet->GetOwnerNode()) {
    158          // The sheet was already removed from the tree and is no longer the
    159          // current sheet of the owning node, we can bail.
    160          return false;
    161        }
    162        return true;
    163      }();
    164 
    165      if (needInsertIntoTree) {
    166        data->mLoader->InsertSheetInTree(*data->mSheet);
    167      }
    168      data->mSheet->SetComplete();
    169    } else if (data->mSheet->IsApplicable()) {
    170      if (dom::Document* doc = data->mLoader->GetDocument()) {
    171        // We post these events for devtools, even though the applicable state
    172        // has not actually changed, to make the cache not observable.
    173        doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
    174      }
    175    }
    176    aDatasToNotify.AppendElement(data);
    177 
    178    NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
    179                 "Broken pending child count on our parent");
    180 
    181    // If we have a parent, our parent is no longer being parsed, and
    182    // we are the last pending child, then our load completion
    183    // completes the parent too.  Note that the parent _can_ still be
    184    // being parsed (eg if the child (us) failed to open the channel
    185    // or some such).
    186    if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
    187        !data->mParentData->mIsBeingParsed) {
    188      LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
    189    }
    190 
    191    data = data->mNext;
    192  } while (data);
    193 
    194  if (aCache) {
    195    aCache->InsertIfNeeded(aData);
    196  }
    197 }
    198 
    199 size_t SharedStyleSheetCache::SizeOfIncludingThis(
    200    MallocSizeOf aMallocSizeOf) const {
    201  size_t n = aMallocSizeOf(this);
    202  n += Base::SizeOfExcludingThis(aMallocSizeOf);
    203  n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
    204  for (const auto& sheetMap : mInlineSheets) {
    205    for (const auto& entry : sheetMap.GetData()) {
    206      n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
    207      n += entry.GetData().ShallowSizeOfExcludingThis(aMallocSizeOf);
    208      for (const auto& candidate : entry.GetData()) {
    209        n += candidate.mSheet->SizeOfIncludingThis(aMallocSizeOf);
    210      }
    211    }
    212  }
    213  return n;
    214 }
    215 
    216 NS_IMETHODIMP
    217 SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
    218                                      nsISupports* aData, bool aAnonymize) {
    219  MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
    220                     KIND_HEAP, UNITS_BYTES,
    221                     SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
    222                     "Memory used for SharedStyleSheetCache to share style "
    223                     "sheets across documents (not to be confused with "
    224                     "GlobalStyleSheetCache)");
    225  return NS_OK;
    226 }
    227 
    228 void SharedStyleSheetCache::ClearInProcess(
    229    const Maybe<bool>& aChrome, const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    230    const Maybe<nsCString>& aSchemelessSite,
    231    const Maybe<OriginAttributesPattern>& aPattern,
    232    const Maybe<nsCString>& aURL) {
    233  Base::ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern, aURL);
    234  if (!aChrome && !aPrincipal && !aSchemelessSite && !aURL) {
    235    mInlineSheets.Clear();
    236  }
    237  if (aURL) {
    238    // Inline sheets don't have a URL.
    239    return;
    240  }
    241 
    242  for (auto iter = mInlineSheets.Iter(); !iter.Done(); iter.Next()) {
    243    if (SharedSubResourceCacheUtils::ShouldClearEntry(
    244            nullptr, iter.Key(), iter.Key(), aChrome, aPrincipal,
    245            aSchemelessSite, aPattern, aURL)) {
    246      iter.Remove();
    247    }
    248  }
    249 }
    250 
    251 void SharedStyleSheetCache::Clear(
    252    const Maybe<bool>& aChrome, const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    253    const Maybe<nsCString>& aSchemelessSite,
    254    const Maybe<OriginAttributesPattern>& aPattern,
    255    const Maybe<nsCString>& aURL) {
    256  using ContentParent = dom::ContentParent;
    257 
    258  if (XRE_IsParentProcess()) {
    259    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
    260      (void)cp->SendClearStyleSheetCache(aChrome, aPrincipal, aSchemelessSite,
    261                                         aPattern, aURL);
    262    }
    263  }
    264 
    265  if (sSingleton) {
    266    sSingleton->ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern,
    267                               aURL);
    268  }
    269 }
    270 
    271 }  // namespace mozilla
    272 
    273 #undef LOG