1/*
2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
5 * Copyright (C) 2008 Alp Toker <alp@atoker.com>
6 * Copyright (C) Research In Motion Limited 2009. All rights reserved.
7 * Copyright (C) 2011 Kris Jordan <krisjordan@gmail.com>
8 * Copyright (C) 2011 Google Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1.  Redistributions of source code must retain the above copyright
15 *     notice, this list of conditions and the following disclaimer.
16 * 2.  Redistributions in binary form must reproduce the above copyright
17 *     notice, this list of conditions and the following disclaimer in the
18 *     documentation and/or other materials provided with the distribution.
19 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
20 *     its contributors may be used to endorse or promote products derived
21 *     from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
27 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#include "config.h"
36#include "IconController.h"
37
38#include "Document.h"
39#include "DocumentLoader.h"
40#include "FrameLoader.h"
41#include "FrameLoaderClient.h"
42#include "IconDatabase.h"
43#include "IconDatabaseBase.h"
44#include "IconLoader.h"
45#include "IconURL.h"
46#include "Logging.h"
47#include "MainFrame.h"
48#include "Page.h"
49#include "Settings.h"
50#include <wtf/text/CString.h>
51
52namespace WebCore {
53
54IconController::IconController(Frame& frame)
55    : m_frame(frame)
56    , m_waitingForLoadDecision(false)
57{
58}
59
60IconController::~IconController()
61{
62}
63
64URL IconController::url()
65{
66    IconURLs iconURLs = urlsForTypes(Favicon);
67    return iconURLs.isEmpty() ? URL() : iconURLs[0].m_iconURL;
68}
69
70IconURL IconController::iconURL(IconType iconType) const
71{
72    IconURL result;
73    const Vector<IconURL>& iconURLs = m_frame.document()->iconURLs(iconType);
74    Vector<IconURL>::const_iterator iter(iconURLs.begin());
75    for (; iter != iconURLs.end(); ++iter) {
76        if (result.m_iconURL.isEmpty() || !iter->m_mimeType.isEmpty())
77            result = *iter;
78    }
79
80    return result;
81}
82
83IconURLs IconController::urlsForTypes(int iconTypesMask)
84{
85    IconURLs iconURLs;
86    if (m_frame.tree().parent())
87        return iconURLs;
88
89    if (iconTypesMask & Favicon && !appendToIconURLs(Favicon, &iconURLs))
90        iconURLs.append(defaultURL(Favicon));
91
92#if ENABLE(TOUCH_ICON_LOADING)
93    int missedIcons = 0;
94    if (iconTypesMask & TouchPrecomposedIcon)
95        missedIcons += appendToIconURLs(TouchPrecomposedIcon, &iconURLs) ? 0:1;
96
97    if (iconTypesMask & TouchIcon)
98      missedIcons += appendToIconURLs(TouchIcon, &iconURLs) ? 0:1;
99
100    // Only return the default touch icons when the both were required and neither was gotten.
101    if (missedIcons == 2) {
102        iconURLs.append(defaultURL(TouchPrecomposedIcon));
103        iconURLs.append(defaultURL(TouchIcon));
104    }
105#endif
106
107    // Finally, append all remaining icons of this type.
108    const Vector<IconURL>& allIconURLs = m_frame.document()->iconURLs(iconTypesMask);
109    for (Vector<IconURL>::const_iterator iter = allIconURLs.begin(); iter != allIconURLs.end(); ++iter) {
110        int i;
111        int iconCount = iconURLs.size();
112        for (i = 0; i < iconCount; ++i) {
113            if (*iter == iconURLs.at(i))
114                break;
115        }
116        if (i == iconCount)
117            iconURLs.append(*iter);
118    }
119
120    return iconURLs;
121}
122
123void IconController::commitToDatabase(const URL& icon)
124{
125    LOG(IconDatabase, "Committing iconURL %s to database for pageURLs %s and %s", icon.string().ascii().data(), m_frame.document()->url().string().ascii().data(), m_frame.loader().initialRequest().url().string().ascii().data());
126    iconDatabase().setIconURLForPageURL(icon.string(), m_frame.document()->url().string());
127    iconDatabase().setIconURLForPageURL(icon.string(), m_frame.loader().initialRequest().url().string());
128}
129
130void IconController::startLoader()
131{
132    // FIXME: We kick off the icon loader when the frame is done receiving its main resource.
133    // But we should instead do it when we're done parsing the head element.
134
135    if (!m_frame.isMainFrame())
136        return;
137
138    if (!iconDatabase().isEnabled())
139        return;
140
141    ASSERT(!m_frame.tree().parent());
142    if (!documentCanHaveIcon(m_frame.document()->url()))
143        return;
144
145    URL iconURL(url());
146    String urlString(iconURL.string());
147    if (urlString.isEmpty())
148        return;
149
150    // People who want to avoid loading images generally want to avoid loading all images, unless an exception has been made for site icons.
151    // Now that we've accounted for URL mapping, avoid starting the network load if images aren't set to display automatically.
152    if (!m_frame.settings().loadsImagesAutomatically() && !m_frame.settings().loadsSiteIconsIgnoringImageLoadingSetting())
153        return;
154
155    // If we're reloading the page, always start the icon load now.
156    // FIXME: How can this condition ever be true?
157    if (m_frame.loader().loadType() == FrameLoadType::Reload && m_frame.loader().loadType() == FrameLoadType::ReloadFromOrigin) {
158        continueLoadWithDecision(IconLoadYes);
159        return;
160    }
161
162    if (iconDatabase().supportsAsynchronousMode()) {
163        m_frame.loader().documentLoader()->getIconLoadDecisionForIconURL(urlString);
164        // Commit the icon url mapping to the database just in case we don't end up loading later.
165        commitToDatabase(iconURL);
166        return;
167    }
168
169    IconLoadDecision decision = iconDatabase().synchronousLoadDecisionForIconURL(urlString, m_frame.loader().documentLoader());
170
171    if (decision == IconLoadUnknown) {
172        // In this case, we may end up loading the icon later, but we still want to commit the icon url mapping to the database
173        // just in case we don't end up loading later - if we commit the mapping a second time after the load, that's no big deal
174        // We also tell the client to register for the notification that the icon is received now so it isn't missed in case the
175        // icon is later read in from disk
176        LOG(IconDatabase, "IconController %p might load icon %s later", this, urlString.ascii().data());
177        m_waitingForLoadDecision = true;
178        m_frame.loader().client().registerForIconNotification();
179        commitToDatabase(iconURL);
180        return;
181    }
182
183    continueLoadWithDecision(decision);
184}
185
186void IconController::stopLoader()
187{
188    if (m_iconLoader)
189        m_iconLoader->stopLoading();
190}
191
192// Callback for the old-style synchronous IconDatabase interface.
193void IconController::loadDecisionReceived(IconLoadDecision iconLoadDecision)
194{
195    if (!m_waitingForLoadDecision)
196        return;
197    LOG(IconDatabase, "IconController %p was told a load decision is available for its icon", this);
198    continueLoadWithDecision(iconLoadDecision);
199    m_waitingForLoadDecision = false;
200}
201
202void IconController::continueLoadWithDecision(IconLoadDecision iconLoadDecision)
203{
204    ASSERT(iconLoadDecision != IconLoadUnknown);
205
206    //  FIXME (<rdar://problem/9168605>) - We should support in-memory-only private browsing icons in asynchronous icon database mode.
207    if (iconDatabase().supportsAsynchronousMode() && m_frame.page()->usesEphemeralSession())
208        return;
209
210    if (iconLoadDecision == IconLoadNo) {
211        URL iconURL(url());
212        String urlString(iconURL.string());
213        if (urlString.isEmpty())
214            return;
215
216        LOG(IconDatabase, "IconController::startLoader() - Told not to load this icon, committing iconURL %s to database for pageURL mapping", urlString.ascii().data());
217        commitToDatabase(iconURL);
218
219        if (iconDatabase().supportsAsynchronousMode()) {
220            m_frame.loader().documentLoader()->getIconDataForIconURL(urlString);
221            return;
222        }
223
224        // We were told not to load this icon - that means this icon is already known by the database
225        // If the icon data hasn't been read in from disk yet, kick off the read of the icon from the database to make sure someone
226        // has done it. This is after registering for the notification so the WebView can call the appropriate delegate method.
227        // Otherwise if the icon data *is* available, notify the delegate
228        if (!iconDatabase().synchronousIconDataKnownForIconURL(urlString)) {
229            LOG(IconDatabase, "Told not to load icon %s but icon data is not yet available - registering for notification and requesting load from disk", urlString.ascii().data());
230            m_frame.loader().client().registerForIconNotification();
231            iconDatabase().synchronousIconForPageURL(m_frame.document()->url().string(), IntSize(0, 0));
232            iconDatabase().synchronousIconForPageURL(m_frame.loader().initialRequest().url().string(), IntSize(0, 0));
233        } else
234            m_frame.loader().client().dispatchDidReceiveIcon();
235
236        return;
237    }
238
239    if (!m_iconLoader)
240        m_iconLoader = std::make_unique<IconLoader>(m_frame);
241
242    m_iconLoader->startLoading();
243}
244
245bool IconController::appendToIconURLs(IconType iconType, IconURLs* iconURLs)
246{
247    IconURL faviconURL = iconURL(iconType);
248    if (faviconURL.m_iconURL.isEmpty())
249        return false;
250
251    iconURLs->append(faviconURL);
252    return true;
253}
254
255IconURL IconController::defaultURL(IconType iconType)
256{
257    // Don't return a favicon iconURL unless we're http or https
258    URL documentURL = m_frame.document()->url();
259    if (!documentURL.protocolIsInHTTPFamily())
260        return IconURL();
261
262    URL url;
263    bool couldSetProtocol = url.setProtocol(documentURL.protocol());
264    ASSERT_UNUSED(couldSetProtocol, couldSetProtocol);
265    url.setHost(documentURL.host());
266    if (documentURL.hasPort())
267        url.setPort(documentURL.port());
268
269    if (iconType == Favicon) {
270        url.setPath("/favicon.ico");
271        return IconURL::defaultIconURL(url, Favicon);
272    }
273#if ENABLE(TOUCH_ICON_LOADING)
274    if (iconType == TouchPrecomposedIcon) {
275        url.setPath("/apple-touch-icon-precomposed.png");
276        return IconURL::defaultIconURL(url, TouchPrecomposedIcon);
277    }
278    if (iconType == TouchIcon) {
279        url.setPath("/apple-touch-icon.png");
280        return IconURL::defaultIconURL(url, TouchIcon);
281    }
282#endif
283    return IconURL();
284}
285
286}
287