Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/uriloader/prefetch/nsPrefetchService.cpp
Line
Count
Source (jump to first uncovered line)
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
6
#include "nsPrefetchService.h"
7
8
#include "mozilla/AsyncEventDispatcher.h"
9
#include "mozilla/Attributes.h"
10
#include "mozilla/CORSMode.h"
11
#include "mozilla/dom/ClientInfo.h"
12
#include "mozilla/dom/HTMLLinkElement.h"
13
#include "mozilla/dom/ServiceWorkerDescriptor.h"
14
#include "mozilla/Preferences.h"
15
16
#include "nsICacheEntry.h"
17
#include "nsIServiceManager.h"
18
#include "nsICategoryManager.h"
19
#include "nsIObserverService.h"
20
#include "nsIWebProgress.h"
21
#include "nsCURILoader.h"
22
#include "nsICacheInfoChannel.h"
23
#include "nsIHttpChannel.h"
24
#include "nsIURL.h"
25
#include "nsISimpleEnumerator.h"
26
#include "nsISupportsPriority.h"
27
#include "nsNetUtil.h"
28
#include "nsString.h"
29
#include "nsReadableUtils.h"
30
#include "nsStreamUtils.h"
31
#include "nsAutoPtr.h"
32
#include "prtime.h"
33
#include "mozilla/Logging.h"
34
#include "plstr.h"
35
#include "nsIAsyncVerifyRedirectCallback.h"
36
#include "nsINode.h"
37
#include "nsIDocument.h"
38
#include "nsContentUtils.h"
39
#include "nsStyleLinkElement.h"
40
#include "mozilla/AsyncEventDispatcher.h"
41
42
using namespace mozilla;
43
44
//
45
// To enable logging (see mozilla/Logging.h for full details):
46
//
47
//    set MOZ_LOG=nsPrefetch:5
48
//    set MOZ_LOG_FILE=prefetch.log
49
//
50
// this enables LogLevel::Debug level information and places all output in
51
// the file prefetch.log
52
//
53
static LazyLogModule gPrefetchLog("nsPrefetch");
54
55
#undef LOG
56
0
#define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
57
58
#undef LOG_ENABLED
59
0
#define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
60
61
0
#define PREFETCH_PREF "network.prefetch-next"
62
0
#define PRELOAD_PREF "network.preload"
63
0
#define PARALLELISM_PREF "network.prefetch-next.parallelism"
64
0
#define AGGRESSIVE_PREF "network.prefetch-next.aggressive"
65
66
//-----------------------------------------------------------------------------
67
// helpers
68
//-----------------------------------------------------------------------------
69
70
static inline uint32_t
71
PRTimeToSeconds(PRTime t_usec)
72
0
{
73
0
    PRTime usec_per_sec = PR_USEC_PER_SEC;
74
0
    return uint32_t(t_usec /= usec_per_sec);
75
0
}
76
77
0
#define NowInSeconds() PRTimeToSeconds(PR_Now())
78
79
//-----------------------------------------------------------------------------
80
// nsPrefetchNode <public>
81
//-----------------------------------------------------------------------------
82
83
nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
84
                               nsIURI *aURI,
85
                               nsIURI *aReferrerURI,
86
                               nsINode *aSource,
87
                               nsContentPolicyType aPolicyType,
88
                               bool aPreload)
89
    : mURI(aURI)
90
    , mReferrerURI(aReferrerURI)
91
    , mPolicyType(aPolicyType)
92
    , mPreload(aPreload)
93
    , mService(aService)
94
    , mChannel(nullptr)
95
    , mBytesRead(0)
96
    , mShouldFireLoadEvent(false)
