tor-browser

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

BrowsingContextWebProgress.cpp (17873B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "BrowsingContextWebProgress.h"
      6 #include "mozilla/AlreadyAddRefed.h"
      7 #include "mozilla/BounceTrackingState.h"
      8 #include "mozilla/dom/CanonicalBrowsingContext.h"
      9 #include "mozilla/ErrorNames.h"
     10 #include "mozilla/Logging.h"
     11 #include "nsCOMPtr.h"
     12 #include "nsIWebProgressListener.h"
     13 #include "nsString.h"
     14 #include "nsPrintfCString.h"
     15 #include "nsIChannel.h"
     16 #include "xptinfo.h"
     17 #include "mozilla/RefPtr.h"
     18 
     19 mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress");
     20 
     21 namespace mozilla {
     22 namespace dom {
     23 
     24 static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext);
     25 static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress);
     26 static nsCString DescribeRequest(nsIRequest* aRequest);
     27 static nsCString DescribeWebProgressFlags(uint32_t aFlags,
     28                                          const nsACString& aPrefix);
     29 static nsCString DescribeError(nsresult aError);
     30 
     31 NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext)
     32 
     33 NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress)
     34 NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress)
     35 
     36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress)
     37  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress)
     38  NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
     39  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
     40 NS_INTERFACE_MAP_END
     41 
     42 BrowsingContextWebProgress::BrowsingContextWebProgress(
     43    CanonicalBrowsingContext* aBrowsingContext)
     44    : mCurrentBrowsingContext(aBrowsingContext) {}
     45 
     46 BrowsingContextWebProgress::~BrowsingContextWebProgress() = default;
     47 
     48 NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener(
     49    nsIWebProgressListener* aListener, uint32_t aNotifyMask) {
     50  nsWeakPtr listener = do_GetWeakReference(aListener);
     51  if (!listener) {
     52    return NS_ERROR_INVALID_ARG;
     53  }
     54 
     55  if (mListenerInfoList.Contains(listener)) {
     56    // The listener is already registered!
     57    return NS_ERROR_FAILURE;
     58  }
     59 
     60  mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask));
     61  return NS_OK;
     62 }
     63 
     64 NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener(
     65    nsIWebProgressListener* aListener) {
     66  nsWeakPtr listener = do_GetWeakReference(aListener);
     67  if (!listener) {
     68    return NS_ERROR_INVALID_ARG;
     69  }
     70 
     71  return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
     72 }
     73 
     74 NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM(
     75    BrowsingContext** aBrowsingContext) {
     76  NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext);
     77  return NS_OK;
     78 }
     79 
     80 BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() {
     81  return mCurrentBrowsingContext;
     82 }
     83 
     84 NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow(
     85    mozIDOMWindowProxy** aDOMWindow) {
     86  return NS_ERROR_NOT_AVAILABLE;
     87 }
     88 
     89 NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) {
     90  *aIsTopLevel = mCurrentBrowsingContext->IsTop();
     91  return NS_OK;
     92 }
     93 
     94 NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument(
     95    bool* aIsLoadingDocument) {
     96  *aIsLoadingDocument = mIsLoadingDocument;
     97  return NS_OK;
     98 }
     99 
    100 NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) {
    101  *aLoadType = mLoadType;
    102  return NS_OK;
    103 }
    104 
    105 NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) {
    106  return NS_ERROR_NOT_IMPLEMENTED;
    107 }
    108 
    109 NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) {
    110  return NS_ERROR_NOT_IMPLEMENTED;
    111 }
    112 
    113 void BrowsingContextWebProgress::UpdateAndNotifyListeners(
    114    uint32_t aFlag,
    115    const std::function<void(nsIWebProgressListener*)>& aCallback) {
    116  RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this;
    117 
    118  ListenerArray::ForwardIterator iter(mListenerInfoList);
    119  while (iter.HasMore()) {
    120    ListenerInfo& info = iter.GetNext();
    121    if (!(info.mNotifyMask & aFlag)) {
    122      continue;
    123    }
    124 
    125    nsCOMPtr<nsIWebProgressListener> listener =
    126        do_QueryReferent(info.mWeakListener);
    127    if (!listener) {
    128      mListenerInfoList.RemoveElement(info);
    129      continue;
    130    }
    131 
    132    aCallback(listener);
    133  }
    134 
    135  mListenerInfoList.Compact();
    136 
    137  // Notify the parent BrowsingContextWebProgress of the event to continue
    138  // propagating.
    139  auto* parent = mCurrentBrowsingContext->GetParent();
    140  if (parent && parent->GetWebProgress()) {
    141    aCallback(parent->GetWebProgress());
    142  }
    143 }
    144 
    145 already_AddRefed<nsIWebProgress> BrowsingContextWebProgress::ResolveWebProgress(
    146    nsIWebProgress* aWebProgress) {
    147  // If we are receiving this notifcation directly from the docshell,
    148  // `nsIWebProgress` will be the docshell, instead of the
    149  // BrowsingContextWebProgress object.
    150  nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress);
    151  if (docShell && docShell->GetBrowsingContext()) {
    152    if (RefPtr<BrowsingContextWebProgress> progress =
    153            docShell->GetBrowsingContext()->Canonical()->GetWebProgress()) {
    154      aWebProgress->GetLoadType(&progress->mLoadType);
    155      return progress.forget();
    156    }
    157  }
    158 
    159  return do_AddRef(aWebProgress);
    160 }
    161 
    162 void BrowsingContextWebProgress::ContextDiscarded() {
    163  if (mBounceTrackingState) {
    164    mBounceTrackingState->OnBrowsingContextDiscarded();
    165  }
    166 
    167  if (!mIsLoadingDocument) {
    168    return;
    169  }
    170 
    171  // If our BrowsingContext is being discarded while still loading a document,
    172  // fire a synthetic `STATE_STOP` to end the ongoing load.
    173  MOZ_LOG(gBCWebProgressLog, LogLevel::Info,
    174          ("Discarded while loading %s",
    175           DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    176 
    177  // This matches what nsDocLoader::doStopDocumentLoad does, except we don't
    178  // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`,
    179  // nsBrowserStatusFilter would filter it out before it gets to the parent
    180  // process.
    181  nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest;
    182  OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK,
    183                NS_ERROR_ABORT);
    184 }
    185 
    186 void BrowsingContextWebProgress::ContextReplaced(
    187    CanonicalBrowsingContext* aNewContext) {
    188  mCurrentBrowsingContext = aNewContext;
    189 }
    190 
    191 already_AddRefed<BounceTrackingState>
    192 BrowsingContextWebProgress::GetBounceTrackingState() {
    193  if (!mBounceTrackingState) {
    194    nsresult rv = NS_OK;
    195    mBounceTrackingState = BounceTrackingState::GetOrCreate(this, rv);
    196    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    197                         "Failed to get BounceTrackingState.");
    198  }
    199  return do_AddRef(mBounceTrackingState);
    200 }
    201 
    202 void BrowsingContextWebProgress::DropBounceTrackingState() {
    203  mBounceTrackingState = nullptr;
    204 }
    205 
    206 ////////////////////////////////////////////////////////////////////////////////
    207 // nsIWebProgressListener
    208 
    209 NS_IMETHODIMP
    210 BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress,
    211                                          nsIRequest* aRequest,
    212                                          uint32_t aStateFlags,
    213                                          nsresult aStatus) {
    214  MOZ_LOG(
    215      gBCWebProgressLog, LogLevel::Info,
    216      ("OnStateChange(%s, %s, %s, %s) on %s",
    217       DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
    218       DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(),
    219       DescribeError(aStatus).get(),
    220       DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    221 
    222  nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress);
    223 
    224  // Track `mIsLoadingDocument` based on the notifications we've received so far
    225  // if the nsIWebProgress being targeted is this one.
    226  if (webProgress == this) {
    227    constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START |
    228                                    nsIWebProgressListener::STATE_IS_DOCUMENT |
    229                                    nsIWebProgressListener::STATE_IS_REQUEST |
    230                                    nsIWebProgressListener::STATE_IS_WINDOW |
    231                                    nsIWebProgressListener::STATE_IS_NETWORK;
    232    constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP |
    233                                   nsIWebProgressListener::STATE_IS_WINDOW;
    234    constexpr uint32_t redirectFlags =
    235        nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
    236    if ((aStateFlags & startFlags) == startFlags) {
    237      if (mIsLoadingDocument) {
    238        // We received a duplicate `STATE_START` notification, silence this
    239        // notification until we receive the matching `STATE_STOP` to not fire
    240        // duplicate `STATE_START` notifications into frontend on process
    241        // switches.
    242        return NS_OK;
    243      }
    244      mIsLoadingDocument = true;
    245 
    246      // Record the request we started the load with, so we can emit a synthetic
    247      // `STATE_STOP` notification if the BrowsingContext is discarded before
    248      // the notification arrives.
    249      mLoadingDocumentRequest = aRequest;
    250    } else if ((aStateFlags & stopFlags) == stopFlags) {
    251      // We've received a `STATE_STOP` notification targeting this web progress,
    252      // clear our loading document flag.
    253      mIsLoadingDocument = false;
    254      mLoadingDocumentRequest = nullptr;
    255    } else if (mIsLoadingDocument &&
    256               (aStateFlags & redirectFlags) == redirectFlags) {
    257      // If we see a redirected document load, update the loading request which
    258      // we'll emit the synthetic STATE_STOP notification with.
    259      mLoadingDocumentRequest = aRequest;
    260    }
    261  }
    262 
    263  // Remove the STATE_IS_NETWORK bit if necessary.
    264  //
    265  // The rule is to remove this bit if the notification has been passed up from
    266  // a child WebProgress, and the current WebProgress is already active.
    267  //
    268  // NOTE: Keep this in-sync with the logic in nsDocLoader::DoFireOnStateChange.
    269  if (webProgress != this && mIsLoadingDocument &&
    270      aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) {
    271    aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK;
    272  }
    273 
    274  UpdateAndNotifyListeners(
    275      ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
    276      [&](nsIWebProgressListener* listener) {
    277        listener->OnStateChange(webProgress, aRequest, aStateFlags, aStatus);
    278      });
    279  return NS_OK;
    280 }
    281 
    282 NS_IMETHODIMP
    283 BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress,
    284                                             nsIRequest* aRequest,
    285                                             int32_t aCurSelfProgress,
    286                                             int32_t aMaxSelfProgress,
    287                                             int32_t aCurTotalProgress,
    288                                             int32_t aMaxTotalProgress) {
    289  MOZ_LOG(
    290      gBCWebProgressLog, LogLevel::Info,
    291      ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s",
    292       DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
    293       aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress,
    294       DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    295 
    296  nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress);
    297 
    298  UpdateAndNotifyListeners(
    299      nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) {
    300        listener->OnProgressChange(webProgress, aRequest, aCurSelfProgress,
    301                                   aMaxSelfProgress, aCurTotalProgress,
    302                                   aMaxTotalProgress);
    303      });
    304  return NS_OK;
    305 }
    306 
    307 NS_IMETHODIMP
    308 BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress,
    309                                             nsIRequest* aRequest,
    310                                             nsIURI* aLocation,
    311                                             uint32_t aFlags) {
    312  MOZ_LOG(
    313      gBCWebProgressLog, LogLevel::Info,
    314      ("OnLocationChange(%s, %s, %s, %s) on %s",
    315       DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
    316       aLocation ? aLocation->GetSpecOrDefault().get() : "<null>",
    317       DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(),
    318       DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    319 
    320  nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress);
    321 
    322  UpdateAndNotifyListeners(
    323      nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) {
    324        listener->OnLocationChange(webProgress, aRequest, aLocation, aFlags);
    325      });
    326  return NS_OK;
    327 }
    328 
    329 NS_IMETHODIMP
    330 BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress,
    331                                           nsIRequest* aRequest,
    332                                           nsresult aStatus,
    333                                           const char16_t* aMessage) {
    334  MOZ_LOG(
    335      gBCWebProgressLog, LogLevel::Info,
    336      ("OnStatusChange(%s, %s, %s, \"%s\") on %s",
    337       DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
    338       DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(),
    339       DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    340 
    341  nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress);
    342 
    343  UpdateAndNotifyListeners(
    344      nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) {
    345        listener->OnStatusChange(webProgress, aRequest, aStatus, aMessage);
    346      });
    347  return NS_OK;
    348 }
    349 
    350 NS_IMETHODIMP
    351 BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress,
    352                                             nsIRequest* aRequest,
    353                                             uint32_t aState) {
    354  MOZ_LOG(
    355      gBCWebProgressLog, LogLevel::Info,
    356      ("OnSecurityChange(%s, %s, %x) on %s",
    357       DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
    358       aState, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    359 
    360  nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress);
    361 
    362  UpdateAndNotifyListeners(
    363      nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) {
    364        listener->OnSecurityChange(webProgress, aRequest, aState);
    365      });
    366  return NS_OK;
    367 }
    368 
    369 NS_IMETHODIMP
    370 BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
    371                                                   nsIRequest* aRequest,
    372                                                   uint32_t aEvent) {
    373  MOZ_LOG(
    374      gBCWebProgressLog, LogLevel::Info,
    375      ("OnContentBlockingEvent(%s, %s, %x) on %s",
    376       DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
    377       aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
    378 
    379  nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress);
    380 
    381  UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
    382                           [&](nsIWebProgressListener* listener) {
    383                             listener->OnContentBlockingEvent(webProgress,
    384                                                              aRequest, aEvent);
    385                           });
    386  return NS_OK;
    387 }
    388 
    389 NS_IMETHODIMP
    390 BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) {
    391  NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest);
    392  return NS_OK;
    393 }
    394 
    395 ////////////////////////////////////////////////////////////////////////////////
    396 // Helper methods for notification logging
    397 
    398 static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) {
    399  if (!aContext) {
    400    return "<null>"_ns;
    401  }
    402 
    403  nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI();
    404  return nsPrintfCString(
    405      "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(),
    406      currentURI ? currentURI->GetSpecOrDefault().get() : "<null>");
    407 }
    408 
    409 static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) {
    410  if (!aWebProgress) {
    411    return "<null>"_ns;
    412  }
    413 
    414  bool isTopLevel = false;
    415  aWebProgress->GetIsTopLevel(&isTopLevel);
    416  bool isLoadingDocument = false;
    417  aWebProgress->GetIsLoadingDocument(&isLoadingDocument);
    418  return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel,
    419                         isLoadingDocument);
    420 }
    421 
    422 static nsCString DescribeRequest(nsIRequest* aRequest) {
    423  if (!aRequest) {
    424    return "<null>"_ns;
    425  }
    426 
    427  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
    428  if (!channel) {
    429    return "<non-channel>"_ns;
    430  }
    431 
    432  nsCOMPtr<nsIURI> originalURI;
    433  channel->GetOriginalURI(getter_AddRefs(originalURI));
    434  nsCOMPtr<nsIURI> uri;
    435  channel->GetURI(getter_AddRefs(uri));
    436 
    437  return nsPrintfCString(
    438      "{URI:%s, originalURI:%s}",
    439      uri ? uri->GetSpecOrDefault().get() : "<null>",
    440      originalURI ? originalURI->GetSpecOrDefault().get() : "<null>");
    441 }
    442 
    443 static nsCString DescribeWebProgressFlags(uint32_t aFlags,
    444                                          const nsACString& aPrefix) {
    445  nsCString flags;
    446  uint32_t remaining = aFlags;
    447 
    448  // Hackily fetch the names of each constant from the XPT information used for
    449  // reflecting it into JS. This doesn't need to be reliable and just exists as
    450  // a logging aid.
    451  //
    452  // If a change to xpt in the future breaks this code, just delete it and
    453  // replace it with a normal hex log.
    454  if (const auto* ifaceInfo =
    455          nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) {
    456    for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) {
    457      const auto& constInfo = ifaceInfo->Constant(i);
    458      nsDependentCString name(constInfo.Name());
    459      if (!StringBeginsWith(name, aPrefix)) {
    460        continue;
    461      }
    462 
    463      if (remaining & constInfo.mValue) {
    464        remaining &= ~constInfo.mValue;
    465        if (!flags.IsEmpty()) {
    466          flags.AppendLiteral("|");
    467        }
    468        flags.Append(name);
    469      }
    470    }
    471  }
    472  if (remaining != 0 || flags.IsEmpty()) {
    473    if (!flags.IsEmpty()) {
    474      flags.AppendLiteral("|");
    475    }
    476    flags.AppendInt(remaining, 16);
    477  }
    478  return flags;
    479 }
    480 
    481 static nsCString DescribeError(nsresult aError) {
    482  nsCString name;
    483  GetErrorName(aError, name);
    484  return name;
    485 }
    486 
    487 }  // namespace dom
    488 }  // namespace mozilla