/* * Copyright 2007, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "WebCore" #include "config.h" #include "ApplicationCacheStorage.h" #include "ChromeClientAndroid.h" #include "DatabaseTracker.h" #include "Document.h" #include "PlatformString.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameView.h" #include "Geolocation.h" #include "GraphicsLayerAndroid.h" #include "HTMLMediaElement.h" #include "HTMLNames.h" #include "Icon.h" #include "LayerAndroid.h" #include "Page.h" #include "PopupMenuAndroid.h" #include "RenderLayer.h" #include "RenderLayerCompositor.h" #include "ScriptController.h" #include "SearchPopupMenuAndroid.h" #include "WebCoreFrameBridge.h" #include "WebCoreViewBridge.h" #include "WebViewCore.h" #include "WindowFeatures.h" #include "Settings.h" #include "UserGestureIndicator.h" #include namespace android { #if ENABLE(DATABASE) static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota); #endif #if USE(ACCELERATED_COMPOSITING) WebCore::GraphicsLayer* ChromeClientAndroid::layersSync() { if (m_rootGraphicsLayer && m_needsLayerSync && m_webFrame) { // We may have more than one frame, so let's first update all of them // (webkit may want to update the GraphicsLayer tree, and we do *not* want // to find this out when we are painting, as it means we could be summarily // deallocated while painting...) GraphicsLayerAndroid* rootLayer = static_cast(m_rootGraphicsLayer); Vector listRootLayers; rootLayer->gatherRootLayers(listRootLayers); for (unsigned int i = 0; i < listRootLayers.size(); i++) { RenderLayer* layer = const_cast(listRootLayers[i]); layer->compositor()->updateCompositingLayers(); } Frame* frame = m_webFrame->page()->mainFrame(); if (FrameView* frameView = frame->view()) frameView->syncCompositingStateIncludingSubframes(); } m_needsLayerSync = false; return m_rootGraphicsLayer; } void ChromeClientAndroid::scheduleCompositingLayerSync() { if (m_needsLayerSync) return; m_needsLayerSync = true; WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view()); if (webViewCore) webViewCore->contentDraw(); } void ChromeClientAndroid::setNeedsOneShotDrawingSynchronization() { // This should not be needed } void ChromeClientAndroid::attachRootGraphicsLayer(WebCore::Frame*, WebCore::GraphicsLayer* layer) { // frame is not used in Android as we should only get root graphics layer for the main frame m_rootGraphicsLayer = layer; if (!layer) return; scheduleCompositingLayerSync(); } #endif void ChromeClientAndroid::setWebFrame(android::WebFrame* webframe) { Release(m_webFrame); m_webFrame = webframe; Retain(m_webFrame); } void ChromeClientAndroid::chromeDestroyed() { Release(m_webFrame); delete this; } void ChromeClientAndroid::setWindowRect(const FloatRect&) { notImplemented(); } FloatRect ChromeClientAndroid::windowRect() { ASSERT(m_webFrame); if (!m_webFrame) return FloatRect(); FrameView* frameView = m_webFrame->page()->mainFrame()->view(); if (!frameView) return FloatRect(); const WebCoreViewBridge* bridge = frameView->platformWidget(); const IntRect& rect = bridge->getWindowBounds(); FloatRect fRect(rect.x(), rect.y(), rect.width(), rect.height()); return fRect; } FloatRect ChromeClientAndroid::pageRect() { notImplemented(); return FloatRect(); } float ChromeClientAndroid::scaleFactor() { ASSERT(m_webFrame); return m_webFrame->density(); } void ChromeClientAndroid::focus() { ASSERT(m_webFrame); bool isUserGesture = UserGestureIndicator::processingUserGesture(); // Ask the application to focus this WebView if the action is intiated by the user if (isUserGesture) m_webFrame->requestFocus(); } void ChromeClientAndroid::unfocus() { notImplemented(); } bool ChromeClientAndroid::canTakeFocus(FocusDirection direction) { return android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->chromeCanTakeFocus(direction); } void ChromeClientAndroid::takeFocus(FocusDirection direction) { android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->chromeTakeFocus(direction); } void ChromeClientAndroid::focusedNodeChanged(Node* node) { android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->focusNodeChanged(node); } void ChromeClientAndroid::focusedFrameChanged(Frame*) { notImplemented(); } Page* ChromeClientAndroid::createWindow(Frame* frame, const FrameLoadRequest&, const WindowFeatures& features, const NavigationAction&) { ASSERT(frame); #ifdef ANDROID_MULTIPLE_WINDOWS if (frame->settings() && !(frame->settings()->supportMultipleWindows())) // If the client doesn't support multiple windows, just return the current page return frame->page(); #endif FloatRect window = windowRect(); bool dialog = features.dialog || !features.resizable || (features.heightSet && features.height < window.height() && features.widthSet && features.width < window.width()) || (!features.menuBarVisible && !features.statusBarVisible && !features.toolBarVisible && !features.locationBarVisible && !features.scrollbarsVisible); // fullscreen definitely means no dialog if (features.fullscreen) dialog = false; WebCore::Frame* newFrame = m_webFrame->createWindow(dialog, ScriptController::processingUserGesture()); if (newFrame) { WebCore::Page* page = newFrame->page(); page->setGroupName(frame->page()->groupName()); return page; } return NULL; } void ChromeClientAndroid::show() { notImplemented(); } bool ChromeClientAndroid::canRunModal() { notImplemented(); return false; } void ChromeClientAndroid::runModal() { notImplemented(); } void ChromeClientAndroid::setToolbarsVisible(bool) { notImplemented(); } bool ChromeClientAndroid::toolbarsVisible() { notImplemented(); return false; } void ChromeClientAndroid::setStatusbarVisible(bool) { notImplemented(); } bool ChromeClientAndroid::statusbarVisible() { notImplemented(); return false; } void ChromeClientAndroid::setScrollbarsVisible(bool) { notImplemented(); } bool ChromeClientAndroid::scrollbarsVisible() { notImplemented(); return false; } void ChromeClientAndroid::setMenubarVisible(bool) { notImplemented(); } bool ChromeClientAndroid::menubarVisible() { notImplemented(); return false; } void ChromeClientAndroid::setResizable(bool) { notImplemented(); } #if ENABLE(CONTEXT_MENUS) void ChromeClientAndroid::showContextMenu() { notImplemented(); } #endif // This function is called by the JavaScript bindings to print usually an error to // a message console. Pass the message to the java side so that the client can // handle it as it sees fit. void ChromeClientAndroid::addMessageToConsole(MessageSource, MessageType, MessageLevel msgLevel, const String& message, unsigned int lineNumber, const String& sourceID) { android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->addMessageToConsole(message, lineNumber, sourceID, msgLevel); } void ChromeClientAndroid::formDidBlur(const WebCore::Node* node) { notImplemented(); } bool ChromeClientAndroid::canRunBeforeUnloadConfirmPanel() { return true; } bool ChromeClientAndroid::runBeforeUnloadConfirmPanel(const String& message, Frame* frame) { String url = frame->document()->documentURI(); return android::WebViewCore::getWebViewCore(frame->view())->jsUnload(url, message); } void ChromeClientAndroid::closeWindowSoon() { ASSERT(m_webFrame); Page* page = m_webFrame->page(); Frame* mainFrame = page->mainFrame(); // This will prevent javascript cross-scripting during unload page->setGroupName(String()); // Stop loading but do not send the unload event mainFrame->loader()->stopLoading(UnloadEventPolicyNone); // Cancel all pending loaders mainFrame->loader()->stopAllLoaders(); // Remove all event listeners so that no javascript can execute as a result // of mouse/keyboard events. mainFrame->document()->removeAllEventListeners(); // Close the window. m_webFrame->closeWindow(android::WebViewCore::getWebViewCore(mainFrame->view())); } void ChromeClientAndroid::runJavaScriptAlert(Frame* frame, const String& message) { String url = frame->document()->documentURI(); android::WebViewCore::getWebViewCore(frame->view())->jsAlert(url, message); } bool ChromeClientAndroid::runJavaScriptConfirm(Frame* frame, const String& message) { String url = frame->document()->documentURI(); return android::WebViewCore::getWebViewCore(frame->view())->jsConfirm(url, message); } /* This function is called for the javascript method Window.prompt(). A dialog should be shown on * the screen with an input put box. First param is the text, the second is the default value for * the input box, third is return param. If the function returns true, the value set in the third parameter * is provided to javascript, else null is returned to the script. */ bool ChromeClientAndroid::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result) { String url = frame->document()->documentURI(); return android::WebViewCore::getWebViewCore(frame->view())->jsPrompt(url, message, defaultValue, result); } void ChromeClientAndroid::setStatusbarText(const String&) { notImplemented(); } // This is called by the JavaScript interpreter when a script has been running for a long // time. A dialog should be shown to the user asking them if they would like to cancel the // Javascript. If true is returned, the script is cancelled. // To make a device more responsive, we default to return true to disallow long running script. // This implies that some of scripts will not be completed. bool ChromeClientAndroid::shouldInterruptJavaScript() { FrameView* frameView = m_webFrame->page()->mainFrame()->view(); return android::WebViewCore::getWebViewCore(frameView)->jsInterrupt(); } KeyboardUIMode ChromeClientAndroid::keyboardUIMode() { return KeyboardAccessTabsToLinks; } IntRect ChromeClientAndroid::windowResizerRect() const { return IntRect(0, 0, 0, 0); } void ChromeClientAndroid::invalidateWindow(const IntRect&, bool) { notImplemented(); } void ChromeClientAndroid::invalidateContentsAndWindow(const IntRect& updateRect, bool /*immediate*/) { notImplemented(); } void ChromeClientAndroid::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate) { notImplemented(); } // new to change 38068 (Nov 6, 2008) void ChromeClientAndroid::scroll(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect) { notImplemented(); } // new to change 38068 (Nov 6, 2008) IntPoint ChromeClientAndroid::screenToWindow(const IntPoint&) const { notImplemented(); return IntPoint(); } // new to change 38068 (Nov 6, 2008) IntRect ChromeClientAndroid::windowToScreen(const IntRect&) const { notImplemented(); return IntRect(); } PlatformPageClient ChromeClientAndroid::platformPageClient() const { Page* page = m_webFrame->page(); Frame* mainFrame = page->mainFrame(); FrameView* view = mainFrame->view(); PlatformWidget viewBridge = view->platformWidget(); return viewBridge; } void ChromeClientAndroid::contentsSizeChanged(Frame*, const IntSize&) const { notImplemented(); } void ChromeClientAndroid::scrollRectIntoView(const IntRect&, const ScrollView*) const { notImplemented(); } void ChromeClientAndroid::formStateDidChange(const Node*) { notImplemented(); } void ChromeClientAndroid::scrollbarsModeDidChange() const { notImplemented(); } void ChromeClientAndroid::dispatchViewportDataDidChange(const ViewportArguments& input) const { #ifdef ANDROID_META_SUPPORT const ViewportArguments emptyArgument; if (input == emptyArgument) { // Empty Argument is for a page with no viewport meta tag; so reset everything. m_webFrame->page()->settings()->resetMetadataSettings(); } Document* doc = m_webFrame->page()->mainFrame()->document(); if (!doc->ownerElement()) { FrameView* view = doc->view(); if (view) PlatformBridge::updateViewport(view); } #endif } void ChromeClientAndroid::mouseDidMoveOverElement(const HitTestResult&, unsigned int) {} void ChromeClientAndroid::setToolTip(const String&, TextDirection) {} void ChromeClientAndroid::print(Frame*) {} /* * This function is called on the main (webcore) thread by SQLTransaction::deliverQuotaIncreaseCallback. * The way that the callback mechanism is designed inside SQLTransaction means that there must be a new quota * (which may be equal to the old quota if the user did not allow more quota) when this function returns. As * we call into the browser thread to ask what to do with the quota, we block here and get woken up when the * browser calls the native WebViewCore::SetDatabaseQuota method with the new quota value. */ #if ENABLE(DATABASE) void ChromeClientAndroid::exceededDatabaseQuota(Frame* frame, const String& name) { SecurityOrigin* origin = frame->document()->securityOrigin(); DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); m_isNewQuotaSet = false; // This origin is being tracked and has exceeded it's quota. Call into // the Java side of things to inform the user. unsigned long long currentQuota = 0; if (tracker.hasEntryForOrigin(origin)) currentQuota = tracker.quotaForOrigin(origin); unsigned long long estimatedSize = 0; // Only update estimatedSize if we are trying to create a a new database, i.e. the usage for the database is 0. if (tracker.usageForDatabase(name, origin) == 0) estimatedSize = tracker.detailsForNameAndOrigin(name, origin).expectedUsage(); if (android::WebViewCore::getWebViewCore(frame->view())->exceededDatabaseQuota(frame->document()->documentURI(), name, currentQuota, estimatedSize)) { // We've sent notification to the browser so now wait for it to come back. m_quotaThreadLock.lock(); while (!m_isNewQuotaSet) { m_quotaThreadCondition.wait(m_quotaThreadLock); } m_quotaThreadLock.unlock(); } else { // We failed to send the message to the UI thread to request a new quota, // so just use the current quota as a default. m_newQuota = currentQuota; } if (m_newQuota < currentQuota) m_newQuota = currentQuota; // 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. If we find such a site, shrink it's quota and ask Java to try // again. if (m_newQuota == currentQuota && !m_triedToReclaimDBQuota) { m_triedToReclaimDBQuota = true; // we should only try this once per quota overflow. unsigned long long reclaimedQuotaBytes = tryToReclaimDatabaseQuota(origin); // If we were able to free up enough space, try asking Java again. // Otherwise, give up and deny the new database. :( if (reclaimedQuotaBytes >= estimatedSize) { exceededDatabaseQuota(frame, name); return; } } // Update the DatabaseTracker with the new quota value (if the user declined // new quota, this may equal the old quota) tracker.setQuota(origin, m_newQuota); m_triedToReclaimDBQuota = false; } static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota) { DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); Vector > origins; tracker.origins(origins); unsigned long long reclaimedQuotaBytes = 0; for (unsigned i = 0; i < origins.size(); i++) { SecurityOrigin* originToReclaimFrom = origins[i].get(); // Don't try to reclaim from the origin that has exceeded its quota. if (originToReclaimFrom->equal(originNeedingQuota)) continue; unsigned long long originUsage = tracker.usageForOrigin(originToReclaimFrom); unsigned long long originQuota = tracker.quotaForOrigin(originToReclaimFrom); // If the origin has a quota that is more than it's current usage +1MB, shrink it. static const int ONE_MB = 1 * 1024 * 1024; if (originUsage + ONE_MB < originQuota) { unsigned long long newQuota = originUsage + ONE_MB; tracker.setQuota(originToReclaimFrom, newQuota); reclaimedQuotaBytes += originQuota - newQuota; } } return reclaimedQuotaBytes; } #endif #if ENABLE(OFFLINE_WEB_APPLICATIONS) void ChromeClientAndroid::reachedMaxAppCacheSize(int64_t spaceNeeded) { m_isNewQuotaSet = false; Page* page = m_webFrame->page(); Frame* mainFrame = page->mainFrame(); FrameView* view = mainFrame->view(); // If we fail to send the message to the UI thread to request a new quota, // there's nothing to do. if (!android::WebViewCore::getWebViewCore(view)->reachedMaxAppCacheSize(spaceNeeded)) return; // We've sent notification to the browser so now wait for it to come back. m_quotaThreadLock.lock(); while (!m_isNewQuotaSet) { m_quotaThreadCondition.wait(m_quotaThreadLock); } m_quotaThreadLock.unlock(); if (m_newQuota > 0) { WebCore::cacheStorage().setMaximumSize(m_newQuota); // Now the app cache will retry the saving the previously failed cache. } } #endif void ChromeClientAndroid::populateVisitedLinks() { Page* page = m_webFrame->page(); Frame* mainFrame = page->mainFrame(); FrameView* view = mainFrame->view(); android::WebViewCore::getWebViewCore(view)->populateVisitedLinks(&page->group()); } void ChromeClientAndroid::runOpenPanel(Frame* frame, PassRefPtr chooser) { android::WebViewCore* core = android::WebViewCore::getWebViewCore( frame->view()); core->openFileChooser(chooser); } void ChromeClientAndroid::chooseIconForFiles(const Vector&, FileChooser*) { notImplemented(); } void ChromeClientAndroid::setCursor(const Cursor&) { notImplemented(); } void ChromeClientAndroid::wakeUpMainThreadWithNewQuota(long long newQuota) { MutexLocker locker(m_quotaThreadLock); m_newQuota = newQuota < 0 ? 0 : newQuota; m_isNewQuotaSet = true; m_quotaThreadCondition.signal(); } #if ENABLE(TOUCH_EVENTS) void ChromeClientAndroid::needTouchEvents(bool needTouchEvents) { FrameView* frameView = m_webFrame->page()->mainFrame()->view(); android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); if (core) core->needTouchEvents(needTouchEvents); } #endif bool ChromeClientAndroid::selectItemWritingDirectionIsNatural() { return false; } bool ChromeClientAndroid::selectItemAlignmentFollowsMenuWritingDirection() { return false; } PassRefPtr ChromeClientAndroid::createPopupMenu(PopupMenuClient* client) const { return adoptRef(new PopupMenuAndroid(static_cast(client))); } PassRefPtr ChromeClientAndroid::createSearchPopupMenu(PopupMenuClient*) const { return adoptRef(new SearchPopupMenuAndroid); } void ChromeClientAndroid::reachedApplicationCacheOriginQuota(SecurityOrigin*) { notImplemented(); } #if ENABLE(VIDEO) bool ChromeClientAndroid::supportsFullscreenForNode(const Node* node) { return node->hasTagName(HTMLNames::videoTag); } void ChromeClientAndroid::enterFullscreenForNode(Node* node) { if (!node->hasTagName(HTMLNames::videoTag)) return; HTMLMediaElement* videoElement = static_cast(node); FrameView* frameView = m_webFrame->page()->mainFrame()->view(); android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); if (core) core->enterFullscreenForVideoLayer(); MediaPlayer* player = videoElement->player(); if (player) { // We need to use the same document object as the // MediaPlayerPrivateAndroid::onStopFullscreen(). Document* doc = player->mediaPlayerClient()->mediaPlayerOwningDocument(); if (doc) doc->webkitWillEnterFullScreenForElement(videoElement); // Now the player is responsible to trigger to the java side for // entering full screen mode. player->enterFullscreenMode(); } } void ChromeClientAndroid::exitFullscreenForNode(Node* node) { FrameView* frameView = m_webFrame->page()->mainFrame()->view(); android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); if (core) core->exitFullscreenVideo(); return; } #endif #if ENABLE(FULLSCREEN_API) void ChromeClientAndroid::exitFullScreenForElement(Element* element) { if (!element) return; HTMLMediaElement* videoElement = static_cast(element); videoElement->exitFullscreen(); } #endif }