97
0
{
98
0
    nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
99
0
    mSources.AppendElement(source);
100
0
}
101
102
nsresult
103
nsPrefetchNode::OpenChannel()
104
0
{
105
0
    if (mSources.IsEmpty()) {
106
0
        // Don't attempt to prefetch if we don't have a source node
107
0
        // (which should never happen).
108
0
        return NS_ERROR_FAILURE;
109
0
    }
110
0
    nsCOMPtr<nsINode> source;
111
0
    while (!mSources.IsEmpty() && !(source = do_QueryReferent(mSources.ElementAt(0)))) {
112
0
        // If source is null remove it.
113
0
        // (which should never happen).
114
0
        mSources.RemoveElementAt(0);
115
0
    }
116
0
117
0
    if (!source) {
118
0
        // Don't attempt to prefetch if we don't have a source node
119
0
        // (which should never happen).
120
0
121
0
        return NS_ERROR_FAILURE;
122
0
    }
123
0
    nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
124
0
    CORSMode corsMode = CORS_NONE;
125
0
    net::ReferrerPolicy referrerPolicy = net::RP_Unset;
126
0
    if (auto* link = dom::HTMLLinkElement::FromNode(source)) {
127
0
      corsMode = link->GetCORSMode();
128
0
      referrerPolicy = link->GetReferrerPolicyAsEnum();
129
0
    }
130
0
131
0
    if (referrerPolicy == net::RP_Unset) {
132
0
      referrerPolicy = source->OwnerDoc()->GetReferrerPolicy();
133
0
    }
134
0
135
0
    uint32_t securityFlags;
136
0
    if (corsMode == CORS_NONE) {
137
0
      securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
138
0
    } else {
139
0
      securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
140
0
      if (corsMode == CORS_USE_CREDENTIALS) {
141
0
        securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
142
0
      }
143
0
    }
144
0
    nsresult rv = NS_NewChannelInternal(getter_AddRefs(mChannel),
145
0
                                        mURI,
146
0
                                        source,
147
0
                                        source->NodePrincipal(),
148
0
                                        nullptr,   //aTriggeringPrincipal
149
0
                                        Maybe<ClientInfo>(),
150
0
                                        Maybe<ServiceWorkerDescriptor>(),
151
0
                                        securityFlags,
152
0
                                        mPolicyType,
153
0
                                        nullptr,   // aPerformanceStorage
154
0
                                        loadGroup, // aLoadGroup
155
0
                                        this,      // aCallbacks
156
0
                                        nsIRequest::LOAD_BACKGROUND |
157
0
                                        nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
158
0
159
0
    NS_ENSURE_SUCCESS(rv, rv);
160
0
161
0
    // configure HTTP specific stuff
162
0
    nsCOMPtr<nsIHttpChannel> httpChannel =
163
0
        do_QueryInterface(mChannel);
164
0
    if (httpChannel) {
165
0
        rv = httpChannel->SetReferrerWithPolicy(mReferrerURI, referrerPolicy);
166
0
        MOZ_ASSERT(NS_SUCCEEDED(rv));
167
0
        rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
168
0
                                           NS_LITERAL_CSTRING("prefetch"),
169
0
                                           false);
170
0
        MOZ_ASSERT(NS_SUCCEEDED(rv));
171
0
    }
172
0
173
0
    // Reduce the priority of prefetch network requests.
174
0
    nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
175
0
    if (priorityChannel) {
176
0
      priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
177
0
    }
178
0
179
0
    rv = mChannel->AsyncOpen2(this);
180
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
181
0
      // Drop the ref to the channel, because we don't want to end up with
182
0
      // cycles through it.
183
0
      mChannel = nullptr;
184
0
    }
185
0
    return rv;
186
0
}
187
188
nsresult
189
nsPrefetchNode::CancelChannel(nsresult error)
190
0
{
191
0
    mChannel->Cancel(error);
192
0
    mChannel = nullptr;
193
0
194
0
    return NS_OK;
195
0
}
196
197
//-----------------------------------------------------------------------------
198
// nsPrefetchNode::nsISupports
199
//-----------------------------------------------------------------------------
200
201
NS_IMPL_ISUPPORTS(nsPrefetchNode,
202
                  nsIRequestObserver,
203
                  nsIStreamListener,
204
                  nsIInterfaceRequestor,
205
                  nsIChannelEventSink,
206
                  nsIRedirectResultListener)
207
208
//-----------------------------------------------------------------------------
209
// nsPrefetchNode::nsIStreamListener
210
//-----------------------------------------------------------------------------
211
212
NS_IMETHODIMP
213
nsPrefetchNode::OnStartRequest(nsIRequest *aRequest,
214
                               nsISupports *aContext)
215
0
{
216
0
    nsresult rv;
217
0
218
0
    nsCOMPtr<nsIHttpChannel> httpChannel =
219
0
        do_QueryInterface(aRequest, &rv);
220
0
    if (NS_FAILED(rv)) return rv;
221
0
222
0
    // if the load is cross origin without CORS, or the CORS access is rejected,
223
0
    // always fire load event to avoid leaking site information.
224
0
    nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
225
0
    if (loadInfo) {
226
0
        mShouldFireLoadEvent = loadInfo->GetTainting() == LoadTainting::Opaque ||
227
0
                               (loadInfo->GetTainting() == LoadTainting::CORS &&
228
0
                                (NS_FAILED(httpChannel->GetStatus(&rv)) ||
229
0
                                 NS_FAILED(rv)));
230
0
    }
231
0
232
0
    // no need to prefetch http error page
233
0
    bool requestSucceeded;
234
0
    if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
235
0
        !requestSucceeded) {
236
0
      return NS_BINDING_ABORTED;
237
0
    }
238
0
239
0
    nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
240
0
        do_QueryInterface(aRequest, &rv);
241
0
    if (NS_FAILED(rv)) return rv;
242
0
 
243
0
    // no need to prefetch a document that is already in the cache
244
0
    bool fromCache;
245
0
    if (NS_SUCCEEDED(cacheInfoChannel->IsFromCache(&fromCache)) &&
246
0
        fromCache) {
247
0
        LOG(("document is already in the cache; canceling prefetch\n"));
248
0
        // although it's canceled we still want to fire load event
249
0
        mShouldFireLoadEvent = true;
250
0
        return NS_BINDING_ABORTED;
251
0
    }
252
0
253
0
    //
254
0
    // no need to prefetch a document that must be requested fresh each
255
0
    // and every time.
256
0
    //
257
0
    uint32_t expTime;
258
0
    if (NS_SUCCEEDED(cacheInfoChannel->GetCacheTokenExpirationTime(&expTime))) {
259
0
        if (NowInSeconds() >= expTime) {
260
0
            LOG(("document cannot be reused from cache; "
261
0
                 "canceling prefetch\n"));
262
0
            return NS_BINDING_ABORTED;
263
0
        }
264
0
    }
265
0
266
0
    return NS_OK;
267
0
}
268
269
NS_IMETHODIMP
270
nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest,
271
                                nsISupports *aContext,
272
                                nsIInputStream *aStream,
273
                                uint64_t aOffset,
274
                                uint32_t aCount)
