[chromium] Unreviewed gardening. Mark some XHR tests as asserting.
[WebKit-https.git] / Source / WebKit / gtk / WebCoreSupport / ChromeClientGtk.cpp
1 /*
2  * Copyright (C) 2007, 2008 Holger Hans Peter Freyther
3  * Copyright (C) 2007, 2008 Christian Dywan <christian@imendio.com>
4  * Copyright (C) 2008 Nuanti Ltd.
5  * Copyright (C) 2008 Alp Toker <alp@atoker.com>
6  * Copyright (C) 2008 Gustavo Noronha Silva <gns@gnome.org>
7  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8  * Copyright (C) 2012 Igalia S. L.
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23  */
24
25 #include "config.h"
26 #include "ChromeClientGtk.h"
27
28 #include "Chrome.h"
29 #include "Console.h"
30 #include "DumpRenderTreeSupportGtk.h"
31 #include "Element.h"
32 #include "FileChooser.h"
33 #include "FileIconLoader.h"
34 #include "FileSystem.h"
35 #include "FloatRect.h"
36 #include "FrameLoadRequest.h"
37 #include "FrameView.h"
38 #include "GtkUtilities.h"
39 #include "GtkVersioning.h"
40 #include "HTMLNames.h"
41 #include "HitTestResult.h"
42 #include "Icon.h"
43 #include "InspectorController.h"
44 #include "IntRect.h"
45 #include "KURL.h"
46 #include "NavigationAction.h"
47 #include "NotImplemented.h"
48 #include "PlatformString.h"
49 #include "PopupMenuClient.h"
50 #include "PopupMenuGtk.h"
51 #include "RefPtrCairo.h"
52 #include "SearchPopupMenuGtk.h"
53 #include "SecurityOrigin.h"
54 #include "WebKitDOMBinding.h"
55 #include "WebKitDOMHTMLElementPrivate.h"
56 #include "WindowFeatures.h"
57 #include "webkitgeolocationpolicydecision.h"
58 #include "webkitgeolocationpolicydecisionprivate.h"
59 #include "webkitnetworkrequest.h"
60 #include "webkitsecurityoriginprivate.h"
61 #include "webkitviewportattributesprivate.h"
62 #include "webkitwebframeprivate.h"
63 #include "webkitwebview.h"
64 #include "webkitwebviewprivate.h"
65 #include "webkitwebwindowfeaturesprivate.h"
66 #include <gdk/gdk.h>
67 #include <gdk/gdkkeysyms.h>
68 #include <glib.h>
69 #include <glib/gi18n-lib.h>
70 #include <gtk/gtk.h>
71 #include <wtf/MathExtras.h>
72 #include <wtf/text/CString.h>
73
74 #if ENABLE(SQL_DATABASE)
75 #include "DatabaseTracker.h"
76 #endif
77
78 using namespace WebCore;
79
80 namespace WebKit {
81
82 ChromeClient::ChromeClient(WebKitWebView* webView)
83     : m_webView(webView)
84     , m_adjustmentWatcher(webView)
85     , m_closeSoonTimer(0)
86     , m_displayTimer(this, &ChromeClient::paint)
87     , m_lastDisplayTime(0)
88     , m_repaintSoonSourceId(0)
89 {
90     ASSERT(m_webView);
91 }
92
93 void ChromeClient::chromeDestroyed()
94 {
95     if (m_closeSoonTimer)
96         g_source_remove(m_closeSoonTimer);
97
98     if (m_repaintSoonSourceId)
99         g_source_remove(m_repaintSoonSourceId);
100
101     delete this;
102 }
103
104 FloatRect ChromeClient::windowRect()
105 {
106     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
107     if (widgetIsOnscreenToplevelWindow(window)) {
108         gint left, top, width, height;
109         gtk_window_get_position(GTK_WINDOW(window), &left, &top);
110         gtk_window_get_size(GTK_WINDOW(window), &width, &height);
111         return IntRect(left, top, width, height);
112     }
113     return FloatRect();
114 }
115
116 void ChromeClient::setWindowRect(const FloatRect& rect)
117 {
118     IntRect intrect = IntRect(rect);
119     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
120
121     g_object_set(webWindowFeatures,
122                  "x", intrect.x(),
123                  "y", intrect.y(),
124                  "width", intrect.width(),
125                  "height", intrect.height(),
126                  NULL);
127
128     gboolean autoResizeWindow;
129     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
130     g_object_get(settings, "auto-resize-window", &autoResizeWindow, NULL);
131
132     if (!autoResizeWindow)
133         return;
134
135     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
136     if (widgetIsOnscreenToplevelWindow(window)) {
137         gtk_window_move(GTK_WINDOW(window), intrect.x(), intrect.y());
138         gtk_window_resize(GTK_WINDOW(window), intrect.width(), intrect.height());
139     }
140 }
141
142 FloatRect ChromeClient::pageRect()
143 {
144     GtkAllocation allocation;
145 #if GTK_CHECK_VERSION(2, 18, 0)
146     gtk_widget_get_allocation(GTK_WIDGET(m_webView), &allocation);
147 #else
148     allocation = GTK_WIDGET(m_webView)->allocation;
149 #endif
150     return IntRect(allocation.x, allocation.y, allocation.width, allocation.height);
151 }
152
153 void ChromeClient::focus()
154 {
155     gtk_widget_grab_focus(GTK_WIDGET(m_webView));
156 }
157
158 void ChromeClient::unfocus()
159 {
160     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
161     if (widgetIsOnscreenToplevelWindow(window))
162         gtk_window_set_focus(GTK_WINDOW(window), NULL);
163 }
164
165 Page* ChromeClient::createWindow(Frame* frame, const FrameLoadRequest& frameLoadRequest, const WindowFeatures& coreFeatures, const NavigationAction&)
166 {
167     WebKitWebView* webView = 0;
168
169     g_signal_emit_by_name(m_webView, "create-web-view", kit(frame), &webView);
170
171     if (!webView)
172         return 0;
173
174     GRefPtr<WebKitWebWindowFeatures> webWindowFeatures(adoptGRef(kitNew(coreFeatures)));
175     g_object_set(webView, "window-features", webWindowFeatures.get(), NULL);
176
177     return core(webView);
178 }
179
180 void ChromeClient::show()
181 {
182     webkit_web_view_notify_ready(m_webView);
183 }
184
185 bool ChromeClient::canRunModal()
186 {
187     notImplemented();
188     return false;
189 }
190
191 void ChromeClient::runModal()
192 {
193     notImplemented();
194 }
195
196 void ChromeClient::setToolbarsVisible(bool visible)
197 {
198     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
199
200     g_object_set(webWindowFeatures, "toolbar-visible", visible, NULL);
201 }
202
203 bool ChromeClient::toolbarsVisible()
204 {
205     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
206     gboolean visible;
207
208     g_object_get(webWindowFeatures, "toolbar-visible", &visible, NULL);
209     return visible;
210 }
211
212 void ChromeClient::setStatusbarVisible(bool visible)
213 {
214     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
215
216     g_object_set(webWindowFeatures, "statusbar-visible", visible, NULL);
217 }
218
219 bool ChromeClient::statusbarVisible()
220 {
221     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
222     gboolean visible;
223
224     g_object_get(webWindowFeatures, "statusbar-visible", &visible, NULL);
225     return visible;
226 }
227
228 void ChromeClient::setScrollbarsVisible(bool visible)
229 {
230     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
231
232     g_object_set(webWindowFeatures, "scrollbar-visible", visible, NULL);
233 }
234
235 bool ChromeClient::scrollbarsVisible()
236 {
237     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
238     gboolean visible;
239
240     g_object_get(webWindowFeatures, "scrollbar-visible", &visible, NULL);
241     return visible;
242 }
243
244 void ChromeClient::setMenubarVisible(bool visible)
245 {
246     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
247
248     g_object_set(webWindowFeatures, "menubar-visible", visible, NULL);
249 }
250
251 bool ChromeClient::menubarVisible()
252 {
253     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
254     gboolean visible;
255
256     g_object_get(webWindowFeatures, "menubar-visible", &visible, NULL);
257     return visible;
258 }
259
260 void ChromeClient::setResizable(bool)
261 {
262     // Ignored for now
263 }
264
265 static gboolean emitCloseWebViewSignalLater(WebKitWebView* view)
266 {
267     gboolean isHandled;
268     g_signal_emit_by_name(view, "close-web-view", &isHandled);
269     return FALSE;
270 }
271
272 void ChromeClient::closeWindowSoon()
273 {
274     // We may not have a WebView as create-web-view can return NULL.
275     if (!m_webView)
276         return;
277     if (m_closeSoonTimer) // Don't call close-web-view more than once.
278         return;
279
280     // We need to remove the parent WebView from WebViewSets here, before it actually
281     // closes, to make sure that JavaScript code that executes before it closes
282     // can't find it. Otherwise, window.open will select a closed WebView instead of 
283     // opening a new one <rdar://problem/3572585>.
284     m_webView->priv->corePage->setGroupName("");
285
286     // We also need to stop the load to prevent further parsing or JavaScript execution
287     // after the window has torn down <rdar://problem/4161660>.
288     webkit_web_view_stop_loading(m_webView);
289
290     // Clients commonly destroy the web view during the close-web-view signal, but our caller
291     // may need to send more signals to the web view. For instance, if this happened in the
292     // onload handler, it will need to call FrameLoaderClient::dispatchDidHandleOnloadEvents.
293     // Instead of firing the close-web-view signal now, fire it after the caller finishes.
294     // This seems to match the Mac/Windows port behavior.
295     m_closeSoonTimer = g_timeout_add(0, reinterpret_cast<GSourceFunc>(emitCloseWebViewSignalLater), m_webView);
296 }
297
298 bool ChromeClient::canTakeFocus(FocusDirection)
299 {
300     return gtk_widget_get_can_focus(GTK_WIDGET(m_webView));
301 }
302
303 void ChromeClient::takeFocus(FocusDirection)
304 {
305     unfocus();
306 }
307
308 void ChromeClient::focusedNodeChanged(Node*)
309 {
310 }
311
312 void ChromeClient::focusedFrameChanged(Frame*)
313 {
314 }
315
316 bool ChromeClient::canRunBeforeUnloadConfirmPanel()
317 {
318     return true;
319 }
320
321 bool ChromeClient::runBeforeUnloadConfirmPanel(const WTF::String& message, WebCore::Frame* frame)
322 {
323     return runJavaScriptConfirm(frame, message);
324 }
325
326 void ChromeClient::addMessageToConsole(WebCore::MessageSource source, WebCore::MessageType type, WebCore::MessageLevel level, const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceId)
327 {
328     gboolean retval;
329     g_signal_emit_by_name(m_webView, "console-message", message.utf8().data(), lineNumber, sourceId.utf8().data(), &retval);
330 }
331
332 void ChromeClient::runJavaScriptAlert(Frame* frame, const String& message)
333 {
334     gboolean retval;
335     g_signal_emit_by_name(m_webView, "script-alert", kit(frame), message.utf8().data(), &retval);
336 }
337
338 bool ChromeClient::runJavaScriptConfirm(Frame* frame, const String& message)
339 {
340     gboolean retval;
341     gboolean didConfirm;
342     g_signal_emit_by_name(m_webView, "script-confirm", kit(frame), message.utf8().data(), &didConfirm, &retval);
343     return didConfirm == TRUE;
344 }
345
346 bool ChromeClient::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result)
347 {
348     gboolean retval;
349     gchar* value = 0;
350     g_signal_emit_by_name(m_webView, "script-prompt", kit(frame), message.utf8().data(), defaultValue.utf8().data(), &value, &retval);
351     if (value) {
352         result = String::fromUTF8(value);
353         g_free(value);
354         return true;
355     }
356     return false;
357 }
358
359 void ChromeClient::setStatusbarText(const String& string)
360 {
361     CString stringMessage = string.utf8();
362     g_signal_emit_by_name(m_webView, "status-bar-text-changed", stringMessage.data());
363 }
364
365 bool ChromeClient::shouldInterruptJavaScript()
366 {
367     notImplemented();
368     return false;
369 }
370
371 KeyboardUIMode ChromeClient::keyboardUIMode()
372 {
373     bool tabsToLinks = true;
374     if (DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled())
375         tabsToLinks = DumpRenderTreeSupportGtk::linksIncludedInFocusChain();
376
377     return tabsToLinks ? KeyboardAccessTabsToLinks : KeyboardAccessDefault;
378 }
379
380 IntRect ChromeClient::windowResizerRect() const
381 {
382     notImplemented();
383     return IntRect();
384 }
385
386 #if ENABLE(REGISTER_PROTOCOL_HANDLER) 
387 void ChromeClient::registerProtocolHandler(const String& scheme, const String& baseURL, const String& url, const String& title) 
388
389     notImplemented(); 
390
391 #endif 
392
393 static gboolean repaintEverythingSoonTimeout(ChromeClient* client)
394 {
395     client->paint(0);
396     return FALSE;
397 }
398
399 static void clipOutOldWidgetArea(cairo_t* cr, const IntSize& oldSize, const IntSize& newSize)
400 {
401     cairo_move_to(cr, oldSize.width(), 0);
402     cairo_line_to(cr, newSize.width(), 0);
403     cairo_line_to(cr, newSize.width(), newSize.height());
404     cairo_line_to(cr, 0, newSize.height());
405     cairo_line_to(cr, 0, oldSize.height());
406     cairo_line_to(cr, oldSize.width(), oldSize.height());
407     cairo_close_path(cr);
408     cairo_clip(cr);
409 }
410
411 static void clearEverywhereInBackingStore(WebKitWebView* webView, cairo_t* cr)
412 {
413     // The strategy here is to quickly draw white into this new canvas, so that
414     // when a user quickly resizes the WebView in an environment that has opaque
415     // resizing (like Gnome Shell), there are no drawing artifacts.
416     if (!webView->priv->transparent) {
417         cairo_set_source_rgb(cr, 1, 1, 1);
418         cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
419     } else
420         cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
421     cairo_paint(cr);
422 }
423
424 void ChromeClient::widgetSizeChanged(const IntSize& oldWidgetSize, IntSize newSize)
425 {
426     WidgetBackingStore* backingStore = m_webView->priv->backingStore.get();
427
428     // Grow the backing store by at least 1.5 times the current size. This prevents
429     // lots of unnecessary allocations during an opaque resize.
430     if (backingStore) {
431         const IntSize& oldSize = backingStore->size();
432         if (newSize.width() > oldSize.width())
433             newSize.setWidth(std::max(newSize.width(), static_cast<int>(oldSize.width() * 1.5)));
434         if (newSize.height() > oldSize.height())
435             newSize.setHeight(std::max(newSize.height(), static_cast<int>(oldSize.height() * 1.5)));
436     }
437
438     // If we did not have a backing store before or if the backing store is growing, we need
439     // to reallocate a new one and set it up so that we don't see artifacts while resizing.
440     if (!backingStore
441         || newSize.width() > backingStore->size().width()
442         || newSize.height() > backingStore->size().height()) {
443
444         OwnPtr<WidgetBackingStore> newBackingStore =
445             WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), newSize);
446         RefPtr<cairo_t> cr = adoptRef(cairo_create(newBackingStore->cairoSurface()));
447
448         clearEverywhereInBackingStore(m_webView, cr.get());
449
450         // Now we copy the old backing store image over the new cleared surface to prevent
451         // annoying flashing as the widget grows. We do the "real" paint in a timeout
452         // since we don't want to block resizing too long.
453         if (backingStore) {
454             cairo_set_source_surface(cr.get(), backingStore->cairoSurface(), 0, 0);
455             cairo_rectangle(cr.get(), 0, 0, backingStore->size().width(), backingStore->size().height());
456             cairo_fill(cr.get());
457         }
458
459         m_webView->priv->backingStore = newBackingStore.release();
460         backingStore = m_webView->priv->backingStore.get();
461
462     } else if (oldWidgetSize.width() < newSize.width() || oldWidgetSize.height() < newSize.height()) {
463         // The widget is growing, but we did not need to create a new backing store.
464         // We should clear any old data outside of the old widget region.
465         RefPtr<cairo_t> cr = adoptRef(cairo_create(backingStore->cairoSurface()));
466         clipOutOldWidgetArea(cr.get(), oldWidgetSize, newSize);
467         clearEverywhereInBackingStore(m_webView, cr.get());
468     }
469
470     // We need to force a redraw and ignore the framerate cap.
471     m_lastDisplayTime = 0;
472     m_dirtyRegion.unite(IntRect(IntPoint(), backingStore->size()));
473
474     // WebCore timers by default have a lower priority which leads to more artifacts when opaque
475     // resize is on, thus we use g_timeout_add here to force a higher timeout priority.
476     if (!m_repaintSoonSourceId)
477         m_repaintSoonSourceId = g_timeout_add(0, reinterpret_cast<GSourceFunc>(repaintEverythingSoonTimeout), this);
478 }
479
480 static void coalesceRectsIfPossible(const IntRect& clipRect, Vector<IntRect>& rects)
481 {
482     const unsigned int cRectThreshold = 10;
483     const float cWastedSpaceThreshold = 0.75f;
484     bool useUnionedRect = (rects.size() <= 1) || (rects.size() > cRectThreshold);
485     if (!useUnionedRect) {
486         // Attempt to guess whether or not we should use the unioned rect or the individual rects.
487         // We do this by computing the percentage of "wasted space" in the union. If that wasted space
488         // is too large, then we will do individual rect painting instead.
489         float unionPixels = (clipRect.width() * clipRect.height());
490         float singlePixels = 0;
491         for (size_t i = 0; i < rects.size(); ++i)
492             singlePixels += rects[i].width() * rects[i].height();
493         float wastedSpace = 1 - (singlePixels / unionPixels);
494         if (wastedSpace <= cWastedSpaceThreshold)
495             useUnionedRect = true;
496     }
497
498     if (!useUnionedRect)
499         return;
500
501     rects.clear();
502     rects.append(clipRect);
503 }
504
505 static void paintWebView(WebKitWebView* webView, Frame* frame, Region dirtyRegion)
506 {
507     if (!webView->priv->backingStore)
508         return;
509
510     Vector<IntRect> rects = dirtyRegion.rects();
511     coalesceRectsIfPossible(dirtyRegion.bounds(), rects);
512
513     RefPtr<cairo_t> backingStoreContext = adoptRef(cairo_create(webView->priv->backingStore->cairoSurface()));
514     GraphicsContext gc(backingStoreContext.get());
515     for (size_t i = 0; i < rects.size(); i++) {
516         const IntRect& rect = rects[i];
517
518         gc.save();
519         gc.clip(rect);
520         if (webView->priv->transparent)
521             gc.clearRect(rect);
522         frame->view()->paint(&gc, rect);
523         gc.restore();
524     }
525
526     gc.save();
527     gc.clip(dirtyRegion.bounds());
528     frame->page()->inspectorController()->drawHighlight(gc);
529     gc.restore();
530 }
531
532 void ChromeClient::invalidateWidgetRect(const IntRect& rect)
533 {
534 #if USE(ACCELERATED_COMPOSITING)
535     AcceleratedCompositingContext* acContext = m_webView->priv->acceleratedCompositingContext.get();
536     if (acContext->enabled()) {
537         acContext->scheduleRootLayerRepaint(rect);
538         return;
539     }
540 #endif
541     gtk_widget_queue_draw_area(GTK_WIDGET(m_webView),
542                                rect.x(), rect.y(),
543                                rect.width(), rect.height());
544 }
545
546 void ChromeClient::performAllPendingScrolls()
547 {
548     if (!m_webView->priv->backingStore)
549         return;
550
551     // Scroll all pending scroll rects and invalidate those parts of the widget.
552     for (size_t i = 0; i < m_rectsToScroll.size(); i++) {
553         IntRect& scrollRect = m_rectsToScroll[i];
554         m_webView->priv->backingStore->scroll(scrollRect, m_scrollOffsets[i]);
555         invalidateWidgetRect(scrollRect);
556     }
557
558     m_rectsToScroll.clear();
559     m_scrollOffsets.clear();
560 }
561
562 void ChromeClient::paint(WebCore::Timer<ChromeClient>*)
563 {
564     static const double minimumFrameInterval = 1.0 / 60.0; // No more than 60 frames a second.
565     double timeSinceLastDisplay = currentTime() - m_lastDisplayTime;
566     double timeUntilNextDisplay = minimumFrameInterval - timeSinceLastDisplay;
567
568     if (timeUntilNextDisplay > 0) {
569         m_displayTimer.startOneShot(timeUntilNextDisplay);
570         return;
571     }
572
573     Frame* frame = core(m_webView)->mainFrame();
574     if (!frame || !frame->contentRenderer() || !frame->view())
575         return;
576
577     frame->view()->updateLayoutAndStyleIfNeededRecursive();
578     performAllPendingScrolls();
579     paintWebView(m_webView, frame, m_dirtyRegion);
580
581     HashSet<GtkWidget*> children = m_webView->priv->children;
582     HashSet<GtkWidget*>::const_iterator end = children.end();
583     for (HashSet<GtkWidget*>::const_iterator current = children.begin(); current != end; ++current) {
584         if (static_cast<GtkAllocation*>(g_object_get_data(G_OBJECT(*current), "delayed-allocation"))) {
585             gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_webView));
586             break;
587         }
588     }
589
590     const IntRect& rect = m_dirtyRegion.bounds();
591     invalidateWidgetRect(rect);
592
593 #if USE(ACCELERATED_COMPOSITING)
594     m_webView->priv->acceleratedCompositingContext->syncLayersNow();
595     m_webView->priv->acceleratedCompositingContext->renderLayersToWindow(rect);
596 #endif
597
598     m_dirtyRegion = Region();
599     m_lastDisplayTime = currentTime();
600     m_repaintSoonSourceId = 0;
601 }
602
603 void ChromeClient::invalidateRootView(const IntRect&, bool immediate)
604 {
605 }
606
607 void ChromeClient::invalidateContentsAndRootView(const IntRect& updateRect, bool immediate)
608 {
609     if (updateRect.isEmpty())
610         return;
611     m_dirtyRegion.unite(updateRect);
612     m_displayTimer.startOneShot(0);
613 }
614
615 void ChromeClient::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate)
616 {
617     invalidateContentsAndRootView(updateRect, immediate);
618     m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
619 }
620
621 void ChromeClient::scroll(const IntSize& delta, const IntRect& rectToScroll, const IntRect& clipRect)
622 {
623     m_rectsToScroll.append(rectToScroll);
624     m_scrollOffsets.append(delta);
625
626     // The code to calculate the scroll repaint region is originally from WebKit2.
627     // Get the part of the dirty region that is in the scroll rect.
628     Region dirtyRegionInScrollRect = intersect(rectToScroll, m_dirtyRegion);
629     if (!dirtyRegionInScrollRect.isEmpty()) {
630         // There are parts of the dirty region that are inside the scroll rect.
631         // We need to subtract them from the region, move them and re-add them.
632         m_dirtyRegion.subtract(rectToScroll);
633
634         // Move the dirty parts.
635         Region movedDirtyRegionInScrollRect = intersect(translate(dirtyRegionInScrollRect, delta), rectToScroll);
636
637         // And add them back.
638         m_dirtyRegion.unite(movedDirtyRegionInScrollRect);
639     }
640
641     // Compute the scroll repaint region.
642     Region scrollRepaintRegion = subtract(rectToScroll, translate(rectToScroll, delta));
643     m_dirtyRegion.unite(scrollRepaintRegion);
644     m_displayTimer.startOneShot(0);
645
646     m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
647 }
648
649 IntRect ChromeClient::rootViewToScreen(const IntRect& rect) const
650 {
651     return IntRect(convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), rect.location()), rect.size());
652 }
653
654 IntPoint ChromeClient::screenToRootView(const IntPoint& point) const
655 {
656     IntPoint widgetPositionOnScreen = convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), IntPoint());
657     IntPoint result(point);
658     result.move(-widgetPositionOnScreen.x(), -widgetPositionOnScreen.y());
659     return result;
660 }
661
662 PlatformPageClient ChromeClient::platformPageClient() const
663 {
664     return GTK_WIDGET(m_webView);
665 }
666
667 void ChromeClient::contentsSizeChanged(Frame* frame, const IntSize& size) const
668 {
669     if (m_adjustmentWatcher.scrollbarsDisabled())
670         return;
671
672     // We need to queue a resize request only if the size changed,
673     // otherwise we get into an infinite loop!
674     GtkWidget* widget = GTK_WIDGET(m_webView);
675     GtkRequisition requisition;
676 #if GTK_CHECK_VERSION(2, 20, 0)
677     gtk_widget_get_requisition(widget, &requisition);
678 #else
679     requisition = widget->requisition;
680 #endif
681     if (gtk_widget_get_realized(widget)
682         && (requisition.height != size.height())
683         || (requisition.width != size.width()))
684         gtk_widget_queue_resize_no_redraw(widget);
685
686     // If this was a main frame size change, update the scrollbars.
687     if (frame != frame->page()->mainFrame())
688         return;
689     m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
690 }
691
692 void ChromeClient::scrollbarsModeDidChange() const
693 {
694     WebKitWebFrame* webFrame = webkit_web_view_get_main_frame(m_webView);
695     if (!webFrame)
696         return;
697
698     g_object_notify(G_OBJECT(webFrame), "horizontal-scrollbar-policy");
699     g_object_notify(G_OBJECT(webFrame), "vertical-scrollbar-policy");
700
701     gboolean isHandled;
702     g_signal_emit_by_name(webFrame, "scrollbars-policy-changed", &isHandled);
703
704     if (isHandled)
705         return;
706
707     GtkWidget* parent = gtk_widget_get_parent(GTK_WIDGET(m_webView));
708     if (!parent || !GTK_IS_SCROLLED_WINDOW(parent))
709         return;
710
711     GtkPolicyType horizontalPolicy = webkit_web_frame_get_horizontal_scrollbar_policy(webFrame);
712     GtkPolicyType verticalPolicy = webkit_web_frame_get_vertical_scrollbar_policy(webFrame);
713
714     // ScrolledWindow doesn't like to display only part of a widget if
715     // the scrollbars are completely disabled; We have a disparity
716     // here on what the policy requested by the web app is and what we
717     // can represent; the idea is not to show scrollbars, only.
718     if (horizontalPolicy == GTK_POLICY_NEVER)
719         horizontalPolicy = GTK_POLICY_AUTOMATIC;
720
721     if (verticalPolicy == GTK_POLICY_NEVER)
722         verticalPolicy = GTK_POLICY_AUTOMATIC;
723
724     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(parent),
725                                    horizontalPolicy, verticalPolicy);
726 }
727
728 void ChromeClient::mouseDidMoveOverElement(const HitTestResult& hit, unsigned modifierFlags)
729 {
730     // check if the element is a link...
731     bool isLink = hit.isLiveLink();
732     if (isLink) {
733         KURL url = hit.absoluteLinkURL();
734         if (!url.isEmpty() && url != m_hoveredLinkURL) {
735             TextDirection dir;
736             CString titleString = hit.title(dir).utf8();
737             CString urlString = url.string().utf8();
738             g_signal_emit_by_name(m_webView, "hovering-over-link", titleString.data(), urlString.data());
739             m_hoveredLinkURL = url;
740         }
741     } else if (!isLink && !m_hoveredLinkURL.isEmpty()) {
742         g_signal_emit_by_name(m_webView, "hovering-over-link", 0, 0);
743         m_hoveredLinkURL = KURL();
744     }
745
746     if (Node* node = hit.innerNonSharedNode()) {
747         Frame* frame = node->document()->frame();
748         FrameView* view = frame ? frame->view() : 0;
749         m_webView->priv->tooltipArea = view ? view->contentsToWindow(node->getRect()) : IntRect();
750     } else
751         m_webView->priv->tooltipArea = IntRect();
752 }
753
754 void ChromeClient::setToolTip(const String& toolTip, TextDirection)
755 {
756     webkit_web_view_set_tooltip_text(m_webView, toolTip.utf8().data());
757 }
758
759 void ChromeClient::print(Frame* frame)
760 {
761     WebKitWebFrame* webFrame = kit(frame);
762     gboolean isHandled = false;
763     g_signal_emit_by_name(m_webView, "print-requested", webFrame, &isHandled);
764
765     if (isHandled)
766         return;
767
768     webkit_web_frame_print(webFrame);
769 }
770
771 #if ENABLE(SQL_DATABASE)
772 void ChromeClient::exceededDatabaseQuota(Frame* frame, const String& databaseName)
773 {
774     guint64 defaultQuota = webkit_get_default_web_database_quota();
775     DatabaseTracker::tracker().setQuota(frame->document()->securityOrigin(), defaultQuota);
776
777     WebKitWebFrame* webFrame = kit(frame);
778     WebKitSecurityOrigin* origin = webkit_web_frame_get_security_origin(webFrame);
779     WebKitWebDatabase* webDatabase = webkit_security_origin_get_web_database(origin, databaseName.utf8().data());
780     g_signal_emit_by_name(m_webView, "database-quota-exceeded", webFrame, webDatabase);
781 }
782 #endif
783
784 void ChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
785 {
786     // FIXME: Free some space.
787     notImplemented();
788 }
789
790 void ChromeClient::reachedApplicationCacheOriginQuota(SecurityOrigin*, int64_t)
791 {
792     notImplemented();
793 }
794
795 void ChromeClient::runOpenPanel(Frame*, PassRefPtr<FileChooser> prpFileChooser)
796 {
797     RefPtr<FileChooser> chooser = prpFileChooser;
798
799     GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
800     if (!widgetIsOnscreenToplevelWindow(toplevel))
801         toplevel = 0;
802
803     GtkWidget* dialog = gtk_file_chooser_dialog_new(_("Upload File"),
804                                                     toplevel ? GTK_WINDOW(toplevel) : 0,
805                                                     GTK_FILE_CHOOSER_ACTION_OPEN,
806                                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
807                                                     GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
808                                                     NULL);
809
810     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), chooser->settings().allowsMultipleFiles);
811
812     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
813         if (gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog))) {
814             GOwnPtr<GSList> filenames(gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)));
815             Vector<String> names;
816             for (GSList* item = filenames.get() ; item ; item = item->next) {
817                 if (!item->data)
818                     continue;
819                 names.append(filenameToString(static_cast<char*>(item->data)));
820                 g_free(item->data);
821             }
822             chooser->chooseFiles(names);
823         } else {
824             gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
825             if (filename)
826                 chooser->chooseFile(filenameToString(filename));
827             g_free(filename);
828         }
829     }
830     gtk_widget_destroy(dialog);
831 }
832
833 void ChromeClient::loadIconForFiles(const Vector<WTF::String>& filenames, WebCore::FileIconLoader* loader)
834 {
835     loader->notifyFinished(Icon::createIconForFiles(filenames));
836 }
837
838 void ChromeClient::dispatchViewportPropertiesDidChange(const ViewportArguments& arguments) const
839 {
840     // Recompute the viewport attributes making it valid.
841     webkitViewportAttributesRecompute(webkit_web_view_get_viewport_attributes(m_webView));
842 }
843
844 void ChromeClient::setCursor(const Cursor& cursor)
845 {
846     // [GTK] Widget::setCursor() gets called frequently
847     // http://bugs.webkit.org/show_bug.cgi?id=16388
848     // Setting the cursor may be an expensive operation in some backends,
849     // so don't re-set the cursor if it's already set to the target value.
850     GdkWindow* window = gtk_widget_get_window(platformPageClient());
851     if (!window)
852         return;
853
854     GdkCursor* currentCursor = gdk_window_get_cursor(window);
855     GdkCursor* newCursor = cursor.platformCursor().get();
856     if (currentCursor != newCursor)
857         gdk_window_set_cursor(window, newCursor);
858 }
859
860 void ChromeClient::setCursorHiddenUntilMouseMoves(bool)
861 {
862     notImplemented();
863 }
864
865 void ChromeClient::requestGeolocationPermissionForFrame(Frame* frame, Geolocation* geolocation)
866 {
867     WebKitWebFrame* webFrame = kit(frame);
868     GRefPtr<WebKitGeolocationPolicyDecision> policyDecision(adoptGRef(webkit_geolocation_policy_decision_new(webFrame, geolocation)));
869
870     gboolean isHandled = FALSE;
871     g_signal_emit_by_name(m_webView, "geolocation-policy-decision-requested", webFrame, policyDecision.get(), &isHandled);
872     if (!isHandled)
873         webkit_geolocation_policy_deny(policyDecision.get());
874 }
875
876 void ChromeClient::cancelGeolocationPermissionRequestForFrame(WebCore::Frame* frame, WebCore::Geolocation*)
877 {
878     g_signal_emit_by_name(m_webView, "geolocation-policy-decision-cancelled", kit(frame));
879 }
880
881 bool ChromeClient::selectItemWritingDirectionIsNatural()
882 {
883     return false;
884 }
885
886 bool ChromeClient::selectItemAlignmentFollowsMenuWritingDirection()
887 {
888     return true;
889 }
890
891 bool ChromeClient::hasOpenedPopup() const
892 {
893     notImplemented();
894     return false;
895 }
896
897 PassRefPtr<WebCore::PopupMenu> ChromeClient::createPopupMenu(WebCore::PopupMenuClient* client) const
898 {
899     return adoptRef(new PopupMenuGtk(client));
900 }
901
902 PassRefPtr<WebCore::SearchPopupMenu> ChromeClient::createSearchPopupMenu(WebCore::PopupMenuClient* client) const
903 {
904     return adoptRef(new SearchPopupMenuGtk(client));
905 }
906
907 #if ENABLE(VIDEO)
908
909 bool ChromeClient::supportsFullscreenForNode(const Node* node)
910 {
911     return node->hasTagName(HTMLNames::videoTag);
912 }
913
914 void ChromeClient::enterFullscreenForNode(Node* node)
915 {
916     webViewEnterFullscreen(m_webView, node);
917 }
918
919 void ChromeClient::exitFullscreenForNode(Node* node)
920 {
921     webViewExitFullscreen(m_webView);
922 }
923 #endif
924
925 #if ENABLE(FULLSCREEN_API)
926 bool ChromeClient::supportsFullScreenForElement(const WebCore::Element* element, bool withKeyboard)
927 {
928     return !withKeyboard;
929 }
930
931 static gboolean onFullscreenGtkKeyPressEvent(GtkWidget* widget, GdkEventKey* event, ChromeClient* chromeClient)
932 {
933     switch (event->keyval) {
934     case GDK_KEY_Escape:
935     case GDK_KEY_f:
936     case GDK_KEY_F:
937         chromeClient->cancelFullScreen();
938         return TRUE;
939     default:
940         break;
941     }
942
943     return FALSE;
944 }
945
946 void ChromeClient::cancelFullScreen()
947 {
948     ASSERT(m_fullScreenElement);
949     m_fullScreenElement->document()->webkitCancelFullScreen();
950 }
951
952 void ChromeClient::enterFullScreenForElement(WebCore::Element* element)
953 {
954     gboolean returnValue;
955     GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(element))));
956     g_signal_emit_by_name(m_webView, "entering-fullscreen", kitElement.get(), &returnValue);
957     if (returnValue)
958         return;
959
960     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
961     if (!widgetIsOnscreenToplevelWindow(window))
962         return;
963
964     g_signal_connect(window, "key-press-event", G_CALLBACK(onFullscreenGtkKeyPressEvent), this);
965
966     m_fullScreenElement = adoptRef(element);
967
968     element->document()->webkitWillEnterFullScreenForElement(element);
969     m_adjustmentWatcher.disableAllScrollbars();
970     gtk_window_fullscreen(GTK_WINDOW(window));
971     element->document()->webkitDidEnterFullScreenForElement(element);
972 }
973
974 void ChromeClient::exitFullScreenForElement(WebCore::Element* element)
975 {
976     gboolean returnValue;
977     GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(element))));
978     g_signal_emit_by_name(m_webView, "leaving-fullscreen", kitElement.get(), &returnValue);
979     if (returnValue)
980         return;
981
982     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
983     ASSERT(widgetIsOnscreenToplevelWindow(window));
984     g_signal_handlers_disconnect_by_func(window, reinterpret_cast<void*>(onFullscreenGtkKeyPressEvent), this);
985
986     element->document()->webkitWillExitFullScreenForElement(element);
987     gtk_window_unfullscreen(GTK_WINDOW(window));
988     m_adjustmentWatcher.enableAllScrollbars();
989     element->document()->webkitDidExitFullScreenForElement(element);
990     m_fullScreenElement.clear();
991 }
992 #endif
993
994 #if USE(ACCELERATED_COMPOSITING)
995 void ChromeClient::attachRootGraphicsLayer(Frame* frame, GraphicsLayer* rootLayer)
996 {
997     m_webView->priv->acceleratedCompositingContext->attachRootGraphicsLayer(rootLayer);
998 }
999
1000 void ChromeClient::setNeedsOneShotDrawingSynchronization()
1001 {
1002     m_webView->priv->acceleratedCompositingContext->markForSync();
1003 }
1004
1005 void ChromeClient::scheduleCompositingLayerSync()
1006 {
1007     m_webView->priv->acceleratedCompositingContext->markForSync();
1008 }
1009
1010 ChromeClient::CompositingTriggerFlags ChromeClient::allowedCompositingTriggers() const
1011 {
1012      if (!platformPageClient())
1013         return false;
1014 #if USE(CLUTTER)
1015     // Currently, we only support CSS 3D Transforms.
1016     return ThreeDTransformTrigger;
1017 #else
1018     return AllTriggers;
1019 #endif
1020 }
1021 #endif
1022
1023 }