Home | History | Annotate | Download | only in WebCoreSupport
      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 
     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 
     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 
     82 void NotificationWrapper::close(Timer<NotificationWrapper>*)
     83 {
     84 #if ENABLE(NOTIFICATIONS)
     85     NotificationPresenterClientQt::notificationPresenter()->cancel(this);
     86 #endif
     87 }
     88 
     89 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 
     99 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 
    109 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 
    122 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 
    135 void NotificationWrapper::notificationClicked()
    136 {
    137 #if ENABLE(NOTIFICATIONS)
    138     NotificationPresenterClientQt::notificationPresenter()->notificationClicked(this);
    139 #endif
    140 }
    141 
    142 void NotificationWrapper::notificationClosed()
    143 {
    144 #if ENABLE(NOTIFICATIONS)
    145     NotificationPresenterClientQt::notificationPresenter()->cancel(this);
    146 #endif
    147 }
    148 
    149 #if ENABLE(NOTIFICATIONS)
    150 
    151 NotificationPresenterClientQt::NotificationPresenterClientQt() : m_clientCount(0)
    152 {
    153 }
    154 
    155 NotificationPresenterClientQt::~NotificationPresenterClientQt()
    156 {
    157     while (!m_notifications.isEmpty()) {
    158         NotificationsQueue::Iterator iter = m_notifications.begin();
    159         detachNotification(iter.key());
    160     }
    161 }
    162 
    163 void NotificationPresenterClientQt::removeClient()
    164 {
    165     m_clientCount--;
    166     if (!m_clientCount) {
    167         s_notificationPresenter = 0;
    168         delete this;
    169     }
    170 }
    171 
    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 
    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 
    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 
    256 void NotificationPresenterClientQt::cancel(NotificationWrapper* wrapper)
    257 {
    258     Notification* notification = notificationForWrapper(wrapper);
    259     if (notification)
    260         cancel(notification);
    261 }
    262 
    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 
    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 
    295 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 
    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 
    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 
    336 NotificationPresenter::Permission NotificationPresenterClientQt::checkPermission(ScriptExecutionContext* context)
    337 {
    338     return m_cachedPermissions.value(context, NotificationPresenter::PermissionNotAllowed);
    339 }
    340 
    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 
    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 
    383 void NotificationPresenterClientQt::sendEvent(Notification* notification, const AtomicString& eventName)
    384 {
    385     if (notification->scriptExecutionContext())
    386         notification->dispatchEvent(Event::create(eventName, false, true));
    387 }
    388 
    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 
    412 void NotificationPresenterClientQt::detachNotification(Notification* notification)
    413 {
    414     delete m_notifications.take(notification);
    415     notification->detachPresenter();
    416     notification->unsetPendingActivity(notification);
    417 }
    418 
    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 
    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 
    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 
    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