275
0
{
276
0
    uint32_t bytesRead = 0;
277
0
    aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
278
0
    mBytesRead += bytesRead;
279
0
    LOG(("prefetched %u bytes [offset=%" PRIu64 "]\n", bytesRead, aOffset));
280
0
    return NS_OK;
281
0
}
282
283
284
NS_IMETHODIMP
285
nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
286
                              nsISupports *aContext,
287
                              nsresult aStatus)
288
0
{
289
0
    LOG(("done prefetching [status=%" PRIx32 "]\n", static_cast<uint32_t>(aStatus)));
290
0
291
0
    if (mBytesRead == 0 && aStatus == NS_OK && mChannel) {
292
0
        // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
293
0
        // specified), but the object should report loadedSize as if it
294
0
        // did.
295
0
        mChannel->GetContentLength(&mBytesRead);
296
0
    }
297
0
298
0
    mService->NotifyLoadCompleted(this);
299
0
    mService->DispatchEvent(this, mShouldFireLoadEvent || NS_SUCCEEDED(aStatus));
300
0
    mService->RemoveNodeAndMaybeStartNextPrefetchURI(this);
301
0
    return NS_OK;
302
0
}
303
304
//-----------------------------------------------------------------------------
305
// nsPrefetchNode::nsIInterfaceRequestor
306
//-----------------------------------------------------------------------------
307
308
NS_IMETHODIMP
309
nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult)
310
0
{
311
0
    if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
312
0
        NS_ADDREF_THIS();
313
0
        *aResult = static_cast<nsIChannelEventSink *>(this);
314
0
        return NS_OK;
315
0
    }
316
0
317
0
    if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
318
0
        NS_ADDREF_THIS();
319
0
        *aResult = static_cast<nsIRedirectResultListener *>(this);
320
0
        return NS_OK;
321
0
    }
322
0
323
0
    return NS_ERROR_NO_INTERFACE;
324
0
}
325
326
//-----------------------------------------------------------------------------
327
// nsPrefetchNode::nsIChannelEventSink
328
//-----------------------------------------------------------------------------
329
330
NS_IMETHODIMP
331
nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
332
                                       nsIChannel *aNewChannel,
333
                                       uint32_t aFlags,
334
                                       nsIAsyncVerifyRedirectCallback *callback)
335
0
{
336
0
    nsCOMPtr<nsIURI> newURI;
337
0
    nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
338
0
    if (NS_FAILED(rv))
339
0
        return rv;
340
0
341
0
    bool match;
342
0
    rv = newURI->SchemeIs("http", &match); 
343
0
    if (NS_FAILED(rv) || !match) {
344
0
        rv = newURI->SchemeIs("https", &match); 
345
0
        if (NS_FAILED(rv) || !match) {
346
0
            LOG(("rejected: URL is not of type http/https\n"));
347
0
            return NS_ERROR_ABORT;
348
0
        }
349
0
    }
350
0
351
0
    // HTTP request headers are not automatically forwarded to the new channel.
352
0
    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
353
0
    NS_ENSURE_STATE(httpChannel);
354
0
355
0
    rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
356
0
                                       NS_LITERAL_CSTRING("prefetch"),
357
0
                                       false);
358
0
    MOZ_ASSERT(NS_SUCCEEDED(rv));
359
0
360
0
    // Assign to mChannel after we get notification about success of the
361
0
    // redirect in OnRedirectResult.
362
0
    mRedirectChannel = aNewChannel;
363
0
364
0
    callback->OnRedirectVerifyCallback(NS_OK);
365
0
    return NS_OK;
366
0
}
367
368
//-----------------------------------------------------------------------------
369
// nsPrefetchNode::nsIRedirectResultListener
370
//-----------------------------------------------------------------------------
371
372
NS_IMETHODIMP
373
nsPrefetchNode::OnRedirectResult(bool proceeding)
374
0
{
375
0
    if (proceeding && mRedirectChannel)
376
0
        mChannel = mRedirectChannel;
377
0
378
0
    mRedirectChannel = nullptr;
379
0
380
0
    return NS_OK;
381
0
}
382
383
//-----------------------------------------------------------------------------
384
// nsPrefetchService <public>
385
//-----------------------------------------------------------------------------
386
387
nsPrefetchService::nsPrefetchService()
388
    : mMaxParallelism(6)
389
    , mStopCount(0)
390
    , mHaveProcessed(false)
391
    , mPrefetchDisabled(true)
392
    , mPreloadDisabled(true)
393
    , mAggressive(false)
