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