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