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