1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/web_contents/web_contents_view_gtk.h" 6 7#include <gdk/gdk.h> 8#include <gdk/gdkkeysyms.h> 9#include <gtk/gtk.h> 10 11#include <algorithm> 12 13#include "base/strings/string_util.h" 14#include "base/strings/utf_string_conversions.h" 15#include "build/build_config.h" 16#include "content/browser/renderer_host/render_view_host_factory.h" 17#include "content/browser/renderer_host/render_view_host_impl.h" 18#include "content/browser/renderer_host/render_widget_host_view_gtk.h" 19#include "content/browser/web_contents/interstitial_page_impl.h" 20#include "content/browser/web_contents/web_contents_impl.h" 21#include "content/browser/web_contents/web_drag_dest_gtk.h" 22#include "content/browser/web_contents/web_drag_source_gtk.h" 23#include "content/public/browser/web_contents_delegate.h" 24#include "content/public/browser/web_contents_view_delegate.h" 25#include "content/public/common/drop_data.h" 26#include "ui/base/gtk/gtk_expanded_container.h" 27#include "ui/gfx/image/image_skia.h" 28#include "ui/gfx/point.h" 29#include "ui/gfx/rect.h" 30#include "ui/gfx/size.h" 31 32using WebKit::WebDragOperation; 33using WebKit::WebDragOperationsMask; 34 35namespace content { 36namespace { 37 38// Called when the mouse leaves the widget. We notify our delegate. 39gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, 40 WebContentsImpl* web_contents) { 41 if (web_contents->GetDelegate()) 42 web_contents->GetDelegate()->ContentsMouseEvent( 43 web_contents, gfx::Point(event->x_root, event->y_root), false); 44 return FALSE; 45} 46 47// Called when the mouse moves within the widget. We notify our delegate. 48gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event, 49 WebContentsImpl* web_contents) { 50 if (web_contents->GetDelegate()) 51 web_contents->GetDelegate()->ContentsMouseEvent( 52 web_contents, gfx::Point(event->x_root, event->y_root), true); 53 return FALSE; 54} 55 56// See tab_contents_view_views.cc for discussion of mouse scroll zooming. 57gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event, 58 WebContentsImpl* web_contents) { 59 if ((event->state & gtk_accelerator_get_default_mod_mask()) != 60 GDK_CONTROL_MASK) { 61 return FALSE; 62 } 63 64 WebContentsDelegate* delegate = web_contents->GetDelegate(); 65 if (!delegate) 66 return FALSE; 67 68 if (!(event->direction == GDK_SCROLL_DOWN || 69 event->direction == GDK_SCROLL_UP)) { 70 return FALSE; 71 } 72 73 delegate->ContentsZoomChange(event->direction == GDK_SCROLL_UP); 74 return TRUE; 75} 76 77} // namespace 78 79WebContentsViewPort* CreateWebContentsView( 80 WebContentsImpl* web_contents, 81 WebContentsViewDelegate* delegate, 82 RenderViewHostDelegateView** render_view_host_delegate_view) { 83 WebContentsViewGtk* rv = new WebContentsViewGtk(web_contents, delegate); 84 *render_view_host_delegate_view = rv; 85 return rv; 86} 87 88WebContentsViewGtk::WebContentsViewGtk( 89 WebContentsImpl* web_contents, 90 WebContentsViewDelegate* delegate) 91 : web_contents_(web_contents), 92 expanded_(gtk_expanded_container_new()), 93 delegate_(delegate) { 94 gtk_widget_set_name(expanded_.get(), "chrome-web-contents-view"); 95 g_signal_connect(expanded_.get(), "size-allocate", 96 G_CALLBACK(OnSizeAllocateThunk), this); 97 g_signal_connect(expanded_.get(), "child-size-request", 98 G_CALLBACK(OnChildSizeRequestThunk), this); 99 100 gtk_widget_show(expanded_.get()); 101 drag_source_.reset(new WebDragSourceGtk(web_contents)); 102 103 if (delegate_) 104 delegate_->Initialize(expanded_.get(), &focus_store_); 105} 106 107WebContentsViewGtk::~WebContentsViewGtk() { 108 expanded_.Destroy(); 109} 110 111gfx::NativeView WebContentsViewGtk::GetNativeView() const { 112 if (delegate_) 113 return delegate_->GetNativeView(); 114 115 return expanded_.get(); 116} 117 118gfx::NativeView WebContentsViewGtk::GetContentNativeView() const { 119 RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView(); 120 if (!rwhv) 121 return NULL; 122 return rwhv->GetNativeView(); 123} 124 125gfx::NativeWindow WebContentsViewGtk::GetTopLevelNativeWindow() const { 126 GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW); 127 return window ? GTK_WINDOW(window) : NULL; 128} 129 130void WebContentsViewGtk::GetContainerBounds(gfx::Rect* out) const { 131 // This is used for positioning the download shelf arrow animation, 132 // as well as sizing some other widgets in Windows. In GTK the size is 133 // managed for us, so it appears to be only used for the download shelf 134 // animation. 135 int x = 0; 136 int y = 0; 137 GdkWindow* expanded_window = gtk_widget_get_window(expanded_.get()); 138 if (expanded_window) 139 gdk_window_get_origin(expanded_window, &x, &y); 140 141 GtkAllocation allocation; 142 gtk_widget_get_allocation(expanded_.get(), &allocation); 143 out->SetRect(x + allocation.x, y + allocation.y, 144 requested_size_.width(), requested_size_.height()); 145} 146 147void WebContentsViewGtk::OnTabCrashed(base::TerminationStatus status, 148 int error_code) { 149} 150 151void WebContentsViewGtk::Focus() { 152 if (web_contents_->ShowingInterstitialPage()) { 153 web_contents_->GetInterstitialPage()->Focus(); 154 } else if (delegate_) { 155 delegate_->Focus(); 156 } 157} 158 159void WebContentsViewGtk::SetInitialFocus() { 160 if (web_contents_->FocusLocationBarByDefault()) 161 web_contents_->SetFocusToLocationBar(false); 162 else 163 Focus(); 164} 165 166void WebContentsViewGtk::StoreFocus() { 167 focus_store_.Store(GetNativeView()); 168} 169 170void WebContentsViewGtk::RestoreFocus() { 171 if (focus_store_.widget()) 172 gtk_widget_grab_focus(focus_store_.widget()); 173 else 174 SetInitialFocus(); 175} 176 177DropData* WebContentsViewGtk::GetDropData() const { 178 return drag_dest_->current_drop_data(); 179} 180 181gfx::Rect WebContentsViewGtk::GetViewBounds() const { 182 gfx::Rect rect; 183 GdkWindow* window = gtk_widget_get_window(GetNativeView()); 184 if (!window) { 185 rect.SetRect(0, 0, requested_size_.width(), requested_size_.height()); 186 return rect; 187 } 188 int x = 0, y = 0, w, h; 189 gdk_window_get_geometry(window, &x, &y, &w, &h, NULL); 190 rect.SetRect(x, y, w, h); 191 return rect; 192} 193 194void WebContentsViewGtk::CreateView( 195 const gfx::Size& initial_size, gfx::NativeView context) { 196 requested_size_ = initial_size; 197} 198 199RenderWidgetHostView* WebContentsViewGtk::CreateViewForWidget( 200 RenderWidgetHost* render_widget_host) { 201 if (render_widget_host->GetView()) { 202 // During testing, the view will already be set up in most cases to the 203 // test view, so we don't want to clobber it with a real one. To verify that 204 // this actually is happening (and somebody isn't accidentally creating the 205 // view twice), we check for the RVH Factory, which will be set when we're 206 // making special ones (which go along with the special views). 207 DCHECK(RenderViewHostFactory::has_factory()); 208 return render_widget_host->GetView(); 209 } 210 211 RenderWidgetHostView* view = 212 RenderWidgetHostView::CreateViewForWidget(render_widget_host); 213 view->InitAsChild(NULL); 214 gfx::NativeView content_view = view->GetNativeView(); 215 g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this); 216 g_signal_connect(content_view, "leave-notify-event", 217 G_CALLBACK(OnLeaveNotify), web_contents_); 218 g_signal_connect(content_view, "motion-notify-event", 219 G_CALLBACK(OnMouseMove), web_contents_); 220 g_signal_connect(content_view, "scroll-event", 221 G_CALLBACK(OnMouseScroll), web_contents_); 222 gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK | 223 GDK_POINTER_MOTION_MASK); 224 InsertIntoContentArea(content_view); 225 226 if (render_widget_host->IsRenderView()) { 227 RenderViewHost* rvh = RenderViewHost::From(render_widget_host); 228 // If |rvh| is already the current render view host for the web contents, we 229 // need to initialize |drag_dest_| for drags to be properly handled. 230 // Otherwise, |drag_dest_| will be updated in RenderViewSwappedIn. The 231 // reason we can't simply check that this isn't a swapped-out view is 232 // because there are navigations that create non-swapped-out views that may 233 // never be displayed, e.g. a navigation that becomes a download. 234 if (rvh == web_contents_->GetRenderViewHost()) { 235 UpdateDragDest(rvh); 236 } 237 } 238 239 return view; 240} 241 242RenderWidgetHostView* WebContentsViewGtk::CreateViewForPopupWidget( 243 RenderWidgetHost* render_widget_host) { 244 return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host); 245} 246 247void WebContentsViewGtk::SetPageTitle(const string16& title) { 248 // Set the window name to include the page title so it's easier to spot 249 // when debugging (e.g. via xwininfo -tree). 250 gfx::NativeView content_view = GetContentNativeView(); 251 if (content_view) { 252 GdkWindow* content_window = gtk_widget_get_window(content_view); 253 if (content_window) { 254 gdk_window_set_title(content_window, UTF16ToUTF8(title).c_str()); 255 } 256 } 257} 258 259void WebContentsViewGtk::SizeContents(const gfx::Size& size) { 260 // We don't need to manually set the size of of widgets in GTK+, but we do 261 // need to pass the sizing information on to the RWHV which will pass the 262 // sizing information on to the renderer. 263 requested_size_ = size; 264 RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView(); 265 if (rwhv) 266 rwhv->SetSize(size); 267} 268 269void WebContentsViewGtk::RenderViewCreated(RenderViewHost* host) { 270} 271 272void WebContentsViewGtk::RenderViewSwappedIn(RenderViewHost* host) { 273 UpdateDragDest(host); 274} 275 276void WebContentsViewGtk::SetOverscrollControllerEnabled(bool enabled) { 277} 278 279WebContents* WebContentsViewGtk::web_contents() { 280 return web_contents_; 281} 282 283void WebContentsViewGtk::UpdateDragCursor(WebDragOperation operation) { 284 drag_dest_->UpdateDragStatus(operation); 285} 286 287void WebContentsViewGtk::GotFocus() { 288 // This is only used in the views FocusManager stuff but it bleeds through 289 // all subclasses. http://crbug.com/21875 290} 291 292// This is called when the renderer asks us to take focus back (i.e., it has 293// iterated past the last focusable element on the page). 294void WebContentsViewGtk::TakeFocus(bool reverse) { 295 if (!web_contents_->GetDelegate()) 296 return; 297 if (!web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) && 298 GetTopLevelNativeWindow()) { 299 gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()), 300 reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD); 301 } 302} 303 304void WebContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) { 305 gtk_container_add(GTK_CONTAINER(expanded_.get()), widget); 306} 307 308void WebContentsViewGtk::UpdateDragDest(RenderViewHost* host) { 309 gfx::NativeView content_view = host->GetView()->GetNativeView(); 310 311 // If the host is already used by the drag_dest_, there's no point in deleting 312 // the old one to create an identical copy. 313 if (drag_dest_.get() && drag_dest_->widget() == content_view) 314 return; 315 316 // Clear the currently connected drag drop signals by deleting the old 317 // drag_dest_ before creating the new one. 318 drag_dest_.reset(); 319 // Create the new drag_dest_. 320 drag_dest_.reset(new WebDragDestGtk(web_contents_, content_view)); 321 322 if (delegate_) 323 drag_dest_->set_delegate(delegate_->GetDragDestDelegate()); 324} 325 326// Called when the content view gtk widget is tabbed to, or after the call to 327// gtk_widget_child_focus() in TakeFocus(). We return true 328// and grab focus if we don't have it. The call to 329// FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to 330// webkit. 331gboolean WebContentsViewGtk::OnFocus(GtkWidget* widget, 332 GtkDirectionType focus) { 333 // Give our view wrapper first chance at this event. 334 if (delegate_) { 335 gboolean return_value = FALSE; 336 if (delegate_->OnNativeViewFocusEvent(widget, focus, &return_value)) 337 return return_value; 338 } 339 340 // If we already have focus, let the next widget have a shot at it. We will 341 // reach this situation after the call to gtk_widget_child_focus() in 342 // TakeFocus(). 343 if (gtk_widget_is_focus(widget)) 344 return FALSE; 345 346 gtk_widget_grab_focus(widget); 347 bool reverse = focus == GTK_DIR_TAB_BACKWARD; 348 web_contents_->FocusThroughTabTraversal(reverse); 349 return TRUE; 350} 351 352void WebContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) { 353 if (delegate_) 354 delegate_->ShowContextMenu(params); 355 else 356 DLOG(ERROR) << "Cannot show context menus without a delegate."; 357} 358 359void WebContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds, 360 int item_height, 361 double item_font_size, 362 int selected_item, 363 const std::vector<MenuItem>& items, 364 bool right_aligned, 365 bool allow_multiple_selection) { 366 // External popup menus are only used on Mac and Android. 367 NOTIMPLEMENTED(); 368} 369 370// Render view DnD ------------------------------------------------------------- 371 372void WebContentsViewGtk::StartDragging(const DropData& drop_data, 373 WebDragOperationsMask ops, 374 const gfx::ImageSkia& image, 375 const gfx::Vector2d& image_offset, 376 const DragEventSourceInfo& event_info) { 377 DCHECK(GetContentNativeView()); 378 379 RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>( 380 web_contents_->GetRenderWidgetHostView()); 381 if (!view_gtk || !view_gtk->GetLastMouseDown() || 382 !drag_source_->StartDragging(drop_data, ops, view_gtk->GetLastMouseDown(), 383 *image.bitmap(), image_offset)) { 384 web_contents_->SystemDragEnded(); 385 } 386} 387 388// ----------------------------------------------------------------------------- 389 390void WebContentsViewGtk::OnChildSizeRequest(GtkWidget* widget, 391 GtkWidget* child, 392 GtkRequisition* requisition) { 393 if (web_contents_->GetDelegate()) { 394 requisition->height += 395 web_contents_->GetDelegate()->GetExtraRenderViewHeight(); 396 } 397} 398 399void WebContentsViewGtk::OnSizeAllocate(GtkWidget* widget, 400 GtkAllocation* allocation) { 401 int width = allocation->width; 402 int height = allocation->height; 403 // |delegate()| can be NULL here during browser teardown. 404 if (web_contents_->GetDelegate()) 405 height += web_contents_->GetDelegate()->GetExtraRenderViewHeight(); 406 gfx::Size size(width, height); 407 requested_size_ = size; 408 409 // We manually tell our RWHV to resize the renderer content. This avoids 410 // spurious resizes from GTK+. 411 RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView(); 412 if (rwhv) 413 rwhv->SetSize(size); 414 if (web_contents_->GetInterstitialPage()) 415 web_contents_->GetInterstitialPage()->SetSize(size); 416} 417 418} // namespace content 419