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