mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
Bug 1590762 - Part 3: Keep track of current loads in BrowsingContext. r=mattwoodrow
Differential Revision: https://phabricator.services.mozilla.com/D75110
This commit is contained in:
parent
ca5db92916
commit
78dfb0a991
@ -1586,8 +1586,10 @@ nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState,
|
||||
// Attempt to initiate this load immediately in the parent, if it succeeds
|
||||
// it'll return a unique identifier so that we can find it later.
|
||||
uint64_t loadIdentifier = 0;
|
||||
if (Canonical()->AttemptLoadURIInParent(aLoadState, &loadIdentifier)) {
|
||||
aLoadState->SetLoadIdentifier(loadIdentifier);
|
||||
if (Canonical()->AttemptLoadURIInParent(aLoadState)) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(GetCurrentLoadIdentifier().isSome());
|
||||
loadIdentifier = GetCurrentLoadIdentifier().value();
|
||||
aLoadState->SetChannelInitialized(true);
|
||||
}
|
||||
|
||||
cp->TransmitBlobDataIfBlobURL(aLoadState->URI(),
|
||||
@ -1612,9 +1614,7 @@ nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell,
|
||||
nsIRequest** aRequest) {
|
||||
nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) {
|
||||
if (IsDiscarded()) {
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1624,8 +1624,7 @@ nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
sourceBC && sourceBC->GetIsActive() && !GetIsActive() &&
|
||||
!Preferences::GetBool("browser.tabs.loadDivertedInBackground", false);
|
||||
if (mDocShell) {
|
||||
nsresult rv = nsDocShell::Cast(mDocShell)->InternalLoad(
|
||||
aLoadState, aDocShell, aRequest);
|
||||
nsresult rv = nsDocShell::Cast(mDocShell)->InternalLoad(aLoadState);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Switch to target tab if we're currently focused window.
|
||||
@ -1650,6 +1649,8 @@ nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
MOZ_TRY(CheckSandboxFlags(aLoadState));
|
||||
|
||||
if (XRE_IsParentProcess()) {
|
||||
SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()));
|
||||
|
||||
if (ContentParent* cp = Canonical()->GetContentParent()) {
|
||||
Unused << cp->SendInternalLoad(this, aLoadState, isActive);
|
||||
}
|
||||
@ -1661,12 +1662,15 @@ nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
return NS_ERROR_DOM_PROP_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()));
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
|
||||
if (WindowGlobalChild* wgc =
|
||||
win->GetCurrentInnerWindow()->GetWindowGlobalChild()) {
|
||||
wgc->SendInternalLoad(this, aLoadState);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -140,6 +140,8 @@ class WindowProxyHolder;
|
||||
FIELD(FullZoom, float) \
|
||||
FIELD(WatchedByDevToolsInternal, bool) \
|
||||
FIELD(TextZoom, float) \
|
||||
/* The current in-progress load. */ \
|
||||
FIELD(CurrentLoadIdentifier, Maybe<uint64_t>) \
|
||||
/* See nsIRequest for possible flags. */ \
|
||||
FIELD(DefaultLoadFlags, uint32_t) \
|
||||
/* Signals that session history is enabled for this browsing context tree. \
|
||||
@ -280,8 +282,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
nsresult LoadURI(nsDocShellLoadState* aLoadState,
|
||||
bool aSetNavigating = false);
|
||||
|
||||
nsresult InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell, nsIRequest** aRequest);
|
||||
nsresult InternalLoad(nsDocShellLoadState* aLoadState);
|
||||
|
||||
// If the load state includes a source BrowsingContext has been passed, check
|
||||
// to see if we are sandboxed from it as the result of an iframe or CSP
|
||||
@ -408,6 +409,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
|
||||
bool IsLoading();
|
||||
|
||||
bool IsLoadingIdentifier(uint64_t aLoadIdentifer) {
|
||||
if (GetCurrentLoadIdentifier() &&
|
||||
*GetCurrentLoadIdentifier() == aLoadIdentifer) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ScreenOrientation related APIs
|
||||
void SetCurrentOrientation(OrientationType aType, float aAngle) {
|
||||
SetCurrentOrientationType(aType);
|
||||
|
@ -813,7 +813,7 @@ MediaController* CanonicalBrowsingContext::GetMediaController() {
|
||||
}
|
||||
|
||||
bool CanonicalBrowsingContext::AttemptLoadURIInParent(
|
||||
nsDocShellLoadState* aLoadState, uint64_t* aLoadIdentifier) {
|
||||
nsDocShellLoadState* aLoadState) {
|
||||
// We currently only support starting loads directly from the
|
||||
// CanonicalBrowsingContext for top-level BCs.
|
||||
if (!IsTopContent() || !GetContentParent() ||
|
||||
@ -866,18 +866,21 @@ bool CanonicalBrowsingContext::AttemptLoadURIInParent(
|
||||
// If we successfully open the DocumentChannel, then it'll register
|
||||
// itself using aLoadIdentifier and be kept alive until it completes
|
||||
// loading.
|
||||
return net::DocumentLoadListener::OpenFromParent(
|
||||
this, aLoadState, outerWindowId, aLoadIdentifier);
|
||||
return net::DocumentLoadListener::OpenFromParent(this, aLoadState,
|
||||
outerWindowId);
|
||||
}
|
||||
|
||||
void CanonicalBrowsingContext::StartDocumentLoad(
|
||||
net::DocumentLoadListener* aLoad) {
|
||||
mCurrentLoad = aLoad;
|
||||
SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier()));
|
||||
}
|
||||
void CanonicalBrowsingContext::EndDocumentLoad(
|
||||
net::DocumentLoadListener* aLoad) {
|
||||
if (mCurrentLoad == aLoad) {
|
||||
mCurrentLoad = nullptr;
|
||||
|
||||
void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) {
|
||||
mCurrentLoad = nullptr;
|
||||
|
||||
if (!aForProcessSwitch) {
|
||||
SetCurrentLoadIdentifier(Nothing());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,8 +150,7 @@ class CanonicalBrowsingContext final : public BrowsingContext {
|
||||
// if the top-level browsing context has been discarded.
|
||||
MediaController* GetMediaController();
|
||||
|
||||
bool AttemptLoadURIInParent(nsDocShellLoadState* aLoadState,
|
||||
uint64_t* aLoadIdentifier);
|
||||
bool AttemptLoadURIInParent(nsDocShellLoadState* aLoadState);
|
||||
|
||||
// Get or create a secure browser UI for this BrowsingContext
|
||||
nsISecureBrowserUI* GetSecureBrowserUI();
|
||||
@ -225,7 +224,7 @@ class CanonicalBrowsingContext final : public BrowsingContext {
|
||||
// Called once DocumentLoadListener completes handling a load, and it
|
||||
// is either complete, or handed off to the final channel to deliver
|
||||
// data to the destination docshell.
|
||||
void EndDocumentLoad(net::DocumentLoadListener* aLoad);
|
||||
void EndDocumentLoad(bool aForProcessSwitch);
|
||||
|
||||
// XXX(farre): Store a ContentParent pointer here rather than mProcessId?
|
||||
// Indicates which process owns the docshell.
|
||||
|
@ -807,9 +807,7 @@ nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) {
|
||||
MOZ_ASSERT(aLoadState->SHEntry() == nullptr,
|
||||
"SHEntry should be null when calling InternalLoad from LoadURI");
|
||||
|
||||
return InternalLoad(aLoadState,
|
||||
nullptr, // no nsIDocShell
|
||||
nullptr); // no nsIRequest
|
||||
return InternalLoad(aLoadState); // no nsIRequest
|
||||
}
|
||||
|
||||
void nsDocShell::MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState) {
|
||||
@ -3742,7 +3740,7 @@ nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
|
||||
mBrowsingContext &&
|
||||
mBrowsingContext->HasValidTransientUserGestureActivation());
|
||||
|
||||
return InternalLoad(loadState, nullptr, nullptr);
|
||||
return InternalLoad(loadState);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -3850,7 +3848,7 @@ nsDocShell::Reload(uint32_t aReloadFlags) {
|
||||
loadState->SetHasValidUserGestureActivation(
|
||||
mBrowsingContext &&
|
||||
mBrowsingContext->HasValidTransientUserGestureActivation());
|
||||
rv = InternalLoad(loadState, nullptr, nullptr);
|
||||
rv = InternalLoad(loadState);
|
||||
}
|
||||
|
||||
return rv;
|
||||
@ -7864,7 +7862,7 @@ class InternalLoadEvent : public Runnable {
|
||||
MOZ_ASSERT(mLoadState->TriggeringPrincipal(),
|
||||
"InternalLoadEvent: Should always have a principal here");
|
||||
#endif
|
||||
return mDocShell->InternalLoad(mLoadState, nullptr, nullptr);
|
||||
return mDocShell->InternalLoad(mLoadState);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -7909,9 +7907,7 @@ uint32_t nsDocShell::DetermineContentType() {
|
||||
return nsIContentPolicy::TYPE_INTERNAL_IFRAME;
|
||||
}
|
||||
|
||||
nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell,
|
||||
nsIRequest** aRequest) {
|
||||
nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
|
||||
MOZ_ASSERT(aLoadState, "need a load state!");
|
||||
MOZ_ASSERT(!aLoadState->Target().IsEmpty(), "should have a target here!");
|
||||
|
||||
@ -8101,6 +8097,8 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState,
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(targetContext, rv);
|
||||
|
||||
aLoadState->SetTargetBrowsingContext(targetContext);
|
||||
//
|
||||
// Transfer the load to the target BrowsingContext... Pass empty string as the
|
||||
// window target name from to prevent recursive retargeting!
|
||||
@ -8109,7 +8107,7 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState,
|
||||
aLoadState->SetTarget(EmptyString());
|
||||
// No forced download
|
||||
aLoadState->SetFileName(VoidString());
|
||||
return targetContext->InternalLoad(aLoadState, aDocShell, aRequest);
|
||||
return targetContext->InternalLoad(aLoadState);
|
||||
}
|
||||
|
||||
bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
|
||||
@ -8416,9 +8414,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell,
|
||||
nsIRequest** aRequest) {
|
||||
nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState) {
|
||||
MOZ_ASSERT(aLoadState, "need a load state!");
|
||||
MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
|
||||
"need a valid TriggeringPrincipal");
|
||||
@ -8437,14 +8433,6 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
("DOCSHELL %p InternalLoad %s\n", this,
|
||||
aLoadState->URI()->GetSpecOrDefault().get()));
|
||||
|
||||
// Initialize aDocShell/aRequest
|
||||
if (aDocShell) {
|
||||
*aDocShell = nullptr;
|
||||
}
|
||||
if (aRequest) {
|
||||
*aRequest = nullptr;
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(IsValidLoadType(aLoadState->LoadType()), NS_ERROR_INVALID_ARG);
|
||||
|
||||
// Cancel loads coming from Docshells that are being destroyed.
|
||||
@ -8459,7 +8447,7 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
|
||||
// If we have a target to move to, do that now.
|
||||
if (!aLoadState->Target().IsEmpty()) {
|
||||
return PerformRetargeting(aLoadState, aDocShell, aRequest);
|
||||
return PerformRetargeting(aLoadState);
|
||||
}
|
||||
|
||||
// If we don't have a target, we're loading into ourselves, and our load
|
||||
@ -8777,10 +8765,7 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
|
||||
|
||||
nsCOMPtr<nsIRequest> req;
|
||||
rv = DoURILoad(aLoadState, aDocShell, getter_AddRefs(req));
|
||||
if (req && aRequest) {
|
||||
NS_ADDREF(*aRequest = req);
|
||||
}
|
||||
rv = DoURILoad(aLoadState, getter_AddRefs(req));
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
|
||||
@ -9276,7 +9261,7 @@ nsIPrincipal* nsDocShell::GetInheritedPrincipal(
|
||||
}
|
||||
|
||||
nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell, nsIRequest** aRequest) {
|
||||
nsIRequest** aRequest) {
|
||||
// Double-check that we're still around to load this URI.
|
||||
if (mIsBeingDestroyed) {
|
||||
// Return NS_OK despite not doing anything to avoid throwing exceptions
|
||||
@ -9381,16 +9366,8 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
|
||||
outRequest.forget(aRequest);
|
||||
}
|
||||
|
||||
rv = OpenInitializedChannel(channel, uriLoader,
|
||||
nsIURILoader::REDIRECTED_CHANNEL);
|
||||
|
||||
// If the channel load failed, we failed and nsIWebProgress just ain't
|
||||
// gonna happen.
|
||||
if (NS_SUCCEEDED(rv) && aDocShell) {
|
||||
nsCOMPtr<nsIDocShell> self = this;
|
||||
self.forget(aDocShell);
|
||||
}
|
||||
return rv;
|
||||
return OpenInitializedChannel(channel, uriLoader,
|
||||
nsIURILoader::REDIRECTED_CHANNEL);
|
||||
}
|
||||
|
||||
// There are two cases we care about:
|
||||
@ -9588,20 +9565,7 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
|
||||
|
||||
uint32_t openFlags =
|
||||
nsDocShell::ComputeURILoaderFlags(mBrowsingContext, mLoadType);
|
||||
rv = OpenInitializedChannel(channel, uriLoader, openFlags);
|
||||
|
||||
//
|
||||
// If the channel load failed, we failed and nsIWebProgress just ain't
|
||||
// gonna happen.
|
||||
//
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (aDocShell) {
|
||||
*aDocShell = this;
|
||||
NS_ADDREF(*aDocShell);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
return OpenInitializedChannel(channel, uriLoader, openFlags);
|
||||
}
|
||||
|
||||
static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure,
|
||||
@ -10839,9 +10803,7 @@ nsresult nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
rv = InternalLoad(loadState,
|
||||
nullptr, // No nsIDocShell
|
||||
nullptr); // No nsIRequest
|
||||
rv = InternalLoad(loadState);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -11567,13 +11529,9 @@ nsresult nsDocShell::EnsureCommandHandler() {
|
||||
|
||||
class OnLinkClickEvent : public Runnable {
|
||||
public:
|
||||
OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent, nsIURI* aURI,
|
||||
const nsAString& aTargetSpec, const nsAString& aFileName,
|
||||
nsIInputStream* aPostDataStream,
|
||||
nsIInputStream* aHeadersDataStream, bool aNoOpenerImplied,
|
||||
bool aIsUserTriggered, bool aIsTrusted,
|
||||
nsIPrincipal* aTriggeringPrincipal,
|
||||
nsIContentSecurityPolicy* aCsp);
|
||||
OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
|
||||
nsDocShellLoadState* aLoadState, bool aNoOpenerImplied,
|
||||
bool aIsTrusted, nsIPrincipal* aTriggeringPrincipal);
|
||||
|
||||
NS_IMETHOD Run() override {
|
||||
AutoPopupStatePusher popupStatePusher(mPopupState);
|
||||
@ -11586,50 +11544,34 @@ class OnLinkClickEvent : public Runnable {
|
||||
// concerned.
|
||||
AutoJSAPI jsapi;
|
||||
if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) {
|
||||
mHandler->OnLinkClickSync(mContent, mURI, mTargetSpec, mFileName,
|
||||
mPostDataStream, mHeadersDataStream,
|
||||
mNoOpenerImplied, nullptr, nullptr,
|
||||
mIsUserTriggered, mTriggeringPrincipal, mCsp);
|
||||
mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
|
||||
mTriggeringPrincipal);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<nsDocShell> mHandler;
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
nsString mTargetSpec;
|
||||
nsString mFileName;
|
||||
nsCOMPtr<nsIInputStream> mPostDataStream;
|
||||
nsCOMPtr<nsIInputStream> mHeadersDataStream;
|
||||
nsCOMPtr<nsIContent> mContent;
|
||||
RefPtr<nsDocShellLoadState> mLoadState;
|
||||
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
|
||||
PopupBlocker::PopupControlState mPopupState;
|
||||
bool mNoOpenerImplied;
|
||||
bool mIsUserTriggered;
|
||||
bool mIsTrusted;
|
||||
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
|
||||
nsCOMPtr<nsIContentSecurityPolicy> mCsp;
|
||||
};
|
||||
|
||||
OnLinkClickEvent::OnLinkClickEvent(
|
||||
nsDocShell* aHandler, nsIContent* aContent, nsIURI* aURI,
|
||||
const nsAString& aTargetSpec, const nsAString& aFileName,
|
||||
nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream,
|
||||
bool aNoOpenerImplied, bool aIsUserTriggered, bool aIsTrusted,
|
||||
nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp)
|
||||
OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
|
||||
nsDocShellLoadState* aLoadState,
|
||||
bool aNoOpenerImplied, bool aIsTrusted,
|
||||
nsIPrincipal* aTriggeringPrincipal)
|
||||
: mozilla::Runnable("OnLinkClickEvent"),
|
||||
mHandler(aHandler),
|
||||
mURI(aURI),
|
||||
mTargetSpec(aTargetSpec),
|
||||
mFileName(aFileName),
|
||||
mPostDataStream(aPostDataStream),
|
||||
mHeadersDataStream(aHeadersDataStream),
|
||||
mContent(aContent),
|
||||
mLoadState(aLoadState),
|
||||
mTriggeringPrincipal(aTriggeringPrincipal),
|
||||
mPopupState(PopupBlocker::GetPopupControlState()),
|
||||
mNoOpenerImplied(aNoOpenerImplied),
|
||||
mIsUserTriggered(aIsUserTriggered),
|
||||
mIsTrusted(aIsTrusted),
|
||||
mTriggeringPrincipal(aTriggeringPrincipal),
|
||||
mCsp(aCsp) {}
|
||||
mIsTrusted(aIsTrusted) {}
|
||||
|
||||
nsresult nsDocShell::OnLinkClick(
|
||||
nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec,
|
||||
@ -11676,10 +11618,20 @@ nsresult nsDocShell::OnLinkClick(
|
||||
target = aTargetSpec;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> ev = new OnLinkClickEvent(
|
||||
this, aContent, aURI, target, aFileName, aPostDataStream,
|
||||
aHeadersDataStream, noOpenerImplied, aIsUserTriggered, aIsTrusted,
|
||||
aTriggeringPrincipal, aCsp);
|
||||
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
|
||||
loadState->SetTarget(target);
|
||||
loadState->SetFileName(aFileName);
|
||||
loadState->SetPostDataStream(aPostDataStream);
|
||||
loadState->SetHeadersStream(aHeadersDataStream);
|
||||
loadState->SetFirstParty(true);
|
||||
loadState->SetTriggeringPrincipal(
|
||||
aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal());
|
||||
loadState->SetPrincipalToInherit(aContent->NodePrincipal());
|
||||
loadState->SetCsp(aCsp ? aCsp : aContent->GetCsp());
|
||||
|
||||
nsCOMPtr<nsIRunnable> ev =
|
||||
new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
|
||||
aIsTrusted, aTriggeringPrincipal);
|
||||
return Dispatch(TaskCategory::UI, ev.forget());
|
||||
}
|
||||
|
||||
@ -11689,21 +11641,11 @@ static bool IsElementAnchorOrArea(nsIContent* aContent) {
|
||||
return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
|
||||
}
|
||||
|
||||
nsresult nsDocShell::OnLinkClickSync(
|
||||
nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec,
|
||||
const nsAString& aFileName, nsIInputStream* aPostDataStream,
|
||||
nsIInputStream* aHeadersDataStream, bool aNoOpenerImplied,
|
||||
nsIDocShell** aDocShell, nsIRequest** aRequest, bool aIsUserTriggered,
|
||||
nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp) {
|
||||
// Initialize the DocShell / Request
|
||||
if (aDocShell) {
|
||||
*aDocShell = nullptr;
|
||||
}
|
||||
if (aRequest) {
|
||||
*aRequest = nullptr;
|
||||
}
|
||||
|
||||
if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) {
|
||||
nsresult nsDocShell::OnLinkClickSync(nsIContent* aContent,
|
||||
nsDocShellLoadState* aLoadState,
|
||||
bool aNoOpenerImplied,
|
||||
nsIPrincipal* aTriggeringPrincipal) {
|
||||
if (!IsNavigationAllowed() || !IsOKToLoadURI(aLoadState->URI())) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -11730,7 +11672,7 @@ nsresult nsDocShell::OnLinkClickSync(
|
||||
do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
|
||||
if (extProtService) {
|
||||
nsAutoCString scheme;
|
||||
aURI->GetScheme(scheme);
|
||||
aLoadState->URI()->GetScheme(scheme);
|
||||
if (!scheme.IsEmpty()) {
|
||||
// if the URL scheme does not correspond to an exposed protocol, then
|
||||
// we need to hand this link click over to the external protocol
|
||||
@ -11739,22 +11681,17 @@ nsresult nsDocShell::OnLinkClickSync(
|
||||
nsresult rv =
|
||||
extProtService->IsExposedProtocol(scheme.get(), &isExposed);
|
||||
if (NS_SUCCEEDED(rv) && !isExposed) {
|
||||
return extProtService->LoadURI(aURI, triggeringPrincipal,
|
||||
return extProtService->LoadURI(aLoadState->URI(), triggeringPrincipal,
|
||||
mBrowsingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp = aCsp;
|
||||
if (!csp) {
|
||||
// Currently, if no csp is passed explicitly we fall back to querying the
|
||||
// CSP from the document.
|
||||
csp = aContent->GetCsp();
|
||||
}
|
||||
|
||||
uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
|
||||
bool isElementAnchorOrArea = IsElementAnchorOrArea(aContent);
|
||||
bool triggeringPrincipalIsSystemPrincipal =
|
||||
aLoadState->TriggeringPrincipal()->IsSystemPrincipal();
|
||||
if (isElementAnchorOrArea) {
|
||||
MOZ_ASSERT(aContent->IsHTMLElement());
|
||||
nsAutoString relString;
|
||||
@ -11763,7 +11700,7 @@ nsresult nsDocShell::OnLinkClickSync(
|
||||
nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
|
||||
relString);
|
||||
|
||||
bool targetBlank = aTargetSpec.LowerCaseEqualsLiteral("_blank");
|
||||
bool targetBlank = aLoadState->Target().LowerCaseEqualsLiteral("_blank");
|
||||
bool explicitOpenerSet = false;
|
||||
|
||||
// The opener behaviour follows a hierarchy, such that if a higher
|
||||
@ -11792,7 +11729,7 @@ nsresult nsDocShell::OnLinkClickSync(
|
||||
}
|
||||
|
||||
if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
|
||||
!explicitOpenerSet && !triggeringPrincipal->IsSystemPrincipal()) {
|
||||
!explicitOpenerSet && !triggeringPrincipalIsSystemPrincipal) {
|
||||
flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
|
||||
}
|
||||
|
||||
@ -11843,29 +11780,22 @@ nsresult nsDocShell::OnLinkClickSync(
|
||||
isElementAnchorOrArea ? new ReferrerInfo(*aContent->AsElement())
|
||||
: new ReferrerInfo(*referrerDoc);
|
||||
|
||||
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
|
||||
loadState->SetReferrerInfo(referrerInfo);
|
||||
loadState->SetTriggeringPrincipal(triggeringPrincipal);
|
||||
loadState->SetPrincipalToInherit(aContent->NodePrincipal());
|
||||
loadState->SetCsp(csp);
|
||||
loadState->SetLoadFlags(flags);
|
||||
loadState->SetTarget(aTargetSpec);
|
||||
loadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint));
|
||||
loadState->SetFileName(aFileName);
|
||||
loadState->SetPostDataStream(aPostDataStream);
|
||||
loadState->SetHeadersStream(aHeadersDataStream);
|
||||
loadState->SetLoadType(loadType);
|
||||
loadState->SetFirstParty(true);
|
||||
loadState->SetSourceBrowsingContext(mBrowsingContext);
|
||||
loadState->SetIsFormSubmission(aContent->IsHTMLElement(nsGkAtoms::form));
|
||||
loadState->SetHasValidUserGestureActivation(
|
||||
aLoadState->SetReferrerInfo(referrerInfo);
|
||||
aLoadState->SetLoadFlags(flags);
|
||||
aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint));
|
||||
aLoadState->SetLoadType(loadType);
|
||||
aLoadState->SetSourceBrowsingContext(mBrowsingContext);
|
||||
aLoadState->SetHasValidUserGestureActivation(
|
||||
mBrowsingContext &&
|
||||
mBrowsingContext->HasValidTransientUserGestureActivation());
|
||||
nsresult rv = InternalLoad(loadState, aDocShell, aRequest);
|
||||
|
||||
nsresult rv = InternalLoad(aLoadState);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsPingListener::DispatchPings(this, aContent, aURI, referrerInfo);
|
||||
nsPingListener::DispatchPings(this, aContent, aLoadState->URI(),
|
||||
referrerInfo);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -12136,7 +12066,7 @@ nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
self->InternalLoad(aLoadState, nullptr, nullptr);
|
||||
self->InternalLoad(aLoadState);
|
||||
|
||||
for (auto& endpoint : aStreamFilterEndpoints) {
|
||||
extensions::StreamFilterParent::Attach(
|
||||
|
@ -261,32 +261,16 @@ class nsDocShell final : public nsDocLoader,
|
||||
* through an event.
|
||||
*
|
||||
* @param aContent the content object used for triggering the link.
|
||||
* @param aURI a URI obect that defines the destination for the link
|
||||
* @param aTargetSpec indicates where the link is targeted (may be an empty
|
||||
* string)
|
||||
* @param aFileName non-null when the link should be downloaded as the given
|
||||
* file
|
||||
* @param aPostDataStream the POST data to send
|
||||
* @param aHeadersDataStream ??? (only used for plugins)
|
||||
* @param aDocShellLoadState the extended load info for this load.
|
||||
* @param aNoOpenerImplied if the link implies "noopener"
|
||||
* @param aDocShell (out-param) the DocShell that the request was opened on
|
||||
* @param aRequest the request that was opened
|
||||
* @param aTriggeringPrincipal, if not passed explicitly we fall back to
|
||||
* the document's principal.
|
||||
* @param aCsp, the CSP to be used for the load, that is the CSP of the
|
||||
* entity responsible for causing the load to occur. Most likely
|
||||
* this is the CSP of the document that started the load. In case
|
||||
* aCsp was not passed explicitly we fall back to using
|
||||
* aContent's document's CSP if that document holds any.
|
||||
*/
|
||||
nsresult OnLinkClickSync(
|
||||
nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec,
|
||||
const nsAString& aFileName, nsIInputStream* aPostDataStream = nullptr,
|
||||
nsIInputStream* aHeadersDataStream = nullptr,
|
||||
bool aNoOpenerImplied = false, nsIDocShell** aDocShell = nullptr,
|
||||
nsIRequest** aRequest = nullptr, bool aIsUserTriggered = false,
|
||||
nsIPrincipal* aTriggeringPrincipal = nullptr,
|
||||
nsIContentSecurityPolicy* aCsp = nullptr);
|
||||
nsresult OnLinkClickSync(nsIContent* aContent,
|
||||
nsDocShellLoadState* aLoadState,
|
||||
bool aNoOpenerImplied,
|
||||
nsIPrincipal* aTriggeringPrincipal);
|
||||
|
||||
/**
|
||||
* Process a mouse-over a link.
|
||||
*
|
||||
@ -418,8 +402,7 @@ class nsDocShell final : public nsDocLoader,
|
||||
* onLinkClickSync, which is triggered during form submission.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||
nsresult InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell, nsIRequest** aRequest);
|
||||
nsresult InternalLoad(nsDocShellLoadState* aLoadState);
|
||||
|
||||
// Clear the document's storage access flag if needed.
|
||||
void MaybeClearStorageAccessFlag();
|
||||
@ -632,8 +615,7 @@ class nsDocShell final : public nsDocLoader,
|
||||
// originalURI on the channel that does the load. If OriginalURI is null, URI
|
||||
// will be set as the originalURI. If LoadReplace is true, LOAD_REPLACE flag
|
||||
// will be set on the nsIChannel.
|
||||
nsresult DoURILoad(nsDocShellLoadState* aLoadState, nsIDocShell** aDocShell,
|
||||
nsIRequest** aRequest);
|
||||
nsresult DoURILoad(nsDocShellLoadState* aLoadState, nsIRequest** aRequest);
|
||||
|
||||
static nsresult AddHeadersToChannel(nsIInputStream* aHeadersData,
|
||||
nsIChannel* aChannel);
|
||||
@ -977,8 +959,7 @@ class nsDocShell final : public nsDocLoader,
|
||||
|
||||
// If we are passed a named target during InternalLoad, this method handles
|
||||
// moving the load to the browsing context the target name resolves to.
|
||||
nsresult PerformRetargeting(nsDocShellLoadState* aLoadState,
|
||||
nsIDocShell** aDocShell, nsIRequest** aRequest);
|
||||
nsresult PerformRetargeting(nsDocShellLoadState* aLoadState);
|
||||
|
||||
// Returns one of nsIContentPolicy::TYPE_DOCUMENT,
|
||||
// nsIContentPolicy::TYPE_INTERNAL_IFRAME, or
|
||||
|
@ -75,30 +75,11 @@ already_AddRefed<nsIURIFixupInfo> GetFixupURIInfo(const nsACString& aStringURI,
|
||||
} // anonymous namespace
|
||||
|
||||
nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI)
|
||||
: mURI(aURI),
|
||||
mResultPrincipalURIIsSome(false),
|
||||
mKeepResultPrincipalURIIfSet(false),
|
||||
mLoadReplace(false),
|
||||
mInheritPrincipal(false),
|
||||
mPrincipalIsExplicit(false),
|
||||
mForceAllowDataURI(false),
|
||||
mOriginalFrameSrc(false),
|
||||
mIsFormSubmission(false),
|
||||
mLoadType(LOAD_NORMAL),
|
||||
mTarget(),
|
||||
mSrcdocData(VoidString()),
|
||||
mLoadFlags(0),
|
||||
mFirstParty(false),
|
||||
mHasValidUserGestureActivation(false),
|
||||
mTypeHint(VoidCString()),
|
||||
mFileName(VoidString()),
|
||||
mIsFromProcessingFrameAttributes(false),
|
||||
mLoadIdentifier(0) {
|
||||
MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!");
|
||||
}
|
||||
: nsDocShellLoadState(aURI, nsContentUtils::GenerateLoadIdentifier()) {}
|
||||
|
||||
nsDocShellLoadState::nsDocShellLoadState(
|
||||
const DocShellLoadStateInit& aLoadState) {
|
||||
const DocShellLoadStateInit& aLoadState)
|
||||
: mLoadIdentifier(aLoadState.LoadIdentifier()) {
|
||||
MOZ_ASSERT(aLoadState.URI(), "Cannot create a LoadState with a null URI!");
|
||||
mResultPrincipalURI = aLoadState.ResultPrincipalURI();
|
||||
mResultPrincipalURIIsSome = aLoadState.ResultPrincipalURIIsSome();
|
||||
@ -111,6 +92,7 @@ nsDocShellLoadState::nsDocShellLoadState(
|
||||
mIsFormSubmission = aLoadState.IsFormSubmission();
|
||||
mLoadType = aLoadState.LoadType();
|
||||
mTarget = aLoadState.Target();
|
||||
mTargetBrowsingContext = aLoadState.SourceBrowsingContext();
|
||||
mLoadFlags = aLoadState.LoadFlags();
|
||||
mFirstParty = aLoadState.FirstParty();
|
||||
mHasValidUserGestureActivation = aLoadState.HasValidUserGestureActivation();
|
||||
@ -132,7 +114,7 @@ nsDocShellLoadState::nsDocShellLoadState(
|
||||
mPostDataStream = aLoadState.PostDataStream();
|
||||
mHeadersStream = aLoadState.HeadersStream();
|
||||
mSrcdocData = aLoadState.SrcdocData();
|
||||
mLoadIdentifier = aLoadState.LoadIdentifier();
|
||||
mChannelInitialized = aLoadState.ChannelInitialized();
|
||||
}
|
||||
|
||||
nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
|
||||
@ -155,6 +137,7 @@ nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
|
||||
mLoadType(aOther.mLoadType),
|
||||
mSHEntry(aOther.mSHEntry),
|
||||
mTarget(aOther.mTarget),
|
||||
mTargetBrowsingContext(aOther.mTargetBrowsingContext),
|
||||
mPostDataStream(aOther.mPostDataStream),
|
||||
mHeadersStream(aOther.mHeadersStream),
|
||||
mSrcdocData(aOther.mSrcdocData),
|
||||
@ -169,12 +152,38 @@ nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
|
||||
mPendingRedirectedChannel(aOther.mPendingRedirectedChannel),
|
||||
mOriginalURIString(aOther.mOriginalURIString),
|
||||
mCancelContentJSEpoch(aOther.mCancelContentJSEpoch),
|
||||
mLoadIdentifier(aOther.mLoadIdentifier) {}
|
||||
mLoadIdentifier(aOther.mLoadIdentifier),
|
||||
mChannelInitialized(aOther.mChannelInitialized) {}
|
||||
|
||||
nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier)
|
||||
: mURI(aURI),
|
||||
mResultPrincipalURIIsSome(false),
|
||||
mKeepResultPrincipalURIIfSet(false),
|
||||
mLoadReplace(false),
|
||||
mInheritPrincipal(false),
|
||||
mPrincipalIsExplicit(false),
|
||||
mForceAllowDataURI(false),
|
||||
mOriginalFrameSrc(false),
|
||||
mIsFormSubmission(false),
|
||||
mLoadType(LOAD_NORMAL),
|
||||
mTarget(),
|
||||
mSrcdocData(VoidString()),
|
||||
mLoadFlags(0),
|
||||
mFirstParty(false),
|
||||
mHasValidUserGestureActivation(false),
|
||||
mTypeHint(VoidCString()),
|
||||
mFileName(VoidString()),
|
||||
mIsFromProcessingFrameAttributes(false),
|
||||
mLoadIdentifier(aLoadIdentifier),
|
||||
mChannelInitialized(false) {
|
||||
MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!");
|
||||
}
|
||||
|
||||
nsDocShellLoadState::~nsDocShellLoadState() {}
|
||||
|
||||
nsresult nsDocShellLoadState::CreateFromPendingChannel(
|
||||
nsIChannel* aPendingChannel, nsDocShellLoadState** aResult) {
|
||||
nsIChannel* aPendingChannel, uint64_t aLoadIdentifier,
|
||||
nsDocShellLoadState** aResult) {
|
||||
// Create the nsDocShellLoadState object with default state pulled from the
|
||||
// passed-in channel.
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
@ -183,7 +192,8 @@ nsresult nsDocShellLoadState::CreateFromPendingChannel(
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(uri);
|
||||
RefPtr<nsDocShellLoadState> loadState =
|
||||
new nsDocShellLoadState(uri, aLoadIdentifier);
|
||||
loadState->mPendingRedirectedChannel = aPendingChannel;
|
||||
|
||||
// Pull relevant state from the channel, and store it on the
|
||||
@ -535,6 +545,11 @@ void nsDocShellLoadState::SetSourceBrowsingContext(
|
||||
mSourceBrowsingContext = aSourceBrowsingContext;
|
||||
}
|
||||
|
||||
void nsDocShellLoadState::SetTargetBrowsingContext(
|
||||
BrowsingContext* aTargetBrowsingContext) {
|
||||
mTargetBrowsingContext = aTargetBrowsingContext;
|
||||
}
|
||||
|
||||
nsIURI* nsDocShellLoadState::BaseURI() const { return mBaseURI; }
|
||||
|
||||
void nsDocShellLoadState::SetBaseURI(nsIURI* aBaseURI) { mBaseURI = aBaseURI; }
|
||||
@ -858,6 +873,7 @@ DocShellLoadStateInit nsDocShellLoadState::Serialize() {
|
||||
loadState.IsFormSubmission() = mIsFormSubmission;
|
||||
loadState.LoadType() = mLoadType;
|
||||
loadState.Target() = mTarget;
|
||||
loadState.TargetBrowsingContext() = mTargetBrowsingContext;
|
||||
loadState.LoadFlags() = mLoadFlags;
|
||||
loadState.FirstParty() = mFirstParty;
|
||||
loadState.HasValidUserGestureActivation() = mHasValidUserGestureActivation;
|
||||
@ -881,5 +897,6 @@ DocShellLoadStateInit nsDocShellLoadState::Serialize() {
|
||||
loadState.SrcdocData() = mSrcdocData;
|
||||
loadState.ResultPrincipalURI() = mResultPrincipalURI;
|
||||
loadState.LoadIdentifier() = mLoadIdentifier;
|
||||
loadState.ChannelInitialized() = mChannelInitialized;
|
||||
return loadState;
|
||||
}
|
||||
|
@ -46,8 +46,10 @@ class nsDocShellLoadState final {
|
||||
explicit nsDocShellLoadState(
|
||||
const mozilla::dom::DocShellLoadStateInit& aLoadState);
|
||||
explicit nsDocShellLoadState(const nsDocShellLoadState& aOther);
|
||||
nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier);
|
||||
|
||||
static nsresult CreateFromPendingChannel(nsIChannel* aPendingChannel,
|
||||
uint64_t aLoadIdentifier,
|
||||
nsDocShellLoadState** aResult);
|
||||
|
||||
static nsresult CreateFromLoadURIOptions(
|
||||
@ -160,6 +162,12 @@ class nsDocShellLoadState final {
|
||||
|
||||
void SetSourceBrowsingContext(BrowsingContext* aSourceBrowsingContext);
|
||||
|
||||
const MaybeDiscarded<BrowsingContext>& TargetBrowsingContext() const {
|
||||
return mTargetBrowsingContext;
|
||||
}
|
||||
|
||||
void SetTargetBrowsingContext(BrowsingContext* aTargetBrowsingContext);
|
||||
|
||||
nsIURI* BaseURI() const;
|
||||
|
||||
void SetBaseURI(nsIURI* aBaseURI);
|
||||
@ -238,9 +246,14 @@ class nsDocShellLoadState final {
|
||||
return mCancelContentJSEpoch;
|
||||
}
|
||||
|
||||
void SetLoadIdentifier(uint64_t aIdent) { mLoadIdentifier = aIdent; }
|
||||
uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
|
||||
|
||||
void SetChannelInitialized(bool aInitilized) {
|
||||
mChannelInitialized = aInitilized;
|
||||
}
|
||||
|
||||
bool GetChannelInitialized() const { return mChannelInitialized; }
|
||||
|
||||
// When loading a document through nsDocShell::LoadURI(), a special set of
|
||||
// flags needs to be set based on other values in nsDocShellLoadState. This
|
||||
// function calculates those flags, before the LoadState is passed to
|
||||
@ -357,6 +370,10 @@ class nsDocShellLoadState final {
|
||||
// Target for load, like _content, _blank etc.
|
||||
nsString mTarget;
|
||||
|
||||
// When set, this is the Target Browsing Context for the navigation
|
||||
// after retargeting.
|
||||
MaybeDiscarded<BrowsingContext> mTargetBrowsingContext;
|
||||
|
||||
// Post data stream (if POSTing)
|
||||
nsCOMPtr<nsIInputStream> mPostDataStream;
|
||||
|
||||
@ -411,11 +428,14 @@ class nsDocShellLoadState final {
|
||||
// when initiating the load.
|
||||
mozilla::Maybe<int32_t> mCancelContentJSEpoch;
|
||||
|
||||
// An optional identifier that refers to a DocumentLoadListener
|
||||
// created in the parent process for this loads. DocumentChannels
|
||||
// created in the content process can use this to find and attach
|
||||
// to the in progress load.
|
||||
uint64_t mLoadIdentifier;
|
||||
// An identifier to make it possible to examine if two loads are
|
||||
// equal, and which browsing context they belong to (see
|
||||
// BrowsingContext::{Get, Set}CurrentLoadIdentifier)
|
||||
const uint64_t mLoadIdentifier;
|
||||
|
||||
// Optional value to indicate that a channel has been
|
||||
// pre-initialized in the parent process.
|
||||
bool mChannelInitialized;
|
||||
};
|
||||
|
||||
#endif /* nsDocShellLoadState_h__ */
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/BindContext.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "mozilla/dom/CustomEvent.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/HTMLFormControlsCollection.h"
|
||||
@ -27,6 +28,7 @@
|
||||
#include "nsContentList.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDocShell.h"
|
||||
#include "nsDocShellLoadState.h"
|
||||
#include "nsError.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsHTMLDocument.h"
|
||||
@ -102,7 +104,6 @@ HTMLFormElement::HTMLFormElement(
|
||||
mRequiredRadioButtonCounts(2),
|
||||
mValueMissingRadioGroups(2),
|
||||
mPendingSubmission(nullptr),
|
||||
mSubmittingRequest(nullptr),
|
||||
mDefaultSubmitElement(nullptr),
|
||||
mFirstSubmitInElements(nullptr),
|
||||
mFirstSubmitNotInElements(nullptr),
|
||||
@ -113,7 +114,6 @@ HTMLFormElement::HTMLFormElement(
|
||||
mFormNumber(-1),
|
||||
mGeneratingSubmit(false),
|
||||
mGeneratingReset(false),
|
||||
mIsSubmitting(false),
|
||||
mDeferSubmission(false),
|
||||
mNotifiedObservers(false),
|
||||
mNotifiedObserversResult(false),
|
||||
@ -142,10 +142,12 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement,
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedRadioButtons)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTargetContext)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
|
||||
nsGenericHTMLElement)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTargetContext)
|
||||
tmp->Clear();
|
||||
tmp->mExpandoAndGeneration.OwnerUnlinked();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
@ -634,16 +636,14 @@ nsresult HTMLFormElement::DoSubmit(Event* aEvent) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mIsSubmitting) {
|
||||
if (IsSubmitting()) {
|
||||
NS_WARNING("Preventing double form submission");
|
||||
// XXX Should this return an error?
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Mark us as submitting so that we don't try to submit again
|
||||
mIsSubmitting = true;
|
||||
NS_ASSERTION(!mWebProgress && !mSubmittingRequest,
|
||||
"Web progress / submitting request should not exist here!");
|
||||
mTargetContext = nullptr;
|
||||
mCurrentLoadId = Nothing();
|
||||
|
||||
UniquePtr<HTMLFormSubmission> submission;
|
||||
|
||||
@ -655,14 +655,10 @@ nsresult HTMLFormElement::DoSubmit(Event* aEvent) {
|
||||
// Don't raise an error if form cannot navigate.
|
||||
if (StaticPrefs::dom_formdata_event_enabled() &&
|
||||
rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
mIsSubmitting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
mIsSubmitting = false;
|
||||
return rv;
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// XXXbz if the script global is that for an sXBL/XBL2 doc, it won't
|
||||
// be a window...
|
||||
@ -678,8 +674,6 @@ nsresult HTMLFormElement::DoSubmit(Event* aEvent) {
|
||||
// defer this submission. let's remember it and return
|
||||
// without submitting
|
||||
mPendingSubmission = std::move(submission);
|
||||
// ensure reentrancy
|
||||
mIsSubmitting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -687,7 +681,6 @@ nsresult HTMLFormElement::DoSubmit(Event* aEvent) {
|
||||
// perform the submission
|
||||
//
|
||||
if (!submission) {
|
||||
mIsSubmitting = false;
|
||||
#ifdef DEBUG
|
||||
HTMLDialogElement* dialog = nullptr;
|
||||
for (nsIContent* parent = GetParent(); parent;
|
||||
@ -764,7 +757,6 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
|
||||
nsCOMPtr<nsIURI> actionURI = aFormSubmission->GetActionURL();
|
||||
if (!actionURI) {
|
||||
mIsSubmitting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -772,7 +764,6 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
Document* doc = GetComposedDoc();
|
||||
nsCOMPtr<nsIDocShell> container = doc ? doc->GetDocShell() : nullptr;
|
||||
if (!container || IsEditable()) {
|
||||
mIsSubmitting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -788,9 +779,6 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
// we're not submitting when submitting to a JS URL. That's kinda bogus, but
|
||||
// there we are.
|
||||
bool schemeIsJavaScript = actionURI->SchemeIs("javascript");
|
||||
if (schemeIsJavaScript) {
|
||||
mIsSubmitting = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Notify observers of submit
|
||||
@ -804,7 +792,6 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
}
|
||||
|
||||
if (cancelSubmit) {
|
||||
mIsSubmitting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -813,7 +800,6 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
NS_ENSURE_SUBMIT_SUCCESS(rv);
|
||||
|
||||
if (cancelSubmit) {
|
||||
mIsSubmitting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -821,6 +807,7 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
// Submit
|
||||
//
|
||||
nsCOMPtr<nsIDocShell> docShell;
|
||||
uint64_t currentLoadId = 0;
|
||||
|
||||
{
|
||||
AutoPopupStatePusher popupStatePusher(mSubmitPopupState);
|
||||
@ -835,31 +822,29 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
|
||||
nsAutoString target;
|
||||
aFormSubmission->GetTarget(target);
|
||||
rv = nsDocShell::Cast(container)->OnLinkClickSync(
|
||||
this, actionURI, target, VoidString(), postDataStream, nullptr, false,
|
||||
getter_AddRefs(docShell), getter_AddRefs(mSubmittingRequest),
|
||||
aFormSubmission->IsInitiatedFromUserInput());
|
||||
|
||||
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(actionURI);
|
||||
loadState->SetTarget(target);
|
||||
loadState->SetPostDataStream(postDataStream);
|
||||
loadState->SetFirstParty(true);
|
||||
loadState->SetIsFormSubmission(true);
|
||||
loadState->SetTriggeringPrincipal(NodePrincipal());
|
||||
loadState->SetPrincipalToInherit(NodePrincipal());
|
||||
loadState->SetCsp(GetCsp());
|
||||
|
||||
rv = nsDocShell::Cast(container)->OnLinkClickSync(this, loadState, false,
|
||||
NodePrincipal());
|
||||
NS_ENSURE_SUBMIT_SUCCESS(rv);
|
||||
|
||||
mTargetContext = loadState->TargetBrowsingContext().GetMaybeDiscarded();
|
||||
currentLoadId = loadState->GetLoadIdentifier();
|
||||
}
|
||||
|
||||
// Even if the submit succeeds, it's possible for there to be no docshell
|
||||
// or request; for example, if it's to a named anchor within the same page
|
||||
// the submit will not really do anything.
|
||||
if (docShell) {
|
||||
// If the channel is pending, we have to listen for web progress.
|
||||
bool pending = false;
|
||||
mSubmittingRequest->IsPending(&pending);
|
||||
if (pending && !schemeIsJavaScript) {
|
||||
nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
|
||||
NS_ASSERTION(webProgress, "nsIDocShell not converted to nsIWebProgress!");
|
||||
rv = webProgress->AddProgressListener(this,
|
||||
nsIWebProgress::NOTIFY_STATE_ALL);
|
||||
NS_ENSURE_SUBMIT_SUCCESS(rv);
|
||||
mWebProgress = do_GetWeakReference(webProgress);
|
||||
NS_ASSERTION(mWebProgress, "can't hold weak ref to webprogress!");
|
||||
} else {
|
||||
ForgetCurrentSubmission();
|
||||
}
|
||||
// Even if the submit succeeds, it's possible for there to be no
|
||||
// browsing context; for example, if it's to a named anchor within
|
||||
// the same page the submit will not really do anything.
|
||||
if (mTargetContext && !mTargetContext->IsDiscarded() && !schemeIsJavaScript) {
|
||||
mCurrentLoadId = Some(currentLoadId);
|
||||
} else {
|
||||
ForgetCurrentSubmission();
|
||||
}
|
||||
@ -869,8 +854,6 @@ nsresult HTMLFormElement::SubmitSubmission(
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-dialog
|
||||
nsresult HTMLFormElement::SubmitDialog(DialogFormSubmission* aFormSubmission) {
|
||||
mIsSubmitting = false;
|
||||
|
||||
// Close the dialog subject. If there is a result, let that be the return
|
||||
// value.
|
||||
HTMLDialogElement* dialog = aFormSubmission->DialogElement();
|
||||
@ -1824,13 +1807,10 @@ int32_t HTMLFormElement::Length() { return mControls->Length(); }
|
||||
|
||||
void HTMLFormElement::ForgetCurrentSubmission() {
|
||||
mNotifiedObservers = false;
|
||||
mIsSubmitting = false;
|
||||
mSubmittingRequest = nullptr;
|
||||
nsCOMPtr<nsIWebProgress> webProgress = do_QueryReferent(mWebProgress);
|
||||
if (webProgress) {
|
||||
webProgress->RemoveProgressListener(this);
|
||||
}
|
||||
mWebProgress = nullptr;
|
||||
|
||||
mTargetContext = nullptr;
|
||||
mCurrentLoadId = Nothing();
|
||||
}
|
||||
|
||||
bool HTMLFormElement::CheckFormValidity(
|
||||
@ -2497,5 +2477,12 @@ void HTMLFormElement::NodeInfoChanged(Document* aOldDoc) {
|
||||
mFormNumber = -1;
|
||||
}
|
||||
|
||||
bool HTMLFormElement::IsSubmitting() const {
|
||||
bool loading = mTargetContext && !mTargetContext->IsDiscarded() &&
|
||||
mCurrentLoadId &&
|
||||
mTargetContext->IsLoadingIdentifier(*mCurrentLoadId);
|
||||
return loading;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIForm.h"
|
||||
#include "nsIFormControl.h"
|
||||
@ -574,6 +575,12 @@ class HTMLFormElement final : public nsGenericHTMLElement,
|
||||
/** The web progress object we are currently listening to */
|
||||
nsWeakPtr mWebProgress;
|
||||
|
||||
/** The target browsing context, if any. */
|
||||
RefPtr<BrowsingContext> mTargetContext;
|
||||
/** The load identifier for the pending request created for a
|
||||
* submit, used to be able to block double submits. */
|
||||
Maybe<uint64_t> mCurrentLoadId;
|
||||
|
||||
/** The default submit element -- WEAK */
|
||||
nsGenericHTMLFormElement* mDefaultSubmitElement;
|
||||
|
||||
@ -619,8 +626,6 @@ class HTMLFormElement final : public nsGenericHTMLElement,
|
||||
bool mGeneratingSubmit;
|
||||
/** Whether we are currently processing a reset event or not */
|
||||
bool mGeneratingReset;
|
||||
/** Whether we are submitting currently */
|
||||
bool mIsSubmitting;
|
||||
/** Whether the submission is to be deferred in case a script triggers it */
|
||||
bool mDeferSubmission;
|
||||
/** Whether we notified NS_FORMSUBMIT_SUBJECT listeners already */
|
||||
@ -638,6 +643,8 @@ class HTMLFormElement final : public nsGenericHTMLElement,
|
||||
bool mIsFiringSubmissionEvents;
|
||||
|
||||
private:
|
||||
bool IsSubmitting() const;
|
||||
|
||||
NotNull<const Encoding*> GetSubmitEncoding();
|
||||
~HTMLFormElement();
|
||||
};
|
||||
|
@ -3433,8 +3433,8 @@ mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect(
|
||||
}
|
||||
|
||||
RefPtr<nsDocShellLoadState> loadState;
|
||||
rv = nsDocShellLoadState::CreateFromPendingChannel(newChannel,
|
||||
getter_AddRefs(loadState));
|
||||
rv = nsDocShellLoadState::CreateFromPendingChannel(
|
||||
newChannel, aArgs.loadIdentifier(), getter_AddRefs(loadState));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return IPC_OK();
|
||||
}
|
||||
@ -4073,7 +4073,7 @@ mozilla::ipc::IPCResult ContentChild::RecvInternalLoad(
|
||||
}
|
||||
BrowsingContext* context = aContext.get();
|
||||
|
||||
context->InternalLoad(aLoadState, nullptr, nullptr);
|
||||
context->InternalLoad(aLoadState);
|
||||
|
||||
if (aTakeFocus) {
|
||||
if (nsCOMPtr<nsPIDOMWindowOuter> domWin = context->GetDOMWindow()) {
|
||||
|
@ -263,6 +263,7 @@ struct DocShellLoadStateInit
|
||||
nsIContentSecurityPolicy Csp;
|
||||
|
||||
MaybeDiscardedBrowsingContext SourceBrowsingContext;
|
||||
MaybeDiscardedBrowsingContext TargetBrowsingContext;
|
||||
|
||||
nsCString? OriginalURIString;
|
||||
int32_t? CancelContentJSEpoch;
|
||||
@ -278,6 +279,8 @@ struct DocShellLoadStateInit
|
||||
// nsIChannel pendingRedirectedChannel; // sent through other mechanism
|
||||
|
||||
uint64_t LoadIdentifier;
|
||||
|
||||
bool ChannelInitialized;
|
||||
};
|
||||
|
||||
struct TimedChannelInfo
|
||||
|
@ -305,7 +305,7 @@ mozilla::ipc::IPCResult WindowGlobalParent::RecvInternalLoad(
|
||||
// FIXME: We should really initiate the load in the parent before bouncing
|
||||
// back down to the child.
|
||||
|
||||
targetBC->InternalLoad(aLoadState, nullptr, nullptr);
|
||||
targetBC->InternalLoad(aLoadState);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,9 @@ DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) {
|
||||
->GetBrowsingContext()
|
||||
->HasValidTransientUserGestureActivation();
|
||||
|
||||
GetDocShell()->GetBrowsingContext()->SetCurrentLoadIdentifier(
|
||||
Some(mLoadState->GetLoadIdentifier()));
|
||||
|
||||
gNeckoChild->SendPDocumentChannelConstructor(
|
||||
this, GetDocShell()->GetBrowsingContext(), args);
|
||||
|
||||
|
@ -36,7 +36,7 @@ bool DocumentChannelParent::Init(dom::CanonicalBrowsingContext* aContext,
|
||||
loadState->URI()->GetSpecOrDefault().get()));
|
||||
|
||||
RefPtr<DocumentLoadListener::OpenPromise> promise;
|
||||
if (loadState->GetLoadIdentifier()) {
|
||||
if (loadState->GetChannelInitialized()) {
|
||||
promise = DocumentLoadListener::ClaimParentLoad(
|
||||
getter_AddRefs(mDocumentLoadListener), loadState->GetLoadIdentifier());
|
||||
if (!promise) {
|
||||
|
@ -377,6 +377,7 @@ auto DocumentLoadListener::Open(
|
||||
OriginAttributes attrs;
|
||||
browsingContext->GetOriginAttributes(attrs);
|
||||
|
||||
mLoadIdentifier = aLoadState->GetLoadIdentifier();
|
||||
MOZ_DIAGNOSTIC_ASSERT_IF(browsingContext->GetParent(),
|
||||
browsingContext->GetParentWindowContext());
|
||||
|
||||
@ -525,9 +526,7 @@ auto DocumentLoadListener::Open(
|
||||
browsingContext->CreateSessionHistoryEntryForLoad(aLoadState, mChannel);
|
||||
}
|
||||
|
||||
if (auto* ctx = GetBrowsingContext()) {
|
||||
ctx->StartDocumentLoad(this);
|
||||
}
|
||||
browsingContext->StartDocumentLoad(this);
|
||||
|
||||
*aRv = NS_OK;
|
||||
mOpenPromise = new OpenPromise::Private(__func__);
|
||||
@ -540,7 +539,7 @@ auto DocumentLoadListener::Open(
|
||||
/* static */
|
||||
bool DocumentLoadListener::OpenFromParent(
|
||||
dom::CanonicalBrowsingContext* aBrowsingContext,
|
||||
nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId, uint64_t* aOutIdent) {
|
||||
nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId) {
|
||||
LOG(("DocumentLoadListener::OpenFromParent"));
|
||||
|
||||
// We currently only support passing nullptr for aLoadInfo for
|
||||
@ -616,7 +615,6 @@ bool DocumentLoadListener::OpenFromParent(
|
||||
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
|
||||
RedirectChannelRegistrar::GetOrCreate();
|
||||
uint64_t loadIdentifier = aLoadState->GetLoadIdentifier();
|
||||
*aOutIdent = loadIdentifier;
|
||||
rv = registrar->RegisterChannel(nullptr, loadIdentifier);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
// Register listener (as an nsIParentChannel) under our new identifier.
|
||||
@ -684,7 +682,7 @@ void DocumentLoadListener::Disconnect() {
|
||||
}
|
||||
|
||||
if (auto* ctx = GetBrowsingContext()) {
|
||||
ctx->EndDocumentLoad(this);
|
||||
ctx->EndDocumentLoad(mDoingProcessSwitch);
|
||||
}
|
||||
}
|
||||
|
||||
@ -777,6 +775,12 @@ void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
|
||||
"aResult=%x]",
|
||||
this, int(aResult)));
|
||||
|
||||
auto endDocumentLoad = MakeScopeExit([&]() {
|
||||
if (auto* ctx = GetBrowsingContext()) {
|
||||
ctx->EndDocumentLoad(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (mDoingProcessSwitch) {
|
||||
DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
|
||||
}
|
||||
@ -802,9 +806,6 @@ void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
|
||||
}
|
||||
mChannel->Cancel(aResult);
|
||||
mChannel->Resume();
|
||||
if (auto* ctx = GetBrowsingContext()) {
|
||||
ctx->EndDocumentLoad(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -960,7 +961,7 @@ bool DocumentLoadListener::ResumeSuspendedChannel(
|
||||
mChannel->Resume();
|
||||
|
||||
if (auto* ctx = GetBrowsingContext()) {
|
||||
ctx->EndDocumentLoad(this);
|
||||
ctx->EndDocumentLoad(mDoingProcessSwitch);
|
||||
}
|
||||
|
||||
return !mIsFinished;
|
||||
@ -977,6 +978,8 @@ void DocumentLoadListener::SerializeRedirectData(
|
||||
mChannel->GetOriginalURI(getter_AddRefs(aArgs.uri()));
|
||||
}
|
||||
|
||||
aArgs.loadIdentifier() = mLoadIdentifier;
|
||||
|
||||
// I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
|
||||
// clears the principal to inherit, which fails tests (probably because this
|
||||
// 'redirect' is usually just an implementation detail). It's also http
|
||||
|
@ -137,7 +137,7 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
|
||||
// and clean up.
|
||||
static bool OpenFromParent(dom::CanonicalBrowsingContext* aBrowsingContext,
|
||||
nsDocShellLoadState* aLoadState,
|
||||
uint64_t aOuterWindowId, uint64_t* aOutIdent);
|
||||
uint64_t aOuterWindowId);
|
||||
|
||||
// Ensures that a load identifier allocated by OpenFromParent has
|
||||
// been deregistered if it hasn't already been claimed.
|
||||
@ -228,6 +228,8 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
|
||||
uint32_t aLoadFlags,
|
||||
dom::ContentParent* aParent) const;
|
||||
|
||||
uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
|
||||
|
||||
protected:
|
||||
virtual ~DocumentLoadListener();
|
||||
|
||||
@ -440,6 +442,9 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
|
||||
// passed to the childChannel in order to identify it in the new process.
|
||||
uint64_t mCrossProcessRedirectIdentifier = 0;
|
||||
|
||||
// The id of the currently pending load.
|
||||
uint64_t mLoadIdentifier = 0;
|
||||
|
||||
// True if cancelled.
|
||||
bool mCancelled = false;
|
||||
|
||||
|
@ -468,6 +468,7 @@ struct RedirectToRealChannelArgs {
|
||||
nsString srcdocData;
|
||||
nsIURI baseUri;
|
||||
SessionHistoryInfoAndId? sessionHistoryInfo;
|
||||
uint64_t loadIdentifier;
|
||||
};
|
||||
|
||||
struct TimingStructArgs {
|
||||
|
@ -337,8 +337,9 @@ mozilla::ipc::IPCResult NeckoParent::RecvPDocumentChannelConstructor(
|
||||
}
|
||||
|
||||
if (!p->Init(aContext.get_canonical(), aArgs)) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
return IPC_FAIL(this, "Couldn't initialize DocumentChannel");
|
||||
}
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -169,6 +169,9 @@ NS_IMETHODIMP ParentProcessDocumentChannel::AsyncOpen(
|
||||
|
||||
gHttpHandler->OnOpeningDocumentRequest(this);
|
||||
|
||||
GetDocShell()->GetBrowsingContext()->SetCurrentLoadIdentifier(
|
||||
Some(mLoadState->GetLoadIdentifier()));
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
Maybe<dom::ClientInfo> initialClientInfo = mInitialClientInfo;
|
||||
auto promise = mDocumentLoadListener->Open(
|
||||
|
Loading…
Reference in New Issue
Block a user