394
0
{
395
0
}
396
397
nsPrefetchService::~nsPrefetchService()
398
0
{
399
0
    Preferences::RemoveObserver(this, PREFETCH_PREF);
400
0
    Preferences::RemoveObserver(this, PRELOAD_PREF);
401
0
    Preferences::RemoveObserver(this, PARALLELISM_PREF);
402
0
    Preferences::RemoveObserver(this, AGGRESSIVE_PREF);
403
0
    // cannot reach destructor if prefetch in progress (listener owns reference
404
0
    // to this service)
405
0
    EmptyPrefetchQueue();
406
0
}
407
408
nsresult
409
nsPrefetchService::Init()
410
0
{
411
0
    nsresult rv;
412
0
413
0
    // read prefs and hook up pref observer
414
0
    mPrefetchDisabled = !Preferences::GetBool(PREFETCH_PREF, !mPrefetchDisabled);
415
0
    Preferences::AddWeakObserver(this, PREFETCH_PREF);
416
0
417
0
    mPreloadDisabled = !Preferences::GetBool(PRELOAD_PREF, !mPreloadDisabled);
418
0
    Preferences::AddWeakObserver(this, PRELOAD_PREF);
419
0
420
0
    mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
421
0
    if (mMaxParallelism < 1) {
422
0
        mMaxParallelism = 1;
423
0
    }
424
0
    Preferences::AddWeakObserver(this, PARALLELISM_PREF);
425
0
426
0
    mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
427
0
    Preferences::AddWeakObserver(this, AGGRESSIVE_PREF);
428
0
429
0
    // Observe xpcom-shutdown event
430
0
    nsCOMPtr<nsIObserverService> observerService =
431
0
      mozilla::services::GetObserverService();
432
0
    if (!observerService)
433
0
      return NS_ERROR_FAILURE;
434
0
435
0
    rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
436
0
    NS_ENSURE_SUCCESS(rv, rv);
437
0
438
0
    if (!mPrefetchDisabled || !mPreloadDisabled) {
439
0
        AddProgressListener();
440
0
    }
441
0
442
0
    return NS_OK;
443
0
}
444
445
void
446
nsPrefetchService::RemoveNodeAndMaybeStartNextPrefetchURI(nsPrefetchNode *aFinished)
447
0
{
448
0
    if (aFinished) {
449
0
        mCurrentNodes.RemoveElement(aFinished);
450
0
    }
451
0
452
0
    if ((!mStopCount && mHaveProcessed) || mAggressive) {
453
0
        ProcessNextPrefetchURI();
454
0
    }
455
0
}
456
457
void
458
nsPrefetchService::ProcessNextPrefetchURI()
459
0
{
460
0
    if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
461
0
        // We already have enough prefetches going on, so hold off
462
0
        // for now.
463
0
        return;
464
0
    }
465
0
466
0
    nsresult rv;
467
0
468
0
    do {
469
0
        if (mPrefetchQueue.empty()) {
470
0
          break;
471
0
        }
472
0
        RefPtr<nsPrefetchNode> node = mPrefetchQueue.front().forget();
473
0
        mPrefetchQueue.pop_front();
474
0
475
0
        if (LOG_ENABLED()) {
476
0
            LOG(("ProcessNextPrefetchURI [%s]\n",
477
0
                 node->mURI->GetSpecOrDefault().get())); }
478
0
479
0
        //
480
0
        // if opening the channel fails (e.g. security check returns an error),
481
0
        // send an error event and then just skip to the next uri
482
0
        //
483
0
        rv = node->OpenChannel();
484
0
        if (NS_SUCCEEDED(rv)) {
485
0
            mCurrentNodes.AppendElement(node);
486
0
        } else {
487
0
          DispatchEvent(node, false);
488
0
        }
489
0
    }
490
0
    while (NS_FAILED(rv));
491
0
}
492
493
void
494
nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node)
495
0
{
496
0
    nsCOMPtr<nsIObserverService> observerService =
497
0
      mozilla::services::GetObserverService();
498
0
    if (!observerService)
499
0
      return;
500
0
501
0
    observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
502
0
                                     (node->mPreload) ? "preload-load-requested"
503
0
                                                      : "prefetch-load-requested",
504
0
                                     nullptr);
505
0
}
506
507
void
508
nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node)
509
0
{
510
0
    nsCOMPtr<nsIObserverService> observerService =
511
0
      mozilla::services::GetObserverService();
512
0
    if (!observerService)
513
0
      return;
514
0
515
0
    observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
516
0
                                     (node->mPreload) ? "preload-load-completed"
517
0
                                                      : "prefetch-load-completed",
518
0
                                     nullptr);
519
0
}
520
521
void
522
nsPrefetchService::DispatchEvent(nsPrefetchNode *node, bool aSuccess)
523
0
{
524
0
    for (uint32_t i = 0; i < node->mSources.Length(); i++) {
525
0
      nsCOMPtr<nsINode> domNode = do_QueryReferent(node->mSources.ElementAt(i));
526
0
      if (domNode && domNode->IsInComposedDoc()) {
527
0
        // We don't dispatch synchronously since |node| might be in a DocGroup
528
0
        // that we're not allowed to touch. (Our network request happens in the
529
0
        // DocGroup of one of the mSources nodes--not necessarily this one).
530
0
        RefPtr<AsyncEventDispatcher> dispatcher =
531
0
          new AsyncEventDispatcher(domNode,
532
0
                                   aSuccess ?
533
0
                                    NS_LITERAL_STRING("load") :
534
0
                                    NS_LITERAL_STRING("error"),
535
0
                                   CanBubble::eNo);
536
0
        dispatcher->RequireNodeInDocument();
537
0
        dispatcher->PostDOMEvent();
538
0
      }
539
0
    }
540
0
}
541
542
//-----------------------------------------------------------------------------
543
// nsPrefetchService <private>
544
//-----------------------------------------------------------------------------
545
546
void
547
nsPrefetchService::AddProgressListener()
548
0
{
549
0
    // Register as an observer for the document loader  
550
0
    nsCOMPtr<nsIWebProgress> progress = 
551
0
        do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
552
0
    if (progress)
553
0
        progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
554
0
}
555
556
void
557
nsPrefetchService::RemoveProgressListener()
558
0
{
559
0
    // Register as an observer for the document loader  
560
0
    nsCOMPtr<nsIWebProgress> progress =
561
0
        do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
562
0
    if (progress)
563
0
        progress->RemoveProgressListener(this);
564
0
}
565
566
nsresult
567
nsPrefetchService::EnqueueURI(nsIURI *aURI,
568
                              nsIURI *aReferrerURI,
569
                              nsINode *aSource,
570
                              nsPrefetchNode **aNode)
