1 /*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "config.h"
33 #include "NotificationPresenterClientQt.h"
34
35 #include "Document.h"
36 #include "DumpRenderTreeSupportQt.h"
37 #include "EventNames.h"
38 #include "KURL.h"
39 #include "Page.h"
40 #include "QtPlatformPlugin.h"
41 #include "ScriptExecutionContext.h"
42 #include "SecurityOrigin.h"
43 #include "UserGestureIndicator.h"
44
45 #include "qwebframe_p.h"
46 #include "qwebkitglobal.h"
47 #include "qwebpage.h"
48
49 namespace WebCore {
50
51 #if ENABLE(NOTIFICATIONS)
52
53 const double notificationTimeout = 10.0;
54
55 bool NotificationPresenterClientQt::dumpNotification = false;
56
57 NotificationPresenterClientQt* s_notificationPresenter = 0;
58
notificationPresenter()59 NotificationPresenterClientQt* NotificationPresenterClientQt::notificationPresenter()
60 {
61 if (s_notificationPresenter)
62 return s_notificationPresenter;
63
64 s_notificationPresenter = new NotificationPresenterClientQt();
65 return s_notificationPresenter;
66 }
67
68 #endif
69
NotificationWrapper()70 NotificationWrapper::NotificationWrapper()
71 : m_closeTimer(this, &NotificationWrapper::close)
72 {
73 #if ENABLE(NOTIFICATIONS)
74
75 #ifndef QT_NO_SYSTEMTRAYICON
76 m_notificationIcon = 0;
77 #endif
78 m_presenter = 0;
79 #endif
80 }
81
close(Timer<NotificationWrapper> *)82 void NotificationWrapper::close(Timer<NotificationWrapper>*)
83 {
84 #if ENABLE(NOTIFICATIONS)
85 NotificationPresenterClientQt::notificationPresenter()->cancel(this);
86 #endif
87 }
88
title() const89 const QString NotificationWrapper::title() const
90 {
91 #if ENABLE(NOTIFICATIONS)
92 Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
93 if (notification)
94 return notification->contents().title();
95 #endif
96 return QString();
97 }
98
message() const99 const QString NotificationWrapper::message() const
100 {
101 #if ENABLE(NOTIFICATIONS)
102 Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
103 if (notification)
104 return notification->contents().body();
105 #endif
106 return QString();
107 }
108
iconData() const109 const QByteArray NotificationWrapper::iconData() const
110 {
111 QByteArray iconData;
112 #if ENABLE(NOTIFICATIONS)
113 Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
114 if (notification) {
115 if (notification->iconData())
116 iconData = QByteArray::fromRawData(notification->iconData()->data(), notification->iconData()->size());
117 }
118 #endif
119 return iconData;
120 }
121
openerPageUrl() const122 const QUrl NotificationWrapper::openerPageUrl() const
123 {
124 QUrl url;
125 #if ENABLE(NOTIFICATIONS)
126 Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
127 if (notification) {
128 if (notification->scriptExecutionContext())
129 url = static_cast<Document*>(notification->scriptExecutionContext())->page()->mainFrame()->document()->url();
130 }
131 #endif
132 return url;
133 }
134
notificationClicked()135 void NotificationWrapper::notificationClicked()
136 {
137 #if ENABLE(NOTIFICATIONS)
138 NotificationPresenterClientQt::notificationPresenter()->notificationClicked(this);
139 #endif
140 }
141
notificationClosed()142 void NotificationWrapper::notificationClosed()
143 {
144 #if ENABLE(NOTIFICATIONS)
145 NotificationPresenterClientQt::notificationPresenter()->cancel(this);
146 #endif
147 }
148
149 #if ENABLE(NOTIFICATIONS)
150
NotificationPresenterClientQt()151 NotificationPresenterClientQt::NotificationPresenterClientQt() : m_clientCount(0)
152 {
153 }
154
~NotificationPresenterClientQt()155 NotificationPresenterClientQt::~NotificationPresenterClientQt()
156 {
157 while (!m_notifications.isEmpty()) {
158 NotificationsQueue::Iterator iter = m_notifications.begin();
159 detachNotification(iter.key());
160 }
161 }
162
removeClient()163 void NotificationPresenterClientQt::removeClient()
164 {
165 m_clientCount--;
166 if (!m_clientCount) {
167 s_notificationPresenter = 0;
168 delete this;
169 }
170 }
171
show(Notification * notification)172 bool NotificationPresenterClientQt::show(Notification* notification)
173 {
174 // FIXME: workers based notifications are not supported yet.
175 if (notification->scriptExecutionContext()->isWorkerContext())
176 return false;
177 notification->setPendingActivity(notification);
178 if (!notification->replaceId().isEmpty())
179 removeReplacedNotificationFromQueue(notification);
180 if (dumpNotification)
181 dumpShowText(notification);
182 QByteArray iconData;
183 if (notification->iconData())
184 iconData = QByteArray::fromRawData(notification->iconData()->data(), notification->iconData()->size());
185 displayNotification(notification, iconData);
186 notification->releaseIconData();
187 return true;
188 }
189
displayNotification(Notification * notification,const QByteArray & bytes)190 void NotificationPresenterClientQt::displayNotification(Notification* notification, const QByteArray& bytes)
191 {
192 NotificationWrapper* wrapper = new NotificationWrapper();
193 m_notifications.insert(notification, wrapper);
194 QString title;
195 QString message;
196 // FIXME: download & display HTML notifications
197 if (notification->isHTML())
198 message = notification->url().string();
199 else {
200 title = notification->contents().title();
201 message = notification->contents().body();
202 }
203
204 if (m_platformPlugin.plugin() && m_platformPlugin.plugin()->supportsExtension(QWebKitPlatformPlugin::Notifications))
205 wrapper->m_presenter = m_platformPlugin.createNotificationPresenter();
206
207 if (!wrapper->m_presenter) {
208 #ifndef QT_NO_SYSTEMTRAYICON
209 if (!dumpNotification)
210 wrapper->m_closeTimer.startOneShot(notificationTimeout);
211 QPixmap pixmap;
212 if (bytes.length() && pixmap.loadFromData(bytes)) {
213 QIcon icon(pixmap);
214 wrapper->m_notificationIcon = new QSystemTrayIcon(icon);
215 } else
216 wrapper->m_notificationIcon = new QSystemTrayIcon();
217 #endif
218 }
219
220 sendEvent(notification, "display");
221
222 // Make sure the notification was not cancelled during handling the display event
223 if (m_notifications.find(notification) == m_notifications.end())
224 return;
225
226 if (wrapper->m_presenter) {
227 wrapper->connect(wrapper->m_presenter.get(), SIGNAL(notificationClosed()), wrapper, SLOT(notificationClosed()), Qt::QueuedConnection);
228 wrapper->connect(wrapper->m_presenter.get(), SIGNAL(notificationClicked()), wrapper, SLOT(notificationClicked()));
229 wrapper->m_presenter->showNotification(wrapper);
230 return;
231 }
232
233 #ifndef QT_NO_SYSTEMTRAYICON
234 wrapper->connect(wrapper->m_notificationIcon.get(), SIGNAL(messageClicked()), wrapper, SLOT(notificationClicked()));
235 wrapper->m_notificationIcon->show();
236 wrapper->m_notificationIcon->showMessage(notification->contents().title(), notification->contents().body());
237 #endif
238 }
239
cancel(Notification * notification)240 void NotificationPresenterClientQt::cancel(Notification* notification)
241 {
242 if (dumpNotification && notification->scriptExecutionContext()) {
243 if (notification->isHTML())
244 printf("DESKTOP NOTIFICATION CLOSED: %s\n", QString(notification->url().string()).toUtf8().constData());
245 else
246 printf("DESKTOP NOTIFICATION CLOSED: %s\n", QString(notification->contents().title()).toUtf8().constData());
247 }
248
249 NotificationsQueue::Iterator iter = m_notifications.find(notification);
250 if (iter != m_notifications.end()) {
251 sendEvent(notification, eventNames().closeEvent);
252 detachNotification(notification);
253 }
254 }
255
cancel(NotificationWrapper * wrapper)256 void NotificationPresenterClientQt::cancel(NotificationWrapper* wrapper)
257 {
258 Notification* notification = notificationForWrapper(wrapper);
259 if (notification)
260 cancel(notification);
261 }
262
notificationClicked(NotificationWrapper * wrapper)263 void NotificationPresenterClientQt::notificationClicked(NotificationWrapper* wrapper)
264 {
265 Notification* notification = notificationForWrapper(wrapper);
266 if (notification) {
267 // Make sure clicks on notifications are treated as user gestures.
268 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
269 sendEvent(notification, eventNames().clickEvent);
270 }
271 }
272
notificationClicked(const QString & title)273 void NotificationPresenterClientQt::notificationClicked(const QString& title)
274 {
275 if (!dumpNotification)
276 return;
277 NotificationsQueue::ConstIterator end = m_notifications.end();
278 NotificationsQueue::ConstIterator iter = m_notifications.begin();
279 Notification* notification = 0;
280 while (iter != end) {
281 notification = iter.key();
282 QString notificationTitle;
283 if (notification->isHTML())
284 notificationTitle = notification->url().string();
285 else
286 notificationTitle = notification->contents().title();
287 if (notificationTitle == title)
288 break;
289 iter++;
290 }
291 if (notification)
292 sendEvent(notification, eventNames().clickEvent);
293 }
294
notificationForWrapper(const NotificationWrapper * wrapper) const295 Notification* NotificationPresenterClientQt::notificationForWrapper(const NotificationWrapper* wrapper) const
296 {
297 NotificationsQueue::ConstIterator end = m_notifications.end();
298 NotificationsQueue::ConstIterator iter = m_notifications.begin();
299 while (iter != end && iter.value() != wrapper)
300 iter++;
301 if (iter != end)
302 return iter.key();
303 return 0;
304 }
305
notificationObjectDestroyed(Notification * notification)306 void NotificationPresenterClientQt::notificationObjectDestroyed(Notification* notification)
307 {
308 // Called from ~Notification(), Remove the entry from the notifications list and delete the icon.
309 NotificationsQueue::Iterator iter = m_notifications.find(notification);
310 if (iter != m_notifications.end())
311 delete m_notifications.take(notification);
312 }
313
requestPermission(ScriptExecutionContext * context,PassRefPtr<VoidCallback> callback)314 void NotificationPresenterClientQt::requestPermission(ScriptExecutionContext* context, PassRefPtr<VoidCallback> callback)
315 {
316 if (dumpNotification)
317 printf("DESKTOP NOTIFICATION PERMISSION REQUESTED: %s\n", QString(context->securityOrigin()->toString()).toUtf8().constData());
318
319 QHash<ScriptExecutionContext*, CallbacksInfo >::iterator iter = m_pendingPermissionRequests.find(context);
320 if (iter != m_pendingPermissionRequests.end())
321 iter.value().m_callbacks.append(callback);
322 else {
323 RefPtr<VoidCallback> cb = callback;
324 CallbacksInfo info;
325 info.m_frame = toFrame(context);
326 info.m_callbacks.append(cb);
327 m_pendingPermissionRequests.insert(context, info);
328
329 if (toPage(context) && toFrame(context)) {
330 m_pendingPermissionRequests.insert(context, info);
331 emit toPage(context)->featurePermissionRequested(toFrame(context), QWebPage::Notifications);
332 }
333 }
334 }
335
checkPermission(ScriptExecutionContext * context)336 NotificationPresenter::Permission NotificationPresenterClientQt::checkPermission(ScriptExecutionContext* context)
337 {
338 return m_cachedPermissions.value(context, NotificationPresenter::PermissionNotAllowed);
339 }
340
cancelRequestsForPermission(ScriptExecutionContext * context)341 void NotificationPresenterClientQt::cancelRequestsForPermission(ScriptExecutionContext* context)
342 {
343 m_cachedPermissions.remove(context);
344
345 QHash<ScriptExecutionContext*, CallbacksInfo >::iterator iter = m_pendingPermissionRequests.find(context);
346 if (iter == m_pendingPermissionRequests.end())
347 return;
348
349 QWebFrame* frame = iter.value().m_frame;
350 if (!frame)
351 return;
352 QWebPage* page = frame->page();
353 m_pendingPermissionRequests.erase(iter);
354
355 if (!page)
356 return;
357
358 if (dumpNotification)
359 printf("DESKTOP NOTIFICATION PERMISSION REQUEST CANCELLED: %s\n", QString(context->securityOrigin()->toString()).toUtf8().constData());
360
361 emit page->featurePermissionRequestCanceled(frame, QWebPage::Notifications);
362 }
363
allowNotificationForFrame(Frame * frame)364 void NotificationPresenterClientQt::allowNotificationForFrame(Frame* frame)
365 {
366 m_cachedPermissions.insert(frame->document(), NotificationPresenter::PermissionAllowed);
367
368 QHash<ScriptExecutionContext*, CallbacksInfo>::iterator iter = m_pendingPermissionRequests.begin();
369 while (iter != m_pendingPermissionRequests.end()) {
370 if (iter.key() == frame->document())
371 break;
372 }
373
374 if (iter == m_pendingPermissionRequests.end())
375 return;
376
377 QList<RefPtr<VoidCallback> >& callbacks = iter.value().m_callbacks;
378 for (int i = 0; i < callbacks.size(); i++)
379 callbacks.at(i)->handleEvent();
380 m_pendingPermissionRequests.remove(iter.key());
381 }
382
sendEvent(Notification * notification,const AtomicString & eventName)383 void NotificationPresenterClientQt::sendEvent(Notification* notification, const AtomicString& eventName)
384 {
385 if (notification->scriptExecutionContext())
386 notification->dispatchEvent(Event::create(eventName, false, true));
387 }
388
removeReplacedNotificationFromQueue(Notification * notification)389 void NotificationPresenterClientQt::removeReplacedNotificationFromQueue(Notification* notification)
390 {
391 Notification* oldNotification = 0;
392 NotificationsQueue::Iterator end = m_notifications.end();
393 NotificationsQueue::Iterator iter = m_notifications.begin();
394
395 while (iter != end) {
396 Notification* existingNotification = iter.key();
397 if (existingNotification->replaceId() == notification->replaceId() && existingNotification->url().protocol() == notification->url().protocol() && existingNotification->url().host() == notification->url().host()) {
398 oldNotification = iter.key();
399 break;
400 }
401 iter++;
402 }
403
404 if (oldNotification) {
405 if (dumpNotification)
406 dumpReplacedIdText(oldNotification);
407 sendEvent(oldNotification, eventNames().closeEvent);
408 detachNotification(oldNotification);
409 }
410 }
411
detachNotification(Notification * notification)412 void NotificationPresenterClientQt::detachNotification(Notification* notification)
413 {
414 delete m_notifications.take(notification);
415 notification->detachPresenter();
416 notification->unsetPendingActivity(notification);
417 }
418
dumpReplacedIdText(Notification * notification)419 void NotificationPresenterClientQt::dumpReplacedIdText(Notification* notification)
420 {
421 if (notification)
422 printf("REPLACING NOTIFICATION %s\n", notification->isHTML() ? QString(notification->url().string()).toUtf8().constData() : QString(notification->contents().title()).toUtf8().constData());
423 }
424
dumpShowText(Notification * notification)425 void NotificationPresenterClientQt::dumpShowText(Notification* notification)
426 {
427 if (notification->isHTML())
428 printf("DESKTOP NOTIFICATION: contents at %s\n", QString(notification->url().string()).toUtf8().constData());
429 else {
430 printf("DESKTOP NOTIFICATION:%s icon %s, title %s, text %s\n",
431 notification->dir() == "rtl" ? "(RTL)" : "",
432 QString(notification->contents().icon().string()).toUtf8().constData(), QString(notification->contents().title()).toUtf8().constData(),
433 QString(notification->contents().body()).toUtf8().constData());
434 }
435 }
436
toPage(ScriptExecutionContext * context)437 QWebPage* NotificationPresenterClientQt::toPage(ScriptExecutionContext* context)
438 {
439 if (!context || context->isWorkerContext())
440 return 0;
441
442 Document* document = static_cast<Document*>(context);
443
444 Page* page = document->page();
445 if (!page || !page->mainFrame())
446 return 0;
447
448 return QWebFramePrivate::kit(page->mainFrame())->page();
449 }
450
toFrame(ScriptExecutionContext * context)451 QWebFrame* NotificationPresenterClientQt::toFrame(ScriptExecutionContext* context)
452 {
453 if (!context || context->isWorkerContext())
454 return 0;
455
456 Document* document = static_cast<Document*>(context);
457 if (!document || !document->frame())
458 return 0;
459
460 return QWebFramePrivate::kit(document->frame());
461 }
462
463 #endif // ENABLE(NOTIFICATIONS)
464 }
465
466 #include "moc_NotificationPresenterClientQt.cpp"
467