Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "base/basictypes.h"
8 : #include "mozilla/net/NeckoCommon.h"
9 : #include "mozilla/net/NeckoChild.h"
10 : #include "nsURLHelper.h"
11 :
12 : #include "nsHTMLDNSPrefetch.h"
13 : #include "nsCOMPtr.h"
14 : #include "nsString.h"
15 :
16 : #include "nsNetUtil.h"
17 : #include "nsNetCID.h"
18 : #include "nsIProtocolHandler.h"
19 :
20 : #include "nsIDNSListener.h"
21 : #include "nsIWebProgressListener.h"
22 : #include "nsIWebProgress.h"
23 : #include "nsCURILoader.h"
24 : #include "nsIDNSRecord.h"
25 : #include "nsIDNSService.h"
26 : #include "nsICancelable.h"
27 : #include "nsGkAtoms.h"
28 : #include "nsIDocument.h"
29 : #include "nsThreadUtils.h"
30 : #include "nsITimer.h"
31 : #include "nsIObserverService.h"
32 : #include "mozilla/dom/Link.h"
33 :
34 : #include "mozilla/Preferences.h"
35 :
36 : using namespace mozilla;
37 : using namespace mozilla::dom;
38 : using namespace mozilla::net;
39 :
40 : static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
41 : bool sDisablePrefetchHTTPSPref;
42 : static bool sInitialized = false;
43 : static nsIDNSService *sDNSService = nullptr;
44 : static nsHTMLDNSPrefetch::nsDeferrals *sPrefetches = nullptr;
45 : static nsHTMLDNSPrefetch::nsListener *sDNSListener = nullptr;
46 :
47 : nsresult
48 3 : nsHTMLDNSPrefetch::Initialize()
49 : {
50 3 : if (sInitialized) {
51 0 : NS_WARNING("Initialize() called twice");
52 0 : return NS_OK;
53 : }
54 :
55 3 : sPrefetches = new nsHTMLDNSPrefetch::nsDeferrals();
56 3 : NS_ADDREF(sPrefetches);
57 :
58 3 : sDNSListener = new nsHTMLDNSPrefetch::nsListener();
59 3 : NS_ADDREF(sDNSListener);
60 :
61 3 : sPrefetches->Activate();
62 :
63 : Preferences::AddBoolVarCache(&sDisablePrefetchHTTPSPref,
64 3 : "network.dns.disablePrefetchFromHTTPS");
65 :
66 : // Default is false, so we need an explicit call to prime the cache.
67 3 : sDisablePrefetchHTTPSPref =
68 3 : Preferences::GetBool("network.dns.disablePrefetchFromHTTPS", true);
69 :
70 3 : NS_IF_RELEASE(sDNSService);
71 : nsresult rv;
72 3 : rv = CallGetService(kDNSServiceCID, &sDNSService);
73 3 : if (NS_FAILED(rv)) return rv;
74 :
75 3 : if (IsNeckoChild())
76 2 : NeckoChild::InitNeckoChild();
77 :
78 3 : sInitialized = true;
79 3 : return NS_OK;
80 : }
81 :
82 : nsresult
83 0 : nsHTMLDNSPrefetch::Shutdown()
84 : {
85 0 : if (!sInitialized) {
86 0 : NS_WARNING("Not Initialized");
87 0 : return NS_OK;
88 : }
89 0 : sInitialized = false;
90 0 : NS_IF_RELEASE(sDNSService);
91 0 : NS_IF_RELEASE(sPrefetches);
92 0 : NS_IF_RELEASE(sDNSListener);
93 :
94 0 : return NS_OK;
95 : }
96 :
97 : bool
98 0 : nsHTMLDNSPrefetch::IsAllowed (nsIDocument *aDocument)
99 : {
100 : // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
101 0 : return aDocument->IsDNSPrefetchAllowed() && aDocument->GetWindow();
102 : }
103 :
104 : nsresult
105 0 : nsHTMLDNSPrefetch::Prefetch(Link *aElement, uint16_t flags)
106 : {
107 0 : if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
108 0 : return NS_ERROR_NOT_AVAILABLE;
109 :
110 0 : return sPrefetches->Add(flags, aElement);
111 : }
112 :
113 : nsresult
114 0 : nsHTMLDNSPrefetch::PrefetchLow(Link *aElement)
115 : {
116 0 : return Prefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW);
117 : }
118 :
119 : nsresult
120 0 : nsHTMLDNSPrefetch::PrefetchMedium(Link *aElement)
121 : {
122 0 : return Prefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
123 : }
124 :
125 : nsresult
126 0 : nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
127 : {
128 0 : return Prefetch(aElement, 0);
129 : }
130 :
131 : nsresult
132 0 : nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname,
133 : const OriginAttributes &aOriginAttributes,
134 : uint16_t flags)
135 : {
136 0 : if (IsNeckoChild()) {
137 : // We need to check IsEmpty() because net_IsValidHostName()
138 : // considers empty strings to be valid hostnames
139 0 : if (!hostname.IsEmpty() &&
140 0 : net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
141 : // during shutdown gNeckoChild might be null
142 0 : if (gNeckoChild) {
143 0 : gNeckoChild->SendHTMLDNSPrefetch(nsString(hostname),
144 0 : aOriginAttributes, flags);
145 : }
146 : }
147 0 : return NS_OK;
148 : }
149 :
150 0 : if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
151 0 : return NS_ERROR_NOT_AVAILABLE;
152 :
153 0 : nsCOMPtr<nsICancelable> tmpOutstanding;
154 0 : return sDNSService->AsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
155 0 : flags | nsIDNSService::RESOLVE_SPECULATE,
156 : sDNSListener, nullptr, aOriginAttributes,
157 0 : getter_AddRefs(tmpOutstanding));
158 : }
159 :
160 : nsresult
161 0 : nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname,
162 : const OriginAttributes &aOriginAttributes)
163 : {
164 0 : return Prefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_LOW);
165 : }
166 :
167 : nsresult
168 0 : nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname,
169 : const OriginAttributes &aOriginAttributes)
170 : {
171 0 : return Prefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
172 : }
173 :
174 : nsresult
175 0 : nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname,
176 : const OriginAttributes &aOriginAttributes)
177 : {
178 0 : return Prefetch(hostname, aOriginAttributes, 0);
179 : }
180 :
181 : nsresult
182 0 : nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
183 : uint16_t flags,
184 : nsresult aReason)
185 : {
186 0 : if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
187 0 : return NS_ERROR_NOT_AVAILABLE;
188 :
189 0 : nsAutoString hostname;
190 0 : aElement->GetHostname(hostname);
191 :
192 0 : Element* element = aElement->GetElement();
193 0 : NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
194 :
195 0 : return CancelPrefetch(hostname,
196 0 : element->NodePrincipal()
197 0 : ->OriginAttributesRef(),
198 0 : flags, aReason);
199 : }
200 :
201 : nsresult
202 0 : nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
203 : const OriginAttributes &aOriginAttributes,
204 : uint16_t flags,
205 : nsresult aReason)
206 : {
207 : // Forward this request to Necko Parent if we're a child process
208 0 : if (IsNeckoChild()) {
209 : // We need to check IsEmpty() because net_IsValidHostName()
210 : // considers empty strings to be valid hostnames
211 0 : if (!hostname.IsEmpty() &&
212 0 : net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
213 : // during shutdown gNeckoChild might be null
214 0 : if (gNeckoChild) {
215 0 : gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname),
216 : aOriginAttributes,
217 : flags,
218 0 : aReason);
219 : }
220 : }
221 0 : return NS_OK;
222 : }
223 :
224 0 : if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
225 0 : return NS_ERROR_NOT_AVAILABLE;
226 :
227 : // Forward cancellation to DNS service
228 0 : return sDNSService->CancelAsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
229 : flags
230 0 : | nsIDNSService::RESOLVE_SPECULATE,
231 0 : sDNSListener, aReason, aOriginAttributes);
232 : }
233 :
234 : nsresult
235 0 : nsHTMLDNSPrefetch::CancelPrefetchLow(Link *aElement, nsresult aReason)
236 : {
237 : return CancelPrefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW,
238 0 : aReason);
239 : }
240 :
241 : nsresult
242 0 : nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname,
243 : const OriginAttributes &aOriginAttributes,
244 : nsresult aReason)
245 : {
246 : return CancelPrefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_LOW,
247 0 : aReason);
248 : }
249 :
250 : void
251 0 : nsHTMLDNSPrefetch::LinkDestroyed(Link* aLink)
252 : {
253 0 : MOZ_ASSERT(aLink->IsInDNSPrefetch());
254 0 : if (sPrefetches) {
255 : // Clean up all the possible links at once.
256 0 : sPrefetches->RemoveUnboundLinks();
257 : }
258 0 : }
259 :
260 : /////////////////////////////////////////////////////////////////////////////////////////////////////////
261 :
262 3 : NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsListener,
263 : nsIDNSListener)
264 :
265 : NS_IMETHODIMP
266 0 : nsHTMLDNSPrefetch::nsListener::OnLookupComplete(nsICancelable *request,
267 : nsIDNSRecord *rec,
268 : nsresult status)
269 : {
270 0 : return NS_OK;
271 : }
272 :
273 : /////////////////////////////////////////////////////////////////////////////////////////////////////////
274 :
275 3 : nsHTMLDNSPrefetch::nsDeferrals::nsDeferrals()
276 : : mHead(0),
277 : mTail(0),
278 : mActiveLoaderCount(0),
279 3 : mTimerArmed(false)
280 : {
281 3 : mTimer = do_CreateInstance("@mozilla.org/timer;1");
282 3 : }
283 :
284 0 : nsHTMLDNSPrefetch::nsDeferrals::~nsDeferrals()
285 : {
286 0 : if (mTimerArmed) {
287 0 : mTimerArmed = false;
288 0 : mTimer->Cancel();
289 : }
290 :
291 0 : Flush();
292 0 : }
293 :
294 117 : NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsDeferrals,
295 : nsIWebProgressListener,
296 : nsISupportsWeakReference,
297 : nsIObserver)
298 :
299 : void
300 0 : nsHTMLDNSPrefetch::nsDeferrals::Flush()
301 : {
302 0 : while (mHead != mTail) {
303 0 : if (mEntries[mTail].mElement) {
304 0 : mEntries[mTail].mElement->ClearIsInDNSPrefetch();
305 : }
306 0 : mEntries[mTail].mElement = nullptr;
307 0 : mTail = (mTail + 1) & sMaxDeferredMask;
308 : }
309 0 : }
310 :
311 : nsresult
312 0 : nsHTMLDNSPrefetch::nsDeferrals::Add(uint16_t flags, Link *aElement)
313 : {
314 : // The FIFO has no lock, so it can only be accessed on main thread
315 0 : NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Add must be on main thread");
316 :
317 0 : aElement->OnDNSPrefetchDeferred();
318 :
319 0 : if (((mHead + 1) & sMaxDeferredMask) == mTail)
320 0 : return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
321 :
322 0 : aElement->SetIsInDNSPrefetch();
323 0 : mEntries[mHead].mFlags = flags;
324 0 : mEntries[mHead].mElement = aElement;
325 0 : mHead = (mHead + 1) & sMaxDeferredMask;
326 :
327 0 : if (!mActiveLoaderCount && !mTimerArmed && mTimer) {
328 0 : mTimerArmed = true;
329 0 : mTimer->InitWithNamedFuncCallback(Tick, this, 2000, nsITimer::TYPE_ONE_SHOT,
330 0 : "nsHTMLDNSPrefetch::nsDeferrals::Tick");
331 : }
332 :
333 0 : return NS_OK;
334 : }
335 :
336 : void
337 3 : nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue()
338 : {
339 3 : NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::SubmitQueue must be on main thread");
340 6 : nsCString hostName;
341 3 : if (!sDNSService) return;
342 :
343 3 : while (mHead != mTail) {
344 0 : nsCOMPtr<Link> link = mEntries[mTail].mElement;
345 0 : if (link) {
346 0 : link->ClearIsInDNSPrefetch();
347 : // Only prefetch here if request was deferred and deferral not cancelled
348 0 : if (link && link->HasDeferredDNSPrefetchRequest()) {
349 0 : nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nullptr);
350 0 : bool isLocalResource = false;
351 0 : nsresult rv = NS_OK;
352 0 : Element* element = link->GetElement();
353 :
354 0 : hostName.Truncate();
355 0 : if (hrefURI) {
356 0 : hrefURI->GetAsciiHost(hostName);
357 0 : rv = NS_URIChainHasFlags(hrefURI,
358 : nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
359 0 : &isLocalResource);
360 : }
361 :
362 0 : if (!hostName.IsEmpty() && NS_SUCCEEDED(rv) && !isLocalResource &&
363 : element) {
364 0 : if (IsNeckoChild()) {
365 : // during shutdown gNeckoChild might be null
366 0 : if (gNeckoChild) {
367 0 : gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
368 0 : element->NodePrincipal()
369 0 : ->OriginAttributesRef(),
370 0 : mEntries[mTail].mFlags);
371 : }
372 : } else {
373 0 : nsCOMPtr<nsICancelable> tmpOutstanding;
374 :
375 0 : rv = sDNSService->AsyncResolveNative(hostName,
376 0 : mEntries[mTail].mFlags
377 0 : | nsIDNSService::RESOLVE_SPECULATE,
378 : sDNSListener, nullptr,
379 0 : element->NodePrincipal()
380 0 : ->OriginAttributesRef(),
381 0 : getter_AddRefs(tmpOutstanding));
382 : // Tell link that deferred prefetch was requested
383 0 : if (NS_SUCCEEDED(rv))
384 0 : link->OnDNSPrefetchRequested();
385 : }
386 : }
387 : }
388 : }
389 :
390 0 : mEntries[mTail].mElement = nullptr;
391 0 : mTail = (mTail + 1) & sMaxDeferredMask;
392 : }
393 :
394 3 : if (mTimerArmed) {
395 0 : mTimerArmed = false;
396 0 : mTimer->Cancel();
397 : }
398 : }
399 :
400 : void
401 3 : nsHTMLDNSPrefetch::nsDeferrals::Activate()
402 : {
403 : // Register as an observer for the document loader
404 : nsCOMPtr<nsIWebProgress> progress =
405 6 : do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
406 3 : if (progress)
407 3 : progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
408 :
409 : // Register as an observer for xpcom shutdown events so we can drop any element refs
410 : nsCOMPtr<nsIObserverService> observerService =
411 6 : mozilla::services::GetObserverService();
412 3 : if (observerService)
413 3 : observerService->AddObserver(this, "xpcom-shutdown", true);
414 3 : }
415 :
416 : void
417 0 : nsHTMLDNSPrefetch::nsDeferrals::RemoveUnboundLinks()
418 : {
419 0 : uint16_t tail = mTail;
420 0 : while (mHead != tail) {
421 0 : if (mEntries[tail].mElement &&
422 0 : !mEntries[tail].mElement->GetElement()->IsInComposedDoc()) {
423 0 : mEntries[tail].mElement->ClearIsInDNSPrefetch();
424 0 : mEntries[tail].mElement = nullptr;
425 : }
426 0 : tail = (tail + 1) & sMaxDeferredMask;
427 : }
428 0 : }
429 :
430 : // nsITimer related method
431 :
432 : void
433 0 : nsHTMLDNSPrefetch::nsDeferrals::Tick(nsITimer *aTimer, void *aClosure)
434 : {
435 0 : nsHTMLDNSPrefetch::nsDeferrals *self = (nsHTMLDNSPrefetch::nsDeferrals *) aClosure;
436 :
437 0 : NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Tick must be on main thread");
438 0 : NS_ASSERTION(self->mTimerArmed, "Timer is not armed");
439 :
440 0 : self->mTimerArmed = false;
441 :
442 : // If the queue is not submitted here because there are outstanding pages being loaded,
443 : // there is no need to rearm the timer as the queue will be submtited when those
444 : // loads complete.
445 0 : if (!self->mActiveLoaderCount)
446 0 : self->SubmitQueue();
447 0 : }
448 :
449 : //////////// nsIWebProgressListener methods
450 :
451 : NS_IMETHODIMP
452 16 : nsHTMLDNSPrefetch::nsDeferrals::OnStateChange(nsIWebProgress* aWebProgress,
453 : nsIRequest *aRequest,
454 : uint32_t progressStateFlags,
455 : nsresult aStatus)
456 : {
457 : // The FIFO has no lock, so it can only be accessed on main thread
458 16 : NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::OnStateChange must be on main thread");
459 :
460 16 : if (progressStateFlags & STATE_IS_DOCUMENT) {
461 16 : if (progressStateFlags & STATE_STOP) {
462 :
463 : // Initialization may have missed a STATE_START notification, so do
464 : // not go negative
465 6 : if (mActiveLoaderCount)
466 6 : mActiveLoaderCount--;
467 :
468 6 : if (!mActiveLoaderCount)
469 3 : SubmitQueue();
470 : }
471 10 : else if (progressStateFlags & STATE_START)
472 6 : mActiveLoaderCount++;
473 : }
474 :
475 16 : return NS_OK;
476 : }
477 :
478 : NS_IMETHODIMP
479 0 : nsHTMLDNSPrefetch::nsDeferrals::OnProgressChange(nsIWebProgress *aProgress,
480 : nsIRequest *aRequest,
481 : int32_t curSelfProgress,
482 : int32_t maxSelfProgress,
483 : int32_t curTotalProgress,
484 : int32_t maxTotalProgress)
485 : {
486 0 : return NS_OK;
487 : }
488 :
489 : NS_IMETHODIMP
490 0 : nsHTMLDNSPrefetch::nsDeferrals::OnLocationChange(nsIWebProgress* aWebProgress,
491 : nsIRequest* aRequest,
492 : nsIURI *location,
493 : uint32_t aFlags)
494 : {
495 0 : return NS_OK;
496 : }
497 :
498 : NS_IMETHODIMP
499 0 : nsHTMLDNSPrefetch::nsDeferrals::OnStatusChange(nsIWebProgress* aWebProgress,
500 : nsIRequest* aRequest,
501 : nsresult aStatus,
502 : const char16_t* aMessage)
503 : {
504 0 : return NS_OK;
505 : }
506 :
507 : NS_IMETHODIMP
508 0 : nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(nsIWebProgress *aWebProgress,
509 : nsIRequest *aRequest,
510 : uint32_t state)
511 : {
512 0 : return NS_OK;
513 : }
514 :
515 : //////////// nsIObserver method
516 :
517 : NS_IMETHODIMP
518 0 : nsHTMLDNSPrefetch::nsDeferrals::Observe(nsISupports *subject,
519 : const char *topic,
520 : const char16_t *data)
521 : {
522 0 : if (!strcmp(topic, "xpcom-shutdown"))
523 0 : Flush();
524 :
525 0 : return NS_OK;
526 : }
|