571
0
{
572
0
    RefPtr<nsPrefetchNode> node = new nsPrefetchNode(this, aURI, aReferrerURI,
573
0
                                                     aSource,
574
0
                                                     nsIContentPolicy::TYPE_OTHER,
575
0
                                                     false);
576
0
    mPrefetchQueue.push_back(node);
577
0
    node.forget(aNode);
578
0
    return NS_OK;
579
0
}
580
581
void
582
nsPrefetchService::EmptyPrefetchQueue()
583
0
{
584
0
    while (!mPrefetchQueue.empty()) {
585
0
        mPrefetchQueue.pop_back();
586
0
    }
587
0
}
588
589
void
590
nsPrefetchService::StartPrefetching()
591
0
{
592
0
    //
593
0
    // at initialization time we might miss the first DOCUMENT START
594
0
    // notification, so we have to be careful to avoid letting our
595
0
    // stop count go negative.
596
0
    //
597
0
    if (mStopCount > 0)
598
0
        mStopCount--;
599
0
600
0
    LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
601
0
602
0
    // only start prefetching after we've received enough DOCUMENT
603
0
    // STOP notifications.  we do this inorder to defer prefetching
604
0
    // until after all sub-frames have finished loading.
605
0
    if (!mStopCount) {
606
0
        mHaveProcessed = true;
607
0
        while (!mPrefetchQueue.empty() &&
608
0
               mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
609
0
            ProcessNextPrefetchURI();
610
0
        }
611
0
    }
612
0
}
613
614
void
615
nsPrefetchService::StopPrefetching()
616
0
{
617
0
    mStopCount++;
618
0
619
0
    LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
620
0
621
0
    // When we start a load, we need to stop all prefetches that has been
622
0
    // added by the old load, therefore call StopAll only at the moment we
623
0
    // switch to a new page load (i.e. mStopCount == 1).
624
0
    // TODO: do not stop prefetches that are relevant for the new load.
625
0
    if (mStopCount == 1) {
626
0
        StopAll();
627
0
    }
628
0
}
629
630
void
631
nsPrefetchService::StopCurrentPrefetchsPreloads(bool aPreload)
632
0
{
633
0
    for (int32_t i = mCurrentNodes.Length() - 1; i >= 0; --i) {
634
0
        if (mCurrentNodes[i]->mPreload == aPreload) {
635
0
            mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
636
0
            mCurrentNodes.RemoveElementAt(i);
637
0
        }
638
0
    }
639
0
640
0
    if (!aPreload) {
641
0
        EmptyPrefetchQueue();
642
0
    }
643
0
}
644
645
void
646
nsPrefetchService::StopAll()
647
0
{
648
0
    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
649
0
        mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
650
0
    }
651
0
    mCurrentNodes.Clear();
652
0
    EmptyPrefetchQueue();
653
0
}
654
655
nsresult
656
nsPrefetchService::CheckURIScheme(nsIURI *aURI, nsIURI *aReferrerURI)
657
0
{
658
0
    //
659
0
    // XXX we should really be asking the protocol handler if it supports
660
0
    // caching, so we can determine if there is any value to prefetching.
661
0
    // for now, we'll only prefetch http and https links since we know that's
662
0
    // the most common case.
663
0
    //
664
0
    bool match;
665
0
    nsresult rv = aURI->SchemeIs("http", &match);
666
0
    if (NS_FAILED(rv) || !match) {
667
0
        rv = aURI->SchemeIs("https", &match);
668
0
        if (NS_FAILED(rv) || !match) {
669
0
            LOG(("rejected: URL is not of type http/https\n"));
670
0
            return NS_ERROR_ABORT;
671
0
        }
672
0
    }
673
0
674
0
    // 
675
0
    // the referrer URI must be http:
676
0
    //
677
0
    rv = aReferrerURI->SchemeIs("http", &match);
678
0
    if (NS_FAILED(rv) || !match) {
679
0
        rv = aReferrerURI->SchemeIs("https", &match);
680
0
        if (NS_FAILED(rv) || !match) {
681
0
            LOG(("rejected: referrer URL is neither http nor https\n"));
682
0
            return NS_ERROR_ABORT;
683
0
        }
684
0
    }
685
0
686
0
    return NS_OK;
687
0
}
688
689
//-----------------------------------------------------------------------------
690
// nsPrefetchService::nsISupports
691
//-----------------------------------------------------------------------------
692
693
NS_IMPL_ISUPPORTS(nsPrefetchService,
694
                  nsIPrefetchService,
695
                  nsIWebProgressListener,
696
                  nsIObserver,
697
                  nsISupportsWeakReference)
698
699
//-----------------------------------------------------------------------------
700
// nsPrefetchService::nsIPrefetchService
701
//-----------------------------------------------------------------------------
702
703
nsresult
704
nsPrefetchService::Preload(nsIURI *aURI,
705
                           nsIURI *aReferrerURI,
706
                           nsINode *aSource,
707
                           nsContentPolicyType aPolicyType)
