1 /* 2 * Copyright 2007, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #define LOG_TAG "WebCore" 27 28 #include "config.h" 29 30 #include "ApplicationCacheStorage.h" 31 #include "ChromeClientAndroid.h" 32 #include "CString.h" 33 #include "DatabaseTracker.h" 34 #include "Document.h" 35 #include "PlatformString.h" 36 #include "FloatRect.h" 37 #include "Frame.h" 38 #include "FrameLoader.h" 39 #include "FrameView.h" 40 #include "Geolocation.h" 41 #include "GraphicsLayerAndroid.h" 42 #include "Page.h" 43 #include "Screen.h" 44 #include "ScriptController.h" 45 #include "WebCoreFrameBridge.h" 46 #include "WebCoreViewBridge.h" 47 #include "WebViewCore.h" 48 #include "WindowFeatures.h" 49 #include "Settings.h" 50 51 namespace android { 52 53 #if ENABLE(DATABASE) 54 static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota); 55 #endif 56 57 #if USE(ACCELERATED_COMPOSITING) 58 59 void ChromeClientAndroid::syncTimerFired(Timer<ChromeClientAndroid>* client) 60 { 61 if (!m_rootGraphicsLayer) 62 return; 63 64 if (m_webFrame) { 65 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 66 if (frameView && frameView->syncCompositingStateRecursive()) { 67 GraphicsLayerAndroid* androidGraphicsLayer = 68 static_cast<GraphicsLayerAndroid*>(m_rootGraphicsLayer); 69 if (androidGraphicsLayer) { 70 androidGraphicsLayer->sendImmediateRepaint(); 71 androidGraphicsLayer->notifyClientAnimationStarted(); 72 } 73 } 74 } 75 } 76 77 void ChromeClientAndroid::scheduleCompositingLayerSync() 78 { 79 if (!m_syncTimer.isActive()) 80 m_syncTimer.startOneShot(0); 81 } 82 83 void ChromeClientAndroid::setNeedsOneShotDrawingSynchronization() 84 { 85 // This should not be needed 86 } 87 88 void ChromeClientAndroid::attachRootGraphicsLayer(WebCore::Frame* frame, WebCore::GraphicsLayer* layer) 89 { 90 m_rootGraphicsLayer = layer; 91 if (!layer) { 92 WebViewCore::getWebViewCore(frame->view())->setUIRootLayer(0); 93 return; 94 } 95 WebCore::GraphicsLayerAndroid* androidGraphicsLayer = static_cast<GraphicsLayerAndroid*>(layer); 96 androidGraphicsLayer->setFrame(frame); 97 scheduleCompositingLayerSync(); 98 } 99 100 #endif 101 102 void ChromeClientAndroid::setWebFrame(android::WebFrame* webframe) 103 { 104 Release(m_webFrame); 105 m_webFrame = webframe; 106 Retain(m_webFrame); 107 } 108 109 void ChromeClientAndroid::chromeDestroyed() 110 { 111 Release(m_webFrame); 112 delete this; 113 } 114 115 void ChromeClientAndroid::setWindowRect(const FloatRect&) { notImplemented(); } 116 117 FloatRect ChromeClientAndroid::windowRect() { 118 ASSERT(m_webFrame); 119 if (!m_webFrame) 120 return FloatRect(); 121 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 122 if (!frameView) 123 return FloatRect(); 124 const WebCoreViewBridge* bridge = frameView->platformWidget(); 125 const IntRect& rect = bridge->getWindowBounds(); 126 FloatRect fRect(rect.x(), rect.y(), rect.width(), rect.height()); 127 return fRect; 128 } 129 130 FloatRect ChromeClientAndroid::pageRect() { notImplemented(); return FloatRect(); } 131 132 float ChromeClientAndroid::scaleFactor() 133 { 134 ASSERT(m_webFrame); 135 return m_webFrame->density(); 136 } 137 138 #ifdef ANDROID_USER_GESTURE 139 void ChromeClientAndroid::focus(bool userGesture) { 140 #else 141 void ChromeClientAndroid::focus() { 142 // The old behavior was to always allow javascript to focus a window. If we 143 // turn off ANDROID_USER_GESTURE, go back to the old behavior by forcing 144 // userGesture to be true. 145 bool userGesture = true; 146 #endif 147 ASSERT(m_webFrame); 148 // Ask the application to focus this WebView. 149 if (userGesture) 150 m_webFrame->requestFocus(); 151 } 152 void ChromeClientAndroid::unfocus() { notImplemented(); } 153 154 bool ChromeClientAndroid::canTakeFocus(FocusDirection) { notImplemented(); return false; } 155 void ChromeClientAndroid::takeFocus(FocusDirection) { notImplemented(); } 156 157 void ChromeClientAndroid::focusedNodeChanged(Node*) { notImplemented(); } 158 159 Page* ChromeClientAndroid::createWindow(Frame* frame, const FrameLoadRequest&, 160 const WindowFeatures& features) 161 { 162 ASSERT(frame); 163 #ifdef ANDROID_MULTIPLE_WINDOWS 164 if (frame->settings() && !(frame->settings()->supportMultipleWindows())) 165 // If the client doesn't support multiple windows, just return the current page 166 return frame->page(); 167 #endif 168 169 WTF::PassRefPtr<WebCore::Screen> screen = WebCore::Screen::create(frame); 170 bool dialog = features.dialog || !features.resizable 171 || (features.heightSet && features.height < screen.get()->height() 172 && features.widthSet && features.width < screen.get()->width()) 173 || (!features.menuBarVisible && !features.statusBarVisible 174 && !features.toolBarVisible && !features.locationBarVisible 175 && !features.scrollbarsVisible); 176 // fullscreen definitely means no dialog 177 if (features.fullscreen) 178 dialog = false; 179 WebCore::Frame* newFrame = m_webFrame->createWindow(dialog, 180 frame->script()->processingUserGesture(mainThreadNormalWorld())); 181 if (newFrame) { 182 WebCore::Page* page = newFrame->page(); 183 page->setGroupName(frame->page()->groupName()); 184 return page; 185 } 186 return NULL; 187 } 188 189 void ChromeClientAndroid::show() { notImplemented(); } 190 191 bool ChromeClientAndroid::canRunModal() { notImplemented(); return false; } 192 void ChromeClientAndroid::runModal() { notImplemented(); } 193 194 void ChromeClientAndroid::setToolbarsVisible(bool) { notImplemented(); } 195 bool ChromeClientAndroid::toolbarsVisible() { notImplemented(); return false; } 196 197 void ChromeClientAndroid::setStatusbarVisible(bool) { notImplemented(); } 198 bool ChromeClientAndroid::statusbarVisible() { notImplemented(); return false; } 199 200 void ChromeClientAndroid::setScrollbarsVisible(bool) { notImplemented(); } 201 bool ChromeClientAndroid::scrollbarsVisible() { notImplemented(); return false; } 202 203 void ChromeClientAndroid::setMenubarVisible(bool) { notImplemented(); } 204 bool ChromeClientAndroid::menubarVisible() { notImplemented(); return false; } 205 206 void ChromeClientAndroid::setResizable(bool) { notImplemented(); } 207 208 // This function is called by the JavaScript bindings to print usually an error to 209 // a message console. Pass the message to the java side so that the client can 210 // handle it as it sees fit. 211 void ChromeClientAndroid::addMessageToConsole(MessageSource, MessageType, MessageLevel msgLevel, const String& message, unsigned int lineNumber, const String& sourceID) { 212 android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->addMessageToConsole(message, lineNumber, sourceID, msgLevel); 213 } 214 215 bool ChromeClientAndroid::canRunBeforeUnloadConfirmPanel() { return true; } 216 bool ChromeClientAndroid::runBeforeUnloadConfirmPanel(const String& message, Frame* frame) { 217 String url = frame->document()->documentURI(); 218 return android::WebViewCore::getWebViewCore(frame->view())->jsUnload(url, message); 219 } 220 221 void ChromeClientAndroid::closeWindowSoon() 222 { 223 ASSERT(m_webFrame); 224 Page* page = m_webFrame->page(); 225 Frame* mainFrame = page->mainFrame(); 226 // This will prevent javascript cross-scripting during unload 227 page->setGroupName(String()); 228 // Stop loading but do not send the unload event 229 mainFrame->loader()->stopLoading(UnloadEventPolicyNone); 230 // Cancel all pending loaders 231 mainFrame->loader()->stopAllLoaders(); 232 // Remove all event listeners so that no javascript can execute as a result 233 // of mouse/keyboard events. 234 mainFrame->document()->removeAllEventListeners(); 235 // Close the window. 236 m_webFrame->closeWindow(android::WebViewCore::getWebViewCore(mainFrame->view())); 237 } 238 239 void ChromeClientAndroid::runJavaScriptAlert(Frame* frame, const String& message) 240 { 241 String url = frame->document()->documentURI(); 242 243 android::WebViewCore::getWebViewCore(frame->view())->jsAlert(url, message); 244 } 245 246 bool ChromeClientAndroid::runJavaScriptConfirm(Frame* frame, const String& message) 247 { 248 String url = frame->document()->documentURI(); 249 250 return android::WebViewCore::getWebViewCore(frame->view())->jsConfirm(url, message); 251 } 252 253 /* This function is called for the javascript method Window.prompt(). A dialog should be shown on 254 * the screen with an input put box. First param is the text, the second is the default value for 255 * the input box, third is return param. If the function returns true, the value set in the third parameter 256 * is provided to javascript, else null is returned to the script. 257 */ 258 bool ChromeClientAndroid::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result) 259 { 260 String url = frame->document()->documentURI(); 261 return android::WebViewCore::getWebViewCore(frame->view())->jsPrompt(url, message, defaultValue, result); 262 } 263 void ChromeClientAndroid::setStatusbarText(const String&) { notImplemented(); } 264 265 // This is called by the JavaScript interpreter when a script has been running for a long 266 // time. A dialog should be shown to the user asking them if they would like to cancel the 267 // Javascript. If true is returned, the script is cancelled. 268 // To make a device more responsive, we default to return true to disallow long running script. 269 // This implies that some of scripts will not be completed. 270 bool ChromeClientAndroid::shouldInterruptJavaScript() { 271 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 272 return android::WebViewCore::getWebViewCore(frameView)->jsInterrupt(); 273 } 274 275 bool ChromeClientAndroid::tabsToLinks() const { return false; } 276 277 IntRect ChromeClientAndroid::windowResizerRect() const { return IntRect(0, 0, 0, 0); } 278 279 // new to change 38068 (Nov 6, 2008) 280 void ChromeClientAndroid::repaint(const IntRect& rect, bool contentChanged, 281 bool immediate, bool repaintContentOnly) { 282 notImplemented(); 283 // was in ScrollViewAndroid::update() : needs to be something like: 284 // android::WebViewCore::getWebViewCore(this)->contentInvalidate(rect); 285 } 286 287 // new to change 38068 (Nov 6, 2008) 288 void ChromeClientAndroid::scroll(const IntSize& scrollDelta, 289 const IntRect& rectToScroll, const IntRect& clipRect) { 290 notImplemented(); 291 } 292 293 // new to change 38068 (Nov 6, 2008) 294 IntPoint ChromeClientAndroid::screenToWindow(const IntPoint&) const { 295 notImplemented(); 296 return IntPoint(); 297 } 298 299 // new to change 38068 (Nov 6, 2008) 300 IntRect ChromeClientAndroid::windowToScreen(const IntRect&) const { 301 notImplemented(); 302 return IntRect(); 303 } 304 305 PlatformPageClient ChromeClientAndroid::platformPageClient() const { 306 Page* page = m_webFrame->page(); 307 Frame* mainFrame = page->mainFrame(); 308 FrameView* view = mainFrame->view(); 309 PlatformWidget viewBridge = view->platformWidget(); 310 return viewBridge; 311 } 312 313 void ChromeClientAndroid::contentsSizeChanged(Frame*, const IntSize&) const 314 { 315 notImplemented(); 316 } 317 318 void ChromeClientAndroid::scrollRectIntoView(const IntRect&, const ScrollView*) const 319 { 320 notImplemented(); 321 } 322 323 void ChromeClientAndroid::formStateDidChange(const Node*) 324 { 325 notImplemented(); 326 } 327 328 void ChromeClientAndroid::scrollbarsModeDidChange() const 329 { 330 notImplemented(); 331 } 332 333 void ChromeClientAndroid::mouseDidMoveOverElement(const HitTestResult&, unsigned int) {} 334 void ChromeClientAndroid::setToolTip(const String&, TextDirection) {} 335 void ChromeClientAndroid::print(Frame*) {} 336 337 /* 338 * This function is called on the main (webcore) thread by SQLTransaction::deliverQuotaIncreaseCallback. 339 * The way that the callback mechanism is designed inside SQLTransaction means that there must be a new quota 340 * (which may be equal to the old quota if the user did not allow more quota) when this function returns. As 341 * we call into the browser thread to ask what to do with the quota, we block here and get woken up when the 342 * browser calls the native WebViewCore::SetDatabaseQuota method with the new quota value. 343 */ 344 #if ENABLE(DATABASE) 345 void ChromeClientAndroid::exceededDatabaseQuota(Frame* frame, const String& name) 346 { 347 SecurityOrigin* origin = frame->document()->securityOrigin(); 348 DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); 349 350 // We want to wait on a new quota from the UI thread. Reset the m_newQuota variable to represent we haven't received a new quota. 351 m_newQuota = -1; 352 353 // This origin is being tracked and has exceeded it's quota. Call into 354 // the Java side of things to inform the user. 355 unsigned long long currentQuota = 0; 356 if (tracker.hasEntryForOrigin(origin)) 357 currentQuota = tracker.quotaForOrigin(origin); 358 359 unsigned long long estimatedSize = 0; 360 361 // Only update estimatedSize if we are trying to create a a new database, i.e. the usage for the database is 0. 362 if (tracker.usageForDatabase(name, origin) == 0) 363 estimatedSize = tracker.detailsForNameAndOrigin(name, origin).expectedUsage(); 364 365 android::WebViewCore::getWebViewCore(frame->view())->exceededDatabaseQuota(frame->document()->documentURI(), name, currentQuota, estimatedSize); 366 367 // We've sent notification to the browser so now wait for it to come back. 368 m_quotaThreadLock.lock(); 369 while (m_newQuota == -1) { 370 m_quotaThreadCondition.wait(m_quotaThreadLock); 371 } 372 m_quotaThreadLock.unlock(); 373 374 // If new quota is unavailable, we may be able to resolve the situation by shrinking the quota of an origin that asked for a lot but is only using a little. 375 // If we find such a site, shrink it's quota and ask Java to try again. 376 377 if ((unsigned long long) m_newQuota == currentQuota && !m_triedToReclaimDBQuota) { 378 m_triedToReclaimDBQuota = true; // we should only try this once per quota overflow. 379 unsigned long long reclaimedQuotaBytes = tryToReclaimDatabaseQuota(origin); 380 381 // If we were able to free up enough space, try asking Java again. 382 // Otherwise, give up and deny the new database. :( 383 if (reclaimedQuotaBytes >= estimatedSize) { 384 exceededDatabaseQuota(frame, name); 385 return; 386 } 387 } 388 389 // Update the DatabaseTracker with the new quota value (if the user declined 390 // new quota, this may equal the old quota) 391 tracker.setQuota(origin, m_newQuota); 392 m_triedToReclaimDBQuota = false; 393 } 394 395 static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota) { 396 DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); 397 Vector<RefPtr<SecurityOrigin> > origins; 398 tracker.origins(origins); 399 unsigned long long reclaimedQuotaBytes = 0; 400 for (unsigned i = 0; i < origins.size(); i++) { 401 SecurityOrigin* originToReclaimFrom = origins[i].get(); 402 403 // Don't try to reclaim from the origin that has exceeded its quota. 404 if (originToReclaimFrom->equal(originNeedingQuota)) 405 continue; 406 407 unsigned long long originUsage = tracker.usageForOrigin(originToReclaimFrom); 408 unsigned long long originQuota = tracker.quotaForOrigin(originToReclaimFrom); 409 // If the origin has a quota that is more than it's current usage +1MB, shrink it. 410 static const int ONE_MB = 1 * 1024 * 1024; 411 if (originUsage + ONE_MB < originQuota) { 412 unsigned long long newQuota = originUsage + ONE_MB; 413 tracker.setQuota(originToReclaimFrom, newQuota); 414 reclaimedQuotaBytes += originQuota - newQuota; 415 } 416 } 417 return reclaimedQuotaBytes; 418 } 419 #endif 420 421 #if ENABLE(OFFLINE_WEB_APPLICATIONS) 422 void ChromeClientAndroid::reachedMaxAppCacheSize(int64_t spaceNeeded) 423 { 424 // Set m_newQuota before calling into the Java side. If we do this after, 425 // we could overwrite the result passed from the Java side and deadlock in the 426 // wait call below. 427 m_newQuota = -1; 428 Page* page = m_webFrame->page(); 429 Frame* mainFrame = page->mainFrame(); 430 FrameView* view = mainFrame->view(); 431 android::WebViewCore::getWebViewCore(view)->reachedMaxAppCacheSize(spaceNeeded); 432 433 // We've sent notification to the browser so now wait for it to come back. 434 m_quotaThreadLock.lock(); 435 while (m_newQuota == -1) { 436 m_quotaThreadCondition.wait(m_quotaThreadLock); 437 } 438 m_quotaThreadLock.unlock(); 439 if (m_newQuota > 0) { 440 WebCore::cacheStorage().setMaximumSize(m_newQuota); 441 // Now the app cache will retry the saving the previously failed cache. 442 } 443 } 444 #endif 445 446 void ChromeClientAndroid::populateVisitedLinks() 447 { 448 Page* page = m_webFrame->page(); 449 Frame* mainFrame = page->mainFrame(); 450 FrameView* view = mainFrame->view(); 451 android::WebViewCore::getWebViewCore(view)->populateVisitedLinks(&page->group()); 452 } 453 454 void ChromeClientAndroid::requestGeolocationPermissionForFrame(Frame* frame, Geolocation* geolocation) 455 { 456 ASSERT(geolocation); 457 if (!m_geolocationPermissions) { 458 m_geolocationPermissions = new GeolocationPermissions(android::WebViewCore::getWebViewCore(frame->view()), 459 m_webFrame->page()->mainFrame()); 460 } 461 m_geolocationPermissions->queryPermissionState(frame); 462 } 463 464 void ChromeClientAndroid::cancelGeolocationPermissionRequestForFrame(Frame* frame) 465 { 466 if (m_geolocationPermissions) 467 m_geolocationPermissions->cancelPermissionStateQuery(frame); 468 } 469 470 void ChromeClientAndroid::provideGeolocationPermissions(const String &origin, bool allow, bool remember) 471 { 472 ASSERT(m_geolocationPermissions); 473 m_geolocationPermissions->providePermissionState(origin, allow, remember); 474 } 475 476 void ChromeClientAndroid::storeGeolocationPermissions() 477 { 478 GeolocationPermissions::maybeStorePermanentPermissions(); 479 } 480 481 void ChromeClientAndroid::onMainFrameLoadStarted() 482 { 483 if (m_geolocationPermissions.get()) 484 m_geolocationPermissions->resetTemporaryPermissionStates(); 485 } 486 487 void ChromeClientAndroid::runOpenPanel(Frame* frame, 488 PassRefPtr<FileChooser> chooser) 489 { 490 android::WebViewCore* core = android::WebViewCore::getWebViewCore( 491 frame->view()); 492 core->openFileChooser(chooser); 493 } 494 495 bool ChromeClientAndroid::setCursor(PlatformCursorHandle) 496 { 497 notImplemented(); 498 return false; 499 } 500 501 void ChromeClientAndroid::wakeUpMainThreadWithNewQuota(long newQuota) { 502 MutexLocker locker(m_quotaThreadLock); 503 m_newQuota = newQuota; 504 m_quotaThreadCondition.signal(); 505 } 506 507 #if ENABLE(TOUCH_EVENTS) 508 void ChromeClientAndroid::needTouchEvents(bool needTouchEvents) 509 { 510 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 511 android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); 512 if (core) 513 core->needTouchEvents(needTouchEvents); 514 } 515 #endif 516 517 } 518