blob: 9b538c42c6536dd18395fc3efc3077318989d9d9 [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Holger Hans Peter Freyther
* Copyright (C) 2007, 2008 Christian Dywan <christian@imendio.com>
* Copyright (C) 2008 Nuanti Ltd.
* Copyright (C) 2008 Alp Toker <alp@atoker.com>
* Copyright (C) 2008 Gustavo Noronha Silva <gns@gnome.org>
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
* Copyright (C) 2012 Igalia S. L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "ChromeClientGtk.h"
#include "Chrome.h"
#include "Console.h"
#include "DumpRenderTreeSupportGtk.h"
#include "Editor.h"
#include "Element.h"
#include "FileChooser.h"
#include "FileIconLoader.h"
#include "FileSystem.h"
#include "FloatRect.h"
#include "FocusController.h"
#include "FrameLoadRequest.h"
#include "FrameSelection.h"
#include "FrameView.h"
#include "GtkUtilities.h"
#include "GtkVersioning.h"
#include "HTMLNames.h"
#include "HitTestResult.h"
#include "Icon.h"
#include "InspectorController.h"
#include "IntRect.h"
#include "KURL.h"
#include "NavigationAction.h"
#include "NotImplemented.h"
#include "PopupMenuClient.h"
#include "PopupMenuGtk.h"
#include "RefPtrCairo.h"
#include "SearchPopupMenuGtk.h"
#include "SecurityOrigin.h"
#include "WebKitDOMHTMLElementPrivate.h"
#include "WindowFeatures.h"
#include "webkitfilechooserrequestprivate.h"
#include "webkitgeolocationpolicydecision.h"
#include "webkitgeolocationpolicydecisionprivate.h"
#include "webkitnetworkrequest.h"
#include "webkitsecurityoriginprivate.h"
#include "webkitviewportattributesprivate.h"
#include "webkitwebframeprivate.h"
#include "webkitwebview.h"
#include "webkitwebviewprivate.h"
#include "webkitwebwindowfeaturesprivate.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <wtf/CurrentTime.h>
#include <wtf/MathExtras.h>
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>
#if ENABLE(SQL_DATABASE)
#include "DatabaseManager.h"
#endif
#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
#include "HTMLMediaElement.h"
#endif
using namespace WebCore;
namespace WebKit {
ChromeClient::ChromeClient(WebKitWebView* webView)
: m_webView(webView)
, m_adjustmentWatcher(webView)
, m_closeSoonTimer(0)
, m_displayTimer(this, &ChromeClient::paint)
, m_forcePaint(false)
, m_lastDisplayTime(0)
, m_repaintSoonSourceId(0)
{
ASSERT(m_webView);
}
void ChromeClient::chromeDestroyed()
{
if (m_closeSoonTimer)
g_source_remove(m_closeSoonTimer);
if (m_repaintSoonSourceId)
g_source_remove(m_repaintSoonSourceId);
delete this;
}
FloatRect ChromeClient::windowRect()
{
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
if (widgetIsOnscreenToplevelWindow(window)) {
gint left, top, width, height;
gtk_window_get_position(GTK_WINDOW(window), &left, &top);
gtk_window_get_size(GTK_WINDOW(window), &width, &height);
return IntRect(left, top, width, height);
}
return FloatRect();
}
void ChromeClient::setWindowRect(const FloatRect& rect)
{
IntRect intrect = IntRect(rect);
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
g_object_set(webWindowFeatures,
"x", intrect.x(),
"y", intrect.y(),
"width", intrect.width(),
"height", intrect.height(),
NULL);
gboolean autoResizeWindow;
WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
g_object_get(settings, "auto-resize-window", &autoResizeWindow, NULL);
if (!autoResizeWindow)
return;
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
if (widgetIsOnscreenToplevelWindow(window)) {
gtk_window_move(GTK_WINDOW(window), intrect.x(), intrect.y());
if (!intrect.isEmpty())
gtk_window_resize(GTK_WINDOW(window), intrect.width(), intrect.height());
}
}
static IntRect getWebViewRect(WebKitWebView* webView)
{
GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(webView), &allocation);
return IntRect(allocation.x, allocation.y, allocation.width, allocation.height);
}
FloatRect ChromeClient::pageRect()
{
return getWebViewRect(m_webView);
}
void ChromeClient::focus()
{
gtk_widget_grab_focus(GTK_WIDGET(m_webView));
}
void ChromeClient::unfocus()
{
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
if (widgetIsOnscreenToplevelWindow(window))
gtk_window_set_focus(GTK_WINDOW(window), NULL);
}
Page* ChromeClient::createWindow(Frame* frame, const FrameLoadRequest& frameLoadRequest, const WindowFeatures& coreFeatures, const NavigationAction&)
{
WebKitWebView* webView = 0;
g_signal_emit_by_name(m_webView, "create-web-view", kit(frame), &webView);
if (!webView)
return 0;
GRefPtr<WebKitWebWindowFeatures> webWindowFeatures(adoptGRef(kitNew(coreFeatures)));
g_object_set(webView, "window-features", webWindowFeatures.get(), NULL);
return core(webView);
}
void ChromeClient::show()
{
webkit_web_view_notify_ready(m_webView);
}
bool ChromeClient::canRunModal()
{
notImplemented();
return false;
}
void ChromeClient::runModal()
{
notImplemented();
}
void ChromeClient::setToolbarsVisible(bool visible)
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
g_object_set(webWindowFeatures, "toolbar-visible", visible, NULL);
}
bool ChromeClient::toolbarsVisible()
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
gboolean visible;
g_object_get(webWindowFeatures, "toolbar-visible", &visible, NULL);
return visible;
}
void ChromeClient::setStatusbarVisible(bool visible)
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
g_object_set(webWindowFeatures, "statusbar-visible", visible, NULL);
}
bool ChromeClient::statusbarVisible()
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
gboolean visible;
g_object_get(webWindowFeatures, "statusbar-visible", &visible, NULL);
return visible;
}
void ChromeClient::setScrollbarsVisible(bool visible)
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
g_object_set(webWindowFeatures, "scrollbar-visible", visible, NULL);
}
bool ChromeClient::scrollbarsVisible()
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
gboolean visible;
g_object_get(webWindowFeatures, "scrollbar-visible", &visible, NULL);
return visible;
}
void ChromeClient::setMenubarVisible(bool visible)
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
g_object_set(webWindowFeatures, "menubar-visible", visible, NULL);
}
bool ChromeClient::menubarVisible()
{
WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
gboolean visible;
g_object_get(webWindowFeatures, "menubar-visible", &visible, NULL);
return visible;
}
void ChromeClient::setResizable(bool)
{
// Ignored for now
}
static gboolean emitCloseWebViewSignalLater(WebKitWebView* view)
{
gboolean isHandled;
g_signal_emit_by_name(view, "close-web-view", &isHandled);
return FALSE;
}
void ChromeClient::closeWindowSoon()
{
// We may not have a WebView as create-web-view can return NULL.
if (!m_webView)
return;
if (m_closeSoonTimer) // Don't call close-web-view more than once.
return;
// We need to remove the parent WebView from WebViewSets here, before it actually
// closes, to make sure that JavaScript code that executes before it closes
// can't find it. Otherwise, window.open will select a closed WebView instead of
// opening a new one <rdar://problem/3572585>.
m_webView->priv->corePage->setGroupName("");
// We also need to stop the load to prevent further parsing or JavaScript execution
// after the window has torn down <rdar://problem/4161660>.
webkit_web_view_stop_loading(m_webView);
// Clients commonly destroy the web view during the close-web-view signal, but our caller
// may need to send more signals to the web view. For instance, if this happened in the
// onload handler, it will need to call FrameLoaderClient::dispatchDidHandleOnloadEvents.
// Instead of firing the close-web-view signal now, fire it after the caller finishes.
// This seems to match the Mac/Windows port behavior.
m_closeSoonTimer = g_timeout_add(0, reinterpret_cast<GSourceFunc>(emitCloseWebViewSignalLater), m_webView);
}
bool ChromeClient::canTakeFocus(FocusDirection)
{
return gtk_widget_get_can_focus(GTK_WIDGET(m_webView));
}
void ChromeClient::takeFocus(FocusDirection)
{
unfocus();
}
void ChromeClient::focusedNodeChanged(Node*)
{
}
void ChromeClient::focusedFrameChanged(Frame*)
{
}
bool ChromeClient::canRunBeforeUnloadConfirmPanel()
{
return true;
}
bool ChromeClient::runBeforeUnloadConfirmPanel(const WTF::String& message, WebCore::Frame* frame)
{
return runJavaScriptConfirm(frame, message);
}
void ChromeClient::addMessageToConsole(WebCore::MessageSource source, WebCore::MessageLevel level, const WTF::String& message, unsigned lineNumber, unsigned columnNumber, const WTF::String& sourceId)
{
gboolean retval;
g_signal_emit_by_name(m_webView, "console-message", message.utf8().data(), lineNumber, sourceId.utf8().data(), &retval);
}
void ChromeClient::runJavaScriptAlert(Frame* frame, const String& message)
{
gboolean retval;
g_signal_emit_by_name(m_webView, "script-alert", kit(frame), message.utf8().data(), &retval);
}
bool ChromeClient::runJavaScriptConfirm(Frame* frame, const String& message)
{
gboolean retval;
gboolean didConfirm;
g_signal_emit_by_name(m_webView, "script-confirm", kit(frame), message.utf8().data(), &didConfirm, &retval);
return didConfirm == TRUE;
}
bool ChromeClient::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result)
{
gboolean retval;
gchar* value = 0;
g_signal_emit_by_name(m_webView, "script-prompt", kit(frame), message.utf8().data(), defaultValue.utf8().data(), &value, &retval);
if (value) {
result = String::fromUTF8(value);
g_free(value);
return true;
}
return false;
}
void ChromeClient::setStatusbarText(const String& string)
{
CString stringMessage = string.utf8();
g_signal_emit_by_name(m_webView, "status-bar-text-changed", stringMessage.data());
}
bool ChromeClient::shouldInterruptJavaScript()
{
notImplemented();
return false;
}
KeyboardUIMode ChromeClient::keyboardUIMode()
{
bool tabsToLinks = true;
if (DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled())
tabsToLinks = DumpRenderTreeSupportGtk::linksIncludedInFocusChain();
return tabsToLinks ? KeyboardAccessTabsToLinks : KeyboardAccessDefault;
}
IntRect ChromeClient::windowResizerRect() const
{
notImplemented();
return IntRect();
}
static gboolean repaintEverythingSoonTimeout(ChromeClient* client)
{
client->paint(0);
return FALSE;
}
static void clipOutOldWidgetArea(cairo_t* cr, const IntSize& oldSize, const IntSize& newSize)
{
cairo_move_to(cr, oldSize.width(), 0);
cairo_line_to(cr, newSize.width(), 0);
cairo_line_to(cr, newSize.width(), newSize.height());
cairo_line_to(cr, 0, newSize.height());
cairo_line_to(cr, 0, oldSize.height());
cairo_line_to(cr, oldSize.width(), oldSize.height());
cairo_close_path(cr);
cairo_clip(cr);
}
static void clearEverywhereInBackingStore(WebKitWebView* webView, cairo_t* cr)
{
// The strategy here is to quickly draw white into this new canvas, so that
// when a user quickly resizes the WebView in an environment that has opaque
// resizing (like Gnome Shell), there are no drawing artifacts.
if (!webView->priv->transparent) {
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
} else
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
}
void ChromeClient::widgetSizeChanged(const IntSize& oldWidgetSize, IntSize newSize)
{
#if USE(ACCELERATED_COMPOSITING)
AcceleratedCompositingContext* compositingContext = m_webView->priv->acceleratedCompositingContext.get();
if (compositingContext->enabled()) {
m_webView->priv->acceleratedCompositingContext->resizeRootLayer(newSize);
return;
}
#endif
// Grow the backing store by at least 1.5 times the current size. This prevents
// lots of unnecessary allocations during an opaque resize.
WidgetBackingStore* backingStore = m_webView->priv->backingStore.get();
if (backingStore && oldWidgetSize == newSize)
return;
if (backingStore) {
const IntSize& oldSize = backingStore->size();
if (newSize.width() > oldSize.width())
newSize.setWidth(std::max(newSize.width(), static_cast<int>(oldSize.width() * 1.5)));
if (newSize.height() > oldSize.height())
newSize.setHeight(std::max(newSize.height(), static_cast<int>(oldSize.height() * 1.5)));
}
// If we did not have a backing store before or if the backing store is growing, we need
// to reallocate a new one and set it up so that we don't see artifacts while resizing.
if (!backingStore
|| newSize.width() > backingStore->size().width()
|| newSize.height() > backingStore->size().height()) {
OwnPtr<WidgetBackingStore> newBackingStore =
WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), newSize);
RefPtr<cairo_t> cr = adoptRef(cairo_create(newBackingStore->cairoSurface()));
clearEverywhereInBackingStore(m_webView, cr.get());
// Now we copy the old backing store image over the new cleared surface to prevent
// annoying flashing as the widget grows. We do the "real" paint in a timeout
// since we don't want to block resizing too long.
if (backingStore) {
cairo_set_source_surface(cr.get(), backingStore->cairoSurface(), 0, 0);
cairo_rectangle(cr.get(), 0, 0, backingStore->size().width(), backingStore->size().height());
cairo_fill(cr.get());
}
m_webView->priv->backingStore = newBackingStore.release();
backingStore = m_webView->priv->backingStore.get();
} else if (oldWidgetSize.width() < newSize.width() || oldWidgetSize.height() < newSize.height()) {
// The widget is growing, but we did not need to create a new backing store.
// We should clear any old data outside of the old widget region.
RefPtr<cairo_t> cr = adoptRef(cairo_create(backingStore->cairoSurface()));
clipOutOldWidgetArea(cr.get(), oldWidgetSize, newSize);
clearEverywhereInBackingStore(m_webView, cr.get());
}
// We need to force a redraw and ignore the framerate cap.
m_lastDisplayTime = 0;
m_dirtyRegion.unite(IntRect(IntPoint(), backingStore->size()));
// WebCore timers by default have a lower priority which leads to more artifacts when opaque
// resize is on, thus we use g_timeout_add here to force a higher timeout priority.
if (!m_repaintSoonSourceId)
m_repaintSoonSourceId = g_timeout_add(0, reinterpret_cast<GSourceFunc>(repaintEverythingSoonTimeout), this);
}
static void coalesceRectsIfPossible(const IntRect& clipRect, Vector<IntRect>& rects)
{
const unsigned int cRectThreshold = 10;
const float cWastedSpaceThreshold = 0.75f;
bool useUnionedRect = (rects.size() <= 1) || (rects.size() > cRectThreshold);
if (!useUnionedRect) {
// Attempt to guess whether or not we should use the unioned rect or the individual rects.
// We do this by computing the percentage of "wasted space" in the union. If that wasted space
// is too large, then we will do individual rect painting instead.
float unionPixels = (clipRect.width() * clipRect.height());
float singlePixels = 0;
for (size_t i = 0; i < rects.size(); ++i)
singlePixels += rects[i].width() * rects[i].height();
float wastedSpace = 1 - (singlePixels / unionPixels);
if (wastedSpace <= cWastedSpaceThreshold)
useUnionedRect = true;
}
if (!useUnionedRect)
return;
rects.clear();
rects.append(clipRect);
}
static void paintWebView(WebKitWebView* webView, Frame* frame, Region dirtyRegion)
{
if (!webView->priv->backingStore)
return;
Vector<IntRect> rects = dirtyRegion.rects();
coalesceRectsIfPossible(dirtyRegion.bounds(), rects);
RefPtr<cairo_t> backingStoreContext = adoptRef(cairo_create(webView->priv->backingStore->cairoSurface()));
GraphicsContext gc(backingStoreContext.get());
gc.applyDeviceScaleFactor(frame->page()->deviceScaleFactor());
for (size_t i = 0; i < rects.size(); i++) {
const IntRect& rect = rects[i];
gc.save();
gc.clip(rect);
if (webView->priv->transparent)
gc.clearRect(rect);
frame->view()->paint(&gc, rect);
gc.restore();
}
gc.save();
gc.clip(dirtyRegion.bounds());
frame->page()->inspectorController()->drawHighlight(gc);
gc.restore();
}
void ChromeClient::performAllPendingScrolls()
{
if (!m_webView->priv->backingStore)
return;
// Scroll all pending scroll rects and invalidate those parts of the widget.
for (size_t i = 0; i < m_rectsToScroll.size(); i++) {
IntRect& scrollRect = m_rectsToScroll[i];
m_webView->priv->backingStore->scroll(scrollRect, m_scrollOffsets[i]);
gtk_widget_queue_draw_area(GTK_WIDGET(m_webView), scrollRect.x(), scrollRect.y(), scrollRect.width(), scrollRect.height());
}
m_rectsToScroll.clear();
m_scrollOffsets.clear();
}
void ChromeClient::paint(WebCore::Timer<ChromeClient>*)
{
static const double minimumFrameInterval = 1.0 / 60.0; // No more than 60 frames a second.
double timeSinceLastDisplay = currentTime() - m_lastDisplayTime;
double timeUntilNextDisplay = minimumFrameInterval - timeSinceLastDisplay;
if (timeUntilNextDisplay > 0 && !m_forcePaint) {
m_displayTimer.startOneShot(timeUntilNextDisplay);
return;
}
Frame* frame = core(m_webView)->mainFrame();
if (!frame || !frame->contentRenderer() || !frame->view())
return;
frame->view()->updateLayoutAndStyleIfNeededRecursive();
performAllPendingScrolls();
paintWebView(m_webView, frame, m_dirtyRegion);
HashSet<GtkWidget*> children = m_webView->priv->children;
HashSet<GtkWidget*>::const_iterator end = children.end();
for (HashSet<GtkWidget*>::const_iterator current = children.begin(); current != end; ++current) {
if (static_cast<GtkAllocation*>(g_object_get_data(G_OBJECT(*current), "delayed-allocation"))) {
gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_webView));
break;
}
}
const IntRect& rect = m_dirtyRegion.bounds();
gtk_widget_queue_draw_area(GTK_WIDGET(m_webView), rect.x(), rect.y(), rect.width(), rect.height());
m_dirtyRegion = Region();
m_lastDisplayTime = currentTime();
m_repaintSoonSourceId = 0;
// We update the IM context window location here, because we want it to be
// synced with cursor movement. For instance, a text field can move without
// the selection changing.
Frame* focusedFrame = core(m_webView)->focusController()->focusedOrMainFrame();
if (focusedFrame && focusedFrame->editor().canEdit())
m_webView->priv->imFilter.setCursorRect(frame->selection()->absoluteCaretBounds());
}
void ChromeClient::forcePaint()
{
#if USE(ACCELERATED_COMPOSITING)
if (m_webView->priv->acceleratedCompositingContext->enabled())
return;
#endif
m_forcePaint = true;
paint(0);
m_forcePaint = false;
}
void ChromeClient::invalidateRootView(const IntRect&, bool immediate)
{
}
void ChromeClient::invalidateContentsAndRootView(const IntRect& updateRect, bool immediate)
{
#if USE(ACCELERATED_COMPOSITING)
AcceleratedCompositingContext* acContext = m_webView->priv->acceleratedCompositingContext.get();
if (acContext->enabled()) {
acContext->setNonCompositedContentsNeedDisplay(updateRect);
return;
}
#endif
if (updateRect.isEmpty())
return;
m_dirtyRegion.unite(updateRect);
m_displayTimer.startOneShot(0);
}
void ChromeClient::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate)
{
m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
#if USE(ACCELERATED_COMPOSITING)
AcceleratedCompositingContext* acContext = m_webView->priv->acceleratedCompositingContext.get();
if (acContext->enabled()) {
acContext->setNonCompositedContentsNeedDisplay(updateRect);
return;
}
#endif
invalidateContentsAndRootView(updateRect, immediate);
}
void ChromeClient::scroll(const IntSize& delta, const IntRect& rectToScroll, const IntRect& clipRect)
{
m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
#if USE(ACCELERATED_COMPOSITING)
AcceleratedCompositingContext* compositingContext = m_webView->priv->acceleratedCompositingContext.get();
if (compositingContext->enabled()) {
ASSERT(!rectToScroll.isEmpty());
ASSERT(delta.width() || delta.height());
compositingContext->scrollNonCompositedContents(rectToScroll, delta);
return;
}
#endif
m_rectsToScroll.append(rectToScroll);
m_scrollOffsets.append(delta);
// The code to calculate the scroll repaint region is originally from WebKit2.
// Get the part of the dirty region that is in the scroll rect.
Region dirtyRegionInScrollRect = intersect(rectToScroll, m_dirtyRegion);
if (!dirtyRegionInScrollRect.isEmpty()) {
// There are parts of the dirty region that are inside the scroll rect.
// We need to subtract them from the region, move them and re-add them.
m_dirtyRegion.subtract(rectToScroll);
// Move the dirty parts.
Region movedDirtyRegionInScrollRect = intersect(translate(dirtyRegionInScrollRect, delta), rectToScroll);
// And add them back.
m_dirtyRegion.unite(movedDirtyRegionInScrollRect);
}
// Compute the scroll repaint region. We ensure that we are not subtracting areas
// that we've scrolled from outside the viewport from the repaint region.
IntRect onScreenScrollRect = rectToScroll;
onScreenScrollRect.intersect(IntRect(IntPoint(), enclosingIntRect(pageRect()).size()));
Region scrollRepaintRegion = subtract(rectToScroll, translate(onScreenScrollRect, delta));
m_dirtyRegion.unite(scrollRepaintRegion);
m_displayTimer.startOneShot(0);
}
IntRect ChromeClient::rootViewToScreen(const IntRect& rect) const
{
return IntRect(convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), rect.location()), rect.size());
}
IntPoint ChromeClient::screenToRootView(const IntPoint& point) const
{
IntPoint widgetPositionOnScreen = convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), IntPoint());
IntPoint result(point);
result.move(-widgetPositionOnScreen.x(), -widgetPositionOnScreen.y());
return result;
}
PlatformPageClient ChromeClient::platformPageClient() const
{
return GTK_WIDGET(m_webView);
}
void ChromeClient::contentsSizeChanged(Frame* frame, const IntSize& size) const
{
if (m_adjustmentWatcher.scrollbarsDisabled())
return;
// We need to queue a resize request only if the size changed,
// otherwise we get into an infinite loop!
GtkWidget* widget = GTK_WIDGET(m_webView);
GtkRequisition requisition;
gtk_widget_get_requisition(widget, &requisition);
if (gtk_widget_get_realized(widget)
&& (requisition.height != size.height())
|| (requisition.width != size.width()))
gtk_widget_queue_resize_no_redraw(widget);
// If this was a main frame size change, update the scrollbars.
if (frame != frame->page()->mainFrame())
return;
m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
}
void ChromeClient::scrollbarsModeDidChange() const
{
WebKitWebFrame* webFrame = webkit_web_view_get_main_frame(m_webView);
if (!webFrame)
return;
g_object_notify(G_OBJECT(webFrame), "horizontal-scrollbar-policy");
g_object_notify(G_OBJECT(webFrame), "vertical-scrollbar-policy");
gboolean isHandled;
g_signal_emit_by_name(webFrame, "scrollbars-policy-changed", &isHandled);
if (isHandled)
return;
GtkWidget* parent = gtk_widget_get_parent(GTK_WIDGET(m_webView));
if (!parent || !GTK_IS_SCROLLED_WINDOW(parent))
return;
GtkPolicyType horizontalPolicy = webkit_web_frame_get_horizontal_scrollbar_policy(webFrame);
GtkPolicyType verticalPolicy = webkit_web_frame_get_vertical_scrollbar_policy(webFrame);
// ScrolledWindow doesn't like to display only part of a widget if
// the scrollbars are completely disabled; We have a disparity
// here on what the policy requested by the web app is and what we
// can represent; the idea is not to show scrollbars, only.
if (horizontalPolicy == GTK_POLICY_NEVER)
horizontalPolicy = GTK_POLICY_AUTOMATIC;
if (verticalPolicy == GTK_POLICY_NEVER)
verticalPolicy = GTK_POLICY_AUTOMATIC;
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(parent),
horizontalPolicy, verticalPolicy);
}
void ChromeClient::mouseDidMoveOverElement(const HitTestResult& hit, unsigned modifierFlags)
{
// check if the element is a link...
bool isLink = hit.isLiveLink();
if (isLink) {
KURL url = hit.absoluteLinkURL();
if (!url.isEmpty() && url != m_hoveredLinkURL) {
TextDirection dir;
CString titleString = hit.title(dir).utf8();
CString urlString = url.string().utf8();
g_signal_emit_by_name(m_webView, "hovering-over-link", titleString.data(), urlString.data());
m_hoveredLinkURL = url;
}
} else if (!isLink && !m_hoveredLinkURL.isEmpty()) {
g_signal_emit_by_name(m_webView, "hovering-over-link", 0, 0);
m_hoveredLinkURL = KURL();
}
if (Node* node = hit.innerNonSharedNode()) {
Frame* frame = node->document()->frame();
FrameView* view = frame ? frame->view() : 0;
m_webView->priv->tooltipArea = view ? view->contentsToWindow(node->pixelSnappedBoundingBox()) : IntRect();
} else
m_webView->priv->tooltipArea = IntRect();
}
void ChromeClient::setToolTip(const String& toolTip, TextDirection)
{
webkit_web_view_set_tooltip_text(m_webView, toolTip.utf8().data());
}
void ChromeClient::print(Frame* frame)
{
WebKitWebFrame* webFrame = kit(frame);
gboolean isHandled = false;
g_signal_emit_by_name(m_webView, "print-requested", webFrame, &isHandled);
if (isHandled)
return;
webkit_web_frame_print(webFrame);
}
#if ENABLE(SQL_DATABASE)
void ChromeClient::exceededDatabaseQuota(Frame* frame, const String& databaseName, DatabaseDetails)
{
guint64 defaultQuota = webkit_get_default_web_database_quota();
DatabaseManager::manager().setQuota(frame->document()->securityOrigin(), defaultQuota);
WebKitWebFrame* webFrame = kit(frame);
WebKitSecurityOrigin* origin = webkit_web_frame_get_security_origin(webFrame);
WebKitWebDatabase* webDatabase = webkit_security_origin_get_web_database(origin, databaseName.utf8().data());
g_signal_emit_by_name(m_webView, "database-quota-exceeded", webFrame, webDatabase);
}
#endif
void ChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
{
// FIXME: Free some space.
notImplemented();
}
void ChromeClient::reachedApplicationCacheOriginQuota(SecurityOrigin*, int64_t)
{
notImplemented();
}
void ChromeClient::runOpenPanel(Frame*, PassRefPtr<FileChooser> prpFileChooser)
{
GRefPtr<WebKitFileChooserRequest> request = adoptGRef(webkit_file_chooser_request_create(prpFileChooser));
webkitWebViewRunFileChooserRequest(m_webView, request.get());
}
void ChromeClient::loadIconForFiles(const Vector<WTF::String>& filenames, WebCore::FileIconLoader* loader)
{
loader->notifyFinished(Icon::createIconForFiles(filenames));
}
void ChromeClient::dispatchViewportPropertiesDidChange(const ViewportArguments& arguments) const
{
// Recompute the viewport attributes making it valid.
webkitViewportAttributesRecompute(webkit_web_view_get_viewport_attributes(m_webView));
}
void ChromeClient::setCursor(const Cursor& cursor)
{
// [GTK] Widget::setCursor() gets called frequently
// http://bugs.webkit.org/show_bug.cgi?id=16388
// Setting the cursor may be an expensive operation in some backends,
// so don't re-set the cursor if it's already set to the target value.
GdkWindow* window = gtk_widget_get_window(platformPageClient());
if (!window)
return;
GdkCursor* currentCursor = gdk_window_get_cursor(window);
GdkCursor* newCursor = cursor.platformCursor().get();
if (currentCursor != newCursor)
gdk_window_set_cursor(window, newCursor);
}
void ChromeClient::setCursorHiddenUntilMouseMoves(bool)
{
notImplemented();
}
bool ChromeClient::selectItemWritingDirectionIsNatural()
{
return false;
}
bool ChromeClient::selectItemAlignmentFollowsMenuWritingDirection()
{
return true;
}
bool ChromeClient::hasOpenedPopup() const
{
notImplemented();
return false;
}
PassRefPtr<WebCore::PopupMenu> ChromeClient::createPopupMenu(WebCore::PopupMenuClient* client) const
{
return adoptRef(new PopupMenuGtk(client));
}
PassRefPtr<WebCore::SearchPopupMenu> ChromeClient::createSearchPopupMenu(WebCore::PopupMenuClient* client) const
{
return adoptRef(new SearchPopupMenuGtk(client));
}
#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
bool ChromeClient::supportsFullscreenForNode(const Node* node)
{
return node->hasTagName(HTMLNames::videoTag);
}
void ChromeClient::enterFullscreenForNode(Node* node)
{
if (!node)
return;
HTMLElement* element = static_cast<HTMLElement*>(node);
if (element && element->isMediaElement()) {
HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element);
if (mediaElement->player() && mediaElement->player()->canEnterFullscreen())
mediaElement->player()->enterFullscreen();
}
}
void ChromeClient::exitFullscreenForNode(Node* node)
{
if (!node)
return;
HTMLElement* element = static_cast<HTMLElement*>(node);
if (element && element->isMediaElement()) {
HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element);
if (mediaElement->player())
mediaElement->player()->exitFullscreen();
}
}
#endif
#if ENABLE(FULLSCREEN_API)
bool ChromeClient::supportsFullScreenForElement(const WebCore::Element* element, bool withKeyboard)
{
return !withKeyboard;
}
static gboolean onFullscreenGtkKeyPressEvent(GtkWidget* widget, GdkEventKey* event, ChromeClient* chromeClient)
{
switch (event->keyval) {
case GDK_KEY_Escape:
case GDK_KEY_f:
case GDK_KEY_F:
chromeClient->cancelFullScreen();
return TRUE;
default:
break;
}
return FALSE;
}
void ChromeClient::cancelFullScreen()
{
ASSERT(m_fullScreenElement);
m_fullScreenElement->document()->webkitCancelFullScreen();
}
void ChromeClient::enterFullScreenForElement(WebCore::Element* element)
{
gboolean returnValue;
GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(element))));
g_signal_emit_by_name(m_webView, "entering-fullscreen", kitElement.get(), &returnValue);
if (returnValue)
return;
#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
if (element && element->isMediaElement()) {
HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element);
if (mediaElement->player() && mediaElement->player()->canEnterFullscreen()) {
element->document()->webkitWillEnterFullScreenForElement(element);
mediaElement->player()->enterFullscreen();
m_fullScreenElement = element;
element->document()->webkitDidEnterFullScreenForElement(element);
}
return;
}
#endif
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
if (!widgetIsOnscreenToplevelWindow(window))
return;
g_signal_connect(window, "key-press-event", G_CALLBACK(onFullscreenGtkKeyPressEvent), this);
m_fullScreenElement = element;
element->document()->webkitWillEnterFullScreenForElement(element);
m_adjustmentWatcher.disableAllScrollbars();
gtk_window_fullscreen(GTK_WINDOW(window));
element->document()->webkitDidEnterFullScreenForElement(element);
}
void ChromeClient::exitFullScreenForElement(WebCore::Element*)
{
// The element passed into this function is not reliable, i.e. it could
// be null. In addition the parameter may be disappearing in the future.
// So we use the reference to the element we saved above.
ASSERT(m_fullScreenElement);
gboolean returnValue;
GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(m_fullScreenElement.get()))));
g_signal_emit_by_name(m_webView, "leaving-fullscreen", kitElement.get(), &returnValue);
if (returnValue)
return;
#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
if (m_fullScreenElement && m_fullScreenElement->isMediaElement()) {
m_fullScreenElement->document()->webkitWillExitFullScreenForElement(m_fullScreenElement.get());
HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(m_fullScreenElement.get());
if (mediaElement->player()) {
mediaElement->player()->exitFullscreen();
m_fullScreenElement->document()->webkitDidExitFullScreenForElement(m_fullScreenElement.get());
m_fullScreenElement.clear();
}
return;
}
#endif
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
ASSERT(widgetIsOnscreenToplevelWindow(window));
g_signal_handlers_disconnect_by_func(window, reinterpret_cast<void*>(onFullscreenGtkKeyPressEvent), this);
m_fullScreenElement->document()->webkitWillExitFullScreenForElement(m_fullScreenElement.get());
gtk_window_unfullscreen(GTK_WINDOW(window));
m_adjustmentWatcher.enableAllScrollbars();
m_fullScreenElement->document()->webkitDidExitFullScreenForElement(m_fullScreenElement.get());
m_fullScreenElement.clear();
}
#endif
#if USE(ACCELERATED_COMPOSITING)
void ChromeClient::attachRootGraphicsLayer(Frame* frame, GraphicsLayer* rootLayer)
{
AcceleratedCompositingContext* context = m_webView->priv->acceleratedCompositingContext.get();
bool turningOffCompositing = !rootLayer && context->enabled();
bool turningOnCompositing = rootLayer && !context->enabled();
context->setRootCompositingLayer(rootLayer);
if (turningOnCompositing) {
m_displayTimer.stop();
m_webView->priv->backingStore = WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), IntSize(1, 1));
}
if (turningOffCompositing) {
m_webView->priv->backingStore = WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), getWebViewRect(m_webView).size());
RefPtr<cairo_t> cr = adoptRef(cairo_create(m_webView->priv->backingStore->cairoSurface()));
clearEverywhereInBackingStore(m_webView, cr.get());
}
}
void ChromeClient::setNeedsOneShotDrawingSynchronization()
{
m_webView->priv->acceleratedCompositingContext->scheduleLayerFlush();
}
void ChromeClient::scheduleCompositingLayerFlush()
{
m_webView->priv->acceleratedCompositingContext->scheduleLayerFlush();
}
ChromeClient::CompositingTriggerFlags ChromeClient::allowedCompositingTriggers() const
{
if (!platformPageClient())
return false;
#if USE(CLUTTER)
// Currently, we only support CSS 3D Transforms.
return ThreeDTransformTrigger | AnimationTrigger;
#else
return AllTriggers;
#endif
}
#endif
}