708
0
{
709
0
    NS_ENSURE_ARG_POINTER(aURI);
710
0
    NS_ENSURE_ARG_POINTER(aReferrerURI);
711
0
    if (LOG_ENABLED()) {
712
0
        LOG(("PreloadURI [%s]\n", aURI->GetSpecOrDefault().get()));
713
0
    }
714
0
715
0
    if (mPreloadDisabled) {
716
0
        LOG(("rejected: preload service is disabled\n"));
717
0
        return NS_ERROR_ABORT;
718
0
    }
719
0
720
0
    nsresult rv = CheckURIScheme(aURI, aReferrerURI);
721
0
    if (NS_FAILED(rv)) {
722
0
        return rv;
723
0
    }
724
0
725
0
    // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
726
0
    // or possibly nsIRequest::loadFlags to determine if this URI should be
727
0
    // prefetched.
728
0
    //
729
0
730
0
    if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
731
0
        if (aSource && aSource->IsInComposedDoc()) {
732
0
            RefPtr<AsyncEventDispatcher> asyncDispatcher =
733
0
                new AsyncEventDispatcher(aSource,
734
0
                                         NS_LITERAL_STRING("error"),
735
0
                                         CanBubble::eNo,
736
0
                                         ChromeOnlyDispatch::eNo);
737
0
            asyncDispatcher->RunDOMEventWhenSafe();
738
0
        }
739
0
        return NS_OK;
740
0
    }
741
0
742
0
    //
743
0
    // Check whether it is being preloaded.
744
0
    //
745
0
    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
746
0
        bool equals;
747
0
        if ((mCurrentNodes[i]->mPolicyType == aPolicyType) &&
748
0
            NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
749
0
            equals) {
750
0
            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
751
0
            if (mCurrentNodes[i]->mSources.IndexOf(source) ==
752
0
                mCurrentNodes[i]->mSources.NoIndex) {
753
0
                LOG(("URL is already being preloaded, add a new reference "
754
0
                     "document\n"));
755
0
                mCurrentNodes[i]->mSources.AppendElement(source);
756
0
                return NS_OK;
757
0
            } else {
758
0
                LOG(("URL is already being preloaded by this document"));
759
0
                return NS_ERROR_ABORT;
760
0
            }
761
0
        }
762
0
    }
763
0
764
0
    LOG(("This is a preload, so start loading immediately.\n"));
765
0
    RefPtr<nsPrefetchNode> enqueuedNode;
766
0
    enqueuedNode = new nsPrefetchNode(this, aURI, aReferrerURI,
767
0
                                      aSource, aPolicyType, true);
768
0
769
0
    NotifyLoadRequested(enqueuedNode);
770
0
    rv = enqueuedNode->OpenChannel();
771
0
    if (NS_SUCCEEDED(rv)) {
772
0
        mCurrentNodes.AppendElement(enqueuedNode);
773
0
    } else {
774
0
        if (aSource && aSource->IsInComposedDoc()) {
775
0
            RefPtr<AsyncEventDispatcher> asyncDispatcher =
776
0
                new AsyncEventDispatcher(aSource,
777
0
                                         NS_LITERAL_STRING("error"),
778
0
                                         CanBubble::eNo,
779
0
                                         ChromeOnlyDispatch::eNo);
780
0
            asyncDispatcher->RunDOMEventWhenSafe();
781
0
        }
782
0
    }
783
0
    return NS_OK;
784
0
}
785
786
nsresult
787
nsPrefetchService::Prefetch(nsIURI *aURI,
788
                            nsIURI *aReferrerURI,
789
                            nsINode *aSource,
790
                            bool aExplicit)
791
0
{
792
0
    NS_ENSURE_ARG_POINTER(aURI);
793
0
    NS_ENSURE_ARG_POINTER(aReferrerURI);
794
0
795
0
    if (LOG_ENABLED()) {
796
0
        LOG(("PrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
797
0
    }
798
0
799
0
    if (mPrefetchDisabled) {
800
0
        LOG(("rejected: prefetch service is disabled\n"));
801
0
        return NS_ERROR_ABORT;
802
0
    }
803
0
804
0
    nsresult rv = CheckURIScheme(aURI, aReferrerURI);
805
0
    if (NS_FAILED(rv)) {
806
0
        return rv;
807
0
    }
808
0
809
0
    // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
810
0
    // or possibly nsIRequest::loadFlags to determine if this URI should be
811
0
    // prefetched.
812
0
    //
813
0
814
0
    // skip URLs that contain query strings, except URLs for which prefetching
815
0
    // has been explicitly requested.
816
0
    if (!aExplicit) {
817
0
        nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
818
0
        if (NS_FAILED(rv)) return rv;
819
0
        nsAutoCString query;
820
0
        rv = url->GetQuery(query);
821
0
        if (NS_FAILED(rv) || !query.IsEmpty()) {
822
0
            LOG(("rejected: URL has a query string\n"));
823
0
            return NS_ERROR_ABORT;
824
0
        }
825
0
    }
826
0
827
0
    //
828
0
    // Check whether it is being prefetched.
829
0
    //
830
0
    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
831
0
        bool equals;
832
0
        if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
833
0
            equals) {
834
0
            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
835
0
            if (mCurrentNodes[i]->mSources.IndexOf(source) ==
836
0
                mCurrentNodes[i]->mSources.NoIndex) {
837
0
                LOG(("URL is already being prefetched, add a new reference "
838
0
                     "document\n"));
839
0
                mCurrentNodes[i]->mSources.AppendElement(source);
840
0
                return NS_OK;
841
0
            } else {
842
0
                LOG(("URL is already being prefetched by this document"));
843
0
                return NS_ERROR_ABORT;
844
0
            }
845
0
        }
846
0
    }
847
0
848
0
    //
849
0
    // Check whether it is on the prefetch queue.
850
0
    //
851
0
    for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mPrefetchQueue.begin();
852
0
         nodeIt != mPrefetchQueue.end(); nodeIt++) {
853
0
        bool equals;
854
0
        RefPtr<nsPrefetchNode> node = nodeIt->get();
855
0
        if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
856
0
            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
857
0
            if (node->mSources.IndexOf(source) ==
858
0
                node->mSources.NoIndex) {
859
0
                LOG(("URL is already being prefetched, add a new reference "
860
0
                     "document\n"));
861
0
                node->mSources.AppendElement(do_GetWeakReference(aSource));
862
0
                return NS_OK;
863
0
            } else {
864
0
                LOG(("URL is already being prefetched by this document"));
865
0
                return NS_ERROR_ABORT;
866
0
            }
867
0
868
0
        }
