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 "Event.h"
37#include "EventNames.h"
38#include "KURL.h"
39#include "Page.h"
40#include "QWebFrameAdapter.h"
41#include "QWebPageAdapter.h"
42#include "QtPlatformPlugin.h"
43#include "ScriptExecutionContext.h"
44#include "SecurityOrigin.h"
45#include "UserGestureIndicator.h"
46#include "qwebkitglobal.h"
47
48namespace WebCore {
49
50#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
51
52const double notificationTimeout = 10.0;
53
54bool NotificationPresenterClientQt::dumpNotification = false;
55
56NotificationPresenterClientQt* s_notificationPresenter = 0;
57
58NotificationPresenterClientQt* NotificationPresenterClientQt::notificationPresenter()
59{
60    if (s_notificationPresenter)
61        return s_notificationPresenter;
62
63    s_notificationPresenter = new NotificationPresenterClientQt();
64    return s_notificationPresenter;
65}
66
67#endif
68
69NotificationWrapper::NotificationWrapper()
70    : m_closeTimer(this, &NotificationWrapper::close)
71    , m_displayEventTimer(this, &NotificationWrapper::sendDisplayEvent)
72{
73#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
74    m_presenter = nullptr;
75#endif
76}
77
78void NotificationWrapper::close(Timer<NotificationWrapper>*)
79{
80#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
81    NotificationPresenterClientQt::notificationPresenter()->cancel(this);
82#endif
83}
84
85void NotificationWrapper::sendDisplayEvent(Timer<NotificationWrapper>*)
86{
87#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
88    NotificationPresenterClientQt::notificationPresenter()->sendDisplayEvent(this);
89#endif
90}
91
92const QString NotificationWrapper::title() const
93{
94#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
95    Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
96    if (notification)
97        return notification->title();
98#endif
99    return QString();
100}
101
102const QString NotificationWrapper::message() const
103{
104#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
105    Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
106    if (notification)
107        return notification->body();
108#endif
109    return QString();
110}
111
112const QUrl NotificationWrapper::iconUrl() const
113{
114#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
115    Notification* notification = NotificationPresenterClientQt::notificationPresenter()->notificationForWrapper(this);
116    if (notification)
117        return notification->iconURL();
118#endif
119    return QUrl();
120}
121
122const QUrl NotificationWrapper::openerPageUrl() const
123{
124    QUrl url;
125#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_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
135void NotificationWrapper::notificationClicked()
136{
137#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
138    NotificationPresenterClientQt::notificationPresenter()->notificationClicked(this);
139#endif
140}
141
142void NotificationWrapper::notificationClosed()
143{
144#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
145    NotificationPresenterClientQt::notificationPresenter()->cancel(this);
146#endif
147}
148
149#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
150
151NotificationPresenterClientQt::NotificationPresenterClientQt() : m_clientCount(0)
152{
153}
154
155NotificationPresenterClientQt::~NotificationPresenterClientQt()
156{
157    while (!m_notifications.isEmpty()) {
158        NotificationsQueue::Iterator iter = m_notifications.begin();
159        detachNotification(iter.key());
160    }
161}
162
163void NotificationPresenterClientQt::removeClient()
164{
165    m_clientCount--;
166    if (!m_clientCount) {
167        s_notificationPresenter = 0;
168        delete this;
169    }
170}
171
172bool 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->tag().isEmpty())
179        removeReplacedNotificationFromQueue(notification);
180    if (dumpNotification)
181        dumpShowText(notification);
182    displayNotification(notification);
183    return true;
184}
185
186void NotificationPresenterClientQt::displayNotification(Notification* notification)
187{
188    NotificationWrapper* wrapper = new NotificationWrapper();
189    m_notifications.insert(notification, wrapper);
190    QString title = notification->title();
191    QString message = notification->body();
192
193    if (m_platformPlugin.plugin() && m_platformPlugin.plugin()->supportsExtension(QWebKitPlatformPlugin::Notifications))
194        wrapper->m_presenter = m_platformPlugin.createNotificationPresenter();
195
196    if (!wrapper->m_presenter) {
197#ifndef QT_NO_SYSTEMTRAYICON
198        if (!dumpNotification)
199            wrapper->m_closeTimer.startOneShot(notificationTimeout);
200#endif
201    }
202
203    wrapper->m_displayEventTimer.startOneShot(0);
204
205    // Make sure the notification was not cancelled during handling the display event
206    if (m_notifications.find(notification) == m_notifications.end())
207        return;
208
209    if (wrapper->m_presenter) {
210        wrapper->connect(wrapper->m_presenter.get(), SIGNAL(notificationClosed()), wrapper, SLOT(notificationClosed()), Qt::QueuedConnection);
211        wrapper->connect(wrapper->m_presenter.get(), SIGNAL(notificationClicked()), wrapper, SLOT(notificationClicked()));
212        wrapper->m_presenter->showNotification(wrapper);
213        return;
214    }
215
216#ifndef QT_NO_SYSTEMTRAYICON
217    wrapper->connect(m_systemTrayIcon.data(), SIGNAL(messageClicked()), wrapper, SLOT(notificationClicked()));
218    QMetaObject::invokeMethod(m_systemTrayIcon.data(), "show");
219    QMetaObject::invokeMethod(m_systemTrayIcon.data(), "showMessage", Q_ARG(QString, notification->title()), Q_ARG(QString, notification->body()));
220#endif
221}
222
223void NotificationPresenterClientQt::cancel(Notification* notification)
224{
225    if (dumpNotification && notification->scriptExecutionContext())
226        printf("DESKTOP NOTIFICATION CLOSED: %s\n", QString(notification->title()).toUtf8().constData());
227
228    NotificationsQueue::Iterator iter = m_notifications.find(notification);
229    if (iter != m_notifications.end()) {
230        sendEvent(notification, eventNames().closeEvent);
231        detachNotification(notification);
232    }
233}
234
235void NotificationPresenterClientQt::cancel(NotificationWrapper* wrapper)
236{
237    Notification* notification = notificationForWrapper(wrapper);
238    if (notification)
239        cancel(notification);
240}
241
242void NotificationPresenterClientQt::notificationClicked(NotificationWrapper* wrapper)
243{
244    Notification* notification =  notificationForWrapper(wrapper);
245    if (notification) {
246        // Make sure clicks on notifications are treated as user gestures.
247        UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
248        sendEvent(notification, eventNames().clickEvent);
249    }
250}
251
252void NotificationPresenterClientQt::notificationClicked(const QString& title)
253{
254    if (!dumpNotification)
255        return;
256    NotificationsQueue::ConstIterator end = m_notifications.end();
257    NotificationsQueue::ConstIterator iter = m_notifications.begin();
258    Notification* notification = 0;
259    while (iter != end) {
260        notification = iter.key();
261        QString notificationTitle = notification->title();
262        if (notificationTitle == title)
263            break;
264        iter++;
265    }
266    if (notification)
267        sendEvent(notification, eventNames().clickEvent);
268}
269
270Notification* NotificationPresenterClientQt::notificationForWrapper(const NotificationWrapper* wrapper) const
271{
272    NotificationsQueue::ConstIterator end = m_notifications.end();
273    NotificationsQueue::ConstIterator iter = m_notifications.begin();
274    while (iter != end && iter.value() != wrapper)
275        iter++;
276    if (iter != end)
277        return iter.key();
278    return 0;
279}
280
281void NotificationPresenterClientQt::notificationObjectDestroyed(Notification* notification)
282{
283    // Called from ~Notification(), Remove the entry from the notifications list and delete the icon.
284    NotificationsQueue::Iterator iter = m_notifications.find(notification);
285    if (iter != m_notifications.end())
286        delete m_notifications.take(notification);
287}
288
289void NotificationPresenterClientQt::notificationControllerDestroyed()
290{
291}
292
293#if ENABLE(LEGACY_NOTIFICATIONS)
294void NotificationPresenterClientQt::requestPermission(ScriptExecutionContext* context, PassRefPtr<VoidCallback> callback)
295{
296    if (dumpNotification)
297        printf("DESKTOP NOTIFICATION PERMISSION REQUESTED: %s\n", QString(context->securityOrigin()->toString()).toUtf8().constData());
298
299    NotificationClient::Permission permission = checkPermission(context);
300    if (permission != NotificationClient::PermissionNotAllowed) {
301        if (callback)
302            callback->handleEvent();
303        return;
304    }
305
306    QHash<ScriptExecutionContext*, CallbacksInfo >::iterator iter = m_pendingPermissionRequests.find(context);
307    if (iter != m_pendingPermissionRequests.end())
308        iter.value().m_voidCallbacks.append(callback);
309    else {
310        RefPtr<VoidCallback> cb = callback;
311        CallbacksInfo info;
312        info.m_frame = toFrame(context);
313        info.m_voidCallbacks.append(cb);
314
315        if (toPage(context) && toFrame(context)) {
316            m_pendingPermissionRequests.insert(context, info);
317            toPage(context)->notificationsPermissionRequested(toFrame(context));
318        }
319    }
320}
321#endif
322
323#if ENABLE(NOTIFICATIONS)
324void NotificationPresenterClientQt::requestPermission(ScriptExecutionContext* context, PassRefPtr<NotificationPermissionCallback> callback)
325{
326    if (dumpNotification)
327        printf("DESKTOP NOTIFICATION PERMISSION REQUESTED: %s\n", QString(context->securityOrigin()->toString()).toUtf8().constData());
328
329    NotificationClient::Permission permission = checkPermission(context);
330    if (permission != NotificationClient::PermissionNotAllowed) {
331        if (callback)
332            callback->handleEvent(Notification::permissionString(permission));
333        return;
334    }
335
336    QHash<ScriptExecutionContext*, CallbacksInfo >::iterator iter = m_pendingPermissionRequests.find(context);
337    if (iter != m_pendingPermissionRequests.end())
338        iter.value().m_callbacks.append(callback);
339    else {
340        RefPtr<NotificationPermissionCallback> cb = callback;
341        CallbacksInfo info;
342        info.m_frame = toFrame(context);
343        info.m_callbacks.append(cb);
344
345        if (toPage(context) && toFrame(context)) {
346            m_pendingPermissionRequests.insert(context, info);
347            toPage(context)->notificationsPermissionRequested(toFrame(context));
348        }
349    }
350}
351#endif
352
353NotificationClient::Permission NotificationPresenterClientQt::checkPermission(ScriptExecutionContext* context)
354{
355    return m_cachedPermissions.value(context, NotificationClient::PermissionNotAllowed);
356}
357
358void NotificationPresenterClientQt::cancelRequestsForPermission(ScriptExecutionContext* context)
359{
360    m_cachedPermissions.remove(context);
361
362    QHash<ScriptExecutionContext*, CallbacksInfo >::iterator iter = m_pendingPermissionRequests.find(context);
363    if (iter == m_pendingPermissionRequests.end())
364        return;
365
366    QWebFrameAdapter* frame = iter.value().m_frame;
367    if (!frame)
368        return;
369    QWebPageAdapter* page = QWebPageAdapter::kit(frame->frame->page());
370    m_pendingPermissionRequests.erase(iter);
371
372    if (!page)
373        return;
374
375    if (dumpNotification)
376        printf("DESKTOP NOTIFICATION PERMISSION REQUEST CANCELLED: %s\n", QString(context->securityOrigin()->toString()).toUtf8().constData());
377
378    page->notificationsPermissionRequestCancelled(frame);
379}
380
381void NotificationPresenterClientQt::setNotificationsAllowedForFrame(Frame* frame, bool allowed)
382{
383    ASSERT(frame->document());
384    if (!frame->document())
385        return;
386
387    NotificationClient::Permission permission = allowed ? NotificationClient::PermissionAllowed : NotificationClient::PermissionDenied;
388    m_cachedPermissions.insert(frame->document(), permission);
389
390    QHash<ScriptExecutionContext*,  CallbacksInfo>::iterator iter = m_pendingPermissionRequests.begin();
391    while (iter != m_pendingPermissionRequests.end()) {
392        if (iter.key() == frame->document())
393            break;
394    }
395
396    if (iter == m_pendingPermissionRequests.end())
397        return;
398
399#if ENABLE(LEGACY_NOTIFICATIONS)
400    QList<RefPtr<VoidCallback> >& voidCallbacks = iter.value().m_voidCallbacks;
401    Q_FOREACH(const RefPtr<VoidCallback>& callback, voidCallbacks) {
402        if (callback)
403            callback->handleEvent();
404    }
405#endif
406#if ENABLE(NOTIFICATIONS)
407    QList<RefPtr<NotificationPermissionCallback> >& callbacks = iter.value().m_callbacks;
408    Q_FOREACH(const RefPtr<NotificationPermissionCallback>& callback, callbacks) {
409        if (callback)
410            callback->handleEvent(Notification::permissionString(permission));
411    }
412#endif
413    m_pendingPermissionRequests.remove(iter.key());
414}
415
416void NotificationPresenterClientQt::sendDisplayEvent(NotificationWrapper* wrapper)
417{
418    Notification* notification = notificationForWrapper(wrapper);
419    if (notification)
420        sendEvent(notification, "show");
421}
422
423void NotificationPresenterClientQt::sendEvent(Notification* notification, const AtomicString& eventName)
424{
425    if (notification->scriptExecutionContext())
426        notification->dispatchEvent(Event::create(eventName, false, true));
427}
428
429void NotificationPresenterClientQt::clearCachedPermissions()
430{
431    m_cachedPermissions.clear();
432}
433
434void NotificationPresenterClientQt::removeReplacedNotificationFromQueue(Notification* notification)
435{
436    Notification* oldNotification = 0;
437    NotificationsQueue::Iterator end = m_notifications.end();
438    NotificationsQueue::Iterator iter = m_notifications.begin();
439
440    while (iter != end) {
441        Notification* existingNotification = iter.key();
442        if (existingNotification->tag() == notification->tag()) {
443            oldNotification = iter.key();
444            break;
445        }
446        iter++;
447    }
448
449    if (oldNotification) {
450        if (dumpNotification)
451            dumpReplacedIdText(oldNotification);
452        sendEvent(oldNotification, eventNames().closeEvent);
453        detachNotification(oldNotification);
454    }
455}
456
457void NotificationPresenterClientQt::detachNotification(Notification* notification)
458{
459    delete m_notifications.take(notification);
460    notification->detachPresenter();
461    notification->unsetPendingActivity(notification);
462}
463
464void NotificationPresenterClientQt::dumpReplacedIdText(Notification* notification)
465{
466    if (notification)
467        printf("REPLACING NOTIFICATION %s\n", QString(notification->title()).toUtf8().constData());
468}
469
470void NotificationPresenterClientQt::dumpShowText(Notification* notification)
471{
472    printf("DESKTOP NOTIFICATION:%s icon %s, title %s, text %s\n",
473        notification->dir() == "rtl" ? "(RTL)" : "",
474        QString(notification->iconURL().string()).toUtf8().constData(), QString(notification->title()).toUtf8().constData(),
475        QString(notification->body()).toUtf8().constData());
476}
477
478QWebPageAdapter* NotificationPresenterClientQt::toPage(ScriptExecutionContext* context)
479{
480    if (!context || context->isWorkerContext())
481        return 0;
482
483    Document* document = static_cast<Document*>(context);
484
485    Page* page = document->page();
486    if (!page || !page->mainFrame())
487        return 0;
488
489    return QWebPageAdapter::kit(page);
490}
491
492QWebFrameAdapter* NotificationPresenterClientQt::toFrame(ScriptExecutionContext* context)
493{
494    if (!context || context->isWorkerContext())
495        return 0;
496
497    Document* document = static_cast<Document*>(context);
498    if (!document || !document->frame())
499        return 0;
500
501    return QWebFrameAdapter::kit(document->frame());
502}
503
504#endif // ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
505}
506
507#include "moc_NotificationPresenterClientQt.cpp"
508