tor-browser

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

nsSHEntryShared.cpp (11817B)


      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 "nsSHEntryShared.h"
      8 
      9 #include "nsArray.h"
     10 #include "nsContentUtils.h"
     11 #include "nsDocShellEditorData.h"
     12 #include "nsIDocumentViewer.h"
     13 #include "nsISHistory.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "nsILayoutHistoryState.h"
     16 #include "nsIWebNavigation.h"
     17 #include "nsSHistory.h"
     18 #include "nsThreadUtils.h"
     19 #include "nsFrameLoader.h"
     20 #include "mozilla/Preferences.h"
     21 
     22 namespace dom = mozilla::dom;
     23 
     24 namespace {
     25 uint64_t gSHEntrySharedID = 0;
     26 nsTHashMap<nsUint64HashKey, mozilla::dom::SHEntrySharedParentState*>*
     27    sIdToSharedState = nullptr;
     28 }  // namespace
     29 
     30 namespace mozilla {
     31 namespace dom {
     32 
     33 /* static */
     34 uint64_t SHEntrySharedState::GenerateId() {
     35  return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID);
     36 }
     37 
     38 /* static */
     39 SHEntrySharedParentState* SHEntrySharedParentState::Lookup(uint64_t aId) {
     40  MOZ_ASSERT(aId != 0);
     41 
     42  return sIdToSharedState ? sIdToSharedState->Get(aId) : nullptr;
     43 }
     44 
     45 static void AddSHEntrySharedParentState(
     46    SHEntrySharedParentState* aSharedState) {
     47  MOZ_ASSERT(aSharedState->mId != 0);
     48 
     49  if (!sIdToSharedState) {
     50    sIdToSharedState =
     51        new nsTHashMap<nsUint64HashKey, SHEntrySharedParentState*>();
     52  }
     53  sIdToSharedState->InsertOrUpdate(aSharedState->mId, aSharedState);
     54 }
     55 
     56 SHEntrySharedParentState::SHEntrySharedParentState() {
     57  AddSHEntrySharedParentState(this);
     58 }
     59 
     60 SHEntrySharedParentState::SHEntrySharedParentState(
     61    nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
     62    nsIPrincipal* aPartitionedPrincipalToInherit,
     63    nsIPolicyContainer* aPolicyContainer, const nsACString& aContentType)
     64    : SHEntrySharedState(aTriggeringPrincipal, aPrincipalToInherit,
     65                         aPartitionedPrincipalToInherit, aPolicyContainer,
     66                         aContentType) {
     67  AddSHEntrySharedParentState(this);
     68 }
     69 
     70 SHEntrySharedParentState::~SHEntrySharedParentState() {
     71  MOZ_ASSERT(mId != 0);
     72 
     73  RefPtr<nsFrameLoader> loader = mFrameLoader;
     74  SetFrameLoader(nullptr);
     75  if (loader) {
     76    if (NS_FAILED(NS_DispatchToCurrentThread(NS_NewRunnableFunction(
     77            "SHEntrySharedParentState::~SHEntrySharedParentState",
     78            [loader]() -> void { loader->AsyncDestroy(); })))) {
     79      // Trigger AsyncDestroy immediately during shutdown.
     80      loader->AsyncDestroy();
     81    }
     82  }
     83 
     84  sIdToSharedState->Remove(mId);
     85  if (sIdToSharedState->IsEmpty()) {
     86    delete sIdToSharedState;
     87    sIdToSharedState = nullptr;
     88  }
     89 }
     90 
     91 void SHEntrySharedParentState::ChangeId(uint64_t aId) {
     92  MOZ_ASSERT(aId != 0);
     93 
     94  sIdToSharedState->Remove(mId);
     95  mId = aId;
     96  sIdToSharedState->InsertOrUpdate(mId, this);
     97 }
     98 
     99 void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) {
    100  mDocShellID = aEntry->mDocShellID;
    101  mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
    102  mPrincipalToInherit = aEntry->mPrincipalToInherit;
    103  mPartitionedPrincipalToInherit = aEntry->mPartitionedPrincipalToInherit;
    104  mPolicyContainer = aEntry->mPolicyContainer;
    105  mSaveLayoutState = aEntry->mSaveLayoutState;
    106  mContentType.Assign(aEntry->mContentType);
    107  mIsFrameNavigation = aEntry->mIsFrameNavigation;
    108  mSticky = aEntry->mSticky;
    109  mDynamicallyCreated = aEntry->mDynamicallyCreated;
    110  mCacheKey = aEntry->mCacheKey;
    111  mLastTouched = aEntry->mLastTouched;
    112 }
    113 
    114 void dom::SHEntrySharedParentState::NotifyListenersDocumentViewerEvicted() {
    115  if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
    116    RefPtr<nsSHistory> nsshistory = static_cast<nsSHistory*>(shistory.get());
    117    nsshistory->NotifyListenersDocumentViewerEvicted(1);
    118  }
    119 }
    120 
    121 void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState* aEntry) {
    122  mChildShells.AppendObjects(aEntry->mChildShells);
    123 }
    124 
    125 void SHEntrySharedParentState::SetFrameLoader(nsFrameLoader* aFrameLoader) {
    126  // If expiration tracker is removing this object, IsTracked() returns false.
    127  if (GetExpirationState()->IsTracked() && mFrameLoader) {
    128    if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
    129      shistory->RemoveFromExpirationTracker(this);
    130    }
    131  }
    132 
    133  mFrameLoader = aFrameLoader;
    134 
    135  if (mFrameLoader) {
    136    if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
    137      shistory->AddToExpirationTracker(this);
    138    }
    139  }
    140 }
    141 
    142 nsFrameLoader* SHEntrySharedParentState::GetFrameLoader() {
    143  return mFrameLoader;
    144 }
    145 
    146 }  // namespace dom
    147 }  // namespace mozilla
    148 
    149 void nsSHEntryShared::Shutdown() {}
    150 
    151 nsSHEntryShared::~nsSHEntryShared() {
    152  // The destruction can be caused by either the entry is removed from session
    153  // history and no one holds the reference, or the whole session history is on
    154  // destruction. We want to ensure that we invoke
    155  // shistory->RemoveFromExpirationTracker for the former case.
    156  RemoveFromExpirationTracker();
    157 
    158  // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
    159  // there couldn't be any SHEntry holding this shared entry, and we noticed
    160  // that calling RemoveDynEntriesForBFCacheEntry in the middle of
    161  // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
    162  // before RemoveFromBFCacheSync.
    163  mSHistory = nullptr;
    164  if (mDocumentViewer) {
    165    RemoveFromBFCacheSync();
    166  }
    167 }
    168 
    169 NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
    170 NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
    171 NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
    172 
    173 already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate() {
    174  RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
    175 
    176  newEntry->dom::SHEntrySharedParentState::CopyFrom(this);
    177  newEntry->dom::SHEntrySharedChildState::CopyFrom(this);
    178 
    179  return newEntry.forget();
    180 }
    181 
    182 void nsSHEntryShared::RemoveFromExpirationTracker() {
    183  nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
    184  if (shistory && GetExpirationState()->IsTracked()) {
    185    shistory->RemoveFromExpirationTracker(this);
    186  }
    187 }
    188 
    189 void nsSHEntryShared::SyncPresentationState() {
    190  if (mDocumentViewer && mWindowState) {
    191    // If we have a content viewer and a window state, we should be ok.
    192    return;
    193  }
    194 
    195  DropPresentationState();
    196 }
    197 
    198 void nsSHEntryShared::DropPresentationState() {
    199  RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
    200 
    201  if (mDocument) {
    202    mDocument->SetBFCacheEntry(nullptr);
    203    mDocument->RemoveMutationObserver(this);
    204    mDocument = nullptr;
    205  }
    206  if (mDocumentViewer) {
    207    mDocumentViewer->ClearHistoryEntry();
    208  }
    209 
    210  RemoveFromExpirationTracker();
    211  mDocumentViewer = nullptr;
    212  mSticky = true;
    213  mWindowState = nullptr;
    214  mViewerBounds.SetRect(0, 0, 0, 0);
    215  mChildShells.Clear();
    216  mRefreshURIList = nullptr;
    217  mEditorData = nullptr;
    218 }
    219 
    220 nsresult nsSHEntryShared::SetDocumentViewer(nsIDocumentViewer* aViewer) {
    221  MOZ_ASSERT(!aViewer || !mDocumentViewer,
    222             "SHEntryShared already contains viewer");
    223 
    224  if (mDocumentViewer || !aViewer) {
    225    DropPresentationState();
    226  }
    227 
    228  // If we're setting mDocumentViewer to null, state should already be cleared
    229  // in the DropPresentationState() call above; If we're setting it to a
    230  // non-null content viewer, the entry shouldn't have been tracked either.
    231  MOZ_ASSERT(!GetExpirationState()->IsTracked());
    232  mDocumentViewer = aViewer;
    233 
    234  if (mDocumentViewer) {
    235    // mSHistory is only set for root entries, but in general bfcache only
    236    // applies to root entries as well. BFCache for subframe navigation has been
    237    // disabled since 2005 in bug 304860.
    238    if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
    239      shistory->AddToExpirationTracker(this);
    240    }
    241 
    242    // Store observed document in strong pointer in case it is removed from
    243    // the documentviewer
    244    mDocument = mDocumentViewer->GetDocument();
    245    if (mDocument) {
    246      mDocument->SetBFCacheEntry(this);
    247      mDocument->AddMutationObserver(this);
    248    }
    249  }
    250 
    251  return NS_OK;
    252 }
    253 
    254 nsresult nsSHEntryShared::RemoveFromBFCacheSync() {
    255  MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!");
    256 
    257  // The call to DropPresentationState could drop the last reference, so hold
    258  // |this| until RemoveDynEntriesForBFCacheEntry finishes.
    259  RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
    260 
    261  // DropPresentationState would clear mDocumentViewer.
    262  nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
    263  DropPresentationState();
    264 
    265  if (viewer) {
    266    viewer->Destroy();
    267  }
    268 
    269  // Now that we've dropped the viewer, we have to clear associated dynamic
    270  // subframe entries.
    271  nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
    272  if (shistory) {
    273    shistory->RemoveDynEntriesForBFCacheEntry(this);
    274  }
    275 
    276  return NS_OK;
    277 }
    278 
    279 nsresult nsSHEntryShared::RemoveFromBFCacheAsync() {
    280  MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!");
    281 
    282  // Check it again to play safe in release builds.
    283  if (!mDocument) {
    284    return NS_ERROR_UNEXPECTED;
    285  }
    286 
    287  // DropPresentationState would clear mDocumentViewer & mDocument. Capture and
    288  // release the references asynchronously so that the document doesn't get
    289  // nuked mid-mutation.
    290  nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
    291  RefPtr<dom::Document> document = mDocument;
    292  RefPtr<nsSHEntryShared> self = this;
    293  nsresult rv = mDocument->Dispatch(NS_NewRunnableFunction(
    294      "nsSHEntryShared::RemoveFromBFCacheAsync", [self, viewer, document]() {
    295        if (viewer) {
    296          viewer->Destroy();
    297        }
    298 
    299        nsCOMPtr<nsISHistory> shistory = do_QueryReferent(self->mSHistory);
    300        if (shistory) {
    301          shistory->RemoveDynEntriesForBFCacheEntry(self);
    302        }
    303      }));
    304 
    305  if (NS_FAILED(rv)) {
    306    NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
    307  } else {
    308    // Drop presentation. Only do this if we succeeded in posting the event
    309    // since otherwise the document could be torn down mid-mutation, causing
    310    // crashes.
    311    DropPresentationState();
    312  }
    313 
    314  return NS_OK;
    315 }
    316 
    317 // Don't evict a page from bfcache for attribute mutations on NAC subtrees like
    318 // scrollbars.
    319 static bool IgnoreMutationForBfCache(const nsINode& aNode) {
    320  for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
    321    if (!node->ChromeOnlyAccess()) {
    322      break;
    323    }
    324    // Make sure we find a scrollbar in the ancestor chain, to be safe.
    325    if (node->IsXULElement(nsGkAtoms::scrollbar)) {
    326      return true;
    327    }
    328  }
    329  return false;
    330 }
    331 
    332 void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent,
    333                                           const CharacterDataChangeInfo&) {
    334  if (!IgnoreMutationForBfCache(*aContent)) {
    335    RemoveFromBFCacheAsync();
    336  }
    337 }
    338 
    339 void nsSHEntryShared::AttributeChanged(dom::Element* aElement,
    340                                       int32_t aNameSpaceID, nsAtom* aAttribute,
    341                                       AttrModType,
    342                                       const nsAttrValue* aOldValue) {
    343  if (!IgnoreMutationForBfCache(*aElement)) {
    344    RemoveFromBFCacheAsync();
    345  }
    346 }
    347 
    348 void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent,
    349                                      const ContentAppendInfo&) {
    350  if (!IgnoreMutationForBfCache(*aFirstNewContent)) {
    351    RemoveFromBFCacheAsync();
    352  }
    353 }
    354 
    355 void nsSHEntryShared::ContentInserted(nsIContent* aChild,
    356                                      const ContentInsertInfo&) {
    357  if (!IgnoreMutationForBfCache(*aChild)) {
    358    RemoveFromBFCacheAsync();
    359  }
    360 }
    361 
    362 void nsSHEntryShared::ContentWillBeRemoved(nsIContent* aChild,
    363                                           const ContentRemoveInfo&) {
    364  if (!IgnoreMutationForBfCache(*aChild)) {
    365    RemoveFromBFCacheAsync();
    366  }
    367 }