869
0
    }
870
0
871
0
    RefPtr<nsPrefetchNode> enqueuedNode;
872
0
    rv = EnqueueURI(aURI, aReferrerURI, aSource,
873
0
                    getter_AddRefs(enqueuedNode));
874
0
    NS_ENSURE_SUCCESS(rv, rv);
875
0
876
0
    NotifyLoadRequested(enqueuedNode);
877
0
878
0
    // if there are no pages loading, kick off the request immediately
879
0
    if ((!mStopCount && mHaveProcessed) || mAggressive) {
880
0
        ProcessNextPrefetchURI();
881
0
    }
882
0
883
0
    return NS_OK;
884
0
}
885
886
NS_IMETHODIMP
887
nsPrefetchService::CancelPrefetchPreloadURI(nsIURI* aURI,
888
                                            nsINode* aSource)
889
0
{
890
0
    NS_ENSURE_ARG_POINTER(aURI);
891
0
892
0
    if (LOG_ENABLED()) {
893
0
        LOG(("CancelPrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
894
0
    }
895
0
896
0
    //
897
0
    // look in current prefetches
898
0
    //
899
0
    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
900
0
        bool equals;
901
0
        if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
902
0
            equals) {
903
0
            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
904
0
            if (mCurrentNodes[i]->mSources.IndexOf(source) !=
905
0
                mCurrentNodes[i]->mSources.NoIndex) {
906
0
                mCurrentNodes[i]->mSources.RemoveElement(source);
907
0
                if (mCurrentNodes[i]->mSources.IsEmpty()) {
908
0
                    mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
909
0
                    mCurrentNodes.RemoveElementAt(i);
910
0
                }
911
0
                return NS_OK;
912
0
            }
913
0
            return NS_ERROR_FAILURE;
914
0
        }
915
0
    }
916
0
917
0
    //
918
0
    // look into the prefetch queue
919
0
    //
920
0
    for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mPrefetchQueue.begin();
921
0
         nodeIt != mPrefetchQueue.end(); nodeIt++) {
922
0
        bool equals;
923
0
        RefPtr<nsPrefetchNode> node = nodeIt->get();
924
0
        if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
925
0
            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
926
0
            if (node->mSources.IndexOf(source) !=
927
0
                node->mSources.NoIndex) {
928
0
929
#ifdef DEBUG
930
                int32_t inx = node->mSources.IndexOf(source);
931
                nsCOMPtr<nsINode> domNode =
932
                    do_QueryReferent(node->mSources.ElementAt(inx));
933
                MOZ_ASSERT(domNode);
934
#endif
935
936
0
                node->mSources.RemoveElement(source);
937
0
                if (node->mSources.IsEmpty()) {
938
0
                    mPrefetchQueue.erase(nodeIt);
939
0
                }
940
0
                return NS_OK;
941
0
            }
942
0
            return NS_ERROR_FAILURE;
943
0
        }
944
0
    }
945
0
946
0
    // not found!
947
0
    return NS_ERROR_FAILURE;
948
0
}
949
950
NS_IMETHODIMP
951
nsPrefetchService::PreloadURI(nsIURI *aURI,
952
                              nsIURI *aReferrerURI,
953
                              nsINode *aSource,
954
                              nsContentPolicyType aPolicyType)
955
0
{
956
0
    return Preload(aURI, aReferrerURI, aSource, aPolicyType);
957
0
}
958
959
NS_IMETHODIMP
960
nsPrefetchService::PrefetchURI(nsIURI *aURI,
961
                               nsIURI *aReferrerURI,
962
                               nsINode *aSource,
963
                               bool aExplicit)
964
0
{
965
0
    return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
966
0
}
967
968
NS_IMETHODIMP
969
nsPrefetchService::HasMoreElements(bool *aHasMore)
970
0
{
971
0
    *aHasMore = (mCurrentNodes.Length() || !mPrefetchQueue.empty());
972
0
    return NS_OK;
973
0
}
974
975
//-----------------------------------------------------------------------------
976
// nsPrefetchService::nsIWebProgressListener
977
//-----------------------------------------------------------------------------
978
979
NS_IMETHODIMP
980
nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress,
981
                                    nsIRequest *aRequest, 
982
                                    int32_t curSelfProgress, 
