uriloader/prefetch/nsPrefetchService.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include "nsPrefetchService.h"
michael@0 6 #include "nsICacheEntry.h"
michael@0 7 #include "nsIServiceManager.h"
michael@0 8 #include "nsICategoryManager.h"
michael@0 9 #include "nsIObserverService.h"
michael@0 10 #include "nsIWebProgress.h"
michael@0 11 #include "nsCURILoader.h"
michael@0 12 #include "nsICachingChannel.h"
michael@0 13 #include "nsICacheVisitor.h"
michael@0 14 #include "nsIHttpChannel.h"
michael@0 15 #include "nsIURL.h"
michael@0 16 #include "nsISimpleEnumerator.h"
michael@0 17 #include "nsNetUtil.h"
michael@0 18 #include "nsString.h"
michael@0 19 #include "nsXPIDLString.h"
michael@0 20 #include "nsReadableUtils.h"
michael@0 21 #include "nsStreamUtils.h"
michael@0 22 #include "nsAutoPtr.h"
michael@0 23 #include "prtime.h"
michael@0 24 #include "prlog.h"
michael@0 25 #include "plstr.h"
michael@0 26 #include "nsIAsyncVerifyRedirectCallback.h"
michael@0 27 #include "mozilla/Preferences.h"
michael@0 28 #include "mozilla/Attributes.h"
michael@0 29 #include "nsIDOMNode.h"
michael@0 30 #include "nsINode.h"
michael@0 31 #include "nsIDocument.h"
michael@0 32
michael@0 33 using namespace mozilla;
michael@0 34
michael@0 35 #if defined(PR_LOGGING)
michael@0 36 //
michael@0 37 // To enable logging (see prlog.h for full details):
michael@0 38 //
michael@0 39 // set NSPR_LOG_MODULES=nsPrefetch:5
michael@0 40 // set NSPR_LOG_FILE=prefetch.log
michael@0 41 //
michael@0 42 // this enables PR_LOG_ALWAYS level information and places all output in
michael@0 43 // the file http.log
michael@0 44 //
michael@0 45 static PRLogModuleInfo *gPrefetchLog;
michael@0 46 #endif
michael@0 47
michael@0 48 #undef LOG
michael@0 49 #define LOG(args) PR_LOG(gPrefetchLog, 4, args)
michael@0 50
michael@0 51 #undef LOG_ENABLED
michael@0 52 #define LOG_ENABLED() PR_LOG_TEST(gPrefetchLog, 4)
michael@0 53
michael@0 54 #define PREFETCH_PREF "network.prefetch-next"
michael@0 55
michael@0 56 //-----------------------------------------------------------------------------
michael@0 57 // helpers
michael@0 58 //-----------------------------------------------------------------------------
michael@0 59
michael@0 60 static inline uint32_t
michael@0 61 PRTimeToSeconds(PRTime t_usec)
michael@0 62 {
michael@0 63 PRTime usec_per_sec = PR_USEC_PER_SEC;
michael@0 64 return uint32_t(t_usec /= usec_per_sec);
michael@0 65 }
michael@0 66
michael@0 67 #define NowInSeconds() PRTimeToSeconds(PR_Now())
michael@0 68
michael@0 69 //-----------------------------------------------------------------------------
michael@0 70 // nsPrefetchQueueEnumerator
michael@0 71 //-----------------------------------------------------------------------------
michael@0 72 class nsPrefetchQueueEnumerator MOZ_FINAL : public nsISimpleEnumerator
michael@0 73 {
michael@0 74 public:
michael@0 75 NS_DECL_ISUPPORTS
michael@0 76 NS_DECL_NSISIMPLEENUMERATOR
michael@0 77 nsPrefetchQueueEnumerator(nsPrefetchService *aService);
michael@0 78 ~nsPrefetchQueueEnumerator();
michael@0 79
michael@0 80 private:
michael@0 81 void Increment();
michael@0 82
michael@0 83 nsRefPtr<nsPrefetchService> mService;
michael@0 84 nsRefPtr<nsPrefetchNode> mCurrent;
michael@0 85 bool mStarted;
michael@0 86 };
michael@0 87
michael@0 88 //-----------------------------------------------------------------------------
michael@0 89 // nsPrefetchQueueEnumerator <public>
michael@0 90 //-----------------------------------------------------------------------------
michael@0 91 nsPrefetchQueueEnumerator::nsPrefetchQueueEnumerator(nsPrefetchService *aService)
michael@0 92 : mService(aService)
michael@0 93 , mStarted(false)
michael@0 94 {
michael@0 95 Increment();
michael@0 96 }
michael@0 97
michael@0 98 nsPrefetchQueueEnumerator::~nsPrefetchQueueEnumerator()
michael@0 99 {
michael@0 100 }
michael@0 101
michael@0 102 //-----------------------------------------------------------------------------
michael@0 103 // nsPrefetchQueueEnumerator::nsISimpleEnumerator
michael@0 104 //-----------------------------------------------------------------------------
michael@0 105 NS_IMETHODIMP
michael@0 106 nsPrefetchQueueEnumerator::HasMoreElements(bool *aHasMore)
michael@0 107 {
michael@0 108 *aHasMore = (mCurrent != nullptr);
michael@0 109 return NS_OK;
michael@0 110 }
michael@0 111
michael@0 112 NS_IMETHODIMP
michael@0 113 nsPrefetchQueueEnumerator::GetNext(nsISupports **aItem)
michael@0 114 {
michael@0 115 if (!mCurrent) return NS_ERROR_FAILURE;
michael@0 116
michael@0 117 NS_ADDREF(*aItem = static_cast<nsIStreamListener*>(mCurrent.get()));
michael@0 118
michael@0 119 Increment();
michael@0 120
michael@0 121 return NS_OK;
michael@0 122 }
michael@0 123
michael@0 124 //-----------------------------------------------------------------------------
michael@0 125 // nsPrefetchQueueEnumerator <private>
michael@0 126 //-----------------------------------------------------------------------------
michael@0 127
michael@0 128 void
michael@0 129 nsPrefetchQueueEnumerator::Increment()
michael@0 130 {
michael@0 131 if (!mStarted) {
michael@0 132 // If the service is currently serving a request, it won't be in
michael@0 133 // the pending queue, so we return it first. If it isn't, we'll
michael@0 134 // just start with the pending queue.
michael@0 135 mStarted = true;
michael@0 136 mCurrent = mService->GetCurrentNode();
michael@0 137 if (!mCurrent)
michael@0 138 mCurrent = mService->GetQueueHead();
michael@0 139 return;
michael@0 140 }
michael@0 141
michael@0 142 if (mCurrent) {
michael@0 143 if (mCurrent == mService->GetCurrentNode()) {
michael@0 144 // If we just returned the node being processed by the service,
michael@0 145 // start with the pending queue
michael@0 146 mCurrent = mService->GetQueueHead();
michael@0 147 }
michael@0 148 else {
michael@0 149 // Otherwise just advance to the next item in the queue
michael@0 150 mCurrent = mCurrent->mNext;
michael@0 151 }
michael@0 152 }
michael@0 153 }
michael@0 154
michael@0 155 //-----------------------------------------------------------------------------
michael@0 156 // nsPrefetchQueueEnumerator::nsISupports
michael@0 157 //-----------------------------------------------------------------------------
michael@0 158
michael@0 159 NS_IMPL_ISUPPORTS(nsPrefetchQueueEnumerator, nsISimpleEnumerator)
michael@0 160
michael@0 161 //-----------------------------------------------------------------------------
michael@0 162 // nsPrefetchNode <public>
michael@0 163 //-----------------------------------------------------------------------------
michael@0 164
michael@0 165 nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
michael@0 166 nsIURI *aURI,
michael@0 167 nsIURI *aReferrerURI,
michael@0 168 nsIDOMNode *aSource)
michael@0 169 : mNext(nullptr)
michael@0 170 , mURI(aURI)
michael@0 171 , mReferrerURI(aReferrerURI)
michael@0 172 , mService(aService)
michael@0 173 , mChannel(nullptr)
michael@0 174 , mBytesRead(0)
michael@0 175 {
michael@0 176 mSource = do_GetWeakReference(aSource);
michael@0 177 }
michael@0 178
michael@0 179 nsresult
michael@0 180 nsPrefetchNode::OpenChannel()
michael@0 181 {
michael@0 182 nsCOMPtr<nsINode> source = do_QueryReferent(mSource);
michael@0 183 if (!source) {
michael@0 184 // Don't attempt to prefetch if we don't have a source node
michael@0 185 // (which should never happen).
michael@0 186 return NS_ERROR_FAILURE;
michael@0 187 }
michael@0 188 nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
michael@0 189 nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
michael@0 190 mURI,
michael@0 191 nullptr, loadGroup, this,
michael@0 192 nsIRequest::LOAD_BACKGROUND |
michael@0 193 nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
michael@0 194 NS_ENSURE_SUCCESS(rv, rv);
michael@0 195
michael@0 196 // configure HTTP specific stuff
michael@0 197 nsCOMPtr<nsIHttpChannel> httpChannel =
michael@0 198 do_QueryInterface(mChannel);
michael@0 199 if (httpChannel) {
michael@0 200 httpChannel->SetReferrer(mReferrerURI);
michael@0 201 httpChannel->SetRequestHeader(
michael@0 202 NS_LITERAL_CSTRING("X-Moz"),
michael@0 203 NS_LITERAL_CSTRING("prefetch"),
michael@0 204 false);
michael@0 205 }
michael@0 206
michael@0 207 rv = mChannel->AsyncOpen(this, nullptr);
michael@0 208 NS_ENSURE_SUCCESS(rv, rv);
michael@0 209
michael@0 210 return NS_OK;
michael@0 211 }
michael@0 212
michael@0 213 nsresult
michael@0 214 nsPrefetchNode::CancelChannel(nsresult error)
michael@0 215 {
michael@0 216 mChannel->Cancel(error);
michael@0 217 mChannel = nullptr;
michael@0 218
michael@0 219 return NS_OK;
michael@0 220 }
michael@0 221
michael@0 222 //-----------------------------------------------------------------------------
michael@0 223 // nsPrefetchNode::nsISupports
michael@0 224 //-----------------------------------------------------------------------------
michael@0 225
michael@0 226 NS_IMPL_ISUPPORTS(nsPrefetchNode,
michael@0 227 nsIRequestObserver,
michael@0 228 nsIStreamListener,
michael@0 229 nsIInterfaceRequestor,
michael@0 230 nsIChannelEventSink,
michael@0 231 nsIRedirectResultListener)
michael@0 232
michael@0 233 //-----------------------------------------------------------------------------
michael@0 234 // nsPrefetchNode::nsIStreamListener
michael@0 235 //-----------------------------------------------------------------------------
michael@0 236
michael@0 237 NS_IMETHODIMP
michael@0 238 nsPrefetchNode::OnStartRequest(nsIRequest *aRequest,
michael@0 239 nsISupports *aContext)
michael@0 240 {
michael@0 241 nsresult rv;
michael@0 242
michael@0 243 nsCOMPtr<nsICachingChannel> cachingChannel =
michael@0 244 do_QueryInterface(aRequest, &rv);
michael@0 245 if (NS_FAILED(rv)) return rv;
michael@0 246
michael@0 247 // no need to prefetch a document that is already in the cache
michael@0 248 bool fromCache;
michael@0 249 if (NS_SUCCEEDED(cachingChannel->IsFromCache(&fromCache)) &&
michael@0 250 fromCache) {
michael@0 251 LOG(("document is already in the cache; canceling prefetch\n"));
michael@0 252 return NS_BINDING_ABORTED;
michael@0 253 }
michael@0 254
michael@0 255 //
michael@0 256 // no need to prefetch a document that must be requested fresh each
michael@0 257 // and every time.
michael@0 258 //
michael@0 259 nsCOMPtr<nsISupports> cacheToken;
michael@0 260 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
michael@0 261 if (!cacheToken)
michael@0 262 return NS_ERROR_ABORT; // bail, no cache entry
michael@0 263
michael@0 264 nsCOMPtr<nsICacheEntry> entryInfo =
michael@0 265 do_QueryInterface(cacheToken, &rv);
michael@0 266 if (NS_FAILED(rv)) return rv;
michael@0 267
michael@0 268 uint32_t expTime;
michael@0 269 if (NS_SUCCEEDED(entryInfo->GetExpirationTime(&expTime))) {
michael@0 270 if (NowInSeconds() >= expTime) {
michael@0 271 LOG(("document cannot be reused from cache; "
michael@0 272 "canceling prefetch\n"));
michael@0 273 return NS_BINDING_ABORTED;
michael@0 274 }
michael@0 275 }
michael@0 276
michael@0 277 return NS_OK;
michael@0 278 }
michael@0 279
michael@0 280 NS_IMETHODIMP
michael@0 281 nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest,
michael@0 282 nsISupports *aContext,
michael@0 283 nsIInputStream *aStream,
michael@0 284 uint64_t aOffset,
michael@0 285 uint32_t aCount)
michael@0 286 {
michael@0 287 uint32_t bytesRead = 0;
michael@0 288 aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
michael@0 289 mBytesRead += bytesRead;
michael@0 290 LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead, aOffset));
michael@0 291 return NS_OK;
michael@0 292 }
michael@0 293
michael@0 294
michael@0 295 NS_IMETHODIMP
michael@0 296 nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
michael@0 297 nsISupports *aContext,
michael@0 298 nsresult aStatus)
michael@0 299 {
michael@0 300 LOG(("done prefetching [status=%x]\n", aStatus));
michael@0 301
michael@0 302 if (mBytesRead == 0 && aStatus == NS_OK) {
michael@0 303 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
michael@0 304 // specified), but the object should report loadedSize as if it
michael@0 305 // did.
michael@0 306 mChannel->GetContentLength(&mBytesRead);
michael@0 307 }
michael@0 308
michael@0 309 mService->NotifyLoadCompleted(this);
michael@0 310 mService->ProcessNextURI();
michael@0 311 return NS_OK;
michael@0 312 }
michael@0 313
michael@0 314 //-----------------------------------------------------------------------------
michael@0 315 // nsPrefetchNode::nsIInterfaceRequestor
michael@0 316 //-----------------------------------------------------------------------------
michael@0 317
michael@0 318 NS_IMETHODIMP
michael@0 319 nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult)
michael@0 320 {
michael@0 321 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
michael@0 322 NS_ADDREF_THIS();
michael@0 323 *aResult = static_cast<nsIChannelEventSink *>(this);
michael@0 324 return NS_OK;
michael@0 325 }
michael@0 326
michael@0 327 if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
michael@0 328 NS_ADDREF_THIS();
michael@0 329 *aResult = static_cast<nsIRedirectResultListener *>(this);
michael@0 330 return NS_OK;
michael@0 331 }
michael@0 332
michael@0 333 return NS_ERROR_NO_INTERFACE;
michael@0 334 }
michael@0 335
michael@0 336 //-----------------------------------------------------------------------------
michael@0 337 // nsPrefetchNode::nsIChannelEventSink
michael@0 338 //-----------------------------------------------------------------------------
michael@0 339
michael@0 340 NS_IMETHODIMP
michael@0 341 nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
michael@0 342 nsIChannel *aNewChannel,
michael@0 343 uint32_t aFlags,
michael@0 344 nsIAsyncVerifyRedirectCallback *callback)
michael@0 345 {
michael@0 346 nsCOMPtr<nsIURI> newURI;
michael@0 347 nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
michael@0 348 if (NS_FAILED(rv))
michael@0 349 return rv;
michael@0 350
michael@0 351 bool match;
michael@0 352 rv = newURI->SchemeIs("http", &match);
michael@0 353 if (NS_FAILED(rv) || !match) {
michael@0 354 rv = newURI->SchemeIs("https", &match);
michael@0 355 if (NS_FAILED(rv) || !match) {
michael@0 356 LOG(("rejected: URL is not of type http/https\n"));
michael@0 357 return NS_ERROR_ABORT;
michael@0 358 }
michael@0 359 }
michael@0 360
michael@0 361 // HTTP request headers are not automatically forwarded to the new channel.
michael@0 362 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
michael@0 363 NS_ENSURE_STATE(httpChannel);
michael@0 364
michael@0 365 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
michael@0 366 NS_LITERAL_CSTRING("prefetch"),
michael@0 367 false);
michael@0 368
michael@0 369 // Assign to mChannel after we get notification about success of the
michael@0 370 // redirect in OnRedirectResult.
michael@0 371 mRedirectChannel = aNewChannel;
michael@0 372
michael@0 373 callback->OnRedirectVerifyCallback(NS_OK);
michael@0 374 return NS_OK;
michael@0 375 }
michael@0 376
michael@0 377 //-----------------------------------------------------------------------------
michael@0 378 // nsPrefetchNode::nsIRedirectResultListener
michael@0 379 //-----------------------------------------------------------------------------
michael@0 380
michael@0 381 NS_IMETHODIMP
michael@0 382 nsPrefetchNode::OnRedirectResult(bool proceeding)
michael@0 383 {
michael@0 384 if (proceeding && mRedirectChannel)
michael@0 385 mChannel = mRedirectChannel;
michael@0 386
michael@0 387 mRedirectChannel = nullptr;
michael@0 388
michael@0 389 return NS_OK;
michael@0 390 }
michael@0 391
michael@0 392 //-----------------------------------------------------------------------------
michael@0 393 // nsPrefetchService <public>
michael@0 394 //-----------------------------------------------------------------------------
michael@0 395
michael@0 396 nsPrefetchService::nsPrefetchService()
michael@0 397 : mQueueHead(nullptr)
michael@0 398 , mQueueTail(nullptr)
michael@0 399 , mStopCount(0)
michael@0 400 , mHaveProcessed(false)
michael@0 401 , mDisabled(true)
michael@0 402 {
michael@0 403 }
michael@0 404
michael@0 405 nsPrefetchService::~nsPrefetchService()
michael@0 406 {
michael@0 407 Preferences::RemoveObserver(this, PREFETCH_PREF);
michael@0 408 // cannot reach destructor if prefetch in progress (listener owns reference
michael@0 409 // to this service)
michael@0 410 EmptyQueue();
michael@0 411 }
michael@0 412
michael@0 413 nsresult
michael@0 414 nsPrefetchService::Init()
michael@0 415 {
michael@0 416 #if defined(PR_LOGGING)
michael@0 417 if (!gPrefetchLog)
michael@0 418 gPrefetchLog = PR_NewLogModule("nsPrefetch");
michael@0 419 #endif
michael@0 420
michael@0 421 nsresult rv;
michael@0 422
michael@0 423 // read prefs and hook up pref observer
michael@0 424 mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled);
michael@0 425 Preferences::AddWeakObserver(this, PREFETCH_PREF);
michael@0 426
michael@0 427 // Observe xpcom-shutdown event
michael@0 428 nsCOMPtr<nsIObserverService> observerService =
michael@0 429 mozilla::services::GetObserverService();
michael@0 430 if (!observerService)
michael@0 431 return NS_ERROR_FAILURE;
michael@0 432
michael@0 433 rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
michael@0 434 NS_ENSURE_SUCCESS(rv, rv);
michael@0 435
michael@0 436 if (!mDisabled)
michael@0 437 AddProgressListener();
michael@0 438
michael@0 439 return NS_OK;
michael@0 440 }
michael@0 441
michael@0 442 void
michael@0 443 nsPrefetchService::ProcessNextURI()
michael@0 444 {
michael@0 445 nsresult rv;
michael@0 446 nsCOMPtr<nsIURI> uri, referrer;
michael@0 447
michael@0 448 mCurrentNode = nullptr;
michael@0 449
michael@0 450 do {
michael@0 451 rv = DequeueNode(getter_AddRefs(mCurrentNode));
michael@0 452
michael@0 453 if (NS_FAILED(rv)) break;
michael@0 454
michael@0 455 #if defined(PR_LOGGING)
michael@0 456 if (LOG_ENABLED()) {
michael@0 457 nsAutoCString spec;
michael@0 458 mCurrentNode->mURI->GetSpec(spec);
michael@0 459 LOG(("ProcessNextURI [%s]\n", spec.get()));
michael@0 460 }
michael@0 461 #endif
michael@0 462
michael@0 463 //
michael@0 464 // if opening the channel fails, then just skip to the next uri
michael@0 465 //
michael@0 466 nsRefPtr<nsPrefetchNode> node = mCurrentNode;
michael@0 467 rv = node->OpenChannel();
michael@0 468 }
michael@0 469 while (NS_FAILED(rv));
michael@0 470 }
michael@0 471
michael@0 472 void
michael@0 473 nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node)
michael@0 474 {
michael@0 475 nsCOMPtr<nsIObserverService> observerService =
michael@0 476 mozilla::services::GetObserverService();
michael@0 477 if (!observerService)
michael@0 478 return;
michael@0 479
michael@0 480 observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
michael@0 481 "prefetch-load-requested", nullptr);
michael@0 482 }
michael@0 483
michael@0 484 void
michael@0 485 nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node)
michael@0 486 {
michael@0 487 nsCOMPtr<nsIObserverService> observerService =
michael@0 488 mozilla::services::GetObserverService();
michael@0 489 if (!observerService)
michael@0 490 return;
michael@0 491
michael@0 492 observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
michael@0 493 "prefetch-load-completed", nullptr);
michael@0 494 }
michael@0 495
michael@0 496 //-----------------------------------------------------------------------------
michael@0 497 // nsPrefetchService <private>
michael@0 498 //-----------------------------------------------------------------------------
michael@0 499
michael@0 500 void
michael@0 501 nsPrefetchService::AddProgressListener()
michael@0 502 {
michael@0 503 // Register as an observer for the document loader
michael@0 504 nsCOMPtr<nsIWebProgress> progress =
michael@0 505 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
michael@0 506 if (progress)
michael@0 507 progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
michael@0 508 }
michael@0 509
michael@0 510 void
michael@0 511 nsPrefetchService::RemoveProgressListener()
michael@0 512 {
michael@0 513 // Register as an observer for the document loader
michael@0 514 nsCOMPtr<nsIWebProgress> progress =
michael@0 515 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
michael@0 516 if (progress)
michael@0 517 progress->RemoveProgressListener(this);
michael@0 518 }
michael@0 519
michael@0 520 nsresult
michael@0 521 nsPrefetchService::EnqueueNode(nsPrefetchNode *aNode)
michael@0 522 {
michael@0 523 NS_ADDREF(aNode);
michael@0 524
michael@0 525 if (!mQueueTail) {
michael@0 526 mQueueHead = aNode;
michael@0 527 mQueueTail = aNode;
michael@0 528 }
michael@0 529 else {
michael@0 530 mQueueTail->mNext = aNode;
michael@0 531 mQueueTail = aNode;
michael@0 532 }
michael@0 533
michael@0 534 return NS_OK;
michael@0 535 }
michael@0 536
michael@0 537 nsresult
michael@0 538 nsPrefetchService::EnqueueURI(nsIURI *aURI,
michael@0 539 nsIURI *aReferrerURI,
michael@0 540 nsIDOMNode *aSource,
michael@0 541 nsPrefetchNode **aNode)
michael@0 542 {
michael@0 543 nsPrefetchNode *node = new nsPrefetchNode(this, aURI, aReferrerURI,
michael@0 544 aSource);
michael@0 545 if (!node)
michael@0 546 return NS_ERROR_OUT_OF_MEMORY;
michael@0 547
michael@0 548 NS_ADDREF(*aNode = node);
michael@0 549
michael@0 550 return EnqueueNode(node);
michael@0 551 }
michael@0 552
michael@0 553 nsresult
michael@0 554 nsPrefetchService::DequeueNode(nsPrefetchNode **node)
michael@0 555 {
michael@0 556 if (!mQueueHead)
michael@0 557 return NS_ERROR_NOT_AVAILABLE;
michael@0 558
michael@0 559 // give the ref to the caller
michael@0 560 *node = mQueueHead;
michael@0 561 mQueueHead = mQueueHead->mNext;
michael@0 562 (*node)->mNext = nullptr;
michael@0 563
michael@0 564 if (!mQueueHead)
michael@0 565 mQueueTail = nullptr;
michael@0 566
michael@0 567 return NS_OK;
michael@0 568 }
michael@0 569
michael@0 570 void
michael@0 571 nsPrefetchService::EmptyQueue()
michael@0 572 {
michael@0 573 do {
michael@0 574 nsRefPtr<nsPrefetchNode> node;
michael@0 575 DequeueNode(getter_AddRefs(node));
michael@0 576 } while (mQueueHead);
michael@0 577 }
michael@0 578
michael@0 579 void
michael@0 580 nsPrefetchService::StartPrefetching()
michael@0 581 {
michael@0 582 //
michael@0 583 // at initialization time we might miss the first DOCUMENT START
michael@0 584 // notification, so we have to be careful to avoid letting our
michael@0 585 // stop count go negative.
michael@0 586 //
michael@0 587 if (mStopCount > 0)
michael@0 588 mStopCount--;
michael@0 589
michael@0 590 LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
michael@0 591
michael@0 592 // only start prefetching after we've received enough DOCUMENT
michael@0 593 // STOP notifications. we do this inorder to defer prefetching
michael@0 594 // until after all sub-frames have finished loading.
michael@0 595 if (mStopCount == 0 && !mCurrentNode) {
michael@0 596 mHaveProcessed = true;
michael@0 597 ProcessNextURI();
michael@0 598 }
michael@0 599 }
michael@0 600
michael@0 601 void
michael@0 602 nsPrefetchService::StopPrefetching()
michael@0 603 {
michael@0 604 mStopCount++;
michael@0 605
michael@0 606 LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
michael@0 607
michael@0 608 // only kill the prefetch queue if we've actually started prefetching.
michael@0 609 if (!mCurrentNode)
michael@0 610 return;
michael@0 611
michael@0 612 mCurrentNode->CancelChannel(NS_BINDING_ABORTED);
michael@0 613 mCurrentNode = nullptr;
michael@0 614 EmptyQueue();
michael@0 615 }
michael@0 616
michael@0 617 //-----------------------------------------------------------------------------
michael@0 618 // nsPrefetchService::nsISupports
michael@0 619 //-----------------------------------------------------------------------------
michael@0 620
michael@0 621 NS_IMPL_ISUPPORTS(nsPrefetchService,
michael@0 622 nsIPrefetchService,
michael@0 623 nsIWebProgressListener,
michael@0 624 nsIObserver,
michael@0 625 nsISupportsWeakReference)
michael@0 626
michael@0 627 //-----------------------------------------------------------------------------
michael@0 628 // nsPrefetchService::nsIPrefetchService
michael@0 629 //-----------------------------------------------------------------------------
michael@0 630
michael@0 631 nsresult
michael@0 632 nsPrefetchService::Prefetch(nsIURI *aURI,
michael@0 633 nsIURI *aReferrerURI,
michael@0 634 nsIDOMNode *aSource,
michael@0 635 bool aExplicit)
michael@0 636 {
michael@0 637 nsresult rv;
michael@0 638
michael@0 639 NS_ENSURE_ARG_POINTER(aURI);
michael@0 640 NS_ENSURE_ARG_POINTER(aReferrerURI);
michael@0 641
michael@0 642 #if defined(PR_LOGGING)
michael@0 643 if (LOG_ENABLED()) {
michael@0 644 nsAutoCString spec;
michael@0 645 aURI->GetSpec(spec);
michael@0 646 LOG(("PrefetchURI [%s]\n", spec.get()));
michael@0 647 }
michael@0 648 #endif
michael@0 649
michael@0 650 if (mDisabled) {
michael@0 651 LOG(("rejected: prefetch service is disabled\n"));
michael@0 652 return NS_ERROR_ABORT;
michael@0 653 }
michael@0 654
michael@0 655 //
michael@0 656 // XXX we should really be asking the protocol handler if it supports
michael@0 657 // caching, so we can determine if there is any value to prefetching.
michael@0 658 // for now, we'll only prefetch http links since we know that's the
michael@0 659 // most common case. ignore https links since https content only goes
michael@0 660 // into the memory cache.
michael@0 661 //
michael@0 662 // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
michael@0 663 // or possibly nsIRequest::loadFlags to determine if this URI should be
michael@0 664 // prefetched.
michael@0 665 //
michael@0 666 bool match;
michael@0 667 rv = aURI->SchemeIs("http", &match);
michael@0 668 if (NS_FAILED(rv) || !match) {
michael@0 669 rv = aURI->SchemeIs("https", &match);
michael@0 670 if (NS_FAILED(rv) || !match) {
michael@0 671 LOG(("rejected: URL is not of type http/https\n"));
michael@0 672 return NS_ERROR_ABORT;
michael@0 673 }
michael@0 674 }
michael@0 675
michael@0 676 //
michael@0 677 // the referrer URI must be http:
michael@0 678 //
michael@0 679 rv = aReferrerURI->SchemeIs("http", &match);
michael@0 680 if (NS_FAILED(rv) || !match) {
michael@0 681 rv = aReferrerURI->SchemeIs("https", &match);
michael@0 682 if (NS_FAILED(rv) || !match) {
michael@0 683 LOG(("rejected: referrer URL is neither http nor https\n"));
michael@0 684 return NS_ERROR_ABORT;
michael@0 685 }
michael@0 686 }
michael@0 687
michael@0 688 // skip URLs that contain query strings, except URLs for which prefetching
michael@0 689 // has been explicitly requested.
michael@0 690 if (!aExplicit) {
michael@0 691 nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
michael@0 692 if (NS_FAILED(rv)) return rv;
michael@0 693 nsAutoCString query;
michael@0 694 rv = url->GetQuery(query);
michael@0 695 if (NS_FAILED(rv) || !query.IsEmpty()) {
michael@0 696 LOG(("rejected: URL has a query string\n"));
michael@0 697 return NS_ERROR_ABORT;
michael@0 698 }
michael@0 699 }
michael@0 700
michael@0 701 //
michael@0 702 // cancel if being prefetched
michael@0 703 //
michael@0 704 if (mCurrentNode) {
michael@0 705 bool equals;
michael@0 706 if (NS_SUCCEEDED(mCurrentNode->mURI->Equals(aURI, &equals)) && equals) {
michael@0 707 LOG(("rejected: URL is already being prefetched\n"));
michael@0 708 return NS_ERROR_ABORT;
michael@0 709 }
michael@0 710 }
michael@0 711
michael@0 712 //
michael@0 713 // cancel if already on the prefetch queue
michael@0 714 //
michael@0 715 nsPrefetchNode *node = mQueueHead;
michael@0 716 for (; node; node = node->mNext) {
michael@0 717 bool equals;
michael@0 718 if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
michael@0 719 LOG(("rejected: URL is already on prefetch queue\n"));
michael@0 720 return NS_ERROR_ABORT;
michael@0 721 }
michael@0 722 }
michael@0 723
michael@0 724 nsRefPtr<nsPrefetchNode> enqueuedNode;
michael@0 725 rv = EnqueueURI(aURI, aReferrerURI, aSource,
michael@0 726 getter_AddRefs(enqueuedNode));
michael@0 727 NS_ENSURE_SUCCESS(rv, rv);
michael@0 728
michael@0 729 NotifyLoadRequested(enqueuedNode);
michael@0 730
michael@0 731 // if there are no pages loading, kick off the request immediately
michael@0 732 if (mStopCount == 0 && mHaveProcessed)
michael@0 733 ProcessNextURI();
michael@0 734
michael@0 735 return NS_OK;
michael@0 736 }
michael@0 737
michael@0 738 NS_IMETHODIMP
michael@0 739 nsPrefetchService::PrefetchURI(nsIURI *aURI,
michael@0 740 nsIURI *aReferrerURI,
michael@0 741 nsIDOMNode *aSource,
michael@0 742 bool aExplicit)
michael@0 743 {
michael@0 744 return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
michael@0 745 }
michael@0 746
michael@0 747 NS_IMETHODIMP
michael@0 748 nsPrefetchService::EnumerateQueue(nsISimpleEnumerator **aEnumerator)
michael@0 749 {
michael@0 750 *aEnumerator = new nsPrefetchQueueEnumerator(this);
michael@0 751 if (!*aEnumerator) return NS_ERROR_OUT_OF_MEMORY;
michael@0 752
michael@0 753 NS_ADDREF(*aEnumerator);
michael@0 754
michael@0 755 return NS_OK;
michael@0 756 }
michael@0 757
michael@0 758 //-----------------------------------------------------------------------------
michael@0 759 // nsPrefetchService::nsIWebProgressListener
michael@0 760 //-----------------------------------------------------------------------------
michael@0 761
michael@0 762 NS_IMETHODIMP
michael@0 763 nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress,
michael@0 764 nsIRequest *aRequest,
michael@0 765 int32_t curSelfProgress,
michael@0 766 int32_t maxSelfProgress,
michael@0 767 int32_t curTotalProgress,
michael@0 768 int32_t maxTotalProgress)
michael@0 769 {
michael@0 770 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
michael@0 771 return NS_OK;
michael@0 772 }
michael@0 773
michael@0 774 NS_IMETHODIMP
michael@0 775 nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress,
michael@0 776 nsIRequest *aRequest,
michael@0 777 uint32_t progressStateFlags,
michael@0 778 nsresult aStatus)
michael@0 779 {
michael@0 780 if (progressStateFlags & STATE_IS_DOCUMENT) {
michael@0 781 if (progressStateFlags & STATE_STOP)
michael@0 782 StartPrefetching();
michael@0 783 else if (progressStateFlags & STATE_START)
michael@0 784 StopPrefetching();
michael@0 785 }
michael@0 786
michael@0 787 return NS_OK;
michael@0 788 }
michael@0 789
michael@0 790
michael@0 791 NS_IMETHODIMP
michael@0 792 nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
michael@0 793 nsIRequest* aRequest,
michael@0 794 nsIURI *location,
michael@0 795 uint32_t aFlags)
michael@0 796 {
michael@0 797 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
michael@0 798 return NS_OK;
michael@0 799 }
michael@0 800
michael@0 801 NS_IMETHODIMP
michael@0 802 nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
michael@0 803 nsIRequest* aRequest,
michael@0 804 nsresult aStatus,
michael@0 805 const char16_t* aMessage)
michael@0 806 {
michael@0 807 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
michael@0 808 return NS_OK;
michael@0 809 }
michael@0 810
michael@0 811 NS_IMETHODIMP
michael@0 812 nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress,
michael@0 813 nsIRequest *aRequest,
michael@0 814 uint32_t state)
michael@0 815 {
michael@0 816 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
michael@0 817 return NS_OK;
michael@0 818 }
michael@0 819
michael@0 820 //-----------------------------------------------------------------------------
michael@0 821 // nsPrefetchService::nsIObserver
michael@0 822 //-----------------------------------------------------------------------------
michael@0 823
michael@0 824 NS_IMETHODIMP
michael@0 825 nsPrefetchService::Observe(nsISupports *aSubject,
michael@0 826 const char *aTopic,
michael@0 827 const char16_t *aData)
michael@0 828 {
michael@0 829 LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
michael@0 830
michael@0 831 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
michael@0 832 StopPrefetching();
michael@0 833 EmptyQueue();
michael@0 834 mDisabled = true;
michael@0 835 }
michael@0 836 else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
michael@0 837 if (Preferences::GetBool(PREFETCH_PREF, false)) {
michael@0 838 if (mDisabled) {
michael@0 839 LOG(("enabling prefetching\n"));
michael@0 840 mDisabled = false;
michael@0 841 AddProgressListener();
michael@0 842 }
michael@0 843 }
michael@0 844 else {
michael@0 845 if (!mDisabled) {
michael@0 846 LOG(("disabling prefetching\n"));
michael@0 847 StopPrefetching();
michael@0 848 EmptyQueue();
michael@0 849 mDisabled = true;
michael@0 850 RemoveProgressListener();
michael@0 851 }
michael@0 852 }
michael@0 853 }
michael@0 854
michael@0 855 return NS_OK;
michael@0 856 }
michael@0 857
michael@0 858 // vim: ts=4 sw=4 expandtab

mercurial