diff options
Diffstat (limited to 'Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp')
-rw-r--r-- | Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp new file mode 100644 index 0000000..6f872b8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp @@ -0,0 +1,605 @@ +/* + * 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 "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "Icon.h" +#include "LayerAndroid.h" +#include "Page.h" +#include "PopupMenuAndroid.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 <wtf/text/CString.h> + +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) { + if (FrameView* frameView = m_webFrame->page()->mainFrame()->view()) + frameView->syncCompositingStateRecursive(); + } + 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->layersDraw(); +} + +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) { notImplemented(); return false; } +void ChromeClientAndroid::takeFocus(FocusDirection) { notImplemented(); } + +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 + + const WebCoreViewBridge* bridge = frame->view()->platformWidget(); + bool dialog = features.dialog || !features.resizable + || (features.heightSet && features.height < bridge->height() + && features.widthSet && features.width < bridge->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) +{ + android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->formDidBlur(node); +} + +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(); +} + +bool ChromeClientAndroid::tabsToLinks() const { return false; } + +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::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(); + + // 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. + m_newQuota = -1; + + // 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(); + + 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_newQuota == -1) { + m_quotaThreadCondition.wait(m_quotaThreadLock); + } + m_quotaThreadLock.unlock(); + + // 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 ((unsigned long long) 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<RefPtr<SecurityOrigin> > 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) +{ + // Set m_newQuota before calling into the Java side. If we do this after, + // we could overwrite the result passed from the Java side and deadlock in the + // wait call below. + m_newQuota = -1; + Page* page = m_webFrame->page(); + Frame* mainFrame = page->mainFrame(); + FrameView* view = mainFrame->view(); + android::WebViewCore::getWebViewCore(view)->reachedMaxAppCacheSize(spaceNeeded); + + // We've sent notification to the browser so now wait for it to come back. + m_quotaThreadLock.lock(); + while (m_newQuota == -1) { + 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::requestGeolocationPermissionForFrame(Frame* frame, Geolocation* geolocation) +{ + ASSERT(geolocation); + if (!m_geolocationPermissions) { + m_geolocationPermissions = new GeolocationPermissions(android::WebViewCore::getWebViewCore(frame->view()), + m_webFrame->page()->mainFrame()); + } + m_geolocationPermissions->queryPermissionState(frame); +} + +void ChromeClientAndroid::cancelGeolocationPermissionRequestForFrame(Frame* frame, WebCore::Geolocation*) +{ + if (m_geolocationPermissions) + m_geolocationPermissions->cancelPermissionStateQuery(frame); +} + +void ChromeClientAndroid::provideGeolocationPermissions(const String &origin, bool allow, bool remember) +{ + ASSERT(m_geolocationPermissions); + m_geolocationPermissions->providePermissionState(origin, allow, remember); +} + +void ChromeClientAndroid::storeGeolocationPermissions() +{ + GeolocationPermissions::maybeStorePermanentPermissions(); +} + +void ChromeClientAndroid::onMainFrameLoadStarted() +{ + if (m_geolocationPermissions.get()) + m_geolocationPermissions->resetTemporaryPermissionStates(); +} + +void ChromeClientAndroid::runOpenPanel(Frame* frame, + PassRefPtr<FileChooser> chooser) +{ + android::WebViewCore* core = android::WebViewCore::getWebViewCore( + frame->view()); + core->openFileChooser(chooser); +} + +void ChromeClientAndroid::chooseIconForFiles(const Vector<WTF::String>&, FileChooser*) +{ + notImplemented(); +} + +void ChromeClientAndroid::setCursor(const Cursor&) +{ + notImplemented(); +} + +void ChromeClientAndroid::wakeUpMainThreadWithNewQuota(long newQuota) { + MutexLocker locker(m_quotaThreadLock); + m_newQuota = newQuota; + 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; +} + +PassRefPtr<PopupMenu> ChromeClientAndroid::createPopupMenu(PopupMenuClient* client) const +{ + return adoptRef(new PopupMenuAndroid(static_cast<ListPopupMenuClient*>(client))); +} + +PassRefPtr<SearchPopupMenu> ChromeClientAndroid::createSearchPopupMenu(PopupMenuClient*) const +{ + return adoptRef(new SearchPopupMenuAndroid); +} + +void ChromeClientAndroid::reachedApplicationCacheOriginQuota(SecurityOrigin*) +{ + notImplemented(); +} + +#if ENABLE(ANDROID_INSTALLABLE_WEB_APPS) +void ChromeClientAndroid::webAppCanBeInstalled() +{ + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); + if (core) + core->notifyWebAppCanBeInstalled(); +} +#endif + +#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<HTMLMediaElement*>(node); + String url = videoElement->currentSrc(); + LayerAndroid* layer = videoElement->platformLayer(); + if (!layer) + return; + + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); + m_webFrame->page()->mainFrame()->document()->webkitWillEnterFullScreenForElement(videoElement); + if (core) + core->enterFullscreenForVideoLayer(layer->uniqueId(), url); +} + +void ChromeClientAndroid::exitFullscreenForNode(Node* node) +{ +} +#endif + +#if ENABLE(FULLSCREEN_API) +void ChromeClientAndroid::exitFullScreenForElement(Element* element) +{ + if (!element) + return; + + HTMLMediaElement* videoElement = static_cast<HTMLMediaElement*>(element); + videoElement->exitFullscreen(); +} +#endif + +} |