983
                                    int32_t maxSelfProgress, 
984
                                    int32_t curTotalProgress, 
985
                                    int32_t maxTotalProgress)
986
0
{
987
0
    MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
988
0
    return NS_OK;
989
0
}
990
991
NS_IMETHODIMP 
992
nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress, 
993
                                 nsIRequest *aRequest, 
994
                                 uint32_t progressStateFlags, 
995
                                 nsresult aStatus)
996
0
{
997
0
    if (progressStateFlags & STATE_IS_DOCUMENT) {
998
0
        if (progressStateFlags & STATE_STOP)
999
0
            StartPrefetching();
1000
0
        else if (progressStateFlags & STATE_START)
1001
0
            StopPrefetching();
1002
0
    }
1003
0
            
1004
0
    return NS_OK;
1005
0
}
1006
1007
1008
NS_IMETHODIMP
1009
nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
1010
                                    nsIRequest* aRequest,
1011
                                    nsIURI *location,
1012
                                    uint32_t aFlags)
1013
0
{
1014
0
    MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1015
0
    return NS_OK;
1016
0
}
1017
1018
NS_IMETHODIMP 
1019
nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
1020
                                  nsIRequest* aRequest,
1021
                                  nsresult aStatus,
1022
                                  const char16_t* aMessage)
1023
0
{
1024
0
    MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1025
0
    return NS_OK;
1026
0
}
1027
1028
NS_IMETHODIMP 
1029
nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress, 
1030
                                    nsIRequest *aRequest, 
1031
                                    uint32_t state)
1032
0
{
1033
0
    MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1034
0
    return NS_OK;
1035
0
}
1036
1037
//-----------------------------------------------------------------------------
1038
// nsPrefetchService::nsIObserver
1039
//-----------------------------------------------------------------------------
1040
1041
NS_IMETHODIMP
1042
nsPrefetchService::Observe(nsISupports     *aSubject,
1043
                           const char      *aTopic,
1044
                           const char16_t *aData)
1045
0
{
1046
0
    LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
1047
0
1048
0
    if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1049
0
        StopAll();
1050
0
        mPrefetchDisabled = true;
1051
0
        mPreloadDisabled = true;
1052
0
    }
1053
0
    else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1054
0
        const nsCString converted = NS_ConvertUTF16toUTF8(aData);
1055
0
        const char* pref = converted.get();
1056
0
        if (!strcmp(pref, PREFETCH_PREF)) {
1057
0
            if (Preferences::GetBool(PREFETCH_PREF, false)) {
1058
0
                if (mPrefetchDisabled) {
1059
0
                    LOG(("enabling prefetching\n"));
1060
0
                    mPrefetchDisabled = false;
1061
0
                    if (mPreloadDisabled) {
1062
0
                        AddProgressListener();
1063
0
                    }
1064
0
                }
1065
0
            } else {
1066
0
                if (!mPrefetchDisabled) {
1067
0
                    LOG(("disabling prefetching\n"));
1068
0
                    StopCurrentPrefetchsPreloads(false);
1069
0
                    mPrefetchDisabled = true;
1070
0
                    if (mPreloadDisabled) {
1071
0
                        RemoveProgressListener();
1072
0
                    }
1073
0
                }
1074
0
            }
1075
0
        } else if (!strcmp(pref, PRELOAD_PREF)) {
1076
0
            if (Preferences::GetBool(PRELOAD_PREF, false)) {
1077
0
                if (mPreloadDisabled) {
1078
0
                    LOG(("enabling preloading\n"));
1079
0
                    mPreloadDisabled = false;
1080
0
                    if (mPrefetchDisabled) {
1081
0
                        AddProgressListener();
1082
0
                    }
1083
0
                }
1084
0
            } else {
1085
0
                if (!mPreloadDisabled) {
1086
0
                  LOG(("disabling preloading\n"));
1087
0
                  StopCurrentPrefetchsPreloads(true);
1088
0
                  mPreloadDisabled = true;
1089
0
                  if (mPrefetchDisabled) {
1090
0
                      RemoveProgressListener();
1091
0
                  }
1092
0
                }
1093
0
            }
1094
0
        } else if (!strcmp(pref, PARALLELISM_PREF)) {
1095
0
            mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
1096
0
            if (mMaxParallelism < 1) {
1097
0
                mMaxParallelism = 1;
1098
0
            }
1099
0
            // If our parallelism has increased, go ahead and kick off enough
1100
0
            // prefetches to fill up our allowance. If we're now over our
1101
0
            // allowance, we'll just silently let some of them finish to get
1102
0
            // back below our limit.
1103
0
            while (((!mStopCount && mHaveProcessed) || mAggressive) &&
1104
0
                   !mPrefetchQueue.empty() &&
1105
0
                   mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
1106
0
                ProcessNextPrefetchURI();
1107
0
            }
1108
0
        } else if (!strcmp(pref, AGGRESSIVE_PREF)) {
1109
0
            mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
1110
0
            // in aggressive mode, start prefetching immediately
1111
0
            if (mAggressive) {
1112
0
                while (mStopCount && !mPrefetchQueue.empty() &&
1113
0
                       mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
1114
0
                    ProcessNextPrefetchURI();
1115
0
                }
1116
0
            }
1117
0
        }
1118
0
    }
1119
0
1120
0
    return NS_OK;
1121
0
}
1122
1123
// vim: ts=4 sw=4 expandtab