/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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 "mozilla/DebugOnly.h" #include "nsLoadGroup.h" #include "nsArrayEnumerator.h" #include "nsCOMArray.h" #include "nsCOMPtr.h" #include "mozilla/Logging.h" #include "nsString.h" #include "nsTArray.h" #include "nsITimedChannel.h" #include "nsIInterfaceRequestor.h" #include "nsIRequestObserver.h" #include "nsIRequestContext.h" #include "CacheObserver.h" #include "MainThreadUtils.h" #include "mozilla/net/NeckoChild.h" namespace mozilla { namespace net { // // Log module for nsILoadGroup logging... // // To enable logging (see prlog.h for full details): // // set MOZ_LOG=LoadGroup:5 // set MOZ_LOG_FILE=network.log // // This enables LogLevel::Debug level information and places all output in // the file network.log. // static LazyLogModule gLoadGroupLog("LoadGroup"); #undef LOG #define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args) //////////////////////////////////////////////////////////////////////////////// class RequestMapEntry : public PLDHashEntryHdr { public: explicit RequestMapEntry(nsIRequest *aRequest) : mKey(aRequest) { } nsCOMPtr mKey; }; static bool RequestHashMatchEntry(const PLDHashEntryHdr *entry, const void *key) { const RequestMapEntry *e = static_cast(entry); const nsIRequest *request = static_cast(key); return e->mKey == request; } static void RequestHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) { RequestMapEntry *e = static_cast(entry); // An entry is being cleared, let the entry do its own cleanup. e->~RequestMapEntry(); } static void RequestHashInitEntry(PLDHashEntryHdr *entry, const void *key) { const nsIRequest *const_request = static_cast(key); nsIRequest *request = const_cast(const_request); // Initialize the entry with placement new new (entry) RequestMapEntry(request); } static const PLDHashTableOps sRequestHashOps = { PLDHashTable::HashVoidPtrKeyStub, RequestHashMatchEntry, PLDHashTable::MoveEntryStub, RequestHashClearEntry, RequestHashInitEntry }; static void RescheduleRequest(nsIRequest *aRequest, int32_t delta) { nsCOMPtr p = do_QueryInterface(aRequest); if (p) p->AdjustPriority(delta); } nsLoadGroup::nsLoadGroup(nsISupports* outer) : mForegroundCount(0) , mLoadFlags(LOAD_NORMAL) , mDefaultLoadFlags(0) , mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) , mStatus(NS_OK) , mPriority(PRIORITY_NORMAL) , mIsCanceling(false) , mDefaultLoadIsTimed(false) , mTimedRequests(0) , mCachedRequests(0) , mTimedNonCachedRequestsUntilOnEndPageLoad(0) { NS_INIT_AGGREGATED(outer); LOG(("LOADGROUP [%x]: Created.\n", this)); } nsLoadGroup::~nsLoadGroup() { DebugOnly rv = Cancel(NS_BINDING_ABORTED); NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed"); mDefaultLoadRequest = nullptr; if (mRequestContext) { nsID rcid; mRequestContext->GetID(&rcid); if (IsNeckoChild() && gNeckoChild) { char rcid_str[NSID_LENGTH]; rcid.ToProvidedString(rcid_str); nsCString rcid_nscs; rcid_nscs.AssignASCII(rcid_str); gNeckoChild->SendRemoveRequestContext(rcid_nscs); } else { mRequestContextService->RemoveRequestContext(rcid); } } LOG(("LOADGROUP [%x]: Destroyed.\n", this)); } //////////////////////////////////////////////////////////////////////////////// // nsISupports methods: NS_IMPL_AGGREGATED(nsLoadGroup) NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup) NS_INTERFACE_MAP_ENTRY(nsILoadGroup) NS_INTERFACE_MAP_ENTRY(nsPILoadGroupInternal) NS_INTERFACE_MAP_ENTRY(nsILoadGroupChild) NS_INTERFACE_MAP_ENTRY(nsIRequest) NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END //////////////////////////////////////////////////////////////////////////////// // nsIRequest methods: NS_IMETHODIMP nsLoadGroup::GetName(nsACString &result) { // XXX is this the right "name" for a load group? if (!mDefaultLoadRequest) { result.Truncate(); return NS_OK; } return mDefaultLoadRequest->GetName(result); } NS_IMETHODIMP nsLoadGroup::IsPending(bool *aResult) { *aResult = (mForegroundCount > 0) ? true : false; return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetStatus(nsresult *status) { if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) return mDefaultLoadRequest->GetStatus(status); *status = mStatus; return NS_OK; } static bool AppendRequestsToArray(PLDHashTable* aTable, nsTArray *aArray) { for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { auto e = static_cast(iter.Get()); nsIRequest *request = e->mKey; NS_ASSERTION(request, "What? Null key in PLDHashTable entry?"); bool ok = !!aArray->AppendElement(request); if (!ok) { break; } NS_ADDREF(request); } if (aArray->Length() != aTable->EntryCount()) { for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) { NS_RELEASE((*aArray)[i]); } return false; } return true; } NS_IMETHODIMP nsLoadGroup::Cancel(nsresult status) { MOZ_ASSERT(NS_IsMainThread()); NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code"); nsresult rv; uint32_t count = mRequests.EntryCount(); AutoTArray requests; if (!AppendRequestsToArray(&mRequests, &requests)) { return NS_ERROR_OUT_OF_MEMORY; } // set the load group status to our cancel status while we cancel // all our requests...once the cancel is done, we'll reset it... // mStatus = status; // Set the flag indicating that the loadgroup is being canceled... This // prevents any new channels from being added during the operation. // mIsCanceling = true; nsresult firstError = NS_OK; while (count > 0) { nsIRequest* request = requests.ElementAt(--count); NS_ASSERTION(request, "NULL request found in list."); if (!mRequests.Search(request)) { // |request| was removed already NS_RELEASE(request); continue; } if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { nsAutoCString nameStr; request->GetName(nameStr); LOG(("LOADGROUP [%x]: Canceling request %x %s.\n", this, request, nameStr.get())); } // // Remove the request from the load group... This may cause // the OnStopRequest notification to fire... // // XXX: What should the context be? // (void)RemoveRequest(request, nullptr, status); // Cancel the request... rv = request->Cancel(status); // Remember the first failure and return it... if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv; NS_RELEASE(request); } #if defined(DEBUG) NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty."); NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active."); #endif mStatus = NS_OK; mIsCanceling = false; return firstError; } NS_IMETHODIMP nsLoadGroup::Suspend() { nsresult rv, firstError; uint32_t count = mRequests.EntryCount(); AutoTArray requests; if (!AppendRequestsToArray(&mRequests, &requests)) { return NS_ERROR_OUT_OF_MEMORY; } firstError = NS_OK; // // Operate the elements from back to front so that if items get // get removed from the list it won't affect our iteration // while (count > 0) { nsIRequest* request = requests.ElementAt(--count); NS_ASSERTION(request, "NULL request found in list."); if (!request) continue; if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { nsAutoCString nameStr; request->GetName(nameStr); LOG(("LOADGROUP [%x]: Suspending request %x %s.\n", this, request, nameStr.get())); } // Suspend the request... rv = request->Suspend(); // Remember the first failure and return it... if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv; NS_RELEASE(request); } return firstError; } NS_IMETHODIMP nsLoadGroup::Resume() { nsresult rv, firstError; uint32_t count = mRequests.EntryCount(); AutoTArray requests; if (!AppendRequestsToArray(&mRequests, &requests)) { return NS_ERROR_OUT_OF_MEMORY; } firstError = NS_OK; // // Operate the elements from back to front so that if items get // get removed from the list it won't affect our iteration // while (count > 0) { nsIRequest* request = requests.ElementAt(--count); NS_ASSERTION(request, "NULL request found in list."); if (!request) continue; if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { nsAutoCString nameStr; request->GetName(nameStr); LOG(("LOADGROUP [%x]: Resuming request %x %s.\n", this, request, nameStr.get())); } // Resume the request... rv = request->Resume(); // Remember the first failure and return it... if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv; NS_RELEASE(request); } return firstError; } NS_IMETHODIMP nsLoadGroup::GetLoadFlags(uint32_t *aLoadFlags) { *aLoadFlags = mLoadFlags; return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags) { mLoadFlags = aLoadFlags; return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetLoadGroup(nsILoadGroup **loadGroup) { *loadGroup = mLoadGroup; NS_IF_ADDREF(*loadGroup); return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetLoadGroup(nsILoadGroup *loadGroup) { mLoadGroup = loadGroup; return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsILoadGroup methods: NS_IMETHODIMP nsLoadGroup::GetDefaultLoadRequest(nsIRequest * *aRequest) { *aRequest = mDefaultLoadRequest; NS_IF_ADDREF(*aRequest); return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetDefaultLoadRequest(nsIRequest *aRequest) { mDefaultLoadRequest = aRequest; // Inherit the group load flags from the default load request if (mDefaultLoadRequest) { mDefaultLoadRequest->GetLoadFlags(&mLoadFlags); // // Mask off any bits that are not part of the nsIRequest flags. // in particular, nsIChannel::LOAD_DOCUMENT_URI... // mLoadFlags &= nsIRequest::LOAD_REQUESTMASK; nsCOMPtr timedChannel = do_QueryInterface(aRequest); mDefaultLoadIsTimed = timedChannel != nullptr; if (mDefaultLoadIsTimed) { timedChannel->SetTimingEnabled(true); } } // Else, do not change the group's load flags (see bug 95981) return NS_OK; } NS_IMETHODIMP nsLoadGroup::AddRequest(nsIRequest *request, nsISupports* ctxt) { nsresult rv; if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { nsAutoCString nameStr; request->GetName(nameStr); LOG(("LOADGROUP [%x]: Adding request %x %s (count=%d).\n", this, request, nameStr.get(), mRequests.EntryCount())); } NS_ASSERTION(!mRequests.Search(request), "Entry added to loadgroup twice, don't do that"); // // Do not add the channel, if the loadgroup is being canceled... // if (mIsCanceling) { LOG(("LOADGROUP [%x]: AddChannel() ABORTED because LoadGroup is" " being canceled!!\n", this)); return NS_BINDING_ABORTED; } nsLoadFlags flags; // if the request is the default load request or if the default load // request is null, then the load group should inherit its load flags from // the request, but also we need to enforce defaultLoadFlags. if (mDefaultLoadRequest == request || !mDefaultLoadRequest) { rv = MergeDefaultLoadFlags(request, flags); } else { rv = MergeLoadFlags(request, flags); } if (NS_FAILED(rv)) return rv; // // Add the request to the list of active requests... // auto entry = static_cast(mRequests.Add(request, fallible)); if (!entry) { return NS_ERROR_OUT_OF_MEMORY; } if (mPriority != 0) RescheduleRequest(request, mPriority); nsCOMPtr timedChannel = do_QueryInterface(request); if (timedChannel) timedChannel->SetTimingEnabled(true); if (!(flags & nsIRequest::LOAD_BACKGROUND)) { // Update the count of foreground URIs.. mForegroundCount += 1; // // Fire the OnStartRequest notification out to the observer... // // If the notification fails then DO NOT add the request to // the load group. // nsCOMPtr observer = do_QueryReferent(mObserver); if (observer) { LOG(("LOADGROUP [%x]: Firing OnStartRequest for request %x." "(foreground count=%d).\n", this, request, mForegroundCount)); rv = observer->OnStartRequest(request, ctxt); if (NS_FAILED(rv)) { LOG(("LOADGROUP [%x]: OnStartRequest for request %x FAILED.\n", this, request)); // // The URI load has been canceled by the observer. Clean up // the damage... // mRequests.Remove(request); rv = NS_OK; mForegroundCount -= 1; } } // Ensure that we're part of our loadgroup while pending if (mForegroundCount == 1 && mLoadGroup) { mLoadGroup->AddRequest(this, nullptr); } } return rv; } NS_IMETHODIMP nsLoadGroup::RemoveRequest(nsIRequest *request, nsISupports* ctxt, nsresult aStatus) { NS_ENSURE_ARG_POINTER(request); nsresult rv; if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { nsAutoCString nameStr; request->GetName(nameStr); LOG(("LOADGROUP [%x]: Removing request %x %s status %x (count=%d).\n", this, request, nameStr.get(), aStatus, mRequests.EntryCount() - 1)); } // Make sure we have a owning reference to the request we're about // to remove. nsCOMPtr kungFuDeathGrip(request); // // Remove the request from the group. If this fails, it means that // the request was *not* in the group so do not update the foreground // count or it will get messed up... // auto entry = static_cast(mRequests.Search(request)); if (!entry) { LOG(("LOADGROUP [%x]: Unable to remove request %x. Not in group!\n", this, request)); return NS_ERROR_FAILURE; } mRequests.RemoveEntry(entry); // Undo any group priority delta... if (mPriority != 0) RescheduleRequest(request, -mPriority); nsLoadFlags flags; rv = request->GetLoadFlags(&flags); if (NS_FAILED(rv)) return rv; if (!(flags & nsIRequest::LOAD_BACKGROUND)) { NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up"); mForegroundCount -= 1; // Fire the OnStopRequest out to the observer... nsCOMPtr observer = do_QueryReferent(mObserver); if (observer) { LOG(("LOADGROUP [%x]: Firing OnStopRequest for request %x." "(foreground count=%d).\n", this, request, mForegroundCount)); rv = observer->OnStopRequest(request, ctxt, aStatus); if (NS_FAILED(rv)) { LOG(("LOADGROUP [%x]: OnStopRequest for request %x FAILED.\n", this, request)); } } // If that was the last request -> remove ourselves from loadgroup if (mForegroundCount == 0 && mLoadGroup) { mLoadGroup->RemoveRequest(this, nullptr, aStatus); } } return rv; } NS_IMETHODIMP nsLoadGroup::GetRequests(nsISimpleEnumerator * *aRequests) { nsCOMArray requests; requests.SetCapacity(mRequests.EntryCount()); for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) { auto e = static_cast(iter.Get()); requests.AppendObject(e->mKey); } return NS_NewArrayEnumerator(aRequests, requests); } NS_IMETHODIMP nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) { mObserver = do_GetWeakReference(aObserver); return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetGroupObserver(nsIRequestObserver* *aResult) { nsCOMPtr observer = do_QueryReferent(mObserver); *aResult = observer; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetActiveCount(uint32_t* aResult) { *aResult = mForegroundCount; return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) { NS_ENSURE_ARG_POINTER(aCallbacks); *aCallbacks = mCallbacks; NS_IF_ADDREF(*aCallbacks); return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) { mCallbacks = aCallbacks; return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetRequestContextID(nsID *aRCID) { if (!mRequestContext) { return NS_ERROR_NOT_AVAILABLE; } return mRequestContext->GetID(aRCID); } //////////////////////////////////////////////////////////////////////////////// // nsILoadGroupChild methods: NS_IMETHODIMP nsLoadGroup::GetParentLoadGroup(nsILoadGroup * *aParentLoadGroup) { *aParentLoadGroup = nullptr; nsCOMPtr parent = do_QueryReferent(mParentLoadGroup); if (!parent) return NS_OK; parent.forget(aParentLoadGroup); return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetParentLoadGroup(nsILoadGroup *aParentLoadGroup) { mParentLoadGroup = do_GetWeakReference(aParentLoadGroup); return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetChildLoadGroup(nsILoadGroup * *aChildLoadGroup) { NS_ADDREF(*aChildLoadGroup = this); return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetRootLoadGroup(nsILoadGroup * *aRootLoadGroup) { // first recursively try the root load group of our parent nsCOMPtr ancestor = do_QueryReferent(mParentLoadGroup); if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup); // next recursively try the root load group of our own load grop ancestor = do_QueryInterface(mLoadGroup); if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup); // finally just return this NS_ADDREF(*aRootLoadGroup = this); return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsPILoadGroupInternal methods: NS_IMETHODIMP nsLoadGroup::OnEndPageLoad(nsIChannel *aDefaultChannel) { // for the moment, nothing to do here. return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsISupportsPriority methods: NS_IMETHODIMP nsLoadGroup::GetPriority(int32_t *aValue) { *aValue = mPriority; return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetPriority(int32_t aValue) { return AdjustPriority(aValue - mPriority); } NS_IMETHODIMP nsLoadGroup::AdjustPriority(int32_t aDelta) { // Update the priority for each request that supports nsISupportsPriority if (aDelta != 0) { mPriority += aDelta; for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) { auto e = static_cast(iter.Get()); RescheduleRequest(e->mKey, aDelta); } } return NS_OK; } NS_IMETHODIMP nsLoadGroup::GetDefaultLoadFlags(uint32_t *aFlags) { *aFlags = mDefaultLoadFlags; return NS_OK; } NS_IMETHODIMP nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) { mDefaultLoadFlags = aFlags; return NS_OK; } //////////////////////////////////////////////////////////////////////////////// nsresult nsLoadGroup::MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& outFlags) { nsresult rv; nsLoadFlags flags, oldFlags; rv = aRequest->GetLoadFlags(&flags); if (NS_FAILED(rv)) { return rv; } oldFlags = flags; // Inherit the following bits... flags |= (mLoadFlags & (LOAD_BACKGROUND | LOAD_BYPASS_CACHE | LOAD_FROM_CACHE | VALIDATE_ALWAYS | VALIDATE_ONCE_PER_SESSION | VALIDATE_NEVER)); // ... and force the default flags. flags |= mDefaultLoadFlags; if (flags != oldFlags) { rv = aRequest->SetLoadFlags(flags); } outFlags = flags; return rv; } nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest *aRequest, nsLoadFlags& outFlags) { nsresult rv; nsLoadFlags flags, oldFlags; rv = aRequest->GetLoadFlags(&flags); if (NS_FAILED(rv)) { return rv; } oldFlags = flags; // ... and force the default flags. flags |= mDefaultLoadFlags; if (flags != oldFlags) { rv = aRequest->SetLoadFlags(flags); } outFlags = flags; return rv; } nsresult nsLoadGroup::Init() { mRequestContextService = do_GetService("@mozilla.org/network/request-context-service;1"); if (mRequestContextService) { nsID requestContextID; if (NS_SUCCEEDED(mRequestContextService->NewRequestContextID(&requestContextID))) { mRequestContextService->GetRequestContext(requestContextID, getter_AddRefs(mRequestContext)); } } return NS_OK; } } // namespace net } // namespace mozilla #undef LOG