nsPrefetchService.cpp 30 KB

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