michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set sw=4 ts=4 sts=4 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "nsLoadGroup.h" michael@0: michael@0: #include "nsArrayEnumerator.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "prlog.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "mozilla/Atomics.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "mozilla/net/PSpdyPush.h" michael@0: #include "nsITimedChannel.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIRequestObserver.h" michael@0: #include "CacheObserver.h" michael@0: #include "MainThreadUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for nsILoadGroup logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=LoadGroup:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: static PRLogModuleInfo* gLoadGroupLog = nullptr; michael@0: #endif michael@0: michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(gLoadGroupLog, PR_LOG_DEBUG, args) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: class RequestMapEntry : public PLDHashEntryHdr michael@0: { michael@0: public: michael@0: RequestMapEntry(nsIRequest *aRequest) : michael@0: mKey(aRequest) michael@0: { michael@0: } michael@0: michael@0: nsCOMPtr mKey; michael@0: }; michael@0: michael@0: static bool michael@0: RequestHashMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *entry, michael@0: const void *key) michael@0: { michael@0: const RequestMapEntry *e = michael@0: static_cast(entry); michael@0: const nsIRequest *request = static_cast(key); michael@0: michael@0: return e->mKey == request; michael@0: } michael@0: michael@0: static void michael@0: RequestHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) michael@0: { michael@0: RequestMapEntry *e = static_cast(entry); michael@0: michael@0: // An entry is being cleared, let the entry do its own cleanup. michael@0: e->~RequestMapEntry(); michael@0: } michael@0: michael@0: static bool michael@0: RequestHashInitEntry(PLDHashTable *table, PLDHashEntryHdr *entry, michael@0: const void *key) michael@0: { michael@0: const nsIRequest *const_request = static_cast(key); michael@0: nsIRequest *request = const_cast(const_request); michael@0: michael@0: // Initialize the entry with placement new michael@0: new (entry) RequestMapEntry(request); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: static void michael@0: RescheduleRequest(nsIRequest *aRequest, int32_t delta) michael@0: { michael@0: nsCOMPtr p = do_QueryInterface(aRequest); michael@0: if (p) michael@0: p->AdjustPriority(delta); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: RescheduleRequests(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: RequestMapEntry *e = static_cast(hdr); michael@0: int32_t *delta = static_cast(arg); michael@0: michael@0: RescheduleRequest(e->mKey, *delta); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: michael@0: nsLoadGroup::nsLoadGroup(nsISupports* outer) michael@0: : mForegroundCount(0) michael@0: , mLoadFlags(LOAD_NORMAL) michael@0: , mDefaultLoadFlags(0) michael@0: , mStatus(NS_OK) michael@0: , mPriority(PRIORITY_NORMAL) michael@0: , mIsCanceling(false) michael@0: , mDefaultLoadIsTimed(false) michael@0: , mTimedRequests(0) michael@0: , mCachedRequests(0) michael@0: , mTimedNonCachedRequestsUntilOnEndPageLoad(0) michael@0: { michael@0: NS_INIT_AGGREGATED(outer); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // Initialize the global PRLogModule for nsILoadGroup logging michael@0: if (nullptr == gLoadGroupLog) michael@0: gLoadGroupLog = PR_NewLogModule("LoadGroup"); michael@0: #endif michael@0: michael@0: LOG(("LOADGROUP [%x]: Created.\n", this)); michael@0: michael@0: // Initialize the ops in the hash to null to make sure we get michael@0: // consistent errors if someone fails to call ::Init() on an michael@0: // nsLoadGroup. michael@0: mRequests.ops = nullptr; michael@0: } michael@0: michael@0: nsLoadGroup::~nsLoadGroup() michael@0: { michael@0: DebugOnly rv = Cancel(NS_BINDING_ABORTED); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed"); michael@0: michael@0: if (mRequests.ops) { michael@0: PL_DHashTableFinish(&mRequests); michael@0: } michael@0: michael@0: mDefaultLoadRequest = 0; michael@0: michael@0: LOG(("LOADGROUP [%x]: Destroyed.\n", this)); michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsISupports methods: michael@0: michael@0: NS_IMPL_AGGREGATED(nsLoadGroup) michael@0: NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup) michael@0: NS_INTERFACE_MAP_ENTRY(nsILoadGroup) michael@0: NS_INTERFACE_MAP_ENTRY(nsPILoadGroupInternal) michael@0: NS_INTERFACE_MAP_ENTRY(nsILoadGroupChild) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIRequest methods: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetName(nsACString &result) michael@0: { michael@0: // XXX is this the right "name" for a load group? michael@0: michael@0: if (!mDefaultLoadRequest) { michael@0: result.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mDefaultLoadRequest->GetName(result); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::IsPending(bool *aResult) michael@0: { michael@0: *aResult = (mForegroundCount > 0) ? true : false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetStatus(nsresult *status) michael@0: { michael@0: if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) michael@0: return mDefaultLoadRequest->GetStatus(status); michael@0: michael@0: *status = mStatus; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // PLDHashTable enumeration callback that appends strong references to michael@0: // all nsIRequest to an nsTArray. michael@0: static PLDHashOperator michael@0: AppendRequestsToArray(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: RequestMapEntry *e = static_cast(hdr); michael@0: nsTArray *array = static_cast *>(arg); michael@0: michael@0: nsIRequest *request = e->mKey; michael@0: NS_ASSERTION(request, "What? Null key in pldhash entry?"); michael@0: michael@0: bool ok = array->AppendElement(request) != nullptr; michael@0: michael@0: if (!ok) { michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: NS_ADDREF(request); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::Cancel(nsresult status) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code"); michael@0: nsresult rv; michael@0: uint32_t count = mRequests.entryCount; michael@0: michael@0: nsAutoTArray requests; michael@0: michael@0: PL_DHashTableEnumerate(&mRequests, AppendRequestsToArray, michael@0: static_cast *>(&requests)); michael@0: michael@0: if (requests.Length() != count) { michael@0: for (uint32_t i = 0, len = requests.Length(); i < len; ++i) { michael@0: NS_RELEASE(requests[i]); michael@0: } michael@0: michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // set the load group status to our cancel status while we cancel michael@0: // all our requests...once the cancel is done, we'll reset it... michael@0: // michael@0: mStatus = status; michael@0: michael@0: // Set the flag indicating that the loadgroup is being canceled... This michael@0: // prevents any new channels from being added during the operation. michael@0: // michael@0: mIsCanceling = true; michael@0: michael@0: nsresult firstError = NS_OK; michael@0: michael@0: while (count > 0) { michael@0: nsIRequest* request = requests.ElementAt(--count); michael@0: michael@0: NS_ASSERTION(request, "NULL request found in list."); michael@0: michael@0: RequestMapEntry *entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mRequests, request, michael@0: PL_DHASH_LOOKUP)); michael@0: michael@0: if (PL_DHASH_ENTRY_IS_FREE(entry)) { michael@0: // |request| was removed already michael@0: michael@0: NS_RELEASE(request); michael@0: michael@0: continue; michael@0: } michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString nameStr; michael@0: request->GetName(nameStr); michael@0: LOG(("LOADGROUP [%x]: Canceling request %x %s.\n", michael@0: this, request, nameStr.get())); michael@0: #endif michael@0: michael@0: // michael@0: // Remove the request from the load group... This may cause michael@0: // the OnStopRequest notification to fire... michael@0: // michael@0: // XXX: What should the context be? michael@0: // michael@0: (void)RemoveRequest(request, nullptr, status); michael@0: michael@0: // Cancel the request... michael@0: rv = request->Cancel(status); michael@0: michael@0: // Remember the first failure and return it... michael@0: if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) michael@0: firstError = rv; michael@0: michael@0: NS_RELEASE(request); michael@0: } michael@0: michael@0: #if defined(DEBUG) michael@0: NS_ASSERTION(mRequests.entryCount == 0, "Request list is not empty."); michael@0: NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active."); michael@0: #endif michael@0: michael@0: mStatus = NS_OK; michael@0: mIsCanceling = false; michael@0: michael@0: return firstError; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::Suspend() michael@0: { michael@0: nsresult rv, firstError; michael@0: uint32_t count = mRequests.entryCount; michael@0: michael@0: nsAutoTArray requests; michael@0: michael@0: PL_DHashTableEnumerate(&mRequests, AppendRequestsToArray, michael@0: static_cast *>(&requests)); michael@0: michael@0: if (requests.Length() != count) { michael@0: for (uint32_t i = 0, len = requests.Length(); i < len; ++i) { michael@0: NS_RELEASE(requests[i]); michael@0: } michael@0: michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: firstError = NS_OK; michael@0: // michael@0: // Operate the elements from back to front so that if items get michael@0: // get removed from the list it won't affect our iteration michael@0: // michael@0: while (count > 0) { michael@0: nsIRequest* request = requests.ElementAt(--count); michael@0: michael@0: NS_ASSERTION(request, "NULL request found in list."); michael@0: if (!request) michael@0: continue; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString nameStr; michael@0: request->GetName(nameStr); michael@0: LOG(("LOADGROUP [%x]: Suspending request %x %s.\n", michael@0: this, request, nameStr.get())); michael@0: #endif michael@0: michael@0: // Suspend the request... michael@0: rv = request->Suspend(); michael@0: michael@0: // Remember the first failure and return it... michael@0: if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) michael@0: firstError = rv; michael@0: michael@0: NS_RELEASE(request); michael@0: } michael@0: michael@0: return firstError; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::Resume() michael@0: { michael@0: nsresult rv, firstError; michael@0: uint32_t count = mRequests.entryCount; michael@0: michael@0: nsAutoTArray requests; michael@0: michael@0: PL_DHashTableEnumerate(&mRequests, AppendRequestsToArray, michael@0: static_cast *>(&requests)); michael@0: michael@0: if (requests.Length() != count) { michael@0: for (uint32_t i = 0, len = requests.Length(); i < len; ++i) { michael@0: NS_RELEASE(requests[i]); michael@0: } michael@0: michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: firstError = NS_OK; michael@0: // michael@0: // Operate the elements from back to front so that if items get michael@0: // get removed from the list it won't affect our iteration michael@0: // michael@0: while (count > 0) { michael@0: nsIRequest* request = requests.ElementAt(--count); michael@0: michael@0: NS_ASSERTION(request, "NULL request found in list."); michael@0: if (!request) michael@0: continue; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString nameStr; michael@0: request->GetName(nameStr); michael@0: LOG(("LOADGROUP [%x]: Resuming request %x %s.\n", michael@0: this, request, nameStr.get())); michael@0: #endif michael@0: michael@0: // Resume the request... michael@0: rv = request->Resume(); michael@0: michael@0: // Remember the first failure and return it... michael@0: if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) michael@0: firstError = rv; michael@0: michael@0: NS_RELEASE(request); michael@0: } michael@0: michael@0: return firstError; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetLoadFlags(uint32_t *aLoadFlags) michael@0: { michael@0: *aLoadFlags = mLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags) michael@0: { michael@0: mLoadFlags = aLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetLoadGroup(nsILoadGroup **loadGroup) michael@0: { michael@0: *loadGroup = mLoadGroup; michael@0: NS_IF_ADDREF(*loadGroup); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetLoadGroup(nsILoadGroup *loadGroup) michael@0: { michael@0: mLoadGroup = loadGroup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsILoadGroup methods: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetDefaultLoadRequest(nsIRequest * *aRequest) michael@0: { michael@0: *aRequest = mDefaultLoadRequest; michael@0: NS_IF_ADDREF(*aRequest); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetDefaultLoadRequest(nsIRequest *aRequest) michael@0: { michael@0: mDefaultLoadRequest = aRequest; michael@0: // Inherit the group load flags from the default load request michael@0: if (mDefaultLoadRequest) { michael@0: mDefaultLoadRequest->GetLoadFlags(&mLoadFlags); michael@0: // michael@0: // Mask off any bits that are not part of the nsIRequest flags. michael@0: // in particular, nsIChannel::LOAD_DOCUMENT_URI... michael@0: // michael@0: mLoadFlags &= nsIRequest::LOAD_REQUESTMASK; michael@0: michael@0: nsCOMPtr timedChannel = do_QueryInterface(aRequest); michael@0: mDefaultLoadIsTimed = timedChannel != nullptr; michael@0: if (mDefaultLoadIsTimed) { michael@0: timedChannel->GetChannelCreation(&mDefaultRequestCreationTime); michael@0: timedChannel->SetTimingEnabled(true); michael@0: } michael@0: } michael@0: // Else, do not change the group's load flags (see bug 95981) michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::AddRequest(nsIRequest *request, nsISupports* ctxt) michael@0: { michael@0: nsresult rv; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: { michael@0: nsAutoCString nameStr; michael@0: request->GetName(nameStr); michael@0: LOG(("LOADGROUP [%x]: Adding request %x %s (count=%d).\n", michael@0: this, request, nameStr.get(), mRequests.entryCount)); michael@0: } michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: RequestMapEntry *entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mRequests, request, michael@0: PL_DHASH_LOOKUP)); michael@0: michael@0: NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(entry), michael@0: "Entry added to loadgroup twice, don't do that"); michael@0: } michael@0: #endif michael@0: michael@0: // michael@0: // Do not add the channel, if the loadgroup is being canceled... michael@0: // michael@0: if (mIsCanceling) { michael@0: michael@0: #if defined(PR_LOGGING) michael@0: LOG(("LOADGROUP [%x]: AddChannel() ABORTED because LoadGroup is" michael@0: " being canceled!!\n", this)); michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: nsLoadFlags flags; michael@0: // if the request is the default load request or if the default michael@0: // load request is null, then the load group should inherit its michael@0: // load flags from the request. michael@0: if (mDefaultLoadRequest == request || !mDefaultLoadRequest) michael@0: rv = request->GetLoadFlags(&flags); michael@0: else michael@0: rv = MergeLoadFlags(request, flags); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // michael@0: // Add the request to the list of active requests... michael@0: // michael@0: michael@0: RequestMapEntry *entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mRequests, request, michael@0: PL_DHASH_ADD)); michael@0: michael@0: if (!entry) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (mPriority != 0) michael@0: RescheduleRequest(request, mPriority); michael@0: michael@0: nsCOMPtr timedChannel = do_QueryInterface(request); michael@0: if (timedChannel) michael@0: timedChannel->SetTimingEnabled(true); michael@0: michael@0: if (!(flags & nsIRequest::LOAD_BACKGROUND)) { michael@0: // Update the count of foreground URIs.. michael@0: mForegroundCount += 1; michael@0: michael@0: // michael@0: // Fire the OnStartRequest notification out to the observer... michael@0: // michael@0: // If the notification fails then DO NOT add the request to michael@0: // the load group. michael@0: // michael@0: nsCOMPtr observer = do_QueryReferent(mObserver); michael@0: if (observer) { michael@0: LOG(("LOADGROUP [%x]: Firing OnStartRequest for request %x." michael@0: "(foreground count=%d).\n", this, request, mForegroundCount)); michael@0: michael@0: rv = observer->OnStartRequest(request, ctxt); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("LOADGROUP [%x]: OnStartRequest for request %x FAILED.\n", michael@0: this, request)); michael@0: // michael@0: // The URI load has been canceled by the observer. Clean up michael@0: // the damage... michael@0: // michael@0: michael@0: PL_DHashTableOperate(&mRequests, request, PL_DHASH_REMOVE); michael@0: michael@0: rv = NS_OK; michael@0: michael@0: mForegroundCount -= 1; michael@0: } michael@0: } michael@0: michael@0: // Ensure that we're part of our loadgroup while pending michael@0: if (mForegroundCount == 1 && mLoadGroup) { michael@0: mLoadGroup->AddRequest(this, nullptr); michael@0: } michael@0: michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::RemoveRequest(nsIRequest *request, nsISupports* ctxt, michael@0: nsresult aStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(request); michael@0: nsresult rv; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: { michael@0: nsAutoCString nameStr; michael@0: request->GetName(nameStr); michael@0: LOG(("LOADGROUP [%x]: Removing request %x %s status %x (count=%d).\n", michael@0: this, request, nameStr.get(), aStatus, mRequests.entryCount-1)); michael@0: } michael@0: #endif michael@0: michael@0: // Make sure we have a owning reference to the request we're about michael@0: // to remove. michael@0: michael@0: nsCOMPtr kungFuDeathGrip(request); michael@0: michael@0: // michael@0: // Remove the request from the group. If this fails, it means that michael@0: // the request was *not* in the group so do not update the foreground michael@0: // count or it will get messed up... michael@0: // michael@0: RequestMapEntry *entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mRequests, request, michael@0: PL_DHASH_LOOKUP)); michael@0: michael@0: if (PL_DHASH_ENTRY_IS_FREE(entry)) { michael@0: LOG(("LOADGROUP [%x]: Unable to remove request %x. Not in group!\n", michael@0: this, request)); michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: PL_DHashTableRawRemove(&mRequests, entry); michael@0: michael@0: // Collect telemetry stats only when default request is a timed channel. michael@0: // Don't include failed requests in the timing statistics. michael@0: if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) { michael@0: nsCOMPtr timedChannel = do_QueryInterface(request); michael@0: if (timedChannel) { michael@0: // Figure out if this request was served from the cache michael@0: ++mTimedRequests; michael@0: TimeStamp timeStamp; michael@0: rv = timedChannel->GetCacheReadStart(&timeStamp); michael@0: if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) { michael@0: ++mCachedRequests; michael@0: } michael@0: else { michael@0: mTimedNonCachedRequestsUntilOnEndPageLoad++; michael@0: } michael@0: michael@0: rv = timedChannel->GetAsyncOpen(&timeStamp); michael@0: if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) { michael@0: Telemetry::AccumulateTimeDelta( michael@0: Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME, michael@0: mDefaultRequestCreationTime, timeStamp); michael@0: } michael@0: michael@0: rv = timedChannel->GetResponseStart(&timeStamp); michael@0: if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) { michael@0: Telemetry::AccumulateTimeDelta( michael@0: Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME, michael@0: mDefaultRequestCreationTime, timeStamp); michael@0: } michael@0: michael@0: TelemetryReportChannel(timedChannel, false); michael@0: } michael@0: } michael@0: michael@0: if (mRequests.entryCount == 0) { michael@0: TelemetryReport(); michael@0: } michael@0: michael@0: // Undo any group priority delta... michael@0: if (mPriority != 0) michael@0: RescheduleRequest(request, -mPriority); michael@0: michael@0: nsLoadFlags flags; michael@0: rv = request->GetLoadFlags(&flags); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (!(flags & nsIRequest::LOAD_BACKGROUND)) { michael@0: NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up"); michael@0: mForegroundCount -= 1; michael@0: michael@0: // Fire the OnStopRequest out to the observer... michael@0: nsCOMPtr observer = do_QueryReferent(mObserver); michael@0: if (observer) { michael@0: LOG(("LOADGROUP [%x]: Firing OnStopRequest for request %x." michael@0: "(foreground count=%d).\n", this, request, mForegroundCount)); michael@0: michael@0: rv = observer->OnStopRequest(request, ctxt, aStatus); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("LOADGROUP [%x]: OnStopRequest for request %x FAILED.\n", michael@0: this, request)); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // If that was the last request -> remove ourselves from loadgroup michael@0: if (mForegroundCount == 0 && mLoadGroup) { michael@0: mLoadGroup->RemoveRequest(this, nullptr, aStatus); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // PLDHashTable enumeration callback that appends all items in the michael@0: // hash to an nsCOMArray michael@0: static PLDHashOperator michael@0: AppendRequestsToCOMArray(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: RequestMapEntry *e = static_cast(hdr); michael@0: static_cast*>(arg)->AppendObject(e->mKey); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetRequests(nsISimpleEnumerator * *aRequests) michael@0: { michael@0: nsCOMArray requests; michael@0: requests.SetCapacity(mRequests.entryCount); michael@0: michael@0: PL_DHashTableEnumerate(&mRequests, AppendRequestsToCOMArray, &requests); michael@0: michael@0: return NS_NewArrayEnumerator(aRequests, requests); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) michael@0: { michael@0: mObserver = do_GetWeakReference(aObserver); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetGroupObserver(nsIRequestObserver* *aResult) michael@0: { michael@0: nsCOMPtr observer = do_QueryReferent(mObserver); michael@0: *aResult = observer; michael@0: NS_IF_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetActiveCount(uint32_t* aResult) michael@0: { michael@0: *aResult = mForegroundCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCallbacks); michael@0: *aCallbacks = mCallbacks; michael@0: NS_IF_ADDREF(*aCallbacks); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) michael@0: { michael@0: mCallbacks = aCallbacks; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetConnectionInfo(nsILoadGroupConnectionInfo **aCI) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCI); michael@0: *aCI = mConnectionInfo; michael@0: NS_IF_ADDREF(*aCI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsILoadGroupChild methods: michael@0: michael@0: /* attribute nsILoadGroup parentLoadGroup; */ michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetParentLoadGroup(nsILoadGroup * *aParentLoadGroup) michael@0: { michael@0: *aParentLoadGroup = nullptr; michael@0: nsCOMPtr parent = do_QueryReferent(mParentLoadGroup); michael@0: if (!parent) michael@0: return NS_OK; michael@0: parent.forget(aParentLoadGroup); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetParentLoadGroup(nsILoadGroup *aParentLoadGroup) michael@0: { michael@0: mParentLoadGroup = do_GetWeakReference(aParentLoadGroup); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsILoadGroup childLoadGroup; */ michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetChildLoadGroup(nsILoadGroup * *aChildLoadGroup) michael@0: { michael@0: NS_ADDREF(*aChildLoadGroup = this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsILoadGroup rootLoadGroup; */ michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetRootLoadGroup(nsILoadGroup * *aRootLoadGroup) michael@0: { michael@0: // first recursively try the root load group of our parent michael@0: nsCOMPtr ancestor = do_QueryReferent(mParentLoadGroup); michael@0: if (ancestor) michael@0: return ancestor->GetRootLoadGroup(aRootLoadGroup); michael@0: michael@0: // next recursively try the root load group of our own load grop michael@0: ancestor = do_QueryInterface(mLoadGroup); michael@0: if (ancestor) michael@0: return ancestor->GetRootLoadGroup(aRootLoadGroup); michael@0: michael@0: // finally just return this michael@0: NS_ADDREF(*aRootLoadGroup = this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsPILoadGroupInternal methods: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::OnEndPageLoad(nsIChannel *aDefaultChannel) michael@0: { michael@0: // for the moment, nothing to do here. michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsISupportsPriority methods: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetPriority(int32_t *aValue) michael@0: { michael@0: *aValue = mPriority; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetPriority(int32_t aValue) michael@0: { michael@0: return AdjustPriority(aValue - mPriority); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::AdjustPriority(int32_t aDelta) michael@0: { michael@0: // Update the priority for each request that supports nsISupportsPriority michael@0: if (aDelta != 0) { michael@0: mPriority += aDelta; michael@0: PL_DHashTableEnumerate(&mRequests, RescheduleRequests, &aDelta); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::GetDefaultLoadFlags(uint32_t *aFlags) michael@0: { michael@0: *aFlags = mDefaultLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) michael@0: { michael@0: mDefaultLoadFlags = aFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void michael@0: nsLoadGroup::TelemetryReport() michael@0: { michael@0: if (mDefaultLoadIsTimed) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests); michael@0: if (mTimedRequests) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE, michael@0: mCachedRequests * 100 / mTimedRequests); michael@0: } michael@0: michael@0: nsCOMPtr timedChannel = michael@0: do_QueryInterface(mDefaultLoadRequest); michael@0: if (timedChannel) michael@0: TelemetryReportChannel(timedChannel, true); michael@0: } michael@0: michael@0: mTimedRequests = 0; michael@0: mCachedRequests = 0; michael@0: mDefaultLoadIsTimed = false; michael@0: } michael@0: michael@0: void michael@0: nsLoadGroup::TelemetryReportChannel(nsITimedChannel *aTimedChannel, michael@0: bool aDefaultRequest) michael@0: { michael@0: nsresult rv; michael@0: bool timingEnabled; michael@0: rv = aTimedChannel->GetTimingEnabled(&timingEnabled); michael@0: if (NS_FAILED(rv) || !timingEnabled) michael@0: return; michael@0: michael@0: TimeStamp asyncOpen; michael@0: rv = aTimedChannel->GetAsyncOpen(&asyncOpen); michael@0: // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way michael@0: if (NS_FAILED(rv) || asyncOpen.IsNull()) michael@0: return; michael@0: michael@0: TimeStamp cacheReadStart; michael@0: rv = aTimedChannel->GetCacheReadStart(&cacheReadStart); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp cacheReadEnd; michael@0: rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp domainLookupStart; michael@0: rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp domainLookupEnd; michael@0: rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp connectStart; michael@0: rv = aTimedChannel->GetConnectStart(&connectStart); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp connectEnd; michael@0: rv = aTimedChannel->GetConnectEnd(&connectEnd); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp requestStart; michael@0: rv = aTimedChannel->GetRequestStart(&requestStart); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp responseStart; michael@0: rv = aTimedChannel->GetResponseStart(&responseStart); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: TimeStamp responseEnd; michael@0: rv = aTimedChannel->GetResponseEnd(&responseEnd); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: #define HTTP_REQUEST_HISTOGRAMS(prefix) \ michael@0: if (!domainLookupStart.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME, \ michael@0: asyncOpen, domainLookupStart); \ michael@0: } \ michael@0: \ michael@0: if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \ michael@0: domainLookupStart, domainLookupEnd); \ michael@0: } \ michael@0: \ michael@0: if (!connectStart.IsNull() && !connectEnd.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_TCP_CONNECTION, \ michael@0: connectStart, connectEnd); \ michael@0: } \ michael@0: \ michael@0: \ michael@0: if (!requestStart.IsNull() && !responseEnd.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, \ michael@0: asyncOpen, requestStart); \ michael@0: \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, \ michael@0: requestStart, responseEnd); \ michael@0: \ michael@0: if (cacheReadStart.IsNull() && !responseStart.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, \ michael@0: asyncOpen, responseStart); \ michael@0: } \ michael@0: } \ michael@0: \ michael@0: if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { \ michael@0: if (!CacheObserver::UseNewCache()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE, \ michael@0: asyncOpen, cacheReadStart); \ michael@0: } else { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, \ michael@0: asyncOpen, cacheReadStart); \ michael@0: } \ michael@0: \ michael@0: if (!CacheObserver::UseNewCache()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_CACHE_READ_TIME, \ michael@0: cacheReadStart, cacheReadEnd); \ michael@0: } else { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, \ michael@0: cacheReadStart, cacheReadEnd); \ michael@0: } \ michael@0: \ michael@0: if (!requestStart.IsNull() && !responseEnd.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_REVALIDATION, \ michael@0: requestStart, responseEnd); \ michael@0: } \ michael@0: } \ michael@0: \ michael@0: if (!cacheReadEnd.IsNull()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \ michael@0: asyncOpen, cacheReadEnd); \ michael@0: \ michael@0: if (!CacheObserver::UseNewCache()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED, \ michael@0: asyncOpen, cacheReadEnd); \ michael@0: } else { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, \ michael@0: asyncOpen, cacheReadEnd); \ michael@0: } \ michael@0: } \ michael@0: else if (!responseEnd.IsNull()) { \ michael@0: if (!CacheObserver::UseNewCache()) { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \ michael@0: asyncOpen, responseEnd); \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET, \ michael@0: asyncOpen, responseEnd); \ michael@0: } else { \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, \ michael@0: asyncOpen, responseEnd); \ michael@0: Telemetry::AccumulateTimeDelta( \ michael@0: Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, \ michael@0: asyncOpen, responseEnd); \ michael@0: } \ michael@0: } michael@0: michael@0: if (aDefaultRequest) { michael@0: HTTP_REQUEST_HISTOGRAMS(PAGE) michael@0: } else { michael@0: HTTP_REQUEST_HISTOGRAMS(SUB) michael@0: } michael@0: #undef HTTP_REQUEST_HISTOGRAMS michael@0: } michael@0: michael@0: nsresult nsLoadGroup::MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& outFlags) michael@0: { michael@0: nsresult rv; michael@0: nsLoadFlags flags, oldFlags; michael@0: michael@0: rv = aRequest->GetLoadFlags(&flags); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: oldFlags = flags; michael@0: michael@0: // Inherit the following bits... michael@0: flags |= (mLoadFlags & (LOAD_BACKGROUND | michael@0: LOAD_BYPASS_CACHE | michael@0: LOAD_FROM_CACHE | michael@0: VALIDATE_ALWAYS | michael@0: VALIDATE_ONCE_PER_SESSION | michael@0: VALIDATE_NEVER)); michael@0: michael@0: // ... and force the default flags. michael@0: flags |= mDefaultLoadFlags; michael@0: michael@0: if (flags != oldFlags) michael@0: rv = aRequest->SetLoadFlags(flags); michael@0: michael@0: outFlags = flags; michael@0: return rv; michael@0: } michael@0: michael@0: // nsLoadGroupConnectionInfo michael@0: michael@0: class nsLoadGroupConnectionInfo MOZ_FINAL : public nsILoadGroupConnectionInfo michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSILOADGROUPCONNECTIONINFO michael@0: michael@0: nsLoadGroupConnectionInfo(); michael@0: private: michael@0: Atomic mBlockingTransactionCount; michael@0: nsAutoPtr mSpdyCache; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo) michael@0: michael@0: nsLoadGroupConnectionInfo::nsLoadGroupConnectionInfo() michael@0: : mBlockingTransactionCount(0) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroupConnectionInfo::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); michael@0: *aBlockingTransactionCount = mBlockingTransactionCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroupConnectionInfo::AddBlockingTransaction() michael@0: { michael@0: mBlockingTransactionCount++; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: mBlockingTransactionCount--; michael@0: *_retval = mBlockingTransactionCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* [noscript] attribute SpdyPushCachePtr spdyPushCache; */ michael@0: NS_IMETHODIMP michael@0: nsLoadGroupConnectionInfo::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache) michael@0: { michael@0: *aSpdyPushCache = mSpdyCache.get(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLoadGroupConnectionInfo::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache) michael@0: { michael@0: mSpdyCache = aSpdyPushCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsLoadGroup::Init() michael@0: { michael@0: static const PLDHashTableOps hash_table_ops = michael@0: { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: PL_DHashVoidPtrKeyStub, michael@0: RequestHashMatchEntry, michael@0: PL_DHashMoveEntryStub, michael@0: RequestHashClearEntry, michael@0: PL_DHashFinalizeStub, michael@0: RequestHashInitEntry michael@0: }; michael@0: michael@0: PL_DHashTableInit(&mRequests, &hash_table_ops, nullptr, michael@0: sizeof(RequestMapEntry), 16); michael@0: michael@0: mConnectionInfo = new nsLoadGroupConnectionInfo(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #undef LOG