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