1/*
2 * Copyright (C) 2011, 2012 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#import "WebNotificationClient.h"
27
28#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
29#import "WebDelegateImplementationCaching.h"
30#import "WebNotificationInternal.h"
31#import "WebPreferencesPrivate.h"
32#import "WebSecurityOriginInternal.h"
33#import "WebUIDelegatePrivate.h"
34#import "WebViewInternal.h"
35#import <WebCore/BlockExceptions.h>
36#import <WebCore/Page.h>
37#import <WebCore/ScriptExecutionContext.h>
38#endif
39
40#if ENABLE(NOTIFICATIONS)
41#import <WebCore/NotificationPermissionCallback.h>
42#endif
43#if ENABLE(LEGACY_NOTIFICATIONS)
44#import <WebCore/VoidCallback.h>
45#endif
46
47using namespace WebCore;
48
49#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
50@interface WebNotificationPolicyListener : NSObject <WebAllowDenyPolicyListener>
51{
52#if ENABLE(NOTIFICATIONS)
53    RefPtr<NotificationPermissionCallback> _callback;
54#endif
55#if ENABLE(LEGACY_NOTIFICATIONS)
56    RefPtr<VoidCallback> _voidCallback;
57    bool _isLegacyRequest;
58#endif
59}
60#if ENABLE(NOTIFICATIONS)
61- (id)initWithCallback:(PassRefPtr<NotificationPermissionCallback>)callback;
62#endif
63#if ENABLE(LEGACY_NOTIFICATIONS)
64- (id)initWithVoidCallback:(PassRefPtr<VoidCallback>)callback;
65#endif
66
67@end
68#endif
69
70#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
71static uint64_t generateNotificationID()
72{
73    static uint64_t uniqueNotificationID = 1;
74    return uniqueNotificationID++;
75}
76#endif
77
78WebNotificationClient::WebNotificationClient(WebView *webView)
79    : m_webView(webView)
80{
81}
82
83bool WebNotificationClient::show(Notification* notification)
84{
85#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
86    if (![m_webView _notificationProvider])
87        return false;
88
89    uint64_t notificationID = generateNotificationID();
90    RetainPtr<WebNotification> webNotification = adoptNS([[WebNotification alloc] initWithCoreNotification:notification notificationID:notificationID]);
91    m_notificationMap.set(notification, webNotification);
92
93    NotificationContextMap::iterator it = m_notificationContextMap.add(notification->scriptExecutionContext(), Vector<RetainPtr<WebNotification> >()).iterator;
94    it->value.append(webNotification);
95
96    [[m_webView _notificationProvider] showNotification:webNotification.get() fromWebView:m_webView];
97    return true;
98#else
99    UNUSED_PARAM(notification);
100    return false;
101#endif
102}
103
104void WebNotificationClient::cancel(Notification* notification)
105{
106#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
107    WebNotification *webNotification = m_notificationMap.get(notification).get();
108    if (!webNotification)
109        return;
110
111    [[m_webView _notificationProvider] cancelNotification:webNotification];
112#else
113    UNUSED_PARAM(notification);
114#endif
115}
116
117void WebNotificationClient::clearNotifications(ScriptExecutionContext* context)
118{
119#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
120    NotificationContextMap::iterator it = m_notificationContextMap.find(context);
121    if (it == m_notificationContextMap.end())
122        return;
123
124    Vector<RetainPtr<WebNotification> >& webNotifications = it->value;
125    NSMutableArray *nsIDs = [NSMutableArray array];
126    size_t count = webNotifications.size();
127    for (size_t i = 0; i < count; ++i) {
128        WebNotification *webNotification = webNotifications[i].get();
129        [nsIDs addObject:[NSNumber numberWithUnsignedLongLong:[webNotification notificationID]]];
130        core(webNotification)->finalize();
131        m_notificationMap.remove(core(webNotification));
132    }
133
134    [[m_webView _notificationProvider] clearNotifications:nsIDs];
135    m_notificationContextMap.remove(it);
136#else
137    UNUSED_PARAM(context);
138#endif
139}
140
141void WebNotificationClient::notificationObjectDestroyed(Notification* notification)
142{
143#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
144    RetainPtr<WebNotification> webNotification = m_notificationMap.take(notification);
145    if (!webNotification)
146        return;
147
148    NotificationContextMap::iterator it = m_notificationContextMap.find(notification->scriptExecutionContext());
149    ASSERT(it != m_notificationContextMap.end());
150    size_t index = it->value.find(webNotification);
151    ASSERT(index != notFound);
152    it->value.remove(index);
153    if (it->value.isEmpty())
154        m_notificationContextMap.remove(it);
155
156    [[m_webView _notificationProvider] notificationDestroyed:webNotification.get()];
157#else
158    UNUSED_PARAM(notification);
159#endif
160}
161
162void WebNotificationClient::notificationControllerDestroyed()
163{
164    delete this;
165}
166
167#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
168void WebNotificationClient::requestPermission(ScriptExecutionContext* context, WebNotificationPolicyListener *listener)
169{
170    SEL selector = @selector(webView:decidePolicyForNotificationRequestFromOrigin:listener:);
171    if (![[m_webView UIDelegate] respondsToSelector:selector])
172        return;
173
174    WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:context->securityOrigin()];
175
176    CallUIDelegate(m_webView, selector, webOrigin, listener);
177
178    [webOrigin release];
179}
180#endif
181
182#if ENABLE(LEGACY_NOTIFICATIONS)
183void WebNotificationClient::requestPermission(ScriptExecutionContext* context, PassRefPtr<VoidCallback> callback)
184{
185    BEGIN_BLOCK_OBJC_EXCEPTIONS;
186    WebNotificationPolicyListener *listener = [[WebNotificationPolicyListener alloc] initWithVoidCallback:callback];
187    requestPermission(context, listener);
188    [listener release];
189    END_BLOCK_OBJC_EXCEPTIONS;
190}
191#endif
192
193#if ENABLE(NOTIFICATIONS)
194void WebNotificationClient::requestPermission(ScriptExecutionContext* context, PassRefPtr<NotificationPermissionCallback> callback)
195{
196    BEGIN_BLOCK_OBJC_EXCEPTIONS;
197    WebNotificationPolicyListener *listener = [[WebNotificationPolicyListener alloc] initWithCallback:callback];
198    requestPermission(context, listener);
199    [listener release];
200    END_BLOCK_OBJC_EXCEPTIONS;
201}
202#endif
203
204NotificationClient::Permission WebNotificationClient::checkPermission(ScriptExecutionContext* context)
205{
206#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
207    if (!context || !context->isDocument())
208        return NotificationClient::PermissionDenied;
209    if (![[m_webView preferences] notificationsEnabled])
210        return NotificationClient::PermissionDenied;
211    WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:context->securityOrigin()];
212    WebNotificationPermission permission = [[m_webView _notificationProvider] policyForOrigin:webOrigin];
213    [webOrigin release];
214    switch (permission) {
215        case WebNotificationPermissionAllowed:
216            return NotificationClient::PermissionAllowed;
217        case WebNotificationPermissionDenied:
218            return NotificationClient::PermissionDenied;
219        case WebNotificationPermissionNotAllowed:
220            return NotificationClient::PermissionNotAllowed;
221        default:
222            return NotificationClient::PermissionNotAllowed;
223    }
224#else
225    UNUSED_PARAM(context);
226    return NotificationClient::PermissionDenied;
227#endif
228}
229
230#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
231uint64_t WebNotificationClient::notificationIDForTesting(WebCore::Notification* notification)
232{
233    return [m_notificationMap.get(notification).get() notificationID];
234}
235
236@implementation WebNotificationPolicyListener
237
238#if ENABLE(NOTIFICATIONS)
239- (id)initWithCallback:(PassRefPtr<NotificationPermissionCallback>)callback
240{
241    if (!(self = [super init]))
242        return nil;
243
244    _callback = callback;
245    return self;
246}
247#endif
248
249#if ENABLE(LEGACY_NOTIFICATIONS)
250- (id)initWithVoidCallback:(PassRefPtr<VoidCallback>)callback
251{
252    if (!(self = [super init]))
253        return nil;
254
255    _isLegacyRequest = true;
256    _voidCallback = callback;
257    return self;
258}
259#endif
260
261- (void)allow
262{
263#if ENABLE(LEGACY_NOTIFICATIONS)
264    if (_isLegacyRequest) {
265        if (_voidCallback)
266            _voidCallback->handleEvent();
267        return;
268    }
269#endif
270#if ENABLE(NOTIFICATIONS)
271    if (_callback)
272        _callback->handleEvent(Notification::permissionString(NotificationClient::PermissionAllowed));
273#endif
274}
275
276- (void)deny
277{
278#if ENABLE(LEGACY_NOTIFICATIONS)
279    if (_isLegacyRequest) {
280        if (_voidCallback)
281            _voidCallback->handleEvent();
282        return;
283    }
284#endif
285#if ENABLE(NOTIFICATIONS)
286    if (_callback)
287        _callback->handleEvent(Notification::permissionString(NotificationClient::PermissionDenied));
288#endif
289}
290
291@end
292#endif
293