This patch switches the main logic from a boolean pref to an integer with different feature modes. See nsIBounceTrackingProtection for the new modes supported. Dry-run mode has been merged into the mode pref. A new state is introduced MODE_ENABLED_STANDBY in which BTP still collects user activation signals, but does not classify or purge bounce trackers. This new mode should be used when it's expected that the feature may be toggled on/off after rollout. Collecting user activation even in the "disabled" state ensures that when the feature gets re-enabled the user activation map is already warmed up and sites important to the user are not classified + purged. The BounceTrackingProtection singleton listens for changes on the mode pref and inits / tears down accordingly. However it never shuts down fully. To fully disable the feature a restart is required. Differential Revision: https://phabricator.services.mozilla.com/D217848
451 lines
16 KiB
C++
451 lines
16 KiB
C++
/* 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 "BrowsingContextWebProgress.h"
|
|
#include "mozilla/AlreadyAddRefed.h"
|
|
#include "mozilla/BounceTrackingState.h"
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
#include "mozilla/ErrorNames.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "nsString.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsIChannel.h"
|
|
#include "xptinfo.h"
|
|
#include "mozilla/RefPtr.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
static mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress");
|
|
|
|
static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext);
|
|
static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress);
|
|
static nsCString DescribeRequest(nsIRequest* aRequest);
|
|
static nsCString DescribeWebProgressFlags(uint32_t aFlags,
|
|
const nsACString& aPrefix);
|
|
static nsCString DescribeError(nsresult aError);
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress)
|
|
NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
|
|
NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
BrowsingContextWebProgress::BrowsingContextWebProgress(
|
|
CanonicalBrowsingContext* aBrowsingContext)
|
|
: mCurrentBrowsingContext(aBrowsingContext) {}
|
|
|
|
BrowsingContextWebProgress::~BrowsingContextWebProgress() = default;
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener(
|
|
nsIWebProgressListener* aListener, uint32_t aNotifyMask) {
|
|
nsWeakPtr listener = do_GetWeakReference(aListener);
|
|
if (!listener) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (mListenerInfoList.Contains(listener)) {
|
|
// The listener is already registered!
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener(
|
|
nsIWebProgressListener* aListener) {
|
|
nsWeakPtr listener = do_GetWeakReference(aListener);
|
|
if (!listener) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM(
|
|
BrowsingContext** aBrowsingContext) {
|
|
NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext);
|
|
return NS_OK;
|
|
}
|
|
|
|
BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() {
|
|
return mCurrentBrowsingContext;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow(
|
|
mozIDOMWindowProxy** aDOMWindow) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) {
|
|
*aIsTopLevel = mCurrentBrowsingContext->IsTop();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument(
|
|
bool* aIsLoadingDocument) {
|
|
*aIsLoadingDocument = mIsLoadingDocument;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) {
|
|
*aLoadType = mLoadType;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
void BrowsingContextWebProgress::UpdateAndNotifyListeners(
|
|
uint32_t aFlag,
|
|
const std::function<void(nsIWebProgressListener*)>& aCallback) {
|
|
RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this;
|
|
|
|
ListenerArray::ForwardIterator iter(mListenerInfoList);
|
|
while (iter.HasMore()) {
|
|
ListenerInfo& info = iter.GetNext();
|
|
if (!(info.mNotifyMask & aFlag)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIWebProgressListener> listener =
|
|
do_QueryReferent(info.mWeakListener);
|
|
if (!listener) {
|
|
mListenerInfoList.RemoveElement(info);
|
|
continue;
|
|
}
|
|
|
|
aCallback(listener);
|
|
}
|
|
|
|
mListenerInfoList.Compact();
|
|
|
|
// Notify the parent BrowsingContextWebProgress of the event to continue
|
|
// propagating.
|
|
auto* parent = mCurrentBrowsingContext->GetParent();
|
|
if (parent && parent->GetWebProgress()) {
|
|
aCallback(parent->GetWebProgress());
|
|
}
|
|
}
|
|
|
|
void BrowsingContextWebProgress::ContextDiscarded() {
|
|
if (!mIsLoadingDocument) {
|
|
return;
|
|
}
|
|
|
|
// If our BrowsingContext is being discarded while still loading a document,
|
|
// fire a synthetic `STATE_STOP` to end the ongoing load.
|
|
MOZ_LOG(gBCWebProgressLog, LogLevel::Info,
|
|
("Discarded while loading %s",
|
|
DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
|
|
// This matches what nsDocLoader::doStopDocumentLoad does, except we don't
|
|
// bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`,
|
|
// nsBrowserStatusFilter would filter it out before it gets to the parent
|
|
// process.
|
|
nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest;
|
|
OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK,
|
|
NS_ERROR_ABORT);
|
|
}
|
|
|
|
void BrowsingContextWebProgress::ContextReplaced(
|
|
CanonicalBrowsingContext* aNewContext) {
|
|
mCurrentBrowsingContext = aNewContext;
|
|
}
|
|
|
|
already_AddRefed<BounceTrackingState>
|
|
BrowsingContextWebProgress::GetBounceTrackingState() {
|
|
if (!mBounceTrackingState) {
|
|
nsresult rv = NS_OK;
|
|
mBounceTrackingState = BounceTrackingState::GetOrCreate(this, rv);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to get BounceTrackingState.");
|
|
}
|
|
return do_AddRef(mBounceTrackingState);
|
|
}
|
|
|
|
void BrowsingContextWebProgress::DropBounceTrackingState() {
|
|
mBounceTrackingState = nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// nsIWebProgressListener
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
uint32_t aStateFlags,
|
|
nsresult aStatus) {
|
|
MOZ_LOG(
|
|
gBCWebProgressLog, LogLevel::Info,
|
|
("OnStateChange(%s, %s, %s, %s) on %s",
|
|
DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
|
|
DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(),
|
|
DescribeError(aStatus).get(),
|
|
DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
|
|
bool targetIsThis = aWebProgress == this;
|
|
|
|
// We may receive a request from an in-process nsDocShell which doesn't have
|
|
// `aWebProgress == this` which we should still consider as targeting
|
|
// ourselves.
|
|
if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress);
|
|
docShell && docShell->GetBrowsingContext() == mCurrentBrowsingContext) {
|
|
targetIsThis = true;
|
|
aWebProgress->GetLoadType(&mLoadType);
|
|
}
|
|
|
|
// Track `mIsLoadingDocument` based on the notifications we've received so far
|
|
// if the nsIWebProgress being targeted is this one.
|
|
if (targetIsThis) {
|
|
constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START |
|
|
nsIWebProgressListener::STATE_IS_DOCUMENT |
|
|
nsIWebProgressListener::STATE_IS_REQUEST |
|
|
nsIWebProgressListener::STATE_IS_WINDOW |
|
|
nsIWebProgressListener::STATE_IS_NETWORK;
|
|
constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP |
|
|
nsIWebProgressListener::STATE_IS_WINDOW;
|
|
constexpr uint32_t redirectFlags =
|
|
nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
|
|
if ((aStateFlags & startFlags) == startFlags) {
|
|
if (mIsLoadingDocument) {
|
|
// We received a duplicate `STATE_START` notification, silence this
|
|
// notification until we receive the matching `STATE_STOP` to not fire
|
|
// duplicate `STATE_START` notifications into frontend on process
|
|
// switches.
|
|
return NS_OK;
|
|
}
|
|
mIsLoadingDocument = true;
|
|
|
|
// Record the request we started the load with, so we can emit a synthetic
|
|
// `STATE_STOP` notification if the BrowsingContext is discarded before
|
|
// the notification arrives.
|
|
mLoadingDocumentRequest = aRequest;
|
|
} else if ((aStateFlags & stopFlags) == stopFlags) {
|
|
// We've received a `STATE_STOP` notification targeting this web progress,
|
|
// clear our loading document flag.
|
|
mIsLoadingDocument = false;
|
|
mLoadingDocumentRequest = nullptr;
|
|
} else if (mIsLoadingDocument &&
|
|
(aStateFlags & redirectFlags) == redirectFlags) {
|
|
// If we see a redirected document load, update the loading request which
|
|
// we'll emit the synthetic STATE_STOP notification with.
|
|
mLoadingDocumentRequest = aRequest;
|
|
}
|
|
}
|
|
|
|
UpdateAndNotifyListeners(
|
|
((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
|
|
[&](nsIWebProgressListener* listener) {
|
|
listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
int32_t aCurSelfProgress,
|
|
int32_t aMaxSelfProgress,
|
|
int32_t aCurTotalProgress,
|
|
int32_t aMaxTotalProgress) {
|
|
MOZ_LOG(
|
|
gBCWebProgressLog, LogLevel::Info,
|
|
("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s",
|
|
DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
|
|
aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress,
|
|
DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
UpdateAndNotifyListeners(
|
|
nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) {
|
|
listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress,
|
|
aMaxSelfProgress, aCurTotalProgress,
|
|
aMaxTotalProgress);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
nsIURI* aLocation,
|
|
uint32_t aFlags) {
|
|
MOZ_LOG(
|
|
gBCWebProgressLog, LogLevel::Info,
|
|
("OnLocationChange(%s, %s, %s, %s) on %s",
|
|
DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
|
|
aLocation ? aLocation->GetSpecOrDefault().get() : "<null>",
|
|
DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(),
|
|
DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
UpdateAndNotifyListeners(
|
|
nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) {
|
|
listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
nsresult aStatus,
|
|
const char16_t* aMessage) {
|
|
MOZ_LOG(
|
|
gBCWebProgressLog, LogLevel::Info,
|
|
("OnStatusChange(%s, %s, %s, \"%s\") on %s",
|
|
DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
|
|
DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(),
|
|
DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
UpdateAndNotifyListeners(
|
|
nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) {
|
|
listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
uint32_t aState) {
|
|
MOZ_LOG(
|
|
gBCWebProgressLog, LogLevel::Info,
|
|
("OnSecurityChange(%s, %s, %x) on %s",
|
|
DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
|
|
aState, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
UpdateAndNotifyListeners(
|
|
nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) {
|
|
listener->OnSecurityChange(aWebProgress, aRequest, aState);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
uint32_t aEvent) {
|
|
MOZ_LOG(
|
|
gBCWebProgressLog, LogLevel::Info,
|
|
("OnContentBlockingEvent(%s, %s, %x) on %s",
|
|
DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
|
|
aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
|
|
UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
|
|
[&](nsIWebProgressListener* listener) {
|
|
listener->OnContentBlockingEvent(aWebProgress,
|
|
aRequest, aEvent);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) {
|
|
NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest);
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Helper methods for notification logging
|
|
|
|
static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) {
|
|
if (!aContext) {
|
|
return "<null>"_ns;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI();
|
|
return nsPrintfCString(
|
|
"{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(),
|
|
currentURI ? currentURI->GetSpecOrDefault().get() : "<null>");
|
|
}
|
|
|
|
static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) {
|
|
if (!aWebProgress) {
|
|
return "<null>"_ns;
|
|
}
|
|
|
|
bool isTopLevel = false;
|
|
aWebProgress->GetIsTopLevel(&isTopLevel);
|
|
bool isLoadingDocument = false;
|
|
aWebProgress->GetIsLoadingDocument(&isLoadingDocument);
|
|
return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel,
|
|
isLoadingDocument);
|
|
}
|
|
|
|
static nsCString DescribeRequest(nsIRequest* aRequest) {
|
|
if (!aRequest) {
|
|
return "<null>"_ns;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
|
if (!channel) {
|
|
return "<non-channel>"_ns;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> originalURI;
|
|
channel->GetOriginalURI(getter_AddRefs(originalURI));
|
|
nsCOMPtr<nsIURI> uri;
|
|
channel->GetURI(getter_AddRefs(uri));
|
|
|
|
return nsPrintfCString(
|
|
"{URI:%s, originalURI:%s}",
|
|
uri ? uri->GetSpecOrDefault().get() : "<null>",
|
|
originalURI ? originalURI->GetSpecOrDefault().get() : "<null>");
|
|
}
|
|
|
|
static nsCString DescribeWebProgressFlags(uint32_t aFlags,
|
|
const nsACString& aPrefix) {
|
|
nsCString flags;
|
|
uint32_t remaining = aFlags;
|
|
|
|
// Hackily fetch the names of each constant from the XPT information used for
|
|
// reflecting it into JS. This doesn't need to be reliable and just exists as
|
|
// a logging aid.
|
|
//
|
|
// If a change to xpt in the future breaks this code, just delete it and
|
|
// replace it with a normal hex log.
|
|
if (const auto* ifaceInfo =
|
|
nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) {
|
|
for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) {
|
|
const auto& constInfo = ifaceInfo->Constant(i);
|
|
nsDependentCString name(constInfo.Name());
|
|
if (!StringBeginsWith(name, aPrefix)) {
|
|
continue;
|
|
}
|
|
|
|
if (remaining & constInfo.mValue) {
|
|
remaining &= ~constInfo.mValue;
|
|
if (!flags.IsEmpty()) {
|
|
flags.AppendLiteral("|");
|
|
}
|
|
flags.Append(name);
|
|
}
|
|
}
|
|
}
|
|
if (remaining != 0 || flags.IsEmpty()) {
|
|
if (!flags.IsEmpty()) {
|
|
flags.AppendLiteral("|");
|
|
}
|
|
flags.AppendInt(remaining, 16);
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
static nsCString DescribeError(nsresult aError) {
|
|
nsCString name;
|
|
GetErrorName(aError, name);
|
|
return name;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|