1/*
2 * Copyright (C) 2011, 2012, 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "WebNotificationManager.h"
28
29#include "WebPage.h"
30#include "WebProcess.h"
31#include "WebProcessCreationParameters.h"
32
33#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
34#include "WebNotification.h"
35#include "WebNotificationManagerMessages.h"
36#include "WebPageProxyMessages.h"
37#include <WebCore/Document.h>
38#include <WebCore/Notification.h>
39#include <WebCore/Page.h>
40#include <WebCore/ScriptExecutionContext.h>
41#include <WebCore/SecurityOrigin.h>
42#include <WebCore/Settings.h>
43#include <WebCore/UserGestureIndicator.h>
44#endif
45
46using namespace WebCore;
47
48namespace WebKit {
49
50#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
51static uint64_t generateNotificationID()
52{
53    static uint64_t uniqueNotificationID = 1;
54    return uniqueNotificationID++;
55}
56#endif
57
58const char* WebNotificationManager::supplementName()
59{
60    return "WebNotificationManager";
61}
62
63WebNotificationManager::WebNotificationManager(WebProcess* process)
64    : m_process(process)
65{
66#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
67    m_process->addMessageReceiver(Messages::WebNotificationManager::messageReceiverName(), this);
68#endif
69}
70
71WebNotificationManager::~WebNotificationManager()
72{
73}
74
75void WebNotificationManager::initialize(const WebProcessCreationParameters& parameters)
76{
77#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
78    m_permissionsMap = parameters.notificationPermissions;
79#else
80    UNUSED_PARAM(parameters);
81#endif
82}
83
84void WebNotificationManager::didUpdateNotificationDecision(const String& originString, bool allowed)
85{
86#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
87    m_permissionsMap.set(originString, allowed);
88#else
89    UNUSED_PARAM(originString);
90    UNUSED_PARAM(allowed);
91#endif
92}
93
94void WebNotificationManager::didRemoveNotificationDecisions(const Vector<String>& originStrings)
95{
96#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
97    size_t count = originStrings.size();
98    for (size_t i = 0; i < count; ++i)
99        m_permissionsMap.remove(originStrings[i]);
100#else
101    UNUSED_PARAM(originStrings);
102#endif
103}
104
105NotificationClient::Permission WebNotificationManager::policyForOrigin(WebCore::SecurityOrigin *origin) const
106{
107#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
108    if (!origin)
109        return NotificationClient::PermissionNotAllowed;
110
111    ASSERT(!origin->isUnique());
112    HashMap<String, bool>::const_iterator it = m_permissionsMap.find(origin->toRawString());
113    if (it != m_permissionsMap.end())
114        return it->value ? NotificationClient::PermissionAllowed : NotificationClient::PermissionDenied;
115#else
116    UNUSED_PARAM(origin);
117#endif
118
119    return NotificationClient::PermissionNotAllowed;
120}
121
122void WebNotificationManager::removeAllPermissionsForTesting()
123{
124#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
125    m_permissionsMap.clear();
126#endif
127}
128
129uint64_t WebNotificationManager::notificationIDForTesting(Notification* notification)
130{
131#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
132    if (!notification)
133        return 0;
134    return m_notificationMap.get(notification);
135#else
136    UNUSED_PARAM(notification);
137    return 0;
138#endif
139}
140
141bool WebNotificationManager::show(Notification* notification, WebPage* page)
142{
143#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
144    if (!notification || !page->corePage()->settings()->notificationsEnabled())
145        return false;
146
147    uint64_t notificationID = generateNotificationID();
148    m_notificationMap.set(notification, notificationID);
149    m_notificationIDMap.set(notificationID, notification);
150
151    NotificationContextMap::iterator it = m_notificationContextMap.add(notification->scriptExecutionContext(), Vector<uint64_t>()).iterator;
152    it->value.append(notificationID);
153
154#if ENABLE(NOTIFICATIONS)
155    m_process->parentProcessConnection()->send(Messages::WebPageProxy::ShowNotification(notification->title(), notification->body(), notification->iconURL().string(), notification->tag(), notification->lang(), notification->dir(), notification->scriptExecutionContext()->securityOrigin()->toString(), notificationID), page->pageID());
156#else
157    m_process->parentProcessConnection()->send(Messages::WebPageProxy::ShowNotification(notification->title(), notification->body(), notification->iconURL().string(), notification->replaceId(), notification->lang(), notification->dir(), notification->scriptExecutionContext()->securityOrigin()->toString(), notificationID), page->pageID());
158#endif
159    return true;
160#else
161    UNUSED_PARAM(notification);
162    UNUSED_PARAM(page);
163    return false;
164#endif
165}
166
167void WebNotificationManager::cancel(Notification* notification, WebPage* page)
168{
169#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
170    if (!notification || !page->corePage()->settings()->notificationsEnabled())
171        return;
172
173    uint64_t notificationID = m_notificationMap.get(notification);
174    if (!notificationID)
175        return;
176
177    m_process->parentProcessConnection()->send(Messages::WebPageProxy::CancelNotification(notificationID), page->pageID());
178#else
179    UNUSED_PARAM(notification);
180    UNUSED_PARAM(page);
181#endif
182}
183
184void WebNotificationManager::clearNotifications(WebCore::ScriptExecutionContext* context, WebPage* page)
185{
186#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
187    NotificationContextMap::iterator it = m_notificationContextMap.find(context);
188    if (it == m_notificationContextMap.end())
189        return;
190
191    Vector<uint64_t>& notificationIDs = it->value;
192    m_process->parentProcessConnection()->send(Messages::WebPageProxy::ClearNotifications(notificationIDs), page->pageID());
193    size_t count = notificationIDs.size();
194    for (size_t i = 0; i < count; ++i) {
195        RefPtr<Notification> notification = m_notificationIDMap.take(notificationIDs[i]);
196        if (!notification)
197            continue;
198        notification->finalize();
199        m_notificationMap.remove(notification);
200    }
201
202    m_notificationContextMap.remove(it);
203#else
204    UNUSED_PARAM(context);
205    UNUSED_PARAM(page);
206#endif
207}
208
209void WebNotificationManager::didDestroyNotification(Notification* notification, WebPage* page)
210{
211#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
212    uint64_t notificationID = m_notificationMap.take(notification);
213    if (!notificationID)
214        return;
215
216    m_notificationIDMap.remove(notificationID);
217    removeNotificationFromContextMap(notificationID, notification);
218    m_process->parentProcessConnection()->send(Messages::WebPageProxy::DidDestroyNotification(notificationID), page->pageID());
219#else
220    UNUSED_PARAM(notification);
221    UNUSED_PARAM(page);
222#endif
223}
224
225void WebNotificationManager::didShowNotification(uint64_t notificationID)
226{
227#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
228    if (!isNotificationIDValid(notificationID))
229        return;
230
231    RefPtr<Notification> notification = m_notificationIDMap.get(notificationID);
232    if (!notification)
233        return;
234
235    notification->dispatchShowEvent();
236#else
237    UNUSED_PARAM(notificationID);
238#endif
239}
240
241void WebNotificationManager::didClickNotification(uint64_t notificationID)
242{
243#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
244    if (!isNotificationIDValid(notificationID))
245        return;
246
247    RefPtr<Notification> notification = m_notificationIDMap.get(notificationID);
248    if (!notification)
249        return;
250
251    // Indicate that this event is being dispatched in reaction to a user's interaction with a platform notification.
252    UserGestureIndicator indicator(DefinitelyProcessingNewUserGesture);
253    notification->dispatchClickEvent();
254#else
255    UNUSED_PARAM(notificationID);
256#endif
257}
258
259void WebNotificationManager::didCloseNotifications(const Vector<uint64_t>& notificationIDs)
260{
261#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
262    size_t count = notificationIDs.size();
263    for (size_t i = 0; i < count; ++i) {
264        uint64_t notificationID = notificationIDs[i];
265        if (!isNotificationIDValid(notificationID))
266            continue;
267
268        RefPtr<Notification> notification = m_notificationIDMap.take(notificationID);
269        if (!notification)
270            continue;
271
272        m_notificationMap.remove(notification);
273        removeNotificationFromContextMap(notificationID, notification.get());
274
275        notification->dispatchCloseEvent();
276    }
277#else
278    UNUSED_PARAM(notificationIDs);
279#endif
280}
281
282#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
283void WebNotificationManager::removeNotificationFromContextMap(uint64_t notificationID, Notification* notification)
284{
285    // This is a helper function for managing the hash maps.
286    NotificationContextMap::iterator it = m_notificationContextMap.find(notification->scriptExecutionContext());
287    ASSERT(it != m_notificationContextMap.end());
288    size_t index = it->value.find(notificationID);
289    ASSERT(index != notFound);
290    it->value.remove(index);
291    if (it->value.isEmpty())
292        m_notificationContextMap.remove(it);
293}
294#endif
295
296} // namespace WebKit
297