1/*
2 * Copyright (C) 2009 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2012 Igalia S.L.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "DNSResolveQueue.h"
29
30#include <wtf/CurrentTime.h>
31#include <wtf/NeverDestroyed.h>
32
33namespace WebCore {
34
35// When resolve queue is empty, we fire async resolution requests immediately (which is important if the prefetch is triggered by hovering).
36// But during page parsing, we should coalesce identical requests to avoid stressing out the DNS resolver.
37static const int gNamesToResolveImmediately = 4;
38
39// Coalesce prefetch requests for this long before sending them out.
40static const double gCoalesceDelayInSeconds = 1.0;
41
42// Sending many DNS requests at once can overwhelm some gateways. See <rdar://8105550> for specific CFNET issues with CFHost throttling.
43static const int gMaxSimultaneousRequests = 8;
44
45// For a page has links to many outside sites, it is likely that the system DNS resolver won't be able to cache them all anyway, and we don't want
46// to negatively affect other applications' performance by pushing their cached entries out.
47// If we end up with lots of names to prefetch, some will be dropped.
48static const int gMaxRequestsToQueue = 64;
49
50// If there were queued names that couldn't be sent simultaneously, check the state of resolvers after this delay.
51static const double gRetryResolvingInSeconds = 0.1;
52
53DNSResolveQueue& DNSResolveQueue::shared()
54{
55    static NeverDestroyed<DNSResolveQueue> queue;
56
57    return queue;
58}
59
60DNSResolveQueue::DNSResolveQueue()
61    : m_timer(this, &DNSResolveQueue::timerFired)
62    , m_requestsInFlight(0)
63    , m_cachedProxyEnabledStatus(false)
64    , m_lastProxyEnabledStatusCheckTime(0)
65{
66}
67
68bool DNSResolveQueue::isUsingProxy()
69{
70    double time = monotonicallyIncreasingTime();
71    static const double minimumProxyCheckDelay = 5;
72    if (time - m_lastProxyEnabledStatusCheckTime > minimumProxyCheckDelay) {
73        m_lastProxyEnabledStatusCheckTime = time;
74        m_cachedProxyEnabledStatus = platformProxyIsEnabledInSystemPreferences();
75    }
76    return m_cachedProxyEnabledStatus;
77}
78
79void DNSResolveQueue::add(const String& hostname)
80{
81    // If there are no names queued, and few enough are in flight, resolve immediately (the mouse may be over a link).
82    if (!m_names.size()) {
83        if (isUsingProxy())
84            return;
85        if (++m_requestsInFlight <= gNamesToResolveImmediately) {
86            platformResolve(hostname);
87            return;
88        }
89        --m_requestsInFlight;
90    }
91
92    // It's better to not prefetch some names than to clog the queue.
93    // Dropping the newest names, because on a single page, these are likely to be below oldest ones.
94    if (m_names.size() < gMaxRequestsToQueue) {
95        m_names.add(hostname);
96        if (!m_timer.isActive())
97            m_timer.startOneShot(gCoalesceDelayInSeconds);
98    }
99}
100
101void DNSResolveQueue::timerFired(Timer<DNSResolveQueue>&)
102{
103    if (isUsingProxy()) {
104        m_names.clear();
105        return;
106    }
107
108    int requestsAllowed = gMaxSimultaneousRequests - m_requestsInFlight;
109
110    for (; !m_names.isEmpty() && requestsAllowed > 0; --requestsAllowed) {
111        ++m_requestsInFlight;
112        HashSet<String>::iterator currentName = m_names.begin();
113        platformResolve(*currentName);
114        m_names.remove(currentName);
115    }
116
117    if (!m_names.isEmpty())
118        m_timer.startOneShot(gRetryResolvingInSeconds);
119}
120
121} // namespace WebCore
122