ChromeClientAndroid.cpp revision ba01ca1ef2bfe8916fafb72fee4a594a0cb2e302
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 "DatabaseTracker.h" 33#include "Document.h" 34#include "PlatformString.h" 35#include "FloatRect.h" 36#include "Frame.h" 37#include "FrameLoader.h" 38#include "FrameView.h" 39#include "Geolocation.h" 40#include "GraphicsLayerAndroid.h" 41#include "Icon.h" 42#include "Page.h" 43#include "PopupMenuAndroid.h" 44#include "ScriptController.h" 45#include "SearchPopupMenuAndroid.h" 46#include "WebCoreFrameBridge.h" 47#include "WebCoreViewBridge.h" 48#include "WebViewCore.h" 49#include "WindowFeatures.h" 50#include "Settings.h" 51#include "UserGestureIndicator.h" 52#include <wtf/text/CString.h> 53 54namespace android { 55 56#if ENABLE(DATABASE) 57static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota); 58#endif 59 60#if USE(ACCELERATED_COMPOSITING) 61 62WebCore::GraphicsLayer* ChromeClientAndroid::layersSync() 63{ 64 if (m_rootGraphicsLayer && m_needsLayerSync && m_webFrame) { 65 if (FrameView* frameView = m_webFrame->page()->mainFrame()->view()) 66 frameView->syncCompositingStateRecursive(); 67 } 68 m_needsLayerSync = false; 69 return m_rootGraphicsLayer; 70} 71 72void ChromeClientAndroid::scheduleCompositingLayerSync() 73{ 74 if (m_needsLayerSync) 75 return; 76 m_needsLayerSync = true; 77 WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view()); 78 if (webViewCore) 79 webViewCore->layersDraw(); 80} 81 82void ChromeClientAndroid::setNeedsOneShotDrawingSynchronization() 83{ 84 // This should not be needed 85} 86 87void ChromeClientAndroid::attachRootGraphicsLayer(WebCore::Frame*, WebCore::GraphicsLayer* layer) 88{ 89 // frame is not used in Android as we should only get root graphics layer for the main frame 90 m_rootGraphicsLayer = layer; 91 if (!layer) 92 return; 93 scheduleCompositingLayerSync(); 94} 95 96#endif 97 98void ChromeClientAndroid::setWebFrame(android::WebFrame* webframe) 99{ 100 Release(m_webFrame); 101 m_webFrame = webframe; 102 Retain(m_webFrame); 103} 104 105void ChromeClientAndroid::chromeDestroyed() 106{ 107 Release(m_webFrame); 108 delete this; 109} 110 111void ChromeClientAndroid::setWindowRect(const FloatRect&) { notImplemented(); } 112 113FloatRect ChromeClientAndroid::windowRect() { 114 ASSERT(m_webFrame); 115 if (!m_webFrame) 116 return FloatRect(); 117 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 118 if (!frameView) 119 return FloatRect(); 120 const WebCoreViewBridge* bridge = frameView->platformWidget(); 121 const IntRect& rect = bridge->getWindowBounds(); 122 FloatRect fRect(rect.x(), rect.y(), rect.width(), rect.height()); 123 return fRect; 124} 125 126FloatRect ChromeClientAndroid::pageRect() { notImplemented(); return FloatRect(); } 127 128float ChromeClientAndroid::scaleFactor() 129{ 130 ASSERT(m_webFrame); 131 return m_webFrame->density(); 132} 133 134void ChromeClientAndroid::focus() 135{ 136 ASSERT(m_webFrame); 137 bool isUserGesture = UserGestureIndicator::processingUserGesture(); 138 139 // Ask the application to focus this WebView if the action is intiated by the user 140 if (isUserGesture) 141 m_webFrame->requestFocus(); 142} 143void ChromeClientAndroid::unfocus() { notImplemented(); } 144 145bool ChromeClientAndroid::canTakeFocus(FocusDirection) { notImplemented(); return false; } 146void ChromeClientAndroid::takeFocus(FocusDirection) { notImplemented(); } 147 148void ChromeClientAndroid::focusedNodeChanged(Node* node) 149{ 150 android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->focusNodeChanged(node); 151} 152 153void ChromeClientAndroid::focusedFrameChanged(Frame*) { notImplemented(); } 154 155Page* ChromeClientAndroid::createWindow(Frame* frame, const FrameLoadRequest&, 156 const WindowFeatures& features, const NavigationAction&) 157{ 158 ASSERT(frame); 159#ifdef ANDROID_MULTIPLE_WINDOWS 160 if (frame->settings() && !(frame->settings()->supportMultipleWindows())) 161 // If the client doesn't support multiple windows, just return the current page 162 return frame->page(); 163#endif 164 165 const WebCoreViewBridge* bridge = frame->view()->platformWidget(); 166 bool dialog = features.dialog || !features.resizable 167 || (features.heightSet && features.height < bridge->height() 168 && features.widthSet && features.width < bridge->width()) 169 || (!features.menuBarVisible && !features.statusBarVisible 170 && !features.toolBarVisible && !features.locationBarVisible 171 && !features.scrollbarsVisible); 172 // fullscreen definitely means no dialog 173 if (features.fullscreen) 174 dialog = false; 175 WebCore::Frame* newFrame = m_webFrame->createWindow(dialog, 176 ScriptController::processingUserGesture()); 177 if (newFrame) { 178 WebCore::Page* page = newFrame->page(); 179 page->setGroupName(frame->page()->groupName()); 180 return page; 181 } 182 return NULL; 183} 184 185void ChromeClientAndroid::show() { notImplemented(); } 186 187bool ChromeClientAndroid::canRunModal() { notImplemented(); return false; } 188void ChromeClientAndroid::runModal() { notImplemented(); } 189 190void ChromeClientAndroid::setToolbarsVisible(bool) { notImplemented(); } 191bool ChromeClientAndroid::toolbarsVisible() { notImplemented(); return false; } 192 193void ChromeClientAndroid::setStatusbarVisible(bool) { notImplemented(); } 194bool ChromeClientAndroid::statusbarVisible() { notImplemented(); return false; } 195 196void ChromeClientAndroid::setScrollbarsVisible(bool) { notImplemented(); } 197bool ChromeClientAndroid::scrollbarsVisible() { notImplemented(); return false; } 198 199void ChromeClientAndroid::setMenubarVisible(bool) { notImplemented(); } 200bool ChromeClientAndroid::menubarVisible() { notImplemented(); return false; } 201 202void ChromeClientAndroid::setResizable(bool) { notImplemented(); } 203 204#if ENABLE(CONTEXT_MENUS) 205void ChromeClientAndroid::showContextMenu() { notImplemented(); } 206#endif 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. 211void 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 215void ChromeClientAndroid::formDidBlur(const WebCore::Node* node) 216{ 217 android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->formDidBlur(node); 218} 219 220bool ChromeClientAndroid::canRunBeforeUnloadConfirmPanel() { return true; } 221bool ChromeClientAndroid::runBeforeUnloadConfirmPanel(const String& message, Frame* frame) { 222 String url = frame->document()->documentURI(); 223 return android::WebViewCore::getWebViewCore(frame->view())->jsUnload(url, message); 224} 225 226void ChromeClientAndroid::closeWindowSoon() 227{ 228 ASSERT(m_webFrame); 229 Page* page = m_webFrame->page(); 230 Frame* mainFrame = page->mainFrame(); 231 // This will prevent javascript cross-scripting during unload 232 page->setGroupName(String()); 233 // Stop loading but do not send the unload event 234 mainFrame->loader()->stopLoading(UnloadEventPolicyNone); 235 // Cancel all pending loaders 236 mainFrame->loader()->stopAllLoaders(); 237 // Remove all event listeners so that no javascript can execute as a result 238 // of mouse/keyboard events. 239 mainFrame->document()->removeAllEventListeners(); 240 // Close the window. 241 m_webFrame->closeWindow(android::WebViewCore::getWebViewCore(mainFrame->view())); 242} 243 244void ChromeClientAndroid::runJavaScriptAlert(Frame* frame, const String& message) 245{ 246 String url = frame->document()->documentURI(); 247 248 android::WebViewCore::getWebViewCore(frame->view())->jsAlert(url, message); 249} 250 251bool ChromeClientAndroid::runJavaScriptConfirm(Frame* frame, const String& message) 252{ 253 String url = frame->document()->documentURI(); 254 255 return android::WebViewCore::getWebViewCore(frame->view())->jsConfirm(url, message); 256} 257 258/* This function is called for the javascript method Window.prompt(). A dialog should be shown on 259 * the screen with an input put box. First param is the text, the second is the default value for 260 * the input box, third is return param. If the function returns true, the value set in the third parameter 261 * is provided to javascript, else null is returned to the script. 262 */ 263bool ChromeClientAndroid::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result) 264{ 265 String url = frame->document()->documentURI(); 266 return android::WebViewCore::getWebViewCore(frame->view())->jsPrompt(url, message, defaultValue, result); 267} 268void ChromeClientAndroid::setStatusbarText(const String&) { notImplemented(); } 269 270// This is called by the JavaScript interpreter when a script has been running for a long 271// time. A dialog should be shown to the user asking them if they would like to cancel the 272// Javascript. If true is returned, the script is cancelled. 273// To make a device more responsive, we default to return true to disallow long running script. 274// This implies that some of scripts will not be completed. 275bool ChromeClientAndroid::shouldInterruptJavaScript() { 276 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 277 return android::WebViewCore::getWebViewCore(frameView)->jsInterrupt(); 278} 279 280bool ChromeClientAndroid::tabsToLinks() const { return false; } 281 282IntRect ChromeClientAndroid::windowResizerRect() const { return IntRect(0, 0, 0, 0); } 283 284void ChromeClientAndroid::invalidateWindow(const IntRect&, bool) 285{ 286 notImplemented(); 287} 288 289void ChromeClientAndroid::invalidateContentsAndWindow(const IntRect& updateRect, bool /*immediate*/) 290{ 291 notImplemented(); 292} 293 294void ChromeClientAndroid::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate) 295{ 296 notImplemented(); 297} 298 299// new to change 38068 (Nov 6, 2008) 300void ChromeClientAndroid::scroll(const IntSize& scrollDelta, 301 const IntRect& rectToScroll, const IntRect& clipRect) { 302 notImplemented(); 303} 304 305// new to change 38068 (Nov 6, 2008) 306IntPoint ChromeClientAndroid::screenToWindow(const IntPoint&) const { 307 notImplemented(); 308 return IntPoint(); 309} 310 311// new to change 38068 (Nov 6, 2008) 312IntRect ChromeClientAndroid::windowToScreen(const IntRect&) const { 313 notImplemented(); 314 return IntRect(); 315} 316 317PlatformPageClient ChromeClientAndroid::platformPageClient() const { 318 Page* page = m_webFrame->page(); 319 Frame* mainFrame = page->mainFrame(); 320 FrameView* view = mainFrame->view(); 321 PlatformWidget viewBridge = view->platformWidget(); 322 return viewBridge; 323} 324 325void ChromeClientAndroid::contentsSizeChanged(Frame*, const IntSize&) const 326{ 327 notImplemented(); 328} 329 330void ChromeClientAndroid::scrollRectIntoView(const IntRect&, const ScrollView*) const 331{ 332 notImplemented(); 333} 334 335void ChromeClientAndroid::formStateDidChange(const Node*) 336{ 337 notImplemented(); 338} 339 340void ChromeClientAndroid::scrollbarsModeDidChange() const 341{ 342 notImplemented(); 343} 344 345void ChromeClientAndroid::mouseDidMoveOverElement(const HitTestResult&, unsigned int) {} 346void ChromeClientAndroid::setToolTip(const String&, TextDirection) {} 347void ChromeClientAndroid::print(Frame*) {} 348 349/* 350 * This function is called on the main (webcore) thread by SQLTransaction::deliverQuotaIncreaseCallback. 351 * The way that the callback mechanism is designed inside SQLTransaction means that there must be a new quota 352 * (which may be equal to the old quota if the user did not allow more quota) when this function returns. As 353 * we call into the browser thread to ask what to do with the quota, we block here and get woken up when the 354 * browser calls the native WebViewCore::SetDatabaseQuota method with the new quota value. 355 */ 356#if ENABLE(DATABASE) 357void ChromeClientAndroid::exceededDatabaseQuota(Frame* frame, const String& name) 358{ 359 SecurityOrigin* origin = frame->document()->securityOrigin(); 360 DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); 361 362 // 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. 363 m_newQuota = -1; 364 365 // This origin is being tracked and has exceeded it's quota. Call into 366 // the Java side of things to inform the user. 367 unsigned long long currentQuota = 0; 368 if (tracker.hasEntryForOrigin(origin)) 369 currentQuota = tracker.quotaForOrigin(origin); 370 371 unsigned long long estimatedSize = 0; 372 373 // Only update estimatedSize if we are trying to create a a new database, i.e. the usage for the database is 0. 374 if (tracker.usageForDatabase(name, origin) == 0) 375 estimatedSize = tracker.detailsForNameAndOrigin(name, origin).expectedUsage(); 376 377 android::WebViewCore::getWebViewCore(frame->view())->exceededDatabaseQuota(frame->document()->documentURI(), name, currentQuota, estimatedSize); 378 379 // We've sent notification to the browser so now wait for it to come back. 380 m_quotaThreadLock.lock(); 381 while (m_newQuota == -1) { 382 m_quotaThreadCondition.wait(m_quotaThreadLock); 383 } 384 m_quotaThreadLock.unlock(); 385 386 // 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. 387 // If we find such a site, shrink it's quota and ask Java to try again. 388 389 if ((unsigned long long) m_newQuota == currentQuota && !m_triedToReclaimDBQuota) { 390 m_triedToReclaimDBQuota = true; // we should only try this once per quota overflow. 391 unsigned long long reclaimedQuotaBytes = tryToReclaimDatabaseQuota(origin); 392 393 // If we were able to free up enough space, try asking Java again. 394 // Otherwise, give up and deny the new database. :( 395 if (reclaimedQuotaBytes >= estimatedSize) { 396 exceededDatabaseQuota(frame, name); 397 return; 398 } 399 } 400 401 // Update the DatabaseTracker with the new quota value (if the user declined 402 // new quota, this may equal the old quota) 403 tracker.setQuota(origin, m_newQuota); 404 m_triedToReclaimDBQuota = false; 405} 406 407static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota) { 408 DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); 409 Vector<RefPtr<SecurityOrigin> > origins; 410 tracker.origins(origins); 411 unsigned long long reclaimedQuotaBytes = 0; 412 for (unsigned i = 0; i < origins.size(); i++) { 413 SecurityOrigin* originToReclaimFrom = origins[i].get(); 414 415 // Don't try to reclaim from the origin that has exceeded its quota. 416 if (originToReclaimFrom->equal(originNeedingQuota)) 417 continue; 418 419 unsigned long long originUsage = tracker.usageForOrigin(originToReclaimFrom); 420 unsigned long long originQuota = tracker.quotaForOrigin(originToReclaimFrom); 421 // If the origin has a quota that is more than it's current usage +1MB, shrink it. 422 static const int ONE_MB = 1 * 1024 * 1024; 423 if (originUsage + ONE_MB < originQuota) { 424 unsigned long long newQuota = originUsage + ONE_MB; 425 tracker.setQuota(originToReclaimFrom, newQuota); 426 reclaimedQuotaBytes += originQuota - newQuota; 427 } 428 } 429 return reclaimedQuotaBytes; 430} 431#endif 432 433#if ENABLE(OFFLINE_WEB_APPLICATIONS) 434void ChromeClientAndroid::reachedMaxAppCacheSize(int64_t spaceNeeded) 435{ 436 // Set m_newQuota before calling into the Java side. If we do this after, 437 // we could overwrite the result passed from the Java side and deadlock in the 438 // wait call below. 439 m_newQuota = -1; 440 Page* page = m_webFrame->page(); 441 Frame* mainFrame = page->mainFrame(); 442 FrameView* view = mainFrame->view(); 443 android::WebViewCore::getWebViewCore(view)->reachedMaxAppCacheSize(spaceNeeded); 444 445 // We've sent notification to the browser so now wait for it to come back. 446 m_quotaThreadLock.lock(); 447 while (m_newQuota == -1) { 448 m_quotaThreadCondition.wait(m_quotaThreadLock); 449 } 450 m_quotaThreadLock.unlock(); 451 if (m_newQuota > 0) { 452 WebCore::cacheStorage().setMaximumSize(m_newQuota); 453 // Now the app cache will retry the saving the previously failed cache. 454 } 455} 456#endif 457 458void ChromeClientAndroid::populateVisitedLinks() 459{ 460 Page* page = m_webFrame->page(); 461 Frame* mainFrame = page->mainFrame(); 462 FrameView* view = mainFrame->view(); 463 android::WebViewCore::getWebViewCore(view)->populateVisitedLinks(&page->group()); 464} 465 466void ChromeClientAndroid::requestGeolocationPermissionForFrame(Frame* frame, Geolocation* geolocation) 467{ 468 ASSERT(geolocation); 469 if (!m_geolocationPermissions) { 470 m_geolocationPermissions = new GeolocationPermissions(android::WebViewCore::getWebViewCore(frame->view()), 471 m_webFrame->page()->mainFrame()); 472 } 473 m_geolocationPermissions->queryPermissionState(frame); 474} 475 476void ChromeClientAndroid::cancelGeolocationPermissionRequestForFrame(Frame* frame, WebCore::Geolocation*) 477{ 478 if (m_geolocationPermissions) 479 m_geolocationPermissions->cancelPermissionStateQuery(frame); 480} 481 482void ChromeClientAndroid::provideGeolocationPermissions(const String &origin, bool allow, bool remember) 483{ 484 ASSERT(m_geolocationPermissions); 485 m_geolocationPermissions->providePermissionState(origin, allow, remember); 486} 487 488void ChromeClientAndroid::storeGeolocationPermissions() 489{ 490 GeolocationPermissions::maybeStorePermanentPermissions(); 491} 492 493void ChromeClientAndroid::onMainFrameLoadStarted() 494{ 495 if (m_geolocationPermissions.get()) 496 m_geolocationPermissions->resetTemporaryPermissionStates(); 497} 498 499void ChromeClientAndroid::runOpenPanel(Frame* frame, 500 PassRefPtr<FileChooser> chooser) 501{ 502 android::WebViewCore* core = android::WebViewCore::getWebViewCore( 503 frame->view()); 504 core->openFileChooser(chooser); 505} 506 507void ChromeClientAndroid::chooseIconForFiles(const Vector<WTF::String>&, FileChooser*) 508{ 509 notImplemented(); 510} 511 512void ChromeClientAndroid::setCursor(const Cursor&) 513{ 514 notImplemented(); 515} 516 517void ChromeClientAndroid::wakeUpMainThreadWithNewQuota(long newQuota) { 518 MutexLocker locker(m_quotaThreadLock); 519 m_newQuota = newQuota; 520 m_quotaThreadCondition.signal(); 521} 522 523#if ENABLE(TOUCH_EVENTS) 524void ChromeClientAndroid::needTouchEvents(bool needTouchEvents) 525{ 526 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 527 android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); 528 if (core) 529 core->needTouchEvents(needTouchEvents); 530} 531#endif 532 533bool ChromeClientAndroid::selectItemWritingDirectionIsNatural() 534{ 535 return false; 536} 537 538PassRefPtr<PopupMenu> ChromeClientAndroid::createPopupMenu(PopupMenuClient* client) const 539{ 540 return adoptRef(new PopupMenuAndroid(client)); 541} 542 543PassRefPtr<SearchPopupMenu> ChromeClientAndroid::createSearchPopupMenu(PopupMenuClient*) const 544{ 545 return adoptRef(new SearchPopupMenuAndroid); 546} 547 548void ChromeClientAndroid::reachedApplicationCacheOriginQuota(SecurityOrigin*) 549{ 550 notImplemented(); 551} 552 553#if ENABLE(ANDROID_INSTALLABLE_WEB_APPS) 554void ChromeClientAndroid::webAppCanBeInstalled() 555{ 556 FrameView* frameView = m_webFrame->page()->mainFrame()->view(); 557 android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); 558 if (core) 559 core->notifyWebAppCanBeInstalled(); 560} 561#endif 562 563} 564