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
49namespace WebCore {
50
51#if ENABLE(NOTIFICATIONS)
52
53const double notificationTimeout = 10.0;
54
55bool NotificationPresenterClientQt::dumpNotification = false;
56
57NotificationPresenterClientQt* s_notificationPresenter = 0;
58
59NotificationPresenterClientQt* 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
70NotificationWrapper::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
82void NotificationWrapper::close(Timer<NotificationWrapper>*)
83{
84#if ENABLE(NOTIFICATIONS)
85    NotificationPresenterClientQt::notificationPresenter()->cancel(this);
86#endif
87}
88
89const 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
99const 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
109const 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
122const 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
135void NotificationWrapper::notificationClicked()
136{
137#if ENABLE(NOTIFICATIONS)
138    NotificationPresenterClientQt::notificationPresenter()->notificationClicked(this);
139#endif
140}
141
142void NotificationWrapper::notificationClosed()
143{
144#if ENABLE(NOTIFICATIONS)
145    NotificationPresenterClientQt::notificationPresenter()->cancel(this);
146#endif
147}
148
149#if ENABLE(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->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
190void 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
240void 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
256void NotificationPresenterClientQt::cancel(NotificationWrapper* wrapper)
257{
258    Notification* notification = notificationForWrapper(wrapper);
259    if (notification)
260        cancel(notification);
261}
262
263void 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
273void 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
295Notification* 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
306void 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
314void 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
336NotificationPresenter::Permission NotificationPresenterClientQt::checkPermission(ScriptExecutionContext* context)
337{
338    return m_cachedPermissions.value(context, NotificationPresenter::PermissionNotAllowed);
339}
340
341void 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
364void 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
383void NotificationPresenterClientQt::sendEvent(Notification* notification, const AtomicString& eventName)
384{
385    if (notification->scriptExecutionContext())
386        notification->dispatchEvent(Event::create(eventName, false, true));
387}
388
389void 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
412void NotificationPresenterClientQt::detachNotification(Notification* notification)
413{
414    delete m_notifications.take(notification);
415    notification->detachPresenter();
416    notification->unsetPendingActivity(notification);
417}
418
419void 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
425void 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
437QWebPage* 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
451QWebFrame* 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