1 // Copyright (c) 2011 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 "chrome/browser/tab_contents/tab_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/string_util.h" 14 #include "base/utf_string_conversions.h" 15 #include "build/build_config.h" 16 #include "chrome/browser/download/download_shelf.h" 17 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" 18 #include "chrome/browser/tab_contents/render_view_context_menu_gtk.h" 19 #include "chrome/browser/tab_contents/web_drag_dest_gtk.h" 20 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 21 #include "chrome/browser/ui/gtk/constrained_window_gtk.h" 22 #include "chrome/browser/ui/gtk/gtk_expanded_container.h" 23 #include "chrome/browser/ui/gtk/gtk_floating_container.h" 24 #include "chrome/browser/ui/gtk/gtk_util.h" 25 #include "chrome/browser/ui/gtk/sad_tab_gtk.h" 26 #include "chrome/browser/ui/gtk/tab_contents_drag_source.h" 27 #include "content/browser/renderer_host/render_process_host.h" 28 #include "content/browser/renderer_host/render_view_host.h" 29 #include "content/browser/renderer_host/render_view_host_factory.h" 30 #include "content/browser/tab_contents/interstitial_page.h" 31 #include "content/browser/tab_contents/tab_contents.h" 32 #include "content/browser/tab_contents/tab_contents_delegate.h" 33 #include "content/common/notification_source.h" 34 #include "content/common/notification_type.h" 35 #include "ui/gfx/point.h" 36 #include "ui/gfx/rect.h" 37 #include "ui/gfx/size.h" 38 #include "webkit/glue/webdropdata.h" 39 40 using WebKit::WebDragOperation; 41 using WebKit::WebDragOperationsMask; 42 43 namespace { 44 45 // Called when the mouse leaves the widget. We notify our delegate. 46 gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, 47 TabContents* tab_contents) { 48 if (tab_contents->delegate()) 49 tab_contents->delegate()->ContentsMouseEvent( 50 tab_contents, gfx::Point(event->x_root, event->y_root), false); 51 return FALSE; 52 } 53 54 // Called when the mouse moves within the widget. We notify our delegate. 55 gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event, 56 TabContents* tab_contents) { 57 if (tab_contents->delegate()) 58 tab_contents->delegate()->ContentsMouseEvent( 59 tab_contents, gfx::Point(event->x_root, event->y_root), true); 60 return FALSE; 61 } 62 63 // See tab_contents_view_views.cc for discussion of mouse scroll zooming. 64 gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event, 65 TabContents* tab_contents) { 66 if ((event->state & gtk_accelerator_get_default_mod_mask()) == 67 GDK_CONTROL_MASK) { 68 if (event->direction == GDK_SCROLL_DOWN) { 69 tab_contents->delegate()->ContentsZoomChange(false); 70 return TRUE; 71 } else if (event->direction == GDK_SCROLL_UP) { 72 tab_contents->delegate()->ContentsZoomChange(true); 73 return TRUE; 74 } 75 } 76 77 return FALSE; 78 } 79 80 } // namespace 81 82 // static 83 TabContentsView* TabContentsView::Create(TabContents* tab_contents) { 84 return new TabContentsViewGtk(tab_contents); 85 } 86 87 TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents) 88 : TabContentsView(tab_contents), 89 floating_(gtk_floating_container_new()), 90 expanded_(gtk_expanded_container_new()), 91 constrained_window_(NULL) { 92 gtk_widget_set_name(expanded_, "chrome-tab-contents-view"); 93 g_signal_connect(expanded_, "size-allocate", 94 G_CALLBACK(OnSizeAllocateThunk), this); 95 g_signal_connect(expanded_, "child-size-request", 96 G_CALLBACK(OnChildSizeRequestThunk), this); 97 g_signal_connect(floating_.get(), "set-floating-position", 98 G_CALLBACK(OnSetFloatingPositionThunk), this); 99 100 gtk_container_add(GTK_CONTAINER(floating_.get()), expanded_); 101 gtk_widget_show(expanded_); 102 gtk_widget_show(floating_.get()); 103 registrar_.Add(this, NotificationType::TAB_CONTENTS_CONNECTED, 104 Source<TabContents>(tab_contents)); 105 drag_source_.reset(new TabContentsDragSource(this)); 106 } 107 108 TabContentsViewGtk::~TabContentsViewGtk() { 109 floating_.Destroy(); 110 } 111 112 void TabContentsViewGtk::AttachConstrainedWindow( 113 ConstrainedWindowGtk* constrained_window) { 114 DCHECK(constrained_window_ == NULL); 115 116 constrained_window_ = constrained_window; 117 gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(floating_.get()), 118 constrained_window->widget()); 119 } 120 121 void TabContentsViewGtk::RemoveConstrainedWindow( 122 ConstrainedWindowGtk* constrained_window) { 123 DCHECK(constrained_window == constrained_window_); 124 125 constrained_window_ = NULL; 126 gtk_container_remove(GTK_CONTAINER(floating_.get()), 127 constrained_window->widget()); 128 } 129 130 void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) { 131 requested_size_ = initial_size; 132 } 133 134 RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget( 135 RenderWidgetHost* render_widget_host) { 136 if (render_widget_host->view()) { 137 // During testing, the view will already be set up in most cases to the 138 // test view, so we don't want to clobber it with a real one. To verify that 139 // this actually is happening (and somebody isn't accidentally creating the 140 // view twice), we check for the RVH Factory, which will be set when we're 141 // making special ones (which go along with the special views). 142 DCHECK(RenderViewHostFactory::has_factory()); 143 return render_widget_host->view(); 144 } 145 146 RenderWidgetHostViewGtk* view = 147 new RenderWidgetHostViewGtk(render_widget_host); 148 view->InitAsChild(); 149 gfx::NativeView content_view = view->native_view(); 150 g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this); 151 g_signal_connect(content_view, "leave-notify-event", 152 G_CALLBACK(OnLeaveNotify), tab_contents()); 153 g_signal_connect(content_view, "motion-notify-event", 154 G_CALLBACK(OnMouseMove), tab_contents()); 155 g_signal_connect(content_view, "scroll-event", 156 G_CALLBACK(OnMouseScroll), tab_contents()); 157 gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK | 158 GDK_POINTER_MOTION_MASK); 159 InsertIntoContentArea(content_view); 160 161 // Renderer target DnD. 162 drag_dest_.reset(new WebDragDestGtk(tab_contents(), content_view)); 163 164 return view; 165 } 166 167 gfx::NativeView TabContentsViewGtk::GetNativeView() const { 168 return floating_.get(); 169 } 170 171 gfx::NativeView TabContentsViewGtk::GetContentNativeView() const { 172 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 173 if (!rwhv) 174 return NULL; 175 return rwhv->GetNativeView(); 176 } 177 178 gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const { 179 GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW); 180 return window ? GTK_WINDOW(window) : NULL; 181 } 182 183 void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const { 184 // This is used for positioning the download shelf arrow animation, 185 // as well as sizing some other widgets in Windows. In GTK the size is 186 // managed for us, so it appears to be only used for the download shelf 187 // animation. 188 int x = 0; 189 int y = 0; 190 if (expanded_->window) 191 gdk_window_get_origin(expanded_->window, &x, &y); 192 out->SetRect(x + expanded_->allocation.x, y + expanded_->allocation.y, 193 requested_size_.width(), requested_size_.height()); 194 } 195 196 void TabContentsViewGtk::SetPageTitle(const std::wstring& title) { 197 // Set the window name to include the page title so it's easier to spot 198 // when debugging (e.g. via xwininfo -tree). 199 gfx::NativeView content_view = GetContentNativeView(); 200 if (content_view && content_view->window) 201 gdk_window_set_title(content_view->window, WideToUTF8(title).c_str()); 202 } 203 204 void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus status, 205 int error_code) { 206 if (tab_contents() != NULL && !sad_tab_.get()) { 207 sad_tab_.reset(new SadTabGtk( 208 tab_contents(), 209 status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ? 210 SadTabGtk::KILLED : SadTabGtk::CRASHED)); 211 InsertIntoContentArea(sad_tab_->widget()); 212 gtk_widget_show(sad_tab_->widget()); 213 } 214 } 215 216 void TabContentsViewGtk::SizeContents(const gfx::Size& size) { 217 // We don't need to manually set the size of of widgets in GTK+, but we do 218 // need to pass the sizing information on to the RWHV which will pass the 219 // sizing information on to the renderer. 220 requested_size_ = size; 221 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 222 if (rwhv) 223 rwhv->SetSize(size); 224 } 225 226 void TabContentsViewGtk::Focus() { 227 if (tab_contents()->showing_interstitial_page()) { 228 tab_contents()->interstitial_page()->Focus(); 229 } else if (!constrained_window_) { 230 GtkWidget* widget = GetContentNativeView(); 231 if (widget) 232 gtk_widget_grab_focus(widget); 233 } 234 } 235 236 void TabContentsViewGtk::SetInitialFocus() { 237 if (tab_contents()->FocusLocationBarByDefault()) 238 tab_contents()->SetFocusToLocationBar(false); 239 else 240 Focus(); 241 } 242 243 void TabContentsViewGtk::StoreFocus() { 244 focus_store_.Store(GetNativeView()); 245 } 246 247 void TabContentsViewGtk::RestoreFocus() { 248 if (focus_store_.widget()) 249 gtk_widget_grab_focus(focus_store_.widget()); 250 else 251 SetInitialFocus(); 252 } 253 254 void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const { 255 if (!floating_->window) { 256 out->SetRect(0, 0, requested_size_.width(), requested_size_.height()); 257 return; 258 } 259 int x = 0, y = 0, w, h; 260 gdk_window_get_geometry(floating_->window, &x, &y, &w, &h, NULL); 261 out->SetRect(x, y, w, h); 262 } 263 264 void TabContentsViewGtk::SetFocusedWidget(GtkWidget* widget) { 265 focus_store_.SetWidget(widget); 266 } 267 268 void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) { 269 drag_dest_->UpdateDragStatus(operation); 270 } 271 272 void TabContentsViewGtk::GotFocus() { 273 // This is only used in the views FocusManager stuff but it bleeds through 274 // all subclasses. http://crbug.com/21875 275 } 276 277 // This is called when we the renderer asks us to take focus back (i.e., it has 278 // iterated past the last focusable element on the page). 279 void TabContentsViewGtk::TakeFocus(bool reverse) { 280 if (!tab_contents()->delegate()->TakeFocus(reverse)) { 281 gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()), 282 reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD); 283 } 284 } 285 286 void TabContentsViewGtk::Observe(NotificationType type, 287 const NotificationSource& source, 288 const NotificationDetails& details) { 289 switch (type.value) { 290 case NotificationType::TAB_CONTENTS_CONNECTED: { 291 // No need to remove the SadTabGtk's widget from the container since 292 // the new RenderWidgetHostViewGtk instance already removed all the 293 // vbox's children. 294 sad_tab_.reset(); 295 break; 296 } 297 default: 298 NOTREACHED() << "Got a notification we didn't register for."; 299 break; 300 } 301 } 302 303 void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) { 304 // Find out the RenderWidgetHostView that corresponds to the render widget on 305 // which this context menu is showed, so that we can retrieve the last mouse 306 // down event on the render widget and use it as the timestamp of the 307 // activation event to show the context menu. 308 RenderWidgetHostView* view = NULL; 309 if (params.custom_context.render_widget_id != 310 webkit_glue::CustomContextMenuContext::kCurrentRenderWidget) { 311 IPC::Channel::Listener* listener = 312 tab_contents()->render_view_host()->process()->GetListenerByID( 313 params.custom_context.render_widget_id); 314 if (!listener) { 315 NOTREACHED(); 316 return; 317 } 318 view = static_cast<RenderWidgetHost*>(listener)->view(); 319 } else { 320 view = tab_contents()->GetRenderWidgetHostView(); 321 } 322 RenderWidgetHostViewGtk* view_gtk = 323 static_cast<RenderWidgetHostViewGtk*>(view); 324 if (!view_gtk || !view_gtk->last_mouse_down()) 325 return; 326 327 context_menu_.reset(new RenderViewContextMenuGtk( 328 tab_contents(), params, view_gtk->last_mouse_down()->time)); 329 context_menu_->Init(); 330 331 gfx::Rect bounds; 332 GetContainerBounds(&bounds); 333 gfx::Point point = bounds.origin(); 334 point.Offset(params.x, params.y); 335 context_menu_->Popup(point); 336 } 337 338 void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds, 339 int item_height, 340 double item_font_size, 341 int selected_item, 342 const std::vector<WebMenuItem>& items, 343 bool right_aligned) { 344 // We are not using external popup menus on Linux, they are rendered by 345 // WebKit. 346 NOTREACHED(); 347 } 348 349 // Render view DnD ------------------------------------------------------------- 350 351 void TabContentsViewGtk::StartDragging(const WebDropData& drop_data, 352 WebDragOperationsMask ops, 353 const SkBitmap& image, 354 const gfx::Point& image_offset) { 355 DCHECK(GetContentNativeView()); 356 357 RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>( 358 tab_contents()->GetRenderWidgetHostView()); 359 if (!view_gtk || !view_gtk->last_mouse_down()) 360 return; 361 362 drag_source_->StartDragging(drop_data, ops, view_gtk->last_mouse_down(), 363 image, image_offset); 364 } 365 366 // ----------------------------------------------------------------------------- 367 368 void TabContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) { 369 gtk_container_add(GTK_CONTAINER(expanded_), widget); 370 } 371 372 // Called when the content view gtk widget is tabbed to, or after the call to 373 // gtk_widget_child_focus() in TakeFocus(). We return true 374 // and grab focus if we don't have it. The call to 375 // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to 376 // webkit. 377 gboolean TabContentsViewGtk::OnFocus(GtkWidget* widget, 378 GtkDirectionType focus) { 379 // If we are showing a constrained window, don't allow the native view to take 380 // focus. 381 if (constrained_window_) { 382 // If we return false, it will revert to the default handler, which will 383 // take focus. We don't want that. But if we return true, the event will 384 // stop being propagated, leaving focus wherever it is currently. That is 385 // also bad. So we return false to let the default handler run, but take 386 // focus first so as to trick it into thinking the view was already focused 387 // and allowing the event to propagate. 388 gtk_widget_grab_focus(widget); 389 return FALSE; 390 } 391 392 // If we already have focus, let the next widget have a shot at it. We will 393 // reach this situation after the call to gtk_widget_child_focus() in 394 // TakeFocus(). 395 if (gtk_widget_is_focus(widget)) 396 return FALSE; 397 398 gtk_widget_grab_focus(widget); 399 bool reverse = focus == GTK_DIR_TAB_BACKWARD; 400 tab_contents()->FocusThroughTabTraversal(reverse); 401 return TRUE; 402 } 403 404 void TabContentsViewGtk::OnChildSizeRequest(GtkWidget* widget, 405 GtkWidget* child, 406 GtkRequisition* requisition) { 407 if (tab_contents()->delegate()) { 408 requisition->height += 409 tab_contents()->delegate()->GetExtraRenderViewHeight(); 410 } 411 } 412 413 void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget, 414 GtkAllocation* allocation) { 415 int width = allocation->width; 416 int height = allocation->height; 417 // |delegate()| can be NULL here during browser teardown. 418 if (tab_contents()->delegate()) 419 height += tab_contents()->delegate()->GetExtraRenderViewHeight(); 420 gfx::Size size(width, height); 421 requested_size_ = size; 422 423 // We manually tell our RWHV to resize the renderer content. This avoids 424 // spurious resizes from GTK+. 425 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 426 if (rwhv) 427 rwhv->SetSize(size); 428 if (tab_contents()->interstitial_page()) 429 tab_contents()->interstitial_page()->SetSize(size); 430 } 431 432 void TabContentsViewGtk::OnSetFloatingPosition( 433 GtkWidget* floating_container, GtkAllocation* allocation) { 434 if (!constrained_window_) 435 return; 436 437 // Place each ConstrainedWindow in the center of the view. 438 GtkWidget* widget = constrained_window_->widget(); 439 DCHECK(widget->parent == floating_.get()); 440 441 GtkRequisition requisition; 442 gtk_widget_size_request(widget, &requisition); 443 444 GValue value = { 0, }; 445 g_value_init(&value, G_TYPE_INT); 446 447 int child_x = std::max((allocation->width - requisition.width) / 2, 0); 448 g_value_set_int(&value, child_x); 449 gtk_container_child_set_property(GTK_CONTAINER(floating_container), 450 widget, "x", &value); 451 452 int child_y = std::max((allocation->height - requisition.height) / 2, 0); 453 g_value_set_int(&value, child_y); 454 gtk_container_child_set_property(GTK_CONTAINER(floating_container), 455 widget, "y", &value); 456 g_value_unset(&value); 457 } 458