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